diff options
283 files changed, 6576 insertions, 2635 deletions
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000000..e5532d381b --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,163 @@ +name: Coverity + +# This GitHub workflow automates submitting builds to Coverity Scan. To enable it, +# set the repository variable `ENABLE_COVERITY_SCAN_FOR_BRANCHES` (for details, see +# https://docs.github.com/en/actions/learn-github-actions/variables) to a JSON +# string array containing the names of the branches for which the workflow should be +# run, e.g. `["main", "next"]`. +# +# In addition, two repository secrets must be set (for details how to add secrets, see +# https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions): +# `COVERITY_SCAN_EMAIL` and `COVERITY_SCAN_TOKEN`. The former specifies the +# email to which the Coverity reports should be sent and the latter can be +# obtained from the Project Settings tab of the Coverity project). +# +# The workflow runs on `ubuntu-latest` by default. This can be overridden by setting +# the repository variable `ENABLE_COVERITY_SCAN_ON_OS` to a JSON string array specifying +# the operating systems, e.g. `["ubuntu-latest", "windows-latest"]`. +# +# By default, the builds are submitted to the Coverity project `git`. To override this, +# set the repository variable `COVERITY_PROJECT`. + +on: + push: + +defaults: + run: + shell: bash + +jobs: + coverity: + if: contains(fromJSON(vars.ENABLE_COVERITY_SCAN_FOR_BRANCHES || '[""]'), github.ref_name) + strategy: + matrix: + os: ${{ fromJSON(vars.ENABLE_COVERITY_SCAN_ON_OS || '["ubuntu-latest"]') }} + runs-on: ${{ matrix.os }} + env: + COVERITY_PROJECT: ${{ vars.COVERITY_PROJECT || 'git' }} + COVERITY_LANGUAGE: cxx + COVERITY_PLATFORM: overridden-below + steps: + - uses: actions/checkout@v3 + - name: install minimal Git for Windows SDK + if: contains(matrix.os, 'windows') + uses: git-for-windows/setup-git-for-windows-sdk@v1 + - run: ci/install-dependencies.sh + if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos') + env: + runs_on_pool: ${{ 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. + - name: get the Coverity Build Tool hash + id: lookup + run: | + case "${{ matrix.os }}" in + *windows*) + COVERITY_PLATFORM=win64 + COVERITY_TOOL_FILENAME=cov-analysis.zip + MAKEFLAGS=-j$(nproc) + ;; + *macos*) + COVERITY_PLATFORM=macOSX + COVERITY_TOOL_FILENAME=cov-analysis.dmg + MAKEFLAGS=-j$(sysctl -n hw.physicalcpu) + ;; + *ubuntu*) + COVERITY_PLATFORM=linux64 + COVERITY_TOOL_FILENAME=cov-analysis.tgz + MAKEFLAGS=-j$(nproc) + ;; + *) + echo '::error::unhandled OS ${{ matrix.os }}' >&2 + exit 1 + ;; + esac + echo "COVERITY_PLATFORM=$COVERITY_PLATFORM" >>$GITHUB_ENV + echo "COVERITY_TOOL_FILENAME=$COVERITY_TOOL_FILENAME" >>$GITHUB_ENV + echo "MAKEFLAGS=$MAKEFLAGS" >>$GITHUB_ENV + MD5=$(curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \ + --fail \ + --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \ + --form project="$COVERITY_PROJECT" \ + --form md5=1) + case $? in + 0) ;; # okay + 22) # 40x, i.e. access denied + echo "::error::incorrect token or project?" >&2 + exit 1 + ;; + *) # other error + echo "::error::Failed to retrieve MD5" >&2 + exit 1 + ;; + esac + echo "hash=$MD5" >>$GITHUB_OUTPUT + + # Try to cache the tool to avoid downloading 1GB+ on every run. + # A cache miss will add ~30s to create, but a cache hit will save minutes. + - name: restore the Coverity Build Tool + id: cache + uses: actions/cache/restore@v3 + with: + path: ${{ runner.temp }}/cov-analysis + key: cov-build-${{ env.COVERITY_LANGUAGE }}-${{ env.COVERITY_PLATFORM }}-${{ steps.lookup.outputs.hash }} + - name: download the Coverity Build Tool (${{ env.COVERITY_LANGUAGE }} / ${{ env.COVERITY_PLATFORM}}) + if: steps.cache.outputs.cache-hit != 'true' + run: | + curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \ + --fail --no-progress-meter \ + --output $RUNNER_TEMP/$COVERITY_TOOL_FILENAME \ + --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \ + --form project="$COVERITY_PROJECT" + - name: extract the Coverity Build Tool + if: steps.cache.outputs.cache-hit != 'true' + run: | + case "$COVERITY_TOOL_FILENAME" in + *.tgz) + mkdir $RUNNER_TEMP/cov-analysis && + tar -xzf $RUNNER_TEMP/$COVERITY_TOOL_FILENAME --strip 1 -C $RUNNER_TEMP/cov-analysis + ;; + *.dmg) + cd $RUNNER_TEMP && + attach="$(hdiutil attach $COVERITY_TOOL_FILENAME)" && + volume="$(echo "$attach" | cut -f 3 | grep /Volumes/)" && + mkdir cov-analysis && + cd cov-analysis && + sh "$volume"/cov-analysis-macosx-*.sh && + ls -l && + hdiutil detach "$volume" + ;; + *.zip) + cd $RUNNER_TEMP && + mkdir cov-analysis-tmp && + unzip -d cov-analysis-tmp $COVERITY_TOOL_FILENAME && + mv cov-analysis-tmp/* cov-analysis + ;; + *) + echo "::error::unhandled archive type: $COVERITY_TOOL_FILENAME" >&2 + exit 1 + ;; + esac + - name: cache the Coverity Build Tool + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v3 + with: + path: ${{ runner.temp }}/cov-analysis + key: cov-build-${{ env.COVERITY_LANGUAGE }}-${{ env.COVERITY_PLATFORM }}-${{ steps.lookup.outputs.hash }} + - name: build with cov-build + run: | + export PATH="$RUNNER_TEMP/cov-analysis/bin:$PATH" && + cov-configure --gcc && + cov-build --dir cov-int make + - name: package the build + run: tar -czvf cov-int.tgz cov-int + - name: submit the build to Coverity Scan + run: | + curl \ + --fail \ + --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \ + --form email='${{ secrets.COVERITY_SCAN_EMAIL }}' \ + --form file=@cov-int.tgz \ + --form version='${{ github.sha }}' \ + "https://scan.coverity.com/builds?project=$COVERITY_PROJECT" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 079645b776..dcf7d78f1d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,9 +5,23 @@ on: [push, pull_request] env: DEVELOPER: 1 +# If more than one workflow run is triggered for the very same commit hash +# (which happens when multiple branches pointing to the same commit), only +# the first one is allowed to run, the second will be kept in the "queued" +# state. This allows a successful completion of the first run to be reused +# in the second run via the `skip-if-redundant` logic in the `config` job. +# +# The only caveat is that if a workflow run is triggered for the same commit +# hash that another run is already being held, that latter run will be +# canceled. For more details about the `concurrency` attribute, see: +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency +concurrency: + group: ${{ github.sha }} + jobs: ci-config: name: config + if: vars.CI_BRANCHES == '' || contains(vars.CI_BRANCHES, github.ref_name) runs-on: ubuntu-latest outputs: enabled: ${{ steps.check-ref.outputs.enabled }}${{ steps.skip-if-redundant.outputs.enabled }} @@ -30,10 +44,13 @@ jobs: name: check whether CI is enabled for ref run: | enabled=yes - if test -x config-repo/ci/config/allow-ref && - ! config-repo/ci/config/allow-ref '${{ github.ref }}' + if test -x config-repo/ci/config/allow-ref then - enabled=no + echo "::warning::ci/config/allow-ref is deprecated; use CI_BRANCHES instead" + if ! config-repo/ci/config/allow-ref '${{ github.ref }}' + then + enabled=no + fi fi skip_concurrent=yes @@ -59,9 +59,9 @@ David Reiss <dreiss@facebook.com> <dreiss@dreiss-vmware.(none)> David S. Miller <davem@davemloft.net> David Turner <novalis@novalis.org> <dturner@twopensource.com> David Turner <novalis@novalis.org> <dturner@twosigma.com> -Derrick Stolee <derrickstolee@github.com> <stolee@gmail.com> -Derrick Stolee <derrickstolee@github.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com> -Derrick Stolee <derrickstolee@github.com> <dstolee@microsoft.com> +Derrick Stolee <stolee@gmail.com> <derrickstolee@github.com> +Derrick Stolee <stolee@gmail.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com> +Derrick Stolee <stolee@gmail.com> <dstolee@microsoft.com> Deskin Miller <deskinm@umich.edu> Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh Dirk Süsserott <newsletter@dirk.my1.cc> diff --git a/Documentation/RelNotes/2.43.0.txt b/Documentation/RelNotes/2.43.0.txt new file mode 100644 index 0000000000..6abdf8bf65 --- /dev/null +++ b/Documentation/RelNotes/2.43.0.txt @@ -0,0 +1,268 @@ +Git v2.43 Release Notes +======================= + +Backward Compatibility Notes + + * The "--rfc" option of "git format-patch" used to be a valid way to + override an earlier "--subject-prefix=<something>" on the command + line and replace it with "[RFC PATCH]", but from this release, it + merely prefixes the string "RFC " in front of the given subject + prefix. If you are negatively affected by this change, please use + "--subject-prefix=PATCH --rfc" as a replacement. + + * "git rev-list --stdin" learned to take non-revisions (like "--not") + recently from the standard input, but the way such a "--not" was + handled was quite confusing, which has been rethought. The updated + rule is that "--not" given from the command line only affects revs + given from the command line that comes but not revs read from the + standard input, and "--not" read from the standard input affects + revs given from the stanrdard input and not revs given from the + command line. + +UI, Workflows & Features + + * A message written in olden time prevented a branch from getting + checked out saying it is already checked out elsewhere, but these + days, we treat a branch that is being bisected or rebased just like + a branch that is checked out and protect it. Rephrase the message + to say that the branch is in use. + + * Hourly and other schedule of "git maintenance" jobs are randomly + distributed now. + + * "git cmd -h" learned to signal which options can be negated by + listing such options like "--[no-]opt". + + * The way authentication related data other than passwords (e.g. + oath token and password expiration data) are stored in libsecret + keyrings has been rethought. + + * Update two credential helpers to correctly match which credential + to erase; they dropped not the ones with stale password. + + * Git GUI updates. + + * "git format-patch" learns a way to feed cover letter description, + that (1) can be used on detached HEAD where there is no branch + description available, and (2) also can override the branch + description if there is one. + + * Use of --max-pack-size to allow multiple packfiles to be created is + now supported even when we are sending unreachable objects to cruft + packs. + + * "git format-patch --rfc --subject-prefix=<foo>" used to ignore the + "--subject-prefix" option and used "[RFC PATCH]"; now we will add + "RFC" prefix to whatever subject prefix is specified. + + * "git log --format" has been taught the %(decorate) placeholder. + + * The default log message created by "git revert", when reverting a + commit that records a revert, has been tweaked, to encourage people + describe complex "revert of revert of revert" situation better in + their own words. + + * The command-line complation support (in contrib/) learned to + complete "git commit --trailer=" for possible trailer keys. + + * "git update-index" learns "--show-index-version" to inspect + the index format version used by the on-disk index file. + + * "git diff" learned diff.statNameWidth configuration variable, to + give the default width for the name part in the "--stat" output. + + * "git range-diff --notes=foo" compared "log --notes=foo --notes" of + the two ranges, instead of using just the specified notes tree. + + * The command line completion script (in contrib/) can be told to + complete aliases by including ": git <cmd> ;" in the alias to tell + it that the alias should be completed similar to how "git <cmd>" is + completed. The parsing code for the alias as been loosened to + allow ';' without an extra space before it. + + * "git for-each-ref" and friends learned to apply mailmap to + authorname and other fields. + + * "git repack" machinery learns to pay attention to the "--filter=" + option. + + * Test coverage for trailers has been improved. + + +Performance, Internal Implementation, Development Support etc. + + * "git check-attr" has been taught to work better with sparse-index. + + * It may be tempting to leave the help text NULL for a command line + option that is either hidden or too obvious, but "git subcmd -h" + and "git subcmd --help-all" would have segfaulted if done so. Now + the help text is optional. + + * Tests that are known to pass with LSan are now marked as such. + (merge 5fafe8c95f tb/mark-more-tests-as-leak-free later to maint). + + * Flaky "git p4" tests, as well as "git svn" tests, are now skipped + in the (rather expensive) sanitizer CI job. + (merge 6ba913629f js/ci-san-skip-p4-and-svn-tests later to maint). + + * Tests with LSan from time to time seem to emit harmless message + that makes our tests unnecessarily flaky; we work it around by + filtering the uninteresting output. + (merge 370ef7e40d jk/test-lsan-denoise-output later to maint). + + * Unused parameters to functions are marked as such, and/or removed, + in order to bring us closer to -Wunused-parameter clean. + + * The code to keep track of existing packs in the repository while + repacking has been refactored. + + * The "streaming" interface used for bulk-checkin codepath has been + narrowed to take only blob objects for now, with no real loss of + functionality. + + * GitHub CI workflow has learned to trigger Coverity check. + (merge 3349520e1a js/ci-coverity later to maint). + + +Fixes since v2.42 +----------------- + + * Overly long label names used in the sequencer machinery are now + chopped to fit under filesystem limitation. + (merge ac300bda10 mp/rebase-label-length-limit later to maint). + + * Scalar updates. + (merge f9a547d3a7 ds/scalar-updates later to maint). + + * Tweak GitHub Actions CI so that pushing the same commit to multiple + branch tips at the same time will not waste building and testing + the same thing twice. + (merge 99fe06cbfd jc/ci-skip-same-commit later to maint). + + * The commit-graph verification code that detects mixture of zero and + non-zero generation numbers has been updated. + (merge db6044d762 tb/commit-graph-verify-fix later to maint). + + * "git diff -w --exit-code" with various options did not work + correctly, which is being addressed. + (merge a64f8b2595 jc/diff-exit-code-with-w-fixes later to maint). + + * transfer.unpackLimit ought to be used as a fallback, but overrode + fetch.unpackLimit and receive.unpackLimit instead. + (merge f3d33f8cfe ts/unpacklimit-config-fix later to maint). + + * The use of API between two calls to require_clean_work_tree() from + the sequencer code has been cleaned up for consistency. + (merge a9b5955e07 ob/sequencer-empty-hint-fix later to maint). + + * "git diff --no-such-option" and other corner cases around the exit + status of the "diff" command has been corrected. + (merge 5cc6b2d70b jk/diff-result-code-cleanup later to maint). + + * "git for-each-ref --sort='contents:size'" sorts the refs according + to size numerically, giving a ref that points at a blob twelve-byte + (12) long before showing a blob hundred-byte (100) long. + (merge 6d79cd8474 ks/ref-filter-sort-numerically later to maint). + + * We now limit depth of the tree objects and maximum length of + pathnames recorded in tree objects. + (merge 4d5693ba05 jk/tree-name-and-depth-limit later to maint). + + * Various fixes to the behavior of "rebase -i" when the command got + interrupted by conflicting changes. + (merge 203573b024 pw/rebase-i-after-failure later to maint). + + * References from description of the `--patch` option in various + manual pages have been simplified and improved. + (merge 11422f23e3 so/diff-doc-for-patch-update later to maint). + + * "git grep -e A --no-or -e B" is accepted, even though the negation + of "or" did not mean anything, which has been tightened. + (merge aae8558b10 rs/grep-no-no-or later to maint). + + * The completion script (in contrib/) has been taught to treat the + "-t" option to "git checkout" and "git switch" just like the + "--track" option, to complete remote-tracking branches. + (merge 9f892830d6 js/complete-checkout-t later to maint). + + * "git diff --no-index -R <(one) <(two)" did not work correctly, + which has been corrected. + (merge 48944f214c pw/diff-no-index-from-named-pipes later to maint). + + * Update "git maintenance" timers' implementation based on systemd + timers to work with WSL. + (merge 5e8515e8e8 js/systemd-timers-wsl-fix later to maint). + + * "git diff --cached" codepath did not fill the necessary stat + information for a file when fsmonitor knows it is clean and ended + up behaving as if it is not clean, which has been corrected. + (merge 6a044a2048 js/diff-cached-fsmonitor-fix later to maint). + + * Clarify how "alias.foo = : git cmd ; aliased-command-string" should + be spelled with necessary whitespaces around punctuation marks to + work. + (merge 4333267995 pb/completion-aliases-doc later to maint). + + * HTTP Header redaction code has been adjusted for a newer version of + cURL library that shows its traces differently from earlier + versions. + (merge 0763c3a2c4 jk/redact-h2h3-headers-fix later to maint). + + * An error message given by "git send-email" when given a malformed + address did not give correct information, which has been corrected. + (merge 12288cc44e tb/send-email-extract-valid-address-error-message-fix later to maint). + + * UBSan options were not propagated through the test framework to git + run via the httpd, unlike ASan options, which has been corrected. + (merge 252d693797 jk/test-pass-ubsan-options-to-http-test later to maint). + + * "checkout --merge -- path" and "update-index --unresolve path" did + not resurrect conflicted state that was resolved to remove path, + but now they do. + (merge 5bdedac3c7 jc/unresolve-removal later to maint). + + * The display width table for unicode characters has been updated for + Unicode 15.1 + (merge 872976c37e bb/unicode-width-table-15 later to maint). + + * Update mailmap entry for Derrick. + (merge 6e5457d8c7 ds/mailmap-entry-update later to maint). + + * In .gitmodules files, submodules are keyed by their names, and the + path to the submodule whose name is $name is specified by the + submodule.$name.path variable. There were a few codepaths that + mixed the name and path up when consulting the submodule database, + which have been corrected. It took long for these bugs to be found + as the name of a submodule initially is the same as its path, and + the problem does not surface until it is moved to a different path, + which apparently happens very rarely. + + * "git diff --merge-base X other args..." insisted that X must be a + commit and errored out when given an annotated tag that peels to a + commit, but we only need it to be a committish. This has been + corrected. + (merge 4adceb5a29 ar/diff-index-merge-base-fix later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge fd3ba590d8 ws/git-push-doc-grammofix later to maint). + (merge 5f33a843de ds/upload-pack-error-sequence-fix later to maint). + (merge beaa1d952b jk/function-pointer-mismatches-fix later to maint). + (merge b46d806ea5 ob/t9001-indent-fix later to maint). + (merge fdc9914c28 ja/worktree-orphan later to maint). + (merge c2cbefc510 jc/mv-d-to-d-error-message-fix later to maint). + (merge d0fc552bfc ch/t6300-verify-commit-test-cleanup later to maint). + (merge aa4b83dd5e ws/git-svn-retire-faketerm later to maint). + (merge edf80d23f1 jk/ci-retire-allow-ref later to maint). + (merge 256a94ef6c bc/more-git-var later to maint). + (merge 82af2c639c ob/sequencer-reword-error-message later to maint). + (merge 2a63c79dae rs/grep-parseopt-simplify later to maint). + (merge 078c42531e rs/name-rev-use-opt-hidden-bool later to maint). + (merge 63642d58b4 ob/sequencer-remove-dead-code later to maint). + (merge 8aae489756 ob/t3404-typofix later to maint). + (merge 58be11432e eg/config-type-path-docfix later to maint). + (merge 563f339d98 ch/clean-docfix later to maint). + (merge 4fbe83fcd9 hy/doc-show-is-like-log-not-diff-tree later to maint). + (merge 43abaaf008 ob/am-msgfix later to maint). + (merge c2c349a15c xz/commit-title-soft-limit-doc later to maint). + (merge f4cbb32c27 rs/parse-opt-ctx-cleanup later to maint). + (merge badf2fe1c3 jk/decoration-and-other-leak-fixes later to maint). diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index dfbdaf00b8..0e8c2832bf 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -736,3 +736,9 @@ core.abbrev:: If set to "no", no abbreviation is made and the object names are shown in their full length. The minimum length is 4. + +core.maxTreeDepth:: + The maximum depth Git is willing to recurse while traversing a + tree (e.g., "a/b/cde/f" has a depth of 4). This is a fail-safe + to allow Git to abort cleanly, and should not generally need to + be adjusted. The default is 4096. diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt index 35a7bf86d7..9391c77e55 100644 --- a/Documentation/config/diff.txt +++ b/Documentation/config/diff.txt @@ -52,6 +52,10 @@ directories with less than 10% of the total amount of changed files, and accumulating child directory counts in the parent directories: `files,10,cumulative`. +diff.statNameWidth:: + Limit the width of the filename part in --stat output. If set, applies + to all commands generating --stat output except format-patch. + diff.statGraphWidth:: Limit the width of the graph part in --stat output. If set, applies to all commands generating --stat output except format-patch. diff --git a/Documentation/config/gc.txt b/Documentation/config/gc.txt index ca47eb2008..c6e3acc99d 100644 --- a/Documentation/config/gc.txt +++ b/Documentation/config/gc.txt @@ -86,6 +86,12 @@ gc.cruftPacks:: linkgit:git-repack[1]) instead of as loose objects. The default is `true`. +gc.maxCruftSize:: + Limit the size of new cruft packs when repacking. When + specified in addition to `--max-cruft-size`, the command line + option takes priority. See the `--max-cruft-size` option of + linkgit:git-repack[1]. + gc.pruneExpire:: When 'git gc' is run, it will call 'prune --expire 2.weeks.ago' (and 'repack --cruft --cruft-expiration 2.weeks.ago' if using @@ -145,6 +151,22 @@ Multiple hooks are supported, but all must exit successfully, else the operation (either generating a cruft pack or unpacking unreachable objects) will be halted. +gc.repackFilter:: + When repacking, use the specified filter to move certain + objects into a separate packfile. See the + `--filter=<filter-spec>` option of linkgit:git-repack[1]. + +gc.repackFilterTo:: + When repacking and using a filter, see `gc.repackFilter`, the + specified location will be used to create the packfile + containing the filtered out objects. **WARNING:** The + specified location should be accessible, using for example the + Git alternates mechanism, otherwise the repo could be + considered corrupt by Git as it migh not be able to access the + objects in that packfile. See the `--filter-to=<dir>` option + of linkgit:git-repack[1] and the `objects/info/alternates` + section of linkgit:gitrepository-layout[5]. + gc.rerereResolved:: Records of conflicted merge you resolved earlier are kept for this many days when 'git rerere gc' is run. diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index afaf6dad99..9c248accec 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -77,3 +77,9 @@ rebase.rebaseMerges:: equivalent to `--no-rebase-merges`. Passing `--rebase-merges` on the command line, with or without an argument, overrides any `rebase.rebaseMerges` configuration. + +rebase.maxLabelLength:: + When generating label names from commit subjects, truncate the names to + this length. By default, the names are truncated to a little less than + `NAME_MAX` (to allow e.g. `.lock` files to be written for the + corresponding loose refs). diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 9f33f88771..35fae7c87c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -22,13 +22,7 @@ ifndef::git-format-patch[] -p:: -u:: --patch:: - Generate patch (see section titled -ifdef::git-log[] -<<generate_patch_text_with_p, "Generating patch text with -p">>). -endif::git-log[] -ifndef::git-log[] -"Generating patch text with -p"). -endif::git-log[] + Generate patch (see <<generate_patch_text_with_p>>). ifdef::git-diff[] This is the default. endif::git-diff[] @@ -210,14 +204,15 @@ have to use `--diff-algorithm=default` option. part. Maximum width defaults to terminal width, or 80 columns if not connected to a terminal, and can be overridden by `<width>`. The width of the filename part can be limited by - giving another width `<name-width>` after a comma. The width - of the graph part can be limited by using - `--stat-graph-width=<width>` (affects all commands generating - a stat graph) or by setting `diff.statGraphWidth=<width>` - (does not affect `git format-patch`). - By giving a third parameter `<count>`, you can limit the - output to the first `<count>` lines, followed by `...` if - there are more. + giving another width `<name-width>` after a comma or by setting + `diff.statNameWidth=<width>`. The width of the graph part can be + limited by using `--stat-graph-width=<width>` or by setting + `diff.statGraphWidth=<width>`. Using `--stat` or + `--stat-graph-width` affects all commands generating a stat graph, + while setting `diff.statNameWidth` or `diff.statGraphWidth` + does not affect `git format-patch`. + By giving a third parameter `<count>`, you can limit the output to + the first `<count>` lines, followed by `...` if there are more. + These parameters can also be set individually with `--stat-width=<width>`, `--stat-name-width=<name-width>` and `--stat-count=<count>`. diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt index 12eae8a222..09b0aecbf8 100644 --- a/Documentation/fsck-msgids.txt +++ b/Documentation/fsck-msgids.txt @@ -103,6 +103,13 @@ `hasDotgit`:: (WARN) A tree contains an entry named `.git`. +`largePathname`:: + (WARN) A tree contains an entry with a very long path name. If + the value of `fsck.largePathname` contains a colon, that value + is used as the maximum allowable length (e.g., "warn:10" would + complain about any path component of 11 or more bytes). The + default value is 4096. + `mailmapSymlink`:: (INFO) `.mailmap` is a symlink. diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 4af0904f47..a30e3ebc51 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -12,8 +12,10 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] --detach [<branch>] 'git checkout' [-q] [-f] [-m] [--detach] <commit> 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new-branch>] [<start-point>] -'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <pathspec>... -'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] --pathspec-from-file=<file> [--pathspec-file-nul] +'git checkout' [-f] <tree-ish> [--] <pathspec>... +'git checkout' [-f] <tree-ish> --pathspec-from-file=<file> [--pathspec-file-nul] +'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [--] <pathspec>... +'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] --pathspec-from-file=<file> [--pathspec-file-nul] 'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...] DESCRIPTION @@ -260,7 +262,8 @@ and mark the resolved paths with `git add` (or `git rm` if the merge should result in deletion of the path). + When checking out paths from the index, this option lets you recreate -the conflicted merge in the specified paths. +the conflicted merge in the specified paths. This option cannot be +used when checking out paths from a tree-ish. + When switching branches with `--merge`, staged changes may be lost. diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 160d08b86b..5e1a3d5148 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -127,7 +127,7 @@ ask each:: quit:: - This lets you quit without do cleaning. + This lets you quit without doing any cleaning. help:: diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 7a2bcb2f6c..b1caac887a 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -201,7 +201,7 @@ Valid `<type>`'s include: 1073741824 upon input. - 'bool-or-int': canonicalize according to either 'bool' or 'int', as described above. -- 'path': canonicalize by adding a leading `~` to the value of `$HOME` and +- 'path': canonicalize by expanding a leading `~` to the value of `$HOME` and `~user` to the home directory for the specified user. This specifier has no effect when setting the value (but you can use `git config section.variable ~/` from the command line to let your shell do the expansion.) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 11b2bc3121..e86d5700dd 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -303,7 +303,11 @@ Fields that have name-email-date tuple as its value (`author`, and `date` to extract the named component. For email fields (`authoremail`, `committeremail` and `taggeremail`), `:trim` can be appended to get the email without angle brackets, and `:localpart` to get the part before the `@` symbol -out of the trimmed email. +out of the trimmed email. In addition to these, the `:mailmap` option and the +corresponding `:mailmap,trim` and `:mailmap,localpart` can be used (order does +not matter) to get values of the name and email according to the .mailmap file +or according to the file set in the mailmap.file or mailmap.blob configuration +variable (see linkgit:gitmailmap[5]). The raw data in an object is `raw`. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 373b46fc0d..711823a7f4 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -215,11 +215,21 @@ is greater than 100 bytes, then the mode will be `message`, otherwise If `<mode>` is `none`, both the cover letter subject and body will be populated with placeholder text. +--description-file=<file>:: + Use the contents of <file> instead of the branch's description + for generating the cover letter. + --subject-prefix=<subject prefix>:: Instead of the standard '[PATCH]' prefix in the subject - line, instead use '[<subject prefix>]'. This - allows for useful naming of a patch series, and can be - combined with the `--numbered` option. + line, instead use '[<subject prefix>]'. This can be used + to name a patch series, and can be combined with the + `--numbered` option. ++ +The configuration variable `format.subjectPrefix` may also be used +to configure a subject prefix to apply to a given repository for +all patches. This is often useful on mailing lists which receive +patches for several repositories and can be used to disambiguate +the patches (with a value of e.g. "PATCH my-project"). --filename-max-length=<n>:: Instead of the standard 64 bytes, chomp the generated output @@ -229,9 +239,9 @@ populated with placeholder text. variable, or 64 if unconfigured. --rfc:: - Alias for `--subject-prefix="RFC PATCH"`. RFC means "Request For - Comments"; use this when sending an experimental patch for - discussion rather than application. + 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. -v <n>:: --reroll-count=<n>:: diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 90806fd26a..b5561c458a 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -59,6 +59,13 @@ be performed as well. cruft pack instead of storing them as loose objects. `--cruft` is on by default. +--max-cruft-size=<n>:: + When packing unreachable objects into a cruft pack, limit the + size of new cruft packs to be at most `<n>` bytes. Overrides any + value specified via the `gc.maxCruftSize` configuration. See + the `--max-cruft-size` option of linkgit:git-repack[1] for + more. + --prune=<date>:: Prune loose objects older than date (default is 2 weeks ago, overridable by the config variable `gc.pruneExpire`). diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 55d8961466..418265f044 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git interpret-trailers' [--in-place] [--trim-empty] - [(--trailer <token>[(=|:)<value>])...] + [(--trailer (<key>|<keyAlias>)[(=|:)<value>])...] [--parse] [<file>...] DESCRIPTION @@ -31,10 +31,15 @@ the last two lines starting with "Signed-off-by" are trailers. This command reads commit messages from either the <file> arguments or the standard input if no <file> is specified. -If `--parse` is specified, the output consists of the parsed trailers. -Otherwise, this command applies the arguments passed using the -`--trailer` option, if any, to each input file. The result is emitted on the -standard output. +If `--parse` is specified, the output consists of the parsed trailers +coming from the input, without influencing them with any command line +options or configuration variables. + +Otherwise, this command applies `trailer.*` configuration variables +(which could potentially add new trailers, as well as reposition them), +as well as any command line arguments that can override configuration +variables (such as `--trailer=...` which could also add new trailers), +to each input file. The result is emitted on the standard output. This command can also operate on the output of linkgit:git-format-patch[1], which is more elaborate than a plain commit message. Namely, such output @@ -48,22 +53,32 @@ are applied to each input and the way any existing trailer in the input is changed. They also make it possible to automatically add some trailers. -By default, a '<token>=<value>' or '<token>:<value>' argument given +By default, a '<key>=<value>' or '<key>:<value>' argument given using `--trailer` will be appended after the existing trailers only if -the last trailer has a different (<token>, <value>) pair (or if there -is no existing trailer). The <token> and <value> parts will be trimmed +the last trailer has a different (<key>, <value>) pair (or if there +is no existing trailer). The <key> and <value> parts will be trimmed to remove starting and trailing whitespace, and the resulting trimmed -<token> and <value> will appear in the output like this: +<key> and <value> will appear in the output like this: ------------------------------------------------ -token: value +key: value ------------------------------------------------ -This means that the trimmed <token> and <value> will be separated by -`': '` (one colon followed by one space). For convenience, the <token> can be a -shortened string key (e.g., "sign") instead of the full string which should -appear before the separator on the output (e.g., "Signed-off-by"). This can be -configured using the 'trailer.<token>.key' configuration variable. +This means that the trimmed <key> and <value> will be separated by +`': '` (one colon followed by one space). + +For convenience, a <keyAlias> can be configured to make using `--trailer` +shorter to type on the command line. This can be configured using the +'trailer.<keyAlias>.key' configuration variable. The <keyAlias> must be a prefix +of the full <key> string, although case sensitivity does not matter. For +example, if you have + +------------------------------------------------ +trailer.sign.key "Signed-off-by: " +------------------------------------------------ + +in your configuration, you only need to specify `--trailer="sign: foo"` +on the command line instead of `--trailer="Signed-off-by: foo"`. By default the new trailer will appear at the end of all the existing trailers. If there is no existing trailer, the new trailer will appear @@ -80,14 +95,14 @@ non-whitespace lines before a line that starts with '---' (followed by a space or the end of the line). When reading trailers, there can be no whitespace before or inside the -<token>, but any number of regular space and tab characters are allowed -between the <token> and the separator. There can be whitespaces before, +<key>, but any number of regular space and tab characters are allowed +between the <key> and the separator. There can be whitespaces before, inside or after the <value>. The <value> may be split over multiple lines with each subsequent line starting with at least one whitespace, like the "folding" in RFC 822. Example: ------------------------------------------------ -token: This is a very long value, with spaces and +key: This is a very long value, with spaces and newlines in it. ------------------------------------------------ @@ -104,35 +119,44 @@ OPTIONS the whole trailer will be removed from the output. This applies to existing trailers as well as new trailers. ---trailer <token>[(=|:)<value>]:: - Specify a (<token>, <value>) pair that should be applied as a +--trailer <key>[(=|:)<value>]:: + Specify a (<key>, <value>) pair that should be applied as a trailer to the inputs. See the description of this command. --where <placement>:: --no-where:: Specify where all new trailers will be added. A setting - provided with '--where' overrides all configuration variables + provided with '--where' overrides the `trailer.where` and any + applicable `trailer.<keyAlias>.where` configuration variables and applies to all '--trailer' options until the next occurrence of - '--where' or '--no-where'. Possible values are `after`, `before`, - `end` or `start`. + '--where' or '--no-where'. Upon encountering '--no-where', clear the + effect of any previous use of '--where', such that the relevant configuration + variables are no longer overridden. Possible placements are `after`, + `before`, `end` or `start`. --if-exists <action>:: --no-if-exists:: Specify what action will be performed when there is already at - least one trailer with the same <token> in the input. A setting - provided with '--if-exists' overrides all configuration variables + least one trailer with the same <key> in the input. A setting + provided with '--if-exists' overrides the `trailer.ifExists` and any + applicable `trailer.<keyAlias>.ifExists` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-exists' or '--no-if-exists'. Possible actions are `addIfDifferent`, + '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists, clear the + effect of any previous use of '--if-exists, such that the relevant configuration + variables are no longer overridden. Possible actions are `addIfDifferent`, `addIfDifferentNeighbor`, `add`, `replace` and `doNothing`. --if-missing <action>:: --no-if-missing:: Specify what action will be performed when there is no other - trailer with the same <token> in the input. A setting - provided with '--if-missing' overrides all configuration variables + trailer with the same <key> in the input. A setting + provided with '--if-missing' overrides the `trailer.ifMissing` and any + applicable `trailer.<keyAlias>.ifMissing` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-missing' or '--no-if-missing'. Possible actions are `doNothing` + '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing, + clear the effect of any previous use of '--if-missing, such that the relevant + configuration variables are no longer overridden. Possible actions are `doNothing` or `add`. --only-trailers:: @@ -140,16 +164,19 @@ OPTIONS --only-input:: Output only trailers that exist in the input; do not add any - from the command-line or by following configured `trailer.*` - rules. + from the command-line or by applying `trailer.*` configuration + variables. --unfold:: - Remove any whitespace-continuation in trailers, so that each - trailer appears on a line by itself with its full content. + If a trailer has a value that runs over multiple lines (aka "folded"), + reformat the value into a single line. --parse:: A convenience alias for `--only-trailers --only-input - --unfold`. + --unfold`. This makes it easier to only see the trailers coming from the + input without influencing them with any command line options or + configuration variables, while also making the output machine-friendly with + --unfold. --no-divider:: Do not treat `---` as the end of the commit message. Use this @@ -170,11 +197,11 @@ used when another separator is not specified in the config for this trailer. + For example, if the value for this option is "%=$", then only lines -using the format '<token><sep><value>' with <sep> containing '%', '=' +using the format '<key><sep><value>' with <sep> containing '%', '=' or '$' and then spaces will be considered trailers. And '%' will be the default separator used, so by default trailers will appear like: -'<token>% <value>' (one percent sign and one space will appear between -the token and the value). +'<key>% <value>' (one percent sign and one space will appear between +the key and the value). trailer.where:: This option tells where a new trailer will be added. @@ -188,41 +215,41 @@ If it is `start`, then each new trailer will appear at the start, instead of the end, of the existing trailers. + If it is `after`, then each new trailer will appear just after the -last trailer with the same <token>. +last trailer with the same <key>. + If it is `before`, then each new trailer will appear just before the -first trailer with the same <token>. +first trailer with the same <key>. trailer.ifexists:: This option makes it possible to choose what action will be performed when there is already at least one trailer with the - same <token> in the input. + same <key> in the input. + The valid values for this option are: `addIfDifferentNeighbor` (this is the default), `addIfDifferent`, `add`, `replace` or `doNothing`. + With `addIfDifferentNeighbor`, a new trailer will be added only if no -trailer with the same (<token>, <value>) pair is above or below the line +trailer with the same (<key>, <value>) pair is above or below the line where the new trailer will be added. + With `addIfDifferent`, a new trailer will be added only if no trailer -with the same (<token>, <value>) pair is already in the input. +with the same (<key>, <value>) pair is already in the input. + With `add`, a new trailer will be added, even if some trailers with -the same (<token>, <value>) pair are already in the input. +the same (<key>, <value>) pair are already in the input. + -With `replace`, an existing trailer with the same <token> will be +With `replace`, an existing trailer with the same <key> will be deleted and the new trailer will be added. The deleted trailer will be -the closest one (with the same <token>) to the place where the new one +the closest one (with the same <key>) to the place where the new one will be added. + With `doNothing`, nothing will be done; that is no new trailer will be -added if there is already one with the same <token> in the input. +added if there is already one with the same <key> in the input. trailer.ifmissing:: This option makes it possible to choose what action will be performed when there is not yet any trailer with the same - <token> in the input. + <key> in the input. + The valid values for this option are: `add` (this is the default) and `doNothing`. @@ -231,34 +258,40 @@ With `add`, a new trailer will be added. + With `doNothing`, nothing will be done. -trailer.<token>.key:: - This `key` will be used instead of <token> in the trailer. At - the end of this key, a separator can appear and then some - space characters. By default the only valid separator is ':', - but this can be changed using the `trailer.separators` config - variable. +trailer.<keyAlias>.key:: + Defines a <keyAlias> for the <key>. The <keyAlias> must be a + prefix (case does not matter) of the <key>. For example, in `git + config trailer.ack.key "Acked-by"` the "Acked-by" is the <key> and + the "ack" is the <keyAlias>. This configuration allows the shorter + `--trailer "ack:..."` invocation on the command line using the "ack" + <keyAlias> instead of the longer `--trailer "Acked-by:..."`. ++ +At the end of the <key>, a separator can appear and then some +space characters. By default the only valid separator is ':', +but this can be changed using the `trailer.separators` config +variable. + -If there is a separator, then the key will be used instead of both the -<token> and the default separator when adding the trailer. +If there is a separator in the key, then it overrides the default +separator when adding the trailer. -trailer.<token>.where:: +trailer.<keyAlias>.where:: This option takes the same values as the 'trailer.where' configuration variable and it overrides what is specified by - that option for trailers with the specified <token>. + that option for trailers with the specified <keyAlias>. -trailer.<token>.ifexists:: +trailer.<keyAlias>.ifexists:: This option takes the same values as the 'trailer.ifexists' configuration variable and it overrides what is specified by - that option for trailers with the specified <token>. + that option for trailers with the specified <keyAlias>. -trailer.<token>.ifmissing:: +trailer.<keyAlias>.ifmissing:: This option takes the same values as the 'trailer.ifmissing' configuration variable and it overrides what is specified by - that option for trailers with the specified <token>. + that option for trailers with the specified <keyAlias>. -trailer.<token>.command:: - Deprecated in favor of 'trailer.<token>.cmd'. - This option behaves in the same way as 'trailer.<token>.cmd', except +trailer.<keyAlias>.command:: + Deprecated in favor of 'trailer.<keyAlias>.cmd'. + This option behaves in the same way as 'trailer.<keyAlias>.cmd', except that it doesn't pass anything as argument to the specified command. Instead the first occurrence of substring $ARG is replaced by the <value> that would be passed as argument. @@ -266,29 +299,29 @@ trailer.<token>.command:: Note that $ARG in the user's command is only replaced once and that the original way of replacing $ARG is not safe. + -When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given -for the same <token>, 'trailer.<token>.cmd' is used and -'trailer.<token>.command' is ignored. +When both 'trailer.<keyAlias>.cmd' and 'trailer.<keyAlias>.command' are given +for the same <keyAlias>, 'trailer.<keyAlias>.cmd' is used and +'trailer.<keyAlias>.command' is ignored. -trailer.<token>.cmd:: +trailer.<keyAlias>.cmd:: This option can be used to specify a shell command that will be called - once to automatically add a trailer with the specified <token>, and then - called each time a '--trailer <token>=<value>' argument is specified to + once to automatically add a trailer with the specified <keyAlias>, and then + called each time a '--trailer <keyAlias>=<value>' argument is specified to modify the <value> of the trailer that this option would produce. + When the specified command is first called to add a trailer -with the specified <token>, the behavior is as if a special -'--trailer <token>=<value>' argument was added at the beginning +with the specified <keyAlias>, the behavior is as if a special +'--trailer <keyAlias>=<value>' argument was added at the beginning of the "git interpret-trailers" command, where <value> is taken to be the standard output of the command with any leading and trailing whitespace trimmed off. + -If some '--trailer <token>=<value>' arguments are also passed +If some '--trailer <keyAlias>=<value>' arguments are also passed on the command line, the command is called again once for each -of these arguments with the same <token>. And the <value> part +of these arguments with the same <keyAlias>. And the <value> part of these arguments, if any, will be passed to the command as its first argument. This way the command can produce a <value> computed -from the <value> passed in the '--trailer <token>=<value>' argument. +from the <value> passed in the '--trailer <keyAlias>=<value>' argument. EXAMPLES -------- diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index a9995a932c..e32404c6aa 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -116,9 +116,7 @@ unreachable object whose mtime is newer than the `--cruft-expiration`). + Incompatible with `--unpack-unreachable`, `--keep-unreachable`, `--pack-loose-unreachable`, `--stdin-packs`, as well as any other -options which imply `--revs`. Also incompatible with `--max-pack-size`; -when this option is set, the maximum pack size is not inferred from -`pack.packSizeLimit`. +options which imply `--revs`. --cruft-expiration=<approxidate>:: If specified, objects are eliminated from the cruft pack if they @@ -298,8 +296,8 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle. nevertheless. --filter=<filter-spec>:: - Requires `--stdout`. Omits certain objects (usually blobs) from - the resulting packfile. See linkgit:git-rev-list[1] for valid + Omits certain objects (usually blobs) from the resulting + packfile. See linkgit:git-rev-list[1] for valid `<filter-spec>` forms. --no-filter:: diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 297927d866..5b4edaf4a8 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -37,7 +37,7 @@ the default `<refspec>` by consulting `remote.*.push` configuration, and if it is not found, honors `push.default` configuration to decide what to push (See linkgit:git-config[1] for the meaning of `push.default`). -When neither the command-line nor the configuration specify what to +When neither the command-line nor the configuration specifies what to push, the default behavior is used, which corresponds to the `simple` value for `push.default`: the current branch is pushed to the corresponding upstream branch, but as a safety measure, the push is diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 4017157949..893b8a2fea 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -74,6 +74,17 @@ to the new separate pack will be written. immediately instead of waiting for the next `git gc` invocation. Only useful with `--cruft -d`. +--max-cruft-size=<n>:: + Repack cruft objects into packs as large as `<n>` bytes before + creating new packs. As long as there are enough cruft packs + smaller than `<n>`, repacking will cause a new cruft pack to + be created containing objects from any combined cruft packs, + along with any new unreachable objects. Cruft packs larger than + `<n>` will not be modified. When the new cruft pack is larger + than `<n>` bytes, it will be split into multiple packs, all of + which are guaranteed to be at most `<n>` bytes in size. Only + useful with `--cruft -d`. + --expire-to=<dir>:: Write a cruft pack containing pruned objects (if any) to the directory `<dir>`. This option is useful for keeping a copy of @@ -143,6 +154,29 @@ depth is 4095. a larger and slower repository; see the discussion in `pack.packSizeLimit`. +--filter=<filter-spec>:: + Remove objects matching the filter specification from the + resulting packfile and put them into a separate packfile. Note + that objects used in the working directory are not filtered + out. So for the split to fully work, it's best to perform it + in a bare repo and to use the `-a` and `-d` options along with + this option. Also `--no-write-bitmap-index` (or the + `repack.writebitmaps` config option set to `false`) should be + used otherwise writing bitmap index will fail, as it supposes + a single packfile containing all the objects. See + linkgit:git-rev-list[1] for valid `<filter-spec>` forms. + +--filter-to=<dir>:: + Write the pack containing filtered out objects to the + directory `<dir>`. Only useful with `--filter`. This can be + used for putting the pack on a separate object directory that + is accessed through the Git alternates mechanism. **WARNING:** + If the packfile containing the filtered out objects is not + accessible, the repo can become corrupt as it might not be + possible to access the objects in that packfile. See the + `objects` and `objects/info/alternates` sections of + linkgit:gitrepository-layout[5]. + -b:: --write-bitmap-index:: Write a reachability bitmap index as part of the repack. This diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt index 5964810caa..c70444705b 100644 --- a/Documentation/git-restore.txt +++ b/Documentation/git-restore.txt @@ -78,6 +78,8 @@ all modified paths. --theirs:: When restoring files in the working tree from the index, use stage #2 ('ours') or #3 ('theirs') for unmerged paths. + This option cannot be used when checking out paths from a + tree-ish (i.e. with the `--source` option). + Note that during `git rebase` and `git pull --rebase`, 'ours' and 'theirs' may appear swapped. See the explanation of the same options @@ -87,6 +89,8 @@ in linkgit:git-checkout[1] for details. --merge:: When restoring files on the working tree from the index, recreate the conflicted merge in the unmerged paths. + This option cannot be used when checking out paths from a + tree-ish (i.e. with the `--source` option). --conflict=<style>:: The same as `--merge` option above, but changes the way the diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index f26a7591e3..6a4968f68a 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -398,7 +398,7 @@ some-command [<options>] <args>... some-command does foo and bar! -- -h,help show the help +h,help! show the help foo some nifty option --foo bar= some cool option --bar with an argument @@ -424,10 +424,10 @@ usage: some-command [<options>] <args>... some-command does foo and bar! -h, --help show the help - --foo some nifty option --foo - --bar ... some cool option --bar with an argument - --baz <arg> another cool option --baz with a named argument - --qux[=<path>] qux may take a path argument but has meaning by itself + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + --[no-]baz <arg> another cool option --baz with a named argument + --[no-]qux[=<path>] qux may take a path argument but has meaning by itself An option group Header -C[...] option C with an optional argument diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index d2e10d3dce..cbe0208834 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -142,6 +142,16 @@ EXAMPLES changes. The revert only modifies the working tree and the index. +DISCUSSION +---------- + +While git creates a basic commit message automatically, it is +_strongly_ recommended to explain why the original commit is being +reverted. +In addition, repeatedly reverting reverts will result in increasingly +unwieldy subject lines, for example 'Reapply "Reapply "<original subject>""'. +Please consider rewording these to be shorter and more unique. + CONFIGURATION ------------- diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 2b1bc7288d..03c0634518 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -26,7 +26,7 @@ with --name-only). For plain blobs, it shows the plain contents. -The command takes options applicable to the 'git diff-tree' command to +Some options that 'git log' command understands can be used to control how the changes the commit introduces are shown. This manual page describes only the most frequently used options. diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index a051b1e8f3..48f46eb204 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -245,10 +245,11 @@ U U unmerged, both modified .... Submodules have more state and instead report - M the submodule has a different HEAD than - recorded in the index - m the submodule has modified content - ? the submodule has untracked files + +* 'M' = the submodule has a different HEAD than recorded in the index +* 'm' = the submodule has modified content +* '?' = the submodule has untracked files + since modified content or untracked files in a submodule cannot be added via `git add` in the superproject to prepare a commit. diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index f4bb9c5daf..1271486ae9 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -162,13 +162,19 @@ you will need to handle the situation manually. Write the resulting index out in the named on-disk format version. Supported versions are 2, 3 and 4. The current default version is 2 or 3, depending on whether extra features are used, such as - `git add -N`. + `git add -N`. With `--verbose`, also report the version the index + file uses before and after this command. + Version 4 performs a simple pathname compression that reduces index size by 30%-50% on large repositories, which results in faster load -time. Version 4 is relatively young (first released in 1.8.0 in -October 2012). Other Git implementations such as JGit and libgit2 -may not support it yet. +time. Git supports it since version 1.8.0, released in October 2012, +and support for it was added to libgit2 in 2016 and to JGit in 2020. +Older versions of this manual page called it "relatively young", but +it should be considered mature technology these days. + +--show-index-version:: + Report the index format version used by the on-disk index file. + See `--index-version` above. -z:: Only meaningful with `--stdin` or `--index-info`; paths are diff --git a/Documentation/gitformat-pack.txt b/Documentation/gitformat-pack.txt index 0c1be2dbe8..870e00f298 100644 --- a/Documentation/gitformat-pack.txt +++ b/Documentation/gitformat-pack.txt @@ -588,51 +588,17 @@ later on. It is linkgit:git-gc[1] that is typically responsible for removing expired unreachable objects. -=== Caution for mixed-version environments - -Repositories that have cruft packs in them will continue to work with any older -version of Git. Note, however, that previous versions of Git which do not -understand the `.mtimes` file will use the cruft pack's mtime as the mtime for -all of the objects in it. In other words, do not expect older (pre-cruft pack) -versions of Git to interpret or even read the contents of the `.mtimes` file. - -Note that having mixed versions of Git GC-ing the same repository can lead to -unreachable objects never being completely pruned. This can happen under the -following circumstances: - - - An older version of Git running GC explodes the contents of an existing - cruft pack loose, using the cruft pack's mtime. - - A newer version running GC collects those loose objects into a cruft pack, - where the .mtime file reflects the loose object's actual mtimes, but the - cruft pack mtime is "now". - -Repeating this process will lead to unreachable objects not getting pruned as a -result of repeatedly resetting the objects' mtimes to the present time. - -If you are GC-ing repositories in a mixed version environment, consider omitting -the `--cruft` option when using linkgit:git-repack[1] and linkgit:git-gc[1], and -setting the `gc.cruftPacks` configuration to "false" until all writers -understand cruft packs. - === Alternatives Notable alternatives to this design include: - - The location of the per-object mtime data, and - - Storing unreachable objects in multiple cruft packs. + - The location of the per-object mtime data. On the location of mtime data, a new auxiliary file tied to the pack was chosen to avoid complicating the `.idx` format. If the `.idx` format were ever to gain support for optional chunks of data, it may make sense to consolidate the `.mtimes` format into the `.idx` itself. -Storing unreachable objects among multiple cruft packs (e.g., creating a new -cruft pack during each repacking operation including only unreachable objects -which aren't already stored in an earlier cruft pack) is significantly more -complicated to construct, and so aren't pursued here. The obvious drawback to -the current implementation is that the entire cruft pack must be re-written from -scratch. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 3b71334459..d38b4ab566 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -122,7 +122,9 @@ The placeholders are: - Placeholders that expand to a single literal character: '%n':: newline '%%':: a raw '%' -'%x00':: print a byte from a hex code +'%x00':: '%x' followed by two hexadecimal digits is replaced with a + byte with the hexadecimal digits' value (we will call this + "literal formatting code" in the rest of this document). - Placeholders that affect formatting of later placeholders: '%Cred':: switch color to red @@ -222,13 +224,30 @@ The placeholders are: linkgit:git-rev-list[1]) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping. -'%(describe[:options])':: human-readable name, like - linkgit:git-describe[1]; empty string for - undescribable commits. The `describe` string - may be followed by a colon and zero or more - comma-separated options. Descriptions can be - inconsistent when tags are added or removed at - the same time. +'%(decorate[:<options>])':: +ref names with custom decorations. The `decorate` string may be followed by a +colon and zero or more comma-separated options. Option values may contain +literal formatting codes. These must be used for commas (`%x2C`) and closing +parentheses (`%x29`), due to their role in the option syntax. ++ +** 'prefix=<value>': Shown before the list of ref names. Defaults to "{nbsp}`(`". +** 'suffix=<value>': Shown after the list of ref names. Defaults to "`)`". +** 'separator=<value>': Shown between ref names. Defaults to "`,`{nbsp}". +** 'pointer=<value>': Shown between HEAD and the branch it points to, if any. + Defaults to "{nbsp}`->`{nbsp}". +** 'tag=<value>': Shown before tag names. Defaults to "`tag:`{nbsp}". + ++ +For example, to produce decorations with no wrapping +or tag annotations, and spaces as separators: ++ +`%(decorate:prefix=,suffix=,tag=,separator= )` + +'%(describe[:<options>])':: +human-readable name, like linkgit:git-describe[1]; empty string for +undescribable commits. The `describe` string may be followed by a colon and +zero or more comma-separated options. Descriptions can be inconsistent when +tags are added or removed at the same time. + ** 'tags[=<bool-value>]': Instead of only considering annotated tags, consider lightweight tags as well. @@ -281,13 +300,11 @@ endif::git-rev-list[] '%gE':: reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) '%gs':: reflog subject -'%(trailers[:options])':: display the trailers of the body as - interpreted by - linkgit:git-interpret-trailers[1]. The - `trailers` string may be followed by a colon - and zero or more comma-separated options. - If any option is provided multiple times the - last occurrence wins. +'%(trailers[:<options>])':: +display the trailers of the body as interpreted by +linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by +a colon and zero or more comma-separated options. If any option is provided +multiple times, the last occurrence wins. + ** 'key=<key>': only show trailers with specified <key>. Matching is done case-insensitively and trailing colon is optional. If option is diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index dc685be363..335395b727 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -87,6 +87,10 @@ being displayed. Examples: "--notes=foo" will show only notes from "--notes --notes=foo --no-notes --notes=bar" will only show notes from "refs/notes/bar". +--show-notes-by-default:: + Show the default notes unless options for displaying specific + notes are given. + --show-notes[=<ref>]:: --[no-]standard-notes:: These options are deprecated. Use the above --notes/--no-notes diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a4a0cb93b2..66d71d1b95 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -151,6 +151,10 @@ endif::git-log[] --not:: Reverses the meaning of the '{caret}' prefix (or lack thereof) for all following revision specifiers, up to the next `--not`. + When used on the command line before --stdin, the revisions passed + through stdin will not be affected by it. Conversely, when passed + via standard input, the revisions passed on the command line will + not be affected by it. --all:: Pretend as if all the refs in `refs/`, along with `HEAD`, are @@ -240,7 +244,9 @@ endif::git-rev-list[] them from standard input as well. This accepts commits and pseudo-options like `--all` and `--glob=`. When a `--` separator is seen, the following input is treated as paths and used to - limit the result. + limit the result. Flags like `--not` which are read via standard input + are only respected for arguments passed in the same way and will not + influence any subsequent command line arguments. ifdef::git-rev-list[] --quiet:: diff --git a/Documentation/scalar.txt b/Documentation/scalar.txt index f33436c7f6..361f51a647 100644 --- a/Documentation/scalar.txt +++ b/Documentation/scalar.txt @@ -8,7 +8,8 @@ scalar - A tool for managing large Git repositories SYNOPSIS -------- [verse] -scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] <url> [<enlistment>] +scalar clone [--single-branch] [--branch <main-branch>] [--full-clone] + [--[no-]src] <url> [<enlistment>] scalar list scalar register [<enlistment>] scalar unregister [<enlistment>] @@ -80,6 +81,11 @@ remote-tracking branch for the branch this option was used for the initial cloning. If the HEAD at the remote did not point at any branch when `--single-branch` clone was made, no remote-tracking branch is created. +--[no-]src:: + By default, `scalar clone` places the cloned repository within a + `<entlistment>/src` directory. Use `--no-src` to place the cloned + repository directly in the `<enlistment>` directory. + --[no-]full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2c8dae398f..9f0307d364 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.42.0 +DEF_VER=v2.42.GIT LF=' ' @@ -800,6 +800,7 @@ 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-fast-rebase.o +TEST_BUILTINS_OBJS += test-find-pack.o TEST_BUILTINS_OBJS += test-fsmonitor-client.o TEST_BUILTINS_OBJS += test-genrandom.o TEST_BUILTINS_OBJS += test-genzeros.o @@ -808,7 +809,6 @@ TEST_BUILTINS_OBJS += test-hash-speed.o TEST_BUILTINS_OBJS += test-hash.o TEST_BUILTINS_OBJS += test-hashmap.o TEST_BUILTINS_OBJS += test-hexdump.o -TEST_BUILTINS_OBJS += test-index-version.o TEST_BUILTINS_OBJS += test-json-writer.o TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o TEST_BUILTINS_OBJS += test-match-trees.o @@ -1040,6 +1040,7 @@ LIB_OBJS += hash-lookup.o LIB_OBJS += hashmap.o LIB_OBJS += help.o LIB_OBJS += hex.o +LIB_OBJS += hex-ll.o LIB_OBJS += hook.o LIB_OBJS += ident.o LIB_OBJS += json-writer.o @@ -1090,6 +1091,7 @@ LIB_OBJS += pack-write.o LIB_OBJS += packfile.o LIB_OBJS += pager.o LIB_OBJS += parallel-checkout.o +LIB_OBJS += parse.o LIB_OBJS += parse-options-cb.o LIB_OBJS += parse-options.o LIB_OBJS += patch-delta.o @@ -1 +1 @@ -Documentation/RelNotes/2.42.0.txt
\ No newline at end of file +Documentation/RelNotes/2.43.0.txt
\ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index add9a1ad43..6bf87e7ae7 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -569,7 +569,7 @@ static int get_modified_files(struct repository *r, copy_pathspec(&rev.prune_data, ps); if (s.mode == FROM_INDEX) - run_diff_index(&rev, 1); + run_diff_index(&rev, DIFF_INDEX_CACHED); else { rev.diffopt.flags.ignore_dirty_submodules = 1; run_diff_files(&rev, 0); @@ -1021,9 +1021,9 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps, return res; } -static int run_help(struct add_i_state *s, const struct pathspec *unused_ps, - struct prefix_item_list *unused_files, - struct list_and_choose_options *unused_opts) +static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED, + struct prefix_item_list *files UNUSED, + struct list_and_choose_options *opts UNUSED) { color_fprintf_ln(stdout, s->help_color, "status - %s", _("show paths with changes")); @@ -1074,7 +1074,7 @@ struct print_command_item_data { const char *color, *reset; }; -static void print_command_item(int i, int selected, +static void print_command_item(int i, int selected UNUSED, struct string_list_item *item, void *print_command_item_data) { @@ -7,7 +7,7 @@ */ #include "git-compat-util.h" -#include "config.h" +#include "parse.h" #include "environment.h" #include "exec-cmd.h" #include "attr.h" @@ -807,35 +807,56 @@ static struct attr_stack *read_attr_from_blob(struct index_state *istate, static struct attr_stack *read_attr_from_index(struct index_state *istate, const char *path, unsigned flags) { + struct attr_stack *stack = NULL; char *buf; unsigned long size; + int sparse_dir_pos = -1; if (!istate) return NULL; /* - * The .gitattributes file only applies to files within its - * parent directory. In the case of cone-mode sparse-checkout, - * the .gitattributes file is sparse if and only if all paths - * within that directory are also sparse. Thus, don't load the - * .gitattributes file since it will not matter. - * - * In the case of a sparse index, it is critical that we don't go - * looking for a .gitattributes file, as doing so would cause the - * index to expand. + * When handling sparse-checkouts, .gitattributes files + * may reside within a sparse directory. We distinguish + * whether a path exists directly in the index or not by + * evaluating if 'pos' is negative. + * If 'pos' is negative, the path is not directly present + * in the index and is likely within a sparse directory. + * For paths not in the index, The absolute value of 'pos' + * minus 1 gives us the position where the path would be + * inserted in lexicographic order within the index. + * We then subtract another 1 from this value + * (sparse_dir_pos = -pos - 2) to find the position of the + * last index entry which is lexicographically smaller than + * the path. This would be the sparse directory containing + * the path. By identifying the sparse directory containing + * the path, we can correctly read the attributes specified + * in the .gitattributes file from the tree object of the + * sparse directory. */ - if (!path_in_cone_mode_sparse_checkout(path, istate)) - return NULL; + if (!path_in_cone_mode_sparse_checkout(path, istate)) { + int pos = index_name_pos_sparse(istate, path, strlen(path)); - 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; + if (pos < 0) + sparse_dir_pos = -pos - 2; } - return read_attr_from_buf(buf, path, flags); + if (sparse_dir_pos >= 0 && + S_ISSPARSEDIR(istate->cache[sparse_dir_pos]->ce_mode) && + !strncmp(istate->cache[sparse_dir_pos]->name, path, ce_namelen(istate->cache[sparse_dir_pos]))) { + const char *relative_path = path + ce_namelen(istate->cache[sparse_dir_pos]); + 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); + } + return stack; } static struct attr_stack *read_attr(struct index_state *istate, @@ -838,7 +838,7 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) if (is_shared_symref(worktrees[i], "HEAD", branch)) { skip_prefix(branch, "refs/heads/", &branch); - die(_("'%s' is already checked out at '%s'"), + die(_("'%s' is already used by worktree at '%s'"), branch, worktrees[i]->path); } } diff --git a/builtin/add.c b/builtin/add.c index 4b0dd798df..c27254a5cd 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -194,8 +194,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; - if (run_diff_files(&rev, 0)) - die(_("Could not write patch")); + run_diff_files(&rev, 0); if (launch_editor(file, NULL, NULL)) die(_("editing patch failed")); @@ -232,6 +231,8 @@ static char *chmod_arg; static int ignore_removal_cb(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_ARG(arg); + /* if we are told to ignore, we are not adding removals */ *(int *)opt->value = !unset ? 0 : 1; return 0; diff --git a/builtin/am.c b/builtin/am.c index 8bde034fae..6655059a57 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1430,7 +1430,7 @@ static void write_index_patch(const struct am_state *state) rev_info.diffopt.close_file = 1; add_pending_object(&rev_info, &tree->object, ""); diff_setup_done(&rev_info.diffopt); - run_diff_index(&rev_info, 1); + run_diff_index(&rev_info, DIFF_INDEX_CACHED); release_revisions(&rev_info); } @@ -1593,7 +1593,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa rev_info.diffopt.filter |= diff_filter_bit('M'); add_pending_oid(&rev_info, "HEAD", &our_tree, 0); diff_setup_done(&rev_info.diffopt); - run_diff_index(&rev_info, 1); + run_diff_index(&rev_info, DIFF_INDEX_CACHED); release_revisions(&rev_info); } @@ -2303,7 +2303,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) return error(_("options '%s=%s' and '%s=%s' " "cannot be used together"), - "--show-current-patch", "--show-current-patch", arg, valid_modes[resume->sub_mode]); + "--show-current-patch", arg, + "--show-current-patch", valid_modes[resume->sub_mode]); resume->mode = RESUME_SHOW_PATCH; resume->sub_mode = new_value; diff --git a/builtin/branch.c b/builtin/branch.c index 08da650516..2ec190b14a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -261,7 +261,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, const char *path; if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " - "checked out at '%s'"), + "used by worktree at '%s'"), bname.buf, path); ret = 1; continue; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index b22ff748c3..c1da1d184e 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -122,6 +122,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, check_attr_options, check_attr_usage, PARSE_OPT_KEEP_DASHDASH); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (repo_read_index(the_repository) < 0) { die("invalid cache"); } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index f62f13f2b5..3b68b47615 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -24,7 +24,7 @@ static int nul_term_line; static int checkout_stage; /* default to checkout stage0 */ static int ignore_skip_worktree; /* default to 0 */ -static int to_tempfile; +static int to_tempfile = -1; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; static struct checkout state = CHECKOUT_INIT; @@ -193,15 +193,16 @@ static const char * const builtin_checkout_index_usage[] = { static int option_parse_stage(const struct option *opt, const char *arg, int unset) { + int *stage = opt->value; + BUG_ON_OPT_NEG(unset); if (!strcmp(arg, "all")) { - to_tempfile = 1; - checkout_stage = CHECKOUT_ALL; + *stage = CHECKOUT_ALL; } else { int ch = arg[0]; if ('1' <= ch && ch <= '3') - checkout_stage = arg[0] - '0'; + *stage = arg[0] - '0'; else die(_("stage should be between 1 and 3 or all")); } @@ -239,7 +240,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) N_("write the content to temporary files")), OPT_STRING(0, "prefix", &state.base_dir, N_("string"), N_("when creating files, prepend <string>")), - OPT_CALLBACK_F(0, "stage", NULL, "(1|2|3|all)", + OPT_CALLBACK_F(0, "stage", &checkout_stage, "(1|2|3|all)", N_("copy out the files from named stage"), PARSE_OPT_NONEG, option_parse_stage), OPT_END() @@ -269,6 +270,12 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) state.base_dir = ""; state.base_dir_len = strlen(state.base_dir); + if (to_tempfile < 0) + to_tempfile = (checkout_stage == CHECKOUT_ALL); + if (!to_tempfile && checkout_stage == CHECKOUT_ALL) + die(_("options '%s' and '%s' cannot be used together"), + "--stage=all", "--no-temp"); + /* * when --prefix is specified we do not want to update cache. */ diff --git a/builtin/checkout.c b/builtin/checkout.c index f53612f468..f02434bc15 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -523,6 +523,15 @@ static int checkout_paths(const struct checkout_opts *opts, "--merge", "--conflict", "--staged"); } + /* + * recreating unmerged index entries and writing out data from + * unmerged index entries would make no sense when checking out + * of a tree-ish. + */ + if ((opts->merge || opts->writeout_stage) && opts->source_tree) + die(_("'%s', '%s', or '%s' cannot be used when checking out of a tree"), + "--merge", "--ours", "--theirs"); + if (opts->patch_mode) { enum add_p_mode patch_mode; const char *rev = new_branch_info->name; @@ -560,6 +569,8 @@ 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); ps_matched = xcalloc(opts->pathspec.nr, 1); @@ -583,10 +594,6 @@ static int checkout_paths(const struct checkout_opts *opts, } free(ps_matched); - /* "checkout -m path" to recreate conflicted state */ - if (opts->merge) - unmerge_marked_index(&the_index); - /* Any unmerged paths? */ for (pos = 0; pos < the_index.cache_nr; pos++) { const struct cache_entry *ce = the_index.cache[pos]; diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index c88389df24..45d035af60 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -69,10 +69,12 @@ static int graph_verify(int argc, const char **argv, const char *prefix) struct commit_graph *graph = NULL; struct object_directory *odb = NULL; char *graph_name; - int open_ok; + char *chain_name; + enum { OPENED_NONE, OPENED_GRAPH, OPENED_CHAIN } opened = OPENED_NONE; int fd; struct stat st; int flags = 0; + int incomplete_chain = 0; int ret; static struct option builtin_commit_graph_verify_options[] = { @@ -102,24 +104,39 @@ static int graph_verify(int argc, const char **argv, const char *prefix) odb = find_odb(the_repository, opts.obj_dir); graph_name = get_commit_graph_filename(odb); - open_ok = open_commit_graph(graph_name, &fd, &st); - if (!open_ok && errno != ENOENT) + chain_name = get_commit_graph_chain_filename(odb); + if (open_commit_graph(graph_name, &fd, &st)) + opened = OPENED_GRAPH; + else if (errno != ENOENT) die_errno(_("Could not open commit-graph '%s'"), graph_name); + else if (open_commit_graph_chain(chain_name, &fd, &st)) + opened = OPENED_CHAIN; + else if (errno != ENOENT) + die_errno(_("could not open commit-graph chain '%s'"), chain_name); FREE_AND_NULL(graph_name); + FREE_AND_NULL(chain_name); FREE_AND_NULL(options); - if (open_ok) + if (opened == OPENED_NONE) + return 0; + else if (opened == OPENED_GRAPH) graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb); else - graph = read_commit_graph_one(the_repository, odb); + graph = load_commit_graph_chain_fd_st(the_repository, fd, &st, + &incomplete_chain); - /* Return failure if open_ok predicted success */ if (!graph) - return !!open_ok; + return 1; ret = verify_commit_graph(the_repository, graph, flags); free_commit_graph(graph); + + if (incomplete_chain) { + error("one or more commit-graph chain files could not be loaded"); + ret |= 1; + } + return ret; } @@ -311,6 +328,7 @@ cleanup: FREE_AND_NULL(options); string_list_clear(&pack_indexes, 0); strbuf_release(&buf); + oidset_clear(&commits); return result; } diff --git a/builtin/describe.c b/builtin/describe.c index b28a4a1f82..fb6b0508f3 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -561,9 +561,11 @@ static void describe(const char *arg, int last_one) static int option_parse_exact_match(const struct option *opt, const char *arg, int unset) { + int *val = opt->value; + BUG_ON_OPT_ARG(arg); - max_candidates = unset ? DEFAULT_CANDIDATES : 0; + *val = unset ? DEFAULT_CANDIDATES : 0; return 0; } @@ -578,7 +580,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "long", &longformat, N_("always use long format")), OPT_BOOL(0, "first-parent", &first_parent, N_("only follow first parent")), OPT__ABBREV(&abbrev), - OPT_CALLBACK_F(0, "exact-match", NULL, NULL, + OPT_CALLBACK_F(0, "exact-match", &max_candidates, NULL, N_("only output exact matches"), PARSE_OPT_NOARG, option_parse_exact_match), OPT_INTEGER(0, "candidates", &max_candidates, @@ -668,7 +670,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) struct lock_file index_lock = LOCK_INIT; struct rev_info revs; struct strvec args = STRVEC_INIT; - int fd, result; + int fd; setup_work_tree(); prepare_repo_settings(the_repository); @@ -685,9 +687,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) strvec_pushv(&args, diff_index_args); if (setup_revisions(args.nr, args.v, &revs, NULL) != 1) BUG("malformed internal diff-index command line"); - result = run_diff_index(&revs, 0); + run_diff_index(&revs, 0); - if (!diff_result_code(&revs.diffopt, result)) + if (!diff_result_code(&revs.diffopt)) suffix = NULL; else suffix = dirty; diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 50330b8dd2..f38912cd40 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -80,14 +80,10 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) diff_merges_set_dense_combined_if_unset(&rev); - if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) { - perror("repo_read_index_preload"); - result = -1; - goto cleanup; - } - result = run_diff_files(&rev, options); - result = diff_result_code(&rev.diffopt, result); -cleanup: + if (repo_read_index_preload(the_repository, &rev.diffopt.pathspec, 0) < 0) + die_errno("repo_read_index_preload"); + run_diff_files(&rev, options); + result = diff_result_code(&rev.diffopt); release_revisions(&rev); return result; } diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 9db7139b83..220f341ffa 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -72,8 +72,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) perror("repo_read_index"); return -1; } - result = run_diff_index(&rev, option); - result = diff_result_code(&rev.diffopt, result); + run_diff_index(&rev, option); + result = diff_result_code(&rev.diffopt); release_revisions(&rev); return result; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index c9ba35f143..86be634286 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -232,5 +232,5 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) diff_free(&opt->diffopt); } - return diff_result_code(&opt->diffopt, 0); + return diff_result_code(&opt->diffopt); } diff --git a/builtin/diff.c b/builtin/diff.c index b19530c996..55e7d21755 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -77,9 +77,9 @@ static void stuff_change(struct diff_options *opt, diff_queue(&diff_queued_diff, one, two); } -static int builtin_diff_b_f(struct rev_info *revs, - int argc, const char **argv UNUSED, - struct object_array_entry **blob) +static void builtin_diff_b_f(struct rev_info *revs, + int argc, const char **argv UNUSED, + struct object_array_entry **blob) { /* Blob vs file in the working tree*/ struct stat st; @@ -109,12 +109,11 @@ static int builtin_diff_b_f(struct rev_info *revs, path); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); - return 0; } -static int builtin_diff_blobs(struct rev_info *revs, - int argc, const char **argv UNUSED, - struct object_array_entry **blob) +static void builtin_diff_blobs(struct rev_info *revs, + int argc, const char **argv UNUSED, + struct object_array_entry **blob) { const unsigned mode = canon_mode(S_IFREG | 0644); @@ -134,11 +133,10 @@ static int builtin_diff_blobs(struct rev_info *revs, blob_path(blob[0]), blob_path(blob[1])); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); - return 0; } -static int builtin_diff_index(struct rev_info *revs, - int argc, const char **argv) +static void builtin_diff_index(struct rev_info *revs, + int argc, const char **argv) { unsigned int option = 0; while (1 < argc) { @@ -163,20 +161,18 @@ static int builtin_diff_index(struct rev_info *revs, setup_work_tree(); if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec, 0) < 0) { - perror("repo_read_index_preload"); - return -1; + die_errno("repo_read_index_preload"); } } else if (repo_read_index(the_repository) < 0) { - perror("repo_read_cache"); - return -1; + die_errno("repo_read_cache"); } - return run_diff_index(revs, option); + run_diff_index(revs, option); } -static int builtin_diff_tree(struct rev_info *revs, - int argc, const char **argv, - struct object_array_entry *ent0, - struct object_array_entry *ent1) +static void builtin_diff_tree(struct rev_info *revs, + int argc, const char **argv, + struct object_array_entry *ent0, + struct object_array_entry *ent1) { const struct object_id *(oid[2]); struct object_id mb_oid; @@ -209,13 +205,12 @@ static int builtin_diff_tree(struct rev_info *revs, } diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); log_tree_diff_flush(revs); - return 0; } -static int builtin_diff_combined(struct rev_info *revs, - int argc, const char **argv UNUSED, - struct object_array_entry *ent, - int ents, int first_non_parent) +static void builtin_diff_combined(struct rev_info *revs, + int argc, const char **argv UNUSED, + struct object_array_entry *ent, + int ents, int first_non_parent) { struct oid_array parents = OID_ARRAY_INIT; int i; @@ -236,7 +231,6 @@ static int builtin_diff_combined(struct rev_info *revs, } diff_tree_combined(&ent[first_non_parent].item->oid, &parents, revs); oid_array_clear(&parents); - return 0; } static void refresh_index_quietly(void) @@ -254,7 +248,7 @@ static void refresh_index_quietly(void) repo_update_index_if_able(the_repository, &lock_file); } -static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) +static void builtin_diff_files(struct rev_info *revs, int argc, const char **argv) { unsigned int options = 0; @@ -269,8 +263,10 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv options |= DIFF_SILENT_ON_REMOVED; else if (!strcmp(argv[1], "-h")) usage(builtin_diff_usage); - else - return error(_("invalid option: %s"), argv[1]); + else { + error(_("invalid option: %s"), argv[1]); + usage(builtin_diff_usage); + } argv++; argc--; } @@ -287,10 +283,9 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv setup_work_tree(); if (repo_read_index_preload(the_repository, &revs->diffopt.pathspec, 0) < 0) { - perror("repo_read_index_preload"); - return -1; + die_errno("repo_read_index_preload"); } - return run_diff_files(revs, options); + run_diff_files(revs, options); } struct symdiff { @@ -404,7 +399,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) int blobs = 0, paths = 0; struct object_array_entry *blob[2]; int nongit = 0, no_index = 0; - int result = 0; + int result; struct symdiff sdiff; /* @@ -479,8 +474,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &rev, prefix); /* Set up defaults that will apply to both no-index and regular diffs. */ - rev.diffopt.stat_width = -1; - rev.diffopt.stat_graph_width = -1; + init_diffstat_widths(&rev.diffopt); rev.diffopt.flags.allow_external = 1; rev.diffopt.flags.allow_textconv = 1; @@ -583,17 +577,17 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (!ent.nr) { switch (blobs) { case 0: - result = builtin_diff_files(&rev, argc, argv); + builtin_diff_files(&rev, argc, argv); break; case 1: if (paths != 1) usage(builtin_diff_usage); - result = builtin_diff_b_f(&rev, argc, argv, blob); + builtin_diff_b_f(&rev, argc, argv, blob); break; case 2: if (paths) usage(builtin_diff_usage); - result = builtin_diff_blobs(&rev, argc, argv, blob); + builtin_diff_blobs(&rev, argc, argv, blob); break; default: usage(builtin_diff_usage); @@ -602,18 +596,18 @@ int cmd_diff(int argc, const char **argv, const char *prefix) else if (blobs) usage(builtin_diff_usage); else if (ent.nr == 1) - result = builtin_diff_index(&rev, argc, argv); + builtin_diff_index(&rev, argc, argv); else if (ent.nr == 2) { if (sdiff.warn) warning(_("%s...%s: multiple merge bases, using %s"), sdiff.left, sdiff.right, sdiff.base); - result = builtin_diff_tree(&rev, argc, argv, - &ent.objects[0], &ent.objects[1]); + builtin_diff_tree(&rev, argc, argv, + &ent.objects[0], &ent.objects[1]); } else - result = builtin_diff_combined(&rev, argc, argv, - ent.objects, ent.nr, - first_non_parent); - result = diff_result_code(&rev.diffopt, result); + builtin_diff_combined(&rev, argc, argv, + ent.objects, ent.nr, + first_non_parent); + result = diff_result_code(&rev.diffopt); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); release_revisions(&rev); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 56dc69fac1..70aff515ac 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -33,9 +33,9 @@ static const char *fast_export_usage[] = { }; static int progress; -static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT; -static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; -static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; +static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT; +static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; +static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; static int fake_missing_tagger; static int use_done_feature; static int no_data; @@ -53,16 +53,18 @@ static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) { + enum signed_tag_mode *val = opt->value; + if (unset || !strcmp(arg, "abort")) - signed_tag_mode = SIGNED_TAG_ABORT; + *val = SIGNED_TAG_ABORT; else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) - signed_tag_mode = VERBATIM; + *val = VERBATIM; else if (!strcmp(arg, "warn")) - signed_tag_mode = WARN; + *val = WARN; else if (!strcmp(arg, "warn-strip")) - signed_tag_mode = WARN_STRIP; + *val = WARN_STRIP; else if (!strcmp(arg, "strip")) - signed_tag_mode = STRIP; + *val = STRIP; else return error("Unknown signed-tags mode: %s", arg); return 0; @@ -71,12 +73,14 @@ static int parse_opt_signed_tag_mode(const struct option *opt, static int parse_opt_tag_of_filtered_mode(const struct option *opt, const char *arg, int unset) { + enum tag_of_filtered_mode *val = opt->value; + if (unset || !strcmp(arg, "abort")) - tag_of_filtered_mode = TAG_FILTERING_ABORT; + *val = TAG_FILTERING_ABORT; else if (!strcmp(arg, "drop")) - tag_of_filtered_mode = DROP; + *val = DROP; else if (!strcmp(arg, "rewrite")) - tag_of_filtered_mode = REWRITE; + *val = REWRITE; else return error("Unknown tag-of-filtered mode: %s", arg); return 0; @@ -85,21 +89,23 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt, static int parse_opt_reencode_mode(const struct option *opt, const char *arg, int unset) { + enum reencode_mode *val = opt->value; + if (unset) { - reencode_mode = REENCODE_ABORT; + *val = REENCODE_ABORT; return 0; } switch (git_parse_maybe_bool(arg)) { case 0: - reencode_mode = REENCODE_NO; + *val = REENCODE_NO; break; case 1: - reencode_mode = REENCODE_YES; + *val = REENCODE_YES; break; default: if (!strcasecmp(arg, "abort")) - reencode_mode = REENCODE_ABORT; + *val = REENCODE_ABORT; else return error("Unknown reencoding mode: %s", arg); } diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 4dbb10aff3..444f41cf8c 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -1102,6 +1102,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) || (pack_size + PACK_SIZE_THRESHOLD + len) < pack_size) cycle_packfile(); + the_hash_algo->init_fn(&checkpoint.ctx); hashfile_checkpoint(pack_file, &checkpoint); offset = checkpoint.offset; diff --git a/builtin/fetch.c b/builtin/fetch.c index eed4a7cdb6..fd134ba74d 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -176,7 +176,7 @@ static int parse_refmap_arg(const struct option *opt, const char *arg, int unset * "git fetch --refmap='' origin foo" * can be used to tell the command not to store anywhere */ - refspec_append(&refmap, arg); + refspec_append(opt->value, arg); return 0; } @@ -308,7 +308,7 @@ static void clear_item(struct refname_hash_entry *item) static void add_already_queued_tags(const char *refname, - const struct object_id *old_oid, + const struct object_id *old_oid UNUSED, const struct object_id *new_oid, void *cb_data) { @@ -2204,7 +2204,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), - OPT_CALLBACK_F(0, "refmap", NULL, N_("refmap"), + OPT_CALLBACK_F(0, "refmap", &refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg), OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")), OPT_IPVERSION(&family), diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c index 7e99c4d61b..5d01db5c02 100644 --- a/builtin/fsmonitor--daemon.c +++ b/builtin/fsmonitor--daemon.c @@ -129,8 +129,9 @@ struct fsmonitor_cookie_item { enum fsmonitor_cookie_item_result result; }; -static int cookies_cmp(const void *data, const struct hashmap_entry *he1, - const struct hashmap_entry *he2, const void *keydata) +static int cookies_cmp(const void *data UNUSED, + const struct hashmap_entry *he1, + const struct hashmap_entry *he2, const void *keydata) { const struct fsmonitor_cookie_item *a = container_of(he1, const struct fsmonitor_cookie_item, entry); @@ -1412,7 +1413,7 @@ done: return err; } -static int try_to_run_foreground_daemon(int detach_console) +static int try_to_run_foreground_daemon(int detach_console MAYBE_UNUSED) { /* * Technically, we don't need to probe for an existing daemon @@ -1442,7 +1443,8 @@ static int try_to_run_foreground_daemon(int detach_console) static start_bg_wait_cb bg_wait_cb; -static int bg_wait_cb(const struct child_process *cp, void *cb_data) +static int bg_wait_cb(const struct child_process *cp UNUSED, + void *cb_data UNUSED) { enum ipc_active_state s = fsmonitor_ipc__get_state(); diff --git a/builtin/gc.c b/builtin/gc.c index 5c4315f0d8..7c11d5ebef 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -52,6 +52,7 @@ static const char * const builtin_gc_usage[] = { static int pack_refs = 1; static int prune_reflogs = 1; static int cruft_packs = 1; +static unsigned long max_cruft_size; static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; @@ -61,6 +62,8 @@ static timestamp_t gc_log_expire_time; static const char *gc_log_expire = "1.day.ago"; static const char *prune_expire = "2.weeks.ago"; static const char *prune_worktrees_expire = "3.months.ago"; +static char *repack_filter; +static char *repack_filter_to; static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; @@ -163,6 +166,7 @@ static void gc_config(void) git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); git_config_get_bool("gc.cruftpacks", &cruft_packs); + git_config_get_ulong("gc.maxcruftsize", &max_cruft_size); git_config_get_expiry("gc.pruneexpire", &prune_expire); git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); git_config_get_expiry("gc.logexpiry", &gc_log_expire); @@ -170,6 +174,9 @@ static void gc_config(void) git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold); git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size); + git_config_get_string("gc.repackfilter", &repack_filter); + git_config_get_string("gc.repackfilterto", &repack_filter_to); + git_config(git_default_config, NULL); } @@ -347,6 +354,9 @@ static void add_repack_all_option(struct string_list *keep_pack) strvec_push(&repack, "--cruft"); if (prune_expire) strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire); + if (max_cruft_size) + strvec_pushf(&repack, "--max-cruft-size=%lu", + max_cruft_size); } else { strvec_push(&repack, "-A"); if (prune_expire) @@ -355,6 +365,11 @@ static void add_repack_all_option(struct string_list *keep_pack) if (keep_pack) for_each_string_list(keep_pack, keep_one_pack, NULL); + + if (repack_filter && *repack_filter) + strvec_pushf(&repack, "--filter=%s", repack_filter); + if (repack_filter_to && *repack_filter_to) + strvec_pushf(&repack, "--filter-to=%s", repack_filter_to); } static void add_repack_incremental_option(void) @@ -575,6 +590,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) N_("prune unreferenced objects"), PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOL(0, "cruft", &cruft_packs, N_("pack unreferenced objects separately")), + OPT_MAGNITUDE(0, "max-cruft-size", &max_cruft_size, + N_("with --cruft, limit the size of new cruft packs")), OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"), PARSE_OPT_NOCOMPLETE), @@ -1403,7 +1420,7 @@ static void initialize_task_config(int schedule) strbuf_release(&config_name); } -static int task_option_parse(const struct option *opt, +static int task_option_parse(const struct option *opt UNUSED, const char *arg, int unset) { int i, num_selected = 0; @@ -1708,6 +1725,15 @@ static int get_schedule_cmd(const char **cmd, int *is_available) return 1; } +static int get_random_minute(void) +{ + /* Use a static value when under tests. */ + if (getenv("GIT_TEST_MAINT_SCHEDULER")) + return 13; + + return git_rand() % 60; +} + static int is_launchctl_available(void) { const char *cmd = "launchctl"; @@ -1820,6 +1846,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT; struct stat st; const char *cmd = "launchctl"; + int minute = get_random_minute(); get_schedule_cmd(&cmd, NULL); preamble = "<?xml version=\"1.0\"?>\n" @@ -1845,29 +1872,30 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit case SCHEDULE_HOURLY: repeat = "<dict>\n" "<key>Hour</key><integer>%d</integer>\n" - "<key>Minute</key><integer>0</integer>\n" + "<key>Minute</key><integer>%d</integer>\n" "</dict>\n"; for (i = 1; i <= 23; i++) - strbuf_addf(&plist, repeat, i); + strbuf_addf(&plist, repeat, i, minute); break; case SCHEDULE_DAILY: repeat = "<dict>\n" "<key>Day</key><integer>%d</integer>\n" "<key>Hour</key><integer>0</integer>\n" - "<key>Minute</key><integer>0</integer>\n" + "<key>Minute</key><integer>%d</integer>\n" "</dict>\n"; for (i = 1; i <= 6; i++) - strbuf_addf(&plist, repeat, i); + strbuf_addf(&plist, repeat, i, minute); break; case SCHEDULE_WEEKLY: - strbuf_addstr(&plist, - "<dict>\n" - "<key>Day</key><integer>0</integer>\n" - "<key>Hour</key><integer>0</integer>\n" - "<key>Minute</key><integer>0</integer>\n" - "</dict>\n"); + strbuf_addf(&plist, + "<dict>\n" + "<key>Day</key><integer>0</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>%d</integer>\n" + "</dict>\n", + minute); break; default: @@ -1923,7 +1951,7 @@ static int launchctl_add_plists(void) launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY); } -static int launchctl_update_schedule(int run_maintenance, int fd) +static int launchctl_update_schedule(int run_maintenance, int fd UNUSED) { if (run_maintenance) return launchctl_add_plists(); @@ -1984,6 +2012,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority const char *frequency = get_frequency(schedule); char *name = schtasks_task_name(frequency); struct strbuf tfilename = STRBUF_INIT; + int minute = get_random_minute(); get_schedule_cmd(&cmd, NULL); @@ -2004,7 +2033,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority switch (schedule) { case SCHEDULE_HOURLY: fprintf(tfile->fp, - "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n" + "<StartBoundary>2020-01-01T01:%02d:00</StartBoundary>\n" "<Enabled>true</Enabled>\n" "<ScheduleByDay>\n" "<DaysInterval>1</DaysInterval>\n" @@ -2013,12 +2042,13 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority "<Interval>PT1H</Interval>\n" "<Duration>PT23H</Duration>\n" "<StopAtDurationEnd>false</StopAtDurationEnd>\n" - "</Repetition>\n"); + "</Repetition>\n", + minute); break; case SCHEDULE_DAILY: fprintf(tfile->fp, - "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n" "<Enabled>true</Enabled>\n" "<ScheduleByWeek>\n" "<DaysOfWeek>\n" @@ -2030,19 +2060,21 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority "<Saturday />\n" "</DaysOfWeek>\n" "<WeeksInterval>1</WeeksInterval>\n" - "</ScheduleByWeek>\n"); + "</ScheduleByWeek>\n", + minute); break; case SCHEDULE_WEEKLY: fprintf(tfile->fp, - "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<StartBoundary>2020-01-01T00:%02d:00</StartBoundary>\n" "<Enabled>true</Enabled>\n" "<ScheduleByWeek>\n" "<DaysOfWeek>\n" "<Sunday />\n" "</DaysOfWeek>\n" "<WeeksInterval>1</WeeksInterval>\n" - "</ScheduleByWeek>\n"); + "</ScheduleByWeek>\n", + minute); break; default: @@ -2100,7 +2132,7 @@ static int schtasks_schedule_tasks(void) schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY); } -static int schtasks_update_schedule(int run_maintenance, int fd) +static int schtasks_update_schedule(int run_maintenance, int fd UNUSED) { if (run_maintenance) return schtasks_schedule_tasks(); @@ -2159,6 +2191,7 @@ static int crontab_update_schedule(int run_maintenance, int fd) FILE *cron_list, *cron_in; struct strbuf line = STRBUF_INIT; struct tempfile *tmpedit = NULL; + int minute = get_random_minute(); get_schedule_cmd(&cmd, NULL); strvec_split(&crontab_list.args, cmd); @@ -2213,11 +2246,11 @@ static int crontab_update_schedule(int run_maintenance, int fd) "# replaced in the future by a Git command.\n\n"); strbuf_addf(&line_format, - "%%s %%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 --config=maintenance.repo maintenance run --schedule=%%s\n", exec_path, exec_path); - fprintf(cron_in, line_format.buf, "0", "1-23", "*", "hourly"); - fprintf(cron_in, line_format.buf, "0", "0", "1-6", "daily"); - fprintf(cron_in, line_format.buf, "0", "0", "0", "weekly"); + fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly"); + fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily"); + fprintf(cron_in, line_format.buf, minute, "0", "0", "weekly"); strbuf_release(&line_format); fprintf(cron_in, "\n%s\n", END_LINE); @@ -2276,77 +2309,54 @@ static char *xdg_config_home_systemd(const char *filename) return xdg_config_home_for("systemd/user", filename); } -static int systemd_timer_enable_unit(int enable, - enum schedule_priority schedule) -{ - const char *cmd = "systemctl"; - struct child_process child = CHILD_PROCESS_INIT; - const char *frequency = get_frequency(schedule); +#define SYSTEMD_UNIT_FORMAT "git-maintenance@%s.%s" - /* - * Disabling the systemd unit while it is already disabled makes - * systemctl print an error. - * Let's ignore it since it means we already are in the expected state: - * the unit is disabled. - * - * On the other hand, enabling a systemd unit which is already enabled - * produces no error. - */ - if (!enable) - child.no_stderr = 1; - - get_schedule_cmd(&cmd, NULL); - strvec_split(&child.args, cmd); - strvec_pushl(&child.args, "--user", enable ? "enable" : "disable", - "--now", NULL); - strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency); - - if (start_command(&child)) - return error(_("failed to start systemctl")); - if (finish_command(&child)) - /* - * Disabling an already disabled systemd unit makes - * systemctl fail. - * Let's ignore this failure. - * - * Enabling an enabled systemd unit doesn't fail. - */ - if (enable) - return error(_("failed to run systemctl")); - return 0; -} - -static int systemd_timer_delete_unit_templates(void) +static int systemd_timer_delete_timer_file(enum schedule_priority priority) { int ret = 0; - char *filename = xdg_config_home_systemd("git-maintenance@.timer"); - if (unlink(filename) && !is_missing_file_error(errno)) - ret = error_errno(_("failed to delete '%s'"), filename); - FREE_AND_NULL(filename); + const char *frequency = get_frequency(priority); + char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer"); + char *filename = xdg_config_home_systemd(local_timer_name); - filename = xdg_config_home_systemd("git-maintenance@.service"); if (unlink(filename) && !is_missing_file_error(errno)) ret = error_errno(_("failed to delete '%s'"), filename); free(filename); + free(local_timer_name); return ret; } -static int systemd_timer_delete_units(void) +static int systemd_timer_delete_service_template(void) { - return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) || - systemd_timer_enable_unit(0, SCHEDULE_DAILY) || - systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) || - systemd_timer_delete_unit_templates(); + int ret = 0; + char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service"); + char *filename = xdg_config_home_systemd(local_service_name); + if (unlink(filename) && !is_missing_file_error(errno)) + ret = error_errno(_("failed to delete '%s'"), filename); + + free(filename); + free(local_service_name); + return ret; } -static int systemd_timer_write_unit_templates(const char *exec_path) +/* + * Write the schedule information into a git-maintenance@<schedule>.timer + * file using a custom minute. This timer file cannot use the templating + * system, so we generate a specific file for each. + */ +static int systemd_timer_write_timer_file(enum schedule_priority schedule, + int minute) { + int res = -1; char *filename; FILE *file; const char *unit; + char *schedule_pattern = NULL; + const char *frequency = get_frequency(schedule); + char *local_timer_name = xstrfmt(SYSTEMD_UNIT_FORMAT, frequency, "timer"); + + filename = xdg_config_home_systemd(local_timer_name); - filename = xdg_config_home_systemd("git-maintenance@.timer"); if (safe_create_leading_directories(filename)) { error(_("failed to create directories for '%s'"), filename); goto error; @@ -2355,6 +2365,23 @@ static int systemd_timer_write_unit_templates(const char *exec_path) if (!file) goto error; + switch (schedule) { + case SCHEDULE_HOURLY: + schedule_pattern = xstrfmt("*-*-* 1..23:%02d:00", minute); + break; + + case SCHEDULE_DAILY: + schedule_pattern = xstrfmt("Tue..Sun *-*-* 0:%02d:00", minute); + break; + + case SCHEDULE_WEEKLY: + schedule_pattern = xstrfmt("Mon 0:%02d:00", minute); + break; + + default: + BUG("Unhandled schedule_priority"); + } + unit = "# This file was created and is maintained by Git.\n" "# Any edits made in this file might be replaced in the future\n" "# by a Git command.\n" @@ -2363,12 +2390,12 @@ static int systemd_timer_write_unit_templates(const char *exec_path) "Description=Optimize Git repositories data\n" "\n" "[Timer]\n" - "OnCalendar=%i\n" + "OnCalendar=%s\n" "Persistent=true\n" "\n" "[Install]\n" "WantedBy=timers.target\n"; - if (fputs(unit, file) == EOF) { + if (fprintf(file, unit, schedule_pattern) < 0) { error(_("failed to write to '%s'"), filename); fclose(file); goto error; @@ -2377,9 +2404,36 @@ static int systemd_timer_write_unit_templates(const char *exec_path) error_errno(_("failed to flush '%s'"), filename); goto error; } + + res = 0; + +error: + free(schedule_pattern); + free(local_timer_name); free(filename); + return res; +} - filename = xdg_config_home_systemd("git-maintenance@.service"); +/* + * No matter the schedule, we use the same service and can make use of the + * templating system. When installing git-maintenance@<schedule>.timer, + * systemd will notice that git-maintenance@.service exists as a template + * and will use this file and insert the <schedule> into the template at + * the position of "%i". + */ +static int systemd_timer_write_service_template(const char *exec_path) +{ + int res = -1; + char *filename; + FILE *file; + const char *unit; + char *local_service_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "service"); + + filename = xdg_config_home_systemd(local_service_name); + if (safe_create_leading_directories(filename)) { + error(_("failed to create directories for '%s'"), filename); + goto error; + } file = fopen_or_warn(filename, "w"); if (!file) goto error; @@ -2397,7 +2451,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path) "LockPersonality=yes\n" "MemoryDenyWriteExecute=yes\n" "NoNewPrivileges=yes\n" - "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n" + "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_VSOCK\n" "RestrictNamespaces=yes\n" "RestrictRealtime=yes\n" "RestrictSUIDSGID=yes\n" @@ -2412,29 +2466,114 @@ static int systemd_timer_write_unit_templates(const char *exec_path) error_errno(_("failed to flush '%s'"), filename); goto error; } + + res = 0; + +error: + free(local_service_name); free(filename); + return res; +} + +static int systemd_timer_enable_unit(int enable, + enum schedule_priority schedule, + int minute) +{ + const char *cmd = "systemctl"; + struct child_process child = CHILD_PROCESS_INIT; + const char *frequency = get_frequency(schedule); + + /* + * Disabling the systemd unit while it is already disabled makes + * systemctl print an error. + * Let's ignore it since it means we already are in the expected state: + * the unit is disabled. + * + * On the other hand, enabling a systemd unit which is already enabled + * produces no error. + */ + if (!enable) + child.no_stderr = 1; + else if (systemd_timer_write_timer_file(schedule, minute)) + return -1; + + get_schedule_cmd(&cmd, NULL); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "--user", enable ? "enable" : "disable", + "--now", NULL); + strvec_pushf(&child.args, SYSTEMD_UNIT_FORMAT, frequency, "timer"); + + if (start_command(&child)) + return error(_("failed to start systemctl")); + if (finish_command(&child)) + /* + * Disabling an already disabled systemd unit makes + * systemctl fail. + * Let's ignore this failure. + * + * Enabling an enabled systemd unit doesn't fail. + */ + if (enable) + return error(_("failed to run systemctl")); return 0; +} + +/* + * A previous version of Git wrote the timer units as template files. + * Clean these up, if they exist. + */ +static void systemd_timer_delete_stale_timer_templates(void) +{ + char *timer_template_name = xstrfmt(SYSTEMD_UNIT_FORMAT, "", "timer"); + char *filename = xdg_config_home_systemd(timer_template_name); + + if (unlink(filename) && !is_missing_file_error(errno)) + warning(_("failed to delete '%s'"), filename); -error: free(filename); - systemd_timer_delete_unit_templates(); - return -1; + free(timer_template_name); +} + +static int systemd_timer_delete_unit_files(void) +{ + systemd_timer_delete_stale_timer_templates(); + + /* Purposefully not short-circuited to make sure all are called. */ + return systemd_timer_delete_timer_file(SCHEDULE_HOURLY) | + systemd_timer_delete_timer_file(SCHEDULE_DAILY) | + systemd_timer_delete_timer_file(SCHEDULE_WEEKLY) | + systemd_timer_delete_service_template(); +} + +static int systemd_timer_delete_units(void) +{ + int minute = get_random_minute(); + /* Purposefully not short-circuited to make sure all are called. */ + return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, minute) | + systemd_timer_enable_unit(0, SCHEDULE_DAILY, minute) | + systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, minute) | + systemd_timer_delete_unit_files(); } static int systemd_timer_setup_units(void) { + int minute = get_random_minute(); const char *exec_path = git_exec_path(); - int ret = systemd_timer_write_unit_templates(exec_path) || - systemd_timer_enable_unit(1, SCHEDULE_HOURLY) || - systemd_timer_enable_unit(1, SCHEDULE_DAILY) || - systemd_timer_enable_unit(1, SCHEDULE_WEEKLY); + int ret = systemd_timer_write_service_template(exec_path) || + systemd_timer_enable_unit(1, SCHEDULE_HOURLY, minute) || + systemd_timer_enable_unit(1, SCHEDULE_DAILY, minute) || + systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, minute); + if (ret) systemd_timer_delete_units(); + else + systemd_timer_delete_stale_timer_templates(); + return ret; } -static int systemd_timer_update_schedule(int run_maintenance, int fd) +static int systemd_timer_update_schedule(int run_maintenance, int fd UNUSED) { if (run_maintenance) return systemd_timer_setup_units(); @@ -2606,9 +2745,12 @@ static int maintenance_start(int argc, const char **argv, const char *prefix) opts.scheduler = resolve_scheduler(opts.scheduler); validate_scheduler(opts.scheduler); + if (update_background_schedule(&opts, 1)) + die(_("failed to set up maintenance schedule")); + if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL)) warning(_("failed to add repo to global config")); - return update_background_schedule(&opts, 1); + return 0; } static const char *const builtin_maintenance_stop_usage[] = { diff --git a/builtin/grep.c b/builtin/grep.c index 50e712a184..b71222330a 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -924,9 +924,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("process binary files with textconv filters")), OPT_SET_INT('r', "recursive", &opt.max_depth, N_("search in subdirectories (default)"), -1), - { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"), - N_("descend at most <depth> levels"), PARSE_OPT_NONEG, - NULL, 1 }, + OPT_INTEGER_F(0, "max-depth", &opt.max_depth, + N_("descend at most <n> levels"), PARSE_OPT_NONEG), OPT_GROUP(""), OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option, N_("use extended POSIX regular expressions"), @@ -990,7 +989,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F(0, "and", &opt, NULL, N_("combine patterns specified with -e"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback), - OPT_BOOL(0, "or", &dummy, ""), + OPT_BOOL_F(0, "or", &dummy, "", PARSE_OPT_NONEG), OPT_CALLBACK_F(0, "not", &opt, NULL, "", PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback), OPT_CALLBACK_F('(', NULL, &opt, NULL, "", diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 006ffdc9c5..dda94a9f46 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1166,6 +1166,7 @@ static void parse_pack_objects(unsigned char *hash) struct ofs_delta_entry *ofs_delta = ofs_deltas; struct object_id ref_delta_oid; struct stat st; + git_hash_ctx tmp_ctx; if (verbose) progress = start_progress( @@ -1202,7 +1203,9 @@ static void parse_pack_objects(unsigned char *hash) /* Check pack integrity */ flush(); - the_hash_algo->final_fn(hash, &input_ctx); + the_hash_algo->init_fn(&tmp_ctx); + the_hash_algo->clone_fn(&tmp_ctx, &input_ctx); + the_hash_algo->final_fn(hash, &tmp_ctx); if (!hasheq(fill(the_hash_algo->rawsz), hash)) die(_("pack is corrupted (SHA1 mismatch)")); use(the_hash_algo->rawsz); diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index c5e8345265..033bd1556c 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -14,7 +14,7 @@ static const char * const git_interpret_trailers_usage[] = { N_("git interpret-trailers [--in-place] [--trim-empty]\n" - " [(--trailer <token>[(=|:)<value>])...]\n" + " [(--trailer (<key>|<keyAlias>)[(=|:)<value>])...]\n" " [--parse] [<file>...]"), NULL }; @@ -24,21 +24,24 @@ static enum trailer_if_exists if_exists; static enum trailer_if_missing if_missing; static int option_parse_where(const struct option *opt, - const char *arg, int unset) + const char *arg, int unset UNUSED) { - return trailer_set_where(&where, arg); + /* unset implies NULL arg, which is handled in our helper */ + return trailer_set_where(opt->value, arg); } static int option_parse_if_exists(const struct option *opt, - const char *arg, int unset) + const char *arg, int unset UNUSED) { - return trailer_set_if_exists(&if_exists, arg); + /* unset implies NULL arg, which is handled in our helper */ + return trailer_set_if_exists(opt->value, arg); } static int option_parse_if_missing(const struct option *opt, - const char *arg, int unset) + const char *arg, int unset UNUSED) { - return trailer_set_if_missing(&if_missing, arg); + /* unset implies NULL arg, which is handled in our helper */ + return trailer_set_if_missing(opt->value, arg); } static void new_trailers_clear(struct list_head *trailers) @@ -97,19 +100,19 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")), OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")), - OPT_CALLBACK(0, "where", NULL, N_("action"), + OPT_CALLBACK(0, "where", &where, N_("placement"), N_("where to place the new trailer"), option_parse_where), - OPT_CALLBACK(0, "if-exists", NULL, N_("action"), + OPT_CALLBACK(0, "if-exists", &if_exists, N_("action"), N_("action if trailer already exists"), option_parse_if_exists), - OPT_CALLBACK(0, "if-missing", NULL, N_("action"), + OPT_CALLBACK(0, "if-missing", &if_missing, N_("action"), N_("action if trailer is missing"), option_parse_if_missing), OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), - OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")), - OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")), - OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("set parsing options"), + OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply trailer.* configuration variables")), + OPT_BOOL(0, "unfold", &opts.unfold, N_("reformat multiline trailer values as single-line values")), + OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("alias for --only-trailers --only-input --unfold"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse), - OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")), + OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat \"---\" as the end of input")), OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), N_("trailer(s) to add"), option_parse_trailer), OPT_END() diff --git a/builtin/log.c b/builtin/log.c index db3a88bfe9..ba775d7b5c 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -118,16 +118,19 @@ static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; -static int clear_decorations_callback(const struct option *opt, - const char *arg, int unset) +static int clear_decorations_callback(const struct option *opt UNUSED, + const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); string_list_clear(&decorate_refs_include, 0); string_list_clear(&decorate_refs_exclude, 0); use_default_decoration_filter = 0; return 0; } -static int decorate_callback(const struct option *opt, const char *arg, int unset) +static int decorate_callback(const struct option *opt UNUSED, const char *arg, + int unset) { if (unset) decoration_style = 0; @@ -173,16 +176,15 @@ static void cmd_log_init_defaults(struct rev_info *rev) if (default_follow) rev->diffopt.flags.default_follow_renames = 1; rev->verbose_header = 1; + init_diffstat_widths(&rev->diffopt); rev->diffopt.flags.recursive = 1; - rev->diffopt.stat_width = -1; /* use full terminal width */ - rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */ + rev->diffopt.flags.allow_textconv = 1; rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; rev->patch_name_max = fmt_patch_name_max; rev->show_signature = default_show_signature; rev->encode_email_headers = default_encode_email_headers; - rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) parse_date_format(default_date_mode, &rev->date_mode); @@ -549,7 +551,7 @@ static int cmd_log_walk_no_free(struct rev_info *rev) rev->diffopt.flags.check_failed) { return 02; } - return diff_result_code(&rev->diffopt, 0); + return diff_result_code(&rev->diffopt); } static int cmd_log_walk(struct rev_info *rev) @@ -1253,7 +1255,15 @@ static void show_diffstat(struct rev_info *rev, fprintf(rev->diffopt.file, "\n"); } +static void read_desc_file(struct strbuf *buf, const char *desc_file) +{ + if (strbuf_read_file(buf, desc_file, 0) < 0) + die_errno(_("unable to read branch description file '%s'"), + desc_file); +} + static void prepare_cover_text(struct pretty_print_context *pp, + const char *description_file, const char *branch_name, struct strbuf *sb, const char *encoding, @@ -1267,7 +1277,9 @@ static void prepare_cover_text(struct pretty_print_context *pp, if (cover_from_description_mode == COVER_FROM_NONE) goto do_pp; - if (branch_name && *branch_name) + if (description_file && *description_file) + read_desc_file(&description_sb, description_file); + else if (branch_name && *branch_name) read_branch_desc(&description_sb, branch_name); if (!description_sb.len) goto do_pp; @@ -1313,6 +1325,7 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev) static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct commit *origin, int nr, struct commit **list, + const char *description_file, const char *branch_name, int quiet) { @@ -1352,7 +1365,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.rev = rev; pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); - prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte); + prepare_cover_text(&pp, description_file, branch_name, &sb, + encoding, need_8bit_cte); fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -1468,19 +1482,16 @@ static int subject_prefix = 0; static int subject_prefix_callback(const struct option *opt, const char *arg, int unset) { + struct strbuf *sprefix; + BUG_ON_OPT_NEG(unset); + sprefix = opt->value; subject_prefix = 1; - ((struct rev_info *)opt->value)->subject_prefix = arg; + strbuf_reset(sprefix); + strbuf_addstr(sprefix, arg); return 0; } -static int rfc_callback(const struct option *opt, const char *arg, int unset) -{ - BUG_ON_OPT_NEG(unset); - BUG_ON_OPT_ARG(arg); - return subject_prefix_callback(opt, "RFC PATCH", unset); -} - static int numbered_cmdline_opt = 0; static int numbered_callback(const struct option *opt, const char *arg, @@ -1555,7 +1566,8 @@ static int inline_callback(const struct option *opt, const char *arg, int unset) return 0; } -static int header_callback(const struct option *opt, const char *arg, int unset) +static int header_callback(const struct option *opt UNUSED, const char *arg, + int unset) { if (unset) { string_list_clear(&extra_hdr, 0); @@ -1567,24 +1579,6 @@ static int header_callback(const struct option *opt, const char *arg, int unset) return 0; } -static int to_callback(const struct option *opt, const char *arg, int unset) -{ - if (unset) - string_list_clear(&extra_to, 0); - else - string_list_append(&extra_to, arg); - return 0; -} - -static int cc_callback(const struct option *opt, const char *arg, int unset) -{ - if (unset) - string_list_clear(&extra_cc, 0); - else - string_list_append(&extra_cc, arg); - return 0; -} - static int from_callback(const struct option *opt, const char *arg, int unset) { char **from = opt->value; @@ -1893,6 +1887,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int quiet = 0; const char *reroll_count = NULL; char *cover_from_description_arg = NULL; + char *description_file = NULL; char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; @@ -1907,6 +1902,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf rdiff_title = STRBUF_INIT; struct strbuf sprefix = STRBUF_INIT; int creation_factor = -1; + int rfc = 0; const struct option builtin_format_patch_options[] = { OPT_CALLBACK_F('n', "numbered", &numbered, NULL, @@ -1930,13 +1926,13 @@ 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_CALLBACK_F(0, "rfc", &rev, NULL, - N_("use [RFC PATCH] instead of [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback), + OPT_BOOL(0, "rfc", &rfc, N_("use [RFC PATCH] instead of [PATCH]")), 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")), - OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), + OPT_FILENAME(0, "description-file", &description_file, + N_("use branch description from file")), + OPT_CALLBACK_F(0, "subject-prefix", &sprefix, N_("prefix"), N_("use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback), OPT_CALLBACK_F('o', "output-directory", &output_directory, @@ -1957,8 +1953,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Messaging")), OPT_CALLBACK(0, "add-header", NULL, N_("header"), N_("add email header"), header_callback), - OPT_CALLBACK(0, "to", NULL, N_("email"), N_("add To: header"), to_callback), - OPT_CALLBACK(0, "cc", NULL, N_("email"), N_("add Cc: header"), cc_callback), + OPT_STRING_LIST(0, "to", &extra_to, N_("email"), N_("add To: header")), + OPT_STRING_LIST(0, "cc", &extra_cc, N_("email"), N_("add Cc: header")), OPT_CALLBACK_F(0, "from", &from, N_("ident"), N_("set From address to <ident> (or committer ident if absent)"), PARSE_OPT_OPTARG, from_callback), @@ -2016,11 +2012,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.max_parents = 1; rev.diffopt.flags.recursive = 1; rev.diffopt.no_free = 1; - rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; + strbuf_addstr(&sprefix, fmt_patch_subject_prefix); if (format_no_prefix) diff_set_noprefix(&rev.diffopt); @@ -2048,13 +2044,16 @@ 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 (reroll_count) { - strbuf_addf(&sprefix, "%s v%s", - rev.subject_prefix, reroll_count); + strbuf_addf(&sprefix, " v%s", reroll_count); rev.reroll_count = reroll_count; - rev.subject_prefix = sprefix.buf; } + rev.subject_prefix = sprefix.buf; + for (i = 0; i < extra_hdr.nr; i++) { strbuf_addstr(&buf, extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); @@ -2321,7 +2320,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, !!output_directory, - origin, nr, list, branch_name, quiet); + origin, nr, list, description_file, branch_name, quiet); print_bases(&bases, rev.diffopt.file); print_signature(rev.diffopt.file); total++; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index f558db5f3b..209d2dc0d5 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -241,7 +241,8 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, return recurse; } -static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, +static int show_tree_name_only(const struct object_id *oid UNUSED, + struct strbuf *base, const char *pathname, unsigned mode, void *context) { diff --git a/builtin/merge.c b/builtin/merge.c index de68910177..d748d46e13 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -79,8 +79,7 @@ static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; +static struct strvec xopts = STRVEC_INIT; static const char *branch; static char *branch_mergeoptions; static int verbosity; @@ -232,7 +231,7 @@ static void append_strategy(struct strategy *s) use_strategies[use_strategies_nr++] = s; } -static int option_parse_strategy(const struct option *opt, +static int option_parse_strategy(const struct option *opt UNUSED, const char *name, int unset) { if (unset) @@ -242,29 +241,9 @@ static int option_parse_strategy(const struct option *opt, return 0; } -static int option_parse_x(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - return 0; - - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); - return 0; -} - -static int option_parse_n(const struct option *opt, - const char *arg, int unset) -{ - BUG_ON_OPT_ARG(arg); - show_diffstat = unset; - return 0; -} - static struct option builtin_merge_options[] = { - OPT_CALLBACK_F('n', NULL, NULL, NULL, - N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG, option_parse_n), + OPT_SET_INT('n', NULL, &show_diffstat, + N_("do not show a diffstat at the end of the merge"), 0), OPT_BOOL(0, "stat", &show_diffstat, N_("show a diffstat at the end of the merge")), OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), @@ -285,10 +264,10 @@ static struct option builtin_merge_options[] = { OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("verify that the named commit has a valid GPG signature")), - OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"), + OPT_CALLBACK('s', "strategy", NULL, N_("strategy"), N_("merge strategy to use"), option_parse_strategy), - OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"), - N_("option for selected merge strategy"), option_parse_x), + OPT_STRVEC('X', "strategy-option", &xopts, N_("option=value"), + N_("option for selected merge strategy")), OPT_CALLBACK('m', "message", &merge_msg, N_("message"), N_("merge commit message (for a non-fast-forward merge)"), option_parse_message), @@ -487,8 +466,7 @@ static void finish(struct commit *head_commit, if (new_head && show_diffstat) { struct diff_options opts; repo_diff_setup(the_repository, &opts); - opts.stat_width = -1; /* use full terminal width */ - opts.stat_graph_width = -1; /* respect statGraphWidth config */ + init_diffstat_widths(&opts); opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; @@ -749,9 +727,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, o.show_rename_progress = show_progress == -1 ? isatty(2) : show_progress; - for (x = 0; x < xopts_nr; x++) - if (parse_merge_opt(&o, xopts[x])) - die(_("unknown strategy option: -X%s"), xopts[x]); + for (x = 0; x < xopts.nr; x++) + if (parse_merge_opt(&o, xopts.v[x])) + die(_("unknown strategy option: -X%s"), xopts.v[x]); o.branch1 = head_arg; o.branch2 = merge_remote_util(remoteheads->item)->name; @@ -777,7 +755,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, return clean ? 0 : 1; } else { return try_merge_command(the_repository, - strategy, xopts_nr, xopts, + strategy, xopts.nr, xopts.v, common, head_arg, remoteheads); } } @@ -1654,6 +1632,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (j = remoteheads; j; j = j->next) { struct commit_list *common_one; + struct commit *common_item; /* * Here we *have* to calculate the individual @@ -1663,7 +1642,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) common_one = repo_get_merge_bases(the_repository, head_commit, j->item); - if (!oideq(&common_one->item->object.oid, &j->item->object.oid)) { + common_item = common_one->item; + free_commit_list(common_one); + if (!oideq(&common_item->object.oid, &j->item->object.oid)) { up_to_date = 0; break; } diff --git a/builtin/mv.c b/builtin/mv.c index 05e7156034..c596515ad0 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -305,7 +305,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } if (S_ISDIR(st.st_mode) && lstat(dst, &dest_st) == 0) { - bad = _("cannot move directory over file"); + bad = _("destination already exists"); goto act_on_entry; } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index c706fa3720..2dd1807c4e 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -582,12 +582,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), - { - /* A Hidden OPT_BOOL */ - OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL, - N_("dereference tags in the input (internal use)"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1, - }, + OPT_HIDDEN_BOOL(0, "peel-tag", &peel_tag, + N_("dereference tags in the input (internal use)")), OPT_END(), }; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index d2a162d528..89a8b5a976 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3603,7 +3603,6 @@ static void read_cruft_objects(void) string_list_append(&discard_packs, buf.buf + 1); else string_list_append(&fresh_packs, buf.buf); - strbuf_reset(&buf); } string_list_sort(&discard_packs); @@ -3739,7 +3738,7 @@ static void show_object__ma_allow_promisor(struct object *obj, const char *name, show_object(obj, name, data); } -static int option_parse_missing_action(const struct option *opt, +static int option_parse_missing_action(const struct option *opt UNUSED, const char *arg, int unset) { assert(arg); @@ -4120,34 +4119,37 @@ static void add_extra_kept_packs(const struct string_list *names) static int option_parse_quiet(const struct option *opt, const char *arg, int unset) { + int *val = opt->value; + BUG_ON_OPT_ARG(arg); if (!unset) - progress = 0; - else if (!progress) - progress = 1; + *val = 0; + else if (!*val) + *val = 1; return 0; } static int option_parse_index_version(const struct option *opt, const char *arg, int unset) { + struct pack_idx_option *popts = opt->value; char *c; const char *val = arg; BUG_ON_OPT_NEG(unset); - pack_idx_opts.version = strtoul(val, &c, 10); - if (pack_idx_opts.version > 2) + popts->version = strtoul(val, &c, 10); + if (popts->version > 2) die(_("unsupported index version %s"), val); if (*c == ',' && c[1]) - pack_idx_opts.off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_opts.off32_limit & 0x80000000) + popts->off32_limit = strtoul(c+1, &c, 0); + if (*c || popts->off32_limit & 0x80000000) die(_("bad index version '%s'"), val); return 0; } -static int option_parse_unpack_unreachable(const struct option *opt, +static int option_parse_unpack_unreachable(const struct option *opt UNUSED, const char *arg, int unset) { if (unset) { @@ -4162,7 +4164,7 @@ static int option_parse_unpack_unreachable(const struct option *opt, return 0; } -static int option_parse_cruft_expiration(const struct option *opt, +static int option_parse_cruft_expiration(const struct option *opt UNUSED, const char *arg, int unset) { if (unset) { @@ -4190,7 +4192,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) LIST_OBJECTS_FILTER_INIT; struct option pack_objects_options[] = { - OPT_CALLBACK_F('q', "quiet", NULL, NULL, + OPT_CALLBACK_F('q', "quiet", &progress, NULL, N_("do not show progress meter"), PARSE_OPT_NOARG, option_parse_quiet), OPT_SET_INT(0, "progress", &progress, @@ -4200,7 +4202,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "all-progress-implied", &all_progress_implied, N_("similar to --all-progress when progress meter is shown")), - OPT_CALLBACK_F(0, "index-version", NULL, N_("<version>[,<offset>]"), + OPT_CALLBACK_F(0, "index-version", &pack_idx_opts, N_("<version>[,<offset>]"), N_("write the pack index file in the specified idx format version"), PARSE_OPT_NONEG, option_parse_index_version), OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, @@ -4383,7 +4385,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!HAVE_THREADS && delta_search_threads != 1) warning(_("no threads support, ignoring --threads")); - if (!pack_to_stdout && !pack_size_limit && !cruft) + if (!pack_to_stdout && !pack_size_limit) pack_size_limit = pack_size_limit_cfg; if (pack_to_stdout && pack_size_limit) die(_("--max-pack-size cannot be used to build a pack for transfer")); @@ -4400,12 +4402,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; - if (filter_options.choice) { - if (!pack_to_stdout) - die(_("cannot use --filter without --stdout")); - if (stdin_packs) - die(_("cannot use --filter with --stdin-packs")); - } + if (stdin_packs && filter_options.choice) + die(_("cannot use --filter with --stdin-packs")); if (stdin_packs && use_internal_rev_list) die(_("cannot use internal rev list with --stdin-packs")); @@ -4415,8 +4413,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) die(_("cannot use internal rev list with --cruft")); if (stdin_packs) die(_("cannot use --stdin-packs with --cruft")); - if (pack_size_limit) - die(_("cannot use --max-pack-size with --cruft")); } /* diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 1fec702a04..8196ca9dd8 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -49,7 +49,7 @@ static const char * const read_tree_usage[] = { NULL }; -static int index_output_cb(const struct option *opt, const char *arg, +static int index_output_cb(const struct option *opt UNUSED, const char *arg, int unset) { BUG_ON_OPT_NEG(unset); diff --git a/builtin/rebase.c b/builtin/rebase.c index 50cb85751f..f990811614 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1803,8 +1803,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* We want color (if set), but no pager */ repo_diff_setup(the_repository, &opts); - opts.stat_width = -1; /* use full terminal width */ - opts.stat_graph_width = -1; /* respect statGraphWidth config */ + init_diffstat_widths(&opts); opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index fb8e1549d1..8c4f0cb90a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -2527,10 +2527,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (cert_nonce_seed) push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL)); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= receive_unpack_limit) + if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; + else if (0 <= transfer_unpack_limit) + unpack_limit = transfer_unpack_limit; switch (determine_protocol_version_server()) { case protocol_v2: diff --git a/builtin/repack.c b/builtin/repack.c index 97051479e4..edaee4dbec 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -21,13 +21,14 @@ #include "pack.h" #include "pack-bitmap.h" #include "refs.h" +#include "list-objects-filter-options.h" #define ALL_INTO_ONE 1 #define LOOSEN_UNREACHABLE 2 #define PACK_CRUFT 4 #define DELETE_PACK 1 -#define CRUFT_PACK 2 +#define RETAIN_PACK 2 static int pack_everything; static int delta_base_offset = 1; @@ -52,11 +53,12 @@ struct pack_objects_args { const char *window_memory; const char *depth; const char *threads; - const char *max_pack_size; + unsigned long max_pack_size; int no_reuse_delta; int no_reuse_object; int quiet; int local; + struct list_objects_filter_options filter_options; }; static int repack_config(const char *var, const char *value, @@ -95,14 +97,143 @@ static int repack_config(const char *var, const char *value, return git_default_config(var, value, ctx, cb); } +struct existing_packs { + struct string_list kept_packs; + struct string_list non_kept_packs; + struct string_list cruft_packs; +}; + +#define EXISTING_PACKS_INIT { \ + .kept_packs = STRING_LIST_INIT_DUP, \ + .non_kept_packs = STRING_LIST_INIT_DUP, \ + .cruft_packs = STRING_LIST_INIT_DUP, \ +} + +static int has_existing_non_kept_packs(const struct existing_packs *existing) +{ + return existing->non_kept_packs.nr || existing->cruft_packs.nr; +} + +static void pack_mark_for_deletion(struct string_list_item *item) +{ + item->util = (void*)((uintptr_t)item->util | DELETE_PACK); +} + +static void pack_unmark_for_deletion(struct string_list_item *item) +{ + item->util = (void*)((uintptr_t)item->util & ~DELETE_PACK); +} + +static int pack_is_marked_for_deletion(struct string_list_item *item) +{ + return (uintptr_t)item->util & DELETE_PACK; +} + +static void pack_mark_retained(struct string_list_item *item) +{ + item->util = (void*)((uintptr_t)item->util | RETAIN_PACK); +} + +static int pack_is_retained(struct string_list_item *item) +{ + return (uintptr_t)item->util & RETAIN_PACK; +} + +static void mark_packs_for_deletion_1(struct string_list *names, + struct string_list *list) +{ + struct string_list_item *item; + const int hexsz = the_hash_algo->hexsz; + + for_each_string_list_item(item, list) { + char *sha1; + size_t len = strlen(item->string); + if (len < hexsz) + continue; + sha1 = item->string + len - hexsz; + + if (pack_is_retained(item)) { + pack_unmark_for_deletion(item); + } else if (!string_list_has_string(names, sha1)) { + /* + * Mark this pack for deletion, which ensures + * that this pack won't be included in a MIDX + * (if `--write-midx` was given) and that we + * will actually delete this pack (if `-d` was + * given). + */ + pack_mark_for_deletion(item); + } + } +} + +static void retain_cruft_pack(struct existing_packs *existing, + struct packed_git *cruft) +{ + struct strbuf buf = STRBUF_INIT; + struct string_list_item *item; + + strbuf_addstr(&buf, pack_basename(cruft)); + strbuf_strip_suffix(&buf, ".pack"); + + item = string_list_lookup(&existing->cruft_packs, buf.buf); + if (!item) + BUG("could not find cruft pack '%s'", pack_basename(cruft)); + + pack_mark_retained(item); + strbuf_release(&buf); +} + +static void mark_packs_for_deletion(struct existing_packs *existing, + struct string_list *names) + +{ + mark_packs_for_deletion_1(names, &existing->non_kept_packs); + mark_packs_for_deletion_1(names, &existing->cruft_packs); +} + +static void remove_redundant_pack(const char *dir_name, const char *base_name) +{ + struct strbuf buf = STRBUF_INIT; + struct multi_pack_index *m = get_local_multi_pack_index(the_repository); + strbuf_addf(&buf, "%s.pack", base_name); + if (m && midx_contains_pack(m, buf.buf)) + clear_midx_file(the_repository); + strbuf_insertf(&buf, 0, "%s/", dir_name); + unlink_pack_path(buf.buf, 1); + strbuf_release(&buf); +} + +static void remove_redundant_packs_1(struct string_list *packs) +{ + struct string_list_item *item; + for_each_string_list_item(item, packs) { + if (!pack_is_marked_for_deletion(item)) + continue; + remove_redundant_pack(packdir, item->string); + } +} + +static void remove_redundant_existing_packs(struct existing_packs *existing) +{ + remove_redundant_packs_1(&existing->non_kept_packs); + remove_redundant_packs_1(&existing->cruft_packs); +} + +static void existing_packs_release(struct existing_packs *existing) +{ + string_list_clear(&existing->kept_packs, 0); + string_list_clear(&existing->non_kept_packs, 0); + string_list_clear(&existing->cruft_packs, 0); +} + /* - * Adds all packs hex strings (pack-$HASH) to either fname_nonkept_list - * or fname_kept_list based on whether each pack has a corresponding + * Adds all packs hex strings (pack-$HASH) to either packs->non_kept + * or packs->kept based on whether each pack has a corresponding * .keep file or not. Packs without a .keep file are not to be kept * if we are going to pack everything into one file. */ -static void collect_pack_filenames(struct string_list *fname_nonkept_list, - struct string_list *fname_kept_list, +static void collect_pack_filenames(struct existing_packs *existing, const struct string_list *extra_keep) { struct packed_git *p; @@ -126,28 +257,16 @@ static void collect_pack_filenames(struct string_list *fname_nonkept_list, strbuf_strip_suffix(&buf, ".pack"); if ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep) - string_list_append(fname_kept_list, buf.buf); - else { - struct string_list_item *item; - item = string_list_append(fname_nonkept_list, buf.buf); - if (p->is_cruft) - item->util = (void*)(uintptr_t)CRUFT_PACK; - } + string_list_append(&existing->kept_packs, buf.buf); + else if (p->is_cruft) + string_list_append(&existing->cruft_packs, buf.buf); + else + string_list_append(&existing->non_kept_packs, buf.buf); } - string_list_sort(fname_kept_list); - strbuf_release(&buf); -} - -static void remove_redundant_pack(const char *dir_name, const char *base_name) -{ - struct strbuf buf = STRBUF_INIT; - struct multi_pack_index *m = get_local_multi_pack_index(the_repository); - strbuf_addf(&buf, "%s.pack", base_name); - if (m && midx_contains_pack(m, buf.buf)) - clear_midx_file(the_repository); - strbuf_insertf(&buf, 0, "%s/", dir_name); - unlink_pack_path(buf.buf, 1); + string_list_sort(&existing->kept_packs); + string_list_sort(&existing->non_kept_packs); + string_list_sort(&existing->cruft_packs); strbuf_release(&buf); } @@ -165,7 +284,7 @@ static void prepare_pack_objects(struct child_process *cmd, if (args->threads) strvec_pushf(&cmd->args, "--threads=%s", args->threads); if (args->max_pack_size) - strvec_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size); + strvec_pushf(&cmd->args, "--max-pack-size=%lu", args->max_pack_size); if (args->no_reuse_delta) strvec_pushf(&cmd->args, "--no-reuse-delta"); if (args->no_reuse_object) @@ -238,6 +357,18 @@ static struct generated_pack_data *populate_pack_exts(const char *name) return data; } +static int has_pack_ext(const struct generated_pack_data *data, + const char *ext) +{ + int i; + for (i = 0; i < ARRAY_SIZE(exts); i++) { + if (strcmp(exts[i].name, ext)) + continue; + return !!data->tempfiles[i]; + } + BUG("unknown pack extension: '%s'", ext); +} + static void repack_promisor_objects(const struct pack_objects_args *args, struct string_list *names) { @@ -303,6 +434,8 @@ struct pack_geometry { struct packed_git **pack; uint32_t pack_nr, pack_alloc; uint32_t split; + + int split_factor; }; static uint32_t geometry_pack_weight(struct packed_git *p) @@ -324,17 +457,13 @@ static int geometry_cmp(const void *va, const void *vb) return 0; } -static void init_pack_geometry(struct pack_geometry **geometry_p, - struct string_list *existing_kept_packs, +static void init_pack_geometry(struct pack_geometry *geometry, + struct existing_packs *existing, const struct pack_objects_args *args) { struct packed_git *p; - struct pack_geometry *geometry; struct strbuf buf = STRBUF_INIT; - *geometry_p = xcalloc(1, sizeof(struct pack_geometry)); - geometry = *geometry_p; - for (p = get_all_packs(the_repository); p; p = p->next) { if (args->local && !p->pack_local) /* @@ -346,23 +475,24 @@ static void init_pack_geometry(struct pack_geometry **geometry_p, if (!pack_kept_objects) { /* - * Any pack that has its pack_keep bit set will appear - * in existing_kept_packs below, but this saves us from - * doing a more expensive check. + * Any pack that has its pack_keep bit set will + * appear in existing->kept_packs below, but + * this saves us from doing a more expensive + * check. */ if (p->pack_keep) continue; /* - * The pack may be kept via the --keep-pack option; - * check 'existing_kept_packs' to determine whether to - * ignore it. + * The pack may be kept via the --keep-pack + * option; check 'existing->kept_packs' to + * determine whether to ignore it. */ strbuf_reset(&buf); strbuf_addstr(&buf, pack_basename(p)); strbuf_strip_suffix(&buf, ".pack"); - if (string_list_has_string(existing_kept_packs, buf.buf)) + if (string_list_has_string(&existing->kept_packs, buf.buf)) continue; } if (p->is_cruft) @@ -380,7 +510,7 @@ static void init_pack_geometry(struct pack_geometry **geometry_p, strbuf_release(&buf); } -static void split_pack_geometry(struct pack_geometry *geometry, int factor) +static void split_pack_geometry(struct pack_geometry *geometry) { uint32_t i; uint32_t split; @@ -399,12 +529,14 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor) struct packed_git *ours = geometry->pack[i]; struct packed_git *prev = geometry->pack[i - 1]; - if (unsigned_mult_overflows(factor, geometry_pack_weight(prev))) + if (unsigned_mult_overflows(geometry->split_factor, + geometry_pack_weight(prev))) die(_("pack %s too large to consider in geometric " "progression"), prev->pack_name); - if (geometry_pack_weight(ours) < factor * geometry_pack_weight(prev)) + if (geometry_pack_weight(ours) < + geometry->split_factor * geometry_pack_weight(prev)) break; } @@ -439,10 +571,12 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor) for (i = split; i < geometry->pack_nr; i++) { struct packed_git *ours = geometry->pack[i]; - if (unsigned_mult_overflows(factor, total_size)) + if (unsigned_mult_overflows(geometry->split_factor, + total_size)) die(_("pack %s too large to roll up"), ours->pack_name); - if (geometry_pack_weight(ours) < factor * total_size) { + if (geometry_pack_weight(ours) < + geometry->split_factor * total_size) { if (unsigned_add_overflows(total_size, geometry_pack_weight(ours))) die(_("pack %s too large to roll up"), @@ -492,13 +626,38 @@ static struct packed_git *get_preferred_pack(struct pack_geometry *geometry) return NULL; } +static void geometry_remove_redundant_packs(struct pack_geometry *geometry, + struct string_list *names, + struct existing_packs *existing) +{ + struct strbuf buf = STRBUF_INIT; + uint32_t i; + + for (i = 0; i < geometry->split; i++) { + struct packed_git *p = geometry->pack[i]; + if (string_list_has_string(names, hash_to_hex(p->hash))) + continue; + + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + + if ((p->pack_keep) || + (string_list_has_string(&existing->kept_packs, buf.buf))) + continue; + + remove_redundant_pack(packdir, buf.buf); + } + + strbuf_release(&buf); +} + static void free_pack_geometry(struct pack_geometry *geometry) { if (!geometry) return; free(geometry->pack); - free(geometry); } struct midx_snapshot_ref_data { @@ -564,18 +723,17 @@ static void midx_snapshot_refs(struct tempfile *f) } static void midx_included_packs(struct string_list *include, - struct string_list *existing_nonkept_packs, - struct string_list *existing_kept_packs, + struct existing_packs *existing, struct string_list *names, struct pack_geometry *geometry) { struct string_list_item *item; - for_each_string_list_item(item, existing_kept_packs) + for_each_string_list_item(item, &existing->kept_packs) string_list_insert(include, xstrfmt("%s.idx", item->string)); for_each_string_list_item(item, names) string_list_insert(include, xstrfmt("pack-%s.idx", item->string)); - if (geometry) { + if (geometry->split_factor) { struct strbuf buf = STRBUF_INIT; uint32_t i; for (i = geometry->split; i < geometry->pack_nr; i++) { @@ -598,28 +756,37 @@ static void midx_included_packs(struct string_list *include, string_list_insert(include, strbuf_detach(&buf, NULL)); } - - for_each_string_list_item(item, existing_nonkept_packs) { - if (!((uintptr_t)item->util & CRUFT_PACK)) { - /* - * no need to check DELETE_PACK, since we're not - * doing an ALL_INTO_ONE repack - */ - continue; - } - string_list_insert(include, xstrfmt("%s.idx", item->string)); - } } else { - for_each_string_list_item(item, existing_nonkept_packs) { - if ((uintptr_t)item->util & DELETE_PACK) + for_each_string_list_item(item, &existing->non_kept_packs) { + if (pack_is_marked_for_deletion(item)) continue; string_list_insert(include, xstrfmt("%s.idx", item->string)); } } + + for_each_string_list_item(item, &existing->cruft_packs) { + /* + * When doing a --geometric repack, there is no need to check + * for deleted packs, since we're by definition not doing an + * ALL_INTO_ONE repack (hence no packs will be deleted). + * Otherwise we must check for and exclude any packs which are + * enqueued for deletion. + * + * So we could omit the conditional below in the --geometric + * case, but doing so is unnecessary since no packs are marked + * as pending deletion (since we only call + * `mark_packs_for_deletion()` when doing an all-into-one + * repack). + */ + if (pack_is_marked_for_deletion(item)) + continue; + string_list_insert(include, xstrfmt("%s.idx", item->string)); + } } static int write_midx_included_packs(struct string_list *include, struct pack_geometry *geometry, + struct string_list *names, const char *refs_snapshot, int show_progress, int write_bitmaps) { @@ -649,6 +816,38 @@ static int write_midx_included_packs(struct string_list *include, if (preferred) strvec_pushf(&cmd.args, "--preferred-pack=%s", pack_basename(preferred)); + else if (names->nr) { + /* The largest pack was repacked, meaning that either + * one or two packs exist depending on whether the + * repository has a cruft pack or not. + * + * Select the non-cruft one as preferred to encourage + * pack-reuse among packs containing reachable objects + * over unreachable ones. + * + * (Note we could write multiple packs here if + * `--max-pack-size` was given, but any one of them + * will suffice, so pick the first one.) + */ + for_each_string_list_item(item, names) { + struct generated_pack_data *data = item->util; + if (has_pack_ext(data, ".mtimes")) + continue; + + strvec_pushf(&cmd.args, "--preferred-pack=pack-%s.pack", + item->string); + break; + } + } else { + /* + * No packs were kept, and no packs were written. The + * only thing remaining are .keep packs (unless + * --pack-kept-objects was given). + * + * Set the `--preferred-pack` arbitrarily here. + */ + ; + } if (refs_snapshot) strvec_pushf(&cmd.args, "--refs-snapshot=%s", refs_snapshot); @@ -694,18 +893,163 @@ static void remove_redundant_bitmaps(struct string_list *include, strbuf_release(&path); } +static int finish_pack_objects_cmd(struct child_process *cmd, + struct string_list *names, + int local) +{ + FILE *out; + struct strbuf line = STRBUF_INIT; + + out = xfdopen(cmd->out, "r"); + while (strbuf_getline_lf(&line, out) != EOF) { + struct string_list_item *item; + + if (line.len != the_hash_algo->hexsz) + die(_("repack: Expecting full hex object ID lines only " + "from pack-objects.")); + /* + * Avoid putting packs written outside of the repository in the + * list of names. + */ + if (local) { + item = string_list_append(names, line.buf); + item->util = populate_pack_exts(line.buf); + } + } + fclose(out); + + strbuf_release(&line); + + return finish_command(cmd); +} + +static int write_filtered_pack(const struct pack_objects_args *args, + const char *destination, + const char *pack_prefix, + struct existing_packs *existing, + struct string_list *names) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct string_list_item *item; + FILE *in; + int ret; + const char *caret; + const char *scratch; + int local = skip_prefix(destination, packdir, &scratch); + + prepare_pack_objects(&cmd, args, destination); + + strvec_push(&cmd.args, "--stdin-packs"); + + if (!pack_kept_objects) + strvec_push(&cmd.args, "--honor-pack-keep"); + for_each_string_list_item(item, &existing->kept_packs) + strvec_pushf(&cmd.args, "--keep-pack=%s", item->string); + + cmd.in = -1; + + ret = start_command(&cmd); + if (ret) + return ret; + + /* + * Here 'names' contains only the pack(s) that were just + * written, which is exactly the packs we want to keep. Also + * 'existing_kept_packs' already contains the packs in + * 'keep_pack_list'. + */ + in = xfdopen(cmd.in, "w"); + for_each_string_list_item(item, names) + fprintf(in, "^%s-%s.pack\n", pack_prefix, item->string); + for_each_string_list_item(item, &existing->non_kept_packs) + fprintf(in, "%s.pack\n", item->string); + for_each_string_list_item(item, &existing->cruft_packs) + fprintf(in, "%s.pack\n", item->string); + caret = pack_kept_objects ? "" : "^"; + for_each_string_list_item(item, &existing->kept_packs) + fprintf(in, "%s%s.pack\n", caret, item->string); + fclose(in); + + return finish_pack_objects_cmd(&cmd, names, local); +} + +static int existing_cruft_pack_cmp(const void *va, const void *vb) +{ + struct packed_git *a = *(struct packed_git **)va; + struct packed_git *b = *(struct packed_git **)vb; + + if (a->pack_size < b->pack_size) + return -1; + if (a->pack_size > b->pack_size) + return 1; + return 0; +} + +static void collapse_small_cruft_packs(FILE *in, size_t max_size, + struct existing_packs *existing) +{ + struct packed_git **existing_cruft, *p; + struct strbuf buf = STRBUF_INIT; + size_t total_size = 0; + size_t existing_cruft_nr = 0; + size_t i; + + ALLOC_ARRAY(existing_cruft, existing->cruft_packs.nr); + + for (p = get_all_packs(the_repository); p; p = p->next) { + if (!(p->is_cruft && p->pack_local)) + continue; + + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + + if (!string_list_has_string(&existing->cruft_packs, buf.buf)) + continue; + + if (existing_cruft_nr >= existing->cruft_packs.nr) + BUG("too many cruft packs (found %"PRIuMAX", but knew " + "of %"PRIuMAX")", + (uintmax_t)existing_cruft_nr + 1, + (uintmax_t)existing->cruft_packs.nr); + existing_cruft[existing_cruft_nr++] = p; + } + + QSORT(existing_cruft, existing_cruft_nr, existing_cruft_pack_cmp); + + for (i = 0; i < existing_cruft_nr; i++) { + size_t proposed; + + p = existing_cruft[i]; + proposed = st_add(total_size, p->pack_size); + + if (proposed <= max_size) { + total_size = proposed; + fprintf(in, "-%s\n", pack_basename(p)); + } else { + retain_cruft_pack(existing, p); + fprintf(in, "%s\n", pack_basename(p)); + } + } + + for (i = 0; i < existing->non_kept_packs.nr; i++) + fprintf(in, "-%s.pack\n", + existing->non_kept_packs.items[i].string); + + strbuf_release(&buf); + free(existing_cruft); +} + static int write_cruft_pack(const struct pack_objects_args *args, const char *destination, const char *pack_prefix, const char *cruft_expiration, struct string_list *names, - struct string_list *existing_packs, - struct string_list *existing_kept_packs) + struct existing_packs *existing) { struct child_process cmd = CHILD_PROCESS_INIT; - struct strbuf line = STRBUF_INIT; struct string_list_item *item; - FILE *in, *out; + FILE *in; int ret; const char *scratch; int local = skip_prefix(destination, packdir, &scratch); @@ -719,7 +1063,6 @@ static int write_cruft_pack(const struct pack_objects_args *args, strvec_push(&cmd.args, "--honor-pack-keep"); strvec_push(&cmd.args, "--non-empty"); - strvec_push(&cmd.args, "--max-pack-size=0"); cmd.in = -1; @@ -743,33 +1086,30 @@ static int write_cruft_pack(const struct pack_objects_args *args, in = xfdopen(cmd.in, "w"); for_each_string_list_item(item, names) fprintf(in, "%s-%s.pack\n", pack_prefix, item->string); - for_each_string_list_item(item, existing_packs) - fprintf(in, "-%s.pack\n", item->string); - for_each_string_list_item(item, existing_kept_packs) + if (args->max_pack_size && !cruft_expiration) { + collapse_small_cruft_packs(in, args->max_pack_size, existing); + } else { + for_each_string_list_item(item, &existing->non_kept_packs) + fprintf(in, "-%s.pack\n", item->string); + for_each_string_list_item(item, &existing->cruft_packs) + fprintf(in, "-%s.pack\n", item->string); + } + for_each_string_list_item(item, &existing->kept_packs) fprintf(in, "%s.pack\n", item->string); fclose(in); - out = xfdopen(cmd.out, "r"); - while (strbuf_getline_lf(&line, out) != EOF) { - struct string_list_item *item; - - if (line.len != the_hash_algo->hexsz) - die(_("repack: Expecting full hex object ID lines only " - "from pack-objects.")); - /* - * avoid putting packs written outside of the repository in the - * list of names - */ - if (local) { - item = string_list_append(names, line.buf); - item->util = populate_pack_exts(line.buf); - } - } - fclose(out); - - strbuf_release(&line); + return finish_pack_objects_cmd(&cmd, names, local); +} - return finish_command(&cmd); +static const char *find_pack_prefix(const char *packdir, const char *packtmp) +{ + const char *pack_prefix; + if (!skip_prefix(packtmp, packdir, &pack_prefix)) + die(_("pack prefix %s does not begin with objdir %s"), + packtmp, packdir); + if (*pack_prefix == '/') + pack_prefix++; + return pack_prefix; } int cmd_repack(int argc, const char **argv, const char *prefix) @@ -777,13 +1117,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; struct string_list names = STRING_LIST_INIT_DUP; - struct string_list existing_nonkept_packs = STRING_LIST_INIT_DUP; - struct string_list existing_kept_packs = STRING_LIST_INIT_DUP; - struct pack_geometry *geometry = NULL; - struct strbuf line = STRBUF_INIT; + struct existing_packs existing = EXISTING_PACKS_INIT; + struct pack_geometry geometry = { 0 }; struct tempfile *refs_snapshot = NULL; int i, ext, ret; - FILE *out; int show_progress; /* variables to be filled by option parsing */ @@ -793,10 +1130,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct pack_objects_args po_args = {NULL}; struct pack_objects_args cruft_po_args = {NULL}; - int geometric_factor = 0; int write_midx = 0; const char *cruft_expiration = NULL; const char *expire_to = NULL; + const char *filter_to = NULL; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -809,6 +1146,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) PACK_CRUFT), OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"), N_("with --cruft, expire objects older than this")), + OPT_MAGNITUDE(0, "max-cruft-size", &cruft_po_args.max_pack_size, + N_("with --cruft, limit the size of new cruft packs")), OPT_BOOL('d', NULL, &delete_redundant, N_("remove redundant packs, and run git-prune-packed")), OPT_BOOL('f', NULL, &po_args.no_reuse_delta, @@ -836,21 +1175,26 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("limits the maximum delta depth")), OPT_STRING(0, "threads", &po_args.threads, N_("n"), N_("limits the maximum number of threads")), - OPT_STRING(0, "max-pack-size", &po_args.max_pack_size, N_("bytes"), + OPT_MAGNITUDE(0, "max-pack-size", &po_args.max_pack_size, N_("maximum size of each packfile")), + OPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, N_("repack objects in packs marked with .keep")), OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), N_("do not repack this pack")), - OPT_INTEGER('g', "geometric", &geometric_factor, + OPT_INTEGER('g', "geometric", &geometry.split_factor, N_("find a geometric progression with factor <N>")), OPT_BOOL('m', "write-midx", &write_midx, N_("write a multi-pack index of the resulting packs")), OPT_STRING(0, "expire-to", &expire_to, N_("dir"), N_("pack prefix to store a pack containing pruned objects")), + OPT_STRING(0, "filter-to", &filter_to, N_("dir"), + N_("pack prefix to store a pack containing filtered out objects")), OPT_END() }; + list_objects_filter_init(&po_args.filter_options); + git_config(repack_config, &cruft_po_args); argc = parse_options(argc, argv, prefix, builtin_repack_options, @@ -915,14 +1259,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid()); packtmp = mkpathdup("%s/%s", packdir, packtmp_name); - collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs, - &keep_pack_list); + collect_pack_filenames(&existing, &keep_pack_list); - if (geometric_factor) { + if (geometry.split_factor) { if (pack_everything) die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a"); - init_pack_geometry(&geometry, &existing_kept_packs, &po_args); - split_pack_geometry(geometry, geometric_factor); + init_pack_geometry(&geometry, &existing, &po_args); + split_pack_geometry(&geometry); } prepare_pack_objects(&cmd, &po_args, packtmp); @@ -936,7 +1279,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_pushf(&cmd.args, "--keep-pack=%s", keep_pack_list.items[i].string); strvec_push(&cmd.args, "--non-empty"); - if (!geometry) { + if (!geometry.split_factor) { /* * We need to grab all reachable objects, including those that * are reachable from reflogs and the index. @@ -965,7 +1308,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (pack_everything & ALL_INTO_ONE) { repack_promisor_objects(&po_args, &names); - if (existing_nonkept_packs.nr && delete_redundant && + if (has_existing_non_kept_packs(&existing) && + delete_redundant && !(pack_everything & PACK_CRUFT)) { for_each_string_list_item(item, &names) { strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack", @@ -983,7 +1327,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_push(&cmd.args, "--pack-loose-unreachable"); } } - } else if (geometry) { + } else if (geometry.split_factor) { strvec_push(&cmd.args, "--stdin-packs"); strvec_push(&cmd.args, "--unpacked"); } else { @@ -991,7 +1335,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_push(&cmd.args, "--incremental"); } - if (geometry) + if (po_args.filter_options.choice) + strvec_pushf(&cmd.args, "--filter=%s", + expand_list_objects_filter_spec(&po_args.filter_options)); + else if (filter_to) + die(_("option '%s' can only be used along with '%s'"), "--filter-to", "--filter"); + + if (geometry.split_factor) cmd.in = -1; else cmd.no_stdin = 1; @@ -1000,32 +1350,21 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (ret) goto cleanup; - if (geometry) { + if (geometry.split_factor) { FILE *in = xfdopen(cmd.in, "w"); /* * The resulting pack should contain all objects in packs that * are going to be rolled up, but exclude objects in packs which * are being left alone. */ - for (i = 0; i < geometry->split; i++) - fprintf(in, "%s\n", pack_basename(geometry->pack[i])); - for (i = geometry->split; i < geometry->pack_nr; i++) - fprintf(in, "^%s\n", pack_basename(geometry->pack[i])); + for (i = 0; i < geometry.split; i++) + fprintf(in, "%s\n", pack_basename(geometry.pack[i])); + for (i = geometry.split; i < geometry.pack_nr; i++) + fprintf(in, "^%s\n", pack_basename(geometry.pack[i])); fclose(in); } - out = xfdopen(cmd.out, "r"); - while (strbuf_getline_lf(&line, out) != EOF) { - struct string_list_item *item; - - if (line.len != the_hash_algo->hexsz) - die(_("repack: Expecting full hex object ID lines only from pack-objects.")); - item = string_list_append(&names, line.buf); - item->util = populate_pack_exts(item->string); - } - strbuf_release(&line); - fclose(out); - ret = finish_command(&cmd); + ret = finish_pack_objects_cmd(&cmd, &names, 1); if (ret) goto cleanup; @@ -1033,12 +1372,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) printf_ln(_("Nothing new to pack.")); if (pack_everything & PACK_CRUFT) { - const char *pack_prefix; - if (!skip_prefix(packtmp, packdir, &pack_prefix)) - die(_("pack prefix %s does not begin with objdir %s"), - packtmp, packdir); - if (*pack_prefix == '/') - pack_prefix++; + const char *pack_prefix = find_pack_prefix(packdir, packtmp); if (!cruft_po_args.window) cruft_po_args.window = po_args.window; @@ -1048,14 +1382,15 @@ int cmd_repack(int argc, const char **argv, const char *prefix) cruft_po_args.depth = po_args.depth; if (!cruft_po_args.threads) cruft_po_args.threads = po_args.threads; + if (!cruft_po_args.max_pack_size) + cruft_po_args.max_pack_size = po_args.max_pack_size; cruft_po_args.local = po_args.local; cruft_po_args.quiet = po_args.quiet; ret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix, cruft_expiration, &names, - &existing_nonkept_packs, - &existing_kept_packs); + &existing); if (ret) goto cleanup; @@ -1086,13 +1421,25 @@ int cmd_repack(int argc, const char **argv, const char *prefix) pack_prefix, NULL, &names, - &existing_nonkept_packs, - &existing_kept_packs); + &existing); if (ret) goto cleanup; } } + if (po_args.filter_options.choice) { + if (!filter_to) + filter_to = packtmp; + + ret = write_filtered_pack(&po_args, + filter_to, + find_pack_prefix(packdir, packtmp), + &existing, + &names); + if (ret) + goto cleanup; + } + string_list_sort(&names); close_object_store(the_repository->objects); @@ -1131,31 +1478,14 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } /* End of pack replacement. */ - if (delete_redundant && pack_everything & ALL_INTO_ONE) { - const int hexsz = the_hash_algo->hexsz; - for_each_string_list_item(item, &existing_nonkept_packs) { - char *sha1; - size_t len = strlen(item->string); - if (len < hexsz) - continue; - sha1 = item->string + len - hexsz; - /* - * Mark this pack for deletion, which ensures that this - * pack won't be included in a MIDX (if `--write-midx` - * was given) and that we will actually delete this pack - * (if `-d` was given). - */ - if (!string_list_has_string(&names, sha1)) - item->util = (void*)(uintptr_t)((size_t)item->util | DELETE_PACK); - } - } + if (delete_redundant && pack_everything & ALL_INTO_ONE) + mark_packs_for_deletion(&existing, &names); if (write_midx) { struct string_list include = STRING_LIST_INIT_NODUP; - midx_included_packs(&include, &existing_nonkept_packs, - &existing_kept_packs, &names, geometry); + midx_included_packs(&include, &existing, &names, &geometry); - ret = write_midx_included_packs(&include, geometry, + ret = write_midx_included_packs(&include, &geometry, &names, refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL, show_progress, write_bitmaps > 0); @@ -1172,35 +1502,11 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (delete_redundant) { int opts = 0; - for_each_string_list_item(item, &existing_nonkept_packs) { - if (!((uintptr_t)item->util & DELETE_PACK)) - continue; - remove_redundant_pack(packdir, item->string); - } - - if (geometry) { - struct strbuf buf = STRBUF_INIT; - - uint32_t i; - for (i = 0; i < geometry->split; i++) { - struct packed_git *p = geometry->pack[i]; - if (string_list_has_string(&names, - hash_to_hex(p->hash))) - continue; + remove_redundant_existing_packs(&existing); - strbuf_reset(&buf); - strbuf_addstr(&buf, pack_basename(p)); - strbuf_strip_suffix(&buf, ".pack"); - - if ((p->pack_keep) || - (string_list_has_string(&existing_kept_packs, - buf.buf))) - continue; - - remove_redundant_pack(packdir, buf.buf); - } - strbuf_release(&buf); - } + if (geometry.split_factor) + geometry_remove_redundant_packs(&geometry, &names, + &existing); if (show_progress) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -1224,9 +1530,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) cleanup: string_list_clear(&names, 1); - string_list_clear(&existing_nonkept_packs, 0); - string_list_clear(&existing_kept_packs, 0); - free_pack_geometry(geometry); + existing_packs_release(&existing); + free_pack_geometry(&geometry); + list_objects_filter_release(&po_args.filter_options); return ret; } diff --git a/builtin/stash.c b/builtin/stash.c index fe64cde9ce..1ad496985a 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -362,7 +362,7 @@ static int is_path_a_directory(const char *path) } static void add_diff_to_buf(struct diff_queue_struct *q, - struct diff_options *options, + struct diff_options *options UNUSED, void *data) { int i; @@ -973,7 +973,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) } log_tree_diff_flush(&rev); - ret = diff_result_code(&rev.diffopt, 0); + ret = diff_result_code(&rev.diffopt); cleanup: strvec_clear(&stash_args); free_stash_info(&info); @@ -1089,7 +1089,6 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked, */ static int check_changes_tracked_files(const struct pathspec *ps) { - int result; struct rev_info rev; struct object_id dummy; int ret = 0; @@ -1111,14 +1110,14 @@ static int check_changes_tracked_files(const struct pathspec *ps) add_head_to_pending(&rev); diff_setup_done(&rev.diffopt); - result = run_diff_index(&rev, 1); - if (diff_result_code(&rev.diffopt, result)) { + run_diff_index(&rev, DIFF_INDEX_CACHED); + if (diff_result_code(&rev.diffopt)) { ret = 1; goto done; } - result = run_diff_files(&rev, 0); - if (diff_result_code(&rev.diffopt, result)) { + run_diff_files(&rev, 0); + if (diff_result_code(&rev.diffopt)) { ret = 1; goto done; } @@ -1309,10 +1308,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); - if (run_diff_index(&rev, 0)) { - ret = -1; - goto done; - } + run_diff_index(&rev, 0); cp_upd_index.git_cmd = 1; strvec_pushl(&cp_upd_index.args, "update-index", diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index f6871efd95..cce46450ab 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -629,7 +629,6 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, char *displaypath; struct strvec diff_files_args = STRVEC_INIT; struct rev_info rev = REV_INFO_INIT; - int diff_files_result; struct strbuf buf = STRBUF_INIT; const char *git_dir; struct setup_revision_opt opt = { @@ -669,9 +668,9 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = 0; setup_revisions(diff_files_args.nr, diff_files_args.v, &rev, &opt); - diff_files_result = run_diff_files(&rev, 0); + run_diff_files(&rev, 0); - if (!diff_result_code(&rev.diffopt, diff_files_result)) { + if (!diff_result_code(&rev.diffopt)) { print_status(flags, ' ', path, ce_oid, displaypath); } else if (!(flags & OPT_CACHED)) { @@ -1141,7 +1140,7 @@ static int compute_summary_module_list(struct object_id *head_oid, } if (diff_cmd == DIFF_INDEX) - run_diff_index(&rev, info->cached); + run_diff_index(&rev, info->cached ? DIFF_INDEX_CACHED : 0); else run_diff_files(&rev, 0); prepare_submodule_summary(info, &list); @@ -2890,7 +2889,7 @@ cleanup: static int module_set_url(int argc, const char **argv, const char *prefix) { - int quiet = 0; + int quiet = 0, ret; const char *newurl; const char *path; char *config_name; @@ -2902,20 +2901,29 @@ static int module_set_url(int argc, const char **argv, const char *prefix) N_("git submodule set-url [--quiet] <path> <newurl>"), NULL }; + const struct submodule *sub; argc = parse_options(argc, argv, prefix, options, usage, 0); if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1])) usage_with_options(usage, options); - config_name = xstrfmt("submodule.%s.url", path); + sub = submodule_from_path(the_repository, null_oid(), path); - config_set_in_gitmodules_file_gently(config_name, newurl); - sync_submodule(path, prefix, NULL, quiet ? OPT_QUIET : 0); + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + path); - free(config_name); + config_name = xstrfmt("submodule.%s.url", sub->name); + ret = config_set_in_gitmodules_file_gently(config_name, newurl); - return 0; + if (!ret) { + repo_read_gitmodules(the_repository, 0); + sync_submodule(sub->path, prefix, NULL, quiet ? OPT_QUIET : 0); + } + + free(config_name); + return !!ret; } static int module_set_branch(int argc, const char **argv, const char *prefix) @@ -2942,6 +2950,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) N_("git submodule set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), NULL }; + const struct submodule *sub; argc = parse_options(argc, argv, prefix, options, usage, 0); @@ -2954,7 +2963,13 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) if (argc != 1 || !(path = argv[0])) usage_with_options(usage, options); - config_name = xstrfmt("submodule.%s.branch", path); + sub = submodule_from_path(the_repository, null_oid(), path); + + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + path); + + config_name = xstrfmt("submodule.%s.branch", sub->name); ret = config_set_in_gitmodules_file_gently(config_name, opt_branch); free(config_name); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 32505255a0..fef7423448 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -609,6 +609,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix UNUSED) { int i; struct object_id oid; + git_hash_ctx tmp_ctx; disable_replace_refs(); @@ -669,7 +670,9 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix UNUSED) the_hash_algo->init_fn(&ctx); unpack_all(); the_hash_algo->update_fn(&ctx, buffer, offset); - the_hash_algo->final_oid_fn(&oid, &ctx); + the_hash_algo->init_fn(&tmp_ctx); + the_hash_algo->clone_fn(&tmp_ctx, &ctx); + the_hash_algo->final_oid_fn(&oid, &tmp_ctx); if (strict) { write_rest(); if (fsck_finish(&fsck_options)) diff --git a/builtin/update-index.c b/builtin/update-index.c index aee3cb8cbd..7bcaa1476c 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -609,9 +609,6 @@ static const char * const update_index_usage[] = { NULL }; -static struct object_id head_oid; -static struct object_id merge_head_oid; - static struct cache_entry *read_one_ent(const char *which, struct object_id *ent, const char *path, int namelen, int stage) @@ -642,84 +639,17 @@ static struct cache_entry *read_one_ent(const char *which, static int unresolve_one(const char *path) { - int namelen = strlen(path); - int pos; - int ret = 0; - struct cache_entry *ce_2 = NULL, *ce_3 = NULL; - - /* See if there is such entry in the index. */ - pos = index_name_pos(&the_index, path, namelen); - if (0 <= pos) { - /* already merged */ - pos = unmerge_index_entry_at(&the_index, pos); - if (pos < the_index.cache_nr) { - const struct cache_entry *ce = the_index.cache[pos]; - if (ce_stage(ce) && - ce_namelen(ce) == namelen && - !memcmp(ce->name, path, namelen)) - return 0; - } - /* no resolve-undo information; fall back */ - } else { - /* If there isn't, either it is unmerged, or - * resolved as "removed" by mistake. We do not - * want to do anything in the former case. - */ - pos = -pos-1; - if (pos < the_index.cache_nr) { - const struct cache_entry *ce = the_index.cache[pos]; - if (ce_namelen(ce) == namelen && - !memcmp(ce->name, path, namelen)) { - fprintf(stderr, - "%s: skipping still unmerged path.\n", - path); - goto free_return; - } - } - } - - /* Grab blobs from given path from HEAD and MERGE_HEAD, - * stuff HEAD version in stage #2, - * stuff MERGE_HEAD version in stage #3. - */ - ce_2 = read_one_ent("our", &head_oid, path, namelen, 2); - ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3); - - if (!ce_2 || !ce_3) { - ret = -1; - goto free_return; - } - if (oideq(&ce_2->oid, &ce_3->oid) && - ce_2->ce_mode == ce_3->ce_mode) { - fprintf(stderr, "%s: identical in both, skipping.\n", - path); - goto free_return; - } - - remove_file_from_index(&the_index, path); - if (add_index_entry(&the_index, ce_2, ADD_CACHE_OK_TO_ADD)) { - error("%s: cannot add our version to the index.", path); - ret = -1; - goto free_return; - } - if (!add_index_entry(&the_index, ce_3, ADD_CACHE_OK_TO_ADD)) - return 0; - error("%s: cannot add their version to the index.", path); - ret = -1; - free_return: - discard_cache_entry(ce_2); - discard_cache_entry(ce_3); - return ret; -} - -static void read_head_pointers(void) -{ - if (read_ref("HEAD", &head_oid)) - die("No HEAD -- no initial commit yet?"); - if (read_ref("MERGE_HEAD", &merge_head_oid)) { - fprintf(stderr, "Not in the middle of a merge.\n"); - exit(0); - } + struct string_list_item *item; + int res = 0; + + if (!the_index.resolve_undo) + return res; + item = string_list_lookup(the_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); + FREE_AND_NULL(item->util); + return res; } static int do_unresolve(int ac, const char **av, @@ -728,11 +658,6 @@ static int do_unresolve(int ac, const char **av, int i; int err = 0; - /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we - * are not doing a merge, so exit with success status. - */ - read_head_pointers(); - for (i = 1; i < ac; i++) { const char *arg = av[i]; char *p = prefix_path(prefix, prefix_length, arg); @@ -751,6 +676,7 @@ static int do_reupdate(const char **paths, int pos; int has_head = 1; struct pathspec pathspec; + struct object_id head_oid; parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, @@ -856,7 +782,7 @@ static int chmod_callback(const struct option *opt, return 0; } -static int resolve_undo_clear_callback(const struct option *opt, +static int resolve_undo_clear_callback(const struct option *opt UNUSED, const char *arg, int unset) { BUG_ON_OPT_NEG(unset); @@ -890,7 +816,7 @@ static int parse_new_style_cacheinfo(const char *arg, } static enum parse_opt_result cacheinfo_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, + struct parse_opt_ctx_t *ctx, const struct option *opt UNUSED, const char *arg, int unset) { struct object_id oid; @@ -1090,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) resolve_undo_clear_callback), OPT_INTEGER(0, "index-version", &preferred_index_format, N_("write index in this format")), + OPT_SET_INT(0, "show-index-version", &preferred_index_format, + N_("report on-disk index format version"), -1), OPT_BOOL(0, "split-index", &split_index, N_("enable or disable split index")), OPT_BOOL(0, "untracked-cache", &untracked_cache, @@ -1182,15 +1110,20 @@ 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 < INDEX_FORMAT_LB || - INDEX_FORMAT_UB < preferred_index_format) + if (preferred_index_format < 0) { + printf(_("%d\n"), the_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); - - if (the_index.version != preferred_index_format) - the_index.cache_changed |= SOMETHING_CHANGED; - the_index.version = preferred_index_format; + } else { + if (the_index.version != preferred_index_format) + the_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; + } } if (read_from_stdin) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 242102273e..c0c4e65e6f 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -311,8 +311,8 @@ static void report_ok(const char *command) fflush(stdout); } -static void parse_cmd_option(struct ref_transaction *transaction, - const char *next, const char *end) +static void parse_cmd_option(struct ref_transaction *transaction UNUSED, + const char *next, const char *end UNUSED) { const char *rest; if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) @@ -321,8 +321,8 @@ static void parse_cmd_option(struct ref_transaction *transaction, die("option unknown: %s", next); } -static void parse_cmd_start(struct ref_transaction *transaction, - const char *next, const char *end) +static void parse_cmd_start(struct ref_transaction *transaction UNUSED, + const char *next, const char *end UNUSED) { if (*next != line_termination) die("start: extra input: %s", next); @@ -330,7 +330,7 @@ static void parse_cmd_start(struct ref_transaction *transaction, } static void parse_cmd_prepare(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -341,7 +341,7 @@ static void parse_cmd_prepare(struct ref_transaction *transaction, } static void parse_cmd_abort(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) @@ -352,7 +352,7 @@ static void parse_cmd_abort(struct ref_transaction *transaction, } static void parse_cmd_commit(struct ref_transaction *transaction, - const char *next, const char *end) + const char *next, const char *end UNUSED) { struct strbuf error = STRBUF_INIT; if (*next != line_termination) diff --git a/builtin/var.c b/builtin/var.c index 74161bdf1c..8cf7dd9e2e 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -66,7 +66,7 @@ static char *git_attr_val_system(int ident_flag UNUSED) static char *git_attr_val_global(int ident_flag UNUSED) { - char *file = xstrdup(git_attr_global_file()); + char *file = xstrdup_or_null(git_attr_global_file()); if (file) { normalize_path_copy(file, file); return file; diff --git a/builtin/worktree.c b/builtin/worktree.c index 4cd01842de..62b7e26f4b 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -628,10 +628,10 @@ static void print_preparing_worktree_line(int detach, * * Returns 0 on failure and non-zero on success. */ -static int first_valid_ref(const char *refname, - const struct object_id *oid, - int flags, - void *cb_data) +static int first_valid_ref(const char *refname UNUSED, + const struct object_id *oid UNUSED, + int flags UNUSED, + void *cb_data UNUSED) { return 1; } @@ -696,7 +696,7 @@ static int can_use_remote_refs(const struct add_opts *opts) return 1; } else if (!opts->force && remote_get(NULL)) { die(_("No local or remote refs exist despite at least one remote\n" - "present, stopping; use 'add -f' to overide or fetch a remote first")); + "present, stopping; use 'add -f' to override or fetch a remote first")); } return 0; } diff --git a/bulk-checkin.c b/bulk-checkin.c index 73bff3a23d..6ce62999e5 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -155,10 +155,10 @@ static int already_written(struct bulk_checkin_packfile *state, struct object_id * status before calling us just in case we ask it to call us again * with a new pack. */ -static int stream_to_pack(struct bulk_checkin_packfile *state, - git_hash_ctx *ctx, off_t *already_hashed_to, - int fd, size_t size, enum object_type type, - const char *path, unsigned flags) +static int stream_blob_to_pack(struct bulk_checkin_packfile *state, + git_hash_ctx *ctx, off_t *already_hashed_to, + int fd, size_t size, const char *path, + unsigned flags) { git_zstream s; unsigned char ibuf[16384]; @@ -170,7 +170,7 @@ static int stream_to_pack(struct bulk_checkin_packfile *state, git_deflate_init(&s, pack_compression_level); - hdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), type, size); + hdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size); s.next_out = obuf + hdrlen; s.avail_out = sizeof(obuf) - hdrlen; @@ -247,11 +247,10 @@ static void prepare_to_stream(struct bulk_checkin_packfile *state, die_errno("unable to write pack header"); } -static int deflate_to_pack(struct bulk_checkin_packfile *state, - struct object_id *result_oid, - int fd, size_t size, - enum object_type type, const char *path, - unsigned flags) +static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, + struct object_id *result_oid, + int fd, size_t size, + const char *path, unsigned flags) { off_t seekback, already_hashed_to; git_hash_ctx ctx; @@ -265,9 +264,10 @@ static int deflate_to_pack(struct bulk_checkin_packfile *state, return error("cannot find the current offset"); header_len = format_object_header((char *)obuf, sizeof(obuf), - type, size); + OBJ_BLOB, size); the_hash_algo->init_fn(&ctx); the_hash_algo->update_fn(&ctx, obuf, header_len); + the_hash_algo->init_fn(&checkpoint.ctx); /* Note: idx is non-NULL when we are writing */ if ((flags & HASH_WRITE_OBJECT) != 0) @@ -282,8 +282,8 @@ static int deflate_to_pack(struct bulk_checkin_packfile *state, idx->offset = state->offset; crc32_begin(state->f); } - if (!stream_to_pack(state, &ctx, &already_hashed_to, - fd, size, type, path, flags)) + if (!stream_blob_to_pack(state, &ctx, &already_hashed_to, + fd, size, path, flags)) break; /* * Writing this object to the current pack will make @@ -350,12 +350,12 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename) } } -int index_bulk_checkin(struct object_id *oid, - int fd, size_t size, enum object_type type, - const char *path, unsigned flags) +int index_blob_bulk_checkin(struct object_id *oid, + int fd, size_t size, + const char *path, unsigned flags) { - int status = deflate_to_pack(&bulk_checkin_packfile, oid, fd, size, type, - path, flags); + int status = deflate_blob_to_pack(&bulk_checkin_packfile, oid, fd, size, + path, flags); if (!odb_transaction_nesting) flush_bulk_checkin_packfile(&bulk_checkin_packfile); return status; diff --git a/bulk-checkin.h b/bulk-checkin.h index 48fe9a6e91..aa7286a7b3 100644 --- a/bulk-checkin.h +++ b/bulk-checkin.h @@ -9,9 +9,9 @@ void prepare_loose_object_bulk_checkin(void); void fsync_loose_object_bulk_checkin(int fd, const char *filename); -int index_bulk_checkin(struct object_id *oid, - int fd, size_t size, enum object_type type, - const char *path, unsigned flags); +int index_blob_bulk_checkin(struct object_id *oid, + int fd, size_t size, + const char *path, unsigned flags); /* * Tell the object database to optimize for adding diff --git a/bundle-uri.c b/bundle-uri.c index 4b5c49b93d..8492fffd2f 100644 --- a/bundle-uri.c +++ b/bundle-uri.c @@ -20,7 +20,7 @@ static struct { { BUNDLE_HEURISTIC_CREATIONTOKEN, "creationToken" }, }; -static int compare_bundles(const void *hashmap_cmp_fn_data, +static int compare_bundles(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *he1, const struct hashmap_entry *he2, const void *id) @@ -45,7 +45,7 @@ void init_bundle_list(struct bundle_list *list) } static int clear_remote_bundle_info(struct remote_bundle_info *bundle, - void *data) + void *data UNUSED) { FREE_AND_NULL(bundle->id); FREE_AND_NULL(bundle->uri); @@ -779,7 +779,7 @@ static int unbundle_all_bundles(struct repository *r, return 0; } -static int unlink_bundle(struct remote_bundle_info *info, void *data) +static int unlink_bundle(struct remote_bundle_info *info, void *data UNUSED) { if (info->file) unlink_or_warn(info->file); diff --git a/ci/config/README b/ci/config/README new file mode 100644 index 0000000000..8de3a04e32 --- /dev/null +++ b/ci/config/README @@ -0,0 +1,14 @@ +You can configure some aspects of the GitHub Actions-based CI on a +per-repository basis by setting "variables" and "secrets" from with the +GitHub web interface. These can be found at: + + https://github.com/<user>/git/settings/secrets/actions + +The following variables can be used: + + - CI_BRANCHES + + By default, CI is run when any branch is pushed. If this variable is + non-empty, then only the branches it lists will run CI. Branch names + should be separated by spaces, and should use their shortened form + (e.g., "main", not "refs/heads/main"). diff --git a/ci/config/allow-ref.sample b/ci/config/allow-ref.sample deleted file mode 100755 index af0e076f8a..0000000000 --- a/ci/config/allow-ref.sample +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -# -# Sample script for enabling/disabling GitHub Actions CI runs on -# particular refs. By default, CI is run for all branches pushed to -# GitHub. You can override this by dropping the ".sample" from the script, -# editing it, committing, and pushing the result to the "ci-config" branch of -# your repository: -# -# git checkout -b ci-config -# cp allow-ref.sample allow-ref -# $EDITOR allow-ref -# git add allow-ref -# git commit -am "implement my ci preferences" -# git push -# -# This script will then be run when any refs are pushed to that repository. It -# gets the fully qualified refname as the first argument, and should exit with -# success only for refs for which you want to run CI. - -case "$1" in -# allow one-off tests by pushing to "for-ci" or "for-ci/mybranch" -refs/heads/for-ci*) true ;; -# always build your integration branch -refs/heads/my-integration-branch) true ;; -# don't build any other branches or tags -*) false ;; -esac @@ -280,6 +280,8 @@ linux-leaks) ;; linux-asan-ubsan) export SANITIZE=address,undefined + export NO_SVN_TESTS=LetsSaveSomeTime + MAKEFLAGS="$MAKEFLAGS NO_PYTHON=YepBecauseP4FlakesTooOften" ;; esac @@ -3,7 +3,7 @@ #include "color.h" #include "editor.h" #include "gettext.h" -#include "hex.h" +#include "hex-ll.h" #include "pager.h" #include "strbuf.h" diff --git a/commit-graph.c b/commit-graph.c index 0aa1640d15..fd2f700b2e 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -128,6 +128,16 @@ timestamp_t commit_graph_generation(const struct commit *c) return GENERATION_NUMBER_INFINITY; } +static timestamp_t commit_graph_generation_from_graph(const struct commit *c) +{ + struct commit_graph_data *data = + commit_graph_data_slab_peek(&commit_graph_data_slab, c); + + if (!data || data->graph_pos == COMMIT_NOT_FROM_GRAPH) + return GENERATION_NUMBER_INFINITY; + return data->generation; +} + static struct commit_graph_data *commit_graph_data_at(const struct commit *c) { unsigned int i, nth_slab; @@ -463,6 +473,31 @@ static struct commit_graph *load_commit_graph_v1(struct repository *r, return g; } +/* + * returns 1 if and only if all graphs in the chain have + * corrected commit dates stored in the generation_data chunk. + */ +static int validate_mixed_generation_chain(struct commit_graph *g) +{ + int read_generation_data = 1; + struct commit_graph *p = g; + + while (read_generation_data && p) { + read_generation_data = p->read_generation_data; + p = p->base_graph; + } + + if (read_generation_data) + return 1; + + while (g) { + g->read_generation_data = 0; + g = g->base_graph; + } + + return 0; +} + static int add_graph_to_chain(struct commit_graph *g, struct commit_graph *chain, struct object_id *oids, @@ -488,8 +523,6 @@ static int add_graph_to_chain(struct commit_graph *g, cur_g = cur_g->base_graph; } - g->base_graph = chain; - if (chain) { if (unsigned_add_overflows(chain->num_commits, chain->num_commits_in_base)) { @@ -500,34 +533,46 @@ static int add_graph_to_chain(struct commit_graph *g, g->num_commits_in_base = chain->num_commits + chain->num_commits_in_base; } + g->base_graph = chain; + return 1; } -static struct commit_graph *load_commit_graph_chain(struct repository *r, - struct object_directory *odb) +int open_commit_graph_chain(const char *chain_file, + int *fd, struct stat *st) +{ + *fd = git_open(chain_file); + if (*fd < 0) + return 0; + if (fstat(*fd, st)) { + close(*fd); + return 0; + } + if (st->st_size < the_hash_algo->hexsz) { + close(*fd); + if (!st->st_size) { + /* treat empty files the same as missing */ + errno = ENOENT; + } else { + warning("commit-graph chain file too small"); + errno = EINVAL; + } + return 0; + } + return 1; +} + +struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, + int fd, struct stat *st, + int *incomplete_chain) { struct commit_graph *graph_chain = NULL; struct strbuf line = STRBUF_INIT; - struct stat st; struct object_id *oids; int i = 0, valid = 1, count; - char *chain_name = get_commit_graph_chain_filename(odb); - FILE *fp; - int stat_res; - - fp = fopen(chain_name, "r"); - stat_res = stat(chain_name, &st); - free(chain_name); + FILE *fp = xfdopen(fd, "r"); - if (!fp) - return NULL; - if (stat_res || - st.st_size <= the_hash_algo->hexsz) { - fclose(fp); - return NULL; - } - - count = st.st_size / (the_hash_algo->hexsz + 1); + count = st->st_size / (the_hash_algo->hexsz + 1); CALLOC_ARRAY(oids, count); prepare_alt_odb(r); @@ -556,6 +601,8 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, if (add_graph_to_chain(g, graph_chain, oids, i)) { graph_chain = g; valid = 1; + } else { + free_commit_graph(g); } break; @@ -568,36 +615,32 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, } } + validate_mixed_generation_chain(graph_chain); + free(oids); fclose(fp); strbuf_release(&line); + *incomplete_chain = !valid; return graph_chain; } -/* - * returns 1 if and only if all graphs in the chain have - * corrected commit dates stored in the generation_data chunk. - */ -static int validate_mixed_generation_chain(struct commit_graph *g) +static struct commit_graph *load_commit_graph_chain(struct repository *r, + struct object_directory *odb) { - int read_generation_data = 1; - struct commit_graph *p = g; - - while (read_generation_data && p) { - read_generation_data = p->read_generation_data; - p = p->base_graph; - } - - if (read_generation_data) - return 1; + char *chain_file = get_commit_graph_chain_filename(odb); + struct stat st; + int fd; + struct commit_graph *g = NULL; - while (g) { - g->read_generation_data = 0; - g = g->base_graph; + if (open_commit_graph_chain(chain_file, &fd, &st)) { + int incomplete; + /* ownership of fd is taken over by load function */ + g = load_commit_graph_chain_fd_st(r, fd, &st, &incomplete); } - return 0; + free(chain_file); + return g; } struct commit_graph *read_commit_graph_one(struct repository *r, @@ -608,8 +651,6 @@ struct commit_graph *read_commit_graph_one(struct repository *r, if (!g) g = load_commit_graph_chain(r, odb); - validate_mixed_generation_chain(g); - return g; } @@ -713,19 +754,10 @@ struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r) return NULL; } -static void close_commit_graph_one(struct commit_graph *g) -{ - if (!g) - return; - - clear_commit_graph_data_slab(&commit_graph_data_slab); - close_commit_graph_one(g->base_graph); - free_commit_graph(g); -} - void close_commit_graph(struct raw_object_store *o) { - close_commit_graph_one(o->commit_graph); + clear_commit_graph_data_slab(&commit_graph_data_slab); + free_commit_graph(o->commit_graph); o->commit_graph = NULL; } @@ -1568,12 +1600,14 @@ static void compute_topological_levels(struct write_commit_graph_context *ctx) stop_progress(&ctx->progress); } -static timestamp_t get_generation_from_graph_data(struct commit *c, void *data) +static timestamp_t get_generation_from_graph_data(struct commit *c, + void *data UNUSED) { return commit_graph_data_at(c)->generation; } -static void set_generation_v2(struct commit *c, timestamp_t t, void *data) +static void set_generation_v2(struct commit *c, timestamp_t t, + void *data UNUSED) { struct commit_graph_data *g = commit_graph_data_at(c); g->generation = t; @@ -1587,7 +1621,6 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) .commits = &ctx->commits, .get_generation = get_generation_from_graph_data, .set_generation = set_generation_v2, - .data = ctx, }; if (ctx->report_progress) @@ -1616,7 +1649,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) } static void set_generation_in_graph_data(struct commit *c, timestamp_t t, - void *data) + void *data UNUSED) { commit_graph_data_at(c)->generation = t; } @@ -2061,9 +2094,11 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) free(graph_name); } + free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]); ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(hash_to_hex(file_hash)); final_graph_name = get_split_graph_filename(ctx->odb, ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]); + free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1]); ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1] = final_graph_name; result = rename(ctx->graph_name, final_graph_name); @@ -2512,6 +2547,7 @@ int write_commit_graph(struct object_directory *odb, cleanup: free(ctx->graph_name); + free(ctx->base_graph_name); free(ctx->commits.list); oid_array_clear(&ctx->oids); clear_topo_level_slab(&topo_levels); @@ -2550,9 +2586,6 @@ static void graph_report(const char *fmt, ...) va_end(ap); } -#define GENERATION_ZERO_EXISTS 1 -#define GENERATION_NUMBER_EXISTS 2 - static int commit_graph_checksum_valid(struct commit_graph *g) { return hashfile_checksum_valid(g->data, g->data_len); @@ -2565,7 +2598,8 @@ static int verify_one_commit_graph(struct repository *r, { uint32_t i, cur_fanout_pos = 0; struct object_id prev_oid, cur_oid; - int generation_zero = 0; + struct commit *seen_gen_zero = NULL; + struct commit *seen_gen_non_zero = NULL; verify_commit_graph_error = verify_commit_graph_lite(g); if (verify_commit_graph_error) @@ -2659,7 +2693,7 @@ static int verify_one_commit_graph(struct repository *r, oid_to_hex(&graph_parents->item->object.oid), oid_to_hex(&odb_parents->item->object.oid)); - generation = commit_graph_generation(graph_parents->item); + generation = commit_graph_generation_from_graph(graph_parents->item); if (generation > max_generation) max_generation = generation; @@ -2671,16 +2705,12 @@ static int verify_one_commit_graph(struct repository *r, graph_report(_("commit-graph parent list for commit %s terminates early"), oid_to_hex(&cur_oid)); - if (!commit_graph_generation(graph_commit)) { - if (generation_zero == GENERATION_NUMBER_EXISTS) - graph_report(_("commit-graph has generation number zero for commit %s, but non-zero elsewhere"), - oid_to_hex(&cur_oid)); - generation_zero = GENERATION_ZERO_EXISTS; - } else if (generation_zero == GENERATION_ZERO_EXISTS) - graph_report(_("commit-graph has non-zero generation number for commit %s, but zero elsewhere"), - oid_to_hex(&cur_oid)); + if (commit_graph_generation_from_graph(graph_commit)) + seen_gen_non_zero = graph_commit; + else + seen_gen_zero = graph_commit; - if (generation_zero == GENERATION_ZERO_EXISTS) + if (seen_gen_zero) continue; /* @@ -2706,6 +2736,12 @@ static int verify_one_commit_graph(struct repository *r, odb_commit->date); } + if (seen_gen_zero && seen_gen_non_zero) + graph_report(_("commit-graph has both zero and non-zero " + "generations (e.g., commits '%s' and '%s')"), + oid_to_hex(&seen_gen_zero->object.oid), + oid_to_hex(&seen_gen_non_zero->object.oid)); + return verify_commit_graph_error; } @@ -2742,15 +2778,17 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) void free_commit_graph(struct commit_graph *g) { - if (!g) - return; - if (g->data) { - munmap((void *)g->data, g->data_len); - g->data = NULL; + while (g) { + struct commit_graph *next = g->base_graph; + + if (g->data) + munmap((void *)g->data, g->data_len); + free(g->filename); + free(g->bloom_filter_settings); + free(g); + + g = next; } - free(g->filename); - free(g->bloom_filter_settings); - free(g); } void disable_commit_graph(struct repository *r) diff --git a/commit-graph.h b/commit-graph.h index 5e534f0fcc..20ada7e891 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -26,6 +26,7 @@ struct string_list; char *get_commit_graph_filename(struct object_directory *odb); char *get_commit_graph_chain_filename(struct object_directory *odb); int open_commit_graph(const char *graph_file, int *fd, struct stat *st); +int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st); /* * Given a commit struct, try to fill the commit struct info, including: @@ -105,6 +106,9 @@ struct commit_graph { struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, int fd, struct stat *st, struct object_directory *odb); +struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, + int fd, struct stat *st, + int *incomplete_chain); struct commit_graph *read_commit_graph_one(struct repository *r, struct object_directory *odb); diff --git a/commit-reach.c b/commit-reach.c index 4b7c233fd4..a868a575ea 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -173,6 +173,7 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in) for (k = bases; k; k = k->next) end = k; } + free_commit_list(ret); ret = new_commits; } return ret; diff --git a/compat/fsmonitor/fsm-health-darwin.c b/compat/fsmonitor/fsm-health-darwin.c index 5b1709d63f..c2afcbe6c8 100644 --- a/compat/fsmonitor/fsm-health-darwin.c +++ b/compat/fsmonitor/fsm-health-darwin.c @@ -4,21 +4,21 @@ #include "fsm-health.h" #include "fsmonitor--daemon.h" -int fsm_health__ctor(struct fsmonitor_daemon_state *state) +int fsm_health__ctor(struct fsmonitor_daemon_state *state UNUSED) { return 0; } -void fsm_health__dtor(struct fsmonitor_daemon_state *state) +void fsm_health__dtor(struct fsmonitor_daemon_state *state UNUSED) { return; } -void fsm_health__loop(struct fsmonitor_daemon_state *state) +void fsm_health__loop(struct fsmonitor_daemon_state *state UNUSED) { return; } -void fsm_health__stop_async(struct fsmonitor_daemon_state *state) +void fsm_health__stop_async(struct fsmonitor_daemon_state *state UNUSED) { } diff --git a/compat/fsmonitor/fsm-ipc-win32.c b/compat/fsmonitor/fsm-ipc-win32.c index 8928fa93ce..41984ea48e 100644 --- a/compat/fsmonitor/fsm-ipc-win32.c +++ b/compat/fsmonitor/fsm-ipc-win32.c @@ -6,6 +6,6 @@ const char *fsmonitor_ipc__get_path(struct repository *r) { static char *ret; if (!ret) - ret = git_pathdup("fsmonitor--daemon.ipc"); + ret = repo_git_path(r, "fsmonitor--daemon.ipc"); return ret; } diff --git a/compat/fsmonitor/fsm-listen-darwin.c b/compat/fsmonitor/fsm-listen-darwin.c index 36c7e13281..11b56d3ef1 100644 --- a/compat/fsmonitor/fsm-listen-darwin.c +++ b/compat/fsmonitor/fsm-listen-darwin.c @@ -191,12 +191,12 @@ static void my_add_path(struct fsmonitor_batch *batch, const char *path) } -static void fsevent_callback(ConstFSEventStreamRef streamRef, +static void fsevent_callback(ConstFSEventStreamRef streamRef UNUSED, void *ctx, size_t num_of_events, void *event_paths, const FSEventStreamEventFlags event_flags[], - const FSEventStreamEventId event_ids[]) + const FSEventStreamEventId event_ids[] UNUSED) { struct fsmonitor_daemon_state *state = ctx; struct fsm_listen_data *data = state->listen_data; diff --git a/compat/fsmonitor/fsm-listen-win32.c b/compat/fsmonitor/fsm-listen-win32.c index a361a7db20..90a2412284 100644 --- a/compat/fsmonitor/fsm-listen-win32.c +++ b/compat/fsmonitor/fsm-listen-win32.c @@ -289,8 +289,7 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state) SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]); } -static struct one_watch *create_watch(struct fsmonitor_daemon_state *state, - const char *path) +static struct one_watch *create_watch(const char *path) { struct one_watch *watch = NULL; DWORD desired_access = FILE_LIST_DIRECTORY; @@ -361,8 +360,7 @@ static void destroy_watch(struct one_watch *watch) free(watch); } -static int start_rdcw_watch(struct fsm_listen_data *data, - struct one_watch *watch) +static int start_rdcw_watch(struct one_watch *watch) { DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | @@ -735,11 +733,11 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) state->listen_error_code = 0; - if (start_rdcw_watch(data, data->watch_worktree) == -1) + if (start_rdcw_watch(data->watch_worktree) == -1) goto force_error_stop; if (data->watch_gitdir && - start_rdcw_watch(data, data->watch_gitdir) == -1) + start_rdcw_watch(data->watch_gitdir) == -1) goto force_error_stop; for (;;) { @@ -755,7 +753,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) } if (result == -2) { /* retryable error */ - if (start_rdcw_watch(data, data->watch_worktree) == -1) + if (start_rdcw_watch(data->watch_worktree) == -1) goto force_error_stop; continue; } @@ -763,7 +761,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) /* have data */ if (process_worktree_events(state) == LISTENER_SHUTDOWN) goto force_shutdown; - if (start_rdcw_watch(data, data->watch_worktree) == -1) + if (start_rdcw_watch(data->watch_worktree) == -1) goto force_error_stop; continue; } @@ -776,7 +774,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) } if (result == -2) { /* retryable error */ - if (start_rdcw_watch(data, data->watch_gitdir) == -1) + if (start_rdcw_watch(data->watch_gitdir) == -1) goto force_error_stop; continue; } @@ -784,7 +782,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) /* have data */ if (process_gitdir_events(state) == LISTENER_SHUTDOWN) goto force_shutdown; - if (start_rdcw_watch(data, data->watch_gitdir) == -1) + if (start_rdcw_watch(data->watch_gitdir) == -1) goto force_error_stop; continue; } @@ -821,16 +819,14 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) data->hEventShutdown = CreateEvent(NULL, TRUE, FALSE, NULL); - data->watch_worktree = create_watch(state, - state->path_worktree_watch.buf); + data->watch_worktree = create_watch(state->path_worktree_watch.buf); if (!data->watch_worktree) goto failed; check_for_shortnames(data->watch_worktree); if (state->nr_paths_watching > 1) { - data->watch_gitdir = create_watch(state, - state->path_gitdir_watch.buf); + data->watch_gitdir = create_watch(state->path_gitdir_watch.buf); if (!data->watch_gitdir) goto failed; } diff --git a/compat/fsmonitor/fsm-path-utils-win32.c b/compat/fsmonitor/fsm-path-utils-win32.c index c8a3e9dcdb..f4f9cc1f33 100644 --- a/compat/fsmonitor/fsm-path-utils-win32.c +++ b/compat/fsmonitor/fsm-path-utils-win32.c @@ -132,7 +132,8 @@ int fsmonitor__is_fs_remote(const char *path) /* * No-op for now. */ -int fsmonitor__get_alias(const char *path, struct alias_info *info) +int fsmonitor__get_alias(const char *path UNUSED, + struct alias_info *info UNUSED) { return 0; } @@ -140,8 +141,8 @@ int fsmonitor__get_alias(const char *path, struct alias_info *info) /* * No-op for now. */ -char *fsmonitor__resolve_alias(const char *path, - const struct alias_info *info) +char *fsmonitor__resolve_alias(const char *path UNUSED, + const struct alias_info *info UNUSED) { return NULL; } diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c index b6f6744494..0f2aa321f6 100644 --- a/compat/fsmonitor/fsm-settings-win32.c +++ b/compat/fsmonitor/fsm-settings-win32.c @@ -25,7 +25,7 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r) return FSMONITOR_REASON_OK; } -enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc) +enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc UNUSED) { enum fsmonitor_reason reason; diff --git a/compat/terminal.c b/compat/terminal.c index 83d95e8656..0afda730f2 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -479,10 +479,13 @@ struct escape_sequence_entry { }; static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, - const struct escape_sequence_entry *e1, - const struct escape_sequence_entry *e2, + const struct hashmap_entry *he1, + const struct hashmap_entry *he2, const void *keydata) { + const struct escape_sequence_entry + *e1 = container_of(he1, const struct escape_sequence_entry, entry), + *e2 = container_of(he2, const struct escape_sequence_entry, entry); return strcmp(e1->sequence, keydata ? keydata : e2->sequence); } @@ -496,8 +499,7 @@ static int is_known_escape_sequence(const char *sequence) struct strbuf buf = STRBUF_INIT; char *p, *eol; - hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp, - NULL, 0); + hashmap_init(&sequences, sequence_entry_cmp, NULL, 0); strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL); if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0)) @@ -11,6 +11,7 @@ #include "date.h" #include "branch.h" #include "config.h" +#include "parse.h" #include "convert.h" #include "environment.h" #include "gettext.h" @@ -1165,129 +1166,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn, return error_return; } -static uintmax_t get_unit_factor(const char *end) -{ - if (!*end) - return 1; - else if (!strcasecmp(end, "k")) - return 1024; - else if (!strcasecmp(end, "m")) - return 1024 * 1024; - else if (!strcasecmp(end, "g")) - return 1024 * 1024 * 1024; - return 0; -} - -static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) -{ - if (value && *value) { - char *end; - intmax_t val; - intmax_t factor; - - if (max < 0) - BUG("max must be a positive integer"); - - errno = 0; - val = strtoimax(value, &end, 0); - if (errno == ERANGE) - return 0; - if (end == value) { - errno = EINVAL; - return 0; - } - factor = get_unit_factor(end); - if (!factor) { - errno = EINVAL; - return 0; - } - if ((val < 0 && -max / factor > val) || - (val > 0 && max / factor < val)) { - errno = ERANGE; - return 0; - } - val *= factor; - *ret = val; - return 1; - } - errno = EINVAL; - return 0; -} - -static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) -{ - if (value && *value) { - char *end; - uintmax_t val; - uintmax_t factor; - - /* negative values would be accepted by strtoumax */ - if (strchr(value, '-')) { - errno = EINVAL; - return 0; - } - errno = 0; - val = strtoumax(value, &end, 0); - if (errno == ERANGE) - return 0; - if (end == value) { - errno = EINVAL; - return 0; - } - factor = get_unit_factor(end); - if (!factor) { - errno = EINVAL; - return 0; - } - if (unsigned_mult_overflows(factor, val) || - factor * val > max) { - errno = ERANGE; - return 0; - } - val *= factor; - *ret = val; - return 1; - } - errno = EINVAL; - return 0; -} - -int git_parse_int(const char *value, int *ret) -{ - intmax_t tmp; - if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int))) - return 0; - *ret = tmp; - return 1; -} - -static int git_parse_int64(const char *value, int64_t *ret) -{ - intmax_t tmp; - if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t))) - return 0; - *ret = tmp; - return 1; -} - -int git_parse_ulong(const char *value, unsigned long *ret) -{ - uintmax_t tmp; - if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long))) - return 0; - *ret = tmp; - return 1; -} - -int git_parse_ssize_t(const char *value, ssize_t *ret) -{ - intmax_t tmp; - if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t))) - return 0; - *ret = tmp; - return 1; -} - NORETURN static void die_bad_number(const char *name, const char *value, const struct key_value_info *kvi) @@ -1363,23 +1241,6 @@ ssize_t git_config_ssize_t(const char *name, const char *value, return ret; } -static int git_parse_maybe_bool_text(const char *value) -{ - if (!value) - return 1; - if (!*value) - return 0; - if (!strcasecmp(value, "true") - || !strcasecmp(value, "yes") - || !strcasecmp(value, "on")) - return 1; - if (!strcasecmp(value, "false") - || !strcasecmp(value, "no") - || !strcasecmp(value, "off")) - return 0; - return -1; -} - static const struct fsync_component_name { const char *name; enum fsync_component component_bits; @@ -1454,16 +1315,6 @@ next_name: return (current & ~negative) | positive; } -int git_parse_maybe_bool(const char *value) -{ - int v = git_parse_maybe_bool_text(value); - if (0 <= v) - return v; - if (git_parse_int(value, &v)) - return !!v; - return -1; -} - int git_config_bool_or_int(const char *name, const char *value, const struct key_value_info *kvi, int *is_bool) { @@ -1801,6 +1652,11 @@ static int git_default_core_config(const char *var, const char *value, return 0; } + if (!strcmp(var, "core.maxtreedepth")) { + max_allowed_tree_depth = git_config_int(var, value, ctx->kvi); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return platform_core_config(var, value, ctx, cb); } @@ -2126,28 +1982,6 @@ void git_global_config(char **user_out, char **xdg_out) *xdg_out = xdg_config; } -/* - * Parse environment variable 'k' as a boolean (in various - * possible spellings); if missing, use the default value 'def'. - */ -int git_env_bool(const char *k, int def) -{ - const char *v = getenv(k); - return v ? git_config_bool(k, v) : def; -} - -/* - * Parse environment variable 'k' as ulong with possibly a unit - * suffix; if missing, use the default value 'val'. - */ -unsigned long git_env_ulong(const char *k, unsigned long val) -{ - const char *v = getenv(k); - if (v && !git_parse_ulong(v, &val)) - die(_("failed to parse %s"), k); - return val; -} - int git_config_system(void) { return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); @@ -4,7 +4,7 @@ #include "hashmap.h" #include "string-list.h" #include "repository.h" - +#include "parse.h" /** * The config API gives callers a way to access Git configuration files @@ -243,16 +243,6 @@ int config_with_options(config_fn_t fn, void *, * The following helper functions aid in parsing string values */ -int git_parse_ssize_t(const char *, ssize_t *); -int git_parse_ulong(const char *, unsigned long *); -int git_parse_int(const char *value, int *ret); - -/** - * Same as `git_config_bool`, except that it returns -1 on error rather - * than dying. - */ -int git_parse_maybe_bool(const char *); - /** * Parse the string to an integer, including unit factors. Dies on error; * otherwise, returns the parsed result. @@ -385,8 +375,6 @@ int git_config_rename_section(const char *, const char *); int git_config_rename_section_in_file(const char *, const char *, const char *); int git_config_copy_section(const char *, const char *); int git_config_copy_section_in_file(const char *, const char *, const char *); -int git_env_bool(const char *, int); -unsigned long git_env_ulong(const char *, unsigned long); int git_config_system(void); int config_error_nonbool(const char *); #if defined(__GNUC__) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 133ec92bfa..477ef8157a 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -28,6 +28,8 @@ # completion style. For example '!f() { : git commit ; ... }; f' will # tell the completion to use commit completion. This also works with aliases # of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '". +# 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: @@ -1182,7 +1184,7 @@ __git_aliased_command () :) : skip null command ;; \'*) : skip opening quote after sh -c ;; *) - cur="$word" + cur="${word%;}" break esac done @@ -1607,7 +1609,7 @@ _git_checkout () if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then __git_complete_refs --mode="refs" - elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then __git_complete_refs --mode="remote-heads" else __git_complete_refs $dwim_opt --mode="refs" @@ -1677,6 +1679,11 @@ _git_clone () __git_untracked_file_modes="all no normal" +__git_trailer_tokens () +{ + __git config --name-only --get-regexp '^trailer\..*\.key$' | cut -d. -f 2- | rev | cut -d. -f2- | rev +} + _git_commit () { case "$prev" in @@ -1701,6 +1708,10 @@ _git_commit () __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}" return ;; + --trailer=*) + __gitcomp_nl "$(__git_trailer_tokens)" "" "${cur##--trailer=}" ":" + return + ;; --*) __gitcomp_builtin commit return @@ -2514,7 +2525,7 @@ _git_switch () if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then __git_complete_refs --mode="refs" - elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then __git_complete_refs --mode="remote-heads" else __git_complete_refs $dwim_opt --mode="heads" diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c index ef681f29d5..215a81d8ba 100644 --- a/contrib/credential/libsecret/git-credential-libsecret.c +++ b/contrib/credential/libsecret/git-credential-libsecret.c @@ -39,6 +39,8 @@ struct credential { char *path; char *username; char *password; + char *password_expiry_utc; + char *oauth_refresh_token; }; #define CREDENTIAL_INIT { 0 } @@ -52,8 +54,29 @@ struct credential_operation { #define CREDENTIAL_OP_END { NULL, NULL } +static void credential_clear(struct credential *c); + /* ----------------- Secret Service functions ----------------- */ +static const SecretSchema schema = { + "org.git.Password", + /* Ignore schema name during search for backwards compatibility */ + SECRET_SCHEMA_DONT_MATCH_NAME, + { + /* + * libsecret assumes attribute values are non-confidential and + * unchanging, so we can't include oauth_refresh_token or + * password_expiry_utc. + */ + { "user", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "object", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, + { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } +}; + static char *make_label(struct credential *c) { if (c->port) @@ -101,7 +124,7 @@ static int keyring_get(struct credential *c) attributes = make_attr_list(c); items = secret_service_search_sync(service, - SECRET_SCHEMA_COMPAT_NETWORK, + &schema, attributes, SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK, NULL, @@ -117,6 +140,7 @@ static int keyring_get(struct credential *c) SecretItem *item; SecretValue *secret; const char *s; + gchar **parts; item = items->data; secret = secret_item_get_secret(item); @@ -130,8 +154,27 @@ static int keyring_get(struct credential *c) s = secret_value_get_text(secret); if (s) { - g_free(c->password); - c->password = g_strdup(s); + /* + * Passwords and other attributes encoded in following format: + * hunter2 + * password_expiry_utc=1684189401 + * oauth_refresh_token=xyzzy + */ + parts = g_strsplit(s, "\n", 0); + if (g_strv_length(parts) >= 1) { + g_free(c->password); + c->password = g_strdup(parts[0]); + } + for (int i = 1; i < g_strv_length(parts); i++) { + if (g_str_has_prefix(parts[i], "password_expiry_utc=")) { + g_free(c->password_expiry_utc); + c->password_expiry_utc = g_strdup(&parts[i][20]); + } else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) { + g_free(c->oauth_refresh_token); + c->oauth_refresh_token = g_strdup(&parts[i][20]); + } + } + g_strfreev(parts); } g_hash_table_unref(attributes); @@ -148,6 +191,7 @@ static int keyring_store(struct credential *c) char *label = NULL; GHashTable *attributes = NULL; GError *error = NULL; + GString *secret = NULL; /* * Sanity check that what we are storing is actually sensible. @@ -162,13 +206,23 @@ static int keyring_store(struct credential *c) label = make_label(c); attributes = make_attr_list(c); - secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK, + secret = g_string_new(c->password); + if (c->password_expiry_utc) { + g_string_append_printf(secret, "\npassword_expiry_utc=%s", + c->password_expiry_utc); + } + if (c->oauth_refresh_token) { + g_string_append_printf(secret, "\noauth_refresh_token=%s", + c->oauth_refresh_token); + } + secret_password_storev_sync(&schema, attributes, NULL, label, - c->password, + secret->str, NULL, &error); + g_string_free(secret, TRUE); g_free(label); g_hash_table_unref(attributes); @@ -185,6 +239,7 @@ static int keyring_erase(struct credential *c) { GHashTable *attributes = NULL; GError *error = NULL; + struct credential existing = CREDENTIAL_INIT; /* * Sanity check that we actually have something to match @@ -197,8 +252,22 @@ static int keyring_erase(struct credential *c) if (!c->protocol && !c->host && !c->path && !c->username) return EXIT_FAILURE; + if (c->password) { + existing.host = g_strdup(c->host); + existing.path = g_strdup(c->path); + existing.port = c->port; + existing.protocol = g_strdup(c->protocol); + existing.username = g_strdup(c->username); + keyring_get(&existing); + if (existing.password && strcmp(c->password, existing.password)) { + credential_clear(&existing); + return EXIT_SUCCESS; + } + credential_clear(&existing); + } + attributes = make_attr_list(c); - secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK, + secret_password_clearv_sync(&schema, attributes, NULL, &error); @@ -238,6 +307,8 @@ static void credential_clear(struct credential *c) g_free(c->path); g_free(c->username); g_free(c->password); + g_free(c->password_expiry_utc); + g_free(c->oauth_refresh_token); credential_init(c); } @@ -284,11 +355,19 @@ static int credential_read(struct credential *c) } else if (!strcmp(key, "username")) { g_free(c->username); c->username = g_strdup(value); + } else if (!strcmp(key, "password_expiry_utc")) { + g_free(c->password_expiry_utc); + c->password_expiry_utc = g_strdup(value); } else if (!strcmp(key, "password")) { g_free(c->password); c->password = g_strdup(value); while (*value) *value++ = '\0'; + } else if (!strcmp(key, "oauth_refresh_token")) { + g_free(c->oauth_refresh_token); + c->oauth_refresh_token = g_strdup(value); + while (*value) + *value++ = '\0'; } /* * Ignore other lines; we don't know what they mean, but @@ -314,6 +393,10 @@ static void credential_write(const struct credential *c) /* only write username/password, if set */ credential_write_item(stdout, "username", c->username); credential_write_item(stdout, "password", c->password); + credential_write_item(stdout, "password_expiry_utc", + c->password_expiry_utc); + credential_write_item(stdout, "oauth_refresh_token", + c->oauth_refresh_token); } static void usage(const char *name) diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 96f10613ae..4cd56c42e2 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -109,7 +109,18 @@ static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim) return match_part_with_last(ptarget, want, delim, 1); } -static int match_cred(const CREDENTIALW *cred) +static int match_cred_password(const CREDENTIALW *cred) { + int ret; + WCHAR *cred_password = xmalloc(cred->CredentialBlobSize); + wcsncpy_s(cred_password, cred->CredentialBlobSize, + (LPCWSTR)cred->CredentialBlob, + cred->CredentialBlobSize / sizeof(WCHAR)); + ret = !wcscmp(cred_password, password); + free(cred_password); + return ret; +} + +static int match_cred(const CREDENTIALW *cred, int match_password) { LPCWSTR target = cred->TargetName; if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L"")) @@ -119,7 +130,8 @@ static int match_cred(const CREDENTIALW *cred) match_part(&target, protocol, L"://") && match_part_last(&target, wusername, L"@") && match_part(&target, host, L"/") && - match_part(&target, path, L""); + match_part(&target, path, L"") && + (!match_password || match_cred_password(cred)); } static void get_credential(void) @@ -134,7 +146,7 @@ static void get_credential(void) /* search for the first credential that matches username */ for (i = 0; i < num_creds; ++i) - if (match_cred(creds[i])) { + if (match_cred(creds[i], 0)) { write_item("username", creds[i]->UserName, creds[i]->UserName ? wcslen(creds[i]->UserName) : 0); write_item("password", @@ -196,7 +208,7 @@ static void erase_credential(void) return; for (i = 0; i < num_creds; ++i) { - if (match_cred(creds[i])) + if (match_cred(creds[i], password != NULL)) CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0); } diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 7db4c45676..e0c5d3b0de 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -33,19 +33,19 @@ git subtree split --prefix=<prefix> [<commit>] git subtree pull --prefix=<prefix> <repository> <ref> git subtree push --prefix=<prefix> <repository> <refspec> -- -h,help show the help -q,quiet quiet -d,debug show debug messages +h,help! show the help +q,quiet! quiet +d,debug! show debug messages P,prefix= the name of the subdir to split out options for 'split' (also: 'push') annotate= add a prefix to commit message of new commits -b,branch= create a new branch from the split subtree +b,branch!= create a new branch from the split subtree ignore-joins ignore prior --rejoin commits onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin') squash merge subtree changes as a single commit -m,message= use the given message as the commit message for the merge commit +m,message!= use the given message as the commit message for the merge commit " indent=0 diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 341c169eca..49a21dd7c9 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -71,7 +71,7 @@ test_expect_success 'shows short help text for -h' ' test_expect_code 129 git subtree -h >out 2>err && test_must_be_empty err && grep -e "^ *or: git subtree pull" out && - grep -e --annotate out + grep -F -e "--[no-]annotate" out ' # diff --git a/credential.c b/credential.c index d664754163..18098bd35e 100644 --- a/credential.c +++ b/credential.c @@ -88,8 +88,8 @@ static int proto_is_http(const char *s) static void credential_describe(struct credential *c, struct strbuf *out); static void credential_format(struct credential *c, struct strbuf *out); -static int select_all(const struct urlmatch_item *a, - const struct urlmatch_item *b) +static int select_all(const struct urlmatch_item *a UNUSED, + const struct urlmatch_item *b UNUSED) { return 0; } diff --git a/csum-file.c b/csum-file.c index cd01713244..870748e016 100644 --- a/csum-file.c +++ b/csum-file.c @@ -207,7 +207,7 @@ int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint lseek(f->fd, offset, SEEK_SET) != offset) return -1; f->total = offset; - f->ctx = checkpoint->ctx; + the_hash_algo->clone_fn(&f->ctx, &checkpoint->ctx); f->offset = 0; /* hashflush() was called in checkpoint */ return 0; } @@ -1243,19 +1243,20 @@ static int serve(struct string_list *listen_addr, int listen_port, int cmd_main(int argc, const char **argv) { int listen_port = 0; - struct string_list listen_addr = STRING_LIST_INIT_NODUP; + struct string_list listen_addr = STRING_LIST_INIT_DUP; int serve_mode = 0, inetd_mode = 0; const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; struct credentials *cred = NULL; int i; + int ret; for (i = 1; i < argc; i++) { const char *arg = argv[i]; const char *v; if (skip_prefix(arg, "--listen=", &v)) { - string_list_append(&listen_addr, xstrdup_tolower(v)); + string_list_append_nodup(&listen_addr, xstrdup_tolower(v)); continue; } if (skip_prefix(arg, "--port=", &v)) { @@ -1437,22 +1438,26 @@ int cmd_main(int argc, const char **argv) die_errno("failed to redirect stderr to /dev/null"); } - if (inetd_mode || serve_mode) - return execute(); + if (inetd_mode || serve_mode) { + ret = execute(); + } else { + if (detach) { + if (daemonize()) + die("--detach not supported on this platform"); + } - if (detach) { - if (daemonize()) - die("--detach not supported on this platform"); - } + if (pid_file) + write_file(pid_file, "%"PRIuMAX, (uintmax_t) getpid()); - if (pid_file) - write_file(pid_file, "%"PRIuMAX, (uintmax_t) getpid()); + /* prepare argv for serving-processes */ + strvec_push(&cld_argv, argv[0]); /* git-daemon */ + strvec_push(&cld_argv, "--serve"); + for (i = 1; i < argc; ++i) + strvec_push(&cld_argv, argv[i]); - /* prepare argv for serving-processes */ - strvec_push(&cld_argv, argv[0]); /* git-daemon */ - strvec_push(&cld_argv, "--serve"); - for (i = 1; i < argc; ++i) - strvec_push(&cld_argv, argv[i]); + ret = serve(&listen_addr, listen_port, cred); + } - return serve(&listen_addr, listen_port, cred); + string_list_clear(&listen_addr, 0); + return ret; } diff --git a/decorate.c b/decorate.c index a5c43c0c14..69aeb142b4 100644 --- a/decorate.c +++ b/decorate.c @@ -81,3 +81,18 @@ void *lookup_decoration(struct decoration *n, const struct object *obj) j = 0; } } + +void clear_decoration(struct decoration *n, void (*free_cb)(void *)) +{ + if (free_cb) { + unsigned int i; + for (i = 0; i < n->size; i++) { + void *d = n->entries[i].decoration; + if (d) + free_cb(d); + } + } + + FREE_AND_NULL(n->entries); + n->size = n->nr = 0; +} diff --git a/decorate.h b/decorate.h index ee43dee1f0..cdeb17c9df 100644 --- a/decorate.h +++ b/decorate.h @@ -58,4 +58,14 @@ void *add_decoration(struct decoration *n, const struct object *obj, void *decor */ void *lookup_decoration(struct decoration *n, const struct object *obj); +/* + * Clear all decoration entries, releasing any memory used by the structure. + * If free_cb is not NULL, it is called for every decoration value currently + * stored. + * + * After clearing, the decoration struct can be used again. The "name" field is + * retained. + */ +void clear_decoration(struct decoration *n, void (*free_cb)(void *)); + #endif diff --git a/diff-lib.c b/diff-lib.c index 6b0c6a7180..0e9ec4f68a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -36,14 +36,14 @@ * exists for ce that is a submodule -- it is a submodule that is not * checked out). Return negative for an error. */ -static int check_removed(const struct index_state *istate, const struct cache_entry *ce, struct stat *st) +static int check_removed(const struct cache_entry *ce, struct stat *st) { - assert(is_fsmonitor_refreshed(istate)); - if (!(ce->ce_flags & CE_FSMONITOR_VALID) && lstat(ce->name, st) < 0) { + if (lstat(ce->name, st) < 0) { if (!is_missing_file_error(errno)) return -1; return 1; } + if (has_symlink_leading_path(ce->name, ce_namelen(ce))) return 1; if (S_ISDIR(st->st_mode)) { @@ -96,7 +96,7 @@ static int match_stat_with_submodule(struct diff_options *diffopt, return changed; } -int run_diff_files(struct rev_info *revs, unsigned int option) +void run_diff_files(struct rev_info *revs, unsigned int option) { int entries, i; int diff_unmerged_stage = revs->max_count; @@ -149,7 +149,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) memset(&(dpath->parent[0]), 0, sizeof(struct combine_diff_parent)*5); - changed = check_removed(istate, ce, &st); + changed = check_removed(ce, &st); if (!changed) wt_mode = ce_mode_from_stat(ce, st.st_mode); else { @@ -229,7 +229,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } else { struct stat st; - changed = check_removed(istate, ce, &st); + changed = check_removed(ce, &st); if (changed) { if (changed < 0) { perror(ce->name); @@ -272,7 +272,6 @@ int run_diff_files(struct rev_info *revs, unsigned int option) diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); trace_performance_since(start, "diff-files"); - return 0; } /* @@ -304,7 +303,7 @@ static int get_stat_data(const struct index_state *istate, if (!cached && !ce_uptodate(ce)) { int changed; struct stat st; - changed = check_removed(istate, ce, &st); + changed = check_removed(ce, &st); if (changed < 0) return -1; else if (changed) { @@ -572,8 +571,6 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb) struct object *obj = revs->pending.objects[i].item; if (obj->flags) die(_("--merge-base does not work with ranges")); - if (obj->type != OBJ_COMMIT) - die(_("--merge-base only works with commits")); } /* @@ -606,7 +603,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb) free_commit_list(merge_bases); } -int run_diff_index(struct rev_info *revs, unsigned int option) +void run_diff_index(struct rev_info *revs, unsigned int option) { struct object_array_entry *ent; int cached = !!(option & DIFF_INDEX_CACHED); @@ -640,7 +637,6 @@ int run_diff_index(struct rev_info *revs, unsigned int option) diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); trace_performance_leave("diff-index"); - return 0; } int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt) @@ -682,7 +678,7 @@ int index_differs_from(struct repository *r, rev.diffopt.flags.ignore_submodules = flags->ignore_submodules; } rev.diffopt.ita_invisible_in_index = ita_invisible_in_index; - run_diff_index(&rev, 1); + run_diff_index(&rev, DIFF_INDEX_CACHED); has_changes = rev.diffopt.flags.has_changes; release_revisions(&rev); return (has_changes != 0); diff --git a/diff-no-index.c b/diff-no-index.c index 4771cf02aa..e7041b89e3 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -232,6 +232,7 @@ static int queue_diff(struct diff_options *o, if (o->flags.reverse_diff) { SWAP(mode1, mode2); SWAP(name1, name2); + SWAP(special1, special2); } d1 = noindex_filespec(name1, mode1, special1); @@ -364,7 +365,7 @@ int diff_no_index(struct rev_info *revs, * The return code for --no-index imitates diff(1): * 0 = no changes, 1 = changes, else error */ - ret = diff_result_code(&revs->diffopt, 0); + ret = diff_result_code(&revs->diffopt); out: for (i = 0; i < ARRAY_SIZE(to_free); i++) @@ -65,6 +65,7 @@ int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; static int diff_relative; +static int diff_stat_name_width; static int diff_stat_graph_width; static int diff_dirstat_permille_default = 30; static struct diff_options default_diff_options; @@ -410,6 +411,10 @@ int git_diff_ui_config(const char *var, const char *value, diff_relative = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.statnamewidth")) { + diff_stat_name_width = git_config_int(var, value, ctx->kvi); + return 0; + } if (!strcmp(var, "diff.statgraphwidth")) { diff_stat_graph_width = git_config_int(var, value, ctx->kvi); return 0; @@ -2704,12 +2709,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) number_width = decimal_width(max_change) > number_width ? decimal_width(max_change) : number_width; + if (options->stat_name_width == -1) + options->stat_name_width = diff_stat_name_width; if (options->stat_graph_width == -1) options->stat_graph_width = diff_stat_graph_width; /* - * Guarantee 3/8*16==6 for the graph part - * and 5/8*16==10 for the filename part + * Guarantee 3/8*16 == 6 for the graph part + * and 5/8*16 == 10 for the filename part */ if (width < 16 + 6 + number_width) width = 16 + 6 + number_width; @@ -3563,18 +3570,21 @@ static void builtin_diff(const char *name_a, strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); + o->found_changes = 1; must_show_header = 1; } else if (lbl[1][0] == '/') { strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); + o->found_changes = 1; must_show_header = 1; } else { if (one->mode != two->mode) { strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset); strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset); + o->found_changes = 1; must_show_header = 1; } if (xfrm_msg) @@ -4832,6 +4842,10 @@ void diff_setup_done(struct diff_options *options) else options->prefix_length = 0; + /* + * --name-only, --name-status, --checkdiff, and -s + * turn other output format off. + */ if (options->output_format & (DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF | @@ -6206,6 +6220,8 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) fprintf(opt->file, "%s", diff_line_prefix(opt)); write_name_quoted(name_a, opt->file, opt->line_termination); } + + opt->found_changes = 1; } static void show_file_mode_name(struct diff_options *opt, const char *newdelete, struct diff_filespec *fs) @@ -6684,6 +6700,21 @@ void diff_flush(struct diff_options *options) separator++; } + if (output_format & DIFF_FORMAT_PATCH) { + if (separator) { + emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0); + if (options->stat_sep) + /* attach patch instead of inline */ + emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP, + NULL, 0, 0); + } + + diff_flush_patch_all_file_pairs(options); + } + + if (output_format & DIFF_FORMAT_CALLBACK) + options->format_callback(q, options, options->format_callback_data); + if (output_format & DIFF_FORMAT_NO_OUTPUT && options->flags.exit_with_status && options->flags.diff_from_contents) { @@ -6705,21 +6736,6 @@ void diff_flush(struct diff_options *options) } } - if (output_format & DIFF_FORMAT_PATCH) { - if (separator) { - emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0); - if (options->stat_sep) - /* attach patch instead of inline */ - emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP, - NULL, 0, 0); - } - - diff_flush_patch_all_file_pairs(options); - } - - if (output_format & DIFF_FORMAT_CALLBACK) - options->format_callback(q, options, options->format_callback_data); - free_queue: diff_free_queue(q); DIFF_QUEUE_CLEAR(q); @@ -6920,6 +6936,13 @@ void diff_queued_diff_prefetch(void *repository) oid_array_clear(&to_fetch); } +void init_diffstat_widths(struct diff_options *options) +{ + options->stat_width = -1; /* use full terminal width */ + options->stat_name_width = -1; /* respect diff.statNameWidth config */ + options->stat_graph_width = -1; /* respect diff.statGraphWidth config */ +} + void diffcore_std(struct diff_options *options) { int output_formats_to_prefetch = DIFF_FORMAT_DIFFSTAT | @@ -6973,16 +6996,14 @@ void diffcore_std(struct diff_options *options) options->found_follow = 0; } -int diff_result_code(struct diff_options *opt, int status) +int diff_result_code(struct diff_options *opt) { int result = 0; diff_warn_rename_limit("diff.renameLimit", opt->needed_rename_limit, opt->degraded_cc_to_c); - if (!opt->flags.exit_with_status && - !(opt->output_format & DIFF_FORMAT_CHECKDIFF)) - return status; + if (opt->flags.exit_with_status && opt->flags.has_changes) result |= 01; @@ -7029,6 +7050,7 @@ void compute_diffstat(struct diff_options *options, if (check_pair_status(p)) diff_flush_stat(p, options, diffstat); } + options->found_changes = !!diffstat->nr; } void diff_addremove(struct diff_options *options, @@ -573,6 +573,7 @@ int git_config_rename(const char *var, const char *value); #define DIFF_PICKAXE_IGNORE_CASE 32 +void init_diffstat_widths(struct diff_options *); void diffcore_std(struct diff_options *); void diffcore_fix_diff_index(void); @@ -637,17 +638,17 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb); #define DIFF_SILENT_ON_REMOVED 01 /* report racily-clean paths as modified */ #define DIFF_RACY_IS_MODIFIED 02 -int run_diff_files(struct rev_info *revs, unsigned int option); +void run_diff_files(struct rev_info *revs, unsigned int option); #define DIFF_INDEX_CACHED 01 #define DIFF_INDEX_MERGE_BASE 02 -int run_diff_index(struct rev_info *revs, unsigned int option); +void run_diff_index(struct rev_info *revs, unsigned int option); int do_diff_cache(const struct object_id *, struct diff_options *); int diff_flush_patch_id(struct diff_options *, struct object_id *, int); void flush_one_hunk(struct object_id *result, git_hash_ctx *ctx); -int diff_result_code(struct diff_options *, int); +int diff_result_code(struct diff_options *); int diff_no_index(struct rev_info *, int implicit_no_index, int, const char **); @@ -581,3 +581,8 @@ void unlink_entry(const struct cache_entry *ce, const char *super_prefix) return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } + +int remove_or_warn(unsigned int mode, const char *file) +{ + return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); +} @@ -62,4 +62,10 @@ int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st) void update_ce_after_write(const struct checkout *state, struct cache_entry *ce, struct stat *st); +/* + * Calls the correct function out of {unlink,rmdir}_or_warn based on + * the supplied file mode. + */ +int remove_or_warn(unsigned int mode, const char *path); + #endif /* ENTRY_H */ diff --git a/environment.c b/environment.c index f98d76f080..bb3c2a96a3 100644 --- a/environment.c +++ b/environment.c @@ -81,6 +81,7 @@ int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; +int max_allowed_tree_depth = 2048; #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 diff --git a/environment.h b/environment.h index c5377473c6..e5351c9dd9 100644 --- a/environment.h +++ b/environment.h @@ -132,6 +132,7 @@ extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; +extern int max_allowed_tree_depth; /* * Accessors for the core.sharedrepository config which lazy-load the value diff --git a/fetch-pack.c b/fetch-pack.c index 65c1ff4bb4..26999e3b65 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -1911,10 +1911,10 @@ static void fetch_pack_setup(void) if (did_setup) return; fetch_pack_config(); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= fetch_unpack_limit) + if (0 <= fetch_unpack_limit) unpack_limit = fetch_unpack_limit; + else if (0 <= transfer_unpack_limit) + unpack_limit = transfer_unpack_limit; did_setup = 1; } @@ -24,6 +24,8 @@ #include "credential.h" #include "help.h" +static ssize_t max_tree_entry_len = 4096; + #define STR(x) #x #define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type }, static struct { @@ -154,15 +156,29 @@ void fsck_set_msg_type(struct fsck_options *options, const char *msg_id_str, const char *msg_type_str) { int msg_id = parse_msg_id(msg_id_str); - enum fsck_msg_type msg_type = parse_msg_type(msg_type_str); + char *to_free = NULL; + enum fsck_msg_type msg_type; if (msg_id < 0) die("Unhandled message id: %s", msg_id_str); + if (msg_id == FSCK_MSG_LARGE_PATHNAME) { + const char *colon = strchr(msg_type_str, ':'); + if (colon) { + msg_type_str = to_free = + xmemdupz(msg_type_str, colon - msg_type_str); + colon++; + if (!git_parse_ssize_t(colon, &max_tree_entry_len)) + die("unable to parse max tree entry len: %s", colon); + } + } + msg_type = parse_msg_type(msg_type_str); + if (msg_type != FSCK_ERROR && msg_id_info[msg_id].msg_type == FSCK_FATAL) die("Cannot demote %s to %s", msg_id_str, msg_type_str); fsck_set_msg_type_from_ids(options, msg_id, msg_type); + free(to_free); } void fsck_set_msg_types(struct fsck_options *options, const char *values) @@ -578,6 +594,7 @@ static int fsck_tree(const struct object_id *tree_oid, int has_bad_modes = 0; int has_dup_entries = 0; int not_properly_sorted = 0; + int has_large_name = 0; struct tree_desc desc; unsigned o_mode; const char *o_name; @@ -607,6 +624,7 @@ static int fsck_tree(const struct object_id *tree_oid, has_dotdot |= !strcmp(name, ".."); has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name); has_zero_pad |= *(char *)desc.buffer == '0'; + has_large_name |= tree_entry_len(&desc.entry) > max_tree_entry_len; if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) { if (!S_ISLNK(mode)) @@ -749,6 +767,10 @@ static int fsck_tree(const struct object_id *tree_oid, retval += report(options, tree_oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted"); + if (has_large_name) + retval += report(options, tree_oid, OBJ_TREE, + FSCK_MSG_LARGE_PATHNAME, + "contains excessively large pathname"); return retval; } @@ -73,6 +73,7 @@ enum fsck_msg_type { FUNC(NULL_SHA1, WARN) \ FUNC(ZERO_PADDED_FILEMODE, WARN) \ FUNC(NUL_IN_COMMIT, WARN) \ + FUNC(LARGE_PATHNAME, WARN) \ /* infos (reported as warnings, but ignored by default) */ \ FUNC(BAD_FILEMODE, INFO) \ FUNC(GITMODULES_PARSE, INFO) \ diff --git a/fsmonitor-ipc.c b/fsmonitor-ipc.c index 88575aa54c..153918cf76 100644 --- a/fsmonitor-ipc.c +++ b/fsmonitor-ipc.c @@ -20,7 +20,7 @@ int fsmonitor_ipc__is_supported(void) return 0; } -const char *fsmonitor_ipc__get_path(struct repository *r) +const char *fsmonitor_ipc__get_path(struct repository *r UNUSED) { return NULL; } @@ -30,14 +30,14 @@ enum ipc_active_state fsmonitor_ipc__get_state(void) return IPC_STATE__OTHER_ERROR; } -int fsmonitor_ipc__send_query(const char *since_token, - struct strbuf *answer) +int fsmonitor_ipc__send_query(const char *since_token UNUSED, + struct strbuf *answer UNUSED) { return -1; } -int fsmonitor_ipc__send_command(const char *command, - struct strbuf *answer) +int fsmonitor_ipc__send_command(const char *command UNUSED, + struct strbuf *answer UNUSED) { return -1; } diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c index b62acf44ae..a6a9e6bc19 100644 --- a/fsmonitor-settings.c +++ b/fsmonitor-settings.c @@ -62,7 +62,8 @@ static enum fsmonitor_reason check_remote(struct repository *r) } #endif -static enum fsmonitor_reason check_for_incompatible(struct repository *r, int ipc) +static enum fsmonitor_reason check_for_incompatible(struct repository *r, + int ipc MAYBE_UNUSED) { if (!r->worktree) { /* diff --git a/git-compat-util.h b/git-compat-util.h index d32aa754ae..3e7a59b5ff 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -422,6 +422,10 @@ char *gitdirname(char *); #define PATH_MAX 4096 #endif +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + typedef uintmax_t timestamp_t; #define PRItime PRIuMAX #define parse_timestamp strtoumax diff --git a/git-gui/Makefile b/git-gui/Makefile index a0d5a4b28e..3f80435436 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -138,25 +138,10 @@ GITGUI_SCRIPT := $$0 GITGUI_RELATIVE := GITGUI_MACOSXAPP := -ifeq ($(uname_O),Cygwin) - GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"` - - # Is this a Cygwin Tcl/Tk binary? If so it knows how to do - # POSIX path translation just like cygpath does and we must - # keep libdir in POSIX format so Cygwin packages of git-gui - # work no matter where the user installs them. - # - ifeq ($(shell echo 'puts [file normalize /]' | '$(TCL_PATH_SQ)'),$(shell cygpath --mixed --absolute /)) - gg_libdir_sed_in := $(gg_libdir) - else - gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)") - endif -else - ifeq ($(exedir),$(gg_libdir)) - GITGUI_RELATIVE := 1 - endif - gg_libdir_sed_in := $(gg_libdir) +ifeq ($(exedir),$(gg_libdir)) + GITGUI_RELATIVE := 1 endif +gg_libdir_sed_in := $(gg_libdir) ifeq ($(uname_S),Darwin) ifeq ($(shell test -d $(TKFRAMEWORK) && echo y),y) GITGUI_MACOSXAPP := YesPlease diff --git a/git-gui/README.md b/git-gui/README.md index 5ce2122fbc..b460b649a8 100644 --- a/git-gui/README.md +++ b/git-gui/README.md @@ -88,7 +88,7 @@ that you first use `git-format-patch` to generate the emails, audit them, and then send them via `git-send-email`. A pretty good guide to configuring and using `git-send-email` can be found -[here](https://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/) +[here](https://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/). ### Using your email client diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 201524c34e..3e5907a460 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -46,6 +46,132 @@ catch {rename send {}} ; # What an evil concept... ###################################################################### ## +## Enabling platform-specific code paths + +proc is_MacOSX {} { + if {[tk windowingsystem] eq {aqua}} { + return 1 + } + return 0 +} + +proc is_Windows {} { + if {$::tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + +set _iscygwin {} +proc is_Cygwin {} { + global _iscygwin + if {$_iscygwin eq {}} { + if {[string match "CYGWIN_*" $::tcl_platform(os)]} { + set _iscygwin 1 + } else { + set _iscygwin 0 + } + } + return $_iscygwin +} + +###################################################################### +## +## PATH lookup + +set _search_path {} +proc _which {what args} { + global env _search_exe _search_path + + if {$_search_path eq {}} { + if {[is_Windows]} { + set gitguidir [file dirname [info script]] + regsub -all ";" $gitguidir "\\;" gitguidir + set env(PATH) "$gitguidir;$env(PATH)" + set _search_path [split $env(PATH) {;}] + # Skip empty `PATH` elements + set _search_path [lsearch -all -inline -not -exact \ + $_search_path ""] + set _search_exe .exe + } else { + set _search_path [split $env(PATH) :] + set _search_exe {} + } + } + + if {[is_Windows] && [lsearch -exact $args -script] >= 0} { + set suffix {} + } else { + set suffix $_search_exe + } + + foreach p $_search_path { + set p [file join $p $what$suffix] + if {[file exists $p]} { + return [file normalize $p] + } + } + return {} +} + +proc sanitize_command_line {command_line from_index} { + set i $from_index + while {$i < [llength $command_line]} { + set cmd [lindex $command_line $i] + if {[llength [file split $cmd]] < 2} { + set fullpath [_which $cmd] + if {$fullpath eq ""} { + throw {NOT-FOUND} "$cmd not found in PATH" + } + lset command_line $i $fullpath + } + + # handle piped commands, e.g. `exec A | B` + for {incr i} {$i < [llength $command_line]} {incr i} { + if {[lindex $command_line $i] eq "|"} { + incr i + break + } + } + } + return $command_line +} + +# Override `exec` to avoid unsafe PATH lookup + +rename exec real_exec + +proc exec {args} { + # skip options + for {set i 0} {$i < [llength $args]} {incr i} { + set arg [lindex $args $i] + if {$arg eq "--"} { + incr i + break + } + if {[string range $arg 0 0] ne "-"} { + break + } + } + set args [sanitize_command_line $args $i] + uplevel 1 real_exec $args +} + +# Override `open` to avoid unsafe PATH lookup + +rename open real_open + +proc open {args} { + set arg0 [lindex $args 0] + if {[string range $arg0 0 0] eq "|"} { + set command_line [string trim [string range $arg0 1 end]] + lset args 0 "| [sanitize_command_line $command_line 0]" + } + uplevel 1 real_open $args +} + +###################################################################### +## ## locate our library if { [info exists ::env(GIT_GUI_LIB_DIR) ] } { @@ -163,8 +289,6 @@ set _isbare {} set _gitexec {} set _githtmldir {} set _reponame {} -set _iscygwin {} -set _search_path {} set _shellpath {@@SHELL_PATH@@} set _trace [lsearch -exact $argv --trace] @@ -211,14 +335,7 @@ proc gitexec {args} { if {[catch {set _gitexec [git --exec-path]} err]} { error "Git not installed?\n\n$err" } - if {[is_Cygwin]} { - set _gitexec [exec cygpath \ - --windows \ - --absolute \ - $_gitexec] - } else { - set _gitexec [file normalize $_gitexec] - } + set _gitexec [file normalize $_gitexec] } if {$args eq {}} { return $_gitexec @@ -233,14 +350,7 @@ proc githtmldir {args} { # Git not installed or option not yet supported return {} } - if {[is_Cygwin]} { - set _githtmldir [exec cygpath \ - --windows \ - --absolute \ - $_githtmldir] - } else { - set _githtmldir [file normalize $_githtmldir] - } + set _githtmldir [file normalize $_githtmldir] } if {$args eq {}} { return $_githtmldir @@ -252,40 +362,6 @@ proc reponame {} { return $::_reponame } -proc is_MacOSX {} { - if {[tk windowingsystem] eq {aqua}} { - return 1 - } - return 0 -} - -proc is_Windows {} { - if {$::tcl_platform(platform) eq {windows}} { - return 1 - } - return 0 -} - -proc is_Cygwin {} { - global _iscygwin - if {$_iscygwin eq {}} { - if {$::tcl_platform(platform) eq {windows}} { - if {[catch {set p [exec cygpath --windir]} err]} { - set _iscygwin 0 - } else { - set _iscygwin 1 - # Handle MSys2 which is only cygwin when MSYSTEM is MSYS. - if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} { - set _iscygwin 0 - } - } - } else { - set _iscygwin 0 - } - } - return $_iscygwin -} - proc is_enabled {option} { global enabled_options if {[catch {set on $enabled_options($option)}]} {return 0} @@ -448,44 +524,6 @@ proc _git_cmd {name} { return $v } -proc _which {what args} { - global env _search_exe _search_path - - if {$_search_path eq {}} { - if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} { - set _search_path [split [exec cygpath \ - --windows \ - --path \ - --absolute \ - $env(PATH)] {;}] - set _search_exe .exe - } elseif {[is_Windows]} { - set gitguidir [file dirname [info script]] - regsub -all ";" $gitguidir "\\;" gitguidir - set env(PATH) "$gitguidir;$env(PATH)" - set _search_path [split $env(PATH) {;}] - set _search_exe .exe - } else { - set _search_path [split $env(PATH) :] - set _search_exe {} - } - } - - if {[is_Windows] && [lsearch -exact $args -script] >= 0} { - set suffix {} - } else { - set suffix $_search_exe - } - - foreach p $_search_path { - set p [file join $p $what$suffix] - if {[file exists $p]} { - return [file normalize $p] - } - } - return {} -} - # Test a file for a hashbang to identify executable scripts on Windows. proc is_shellscript {filename} { if {![file exists $filename]} {return 0} @@ -623,31 +661,8 @@ proc git_write {args} { } proc githook_read {hook_name args} { - set pchook [gitdir hooks $hook_name] - lappend args 2>@1 - - # On Windows [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. - # - if {[is_Windows]} { - upvar #0 _sh interp - if {![info exists interp]} { - set interp [_which sh] - } - if {$interp eq {}} { - error "hook execution requires sh (not in PATH)" - } - - set scr {if test -x "$1";then exec "$@";fi} - set sh_c [list $interp -c $scr $interp $pchook] - return [_open_stdout_stderr [concat $sh_c $args]] - } - - if {[file executable $pchook]} { - return [_open_stdout_stderr [concat [list $pchook] $args]] - } - - return {} + set cmd [concat git hook run --ignore-missing $hook_name -- $args 2>@1] + return [_open_stdout_stderr $cmd] } proc kill_file_process {fd} { @@ -1259,9 +1274,6 @@ if {$_gitdir eq "."} { set _gitdir [pwd] } -if {![file isdirectory $_gitdir] && [is_Cygwin]} { - catch {set _gitdir [exec cygpath --windows $_gitdir]} -} if {![file isdirectory $_gitdir]} { catch {wm withdraw .} error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] @@ -1273,11 +1285,7 @@ apply_config # v1.7.0 introduced --show-toplevel to return the canonical work-tree if {[package vcompare $_git_version 1.7.0] >= 0} { - if { [is_Cygwin] } { - catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]} - } else { - set _gitworktree [git rev-parse --show-toplevel] - } + set _gitworktree [git rev-parse --show-toplevel] } else { # try to set work tree from environment, core.worktree or use # cdup to obtain a relative path to the top of the worktree. If @@ -1502,24 +1510,8 @@ proc rescan {after {honor_trustmtime 1}} { } } -if {[is_Cygwin]} { - set is_git_info_exclude {} - proc have_info_exclude {} { - global is_git_info_exclude - - if {$is_git_info_exclude eq {}} { - if {[catch {exec test -f [gitdir info exclude]}]} { - set is_git_info_exclude 0 - } else { - set is_git_info_exclude 1 - } - } - return $is_git_info_exclude - } -} else { - proc have_info_exclude {} { - return [file readable [gitdir info exclude]] - } +proc have_info_exclude {} { + return [file readable [gitdir info exclude]] } proc rescan_stage2 {fd after} { @@ -2259,7 +2251,9 @@ proc do_git_gui {} { # Get the system-specific explorer app/command. proc get_explorer {} { - if {[is_Cygwin] || [is_Windows]} { + if {[is_Cygwin]} { + set explorer "/bin/cygstart.exe --explore" + } elseif {[is_Windows]} { set explorer "explorer.exe" } elseif {[is_MacOSX]} { set explorer "open" @@ -3053,10 +3047,6 @@ if {[is_MacOSX]} { set doc_path [githtmldir] if {$doc_path ne {}} { set doc_path [file join $doc_path index.html] - - if {[is_Cygwin]} { - set doc_path [exec cygpath --mixed $doc_path] - } } if {[file isfile $doc_path]} { @@ -4028,60 +4018,6 @@ set file_lists($ui_workdir) [list] wm title . "[appname] ([reponame]) [file normalize $_gitworktree]" focus -force $ui_comm -# -- Warn the user about environmental problems. Cygwin's Tcl -# does *not* pass its env array onto any processes it spawns. -# This means that git processes get none of our environment. -# -if {[is_Cygwin]} { - set ignored_env 0 - set suggest_user {} - set msg [mc "Possible environment issues exist. - -The following environment variables are probably -going to be ignored by any Git subprocess run -by %s: - -" [appname]] - foreach name [array names env] { - switch -regexp -- $name { - {^GIT_INDEX_FILE$} - - {^GIT_OBJECT_DIRECTORY$} - - {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} - - {^GIT_DIFF_OPTS$} - - {^GIT_EXTERNAL_DIFF$} - - {^GIT_PAGER$} - - {^GIT_TRACE$} - - {^GIT_CONFIG$} - - {^GIT_(AUTHOR|COMMITTER)_DATE$} { - append msg " - $name\n" - incr ignored_env - } - {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} { - append msg " - $name\n" - incr ignored_env - set suggest_user $name - } - } - } - if {$ignored_env > 0} { - append msg [mc " -This is due to a known issue with the -Tcl binary distributed by Cygwin."] - - if {$suggest_user ne {}} { - append msg [mc " - -A good replacement for %s -is placing values for the user.name and -user.email settings into your personal -~/.gitconfig file. -" $suggest_user] - } - warn_popup $msg - } - unset ignored_env msg suggest_user name -} - # -- Only initialize complex UI if we are going to stay running. # if {[is_enabled transport]} { diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index af1fee7c75..d23abedcb3 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -174,9 +174,6 @@ constructor pick {} { -foreground blue \ -underline 1 set home $::env(HOME) - if {[is_Cygwin]} { - set home [exec cygpath --windows --absolute $home] - } set home "[file normalize $home]/" set hlen [string length $home] foreach p $sorted_recent { @@ -374,18 +371,6 @@ proc _objdir {path} { return $objdir } - if {[is_Cygwin]} { - set objdir [file join $path .git objects.lnk] - if {[file isfile $objdir]} { - return [win32_read_lnk $objdir] - } - - set objdir [file join $path objects.lnk] - if {[file isfile $objdir]} { - return [win32_read_lnk $objdir] - } - } - return {} } @@ -623,12 +608,6 @@ method _do_clone2 {} { } set giturl $origin_url - if {[is_Cygwin] && [file isdirectory $giturl]} { - set giturl [exec cygpath --unix --absolute $giturl] - if {$clone_type eq {shared}} { - set objdir [exec cygpath --unix --absolute $objdir] - } - } if {[file exists $local_path]} { error_popup [mc "Location %s already exists." $local_path] @@ -668,11 +647,7 @@ method _do_clone2 {} { fconfigure $f_cp -translation binary -encoding binary cd $objdir while {[gets $f_in line] >= 0} { - if {[is_Cygwin]} { - puts $f_cp [exec cygpath --unix --absolute $line] - } else { - puts $f_cp [file normalize $line] - } + puts $f_cp [file normalize $line] } close $f_in close $f_cp diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 97d1d7aa02..674a41f5e0 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -27,13 +27,10 @@ proc do_windows_shortcut {} { } proc do_cygwin_shortcut {} { - global argv0 _gitworktree + global argv0 _gitworktree oguilib if {[catch { set desktop [exec cygpath \ - --windows \ - --absolute \ - --long-name \ --desktop] }]} { set desktop . @@ -48,19 +45,19 @@ proc do_cygwin_shortcut {} { set fn ${fn}.lnk } if {[catch { - set sh [exec cygpath \ - --windows \ - --absolute \ - /bin/sh.exe] - set me [exec cygpath \ - --unix \ - --absolute \ - $argv0] - win32_create_lnk $fn [list \ - $sh -c \ - "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \ - ] \ - [file normalize $_gitworktree] + set repodir [file normalize $_gitworktree] + set shargs {-c \ + "CHERE_INVOKING=1 \ + source /etc/profile; \ + git gui"} + exec /bin/mkshortcut.exe \ + --arguments $shargs \ + --desc "git-gui on $repodir" \ + --icon $oguilib/git-gui.ico \ + --name $fn \ + --show min \ + --workingdir $repodir \ + /bin/sh.exe } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] } diff --git a/git-send-email.perl b/git-send-email.perl index 897cea6564..288ea1ae80 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1166,10 +1166,10 @@ sub extract_valid_address { sub extract_valid_address_or_die { my $address = shift; - $address = extract_valid_address($address); + my $valid_address = extract_valid_address($address); die sprintf(__("error: unable to extract a valid address from: %s\n"), $address) - if !$address; - return $address; + if !$valid_address; + return $valid_address; } sub validate_address { diff --git a/git-svn.perl b/git-svn.perl index be987e316f..4e8878f035 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -297,28 +297,12 @@ my %cmd = ( {} ], ); -package FakeTerm; -sub new { - my ($class, $reason) = @_; - return bless \$reason, shift; -} -sub readline { - my $self = shift; - die "Cannot use readline on FakeTerm: $$self"; -} -package main; - my $term; sub term_init { - $term = eval { - require Term::ReadLine; - $ENV{"GIT_SVN_NOTTY"} + require Term::ReadLine; + $term = $ENV{"GIT_SVN_NOTTY"} ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT : new Term::ReadLine 'git-svn'; - }; - if ($@) { - $term = new FakeTerm "$@: going non-interactive"; - } } my $cmd; @@ -339,7 +339,6 @@ void graph_setup_line_prefix(struct diff_options *diffopt) diffopt->output_prefix = diff_output_prefix_callback; } - struct git_graph *graph_init(struct rev_info *opt) { struct git_graph *graph = xmalloc(sizeof(struct git_graph)); @@ -17,7 +17,7 @@ static int grep_source_load(struct grep_source *gs); static int grep_source_is_binary(struct grep_source *gs, struct index_state *istate); -static void std_output(struct grep_opt *opt, const void *buf, size_t size) +static void std_output(struct grep_opt *opt UNUSED, const void *buf, size_t size) { fwrite(buf, size, 1, stdout); } @@ -452,18 +452,20 @@ static void free_pcre2_pattern(struct grep_pat *p) pcre2_general_context_free(p->pcre2_general_context); } #else /* !USE_LIBPCRE2 */ -static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) +static void compile_pcre2_pattern(struct grep_pat *p UNUSED, + const struct grep_opt *opt UNUSED) { die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); } -static int pcre2match(struct grep_pat *p, const char *line, const char *eol, - regmatch_t *match, int eflags) +static int pcre2match(struct grep_pat *p UNUSED, const char *line UNUSED, + const char *eol UNUSED, regmatch_t *match UNUSED, + int eflags UNUSED) { return 1; } -static void free_pcre2_pattern(struct grep_pat *p) +static void free_pcre2_pattern(struct grep_pat *p UNUSED) { } diff --git a/hex-ll.c b/hex-ll.c new file mode 100644 index 0000000000..4d7ece1de5 --- /dev/null +++ b/hex-ll.c @@ -0,0 +1,49 @@ +#include "git-compat-util.h" +#include "hex-ll.h" + +const signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; + +int hex_to_bytes(unsigned char *binary, const char *hex, size_t len) +{ + for (; len; len--, hex += 2) { + unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); + + if (val & ~0xff) + return -1; + *binary++ = val; + } + return 0; +} diff --git a/hex-ll.h b/hex-ll.h new file mode 100644 index 0000000000..a381fa8556 --- /dev/null +++ b/hex-ll.h @@ -0,0 +1,27 @@ +#ifndef HEX_LL_H +#define HEX_LL_H + +extern const signed char hexval_table[256]; +static inline unsigned int hexval(unsigned char c) +{ + return hexval_table[c]; +} + +/* + * Convert two consecutive hexadecimal digits into a char. Return a + * negative value on error. Don't run over the end of short strings. + */ +static inline int hex2chr(const char *s) +{ + unsigned int val = hexval(s[0]); + return (val & ~0xf) ? val : (val << 4) | hexval(s[1]); +} + +/* + * Read `len` pairs of hexadecimal digits from `hex` and write the + * values to `binary` as `len` bytes. Return 0 on success, or -1 if + * the input does not consist of hex digits). + */ +int hex_to_bytes(unsigned char *binary, const char *hex, size_t len); + +#endif @@ -2,53 +2,6 @@ #include "hash.h" #include "hex.h" -const signed char hexval_table[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ -}; - -int hex_to_bytes(unsigned char *binary, const char *hex, size_t len) -{ - for (; len; len--, hex += 2) { - unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); - - if (val & ~0xff) - return -1; - *binary++ = val; - } - return 0; -} - static int get_hash_hex_algop(const char *hex, unsigned char *hash, const struct git_hash_algo *algop) { @@ -2,22 +2,7 @@ #define HEX_H #include "hash-ll.h" - -extern const signed char hexval_table[256]; -static inline unsigned int hexval(unsigned char c) -{ - return hexval_table[c]; -} - -/* - * Convert two consecutive hexadecimal digits into a char. Return a - * negative value on error. Don't run over the end of short strings. - */ -static inline int hex2chr(const char *s) -{ - unsigned int val = hexval(s[0]); - return (val & ~0xf) ? val : (val << 4) | hexval(s[1]); -} +#include "hex-ll.h" /* * Try to read a hash (specified by the_hash_algo) in hexadecimal @@ -35,13 +20,6 @@ int get_oid_hex(const char *hex, struct object_id *oid); int get_oid_hex_algop(const char *hex, struct object_id *oid, const struct git_hash_algo *algop); /* - * Read `len` pairs of hexadecimal digits from `hex` and write the - * values to `binary` as `len` bytes. Return 0 on success, or -1 if - * the input does not consist of hex digits). - */ -int hex_to_bytes(unsigned char *binary, const char *hex, size_t len); - -/* * Convert a binary hash in "unsigned char []" or an object name in * "struct object_id *" to its hex equivalent. The `_r` variant is reentrant, * and writes the NUL-terminated output to the buffer `out`, which must be at @@ -738,18 +738,43 @@ static int redact_sensitive_header(struct strbuf *header, size_t offset) return ret; } +static int match_curl_h2_trace(const char *line, const char **out) +{ + const char *p; + + /* + * curl prior to 8.1.0 gives us: + * + * h2h3 [<header-name>: <header-val>] + * + * Starting in 8.1.0, the first token became just "h2". + */ + if (skip_iprefix(line, "h2h3 [", out) || + skip_iprefix(line, "h2 [", out)) + return 1; + + /* + * curl 8.3.0 uses: + * [HTTP/2] [<stream-id>] [<header-name>: <header-val>] + * where <stream-id> is numeric. + */ + if (skip_iprefix(line, "[HTTP/2] [", &p)) { + while (isdigit(*p)) + p++; + if (skip_prefix(p, "] [", out)) + return 1; + } + + return 0; +} + /* Redact headers in info */ static void redact_sensitive_info_header(struct strbuf *header) { const char *sensitive_header; - /* - * curl's h2h3 prints headers in info, e.g.: - * h2h3 [<header-name>: <header-val>] - */ if (trace_curl_redact && - (skip_iprefix(header->buf, "h2h3 [", &sensitive_header) || - skip_iprefix(header->buf, "h2 [", &sensitive_header))) { + match_curl_h2_trace(header->buf, &sensitive_header)) { if (redact_sensitive_header(header, sensitive_header - header->buf)) { /* redaction ate our closing bracket */ strbuf_addch(header, ']'); diff --git a/imap-send.c b/imap-send.c index 06386e0b3b..996651e4f8 100644 --- a/imap-send.c +++ b/imap-send.c @@ -206,10 +206,14 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret) else fprintf(stderr, "%s: unexpected EOF\n", func); } + /* mark as used to appease -Wunused-parameter with NO_OPENSSL */ + (void)sock; } #ifdef NO_OPENSSL -static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify) +static int ssl_socket_connect(struct imap_socket *sock UNUSED, + int use_tls_only UNUSED, + int verify UNUSED) { fprintf(stderr, "SSL requested but SSL support not compiled in\n"); return -1; @@ -904,7 +908,9 @@ static char *cram(const char *challenge_64, const char *user, const char *pass) #else -static char *cram(const char *challenge_64, const char *user, const char *pass) +static char *cram(const char *challenge_64 UNUSED, + const char *user UNUSED, + const char *pass UNUSED) { die("If you want to use CRAM-MD5 authenticate method, " "you have to build git-imap-send with OpenSSL library."); diff --git a/line-log.c b/line-log.c index 790ab73212..24a1ecb677 100644 --- a/line-log.c +++ b/line-log.c @@ -1327,3 +1327,13 @@ int line_log_filter(struct rev_info *rev) return 0; } + +static void free_void_line_log_data(void *data) +{ + free_line_log_data(data); +} + +void line_log_free(struct rev_info *rev) +{ + clear_decoration(&rev->line_log_data, free_void_line_log_data); +} diff --git a/line-log.h b/line-log.h index adff361b1b..4291da8d79 100644 --- a/line-log.h +++ b/line-log.h @@ -60,4 +60,6 @@ int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, int line_log_print(struct rev_info *rev, struct commit *commit); +void line_log_free(struct rev_info *rev); + #endif /* LINE_LOG_H */ diff --git a/list-objects.c b/list-objects.c index e60a6cd5b4..c25c72b32c 100644 --- a/list-objects.c +++ b/list-objects.c @@ -14,6 +14,7 @@ #include "packfile.h" #include "object-store-ll.h" #include "trace.h" +#include "environment.h" struct traversal_context { struct rev_info *revs; @@ -21,6 +22,7 @@ struct traversal_context { show_commit_fn show_commit; void *show_data; struct filter *filter; + int depth; }; static void show_commit(struct traversal_context *ctx, @@ -118,7 +120,9 @@ static void process_tree_contents(struct traversal_context *ctx, entry.path, oid_to_hex(&tree->object.oid)); } t->object.flags |= NOT_USER_GIVEN; + ctx->depth++; process_tree(ctx, t, base, entry.path); + ctx->depth--; } else if (S_ISGITLINK(entry.mode)) ; /* ignore gitlink */ @@ -156,6 +160,9 @@ static void process_tree(struct traversal_context *ctx, !revs->include_check_obj(&tree->object, revs->include_check_data)) return; + if (ctx->depth > max_allowed_tree_depth) + die("exceeded maximum allowed tree depth"); + failed_parse = parse_tree_gently(tree, 1); if (failed_parse) { if (revs->ignore_missing_links) @@ -349,6 +356,7 @@ static void traverse_non_commits(struct traversal_context *ctx, if (!path) path = ""; if (obj->type == OBJ_TREE) { + ctx->depth = 0; process_tree(ctx, (struct tree *)obj, base, path); continue; } diff --git a/log-tree.c b/log-tree.c index 208c69cbb7..504da6b519 100644 --- a/log-tree.c +++ b/log-tree.c @@ -303,26 +303,43 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio /* * The caller makes sure there is no funny color before calling. - * format_decorations_extended makes sure the same after return. + * format_decorations ensures the same after return. */ -void format_decorations_extended(struct strbuf *sb, +void format_decorations(struct strbuf *sb, const struct commit *commit, int use_color, - const char *prefix, - const char *separator, - const char *suffix) + const struct decoration_options *opts) { const struct name_decoration *decoration; const struct name_decoration *current_and_HEAD; - const char *color_commit = - diff_get_color(use_color, DIFF_COMMIT); - const char *color_reset = - decorate_get_color(use_color, DECORATION_NONE); + const char *color_commit, *color_reset; + + const char *prefix = " ("; + const char *suffix = ")"; + const char *separator = ", "; + const char *pointer = " -> "; + const char *tag = "tag: "; decoration = get_name_decoration(&commit->object); if (!decoration) return; + if (opts) { + if (opts->prefix) + prefix = opts->prefix; + if (opts->suffix) + suffix = opts->suffix; + if (opts->separator) + separator = opts->separator; + if (opts->pointer) + pointer = opts->pointer; + if (opts->tag) + tag = opts->tag; + } + + color_commit = diff_get_color(use_color, DIFF_COMMIT); + color_reset = decorate_get_color(use_color, DECORATION_NONE); + current_and_HEAD = current_pointed_by_HEAD(decoration); while (decoration) { /* @@ -331,31 +348,44 @@ void format_decorations_extended(struct strbuf *sb, * appeared, skipping the entry for current. */ if (decoration != current_and_HEAD) { - strbuf_addstr(sb, color_commit); - strbuf_addstr(sb, prefix); - strbuf_addstr(sb, color_reset); - strbuf_addstr(sb, decorate_get_color(use_color, decoration->type)); - if (decoration->type == DECORATION_REF_TAG) - strbuf_addstr(sb, "tag: "); + const char *color = + decorate_get_color(use_color, decoration->type); + + if (*prefix) { + strbuf_addstr(sb, color_commit); + strbuf_addstr(sb, prefix); + strbuf_addstr(sb, color_reset); + } + if (*tag && decoration->type == DECORATION_REF_TAG) { + strbuf_addstr(sb, color); + strbuf_addstr(sb, tag); + strbuf_addstr(sb, color_reset); + } + + strbuf_addstr(sb, color); show_name(sb, decoration); + strbuf_addstr(sb, color_reset); if (current_and_HEAD && decoration->type == DECORATION_REF_HEAD) { - strbuf_addstr(sb, " -> "); + strbuf_addstr(sb, color_commit); + strbuf_addstr(sb, pointer); strbuf_addstr(sb, color_reset); strbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type)); show_name(sb, current_and_HEAD); + strbuf_addstr(sb, color_reset); } - strbuf_addstr(sb, color_reset); prefix = separator; } decoration = decoration->next; } - strbuf_addstr(sb, color_commit); - strbuf_addstr(sb, suffix); - strbuf_addstr(sb, color_reset); + if (*suffix) { + strbuf_addstr(sb, color_commit); + strbuf_addstr(sb, suffix); + strbuf_addstr(sb, color_reset); + } } void show_decorations(struct rev_info *opt, struct commit *commit) @@ -370,7 +400,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit) } if (!opt->show_decorations) return; - format_decorations(&sb, commit, opt->diffopt.use_color); + format_decorations(&sb, commit, opt->diffopt.use_color, NULL); fputs(sb.buf, opt->diffopt.file); strbuf_release(&sb); } diff --git a/log-tree.h b/log-tree.h index bdb6432815..41c776fea5 100644 --- a/log-tree.h +++ b/log-tree.h @@ -13,17 +13,20 @@ struct decoration_filter { struct string_list *exclude_ref_config_pattern; }; +struct decoration_options { + char *prefix; + char *suffix; + char *separator; + char *pointer; + char *tag; +}; + int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); void show_log(struct rev_info *opt); -void format_decorations_extended(struct strbuf *sb, const struct commit *commit, - int use_color, - const char *prefix, - const char *separator, - const char *suffix); -#define format_decorations(strbuf, commit, color) \ - format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")") +void format_decorations(struct strbuf *sb, const struct commit *commit, + int use_color, const struct decoration_options *opts); void show_decorations(struct rev_info *opt, struct commit *commit); void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **extra_headers_p, diff --git a/mailinfo.c b/mailinfo.c index 931505363c..a07d2da16d 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -1,7 +1,7 @@ #include "git-compat-util.h" #include "config.h" #include "gettext.h" -#include "hex.h" +#include "hex-ll.h" #include "utf8.h" #include "strbuf.h" #include "mailinfo.h" diff --git a/merge-ort.c b/merge-ort.c index 8631c99700..7857ce9fbd 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -721,23 +721,6 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, renames->callback_data_nr = renames->callback_data_alloc = 0; } -__attribute__((format (printf, 2, 3))) -static int err(struct merge_options *opt, const char *err, ...) -{ - va_list params; - struct strbuf sb = STRBUF_INIT; - - strbuf_addstr(&sb, "error: "); - va_start(params, err); - strbuf_vaddf(&sb, err, params); - va_end(params); - - error("%s", sb.buf); - strbuf_release(&sb); - - return -1; -} - static void format_commit(struct strbuf *sb, int indent, struct repository *repo, @@ -2122,13 +2105,12 @@ static int handle_content_merge(struct merge_options *opt, &result_buf); if ((merge_status < 0) || !result_buf.ptr) - ret = err(opt, _("Failed to execute internal merge")); + ret = error(_("failed to execute internal merge")); if (!ret && write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &result->oid)) - ret = err(opt, _("Unable to add %s to database"), - path); + ret = error(_("unable to add %s to database"), path); free(result_buf.ptr); if (ret) @@ -3342,10 +3324,7 @@ static int collect_renames(struct merge_options *opt, return clean; } -static int detect_and_process_renames(struct merge_options *opt, - struct tree *merge_base, - struct tree *side1, - struct tree *side2) +static int detect_and_process_renames(struct merge_options *opt) { struct diff_queue_struct combined = { 0 }; struct rename_info *renames = &opt->priv->renames; @@ -3509,8 +3488,7 @@ static int sort_dirs_next_to_their_children(const char *one, const char *two) return c1 - c2; } -static int read_oid_strbuf(struct merge_options *opt, - const struct object_id *oid, +static int read_oid_strbuf(const struct object_id *oid, struct strbuf *dst) { void *buf; @@ -3518,10 +3496,10 @@ static int read_oid_strbuf(struct merge_options *opt, unsigned long size; buf = repo_read_object_file(the_repository, oid, &type, &size); if (!buf) - return err(opt, _("cannot read object %s"), oid_to_hex(oid)); + return error(_("cannot read object %s"), oid_to_hex(oid)); if (type != OBJ_BLOB) { free(buf); - return err(opt, _("object %s is not a blob"), oid_to_hex(oid)); + return error(_("object %s is not a blob"), oid_to_hex(oid)); } strbuf_attach(dst, buf, size, size + 1); return 0; @@ -3545,8 +3523,8 @@ static int blob_unchanged(struct merge_options *opt, if (oideq(&base->oid, &side->oid)) return 1; - if (read_oid_strbuf(opt, &base->oid, &basebuf) || - read_oid_strbuf(opt, &side->oid, &sidebuf)) + if (read_oid_strbuf(&base->oid, &basebuf) || + read_oid_strbuf(&side->oid, &sidebuf)) goto error_return; /* * Note: binary | is used so that both renormalizations are @@ -4902,8 +4880,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_leave("merge", "allocate/init", opt->repo); } -static void merge_check_renames_reusable(struct merge_options *opt, - struct merge_result *result, +static void merge_check_renames_reusable(struct merge_result *result, struct tree *merge_base, struct tree *side1, struct tree *side2) @@ -4973,7 +4950,7 @@ redo: * TRANSLATORS: The %s arguments are: 1) tree hash of a merge * base, and 2-3) the trees for the two trees we're merging. */ - err(opt, _("collecting merge info failed for trees %s, %s, %s"), + error(_("collecting merge info failed for trees %s, %s, %s"), oid_to_hex(&merge_base->object.oid), oid_to_hex(&side1->object.oid), oid_to_hex(&side2->object.oid)); @@ -4983,8 +4960,7 @@ redo: trace2_region_leave("merge", "collect_merge_info", opt->repo); trace2_region_enter("merge", "renames", opt->repo); - result->clean = detect_and_process_renames(opt, merge_base, - side1, side2); + result->clean = detect_and_process_renames(opt); trace2_region_leave("merge", "renames", opt->repo); if (opt->priv->renames.redo_after_renames == 2) { trace2_region_enter("merge", "reset_maps", opt->repo); @@ -5106,7 +5082,7 @@ void merge_incore_nonrecursive(struct merge_options *opt, trace2_region_enter("merge", "merge_start", opt->repo); assert(opt->ancestor != NULL); - merge_check_renames_reusable(opt, result, merge_base, side1, side2); + merge_check_renames_reusable(result, merge_base, side1, side2); merge_start(opt, result); /* * Record the trees used in this merge, so if there's a next merge in diff --git a/merge-recursive.c b/merge-recursive.c index 6a4081bb0f..0d7e57e2df 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1383,12 +1383,12 @@ static int merge_mode_and_contents(struct merge_options *opt, extra_marker_size); if ((merge_status < 0) || !result_buf.ptr) - ret = err(opt, _("Failed to execute internal merge")); + ret = err(opt, _("failed to execute internal merge")); if (!ret && write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &result->blob.oid)) - ret = err(opt, _("Unable to add %s to database"), + ret = err(opt, _("unable to add %s to database"), a->path); free(result_buf.ptr); diff --git a/negotiator/noop.c b/negotiator/noop.c index 7b72937686..de39028ab7 100644 --- a/negotiator/noop.c +++ b/negotiator/noop.c @@ -3,22 +3,24 @@ #include "../commit.h" #include "../fetch-negotiator.h" -static void known_common(struct fetch_negotiator *n, struct commit *c) +static void known_common(struct fetch_negotiator *n UNUSED, + struct commit *c UNUSED) { /* do nothing */ } -static void add_tip(struct fetch_negotiator *n, struct commit *c) +static void add_tip(struct fetch_negotiator *n UNUSED, + struct commit *c UNUSED) { /* do nothing */ } -static const struct object_id *next(struct fetch_negotiator *n) +static const struct object_id *next(struct fetch_negotiator *n UNUSED) { return NULL; } -static int ack(struct fetch_negotiator *n, struct commit *c) +static int ack(struct fetch_negotiator *n UNUSED, struct commit *c UNUSED) { /* * This negotiator does not emit any commits, so there is no commit to @@ -28,7 +30,7 @@ static int ack(struct fetch_negotiator *n, struct commit *c) return 0; } -static void release(struct fetch_negotiator *n) +static void release(struct fetch_negotiator *n UNUSED) { /* nothing to release */ } diff --git a/object-file.c b/object-file.c index 7dc0c4bfbb..7c7afe5793 100644 --- a/object-file.c +++ b/object-file.c @@ -2446,11 +2446,11 @@ static int index_core(struct index_state *istate, * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -static int index_stream(struct object_id *oid, int fd, size_t size, - enum object_type type, const char *path, - unsigned flags) +static int index_blob_stream(struct object_id *oid, int fd, size_t size, + const char *path, + unsigned flags) { - return index_bulk_checkin(oid, fd, size, type, path, flags); + return index_blob_bulk_checkin(oid, fd, size, path, flags); } int index_fd(struct index_state *istate, struct object_id *oid, @@ -2472,8 +2472,8 @@ int index_fd(struct index_state *istate, struct object_id *oid, ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); else - ret = index_stream(oid, fd, xsize_t(st->st_size), type, path, - flags); + ret = index_blob_stream(oid, fd, xsize_t(st->st_size), path, + flags); close(fd); return ret; } diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index f6757c3cbf..f4ecdf8b0e 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -413,15 +413,19 @@ static int fill_bitmap_commit(struct bb_commit *ent, if (old_bitmap && mapping) { struct ewah_bitmap *old = bitmap_for_commit(old_bitmap, c); + struct bitmap *remapped = bitmap_new(); /* * If this commit has an old bitmap, then translate that * bitmap and add its bits to this one. No need to walk * parents or the tree for this commit. */ - if (old && !rebuild_bitmap(mapping, old, ent->bitmap)) { + if (old && !rebuild_bitmap(mapping, old, remapped)) { + bitmap_or(ent->bitmap, remapped); + bitmap_free(remapped); reused_bitmaps_nr++; continue; } + bitmap_free(remapped); } /* diff --git a/pack-bitmap.c b/pack-bitmap.c index 6afc03d1e4..ca8319b87c 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -1101,8 +1101,9 @@ static void show_boundary_commit(struct commit *commit, void *_data) } } -static void show_boundary_object(struct object *object, - const char *name, void *data) +static void show_boundary_object(struct object *object UNUSED, + const char *name UNUSED, + void *data UNUSED) { BUG("should not be called"); } diff --git a/pack-objects.c b/pack-objects.c index 1b8052bece..f403ca6986 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -3,7 +3,7 @@ #include "pack.h" #include "pack-objects.h" #include "packfile.h" -#include "config.h" +#include "parse.h" static uint32_t locate_object_entry_hash(struct packing_data *pdata, const struct object_id *oid, diff --git a/pack-revindex.c b/pack-revindex.c index 7fffcad912..a01a2a4640 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -6,7 +6,7 @@ #include "packfile.h" #include "strbuf.h" #include "trace2.h" -#include "config.h" +#include "parse.h" #include "midx.h" #include "csum-file.h" diff --git a/parse-options-cb.c b/parse-options-cb.c index a24521dee0..bdc7fae497 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -227,7 +227,9 @@ int parse_opt_strvec(const struct option *opt, const char *arg, int unset) return 0; } -int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) +int parse_opt_noop_cb(const struct option *opt UNUSED, + const char *arg UNUSED, + int unset UNUSED) { return 0; } diff --git a/parse-options.c b/parse-options.c index 60224cf8d0..093eaf2db8 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1,11 +1,12 @@ #include "git-compat-util.h" #include "parse-options.h" #include "abspath.h" -#include "config.h" +#include "parse.h" #include "commit.h" #include "color.h" #include "gettext.h" #include "strbuf.h" +#include "string-list.h" #include "utf8.h" static int disallow_abbreviated_options; @@ -1023,14 +1024,37 @@ static int usage_argh(const struct option *opts, FILE *outfile) return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); } -#define USAGE_OPTS_WIDTH 24 -#define USAGE_GAP 2 +static int usage_indent(FILE *outfile) +{ + return fprintf(outfile, " "); +} + +#define USAGE_OPTS_WIDTH 26 + +static void usage_padding(FILE *outfile, size_t pos) +{ + if (pos < USAGE_OPTS_WIDTH) + fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, ""); + else + fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, ""); +} + +static const struct option *find_option_by_long_name(const struct option *opts, + const char *long_name) +{ + for (; opts->type != OPTION_END; opts++) { + if (opts->long_name && !strcmp(opts->long_name, long_name)) + return opts; + } + return NULL; +} static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, const char * const *usagestr, const struct option *opts, int full, int err) { + const struct option *all_opts = opts; FILE *outfile = err ? stderr : stdout; int need_newline; @@ -1111,8 +1135,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (; opts->type != OPTION_END; opts++) { size_t pos; - int pad; const char *cp, *np; + const char *positive_name = NULL; if (opts->type == OPTION_SUBCOMMAND) continue; @@ -1131,7 +1155,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t need_newline = 0; } - pos = fprintf(outfile, " "); + pos = usage_indent(outfile); if (opts->short_name) { if (opts->flags & PARSE_OPT_NODASH) pos += fprintf(outfile, "%c", opts->short_name); @@ -1140,8 +1164,15 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t } if (opts->long_name && opts->short_name) pos += fprintf(outfile, ", "); - if (opts->long_name) - pos += fprintf(outfile, "--%s", opts->long_name); + if (opts->long_name) { + const char *long_name = opts->long_name; + if ((opts->flags & PARSE_OPT_NONEG) || + skip_prefix(long_name, "no-", &positive_name)) + pos += fprintf(outfile, "--%s", long_name); + else + pos += fprintf(outfile, "--[no-]%s", long_name); + } + if (opts->type == OPTION_NUMBER) pos += utf8_fprintf(outfile, _("-NUM")); @@ -1149,29 +1180,31 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t !(opts->flags & PARSE_OPT_NOARG)) pos += usage_argh(opts, outfile); - if (pos == USAGE_OPTS_WIDTH + 1) - pad = -1; - else if (pos <= USAGE_OPTS_WIDTH) - pad = USAGE_OPTS_WIDTH - pos; - else { - fputc('\n', outfile); - pad = USAGE_OPTS_WIDTH; - } if (opts->type == OPTION_ALIAS) { - fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + usage_padding(outfile, pos); fprintf_ln(outfile, _("alias of --%s"), (const char *)opts->value); continue; } - for (cp = _(opts->help); *cp; cp = np) { + for (cp = opts->help ? _(opts->help) : ""; *cp; cp = np) { np = strchrnul(cp, '\n'); - fprintf(outfile, - "%*s%.*s\n", pad + USAGE_GAP, "", - (int)(np - cp), cp); if (*np) np++; - pad = USAGE_OPTS_WIDTH; + usage_padding(outfile, pos); + fwrite(cp, 1, np - cp, outfile); + pos = 0; + } + fputc('\n', outfile); + + if (positive_name) { + if (find_option_by_long_name(all_opts, positive_name)) + continue; + pos = usage_indent(outfile); + pos += fprintf(outfile, "--%s", positive_name); + usage_padding(outfile, pos); + fprintf_ln(outfile, _("opposite of --no-%s"), + positive_name); } } fputc('\n', outfile); diff --git a/parse-options.h b/parse-options.h index 57a7fe9d91..4a66ec3bf5 100644 --- a/parse-options.h +++ b/parse-options.h @@ -459,7 +459,6 @@ struct parse_opt_ctx_t { unsigned has_subcommands; const char *prefix; const char **alias_groups; /* must be in groups of 3 elements! */ - struct option *updated_options; }; void parse_options_start(struct parse_opt_ctx_t *ctx, diff --git a/parse.c b/parse.c new file mode 100644 index 0000000000..42d691a0fb --- /dev/null +++ b/parse.c @@ -0,0 +1,182 @@ +#include "git-compat-util.h" +#include "gettext.h" +#include "parse.h" + +static uintmax_t get_unit_factor(const char *end) +{ + if (!*end) + return 1; + else if (!strcasecmp(end, "k")) + return 1024; + else if (!strcasecmp(end, "m")) + return 1024 * 1024; + else if (!strcasecmp(end, "g")) + return 1024 * 1024 * 1024; + return 0; +} + +int git_parse_signed(const char *value, intmax_t *ret, intmax_t max) +{ + if (value && *value) { + char *end; + intmax_t val; + intmax_t factor; + + if (max < 0) + BUG("max must be a positive integer"); + + errno = 0; + val = strtoimax(value, &end, 0); + if (errno == ERANGE) + return 0; + if (end == value) { + errno = EINVAL; + return 0; + } + factor = get_unit_factor(end); + if (!factor) { + errno = EINVAL; + return 0; + } + if ((val < 0 && -max / factor > val) || + (val > 0 && max / factor < val)) { + errno = ERANGE; + return 0; + } + val *= factor; + *ret = val; + return 1; + } + errno = EINVAL; + return 0; +} + +static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max) +{ + if (value && *value) { + char *end; + uintmax_t val; + uintmax_t factor; + + /* negative values would be accepted by strtoumax */ + if (strchr(value, '-')) { + errno = EINVAL; + return 0; + } + errno = 0; + val = strtoumax(value, &end, 0); + if (errno == ERANGE) + return 0; + if (end == value) { + errno = EINVAL; + return 0; + } + factor = get_unit_factor(end); + if (!factor) { + errno = EINVAL; + return 0; + } + if (unsigned_mult_overflows(factor, val) || + factor * val > max) { + errno = ERANGE; + return 0; + } + val *= factor; + *ret = val; + return 1; + } + errno = EINVAL; + return 0; +} + +int git_parse_int(const char *value, int *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int))) + return 0; + *ret = tmp; + return 1; +} + +int git_parse_int64(const char *value, int64_t *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t))) + return 0; + *ret = tmp; + return 1; +} + +int git_parse_ulong(const char *value, unsigned long *ret) +{ + uintmax_t tmp; + if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long))) + return 0; + *ret = tmp; + return 1; +} + +int git_parse_ssize_t(const char *value, ssize_t *ret) +{ + intmax_t tmp; + if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t))) + return 0; + *ret = tmp; + return 1; +} + +int git_parse_maybe_bool_text(const char *value) +{ + if (!value) + return 1; + if (!*value) + return 0; + if (!strcasecmp(value, "true") + || !strcasecmp(value, "yes") + || !strcasecmp(value, "on")) + return 1; + if (!strcasecmp(value, "false") + || !strcasecmp(value, "no") + || !strcasecmp(value, "off")) + return 0; + return -1; +} + +int git_parse_maybe_bool(const char *value) +{ + int v = git_parse_maybe_bool_text(value); + if (0 <= v) + return v; + if (git_parse_int(value, &v)) + return !!v; + return -1; +} + +/* + * Parse environment variable 'k' as a boolean (in various + * possible spellings); if missing, use the default value 'def'. + */ +int git_env_bool(const char *k, int def) +{ + const char *v = getenv(k); + int val; + if (!v) + return def; + val = git_parse_maybe_bool(v); + if (val < 0) + die(_("bad boolean environment value '%s' for '%s'"), + v, k); + return val; +} + +/* + * Parse environment variable 'k' as ulong with possibly a unit + * suffix; if missing, use the default value 'val'. + */ +unsigned long git_env_ulong(const char *k, unsigned long val) +{ + const char *v = getenv(k); + if (v && !git_parse_ulong(v, &val)) + die(_("failed to parse %s"), k); + return val; +} diff --git a/parse.h b/parse.h new file mode 100644 index 0000000000..07d2193d69 --- /dev/null +++ b/parse.h @@ -0,0 +1,20 @@ +#ifndef PARSE_H +#define PARSE_H + +int git_parse_signed(const char *value, intmax_t *ret, intmax_t max); +int git_parse_ssize_t(const char *, ssize_t *); +int git_parse_ulong(const char *, unsigned long *); +int git_parse_int(const char *value, int *ret); +int git_parse_int64(const char *value, int64_t *ret); + +/** + * Same as `git_config_bool`, except that it returns -1 on error rather + * than dying. + */ +int git_parse_maybe_bool(const char *); +int git_parse_maybe_bool_text(const char *value); + +int git_env_bool(const char *, int); +unsigned long git_env_ulong(const char *, unsigned long); + +#endif /* PARSE_H */ diff --git a/pathspec.c b/pathspec.c index 3a3a5724c4..7f88f1c02b 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,6 +1,6 @@ #include "git-compat-util.h" #include "abspath.h" -#include "config.h" +#include "parse.h" #include "dir.h" #include "environment.h" #include "gettext.h" diff --git a/preload-index.c b/preload-index.c index e44530c80c..63fd35d64b 100644 --- a/preload-index.c +++ b/preload-index.c @@ -7,7 +7,7 @@ #include "environment.h" #include "fsmonitor.h" #include "gettext.h" -#include "config.h" +#include "parse.h" #include "preload-index.h" #include "progress.h" #include "read-cache.h" @@ -1252,8 +1252,8 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud) return 0; } -static struct strbuf *expand_separator(struct strbuf *sb, - const char *argval, size_t arglen) +static struct strbuf *expand_string_arg(struct strbuf *sb, + const char *argval, size_t arglen) { char *fmt = xstrndup(argval, arglen); const char *format = fmt; @@ -1301,9 +1301,9 @@ int format_set_trailers_options(struct process_trailer_options *opts, opts->filter_data = filter_list; opts->only_trailers = 1; } else if (match_placeholder_arg_value(*arg, "separator", arg, &argval, &arglen)) { - opts->separator = expand_separator(sepbuf, argval, arglen); + opts->separator = expand_string_arg(sepbuf, argval, arglen); } else if (match_placeholder_arg_value(*arg, "key_value_separator", arg, &argval, &arglen)) { - opts->key_value_separator = expand_separator(kvsepbuf, argval, arglen); + opts->key_value_separator = expand_string_arg(kvsepbuf, argval, arglen); } else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) && !match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) && !match_placeholder_bool_arg(*arg, "keyonly", arg, &opts->key_only) && @@ -1384,6 +1384,44 @@ static size_t parse_describe_args(const char *start, struct strvec *args) return arg - start; } + +static int parse_decoration_option(const char **arg, + const char *name, + char **opt) +{ + const char *argval; + size_t arglen; + + if (match_placeholder_arg_value(*arg, name, arg, &argval, &arglen)) { + struct strbuf sb = STRBUF_INIT; + + expand_string_arg(&sb, argval, arglen); + *opt = strbuf_detach(&sb, NULL); + return 1; + } + return 0; +} + +static void parse_decoration_options(const char **arg, + struct decoration_options *opts) +{ + while (parse_decoration_option(arg, "prefix", &opts->prefix) || + parse_decoration_option(arg, "suffix", &opts->suffix) || + parse_decoration_option(arg, "separator", &opts->separator) || + parse_decoration_option(arg, "pointer", &opts->pointer) || + parse_decoration_option(arg, "tag", &opts->tag)) + ; +} + +static void free_decoration_options(const struct decoration_options *opts) +{ + free(opts->prefix); + free(opts->suffix); + free(opts->separator); + free(opts->pointer); + free(opts->tag); +} + static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const char *placeholder, void *context) @@ -1537,11 +1575,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': - format_decorations(sb, commit, c->auto_color); + format_decorations(sb, commit, c->auto_color, NULL); return 1; case 'D': - format_decorations_extended(sb, commit, c->auto_color, "", ", ", ""); - return 1; + { + const struct decoration_options opts = { + .prefix = "", + .suffix = "" + }; + + format_decorations(sb, commit, c->auto_color, &opts); + return 1; + } case 'S': /* tag/branch like --source */ if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources)) return 0; @@ -1638,6 +1683,23 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ return 2; } + if (skip_prefix(placeholder, "(decorate", &arg)) { + struct decoration_options opts = { NULL }; + size_t ret = 0; + + if (*arg == ':') { + arg++; + parse_decoration_options(&arg, &opts); + } + if (*arg == ')') { + format_decorations(sb, commit, c->auto_color, &opts); + ret = arg - placeholder + 1; + } + + free_decoration_options(&opts); + return ret; + } + /* For the rest we have to parse the commit header. */ if (!c->commit_header_parsed) { msg = c->message = diff --git a/progress.c b/progress.c index f695798aca..c83cb60bf1 100644 --- a/progress.c +++ b/progress.c @@ -17,7 +17,7 @@ #include "trace.h" #include "trace2.h" #include "utf8.h" -#include "config.h" +#include "parse.h" #define TP_IDX_MAX 8 @@ -1,5 +1,5 @@ #include "git-compat-util.h" -#include "config.h" +#include "parse.h" #include "environment.h" #include "run-command.h" #include "strbuf.h" diff --git a/range-diff.c b/range-diff.c index 2e86063491..c45b6d849c 100644 --- a/range-diff.c +++ b/range-diff.c @@ -60,7 +60,7 @@ static int read_patches(const char *range, struct string_list *list, "--output-indicator-context=#", "--no-abbrev-commit", "--pretty=medium", - "--notes", + "--show-notes-by-default", NULL); strvec_push(&cp.args, range); if (other_arg) @@ -230,16 +230,19 @@ cleanup: } static int patch_util_cmp(const void *cmp_data UNUSED, - const struct patch_util *a, - const struct patch_util *b, - const char *keydata) + const struct hashmap_entry *ha, + const struct hashmap_entry *hb, + const void *keydata) { + const struct patch_util + *a = container_of(ha, const struct patch_util, e), + *b = container_of(hb, const struct patch_util, e); return strcmp(a->diff, keydata ? keydata : b->diff); } static void find_exact_matches(struct string_list *a, struct string_list *b) { - struct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL); + struct hashmap map = HASHMAP_INIT(patch_util_cmp, NULL); int i; /* First, add the patches of a to a hash map */ @@ -1,6 +1,6 @@ #include "git-compat-util.h" #include "rebase.h" -#include "config.h" +#include "parse.h" #include "gettext.h" /* diff --git a/ref-filter.c b/ref-filter.c index 1bfaf20fbf..e4d3510e28 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -13,6 +13,8 @@ #include "oid-array.h" #include "repository.h" #include "commit.h" +#include "mailmap.h" +#include "ident.h" #include "remote.h" #include "color.h" #include "tag.h" @@ -215,8 +217,16 @@ static struct used_atom { struct { enum { O_SIZE, O_SIZE_DISK } option; } objectsize; - struct email_option { - enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; + struct { + enum { N_RAW, N_MAILMAP } option; + } name_option; + struct { + enum { + EO_RAW = 0, + EO_TRIM = 1<<0, + EO_LOCALPART = 1<<1, + EO_MAILMAP = 1<<2, + } option; } email_option; struct { enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, @@ -549,7 +559,8 @@ static int signature_atom_parser(struct ref_format *format UNUSED, return 0; } -static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom, +static int trailers_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, const char *arg, struct strbuf *err) { atom->u.contents.trailer_opts.no_divider = 1; @@ -582,9 +593,10 @@ static int contents_atom_parser(struct ref_format *format, struct used_atom *ato atom->u.contents.option = C_BARE; else if (!strcmp(arg, "body")) atom->u.contents.option = C_BODY; - else if (!strcmp(arg, "size")) + else if (!strcmp(arg, "size")) { + atom->type = FIELD_ULONG; atom->u.contents.option = C_LENGTH; - else if (!strcmp(arg, "signature")) + } else if (!strcmp(arg, "signature")) atom->u.contents.option = C_SIG; else if (!strcmp(arg, "subject")) atom->u.contents.option = C_SUB; @@ -690,9 +702,10 @@ static int raw_atom_parser(struct ref_format *format UNUSED, { if (!arg) atom->u.raw_data.option = RAW_BARE; - else if (!strcmp(arg, "size")) + else if (!strcmp(arg, "size")) { + atom->type = FIELD_ULONG; atom->u.raw_data.option = RAW_LENGTH; - else + } else return err_bad_arg(err, "raw", arg); return 0; } @@ -717,21 +730,55 @@ static int oid_atom_parser(struct ref_format *format UNUSED, return 0; } -static int person_email_atom_parser(struct ref_format *format UNUSED, - struct used_atom *atom, - const char *arg, struct strbuf *err) +static int person_name_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) { if (!arg) - atom->u.email_option.option = EO_RAW; - else if (!strcmp(arg, "trim")) - atom->u.email_option.option = EO_TRIM; - else if (!strcmp(arg, "localpart")) - atom->u.email_option.option = EO_LOCALPART; + atom->u.name_option.option = N_RAW; + else if (!strcmp(arg, "mailmap")) + atom->u.name_option.option = N_MAILMAP; else return err_bad_arg(err, atom->name, arg); return 0; } +static int email_atom_option_parser(struct used_atom *atom, + const char **arg, struct strbuf *err) +{ + if (!*arg) + return EO_RAW; + if (skip_prefix(*arg, "trim", arg)) + return EO_TRIM; + if (skip_prefix(*arg, "localpart", arg)) + return EO_LOCALPART; + if (skip_prefix(*arg, "mailmap", arg)) + return EO_MAILMAP; + return -1; +} + +static int person_email_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) +{ + for (;;) { + int opt = email_atom_option_parser(atom, &arg, err); + const char *bad_arg = arg; + + if (opt < 0) + return err_bad_arg(err, atom->name, bad_arg); + atom->u.email_option.option |= opt; + + if (!arg || !*arg) + break; + if (*arg == ',') + arg++; + else + return err_bad_arg(err, atom->name, bad_arg); + } + return 0; +} + static int refname_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, const char *arg, struct strbuf *err) @@ -819,7 +866,7 @@ static int if_atom_parser(struct ref_format *format UNUSED, return 0; } -static int rest_atom_parser(struct ref_format *format, +static int rest_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom UNUSED, const char *arg, struct strbuf *err) { @@ -828,7 +875,8 @@ static int rest_atom_parser(struct ref_format *format, return 0; } -static int ahead_behind_atom_parser(struct ref_format *format, struct used_atom *atom, +static int ahead_behind_atom_parser(struct ref_format *format, + struct used_atom *atom UNUSED, const char *arg, struct strbuf *err) { struct string_list_item *item; @@ -873,15 +921,15 @@ static struct { [ATOM_TYPE] = { "type", SOURCE_OBJ }, [ATOM_TAG] = { "tag", SOURCE_OBJ }, [ATOM_AUTHOR] = { "author", SOURCE_OBJ }, - [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ }, + [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME }, [ATOM_COMMITTER] = { "committer", SOURCE_OBJ }, - [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ }, + [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_TAGGER] = { "tagger", SOURCE_OBJ }, - [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ }, + [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_CREATOR] = { "creator", SOURCE_OBJ }, @@ -1482,32 +1530,49 @@ static const char *copy_name(const char *buf) return xstrdup(""); } +static const char *find_end_of_email(const char *email, int opt) +{ + const char *eoemail; + + if (opt & EO_LOCALPART) { + eoemail = strchr(email, '@'); + if (eoemail) + return eoemail; + return strchr(email, '>'); + } + + if (opt & EO_TRIM) + return strchr(email, '>'); + + /* + * The option here is either the raw email option or the raw + * mailmap option (that is EO_RAW or EO_MAILMAP). In such cases, + * we directly grab the whole email including the closing + * angle brackets. + * + * If EO_MAILMAP was set with any other option (that is either + * EO_TRIM or EO_LOCALPART), we already grab the end of email + * above. + */ + eoemail = strchr(email, '>'); + if (eoemail) + eoemail++; + return eoemail; +} + static const char *copy_email(const char *buf, struct used_atom *atom) { const char *email = strchr(buf, '<'); const char *eoemail; + int opt = atom->u.email_option.option; + if (!email) return xstrdup(""); - switch (atom->u.email_option.option) { - case EO_RAW: - eoemail = strchr(email, '>'); - if (eoemail) - eoemail++; - break; - case EO_TRIM: - email++; - eoemail = strchr(email, '>'); - break; - case EO_LOCALPART: + + if (opt & (EO_LOCALPART | EO_TRIM)) email++; - eoemail = strchr(email, '@'); - if (!eoemail) - eoemail = strchr(email, '>'); - break; - default: - BUG("unknown email option"); - } + eoemail = find_end_of_email(email, opt); if (!eoemail) return xstrdup(""); return xmemdupz(email, eoemail - email); @@ -1568,16 +1633,23 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam v->value = 0; } +static struct string_list mailmap = STRING_LIST_INIT_NODUP; + /* See grab_values */ static void grab_person(const char *who, struct atom_value *val, int deref, void *buf) { int i; int wholen = strlen(who); const char *wholine = NULL; + const char *headers[] = { "author ", "committer ", + "tagger ", NULL }; for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i].name; + struct used_atom *atom = &used_atom[i]; + const char *name = atom->name; struct atom_value *v = &val[i]; + struct strbuf mailmap_buf = STRBUF_INIT; + if (!!deref != (*name == '*')) continue; if (deref) @@ -1585,22 +1657,36 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void if (strncmp(who, name, wholen)) continue; if (name[wholen] != 0 && - strcmp(name + wholen, "name") && + !starts_with(name + wholen, "name") && !starts_with(name + wholen, "email") && !starts_with(name + wholen, "date")) continue; - if (!wholine) + + if ((starts_with(name + wholen, "name") && + (atom->u.name_option.option == N_MAILMAP)) || + (starts_with(name + wholen, "email") && + (atom->u.email_option.option & EO_MAILMAP))) { + if (!mailmap.items) + read_mailmap(&mailmap); + strbuf_addstr(&mailmap_buf, buf); + apply_mailmap_to_header(&mailmap_buf, headers, &mailmap); + wholine = find_wholine(who, wholen, mailmap_buf.buf); + } else { wholine = find_wholine(who, wholen, buf); + } + if (!wholine) return; /* no point looking for it */ if (name[wholen] == 0) v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) + else if (starts_with(name + wholen, "name")) v->s = copy_name(wholine); else if (starts_with(name + wholen, "email")) v->s = copy_email(wholine, &used_atom[i]); else if (starts_with(name + wholen, "date")) grab_date(wholine, v, name); + + strbuf_release(&mailmap_buf); } /* @@ -1857,7 +1943,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp v->s = xmemdupz(buf, buf_size); v->s_size = buf_size; } else if (atom->u.raw_data.option == RAW_LENGTH) { - v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size); + v->value = buf_size; + v->s = xstrfmt("%"PRIuMAX, v->value); } continue; } @@ -1883,9 +1970,10 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct exp v->s = strbuf_detach(&sb, NULL); } else if (atom->u.contents.option == C_BODY_DEP) v->s = xmemdupz(bodypos, bodylen); - else if (atom->u.contents.option == C_LENGTH) - v->s = xstrfmt("%"PRIuMAX, (uintmax_t)strlen(subpos)); - else if (atom->u.contents.option == C_BODY) + else if (atom->u.contents.option == C_LENGTH) { + v->value = strlen(subpos); + v->s = xstrfmt("%"PRIuMAX, v->value); + } else if (atom->u.contents.option == C_BODY) v->s = xmemdupz(bodypos, nonsiglen); else if (atom->u.contents.option == C_SIG) v->s = xmemdupz(sigpos, siglen); @@ -2265,6 +2353,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err) v->s_size = ATOM_SIZE_UNSPECIFIED; v->handler = append_atom; + v->value = 0; v->atom = atom; if (*name == '*') { @@ -1112,7 +1112,7 @@ int rerere_forget(struct repository *r, struct pathspec *pathspec) * recover the original conflicted state and then * find the conflicted paths. */ - unmerge_index(r->index, pathspec); + unmerge_index(r->index, pathspec, 0); find_conflict(r, &conflict); for (i = 0; i < conflict.nr; i++) { struct string_list_item *it = &conflict.items[i]; diff --git a/resolve-undo.c b/resolve-undo.c index 7817f5d6db..cd02dc9928 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -117,86 +117,59 @@ void resolve_undo_clear_index(struct index_state *istate) istate->cache_changed |= RESOLVE_UNDO_CHANGED; } -int unmerge_index_entry_at(struct index_state *istate, int pos) +int unmerge_index_entry(struct index_state *istate, const char *path, + struct resolve_undo_info *ru, unsigned ce_flags) { - const struct cache_entry *ce; - struct string_list_item *item; - struct resolve_undo_info *ru; - int i, err = 0, matched; - char *name; - - if (!istate->resolve_undo) - return pos; - - ce = istate->cache[pos]; - if (ce_stage(ce)) { - /* already unmerged */ - while ((pos < istate->cache_nr) && - ! strcmp(istate->cache[pos]->name, ce->name)) - pos++; - return pos - 1; /* return the last entry processed */ + int i = index_name_pos(istate, path, strlen(path)); + + if (i < 0) { + /* unmerged? */ + i = -i - 1; + if (i < istate->cache_nr && + !strcmp(istate->cache[i]->name, path)) + /* yes, it is already unmerged */ + return 0; + /* fallthru: resolved to removal */ + } else { + /* merged - remove it to replace it with unmerged entries */ + remove_index_entry_at(istate, i); } - item = string_list_lookup(istate->resolve_undo, ce->name); - if (!item) - return pos; - ru = item->util; - if (!ru) - return pos; - matched = ce->ce_flags & CE_MATCHED; - name = xstrdup(ce->name); - remove_index_entry_at(istate, pos); + for (i = 0; i < 3; i++) { - struct cache_entry *nce; + struct cache_entry *ce; if (!ru->mode[i]) continue; - nce = make_cache_entry(istate, - ru->mode[i], - &ru->oid[i], - name, i + 1, 0); - if (matched) - nce->ce_flags |= CE_MATCHED; - if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { - err = 1; - error("cannot unmerge '%s'", name); - } + ce = make_cache_entry(istate, ru->mode[i], &ru->oid[i], + path, i + 1, 0); + ce->ce_flags |= ce_flags; + if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD)) + return error("cannot unmerge '%s'", path); } - free(name); - if (err) - return pos; - free(ru); - item->util = NULL; - return unmerge_index_entry_at(istate, pos); + return 0; } -void unmerge_marked_index(struct index_state *istate) +void unmerge_index(struct index_state *istate, const struct pathspec *pathspec, + unsigned ce_flags) { - int i; + struct string_list_item *item; if (!istate->resolve_undo) return; /* TODO: audit for interaction with sparse-index. */ ensure_full_index(istate); - for (i = 0; i < istate->cache_nr; i++) { - const struct cache_entry *ce = istate->cache[i]; - if (ce->ce_flags & CE_MATCHED) - i = unmerge_index_entry_at(istate, i); - } -} -void unmerge_index(struct index_state *istate, const struct pathspec *pathspec) -{ - int i; - - if (!istate->resolve_undo) - return; - - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(istate); - for (i = 0; i < istate->cache_nr; i++) { - const struct cache_entry *ce = istate->cache[i]; - if (!ce_path_match(istate, ce, pathspec, NULL)) + for_each_string_list_item(item, istate->resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ru = item->util; + if (!item->util) + continue; + if (!match_pathspec(istate, pathspec, + item->string, strlen(item->string), + 0, NULL, 0)) continue; - i = unmerge_index_entry_at(istate, i); + unmerge_index_entry(istate, path, ru, ce_flags); + free(ru); + item->util = NULL; } } diff --git a/resolve-undo.h b/resolve-undo.h index c5deafc92f..f3f8462751 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -17,8 +17,7 @@ void record_resolve_undo(struct index_state *, struct cache_entry *); void resolve_undo_write(struct strbuf *, struct string_list *); struct string_list *resolve_undo_read(const char *, unsigned long); void resolve_undo_clear_index(struct index_state *); -int unmerge_index_entry_at(struct index_state *, int); -void unmerge_index(struct index_state *, const struct pathspec *); -void unmerge_marked_index(struct index_state *); +int unmerge_index_entry(struct index_state *, const char *, struct resolve_undo_info *, unsigned); +void unmerge_index(struct index_state *, const struct pathspec *, unsigned); #endif diff --git a/revision.c b/revision.c index 2f4c53ea20..219dc76716 100644 --- a/revision.c +++ b/revision.c @@ -2484,6 +2484,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->break_bar = xstrdup(optarg); revs->track_linear = 1; revs->track_first_time = 1; + } else if (!strcmp(arg, "--show-notes-by-default")) { + revs->show_notes_by_default = 1; } else if (skip_prefix(arg, "--show-notes=", &optarg) || skip_prefix(arg, "--notes=", &optarg)) { if (starts_with(arg, "--show-notes=") && @@ -2788,13 +2790,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, } static void read_revisions_from_stdin(struct rev_info *revs, - struct strvec *prune, - int *flags) + struct strvec *prune) { struct strbuf sb; int seen_dashdash = 0; int seen_end_of_options = 0; int save_warning; + int flags = 0; save_warning = warn_on_object_refname_ambiguity; warn_on_object_refname_ambiguity = 0; @@ -2817,13 +2819,13 @@ static void read_revisions_from_stdin(struct rev_info *revs, continue; } - if (handle_revision_pseudo_opt(revs, argv, flags) > 0) + if (handle_revision_pseudo_opt(revs, argv, &flags) > 0) continue; die(_("invalid option '%s' in --stdin mode"), sb.buf); } - if (handle_revision_arg(sb.buf, revs, 0, + if (handle_revision_arg(sb.buf, revs, flags, REVARG_CANNOT_BE_FILENAME)) die("bad revision '%s'", sb.buf); } @@ -2906,7 +2908,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s } if (revs->read_from_stdin++) die("--stdin given twice?"); - read_revisions_from_stdin(revs, &prune_data, &flags); + read_revisions_from_stdin(revs, &prune_data); continue; } @@ -3054,6 +3056,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->expand_tabs_in_log < 0) revs->expand_tabs_in_log = revs->expand_tabs_in_log_default; + if (!revs->show_notes_given && revs->show_notes_by_default) { + enable_default_display_notes(&revs->notes_opt, &revs->show_notes); + revs->show_notes_given = 1; + } + return left; } @@ -3076,6 +3083,11 @@ static void release_revisions_mailmap(struct string_list *mailmap) static void release_revisions_topo_walk_info(struct topo_walk_info *info); +static void free_void_commit_list(void *list) +{ + free_commit_list(list); +} + void release_revisions(struct rev_info *revs) { free_commit_list(revs->commits); @@ -3093,6 +3105,10 @@ void release_revisions(struct rev_info *revs) diff_free(&revs->pruning); reflog_walk_info_release(revs->reflog_info); release_revisions_topo_walk_info(revs->topo_walk_info); + clear_decoration(&revs->children, free_void_commit_list); + clear_decoration(&revs->merge_simplification, free); + clear_decoration(&revs->treesame, free); + line_log_free(revs); } static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child) diff --git a/revision.h b/revision.h index 82ab400139..50091bbd13 100644 --- a/revision.h +++ b/revision.h @@ -253,6 +253,7 @@ struct rev_info { shown_dashes:1, show_merge:1, show_notes_given:1, + show_notes_by_default:1, show_signature:1, pretty_given:1, abbrev_commit:1, @@ -409,6 +409,7 @@ static int cmd_clone(int argc, const char **argv) { const char *branch = NULL; int full_clone = 0, single_branch = 0, show_progress = isatty(2); + int src = 1; struct option clone_options[] = { OPT_STRING('b', "branch", &branch, N_("<branch>"), N_("branch to checkout after clone")), @@ -417,10 +418,13 @@ static int cmd_clone(int argc, const char **argv) OPT_BOOL(0, "single-branch", &single_branch, N_("only download metadata for the branch that will " "be checked out")), + OPT_BOOL(0, "src", &src, + N_("create repository within 'src' directory")), OPT_END(), }; const char * const clone_usage[] = { - N_("scalar clone [<options>] [--] <repo> [<dir>]"), + N_("scalar clone [--single-branch] [--branch <main-branch>] [--full-clone]\n" + "\t[--[no-]src] <url> [<enlistment>]"), NULL }; const char *url; @@ -456,7 +460,10 @@ static int cmd_clone(int argc, const char **argv) if (is_directory(enlistment)) die(_("directory '%s' exists already"), enlistment); - dir = xstrfmt("%s/src", enlistment); + if (src) + dir = xstrfmt("%s/src", enlistment); + else + dir = xstrdup(enlistment); strbuf_reset(&buf); if (branch) @@ -657,6 +664,7 @@ static int cmd_reconfigure(int argc, const char **argv) git_config(get_scalar_repos, &scalar_repos); for (i = 0; i < scalar_repos.nr; i++) { + int succeeded = 0; const char *dir = scalar_repos.items[i].string; strbuf_reset(&commondir); @@ -667,30 +675,56 @@ static int cmd_reconfigure(int argc, const char **argv) if (errno != ENOENT) { warning_errno(_("could not switch to '%s'"), dir); - res = -1; - continue; + goto loop_end; } strbuf_addstr(&buf, dir); if (remove_deleted_enlistment(&buf)) - res = error(_("could not remove stale " - "scalar.repo '%s'"), dir); - else - warning(_("removing stale scalar.repo '%s'"), + error(_("could not remove stale " + "scalar.repo '%s'"), dir); + else { + warning(_("removed stale scalar.repo '%s'"), dir); + succeeded = 1; + } strbuf_release(&buf); - } else if (discover_git_directory(&commondir, &gitdir) < 0) { - warning_errno(_("git repository gone in '%s'"), dir); - res = -1; - } else { - git_config_clear(); + goto loop_end; + } + + switch (discover_git_directory_reason(&commondir, &gitdir)) { + case GIT_DIR_INVALID_OWNERSHIP: + warning(_("repository at '%s' has different owner"), dir); + goto loop_end; + + case GIT_DIR_INVALID_GITFILE: + case GIT_DIR_INVALID_FORMAT: + warning(_("repository at '%s' has a format issue"), dir); + goto loop_end; + + case GIT_DIR_DISCOVERED: + succeeded = 1; + break; + + default: + warning(_("repository not found in '%s'"), dir); + break; + } - the_repository = &r; - r.commondir = commondir.buf; - r.gitdir = gitdir.buf; + git_config_clear(); - if (set_recommended_config(1) < 0) - res = -1; + the_repository = &r; + r.commondir = commondir.buf; + r.gitdir = gitdir.buf; + + if (set_recommended_config(1) >= 0) + succeeded = 1; + +loop_end: + if (!succeeded) { + res = -1; + warning(_("to unregister this repository from Scalar, run\n" + "\tgit config --global --unset --fixed-value scalar.repo \"%s\""), + dir); } } diff --git a/sequencer.c b/sequencer.c index 5e0c15a16b..d584cac8ed 100644 --- a/sequencer.c +++ b/sequencer.c @@ -51,6 +51,15 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +/* + * To accommodate common filesystem limitations, where the loose refs' file + * names must not exceed `NAME_MAX`, the labels generated by `git rebase + * --rebase-merges` need to be truncated if the corresponding commit subjects + * are too long. + * Add some margin to stay clear from reaching `NAME_MAX`. + */ +#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN) - 16) + static const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; @@ -139,6 +148,11 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* + * When we stop for the user to resolve conflicts this file contains + * the patch of the commit that is being picked. + */ +static GIT_PATH_FUNC(rebase_path_patch, "rebase-merge/patch") +/* * For the post-rewrite hook, we make a list of rewritten commits and * their new sha1s. The rewritten-pending list keeps the sha1s of * commits that have been processed, but not committed yet, @@ -424,10 +438,9 @@ struct commit_message { const char *message; }; -static const char *short_commit_name(struct commit *commit) +static const char *short_commit_name(struct repository *r, struct commit *commit) { - return repo_find_unique_abbrev(the_repository, &commit->object.oid, - DEFAULT_ABBREV); + return repo_find_unique_abbrev(r, &commit->object.oid, DEFAULT_ABBREV); } static int get_message(struct commit *commit, struct commit_message *out) @@ -437,7 +450,7 @@ static int get_message(struct commit *commit, struct commit_message *out) out->message = repo_logmsg_reencode(the_repository, commit, NULL, get_commit_output_encoding()); - abbrev = short_commit_name(commit); + abbrev = short_commit_name(the_repository, commit); subject_len = find_commit_subject(out->message, &subject); @@ -2249,6 +2262,8 @@ static int do_pick_commit(struct repository *r, */ if (command == TODO_REVERT) { + const char *orig_subject; + base = commit; base_label = msg.label; next = parent; @@ -2256,6 +2271,15 @@ static int do_pick_commit(struct repository *r, if (opts->commit_use_reference) { strbuf_addstr(&msgbuf, "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***"); + } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) && + /* + * We don't touch pre-existing repeated reverts, because + * theoretically these can be nested arbitrarily deeply, + * thus requiring excessive complexity to deal with. + */ + !starts_with(orig_subject, "Revert \"")) { + strbuf_addstr(&msgbuf, "Reapply \""); + strbuf_addstr(&msgbuf, orig_subject); } else { strbuf_addstr(&msgbuf, "Revert \""); strbuf_addstr(&msgbuf, msg.subject); @@ -2311,7 +2335,7 @@ static int do_pick_commit(struct repository *r, const char *dest = git_path_squash_msg(r); unlink(dest); if (copy_file(dest, rebase_path_squash_msg(), 0666)) { - res = error(_("could not rename '%s' to '%s'"), + res = error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), dest); goto leave; } @@ -2374,7 +2398,7 @@ static int do_pick_commit(struct repository *r, error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), - short_commit_name(commit), msg.subject); + short_commit_name(r, commit), msg.subject); print_advice(r, res == 1, opts); repo_rerere(r, opts->allow_rerere_auto); goto leave; @@ -2641,7 +2665,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return item->commit ? 0 : -1; } -int sequencer_get_last_command(struct repository *r, enum replay_action *action) +int sequencer_get_last_command(struct repository *r UNUSED, enum replay_action *action) { const char *todo_file, *bol; struct strbuf buf = STRBUF_INIT; @@ -3163,7 +3187,8 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, - short_commit_name(commit), subject_len, subject); + short_commit_name(the_repository, commit), + subject_len, subject); repo_unuse_commit_buffer(the_repository, commit, commit_buffer); } @@ -3392,7 +3417,8 @@ give_advice: return -1; } -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts, + int reschedule) { struct lock_file todo_lock = LOCK_INIT; const char *todo_path = get_todo_path(opts); @@ -3402,7 +3428,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) * rebase -i writes "git-rebase-todo" without the currently executing * command, appending it to "done" instead. */ - if (is_rebase_i(opts)) + if (is_rebase_i(opts) && !reschedule) next++; fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); @@ -3415,7 +3441,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'"), todo_path); - if (is_rebase_i(opts) && next > 0) { + if (is_rebase_i(opts) && !reschedule && next > 0) { const char *done = rebase_path_done(); int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666); int ret = 0; @@ -3496,18 +3522,19 @@ static int make_patch(struct repository *r, struct commit *commit, struct replay_opts *opts) { - struct strbuf buf = STRBUF_INIT; struct rev_info log_tree_opt; const char *subject; char hex[GIT_MAX_HEXSZ + 1]; int res = 0; + if (!is_rebase_i(opts)) + BUG("make_patch should only be called when rebasing"); + oid_to_hex_r(hex, &commit->object.oid); if (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0) return -1; res |= write_rebase_head(&commit->object.oid); - strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); repo_init_revisions(r, &log_tree_opt, NULL); log_tree_opt.abbrev = 0; @@ -3515,28 +3542,26 @@ static int make_patch(struct repository *r, log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; log_tree_opt.disable_stdin = 1; log_tree_opt.no_commit_id = 1; - log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.file = fopen(rebase_path_patch(), "w"); log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; if (!log_tree_opt.diffopt.file) - res |= error_errno(_("could not open '%s'"), buf.buf); + res |= error_errno(_("could not open '%s'"), + rebase_path_patch()); else { res |= log_tree_commit(&log_tree_opt, commit); fclose(log_tree_opt.diffopt.file); } - strbuf_reset(&buf); - strbuf_addf(&buf, "%s/message", get_dir(opts)); - if (!file_exists(buf.buf)) { + if (!file_exists(rebase_path_message())) { const char *encoding = get_commit_output_encoding(); const char *commit_buffer = repo_logmsg_reencode(r, commit, NULL, encoding); find_commit_subject(commit_buffer, &subject); - res |= write_message(subject, strlen(subject), buf.buf, 1); + res |= write_message(subject, strlen(subject), rebase_path_message(), 1); repo_unuse_commit_buffer(r, commit, commit_buffer); } - strbuf_release(&buf); release_revisions(&log_tree_opt); return res; @@ -3584,7 +3609,7 @@ static int error_with_patch(struct repository *r, } else if (exit_code) { if (commit) fprintf_ln(stderr, _("Could not apply %s... %.*s"), - short_commit_name(commit), subject_len, subject); + short_commit_name(r, commit), subject_len, subject); else /* * We don't have the hash of the parent so @@ -4154,6 +4179,7 @@ static int do_merge(struct repository *r, if (ret < 0) { error(_("could not even attempt to merge '%.*s'"), merge_arg_len, arg); + unlink(git_path_merge_msg(r)); goto leave_merge; } /* @@ -4641,6 +4667,68 @@ N_("Could not execute the todo command\n" " git rebase --edit-todo\n" " git rebase --continue\n"); +static int pick_one_commit(struct repository *r, + struct todo_list *todo_list, + struct replay_opts *opts, + int *check_todo, int* reschedule) +{ + 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( + opts, command_to_string(item->command), NULL); + + res = do_pick_commit(r, item, opts, is_final_fixup(todo_list), + check_todo); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + *reschedule = 1; + return -1; + } + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) { + if (!opts->verbose) + term_clear_line(); + fprintf(stderr, _("Stopped at %s... %.*s\n"), + short_commit_name(r, commit), item->arg_len, arg); + } + return error_with_patch(r, commit, + arg, item->arg_len, opts, res, !res); + } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(r, item->commit, opts, + item->arg_len, arg); + } else if (res && is_rebase_i(opts) && item->commit) { + int to_amend = 0; + struct object_id oid; + + /* + * If we are rewording and have either + * fast-forwarded already, or are about to + * create a new root commit, we want to amend, + * otherwise we do not. + */ + if (item->command == TODO_REWORD && + !repo_get_oid(r, "HEAD", &oid) && + (oideq(&item->commit->object.oid, &oid) || + (opts->have_squash_onto && + oideq(&opts->squash_onto, &oid)))) + to_amend = 1; + + return res | error_with_patch(r, item->commit, + arg, item->arg_len, opts, + res, to_amend); + } + return res; +} + static int pick_commits(struct repository *r, struct todo_list *todo_list, struct replay_opts *opts) @@ -4656,12 +4744,17 @@ static int pick_commits(struct repository *r, if (read_and_refresh_cache(r, opts)) return -1; + unlink(rebase_path_message()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + unlink(rebase_path_patch()); + while (todo_list->current < todo_list->nr) { struct todo_item *item = todo_list->items + todo_list->current; const char *arg = todo_item_get_arg(todo_list, item); int check_todo = 0; - if (save_todo(todo_list, opts)) + if (save_todo(todo_list, opts, reschedule)) return -1; if (is_rebase_i(opts)) { if (item->command != TODO_COMMENT) { @@ -4679,10 +4772,7 @@ static int pick_commits(struct repository *r, todo_list->total_nr, opts->verbose ? "\n" : "\r"); } - unlink(rebase_path_message()); unlink(rebase_path_author_script()); - unlink(rebase_path_stopped_sha()); - unlink(rebase_path_amend()); unlink(git_path_merge_head(r)); unlink(git_path_auto_merge(r)); delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); @@ -4694,66 +4784,10 @@ static int pick_commits(struct repository *r, } } if (item->command <= TODO_SQUASH) { - if (is_rebase_i(opts)) - opts->reflog_message = reflog_message(opts, - command_to_string(item->command), NULL); - - res = do_pick_commit(r, item, opts, - is_final_fixup(todo_list), - &check_todo); - if (is_rebase_i(opts) && res < 0) { - /* Reschedule */ - advise(_(rescheduled_advice), - get_item_line_length(todo_list, - todo_list->current), - get_item_line(todo_list, - todo_list->current)); - todo_list->current--; - if (save_todo(todo_list, opts)) - return -1; - } - if (item->command == TODO_EDIT) { - struct commit *commit = item->commit; - if (!res) { - if (!opts->verbose) - term_clear_line(); - fprintf(stderr, - _("Stopped at %s... %.*s\n"), - short_commit_name(commit), - item->arg_len, arg); - } - return error_with_patch(r, commit, - arg, item->arg_len, opts, res, !res); - } - if (is_rebase_i(opts) && !res) - record_in_rewritten(&item->commit->object.oid, - peek_command(todo_list, 1)); - if (res && is_fixup(item->command)) { - if (res == 1) - intend_to_amend(); - return error_failed_squash(r, item->commit, opts, - item->arg_len, arg); - } else if (res && is_rebase_i(opts) && item->commit) { - int to_amend = 0; - struct object_id oid; - - /* - * If we are rewording and have either - * fast-forwarded already, or are about to - * create a new root commit, we want to amend, - * otherwise we do not. - */ - if (item->command == TODO_REWORD && - !repo_get_oid(r, "HEAD", &oid) && - (oideq(&item->commit->object.oid, &oid) || - (opts->have_squash_onto && - oideq(&opts->squash_onto, &oid)))) - to_amend = 1; - - return res | error_with_patch(r, item->commit, - arg, item->arg_len, opts, - res, to_amend); - } + res = pick_one_commit(r, todo_list, opts, &check_todo, + &reschedule); + if (!res && item->command == TODO_EDIT) + return 0; } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(arg + item->arg_len); int saved = *end_of_arg; @@ -4801,32 +4835,25 @@ static int pick_commits(struct repository *r, get_item_line_length(todo_list, todo_list->current), get_item_line(todo_list, todo_list->current)); - todo_list->current--; - if (save_todo(todo_list, opts)) + if (save_todo(todo_list, opts, reschedule)) return -1; if (item->commit) - return error_with_patch(r, - item->commit, - arg, item->arg_len, - opts, res, 0); + write_rebase_head(&item->commit->object.oid); } else if (is_rebase_i(opts) && check_todo && !res && reread_todo_if_changed(r, todo_list, opts)) { return -1; } - todo_list->current++; if (res) return res; + + todo_list->current++; } if (is_rebase_i(opts)) { struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; struct stat st; - /* Stopped in the middle, as planned? */ - if (todo_list->current < todo_list->nr) - return 0; - if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && starts_with(head_ref.buf, "refs/")) { const char *msg; @@ -4969,6 +4996,11 @@ static int commit_staged_changes(struct repository *r, is_clean = !has_uncommitted_changes(r, 0); + if (!is_clean && !file_exists(rebase_path_message())) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + + return error(_(staged_changes_advice), gpg_opt, gpg_opt); + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; struct object_id head, to_amend; @@ -5352,6 +5384,7 @@ struct label_state { struct oidmap commit2label; struct hashmap labels; struct strbuf buf; + int max_label_length; }; static const char *label_oid(struct object_id *oid, const char *label, @@ -5408,6 +5441,8 @@ static const char *label_oid(struct object_id *oid, const char *label, } } else { struct strbuf *buf = &state->buf; + int label_is_utf8 = 1; /* start with this assumption */ + size_t max_len = buf->len + state->max_label_length; /* * Sanitize labels by replacing non-alpha-numeric characters @@ -5416,14 +5451,34 @@ static const char *label_oid(struct object_id *oid, const char *label, * * Note that we retain non-ASCII UTF-8 characters (identified * via the most significant bit). They should be all acceptable - * in file names. We do not validate the UTF-8 here, that's not - * the job of this function. + * in file names. + * + * As we will use the labels as names of (loose) refs, it is + * vital that the name not be longer than the maximum component + * size of the file system (`NAME_MAX`). We are careful to + * truncate the label accordingly, allowing for the `.lock` + * suffix and for the label to be UTF-8 encoded (i.e. we avoid + * truncating in the middle of a character). */ - for (; *label; label++) - if ((*label & 0x80) || isalnum(*label)) + for (; *label && buf->len + 1 < max_len; label++) + if (isalnum(*label) || + (!label_is_utf8 && (*label & 0x80))) strbuf_addch(buf, *label); + else if (*label & 0x80) { + const char *p = label; + + utf8_width(&p, NULL); + if (p) { + if (buf->len + (p - label) > max_len) + break; + strbuf_add(buf, label, p - label); + label = p - 1; + } else { + label_is_utf8 = 0; + strbuf_addch(buf, *label); + } /* avoid leading dash and double-dashes */ - else if (buf->len && buf->buf[buf->len - 1] != '-') + } else if (buf->len && buf->buf[buf->len - 1] != '-') strbuf_addch(buf, '-'); if (!buf->len) { strbuf_addstr(buf, "rev-"); @@ -5485,7 +5540,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, struct string_entry *entry; struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT, shown = OIDSET_INIT; - struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT }; + struct label_state state = + { OIDMAP_INIT, { NULL }, STRBUF_INIT, GIT_MAX_LABEL_LENGTH }; int abbr = flags & TODO_LIST_ABBREVIATE_CMDS; const char *cmd_pick = abbr ? "p" : "pick", @@ -5493,6 +5549,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, *cmd_reset = abbr ? "t" : "reset", *cmd_merge = abbr ? "m" : "merge"; + git_config_get_int("rebase.maxlabellength", &state.max_label_length); + oidmap_init(&commit2todo, 0); oidmap_init(&state.commit2label, 0); hashmap_init(&state.labels, labels_cmp, NULL, 0); @@ -5529,7 +5587,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, if (!is_empty && (commit->object.flags & PATCHSAME)) { if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS) warning(_("skipped previously applied commit %s"), - short_commit_name(commit)); + short_commit_name(the_repository, commit)); skipped_commit = 1; continue; } @@ -5765,7 +5823,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, if (!is_empty && (commit->object.flags & PATCHSAME)) { if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS) warning(_("skipped previously applied commit %s"), - short_commit_name(commit)); + short_commit_name(r, commit)); skipped_commit = 1; continue; } @@ -5857,7 +5915,8 @@ static void todo_list_add_exec_commands(struct todo_list *todo_list, todo_list->alloc = alloc; } -static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, +static void todo_list_to_strbuf(struct repository *r, + struct todo_list *todo_list, struct strbuf *buf, int num, unsigned flags) { struct todo_item *item; @@ -5886,7 +5945,7 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis /* add commit id */ if (item->commit) { const char *oid = flags & TODO_LIST_SHORTEN_IDS ? - short_commit_name(item->commit) : + short_commit_name(r, item->commit) : oid_to_hex(&item->commit->object.oid); if (item->command == TODO_FIXUP) { @@ -6194,7 +6253,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla if (checkout_onto(r, opts, onto_name, &oid, orig_head)) goto cleanup; - if (require_clean_work_tree(r, "rebase", "", 1, 1)) + if (require_clean_work_tree(r, "rebase", NULL, 1, 1)) goto cleanup; todo_list_write_total_nr(&new_todo); @@ -6245,7 +6304,7 @@ static int skip_fixupish(const char *subject, const char **p) { int todo_list_rearrange_squash(struct todo_list *todo_list) { struct hashmap subject2item; - int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0; + int rearranged = 0, *next, *tail, i, nr = 0; char **subjects; struct commit_todo_item commit_todo; struct todo_item *items = NULL; @@ -6357,6 +6416,8 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) } if (rearranged) { + ALLOC_ARRAY(items, todo_list->nr); + for (i = 0; i < todo_list->nr; i++) { enum todo_command command = todo_list->items[i].command; int cur = i; @@ -6369,16 +6430,15 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) continue; while (cur >= 0) { - ALLOC_GROW(items, nr + 1, alloc); items[nr++] = todo_list->items[cur]; cur = next[cur]; } } + assert(nr == todo_list->nr); + todo_list->alloc = nr; FREE_AND_NULL(todo_list->items); todo_list->items = items; - todo_list->nr = nr; - todo_list->alloc = alloc; } free(next); @@ -1221,19 +1221,6 @@ static const char *allowed_bare_repo_to_string( return NULL; } -enum discovery_result { - GIT_DIR_NONE = 0, - GIT_DIR_EXPLICIT, - GIT_DIR_DISCOVERED, - GIT_DIR_BARE, - /* these are errors */ - GIT_DIR_HIT_CEILING = -1, - GIT_DIR_HIT_MOUNT_POINT = -2, - GIT_DIR_INVALID_GITFILE = -3, - GIT_DIR_INVALID_OWNERSHIP = -4, - GIT_DIR_DISALLOWED_BARE = -5, -}; - /* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. @@ -1385,21 +1372,23 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, } } -int discover_git_directory(struct strbuf *commondir, - struct strbuf *gitdir) +enum discovery_result discover_git_directory_reason(struct strbuf *commondir, + struct strbuf *gitdir) { struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT; size_t gitdir_offset = gitdir->len, cwd_len; size_t commondir_offset = commondir->len; struct repository_format candidate = REPOSITORY_FORMAT_INIT; + enum discovery_result result; if (strbuf_getcwd(&dir)) - return -1; + return GIT_DIR_CWD_FAILURE; cwd_len = dir.len; - if (setup_git_directory_gently_1(&dir, gitdir, NULL, 0) <= 0) { + result = setup_git_directory_gently_1(&dir, gitdir, NULL, 0); + if (result <= 0) { strbuf_release(&dir); - return -1; + return result; } /* @@ -1429,11 +1418,11 @@ int discover_git_directory(struct strbuf *commondir, strbuf_setlen(commondir, commondir_offset); strbuf_setlen(gitdir, gitdir_offset); clear_repository_format(&candidate); - return -1; + return GIT_DIR_INVALID_FORMAT; } clear_repository_format(&candidate); - return 0; + return result; } const char *setup_git_directory_gently(int *nongit_ok) @@ -1515,10 +1504,11 @@ const char *setup_git_directory_gently(int *nongit_ok) } *nongit_ok = 1; break; - case GIT_DIR_NONE: + case GIT_DIR_CWD_FAILURE: + case GIT_DIR_INVALID_FORMAT: /* * As a safeguard against setup_git_directory_gently_1 returning - * this value, fallthrough to BUG. Otherwise it is possible to + * these values, fallthrough to BUG. Otherwise it is possible to * set startup_info->have_repository to 1 when we did nothing to * find a repository. */ @@ -42,16 +42,45 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); #define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) void setup_work_tree(void); + +/* + * discover_git_directory_reason() is similar to discover_git_directory(), + * except it returns an enum value instead. It is important to note that + * a zero-valued return here is actually GIT_DIR_NONE, which is different + * from discover_git_directory. + */ +enum discovery_result { + GIT_DIR_EXPLICIT = 1, + GIT_DIR_DISCOVERED = 2, + GIT_DIR_BARE = 3, + /* these are errors */ + GIT_DIR_HIT_CEILING = -1, + GIT_DIR_HIT_MOUNT_POINT = -2, + GIT_DIR_INVALID_GITFILE = -3, + GIT_DIR_INVALID_OWNERSHIP = -4, + GIT_DIR_DISALLOWED_BARE = -5, + GIT_DIR_INVALID_FORMAT = -6, + GIT_DIR_CWD_FAILURE = -7, +}; +enum discovery_result discover_git_directory_reason(struct strbuf *commondir, + struct strbuf *gitdir); + /* * Find the commondir and gitdir of the repository that contains the current * working directory, without changing the working directory or other global * state. The result is appended to commondir and gitdir. If the discovered * gitdir does not correspond to a worktree, then 'commondir' and 'gitdir' will * both have the same result appended to the buffer. The return value is - * either 0 upon success and non-zero if no repository was found. + * either 0 upon success and -1 if no repository was found. */ -int discover_git_directory(struct strbuf *commondir, - struct strbuf *gitdir); +static inline int discover_git_directory(struct strbuf *commondir, + struct strbuf *gitdir) +{ + if (discover_git_directory_reason(commondir, gitdir) <= 0) + return -1; + return 0; +} + const char *setup_git_directory_gently(int *); const char *setup_git_directory(void); char *prefix_path(const char *prefix, int len, const char *path); diff --git a/sparse-index.c b/sparse-index.c index 1fdb07a9e6..3578feb283 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -391,7 +391,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) strbuf_setlen(&base, 0); strbuf_add(&base, ce->name, strlen(ce->name)); - read_tree_at(istate->repo, tree, &base, &ps, + read_tree_at(istate->repo, tree, &base, 0, &ps, add_path_to_index, &ctx); /* free directory entries. full entries are re-used */ @@ -1,6 +1,6 @@ #include "git-compat-util.h" #include "gettext.h" -#include "hex.h" +#include "hex-ll.h" #include "strbuf.h" #include "string-list.h" #include "utf8.h" diff --git a/t/helper/test-env-helper.c b/t/helper/test-env-helper.c index 66c88b8ff3..1c486888a4 100644 --- a/t/helper/test-env-helper.c +++ b/t/helper/test-env-helper.c @@ -1,5 +1,5 @@ #include "test-tool.h" -#include "config.h" +#include "parse.h" #include "parse-options.h" static char const * const env__helper_usage[] = { diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c index 2ed910adaa..8f59f6be4c 100644 --- a/t/helper/test-example-decorate.c +++ b/t/helper/test-example-decorate.c @@ -72,5 +72,7 @@ int cmd__example_decorate(int argc UNUSED, const char **argv UNUSED) if (objects_noticed != 2) BUG("should have 2 objects"); + clear_decoration(&n, NULL); + return 0; } diff --git a/t/helper/test-find-pack.c b/t/helper/test-find-pack.c new file mode 100644 index 0000000000..e8bd793e58 --- /dev/null +++ b/t/helper/test-find-pack.c @@ -0,0 +1,50 @@ +#include "test-tool.h" +#include "object-name.h" +#include "object-store.h" +#include "packfile.h" +#include "parse-options.h" +#include "setup.h" + +/* + * Display the path(s), one per line, of the packfile(s) containing + * the given object. + * + * If '--check-count <n>' is passed, then error out if the number of + * packfiles containing the object is not <n>. + */ + +static const char *find_pack_usage[] = { + "test-tool find-pack [--check-count <n>] <object>", + NULL +}; + +int cmd__find_pack(int argc, const char **argv) +{ + struct object_id oid; + struct packed_git *p; + int count = -1, actual_count = 0; + const char *prefix = setup_git_directory(); + + struct option options[] = { + OPT_INTEGER('c', "check-count", &count, "expected number of packs"), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, find_pack_usage, 0); + if (argc != 1) + usage(find_pack_usage[0]); + + if (repo_get_oid(the_repository, argv[0], &oid)) + die("cannot parse %s as an object name", argv[0]); + + for (p = get_all_packs(the_repository); p; p = p->next) + if (find_pack_entry_one(oid.hash, p)) { + printf("%s\n", p->pack_name); + actual_count++; + } + + if (count > -1 && count != actual_count) + die("bad packfile count %d instead of %d", actual_count, count); + + return 0; +} diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c deleted file mode 100644 index f3c2dbe0a2..0000000000 --- a/t/helper/test-index-version.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "test-tool.h" -#include "read-cache-ll.h" - -int cmd__index_version(int argc UNUSED, const char **argv UNUSED) -{ - struct cache_header hdr; - int version; - - memset(&hdr,0,sizeof(hdr)); - if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) - return 0; - version = ntohl(hdr.hdr_version); - printf("%d\n", version); - return 0; -} diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c index 3d1436da59..941ae7e3bc 100644 --- a/t/helper/test-simple-ipc.c +++ b/t/helper/test-simple-ipc.c @@ -278,7 +278,8 @@ static int daemon__run_server(void) static start_bg_wait_cb bg_wait_cb; -static int bg_wait_cb(const struct child_process *cp, void *cb_data) +static int bg_wait_cb(const struct child_process *cp UNUSED, + void *cb_data UNUSED) { int s = ipc_get_active_state(cl_args.path); diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index abe8a785eb..9010ac6de7 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -31,6 +31,7 @@ static struct test_cmd cmds[] = { { "env-helper", cmd__env_helper }, { "example-decorate", cmd__example_decorate }, { "fast-rebase", cmd__fast_rebase }, + { "find-pack", cmd__find_pack }, { "fsmonitor-client", cmd__fsmonitor_client }, { "genrandom", cmd__genrandom }, { "genzeros", cmd__genzeros }, @@ -38,7 +39,6 @@ static struct test_cmd cmds[] = { { "hashmap", cmd__hashmap }, { "hash-speed", cmd__hash_speed }, { "hexdump", cmd__hexdump }, - { "index-version", cmd__index_version }, { "json-writer", cmd__json_writer }, { "lazy-init-name-hash", cmd__lazy_init_name_hash }, { "match-trees", cmd__match_trees }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index ea2672436c..f134f96b97 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -25,6 +25,7 @@ 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__fast_rebase(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); int cmd__genzeros(int argc, const char **argv); @@ -32,7 +33,6 @@ int cmd__getcwd(int argc, const char **argv); int cmd__hashmap(int argc, const char **argv); int cmd__hash_speed(int argc, const char **argv); int cmd__hexdump(int argc, const char **argv); -int cmd__index_version(int argc, const char **argv); int cmd__json_writer(int argc, const char **argv); int cmd__lazy_init_name_hash(int argc, const char **argv); int cmd__match_trees(int argc, const char **argv); diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c index 20c7495f38..d5ca0046c8 100644 --- a/t/helper/test-trace2.c +++ b/t/helper/test-trace2.c @@ -45,7 +45,7 @@ static int get_i(int *p_value, const char *data) * [] "def_param" events for all of the "interesting" pre-defined * config settings. */ -static int ut_001return(int argc, const char **argv) +static int ut_001return(int argc UNUSED, const char **argv) { int rc; @@ -65,7 +65,7 @@ static int ut_001return(int argc, const char **argv) * [] "def_param" events for all of the "interesting" pre-defined * config settings. */ -static int ut_002exit(int argc, const char **argv) +static int ut_002exit(int argc UNUSED, const char **argv) { int rc; @@ -201,7 +201,7 @@ static int ut_006data(int argc, const char **argv) return 0; } -static int ut_007BUG(int argc, const char **argv) +static int ut_007BUG(int argc UNUSED, const char **argv UNUSED) { /* * Exercise BUG() to ensure that the message is printed to trace2. diff --git a/t/lib-credential.sh b/t/lib-credential.sh index 032b2d8fcc..15fc9a31e2 100644 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -43,6 +43,8 @@ helper_test_clean() { reject $1 https example.com store-user reject $1 https example.com user1 reject $1 https example.com user2 + reject $1 https example.com user-expiry + reject $1 https example.com user-expiry-overwrite reject $1 https example.com user4 reject $1 https example.com user-distinct-pass reject $1 https example.com user-overwrite @@ -431,6 +433,81 @@ helper_test_timeout() { ' } +helper_test_password_expiry_utc() { + HELPER=$1 + + test_expect_success "helper ($HELPER) stores password_expiry_utc" ' + check approve $HELPER <<-\EOF + protocol=https + host=example.com + username=user-expiry + password=pass + password_expiry_utc=9999999999 + EOF + ' + + test_expect_success "helper ($HELPER) gets password_expiry_utc" ' + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=user-expiry + -- + protocol=https + host=example.com + username=user-expiry + password=pass + password_expiry_utc=9999999999 + -- + EOF + ' + + test_expect_success "helper ($HELPER) overwrites when password_expiry_utc changes" ' + check approve $HELPER <<-\EOF && + protocol=https + host=example.com + username=user-expiry-overwrite + password=pass1 + password_expiry_utc=9999999998 + EOF + check approve $HELPER <<-\EOF && + protocol=https + host=example.com + username=user-expiry-overwrite + password=pass2 + password_expiry_utc=9999999999 + EOF + check fill $HELPER <<-\EOF && + protocol=https + host=example.com + username=user-expiry-overwrite + -- + protocol=https + host=example.com + username=user-expiry-overwrite + password=pass2 + password_expiry_utc=9999999999 + EOF + check reject $HELPER <<-\EOF && + protocol=https + host=example.com + username=user-expiry-overwrite + password=pass2 + EOF + check fill $HELPER <<-\EOF + protocol=https + host=example.com + username=user-expiry-overwrite + -- + protocol=https + host=example.com + username=user-expiry-overwrite + password=askpass-password + -- + askpass: Password for '\''https://user-expiry-overwrite@example.com'\'': + EOF + ' +} + helper_test_oauth_refresh_token() { HELPER=$1 diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 4eebd9c2b5..83b83c9abb 100644 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -45,6 +45,7 @@ test_lazy_prereq GPG ' "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && gpg --homedir "${GNUPGHOME}" --import-ownertrust \ "$TEST_DIRECTORY"/lib-gpg/ownertrust && + gpg --homedir "${GNUPGHOME}" --update-trustdb && gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \ --sign -u committer@example.com ;; diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index a22d138605..022276a6b9 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -92,6 +92,7 @@ PassEnv GIT_VALGRIND_OPTIONS PassEnv GNUPGHOME PassEnv ASAN_OPTIONS PassEnv LSAN_OPTIONS +PassEnv UBSAN_OPTIONS PassEnv GIT_TRACE PassEnv GIT_CONFIG_NOSYSTEM PassEnv GIT_TEST_SIDEBAND_ALL diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 7ca5b918f0..11d2dc9fe3 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -8,18 +8,21 @@ # - check that non-commit messages have a certain line count with $EXPECT_COUNT # - check the commit count in the commit message header with $EXPECT_HEADER_COUNT # - rewrite a rebase -i script as directed by $FAKE_LINES. -# $FAKE_LINES consists of a sequence of words separated by spaces. -# The following word combinations are possible: +# $FAKE_LINES consists of a sequence of words separated by spaces; +# spaces inside the words are encoded as underscores. +# The following words are possible: # -# "<lineno>" -- add a "pick" line with the SHA1 taken from the -# specified line. +# "<cmd>" -- override the command for the next line specification. Can be +# "pick", "squash", "fixup[_-(c|C)]", "edit", "reword", "drop", +# "merge[_-(c|C)_<rev>]", or "bad" for an invalid command. # -# "<cmd> <lineno>" -- add a line with the specified command -# ("pick", "squash", "fixup"|"fixup_-C"|"fixup_-c", "edit", "reword" or "drop") -# and the SHA1 taken from the specified line. +# "<lineno>" -- add a command, using the specified line as a template. +# If the command has not been overridden, the line will be copied +# verbatim, usually resulting in a "pick" line. # -# "_" -- add a space, like "fixup_-C" implies "fixup -C" and -# "exec_cmd_with_args" add an "exec cmd with args" line. +# "fakesha" -- add a command ("pick" by default), using a fake SHA1. +# +# "exec_[command...]", "break" -- add the specified command. # # "#" -- Add a comment line. # @@ -49,7 +52,7 @@ set_fake_editor () { action=\& for line in $FAKE_LINES; do case $line in - pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|r|merge|m) + pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d|label|l|reset|t|merge|m) action="$line";; exec_*|x_*|break|b) echo "$line" | sed 's/_/ /g' >> "$1";; @@ -64,7 +67,7 @@ set_fake_editor () { fakesha) test \& != "$action" || action=pick echo "$action XXXXXXX False commit" >> "$1" - action=pick;; + action=\&;; *) sed -n "${line}s/^[a-z][a-z]*/$action/p" < "$1".tmp >> "$1" action=\&;; diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index 96ed3e1d69..39e92b0841 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -134,5 +134,6 @@ test_perf_on_all git diff-files -- $SPARSE_CONE/a test_perf_on_all git diff-tree HEAD test_perf_on_all git diff-tree HEAD -- $SPARSE_CONE/a test_perf_on_all "git worktree add ../temp && git worktree remove ../temp" +test_perf_on_all git check-attr -a -- $SPARSE_CONE/a test_done diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh index 8cb597f99c..ff4fd9348c 100755 --- a/t/t0007-git-var.sh +++ b/t/t0007-git-var.sh @@ -268,4 +268,13 @@ test_expect_success 'listing and asking for variables are exclusive' ' test_must_fail git var -l GIT_COMMITTER_IDENT ' +test_expect_success '`git var -l` works even without HOME' ' + ( + XDG_CONFIG_HOME= && + export XDG_CONFIG_HOME && + unset HOME && + git var -l + ) +' + test_done diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index e19a199636..a0ad6192d6 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -13,30 +13,35 @@ usage: test-tool parse-options <options> A helper function for the parse-options API. - --yes get a boolean + --[no-]yes get a boolean -D, --no-doubt begins with 'no-' + --doubt opposite of --no-doubt -B, --no-fear be brave - -b, --boolean increment by one - -4, --or4 bitwise-or boolean with ...0100 - --neg-or4 same as --no-or4 + -b, --[no-]boolean increment by one + -4, --[no-]or4 bitwise-or boolean with ...0100 + --[no-]neg-or4 same as --no-or4 - -i, --integer <n> get a integer + -i, --[no-]integer <n> + get a integer -j <n> get a integer, too -m, --magnitude <n> get a magnitude - --set23 set integer to 23 + --[no-]set23 set integer to 23 --mode1 set integer to 1 (cmdmode option) --mode2 set integer to 2 (cmdmode option) - -L, --length <str> get length of <str> - -F, --file <file> set file to <file> + -L, --[no-]length <str> + get length of <str> + -F, --[no-]file <file> + set file to <file> String options - -s, --string <string> get a string - --string2 <str> get another string - --st <st> get another string (pervert ordering) + -s, --[no-]string <string> + get a string + --[no-]string2 <str> get another string + --[no-]st <st> get another string (pervert ordering) -o <str> get another string --longhelp help text of this entry spans multiple lines - --list <str> add str to list + --[no-]list <str> add str to list Magic arguments -NUM set integer to NUM @@ -45,16 +50,17 @@ Magic arguments --no-ambiguous negative ambiguity Standard options - --abbrev[=<n>] use <n> digits to display object names - -v, --verbose be verbose - -n, --dry-run dry run - -q, --quiet be quiet - --expect <string> expected output in the variable dump + --[no-]abbrev[=<n>] use <n> digits to display object names + -v, --[no-]verbose be verbose + -n, --[no-]dry-run dry run + -q, --[no-]quiet be quiet + --[no-]expect <string> + expected output in the variable dump Alias - -A, --alias-source <string> + -A, --[no-]alias-source <string> get a string - -Z, --alias-target <string> + -Z, --[no-]alias-target <string> alias of --alias-source EOF diff --git a/t/t0081-find-pack.sh b/t/t0081-find-pack.sh new file mode 100755 index 0000000000..67b11216a3 --- /dev/null +++ b/t/t0081-find-pack.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +test_description='test `test-tool find-pack`' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit one && + test_commit two && + test_commit three && + test_commit four && + test_commit five +' + +test_expect_success 'repack everything into a single packfile' ' + git repack -a -d --no-write-bitmap-index && + + head_commit_pack=$(test-tool find-pack HEAD) && + head_tree_pack=$(test-tool find-pack HEAD^{tree}) && + one_pack=$(test-tool find-pack HEAD:one.t) && + three_pack=$(test-tool find-pack HEAD:three.t) && + old_commit_pack=$(test-tool find-pack HEAD~4) && + + test-tool find-pack --check-count 1 HEAD && + test-tool find-pack --check-count=1 HEAD^{tree} && + ! test-tool find-pack --check-count=0 HEAD:one.t && + ! test-tool find-pack -c 2 HEAD:one.t && + test-tool find-pack -c 1 HEAD:three.t && + + # Packfile exists at the right path + case "$head_commit_pack" in + ".git/objects/pack/pack-"*".pack") true ;; + *) false ;; + esac && + test -f "$head_commit_pack" && + + # Everything is in the same pack + test "$head_commit_pack" = "$head_tree_pack" && + test "$head_commit_pack" = "$one_pack" && + test "$head_commit_pack" = "$three_pack" && + test "$head_commit_pack" = "$old_commit_pack" +' + +test_expect_success 'add more packfiles' ' + git rev-parse HEAD^{tree} HEAD:two.t HEAD:four.t >objects && + git pack-objects .git/objects/pack/mypackname1 >packhash1 <objects && + + git rev-parse HEAD~ HEAD~^{tree} HEAD:five.t >objects && + git pack-objects .git/objects/pack/mypackname2 >packhash2 <objects && + + head_commit_pack=$(test-tool find-pack HEAD) && + + # HEAD^{tree} is in 2 packfiles + test-tool find-pack HEAD^{tree} >head_tree_packs && + grep "$head_commit_pack" head_tree_packs && + grep mypackname1 head_tree_packs && + ! grep mypackname2 head_tree_packs && + test-tool find-pack --check-count 2 HEAD^{tree} && + ! test-tool find-pack --check-count 1 HEAD^{tree} && + + # HEAD:five.t is also in 2 packfiles + test-tool find-pack HEAD:five.t >five_packs && + grep "$head_commit_pack" five_packs && + ! grep mypackname1 five_packs && + grep mypackname2 five_packs && + test-tool find-pack -c 2 HEAD:five.t && + ! test-tool find-pack --check-count=0 HEAD:five.t +' + +test_expect_success 'add more commits (as loose objects)' ' + test_commit six && + test_commit seven && + + test -z "$(test-tool find-pack HEAD)" && + test -z "$(test-tool find-pack HEAD:six.t)" && + test-tool find-pack --check-count 0 HEAD && + test-tool find-pack -c 0 HEAD:six.t && + ! test-tool find-pack -c 1 HEAD:seven.t +' + +test_done diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index c02a3b5969..8300faadea 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -29,6 +29,7 @@ test_atexit 'git credential-cache exit' # test that the daemon works with no special setup helper_test cache +helper_test_password_expiry_utc cache helper_test_oauth_refresh_token cache test_expect_success 'socket defaults to ~/.cache/git/credential/socket' ' diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh index f028fd1418..095574bfc6 100755 --- a/t/t0303-credential-external.sh +++ b/t/t0303-credential-external.sh @@ -45,6 +45,8 @@ test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" || helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER" helper_test "$GIT_TEST_CREDENTIAL_HELPER" +helper_test_password_expiry_utc "$GIT_TEST_CREDENTIAL_HELPER" +helper_test_oauth_refresh_token "$GIT_TEST_CREDENTIAL_HELPER" if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)" diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 8a95adf4b5..2a4f35e984 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -2259,4 +2259,76 @@ test_expect_success 'worktree is not expanded' ' ensure_not_expanded worktree remove .worktrees/hotfix ' +test_expect_success 'check-attr with pathspec inside sparse definition' ' + init_repos && + + echo "a -crlf myAttr" >>.gitattributes && + run_on_all cp ../.gitattributes ./deep && + + test_all_match git check-attr -a -- deep/a && + + test_all_match git add deep/.gitattributes && + test_all_match git check-attr -a --cached -- deep/a +' + +test_expect_success 'check-attr with pathspec outside sparse definition' ' + init_repos && + + echo "a -crlf myAttr" >>.gitattributes && + run_on_sparse mkdir folder1 && + run_on_all cp ../.gitattributes ./folder1 && + run_on_all cp a folder1/a && + + test_all_match git check-attr -a -- folder1/a && + + git -C full-checkout add folder1/.gitattributes && + test_sparse_match git add --sparse folder1/.gitattributes && + test_all_match git commit -m "add .gitattributes" && + test_sparse_match git sparse-checkout reapply && + test_all_match git check-attr -a --cached -- folder1/a +' + +# NEEDSWORK: The 'diff --check' test is left as 'test_expect_failure' due +# to an underlying issue in oneway_diff() within diff-lib.c. +# 'do_oneway_diff()' is not called as expected for paths that could match +# inside of a sparse directory. Specifically, the 'ce_path_match()' function +# fails to recognize files inside a sparse directory (e.g., when 'folder1/' +# is a sparse directory, 'folder1/a' cannot be recognized). The goal is to +# proceed with 'do_oneway_diff()' if the pathspec could match inside of a +# sparse directory. +test_expect_failure 'diff --check with pathspec outside sparse definition' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo "a " >"$1" + EOF + + test_all_match git config core.whitespace -trailing-space,-space-before-tab && + + echo "a whitespace=trailing-space,space-before-tab" >>.gitattributes && + run_on_all mkdir -p folder1 && + run_on_all cp ../.gitattributes ./folder1 && + test_all_match git add --sparse folder1/.gitattributes && + run_on_all ../edit-contents folder1/a && + test_all_match git add --sparse folder1/a && + + test_sparse_match git sparse-checkout reapply && + test_all_match test_must_fail git diff --check --cached -- folder1/a +' + +test_expect_success 'sparse-index is not expanded: check-attr' ' + init_repos && + + echo "a -crlf myAttr" >>.gitattributes && + mkdir ./sparse-index/folder1 && + cp ./sparse-index/a ./sparse-index/folder1/a && + cp .gitattributes ./sparse-index/deep && + cp .gitattributes ./sparse-index/folder1 && + + git -C sparse-index add deep/.gitattributes && + git -C sparse-index add --sparse folder1/.gitattributes && + ensure_not_expanded check-attr -a --cached -- deep/a && + ensure_not_expanded check-attr -a --cached -- folder1/a +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 5805d47eb9..10a539158c 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -589,6 +589,16 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' ' ) ' +test_expect_success 'fsck notices excessively large tree entry name' ' + git init large-name && + ( + cd large-name && + test_commit a-long-name && + git -c fsck.largePathname=warn:10 fsck 2>out && + grep "warning.*large pathname" out + ) +' + while read name path pretty; do while read mode type; do : ${pretty:=$path} diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index dd811b7fb4..f0737593c3 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -3,13 +3,29 @@ test_description='test git rev-parse --parseopt' . ./test-lib.sh +check_invalid_long_option () { + spec="$1" + opt="$2" + test_expect_success "test --parseopt invalid switch $opt help output for $spec" ' + { + cat <<-\EOF && + error: unknown option `'${opt#--}\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/$spec.help" + } >expect && + test_expect_code 129 git rev-parse --parseopt -- $opt \ + 2>output <"$TEST_DIRECTORY/t1502/$spec" && + test_cmp expect output + ' +} + test_expect_success 'setup optionspec' ' sed -e "s/^|//" >optionspec <<\EOF |some-command [options] <args>... | |some-command does foo and bar! |-- -|h,help show the help +|h,help! show the help | |foo some nifty option --foo |bar= some cool option --bar with an argument @@ -58,44 +74,8 @@ EOF ' test_expect_success 'test --parseopt help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|cat <<\EOF -|usage: some-command [options] <args>... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B <arg> short option required argument -| --bar2 <arg> long option required argument -| -e, --fuz <with-space> -| short and long option required argument -| -s[<some>] short option optional argument -| --long[=<data>] long option optional argument -| -g, --fluf[=<path>] short and long option optional argument -| --longest <very-long-argument-hint> -| a very long argument hint -| --pair <key=value> with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch <hint> hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint <a> with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -|EOF -END_EXPECT test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec && - test_cmp expect output + test_cmp "$TEST_DIRECTORY/t1502/optionspec.help" output ' test_expect_success 'test --parseopt help output no switches' ' @@ -131,7 +111,7 @@ test_expect_success 'test --parseopt help-all output hidden switches' ' | | some-command does foo and bar! | -| --hidden1 A hidden switch +| --[no-]hidden1 A hidden switch | |EOF END_EXPECT @@ -140,41 +120,12 @@ END_EXPECT ' test_expect_success 'test --parseopt invalid switch help output' ' - sed -e "s/^|//" >expect <<\END_EXPECT && -|error: unknown option `does-not-exist'\'' -|usage: some-command [options] <args>... -| -| some-command does foo and bar! -| -| -h, --help show the help -| --foo some nifty option --foo -| --bar ... some cool option --bar with an argument -| -b, --baz a short and long option -| -|An option group Header -| -C[...] option C with an optional argument -| -d, --data[=...] short and long option with an optional argument -| -|Argument hints -| -B <arg> short option required argument -| --bar2 <arg> long option required argument -| -e, --fuz <with-space> -| short and long option required argument -| -s[<some>] short option optional argument -| --long[=<data>] long option optional argument -| -g, --fluf[=<path>] short and long option optional argument -| --longest <very-long-argument-hint> -| a very long argument hint -| --pair <key=value> with an equals sign in the hint -| --aswitch help te=t contains? fl*g characters!` -| --bswitch <hint> hint has trailing tab character -| --cswitch switch has trailing tab character -| --short-hint <a> with a one symbol hint -| -|Extras -| --extra1 line above used to cause a segfault but no longer does -| -END_EXPECT + { + cat <<-\EOF && + error: unknown option `does-not-exist'\'' + EOF + sed -e 1d -e \$d <"$TEST_DIRECTORY/t1502/optionspec.help" + } >expect && test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec && test_cmp expect output ' @@ -288,7 +239,7 @@ test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" | [--another-option] |cmd [--yet-another-option] |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && @@ -322,7 +273,7 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l |line |blurb |-- - |h,help show the help + |h,help! show the help EOF sed -e "s/^|//" >expect <<-\END_EXPECT && @@ -343,4 +294,32 @@ test_expect_success 'test --parseopt help output: multi-line blurb after empty l test_cmp expect actual ' +test_expect_success 'test --parseopt help output for optionspec-neg' ' + test_expect_code 129 git rev-parse --parseopt -- \ + -h >output <"$TEST_DIRECTORY/t1502/optionspec-neg" && + test_cmp "$TEST_DIRECTORY/t1502/optionspec-neg.help" output +' + +test_expect_success 'test --parseopt valid options for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --foo --no-foo --no-bar --positive-only --no-negative -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --foo --no-foo --no-bar --positive-only --no-negative && + test_cmp expect output +' + +test_expect_success 'test --parseopt positivated option for optionspec-neg' ' + cat >expect <<-\EOF && + set -- --no-no-bar --no-no-bar -- + EOF + git rev-parse --parseopt -- <"$TEST_DIRECTORY/t1502/optionspec-neg" >output \ + --no-no-bar --bar && + test_cmp expect output +' + +check_invalid_long_option optionspec-neg --no-positive-only +check_invalid_long_option optionspec-neg --negative +check_invalid_long_option optionspec-neg --no-no-negative + test_done diff --git a/t/t1502/.gitattributes b/t/t1502/.gitattributes new file mode 100644 index 0000000000..562b12e16e --- /dev/null +++ b/t/t1502/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t1502/optionspec-neg b/t/t1502/optionspec-neg new file mode 100644 index 0000000000..392f43eb0b --- /dev/null +++ b/t/t1502/optionspec-neg @@ -0,0 +1,8 @@ +some-command [options] <args>... + +some-command does foo and bar! +-- +foo can be negated +no-bar can be positivated +positive-only! cannot be negated +no-negative! cannot be positivated diff --git a/t/t1502/optionspec-neg.help b/t/t1502/optionspec-neg.help new file mode 100644 index 0000000000..7a29f8cb03 --- /dev/null +++ b/t/t1502/optionspec-neg.help @@ -0,0 +1,12 @@ +cat <<\EOF +usage: some-command [options] <args>... + + some-command does foo and bar! + + --[no-]foo can be negated + --no-bar can be positivated + --bar opposite of --no-bar + --positive-only cannot be negated + --no-negative cannot be positivated + +EOF diff --git a/t/t1502/optionspec.help b/t/t1502/optionspec.help new file mode 100755 index 0000000000..cbdd54d41b --- /dev/null +++ b/t/t1502/optionspec.help @@ -0,0 +1,36 @@ +cat <<\EOF +usage: some-command [options] <args>... + + some-command does foo and bar! + + -h, --help show the help + --[no-]foo some nifty option --foo + --[no-]bar ... some cool option --bar with an argument + -b, --[no-]baz a short and long option + +An option group Header + -C[...] option C with an optional argument + -d, --[no-]data[=...] short and long option with an optional argument + +Argument hints + -B <arg> short option required argument + --[no-]bar2 <arg> long option required argument + -e, --[no-]fuz <with-space> + short and long option required argument + -s[<some>] short option optional argument + --[no-]long[=<data>] long option optional argument + -g, --[no-]fluf[=<path>] + short and long option optional argument + --[no-]longest <very-long-argument-hint> + a very long argument hint + --[no-]pair <key=value> + with an equals sign in the hint + --[no-]aswitch help te=t contains? fl*g characters!` + --[no-]bswitch <hint> hint has trailing tab character + --[no-]cswitch switch has trailing tab character + --[no-]short-hint <a> with a one symbol hint + +Extras + --[no-]extra1 line above used to cause a segfault but no longer does + +EOF diff --git a/t/t1600-index.sh b/t/t1600-index.sh index 9368d82f7d..62e7fd1596 100755 --- a/t/t1600-index.sh +++ b/t/t1600-index.sh @@ -118,7 +118,7 @@ test_index_version () { fi && git add a && echo $EXPECTED_OUTPUT_VERSION >expect && - test-tool index-version <.git/index >actual && + git update-index --show-index-version >actual && test_cmp expect actual ) } diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index b4ab166369..a7b7263b35 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -43,7 +43,7 @@ test_expect_success 'enable split index' ' git config splitIndex.maxPercentChange 100 && git update-index --split-index && test-tool dump-split-index .git/index >actual && - indexversion=$(test-tool index-version <.git/index) && + indexversion=$(git update-index --show-index-version) && # NEEDSWORK: Stop hard-coding checksums. if test "$indexversion" = "4" diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh index b16d69ca4a..45dd1bc858 100755 --- a/t/t2004-checkout-cache-temp.sh +++ b/t/t2004-checkout-cache-temp.sh @@ -117,6 +117,26 @@ test_expect_success 'checkout all stages/one file to temporary files' ' test $(cat $s3) = tree3path1) ' +test_expect_success '--stage=all implies --temp' ' + rm -f path* .merge_* actual && + git checkout-index --stage=all -- path1 && + test_path_is_missing path1 +' + +test_expect_success 'overriding --stage=all resets implied --temp' ' + rm -f path* .merge_* actual && + git checkout-index --stage=all --stage=2 -- path1 && + echo tree2path1 >expect && + test_cmp expect path1 +' + +test_expect_success '--stage=all --no-temp is rejected' ' + rm -f path* .merge_* actual && + test_must_fail git checkout-index --stage=all --no-temp -- path1 2>err && + grep -v "already exists" err && + grep "options .--stage=all. and .--no-temp. cannot be used together" err +' + test_expect_success 'checkout some stages/one file to temporary files' ' rm -f path* .merge_* actual && git checkout-index --stage=all --temp -- path2 >actual && diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index 2d8c70b03a..3eda385ca2 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -37,11 +37,17 @@ prime_resolve_undo () { git checkout second^0 && test_tick && test_must_fail git merge third^0 && - echo merge does not leave anything && check_resolve_undo empty && - echo different >fi/le && - git add fi/le && - echo resolving records && + + # how should the conflict be resolved? + case "$1" in + remove) + rm -f file/le && git rm fi/le + ;; + *) # modify + echo different >fi/le && git add fi/le + ;; + esac check_resolve_undo recorded fi/le initial:fi/le second:fi/le third:fi/le } @@ -122,6 +128,37 @@ test_expect_success 'add records checkout -m undoes' ' test_expect_success 'unmerge with plumbing' ' prime_resolve_undo && git update-index --unresolve fi/le && + git ls-files --resolve-undo fi/le >actual && + test_must_be_empty actual && + git ls-files -u >actual && + test_line_count = 3 actual +' + +test_expect_success 'unmerge can be done even after committing' ' + prime_resolve_undo && + git commit -m "record to nuke MERGE_HEAD" && + git update-index --unresolve fi/le && + git ls-files --resolve-undo fi/le >actual && + test_must_be_empty actual && + git ls-files -u >actual && + test_line_count = 3 actual +' + +test_expect_success 'unmerge removal' ' + prime_resolve_undo remove && + git update-index --unresolve fi/le && + git ls-files --resolve-undo fi/le >actual && + test_must_be_empty actual && + git ls-files -u >actual && + test_line_count = 3 actual +' + +test_expect_success 'unmerge removal after committing' ' + prime_resolve_undo remove && + git commit -m "record to nuke MERGE_HEAD" && + git update-index --unresolve fi/le && + git ls-files --resolve-undo fi/le >actual && + test_must_be_empty actual && git ls-files -u >actual && test_line_count = 3 actual ' diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh index c5d19dd973..16d6348b69 100755 --- a/t/t2070-restore.sh +++ b/t/t2070-restore.sh @@ -137,11 +137,78 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' ' test_must_fail git rev-parse HEAD:new1 ' -test_expect_success 'restore with merge options rejects --staged' ' +test_expect_success 'restore --merge to unresolve' ' + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + { + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" + } | git update-index --index-info && + echo nothing >file && + git restore --worktree --merge file && + cat >expect <<-\EOF && + <<<<<<< ours + ourside + ======= + theirside + >>>>>>> theirs + EOF + test_cmp expect file +' + +test_expect_success 'restore --merge to unresolve after (mistaken) resolution' ' + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + { + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" + } | git update-index --index-info && + echo nothing >file && + git add file && + git restore --worktree --merge file && + cat >expect <<-\EOF && + <<<<<<< ours + ourside + ======= + theirside + >>>>>>> theirs + EOF + test_cmp expect file +' + +test_expect_success 'restore --merge to unresolve after (mistaken) resolution' ' + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + { + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" + } | git update-index --index-info && + git rm -f file && + git restore --worktree --merge file && + cat >expect <<-\EOF && + <<<<<<< ours + ourside + ======= + theirside + >>>>>>> theirs + EOF + test_cmp expect file +' + +test_expect_success 'restore with merge options are incompatible with certain options' ' for opts in \ "--staged --ours" \ "--staged --theirs" \ "--staged --merge" \ + "--source=HEAD --ours" \ + "--source=HEAD --theirs" \ + "--source=HEAD --merge" \ "--staged --conflict=diff3" \ "--staged --worktree --ours" \ "--staged --worktree --theirs" \ @@ -149,7 +216,7 @@ test_expect_success 'restore with merge options rejects --staged' ' "--staged --worktree --conflict=zdiff3" do test_must_fail git restore $opts . 2>err && - grep "cannot be used with --staged" err || return + grep "cannot be used" err || return done ' diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh index b8686aabd3..0bab134d71 100755 --- a/t/t2104-update-index-skip-worktree.sh +++ b/t/t2104-update-index-skip-worktree.sh @@ -39,7 +39,7 @@ test_expect_success 'setup' ' ' test_expect_success 'index is at version 2' ' - test "$(test-tool index-version < .git/index)" = 2 + test "$(git update-index --show-index-version)" = 2 ' test_expect_success 'update-index --skip-worktree' ' @@ -48,7 +48,7 @@ test_expect_success 'update-index --skip-worktree' ' ' test_expect_success 'index is at version 3 after having some skip-worktree entries' ' - test "$(test-tool index-version < .git/index)" = 3 + test "$(git update-index --show-index-version)" = 3 ' test_expect_success 'ls-files -t' ' @@ -61,7 +61,7 @@ test_expect_success 'update-index --no-skip-worktree' ' ' test_expect_success 'index version is back to 2 when there is no skip-worktree entry' ' - test "$(test-tool index-version < .git/index)" = 2 + test "$(git update-index --show-index-version)" = 2 ' test_done diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh index 89b285fa3a..22f4c92399 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh @@ -111,4 +111,35 @@ test_expect_success '--chmod=+x and chmod=-x in the same argument list' ' test_cmp expect actual ' +test_expect_success '--index-version' ' + git commit --allow-empty -m snap && + git reset --hard && + git rm -f -r --cached . && + + # The default index version is 2 --- update this test + # when you change it in the code + git update-index --show-index-version >actual && + echo 2 >expect && + test_cmp expect actual && + + # The next test wants us to be using version 2 + git update-index --index-version 2 && + + git update-index --index-version 4 --verbose >actual && + echo "index-version: was 2, set to 4" >expect && + test_cmp expect actual && + + git update-index --index-version 4 --verbose >actual && + echo "index-version: was 4, set to 4" >expect && + test_cmp expect actual && + + git update-index --index-version 2 --verbose >actual && + echo "index-version: was 4, set to 2" >expect && + test_cmp expect actual && + + # non-verbose should be silent + git update-index --index-version 4 >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 051363acbb..df4aff7825 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -121,7 +121,8 @@ test_expect_success '"add" worktree creating new branch' ' test_expect_success 'die the same branch is already checked out' ' ( cd here && - test_must_fail git checkout newmain + test_must_fail git checkout newmain 2>actual && + grep "already used by worktree at" actual ) ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index daf1666df7..080e4f24a6 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -942,7 +942,19 @@ test_expect_success 'test deleting branch without config' ' test_expect_success 'deleting currently checked out branch fails' ' git worktree add -b my7 my7 && test_must_fail git -C my7 branch -d my7 && - test_must_fail git branch -d my7 && + test_must_fail git branch -d my7 2>actual && + grep "^error: Cannot delete branch .my7. used by worktree at " actual && + rm -r my7 && + git worktree prune +' + +test_expect_success 'deleting in-use branch fails' ' + git worktree add my7 && + test_commit -C my7 bt7 && + git -C my7 bisect start HEAD HEAD~2 && + test_must_fail git -C my7 branch -d my7 && + test_must_fail git branch -d my7 2>actual && + grep "^error: Cannot delete branch .my7. used by worktree at " actual && rm -r my7 && git worktree prune ' diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index b5f4d6a653..b33afa1c6a 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -662,6 +662,20 @@ test_expect_success 'range-diff with multiple --notes' ' test_cmp expect actual ' +# `range-diff` should act like `log` with regards to notes +test_expect_success 'range-diff with --notes=custom does not show default notes' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + git notes --ref=custom add -m "topic note" topic && + git notes --ref=custom add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + test_when_finished git notes --ref=custom remove topic unmodified && + git range-diff --notes=custom main..topic main..unmodified \ + >actual && + ! grep "## Notes ##" actual && + grep "## Notes (custom) ##" actual +' + test_expect_success 'format-patch --range-diff does not compare notes by default' ' git notes add -m "topic note" topic && git notes add -m "unmodified note" unmodified && @@ -679,6 +693,20 @@ test_expect_success 'format-patch --range-diff does not compare notes by default ! grep "note" 0000-* ' +test_expect_success 'format-patch --notes=custom --range-diff only compares custom notes' ' + git notes add -m "topic note" topic && + git notes --ref=custom add -m "topic note (custom)" topic && + git notes add -m "unmodified note" unmodified && + git notes --ref=custom add -m "unmodified note (custom)" unmodified && + test_when_finished git notes remove topic unmodified && + test_when_finished git notes --ref=custom remove topic unmodified && + git format-patch --notes=custom --cover-letter --range-diff=$prev \ + main..unmodified >actual && + test_when_finished "rm 000?-*" && + grep "## Notes (custom) ##" 0000-* && + ! grep "## Notes ##" 0000-* +' + test_expect_success 'format-patch --range-diff with --no-notes' ' git notes add -m "topic note" topic && git notes add -m "unmodified note" unmodified && diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh index 028d825e8f..36abdca5ee 100755 --- a/t/t3321-notes-stripspace.sh +++ b/t/t3321-notes-stripspace.sh @@ -5,6 +5,7 @@ test_description='Test commit notes with stripspace behavior' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh MULTI_LF="$LF$LF$LF" diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 3ce918fdb8..d3df19a51f 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -421,7 +421,7 @@ test_expect_success 'refuse to switch to branch checked out elsewhere' ' git checkout main && git worktree add wt && test_must_fail git -C wt rebase main main 2>err && - test_i18ngrep "already checked out" err + test_i18ngrep "already used by worktree at" err ' test_expect_success MINGW,SYMLINKS_WINDOWS 'rebase when .git/logs is a symlink' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 96a56aafbe..8ea2bf1302 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -604,7 +604,8 @@ test_expect_success 'clean error after failed "exec"' ' echo "edited again" > file7 && git add file7 && test_must_fail git rebase --continue 2>error && - test_i18ngrep "you have staged changes in your working tree" error + test_i18ngrep "you have staged changes in your working tree" error && + test_i18ngrep ! "could not open.*for reading" error ' test_expect_success 'rebase a detached HEAD' ' @@ -758,7 +759,7 @@ test_expect_success 'reword' ' git show HEAD~2 | grep "C changed" ' -test_expect_success 'no uncommited changes when rewording the todo list is reloaded' ' +test_expect_success 'no uncommitted changes when rewording and the todo list is reloaded' ' git checkout E && test_when_finished "git checkout @{-1}" && ( @@ -1276,20 +1277,34 @@ test_expect_success 'todo count' ' ' test_expect_success 'rebase -i commits that overwrite untracked files (pick)' ' - git checkout --force branch2 && + git checkout --force A && git clean -f && + cat >todo <<-EOF && + exec >file2 + pick $(git rev-parse B) B + pick $(git rev-parse C) C + pick $(git rev-parse D) D + exec cat .git/rebase-merge/done >actual + EOF ( - set_fake_editor && - FAKE_LINES="edit 1 2" git rebase -i A - ) && - test_cmp_rev HEAD F && - test_path_is_missing file6 && - >file6 && - test_must_fail git rebase --continue && - test_cmp_rev HEAD F && - rm file6 && + set_replace_editor todo && + test_must_fail git rebase -i A + ) && + test_cmp_rev HEAD B && + test_cmp_rev REBASE_HEAD C && + head -n3 todo >expect && + test_cmp expect .git/rebase-merge/done && + rm file2 && + test_path_is_missing .git/rebase-merge/patch && + echo changed >file1 && + git add file1 && + test_must_fail git rebase --continue 2>err && + grep "error: you have staged changes in your working tree" err && + git reset --hard HEAD && git rebase --continue && - test_cmp_rev HEAD I + test_cmp_rev HEAD D && + tail -n3 todo >>expect && + test_cmp expect actual ' test_expect_success 'rebase -i commits that overwrite untracked files (squash)' ' @@ -1305,7 +1320,14 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)' >file6 && test_must_fail git rebase --continue && test_cmp_rev HEAD F && + test_cmp_rev REBASE_HEAD I && rm file6 && + test_path_is_missing .git/rebase-merge/patch && + echo changed >file1 && + git add file1 && + test_must_fail git rebase --continue 2>err && + grep "error: you have staged changes in your working tree" err && + git reset --hard HEAD && git rebase --continue && test $(git cat-file commit HEAD | sed -ne \$p) = I && git reset --hard original-branch2 @@ -1323,7 +1345,14 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' ' >file6 && test_must_fail git rebase --continue && test $(git cat-file commit HEAD | sed -ne \$p) = F && + test_cmp_rev REBASE_HEAD I && rm file6 && + test_path_is_missing .git/rebase-merge/patch && + echo changed >file1 && + git add file1 && + test_must_fail git rebase --continue 2>err && + grep "error: you have staged changes in your working tree" err && + git reset --hard HEAD && git rebase --continue && test $(git cat-file commit HEAD | sed -ne \$p) = I ' diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index fb7b68990c..c4e2fcac67 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -268,6 +268,24 @@ test_expect_success 'the todo command "break" works' ' test_path_is_file execed ' +test_expect_success 'patch file is removed before break command' ' + test_when_finished "git rebase --abort" && + cat >todo <<-\EOF && + pick commit-new-file-F2-on-topic-branch + break + EOF + + ( + set_replace_editor todo && + test_must_fail git rebase -i --onto commit-new-file-F2 HEAD + ) && + test_path_is_file .git/rebase-merge/patch && + echo 22>F2 && + git add F2 && + git rebase --continue && + test_path_is_missing .git/rebase-merge/patch +' + test_expect_success '--reschedule-failed-exec' ' test_when_finished "git rebase --abort" && test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ && diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index 96ae0edf1e..59b5d6b6f2 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -128,14 +128,24 @@ test_expect_success 'generate correct todo list' ' ' test_expect_success '`reset` refuses to overwrite untracked files' ' - git checkout -b refuse-to-reset && + git checkout B && test_commit dont-overwrite-untracked && - git checkout @{-1} && - : >dont-overwrite-untracked.t && - echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch && + cat >script-from-scratch <<-EOF && + exec >dont-overwrite-untracked.t + pick $(git rev-parse B) B + reset refs/tags/dont-overwrite-untracked + pick $(git rev-parse C) C + exec cat .git/rebase-merge/done >actual + EOF test_config sequence.editor \""$PWD"/replace-editor.sh\" && - test_must_fail git rebase -ir HEAD && - git rebase --abort + test_must_fail git rebase -ir A && + test_cmp_rev HEAD B && + head -n3 script-from-scratch >expect && + test_cmp expect .git/rebase-merge/done && + rm dont-overwrite-untracked.t && + git rebase --continue && + tail -n3 script-from-scratch >>expect && + test_cmp expect actual ' test_expect_success '`reset` rejects trees' ' @@ -165,12 +175,16 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' ' test_config sequence.editor \""$PWD"/replace-editor.sh\" && test_tick && test_must_fail git rebase -ir HEAD && + test_cmp_rev REBASE_HEAD H^0 && grep "^merge -C .* G$" .git/rebase-merge/done && grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && - test_path_is_file .git/rebase-merge/patch && + test_path_is_missing .git/rebase-merge/patch && + echo changed >file1 && + git add file1 && + test_must_fail git rebase --continue 2>err && + grep "error: you have staged changes in your working tree" err && : fail because of merge conflict && - rm G.t .git/rebase-merge/patch && git reset --hard conflicting-G && test_must_fail git rebase --continue && ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && @@ -586,4 +600,15 @@ test_expect_success 'progress shows the correct total' ' test_line_count = 14 progress ' +test_expect_success 'truncate label names' ' + commit=$(git commit-tree -p HEAD^ -p HEAD -m "0123456789 我 123" HEAD^{tree}) && + git merge --ff-only $commit && + + done="$(git rev-parse --git-path rebase-merge/done)" && + git -c rebase.maxLabelLength=14 rebase --rebase-merges -x "cp \"$done\" out" --root && + grep "label 0123456789-我$" out && + git -c rebase.maxLabelLength=13 rebase --rebase-merges -x "cp \"$done\" out" --root && + grep "label 0123456789-$" out +' + test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index e2ef619323..4158590322 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -176,6 +176,29 @@ test_expect_success 'advice from failed revert' ' test_cmp expected actual ' +test_expect_subject () { + echo "$1" >expect && + git log -1 --pretty=%s >actual && + test_cmp expect actual +} + +test_expect_success 'titles of fresh reverts' ' + test_commit --no-tag A file1 && + test_commit --no-tag B file1 && + git revert --no-edit HEAD && + test_expect_subject "Revert \"B\"" && + git revert --no-edit HEAD && + test_expect_subject "Reapply \"B\"" && + git revert --no-edit HEAD && + test_expect_subject "Revert \"Reapply \"B\"\"" +' + +test_expect_success 'title of legacy double revert' ' + test_commit --no-tag "Revert \"Revert \"B\"\"" file1 && + git revert --no-edit HEAD && + test_expect_subject "Revert \"Revert \"Revert \"B\"\"\"" +' + test_expect_success 'identification of reverted commit (default)' ' test_commit to-ident && test_when_finished "git reset --hard to-ident" && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 3cf2b7a7fb..0a4ab36c3a 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1373,7 +1373,27 @@ test_expect_success '--rfc' ' Subject: [RFC PATCH 1/1] header with . in it EOF git format-patch -n -1 --stdout --rfc >patch && - grep ^Subject: patch >actual && + grep "^Subject:" patch >actual && + test_cmp expect actual +' + +test_expect_success '--rfc does not overwrite prefix' ' + cat >expect <<-\EOF && + Subject: [RFC PATCH foobar 1/1] header with . in it + EOF + git -c format.subjectPrefix="PATCH foobar" \ + format-patch -n -1 --stdout --rfc >patch && + grep "^Subject:" patch >actual && + test_cmp expect actual +' + +test_expect_success '--rfc is argument order independent' ' + cat >expect <<-\EOF && + Subject: [RFC PATCH foobar 1/1] header with . in it + EOF + git format-patch -n -1 --stdout --rfc \ + --subject-prefix="PATCH foobar" >patch && + grep "^Subject:" patch >actual && test_cmp expect actual ' @@ -1991,6 +2011,20 @@ test_expect_success 'cover letter using branch description (6)' ' grep hello actual ' +test_expect_success 'cover letter with --description-file' ' + test_when_finished "rm -f description.txt" && + cat >description.txt <<-\EOF && + subject from file + + body from file + EOF + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description auto \ + --description-file description.txt main >actual && + grep "^Subject: \[PATCH 0/2\] subject from file$" actual && + grep "^body from file$" actual +' + test_expect_success 'cover letter with nothing' ' git format-patch --stdout --cover-letter >actual && test_line_count = 0 actual diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index b298f220e0..fcd2473e52 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -1,7 +1,7 @@ #!/bin/sh # # Copyright (c) 2006 Johannes E. Schindelin -# +# Copyright (c) 2023 Google LLC test_description='Test special whitespace in diff engine. @@ -11,6 +11,43 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh +for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \ + --raw! --name-only! --name-status! +do + opts=${opt_res%!} expect_failure= + test "$opts" = "$opt_res" || + expect_failure="test_expect_code 1" + + test_expect_success "status with $opts (different)" ' + echo foo >x && + git add x && + echo bar >x && + test_expect_code 1 git diff -w $opts --exit-code x + ' + + test_expect_success POSIXPERM "status with $opts (mode differs)" ' + test_when_finished "git update-index --chmod=-x x" && + echo foo >x && + git add x && + git update-index --chmod=+x x && + test_expect_code 1 git diff -w $opts --exit-code x + ' + + test_expect_success "status with $opts (removing an empty file)" ' + : >x && + git add x && + rm x && + test_expect_code 1 git diff -w $opts --exit-code -- x + ' + + test_expect_success "status with $opts (different but equivalent)" ' + echo foo >x && + git add x && + echo " foo" >x && + $expect_failure git diff -w $opts --exit-code x + ' +done + test_expect_success "Ray Lehtiniemi's example" ' cat <<-\EOF >x && do { diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh index 5bc28ad9f0..f439f469bd 100755 --- a/t/t4017-diff-retval.sh +++ b/t/t4017-diff-retval.sh @@ -138,4 +138,9 @@ test_expect_success 'check honors conflict marker length' ' git reset --hard ' +test_expect_success 'option errors are not confused by --exit-code' ' + test_must_fail git diff --exit-code --nonsense 2>err && + grep '^usage:' err +' + test_done diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh index e70e020ae9..eec3d73dc2 100755 --- a/t/t4040-whitespace-status.sh +++ b/t/t4040-whitespace-status.sh @@ -28,8 +28,7 @@ test_expect_success 'diff-tree --exit-code' ' test_expect_success 'diff-tree -b --exit-code' ' git diff -b --exit-code HEAD^ HEAD && - git diff-tree -b -p --exit-code HEAD^ HEAD && - git diff-tree -b --exit-code HEAD^ HEAD + git diff-tree -b -p --exit-code HEAD^ HEAD ' test_expect_success 'diff-index --cached --exit-code' ' diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh index 3ee27e277d..7badd72488 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh @@ -12,7 +12,7 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh -# 120 character name +# 120-character name name=aaaaaaaaaa name=$name$name$name$name$name$name$name$name$name$name$name$name test_expect_success 'preparation' ' @@ -49,12 +49,41 @@ log -1 --stat EOF cat >expect.60 <<-'EOF' - ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + EOF cat >expect.6030 <<-'EOF' ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + EOF -cat >expect2.60 <<-'EOF' +while read verb expect cmd args +do + # No width limit applied when statNameWidth is ignored + case "$expect" in expect72|expect.6030) + test_expect_success "$cmd $verb diff.statNameWidth with long name" ' + git -c diff.statNameWidth=30 $cmd $args >output && + grep " | " output >actual && + test_cmp $expect actual + ';; + esac + # Maximum width limit still applied when statNameWidth is ignored + case "$expect" in expect.60|expect.6030) + test_expect_success "$cmd --stat=width $verb diff.statNameWidth with long name" ' + git -c diff.statNameWidth=30 $cmd $args --stat=60 >output && + grep " | " output >actual && + test_cmp $expect actual + ';; + esac +done <<\EOF +ignores expect72 format-patch -1 --stdout +ignores expect.60 format-patch -1 --stdout +respects expect.6030 diff HEAD^ HEAD --stat +respects expect.6030 show --stat +respects expect.6030 log -1 --stat +EOF + +cat >expect.40 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +cat >expect2.40 <<-'EOF' ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + EOF @@ -67,22 +96,22 @@ do test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" ' git $cmd $args --stat=40 >output && grep " | " output >actual && - test_cmp $expect.60 actual + test_cmp $expect.40 actual ' test_expect_success "$cmd --stat-width=width with long name" ' git $cmd $args --stat-width=40 >output && grep " | " output >actual && - test_cmp $expect.60 actual + test_cmp $expect.40 actual ' - test_expect_success "$cmd --stat=...,name-width with long name" ' + test_expect_success "$cmd --stat=width,name-width with long name" ' git $cmd $args --stat=60,30 >output && grep " | " output >actual && test_cmp $expect.6030 actual ' - test_expect_success "$cmd --stat-name-width with long name" ' + test_expect_success "$cmd --stat-name-width=width with long name" ' git $cmd $args --stat-name-width=30 >output && grep " | " output >actual && test_cmp $expect.6030 actual @@ -94,8 +123,7 @@ expect show --stat expect log -1 --stat EOF - -test_expect_success 'preparation for big change tests' ' +test_expect_success 'preparation for big-change tests' ' >abcd && git add abcd && git commit -m message && @@ -111,7 +139,7 @@ cat >expect72 <<'EOF' abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF -test_expect_success "format-patch --cover-letter ignores COLUMNS (big change)" ' +test_expect_success "format-patch --cover-letter ignores COLUMNS with big change" ' COLUMNS=200 git format-patch -1 --stdout --cover-letter >output && grep " | " output >actual && test_cmp expect72 actual @@ -131,7 +159,7 @@ cat >expect200-graph <<'EOF' EOF while read verb expect cmd args do - test_expect_success "$cmd $verb COLUMNS (big change)" ' + test_expect_success "$cmd $verb COLUMNS with big change" ' COLUMNS=200 git $cmd $args >output && grep " | " output >actual && test_cmp "$expect" actual @@ -139,7 +167,7 @@ do case "$cmd" in diff|show) continue;; esac - test_expect_success "$cmd --graph $verb COLUMNS (big change)" ' + test_expect_success "$cmd --graph $verb COLUMNS with big change" ' COLUMNS=200 git $cmd $args --graph >output && grep " | " output >actual && test_cmp "$expect-graph" actual @@ -159,7 +187,7 @@ cat >expect40-graph <<'EOF' EOF while read verb expect cmd args do - test_expect_success "$cmd $verb not enough COLUMNS (big change)" ' + test_expect_success "$cmd $verb not enough COLUMNS with big change" ' COLUMNS=40 git $cmd $args >output && grep " | " output >actual && test_cmp "$expect" actual @@ -167,7 +195,7 @@ do case "$cmd" in diff|show) continue;; esac - test_expect_success "$cmd --graph $verb not enough COLUMNS (big change)" ' + test_expect_success "$cmd --graph $verb not enough COLUMNS with big change" ' COLUMNS=40 git $cmd $args --graph >output && grep " | " output >actual && test_cmp "$expect-graph" actual @@ -187,7 +215,7 @@ cat >expect40-graph <<'EOF' EOF while read verb expect cmd args do - test_expect_success "$cmd $verb statGraphWidth config" ' + test_expect_success "$cmd $verb diff.statGraphWidth" ' git -c diff.statGraphWidth=26 $cmd $args >output && grep " | " output >actual && test_cmp "$expect" actual @@ -195,7 +223,7 @@ do case "$cmd" in diff|show) continue;; esac - test_expect_success "$cmd --graph $verb statGraphWidth config" ' + test_expect_success "$cmd --graph $verb diff.statGraphWidth" ' git -c diff.statGraphWidth=26 $cmd $args --graph >output && grep " | " output >actual && test_cmp "$expect-graph" actual @@ -207,7 +235,6 @@ respects expect40 show --stat respects expect40 log -1 --stat EOF - cat >expect <<'EOF' abcd | 1000 ++++++++++++++++++++++++++ EOF @@ -228,7 +255,7 @@ do test_cmp expect actual ' - test_expect_success "$cmd --stat-graph-width with big change" ' + test_expect_success "$cmd --stat-graph-width=width with big change" ' git $cmd $args --stat-graph-width=26 >output && grep " | " output >actual && test_cmp expect actual @@ -242,7 +269,7 @@ do test_cmp expect-graph actual ' - test_expect_success "$cmd --stat-graph-width --graph with big change" ' + test_expect_success "$cmd --stat-graph-width=width --graph with big change" ' git $cmd $args --stat-graph-width=26 --graph >output && grep " | " output >actual && test_cmp expect-graph actual @@ -254,7 +281,7 @@ show --stat log -1 --stat EOF -test_expect_success 'preparation for long filename tests' ' +test_expect_success 'preparation for long-name tests' ' cp abcd aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && git add aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && git commit -m message @@ -302,7 +329,7 @@ cat >expect200-graph <<'EOF' EOF while read verb expect cmd args do - test_expect_success "$cmd $verb COLUMNS (long filename)" ' + test_expect_success "$cmd $verb COLUMNS with long name" ' COLUMNS=200 git $cmd $args >output && grep " | " output >actual && test_cmp "$expect" actual @@ -310,7 +337,7 @@ do case "$cmd" in diff|show) continue;; esac - test_expect_success "$cmd --graph $verb COLUMNS (long filename)" ' + test_expect_success "$cmd --graph $verb COLUMNS with long name" ' COLUMNS=200 git $cmd $args --graph >output && grep " | " output >actual && test_cmp "$expect-graph" actual @@ -331,7 +358,7 @@ EOF while read verb expect cmd args do test_expect_success COLUMNS_CAN_BE_1 \ - "$cmd $verb prefix greater than COLUMNS (big change)" ' + "$cmd $verb prefix greater than COLUMNS with big change" ' COLUMNS=1 git $cmd $args >output && grep " | " output >actual && test_cmp "$expect" actual @@ -340,7 +367,7 @@ do case "$cmd" in diff|show) continue;; esac test_expect_success COLUMNS_CAN_BE_1 \ - "$cmd --graph $verb prefix greater than COLUMNS (big change)" ' + "$cmd --graph $verb prefix greater than COLUMNS with big change" ' COLUMNS=1 git $cmd $args --graph >output && grep " | " output >actual && test_cmp "$expect-graph" actual @@ -355,8 +382,14 @@ EOF cat >expect <<'EOF' abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF -test_expect_success 'merge --stat respects COLUMNS (big change)' ' - git checkout -b branch HEAD^^ && +test_expect_success 'merge --stat respects diff.statGraphWidth with big change' ' + git checkout -b branch1 HEAD^^ && + git -c diff.statGraphWidth=26 merge --stat --no-ff main^ >output && + grep " | " output >actual && + test_cmp expect40 actual +' +test_expect_success 'merge --stat respects COLUMNS with big change' ' + git checkout -b branch2 HEAD^^ && COLUMNS=100 git merge --stat --no-ff main^ >output && grep " | " output >actual && test_cmp expect actual @@ -365,7 +398,17 @@ test_expect_success 'merge --stat respects COLUMNS (big change)' ' cat >expect <<'EOF' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++ EOF -test_expect_success 'merge --stat respects COLUMNS (long filename)' ' +cat >expect.30 <<'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++++++++++++++++++++++ +EOF +test_expect_success 'merge --stat respects diff.statNameWidth with long name' ' + git switch branch1 && + git -c diff.statNameWidth=30 merge --stat --no-ff main >output && + grep " | " output >actual && + test_cmp expect.30 actual +' +test_expect_success 'merge --stat respects COLUMNS with long name' ' + git switch branch2 && COLUMNS=100 git merge --stat --no-ff main >output && grep " | " output >actual && test_cmp expect actual diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 6781cc9078..5f059f65fc 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -224,6 +224,25 @@ test_expect_success "diff --no-index treats '-' as stdin" ' test_must_be_empty actual ' +test_expect_success "diff --no-index -R treats '-' as stdin" ' + cat >expect <<-EOF && + diff --git b/a/1 a/- + index $(git hash-object --stdin <a/1)..$ZERO_OID 100644 + --- b/a/1 + +++ a/- + @@ -1 +1 @@ + -1 + +x + EOF + + test_write_lines x | test_expect_code 1 \ + git -c core.abbrev=no diff --no-index -R -- - a/1 >actual && + test_cmp expect actual && + + test_write_lines 1 | git diff --no-index -R -- a/1 - >actual && + test_must_be_empty actual +' + test_expect_success 'diff --no-index refuses to diff stdin and a directory' ' test_must_fail git diff --no-index -- - a </dev/null 2>err && grep "fatal: cannot compare stdin to a directory" err diff --git a/t/t4068-diff-symmetric-merge-base.sh b/t/t4068-diff-symmetric-merge-base.sh index 2d650d8f10..541642650f 100755 --- a/t/t4068-diff-symmetric-merge-base.sh +++ b/t/t4068-diff-symmetric-merge-base.sh @@ -34,7 +34,7 @@ test_expect_success setup ' echo c >c && git add c && git commit -m C && - git tag commit-C && + git tag -m commit-C commit-C && git merge -m D main && git tag commit-D && git checkout main && @@ -109,6 +109,13 @@ do test_cmp expect actual ' + test_expect_success "$cmd --merge-base with annotated tag" ' + git checkout main && + git $cmd commit-C >expect && + git $cmd --merge-base commit-C >actual && + test_cmp expect actual + ' + test_expect_success "$cmd --merge-base with one commit and unstaged changes" ' git checkout main && test_when_finished git reset --hard && @@ -143,7 +150,7 @@ do test_expect_success "$cmd --merge-base with non-commit" ' git checkout main && test_must_fail git $cmd --merge-base main^{tree} 2>err && - test_i18ngrep "fatal: --merge-base only works with commits" err + test_i18ngrep "is a tree, not a commit" err ' test_expect_success "$cmd --merge-base with no merge bases and one commit" ' @@ -169,7 +176,7 @@ do test_expect_success "$cmd --merge-base commit and non-commit" ' test_must_fail git $cmd --merge-base br2 main^{tree} 2>err && - test_i18ngrep "fatal: --merge-base only works with commits" err + test_i18ngrep "is a tree, not a commit" err ' test_expect_success "$cmd --merge-base with no merge bases and two commits" ' diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index dd9035aa38..16626e4fe9 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -576,6 +576,38 @@ test_expect_success 'clean log decoration' ' test_cmp expected actual1 ' +test_expect_success 'pretty format %decorate' ' + git checkout -b foo && + git commit --allow-empty -m "new commit" && + git tag bar && + git branch qux && + + echo " (HEAD -> foo, tag: bar, qux)" >expect1 && + git log --format="%(decorate)" -1 >actual1 && + test_cmp expect1 actual1 && + + echo "HEAD -> foo, tag: bar, qux" >expect2 && + git log --format="%(decorate:prefix=,suffix=)" -1 >actual2 && + test_cmp expect2 actual2 && + + echo "[ HEAD -> foo; tag: bar; qux ]" >expect3 && + git log --format="%(decorate:prefix=[ ,suffix= ],separator=%x3B )" \ + -1 >actual3 && + test_cmp expect3 actual3 && + + # Try with a typo (in "separator"), in which case the placeholder should + # not be replaced. + echo "%(decorate:prefix=[ ,suffix= ],separater=; )" >expect4 && + git log --format="%(decorate:prefix=[ ,suffix= ],separater=%x3B )" \ + -1 >actual4 && + test_cmp expect4 actual4 && + + echo "HEAD->foo bar qux" >expect5 && + git log --format="%(decorate:prefix=,suffix=,separator= ,tag=,pointer=->)" \ + -1 >actual5 && + test_cmp expect5 actual5 +' + cat >trailers <<EOF Signed-off-by: A U Thor <author@example.com> Acked-by: A U Thor <author@example.com> diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index ded33a82e2..21986a866d 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -53,15 +53,17 @@ cmp_filtered_decorations () { # to this test since it does not contain any decoration, hence --first-parent test_expect_success 'commit decorations colored correctly' ' cat >expect <<-EOF && - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ -${c_reset}${c_branch}main${c_reset}${c_commit}, \ -${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \ -${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit}, \ + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\ +${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B +${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \ ${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1 - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset} \ -On main: Changes to A.t - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A EOF git log --first-parent --no-abbrev --decorate --oneline --color=always --all >actual && @@ -76,12 +78,14 @@ test_expect_success 'test coloring with replace-objects' ' git replace HEAD~1 HEAD~2 && cat >expect <<-EOF && - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ -${c_reset}${c_branch}main${c_reset}${c_commit}, \ -${c_reset}${c_tag}tag: D${c_reset}${c_commit})${c_reset} D - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: C${c_reset}${c_commit}, \ + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\ +${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit})${c_reset} D + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}C${c_reset}${c_commit}, \ ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} B - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A EOF git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual && @@ -100,13 +104,15 @@ test_expect_success 'test coloring with grafted commit' ' git replace --graft HEAD HEAD~2 && cat >expect <<-EOF && - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ -${c_reset}${c_branch}main${c_reset}${c_commit}, \ -${c_reset}${c_tag}tag: D${c_reset}${c_commit}, \ + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}\ +${c_commit} -> ${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: ${c_reset}${c_tag}D${c_reset}${c_commit}, \ ${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} D - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \ -${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B - ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\ +${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A EOF git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual && diff --git a/t/t4214-log-graph-octopus.sh b/t/t4214-log-graph-octopus.sh index f70c46bbbf..7905597869 100755 --- a/t/t4214-log-graph-octopus.sh +++ b/t/t4214-log-graph-octopus.sh @@ -5,6 +5,7 @@ test_description='git log --graph of skewed left octopus merge.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh index 28d0779a8c..b877ac7235 100755 --- a/t/t4215-log-skewed-merges.sh +++ b/t/t4215-log-skewed-merges.sh @@ -2,6 +2,7 @@ test_description='git log --graph of skewed merges' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-log-graph.sh diff --git a/t/t4217-log-limit.sh b/t/t4217-log-limit.sh index 6e01e2629c..613f0710e9 100755 --- a/t/t4217-log-limit.sh +++ b/t/t4217-log-limit.sh @@ -2,6 +2,7 @@ test_description='git log with filter options limiting the output' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup test' ' diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index b26d476c64..2ff3eef9a3 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -53,6 +53,14 @@ test_expect_success 'verify blob:none packfile has no blobs' ' ! grep blob verify_result ' +test_expect_success 'verify blob:none packfile without --stdout' ' + git -C r1 pack-objects --revs --filter=blob:none mypackname >packhash <<-EOF && + HEAD + EOF + git -C r1 verify-pack -v "mypackname-$(cat packhash).pack" >verify_result && + ! grep blob verify_result +' + test_expect_success 'verify normal and blob:none packfiles have same commits/trees' ' git -C r1 verify-pack -v ../all.pack >verify_result && grep -E "commit|tree" verify_result | diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 4df76173a8..ba65f17dd9 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -450,14 +450,15 @@ GRAPH_BYTE_FANOUT2=$(($GRAPH_FANOUT_OFFSET + 4 * 255)) GRAPH_OID_LOOKUP_OFFSET=$(($GRAPH_FANOUT_OFFSET + 4 * 256)) GRAPH_BYTE_OID_LOOKUP_ORDER=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 8)) GRAPH_BYTE_OID_LOOKUP_MISSING=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 4 + 10)) +GRAPH_COMMIT_DATA_WIDTH=$(($HASH_LEN + 16)) GRAPH_COMMIT_DATA_OFFSET=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * $NUM_COMMITS)) GRAPH_BYTE_COMMIT_TREE=$GRAPH_COMMIT_DATA_OFFSET GRAPH_BYTE_COMMIT_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN)) GRAPH_BYTE_COMMIT_EXTRA_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 4)) GRAPH_BYTE_COMMIT_WRONG_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 3)) GRAPH_BYTE_COMMIT_GENERATION=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 11)) +GRAPH_BYTE_COMMIT_GENERATION_LAST=$(($GRAPH_BYTE_COMMIT_GENERATION + $(($NUM_COMMITS - 1)) * $GRAPH_COMMIT_DATA_WIDTH)) GRAPH_BYTE_COMMIT_DATE=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 12)) -GRAPH_COMMIT_DATA_WIDTH=$(($HASH_LEN + 16)) GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \ $GRAPH_COMMIT_DATA_WIDTH * $NUM_COMMITS)) GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4)) @@ -596,11 +597,6 @@ test_expect_success 'detect incorrect generation number' ' "generation for commit" ' -test_expect_success 'detect incorrect generation number' ' - corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\01" \ - "commit-graph generation for commit" -' - test_expect_success 'detect incorrect commit date' ' corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_DATE "\01" \ "commit date" @@ -622,6 +618,16 @@ test_expect_success 'detect incorrect chunk count' ' $GRAPH_CHUNK_LOOKUP_OFFSET ' +test_expect_success 'detect mixed generation numbers (non-zero to zero)' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION_LAST "\0\0\0\0" \ + "both zero and non-zero generations" +' + +test_expect_success 'detect mixed generation numbers (zero to non-zero)' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\0\0\0\0" \ + "both zero and non-zero generations" +' + test_expect_success 'git fsck (checks commit-graph when config set to true)' ' git -C full fsck && corrupt_graph_and_verify $GRAPH_BYTE_FOOTER "\00" \ diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh index 36c4141e67..8a9720dcb0 100755 --- a/t/t5324-split-commit-graph.sh +++ b/t/t5324-split-commit-graph.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='split commit graph' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh GIT_TEST_COMMIT_GRAPH=0 @@ -285,6 +287,32 @@ test_expect_success 'verify hashes along chain, even in shallow' ' ) ' +test_expect_success 'verify notices chain slice which is bogus (base)' ' + git clone --no-hardlinks . verify-chain-bogus-base && + ( + cd verify-chain-bogus-base && + git commit-graph verify && + base_file=$graphdir/graph-$(sed -n 1p $graphdir/commit-graph-chain).graph && + echo "garbage" >$base_file && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + grep "commit-graph file is too small" err + ) +' + +test_expect_success 'verify notices chain slice which is bogus (tip)' ' + git clone --no-hardlinks . verify-chain-bogus-tip && + ( + cd verify-chain-bogus-tip && + git commit-graph verify && + tip_file=$graphdir/graph-$(sed -n 2p $graphdir/commit-graph-chain).graph && + echo "garbage" >$tip_file && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + grep "commit-graph file is too small" err + ) +' + test_expect_success 'verify --shallow does not check base contents' ' git clone --no-hardlinks . verify-shallow && ( @@ -306,27 +334,54 @@ test_expect_success 'warn on base graph chunk incorrect' ' git commit-graph verify && base_file=$graphdir/graph-$(tail -n 1 $graphdir/commit-graph-chain).graph && corrupt_file "$base_file" $(test_oid base) "\01" && - git commit-graph verify --shallow 2>test_err && + test_must_fail git commit-graph verify --shallow 2>test_err && grep -v "^+" test_err >err && test_i18ngrep "commit-graph chain does not match" err ) ' -test_expect_success 'verify after commit-graph-chain corruption' ' - git clone --no-hardlinks . verify-chain && +test_expect_success 'verify after commit-graph-chain corruption (base)' ' + git clone --no-hardlinks . verify-chain-base && ( - cd verify-chain && - corrupt_file "$graphdir/commit-graph-chain" 60 "G" && - git commit-graph verify 2>test_err && + cd verify-chain-base && + corrupt_file "$graphdir/commit-graph-chain" 30 "G" && + test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_i18ngrep "invalid commit-graph chain" err && - corrupt_file "$graphdir/commit-graph-chain" 60 "A" && - git commit-graph verify 2>test_err && + corrupt_file "$graphdir/commit-graph-chain" 30 "A" && + test_must_fail git commit-graph verify 2>test_err && grep -v "^+" test_err >err && test_i18ngrep "unable to find all commit-graph files" err ) ' +test_expect_success 'verify after commit-graph-chain corruption (tip)' ' + git clone --no-hardlinks . verify-chain-tip && + ( + cd verify-chain-tip && + corrupt_file "$graphdir/commit-graph-chain" 70 "G" && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + test_i18ngrep "invalid commit-graph chain" err && + corrupt_file "$graphdir/commit-graph-chain" 70 "A" && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + test_i18ngrep "unable to find all commit-graph files" err + ) +' + +test_expect_success 'verify notices too-short chain file' ' + git clone --no-hardlinks . verify-chain-short && + ( + cd verify-chain-short && + git commit-graph verify && + echo "garbage" >$graphdir/commit-graph-chain && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + grep "commit-graph chain file too small" err + ) +' + test_expect_success 'verify across alternates' ' git clone --no-hardlinks . verify-alt && ( diff --git a/t/t5328-commit-graph-64bit-time.sh b/t/t5328-commit-graph-64bit-time.sh index e9c521c061..ca476e80a0 100755 --- a/t/t5328-commit-graph-64bit-time.sh +++ b/t/t5328-commit-graph-64bit-time.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='commit graph with 64-bit timestamps' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh if ! test_have_prereq TIME_IS_64BIT || ! test_have_prereq TIME_T_IS_64BIT diff --git a/t/t5329-pack-objects-cruft.sh b/t/t5329-pack-objects-cruft.sh index 45667d4999..fc5fedbe9b 100755 --- a/t/t5329-pack-objects-cruft.sh +++ b/t/t5329-pack-objects-cruft.sh @@ -573,23 +573,54 @@ test_expect_success 'cruft repack with no reachable objects' ' ) ' -test_expect_success 'cruft repack ignores --max-pack-size' ' +write_blob () { + test-tool genrandom "$@" >in && + git hash-object -w -t blob in +} + +find_pack () { + for idx in $(ls $packdir/pack-*.idx) + do + git show-index <$idx >out && + if grep -q "$1" out + then + echo $idx + fi || return 1 + done +} + +test_expect_success 'cruft repack with --max-pack-size' ' git init max-pack-size && ( cd max-pack-size && test_commit base && + # two cruft objects which exceed the maximum pack size - test-tool genrandom foo 1048576 | git hash-object --stdin -w && - test-tool genrandom bar 1048576 | git hash-object --stdin -w && + foo=$(write_blob foo 1048576) && + bar=$(write_blob bar 1048576) && + test-tool chmtime --get -1000 \ + "$objdir/$(test_oid_to_path $foo)" >foo.mtime && + test-tool chmtime --get -2000 \ + "$objdir/$(test_oid_to_path $bar)" >bar.mtime && git repack --cruft --max-pack-size=1M && find $packdir -name "*.mtimes" >cruft && - test_line_count = 1 cruft && - test-tool pack-mtimes "$(basename "$(cat cruft)")" >objects && - test_line_count = 2 objects + test_line_count = 2 cruft && + + foo_mtimes="$(basename $(find_pack $foo) .idx).mtimes" && + bar_mtimes="$(basename $(find_pack $bar) .idx).mtimes" && + test-tool pack-mtimes $foo_mtimes >foo.actual && + test-tool pack-mtimes $bar_mtimes >bar.actual && + + echo "$foo $(cat foo.mtime)" >foo.expect && + echo "$bar $(cat bar.mtime)" >bar.expect && + + test_cmp foo.expect foo.actual && + test_cmp bar.expect bar.actual && + test "$foo_mtimes" != "$bar_mtimes" ) ' -test_expect_success 'cruft repack ignores pack.packSizeLimit' ' +test_expect_success 'cruft repack with pack.packSizeLimit' ' ( cd max-pack-size && # repack everything back together to remove the existing cruft @@ -599,9 +630,12 @@ test_expect_success 'cruft repack ignores pack.packSizeLimit' ' # ensure the same post condition is met when --max-pack-size # would otherwise be inferred from the configuration find $packdir -name "*.mtimes" >cruft && - test_line_count = 1 cruft && - test-tool pack-mtimes "$(basename "$(cat cruft)")" >objects && - test_line_count = 2 objects + test_line_count = 2 cruft && + for pack in $(cat cruft) + do + test-tool pack-mtimes "$(basename $pack)" >objects && + test_line_count = 1 objects || return 1 + done ) ' diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 5f3ff051ca..ad7f8c6f00 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -17,6 +17,12 @@ test_expect_success 'setup' ' git checkout A^0 && test_commit E bar E && test_commit F foo F && + git checkout B && + git merge E && + git tag merge-E && + test_commit G G && + test_commit H H && + test_commit I I && git checkout main && test_hook --setup post-rewrite <<-EOF @@ -173,6 +179,48 @@ test_fail_interactive_rebase () { ) } +test_expect_success 'git rebase with failed pick' ' + clear_hook_input && + cat >todo <<-\EOF && + exec >bar + merge -C merge-E E + exec >G + pick G + exec >H 2>I + pick H + fixup I + EOF + + ( + set_replace_editor todo && + test_must_fail git rebase -i D D 2>err + ) && + grep "would be overwritten" err && + rm bar && + + test_must_fail git rebase --continue 2>err && + grep "would be overwritten" err && + rm G && + + test_must_fail git rebase --continue 2>err && + grep "would be overwritten" err && + rm H && + + test_must_fail git rebase --continue 2>err && + grep "would be overwritten" err && + rm I && + + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<-EOF && + $(git rev-parse merge-E) $(git rev-parse HEAD~2) + $(git rev-parse G) $(git rev-parse HEAD~1) + $(git rev-parse H) $(git rev-parse HEAD) + $(git rev-parse I) $(git rev-parse HEAD) + EOF + verify_hook_input +' + test_expect_success 'git rebase -i (unchanged)' ' git reset --hard D && clear_hook_input && diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 4f289063ce..19c36b57f4 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -1127,6 +1127,52 @@ do ' done +test_expect_success 'prepare source branch' ' + echo one >onebranch && + git checkout --orphan onebranch && + git rm --cached -r . && + git add onebranch && + git commit -m onebranch && + git rev-list --objects onebranch -- >actual && + # 3 objects should be created, at least ... + test 3 -le $(wc -l <actual) +' + +validate_store_type () { + git -C dest count-objects -v >actual && + case "$store_type" in + packed) + grep "^count: 0$" actual ;; + loose) + grep "^packs: 0$" actual ;; + esac || { + echo "store_type is $store_type" + cat actual + false + } +} + +test_unpack_limit () { + store_type=$1 + + case "$store_type" in + packed) fetch_limit=1 transfer_limit=10000 ;; + loose) fetch_limit=10000 transfer_limit=1 ;; + esac + + test_expect_success "fetch trumps transfer limit" ' + rm -fr dest && + git --bare init dest && + git -C dest config fetch.unpacklimit $fetch_limit && + git -C dest config transfer.unpacklimit $transfer_limit && + git -C dest fetch .. onebranch && + validate_store_type + ' +} + +test_unpack_limit packed +test_unpack_limit loose + setup_negotiation_tip () { SERVER="$1" URL="$2" diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index 264de29c35..079b2f2536 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -5,6 +5,7 @@ test_description='pull options' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh index eed3c9d81a..9fc9ba552f 100755 --- a/t/t5546-receive-limits.sh +++ b/t/t5546-receive-limits.sh @@ -9,10 +9,26 @@ TEST_PASSES_SANITIZE_LEAK=true # When the limit is 1, `git receive-pack` will call `git index-pack`. # When the limit is 10000, `git receive-pack` will call `git unpack-objects`. +validate_store_type () { + git -C dest count-objects -v >actual && + case "$store_type" in + index) + grep "^count: 0$" actual ;; + unpack) + grep "^packs: 0$" actual ;; + esac || { + echo "store_type is $store_type" + cat actual + false; + } +} + test_pack_input_limit () { - case "$1" in - index) unpack_limit=1 ;; - unpack) unpack_limit=10000 ;; + store_type=$1 + + case "$store_type" in + index) unpack_limit=1 other_limit=10000 ;; + unpack) unpack_limit=10000 other_limit=1 ;; esac test_expect_success 'prepare destination repository' ' @@ -43,6 +59,19 @@ test_pack_input_limit () { git --git-dir=dest config receive.maxInputSize 0 && git push dest HEAD ' + + test_expect_success 'prepare destination repository (once more)' ' + rm -fr dest && + git --bare init dest + ' + + test_expect_success 'receive trumps transfer' ' + git --git-dir=dest config receive.unpacklimit "$unpack_limit" && + git --git-dir=dest config transfer.unpacklimit "$other_limit" && + git push dest HEAD && + validate_store_type + ' + } test_expect_success "create known-size (1024 bytes) commit" ' diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh index a11b20e378..448134c4bf 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -4,6 +4,7 @@ test_description='check pre-push hooks' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t5583-push-branches.sh b/t/t5583-push-branches.sh index e7e1b6dab6..320f49c753 100755 --- a/t/t5583-push-branches.sh +++ b/t/t5583-push-branches.sh @@ -5,6 +5,7 @@ test_description='check the consisitency of behavior of --all and --branches' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh delete_refs() { diff --git a/t/t5811-proto-disable-git.sh b/t/t5811-proto-disable-git.sh index 8ac6b2a1d0..ed773e7432 100755 --- a/t/t5811-proto-disable-git.sh +++ b/t/t5811-proto-disable-git.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test disabling of git-over-tcp in clone/fetch' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY/lib-proto-disable.sh" . "$TEST_DIRECTORY/lib-git-daemon.sh" diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 5a67bbc760..ced40157ed 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -5,6 +5,7 @@ test_description='ancestor culling and limiting by parent number' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_revlist () { diff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh index a57f1ae2ba..4821b90e74 100755 --- a/t/t6017-rev-list-stdin.sh +++ b/t/t6017-rev-list-stdin.sh @@ -68,6 +68,7 @@ check --glob=refs/heads check --glob=refs/heads -- check --glob=refs/heads -- file-1 check --end-of-options -dashed-branch +check --all --not refs/heads/main test_expect_success 'not only --stdin' ' cat >expect <<-EOF && @@ -127,4 +128,24 @@ test_expect_success 'unknown option without --end-of-options' ' test_cmp expect error ' +test_expect_success '--not on command line does not influence revisions read via --stdin' ' + cat >input <<-EOF && + refs/heads/main + EOF + git rev-list refs/heads/main >expect && + + git rev-list refs/heads/main --not --stdin <input >actual && + test_cmp expect actual +' + +test_expect_success '--not via stdin does not influence revisions from command line' ' + cat >input <<-EOF && + --not + EOF + git rev-list refs/heads/main >expect && + + git rev-list refs/heads/main --stdin refs/heads/main <input >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 5b434ab451..00a060df0b 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -25,6 +25,13 @@ test_expect_success setup ' disklen sha1:138 disklen sha256:154 EOF + + # setup .mailmap + cat >.mailmap <<-EOF && + A Thor <athor@example.com> A U Thor <author@example.com> + C Mitter <cmitter@example.com> C O Mitter <committer@example.com> + EOF + setdate_and_increment && echo "Using $datestamp" > one && git add one && @@ -41,25 +48,29 @@ test_expect_success setup ' git config push.default current ' -test_atom() { +test_atom () { case "$1" in head) ref=refs/heads/main ;; tag) ref=refs/tags/testtag ;; sym) ref=refs/heads/sym ;; *) ref=$1 ;; esac + format=$2 + test_do=test_expect_${4:-success} + printf '%s\n' "$3" >expected - test_expect_${4:-success} $PREREQ "basic atom: $1 $2" " - git for-each-ref --format='%($2)' $ref >actual && + $test_do $PREREQ "basic atom: $ref $format" ' + git for-each-ref --format="%($format)" "$ref" >actual && sanitize_pgp <actual >actual.clean && test_cmp expected actual.clean - " + ' + # Automatically test "contents:size" atom after testing "contents" - if test "$2" = "contents" + if test "$format" = "contents" then # for commit leg, $3 is changed there expect=$(printf '%s' "$3" | wc -c) - test_expect_${4:-success} $PREREQ "basic atom: $1 contents:size" ' + $test_do $PREREQ "basic atom: $ref contents:size" ' type=$(git cat-file -t "$ref") && case $type in tag) @@ -141,15 +152,31 @@ test_atom head '*objectname' '' test_atom head '*objecttype' '' test_atom head author 'A U Thor <author@example.com> 1151968724 +0200' test_atom head authorname 'A U Thor' +test_atom head authorname:mailmap 'A Thor' test_atom head authoremail '<author@example.com>' test_atom head authoremail:trim 'author@example.com' test_atom head authoremail:localpart 'author' +test_atom head authoremail:trim,localpart 'author' +test_atom head authoremail:mailmap '<athor@example.com>' +test_atom head authoremail:mailmap,trim 'athor@example.com' +test_atom head authoremail:trim,mailmap 'athor@example.com' +test_atom head authoremail:mailmap,localpart 'athor' +test_atom head authoremail:localpart,mailmap 'athor' +test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor' test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200' test_atom head committername 'C O Mitter' +test_atom head committername:mailmap 'C Mitter' test_atom head committeremail '<committer@example.com>' test_atom head committeremail:trim 'committer@example.com' test_atom head committeremail:localpart 'committer' +test_atom head committeremail:localpart,trim 'committer' +test_atom head committeremail:mailmap '<cmitter@example.com>' +test_atom head committeremail:mailmap,trim 'cmitter@example.com' +test_atom head committeremail:trim,mailmap 'cmitter@example.com' +test_atom head committeremail:mailmap,localpart 'cmitter' +test_atom head committeremail:localpart,mailmap 'cmitter' +test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter' test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head tag '' test_atom head tagger '' @@ -199,22 +226,46 @@ test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{}) test_atom tag '*objecttype' 'commit' test_atom tag author '' test_atom tag authorname '' +test_atom tag authorname:mailmap '' test_atom tag authoremail '' test_atom tag authoremail:trim '' test_atom tag authoremail:localpart '' +test_atom tag authoremail:trim,localpart '' +test_atom tag authoremail:mailmap '' +test_atom tag authoremail:mailmap,trim '' +test_atom tag authoremail:trim,mailmap '' +test_atom tag authoremail:mailmap,localpart '' +test_atom tag authoremail:localpart,mailmap '' +test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim '' test_atom tag authordate '' test_atom tag committer '' test_atom tag committername '' +test_atom tag committername:mailmap '' test_atom tag committeremail '' test_atom tag committeremail:trim '' test_atom tag committeremail:localpart '' +test_atom tag committeremail:localpart,trim '' +test_atom tag committeremail:mailmap '' +test_atom tag committeremail:mailmap,trim '' +test_atom tag committeremail:trim,mailmap '' +test_atom tag committeremail:mailmap,localpart '' +test_atom tag committeremail:localpart,mailmap '' +test_atom tag committeremail:trim,mailmap,trim,trim,localpart '' test_atom tag committerdate '' test_atom tag tag 'testtag' test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200' test_atom tag taggername 'C O Mitter' +test_atom tag taggername:mailmap 'C Mitter' test_atom tag taggeremail '<committer@example.com>' test_atom tag taggeremail:trim 'committer@example.com' test_atom tag taggeremail:localpart 'committer' +test_atom tag taggeremail:trim,localpart 'committer' +test_atom tag taggeremail:mailmap '<cmitter@example.com>' +test_atom tag taggeremail:mailmap,trim 'cmitter@example.com' +test_atom tag taggeremail:trim,mailmap 'cmitter@example.com' +test_atom tag taggeremail:mailmap,localpart 'cmitter' +test_atom tag taggeremail:localpart,mailmap 'cmitter' +test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter' test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200' test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' @@ -267,6 +318,66 @@ test_expect_success 'arguments to %(objectname:short=) must be positive integers test_must_fail git for-each-ref --format="%(objectname:short=foo)" ' +test_bad_atom () { + case "$1" in + head) ref=refs/heads/main ;; + tag) ref=refs/tags/testtag ;; + sym) ref=refs/heads/sym ;; + *) ref=$1 ;; + esac + format=$2 + test_do=test_expect_${4:-success} + + printf '%s\n' "$3" >expect + $test_do $PREREQ "err basic atom: $ref $format" ' + test_must_fail git for-each-ref \ + --format="%($format)" "$ref" 2>error && + test_cmp expect error + ' +} + +test_bad_atom head 'authoremail:foo' \ + 'fatal: unrecognized %(authoremail) argument: foo' + +test_bad_atom head 'authoremail:mailmap,trim,bar' \ + 'fatal: unrecognized %(authoremail) argument: bar' + +test_bad_atom head 'authoremail:trim,' \ + 'fatal: unrecognized %(authoremail) argument: ' + +test_bad_atom head 'authoremail:mailmaptrim' \ + 'fatal: unrecognized %(authoremail) argument: trim' + +test_bad_atom head 'committeremail: ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail: trim,foo' \ + 'fatal: unrecognized %(committeremail) argument: trim,foo' + +test_bad_atom head 'committeremail:mailmap,localpart ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail:trim_localpart' \ + 'fatal: unrecognized %(committeremail) argument: _localpart' + +test_bad_atom head 'committeremail:localpart,,,trim' \ + 'fatal: unrecognized %(committeremail) argument: ,,trim' + +test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \ + 'fatal: unrecognized %(taggeremail) argument: foo ' + +test_bad_atom tag 'taggeremail:trim,localpart,' \ + 'fatal: unrecognized %(taggeremail) argument: ' + +test_bad_atom tag 'taggeremail:mailmap;localpart trim' \ + 'fatal: unrecognized %(taggeremail) argument: ;localpart trim' + +test_bad_atom tag 'taggeremail:localpart trim' \ + 'fatal: unrecognized %(taggeremail) argument: trim' + +test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \ + 'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim' + test_date () { f=$1 && committer_date=$2 && @@ -1017,16 +1128,16 @@ test_expect_success 'Verify sorts with raw' ' test_expect_success 'Verify sorts with raw:size' ' cat >expected <<-EOF && refs/myblobs/blob8 - refs/myblobs/first refs/myblobs/blob7 - refs/heads/main refs/myblobs/blob4 refs/myblobs/blob1 refs/myblobs/blob2 refs/myblobs/blob3 refs/myblobs/blob5 refs/myblobs/blob6 + refs/myblobs/first refs/mytrees/first + refs/heads/main EOF git for-each-ref --format="%(refname)" --sort=raw:size \ refs/heads/main refs/myblobs/ refs/mytrees/first >actual && @@ -1138,6 +1249,17 @@ test_expect_success 'for-each-ref --format compare with cat-file --batch' ' test_cmp expected actual ' +test_expect_success 'verify sorts with contents:size' ' + cat >expect <<-\EOF && + refs/heads/main + refs/heads/newtag + refs/heads/ambiguous + EOF + git for-each-ref --format="%(refname)" \ + --sort=contents:size refs/heads/ >actual && + test_cmp expect actual +' + test_expect_success 'set up multiple-sort tags' ' for when in 100000 200000 do @@ -1763,10 +1885,7 @@ test_expect_success GPGSSH 'setup for signature atom using ssh' ' ' test_expect_success GPG2 'bare signature atom' ' - git verify-commit first-signed 2>out.raw && - grep -Ev "checking the trustdb|PGP trust model" out.raw >out && - head -3 out >expect && - tail -1 out >>expect && + git verify-commit first-signed 2>expect && echo >>expect && git for-each-ref refs/tags/first-signed \ --format="%(signature)" >actual && diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 9677180a5b..72f8c1722f 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -179,7 +179,8 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal' >./please-abort && echo "* merge=custom" >.gitattributes && - test_must_fail git merge main && + test_must_fail git merge main 2>err && + grep "^error: failed to execute internal merge" err && git ls-files -u >output && git diff --name-only HEAD >>output && test_must_be_empty output diff --git a/t/t6416-recursive-corner-cases.sh b/t/t6416-recursive-corner-cases.sh index 17b54d625d..5f414abc89 100755 --- a/t/t6416-recursive-corner-cases.sh +++ b/t/t6416-recursive-corner-cases.sh @@ -5,6 +5,7 @@ test_description='recursive merge corner cases involving criss-cross merges' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh diff --git a/t/t6433-merge-toplevel.sh b/t/t6433-merge-toplevel.sh index b16031465f..2b42f095dc 100755 --- a/t/t6433-merge-toplevel.sh +++ b/t/t6433-merge-toplevel.sh @@ -5,6 +5,7 @@ test_description='"git merge" top-level frontend' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh t3033_reset () { diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c9a86f2e94..daa507862c 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -8,6 +8,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 69509d0c11..04acf22d93 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -202,6 +202,30 @@ test_expect_success 'one of gc.reflogExpire{Unreachable,}=never does not skip "e grep -E "^trace: (built-in|exec|run_command): git reflog expire --" trace.out ' +test_expect_success 'gc.repackFilter launches repack with a filter' ' + git clone --no-local --bare . bare.git && + + git -C bare.git -c gc.cruftPacks=false gc && + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + + GIT_TRACE=$(pwd)/trace.out git -C bare.git -c gc.repackFilter=blob:none \ + -c repack.writeBitmaps=false -c gc.cruftPacks=false gc && + test_stdout_line_count = 2 ls bare.git/objects/pack/*.pack && + grep -E "^trace: (built-in|exec|run_command): git repack .* --filter=blob:none ?.*" trace.out +' + +test_expect_success 'gc.repackFilterTo store filtered out objects' ' + test_when_finished "rm -rf bare.git filtered.git" && + + git init --bare filtered.git && + git -C bare.git -c gc.repackFilter=blob:none \ + -c gc.repackFilterTo=../filtered.git/objects/pack/pack \ + -c repack.writeBitmaps=false -c gc.cruftPacks=false gc && + + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + test_stdout_line_count = 1 ls filtered.git/objects/pack/*.pack +' + prepare_cruft_history () { test_commit base && @@ -303,6 +327,33 @@ test_expect_success 'gc.bigPackThreshold ignores cruft packs' ' ) ' +cruft_max_size_opts="git repack -d -l --cruft --cruft-expiration=2.weeks.ago" + +test_expect_success 'setup for --max-cruft-size tests' ' + git init cruft--max-size && + ( + cd cruft--max-size && + prepare_cruft_history + ) +' + +test_expect_success '--max-cruft-size sets appropriate repack options' ' + GIT_TRACE2_EVENT=$(pwd)/trace2.txt git -C cruft--max-size \ + gc --cruft --max-cruft-size=1M && + test_subcommand $cruft_max_size_opts --max-cruft-size=1048576 <trace2.txt +' + +test_expect_success 'gc.maxCruftSize sets appropriate repack options' ' + GIT_TRACE2_EVENT=$(pwd)/trace2.txt \ + git -C cruft--max-size -c gc.maxCruftSize=2M gc --cruft && + test_subcommand $cruft_max_size_opts --max-cruft-size=2097152 <trace2.txt && + + GIT_TRACE2_EVENT=$(pwd)/trace2.txt \ + git -C cruft--max-size -c gc.maxCruftSize=2M gc --cruft \ + --max-cruft-size=3M && + test_subcommand $cruft_max_size_opts --max-cruft-size=3145728 <trace2.txt +' + run_and_wait_for_auto_gc () { # We read stdout from gc for the side effect of waiting until the # background gc process exits, closing its fd 9. Furthermore, the diff --git a/t/t6700-tree-depth.sh b/t/t6700-tree-depth.sh new file mode 100755 index 0000000000..9e70a7c763 --- /dev/null +++ b/t/t6700-tree-depth.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +test_description='handling of deep trees in various commands' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +# We'll test against two depths here: a small one that will let us check the +# behavior of the config setting easily, and a large one that should be +# forbidden by default. Testing the default depth will let us know whether our +# default is enough to prevent segfaults on systems that run the tests. +small_depth=50 +big_depth=4100 + +small_ok="-c core.maxtreedepth=$small_depth" +small_no="-c core.maxtreedepth=$((small_depth-1))" + +# usage: mkdeep <name> <depth> +# Create a tag <name> containing a file whose path has depth <depth>. +# +# We'll use fast-import here for two reasons: +# +# 1. It's faster than creating $big_depth tree objects. +# +# 2. As we tighten tree limits, it's more likely to allow large sizes +# than trying to stuff a deep path into the index. +mkdeep () { + { + echo "commit refs/tags/$1" && + echo "committer foo <foo@example.com> 1234 -0000" && + echo "data <<EOF" && + echo "the commit message" && + echo "EOF" && + + printf 'M 100644 inline ' && + i=0 && + while test $i -lt $2 + do + printf 'a/' + i=$((i+1)) + done && + echo "file" && + + echo "data <<EOF" && + echo "the file contents" && + echo "EOF" && + echo + } | git fast-import +} + +test_expect_success 'create small tree' ' + mkdeep small $small_depth +' + +test_expect_success 'create big tree' ' + mkdeep big $big_depth +' + +test_expect_success 'limit recursion of git-archive' ' + git $small_ok archive small >/dev/null && + test_must_fail git $small_no archive small >/dev/null +' + +test_expect_success 'default limit for git-archive fails gracefully' ' + test_must_fail git archive big >/dev/null +' + +test_expect_success 'limit recursion of ls-tree -r' ' + git $small_ok ls-tree -r small && + test_must_fail git $small_no ls-tree -r small +' + +test_expect_success 'default limit for ls-tree fails gracefully' ' + test_must_fail git ls-tree -r big >/dev/null +' + +test_expect_success 'limit recursion of rev-list --objects' ' + git $small_ok rev-list --objects small >/dev/null && + test_must_fail git $small_no rev-list --objects small >/dev/null +' + +test_expect_success 'default limit for rev-list fails gracefully' ' + test_must_fail git rev-list --objects big >/dev/null +' + +test_expect_success 'limit recursion of diff-tree -r' ' + git $small_ok diff-tree -r $EMPTY_TREE small && + test_must_fail git $small_no diff-tree -r $EMPTY_TREE small +' + +test_expect_success 'default limit for diff-tree fails gracefully' ' + test_must_fail git diff-tree -r $EMPTY_TREE big +' + +test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 35b9e6ed6b..ebf273e843 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -497,6 +497,11 @@ test_expect_success 'checkout unmerged stage' ' test ztheirside = "z$(cat file)" ' +test_expect_success 'checkout path with --merge from tree-ish is a no-no' ' + setup_conflicting_index && + test_must_fail git checkout -m HEAD -- file +' + test_expect_success 'checkout with --merge' ' setup_conflicting_index && echo "none of the above" >sample && @@ -517,6 +522,48 @@ test_expect_success 'checkout with --merge' ' test_cmp merged file ' +test_expect_success 'checkout -m works after (mistaken) resolution' ' + setup_conflicting_index && + echo "none of the above" >sample && + cat sample >fild && + cat sample >file && + cat sample >filf && + # resolve to something + git add file && + git checkout --merge -- fild file filf && + { + echo "<<<<<<< ours" && + echo ourside && + echo "=======" && + echo theirside && + echo ">>>>>>> theirs" + } >merged && + test_cmp expect fild && + test_cmp expect filf && + test_cmp merged file +' + +test_expect_success 'checkout -m works after (mistaken) resolution to remove' ' + setup_conflicting_index && + echo "none of the above" >sample && + cat sample >fild && + cat sample >file && + cat sample >filf && + # resolve to remove + git rm file && + git checkout --merge -- fild file filf && + { + echo "<<<<<<< ours" && + echo ourside && + echo "=======" && + echo theirside && + echo ">>>>>>> theirs" + } >merged && + test_cmp expect fild && + test_cmp expect filf && + test_cmp merged file +' + test_expect_success 'checkout with --merge, in diff3 -m style' ' git config merge.conflictstyle diff3 && setup_conflicting_index && diff --git a/t/t7419-submodule-set-branch.sh b/t/t7419-submodule-set-branch.sh index 232065504c..a5d1bc5c54 100755 --- a/t/t7419-submodule-set-branch.sh +++ b/t/t7419-submodule-set-branch.sh @@ -11,6 +11,10 @@ as expected. TEST_PASSES_SANITIZE_LEAK=true TEST_NO_CREATE_REPO=1 + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + . ./test-lib.sh test_expect_success 'setup' ' @@ -27,26 +31,28 @@ test_expect_success 'submodule config cache setup' ' git checkout -b topic && echo b >a && git add . && - git commit -mb + git commit -mb && + git checkout main ) && mkdir super && (cd super && git init && git submodule add ../submodule && - git commit -m "add submodule" + git submodule add --name thename ../submodule thepath && + git commit -m "add submodules" ) ' test_expect_success 'ensure submodule branch is unset' ' (cd super && - ! grep branch .gitmodules + test_cmp_config "" -f .gitmodules --default "" submodule.submodule.branch ) ' test_expect_success 'test submodule set-branch --branch' ' (cd super && git submodule set-branch --branch topic submodule && - grep "branch = topic" .gitmodules && + test_cmp_config topic -f .gitmodules submodule.submodule.branch && git submodule update --remote && cat <<-\EOF >expect && b @@ -57,13 +63,12 @@ test_expect_success 'test submodule set-branch --branch' ' ' test_expect_success 'test submodule set-branch --default' ' - test_commit -C submodule c && (cd super && git submodule set-branch --default submodule && - ! grep branch .gitmodules && + test_cmp_config "" -f .gitmodules --default "" submodule.submodule.branch && git submodule update --remote && cat <<-\EOF >expect && - c + a EOF git -C submodule show -s --pretty=%s >actual && test_cmp expect actual @@ -71,10 +76,9 @@ test_expect_success 'test submodule set-branch --default' ' ' test_expect_success 'test submodule set-branch -b' ' - test_commit -C submodule b && (cd super && git submodule set-branch -b topic submodule && - grep "branch = topic" .gitmodules && + test_cmp_config topic -f .gitmodules submodule.submodule.branch && git submodule update --remote && cat <<-\EOF >expect && b @@ -85,17 +89,43 @@ test_expect_success 'test submodule set-branch -b' ' ' test_expect_success 'test submodule set-branch -d' ' - test_commit -C submodule d && (cd super && git submodule set-branch -d submodule && - ! grep branch .gitmodules && + test_cmp_config "" -f .gitmodules --default "" submodule.submodule.branch && git submodule update --remote && cat <<-\EOF >expect && - d + a EOF git -C submodule show -s --pretty=%s >actual && test_cmp expect actual ) ' +test_expect_success 'test submodule set-branch --branch with named submodule' ' + (cd super && + git submodule set-branch --branch topic thepath && + test_cmp_config topic -f .gitmodules submodule.thename.branch && + test_cmp_config "" -f .gitmodules --default "" submodule.thepath.branch && + git submodule update --remote && + cat <<-\EOF >expect && + b + EOF + git -C thepath show -s --pretty=%s >actual && + test_cmp expect actual + ) +' + +test_expect_success 'test submodule set-branch --default with named submodule' ' + (cd super && + git submodule set-branch --default thepath && + test_cmp_config "" -f .gitmodules --default "" submodule.thename.branch && + git submodule update --remote && + cat <<-\EOF >expect && + a + EOF + git -C thepath show -s --pretty=%s >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t7420-submodule-set-url.sh b/t/t7420-submodule-set-url.sh index d6bf62b3ac..bf7f15ee79 100755 --- a/t/t7420-submodule-set-url.sh +++ b/t/t7420-submodule-set-url.sh @@ -25,17 +25,26 @@ test_expect_success 'submodule config cache setup' ' git add file && git commit -ma ) && + mkdir namedsubmodule && + ( + cd namedsubmodule && + git init && + echo 1 >file && + git add file && + git commit -m1 + ) && mkdir super && ( cd super && git init && git submodule add ../submodule && - git commit -m "add submodule" + git submodule add --name thename ../namedsubmodule thepath && + git commit -m "add submodules" ) ' test_expect_success 'test submodule set-url' ' - # add a commit and move the submodule (change the url) + # add commits and move the submodules (change the urls) ( cd submodule && echo b >>file && @@ -44,15 +53,28 @@ test_expect_success 'test submodule set-url' ' ) && mv submodule newsubmodule && + ( + cd namedsubmodule && + echo 2 >>file && + git add file && + git commit -m2 + ) && + mv namedsubmodule newnamedsubmodule && + git -C newsubmodule show >expect && + git -C newnamedsubmodule show >>expect && ( cd super && test_must_fail git submodule update --remote && git submodule set-url submodule ../newsubmodule && - grep -F "url = ../newsubmodule" .gitmodules && + test_cmp_config ../newsubmodule -f .gitmodules submodule.submodule.url && + git submodule set-url thepath ../newnamedsubmodule && + test_cmp_config ../newnamedsubmodule -f .gitmodules submodule.thename.url && + test_cmp_config "" -f .gitmodules --default "" submodule.thepath.url && git submodule update --remote ) && git -C super/submodule show >actual && + git -C super/thepath show >>actual && test_cmp expect actual ' diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 97f10905d2..832aff0616 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -489,7 +489,7 @@ test_expect_success 'multiline field treated as atomic for neighbor check' ' ' test_expect_success 'with config setup' ' - git config trailer.ack.key "Acked-by: " && + test_config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && Acked-by: Peff @@ -503,8 +503,8 @@ test_expect_success 'with config setup' ' ' test_expect_success 'with config setup and ":=" as separators' ' - git config trailer.separators ":=" && - git config trailer.ack.key "Acked-by= " && + test_config trailer.separators ":=" && + test_config trailer.ack.key "Acked-by= " && cat >expected <<-\EOF && Acked-by= Peff @@ -518,7 +518,7 @@ test_expect_success 'with config setup and ":=" as separators' ' ' test_expect_success 'with config setup and "%" as separators' ' - git config trailer.separators "%" && + test_config trailer.separators "%" && cat >expected <<-\EOF && bug% 42 @@ -532,6 +532,7 @@ test_expect_success 'with config setup and "%" as separators' ' ' test_expect_success 'with "%" as separators and a message with trailers' ' + test_config trailer.separators "%" && cat >special_message <<-\EOF && Special Message @@ -553,8 +554,8 @@ test_expect_success 'with "%" as separators and a message with trailers' ' ' test_expect_success 'with config setup and ":=#" as separators' ' - git config trailer.separators ":=#" && - git config trailer.bug.key "Bug #" && + test_config trailer.separators ":=#" && + test_config trailer.bug.key "Bug #" && cat >expected <<-\EOF && Bug #42 @@ -581,6 +582,8 @@ test_expect_success 'with basic patch' ' ' test_expect_success 'with commit complex message as argument' ' + test_config trailer.separators ":=" && + test_config trailer.ack.key "Acked-by= " && cat complex_message_body complex_message_trailers >complex_message && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && @@ -594,6 +597,8 @@ test_expect_success 'with commit complex message as argument' ' ' test_expect_success 'with 2 files arguments' ' + test_config trailer.separators ":=" && + test_config trailer.ack.key "Acked-by= " && cat basic_message >>expected && echo >>expected && cat basic_patch >>expected && @@ -677,6 +682,9 @@ test_expect_success 'with message that has an old style conflict block' ' ' test_expect_success 'with commit complex message and trailer args' ' + test_config trailer.separators ":=#" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -692,6 +700,9 @@ test_expect_success 'with commit complex message and trailer args' ' ' test_expect_success 'with complex patch, args and --trim-empty' ' + test_config trailer.separators ":=#" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && cat complex_message >complex_patch && cat basic_patch >>complex_patch && cat complex_message_body >expected && @@ -746,7 +757,10 @@ test_expect_success POSIXPERM,SANITY "in-place editing doesn't clobber original ' test_expect_success 'using "where = before"' ' - git config trailer.bug.where "before" && + test_config trailer.separators ":=#" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -762,7 +776,9 @@ test_expect_success 'using "where = before"' ' ' test_expect_success 'overriding configuration with "--where after"' ' - git config trailer.ack.where "before" && + test_config trailer.separators ":=" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "before" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -776,7 +792,12 @@ test_expect_success 'overriding configuration with "--where after"' ' test_cmp expected actual ' -test_expect_success 'using "where = before" with "--no-where"' ' +test_expect_success 'using "--where after" with "--no-where"' ' + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "before" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -791,8 +812,59 @@ test_expect_success 'using "where = before" with "--no-where"' ' test_cmp expected actual ' +# Check whether using "--no-where" clears out only the "--where after", such +# that we still use the configuration in trailer.where (which is different from +# the hardcoded default (in WHERE_END) assuming the absence of .gitconfig). +# Here, the "start" setting of trailer.where is respected, so the new "Acked-by" +# and "Bug" trailers are placed at the beginning, and not at the end which is +# the harcoded default. +test_expect_success 'using "--where after" with "--no-where" defaults to configuration' ' + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && + test_config trailer.separators ":=#" && + test_config trailer.where "start" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Acked-by= Peff + Fixes: Z + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --no-where --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + +# The "--where after" will only get respected for the trailer that came +# immediately after it. For the next trailer (Bug #42), we default to using the +# hardcoded WHERE_END because we don't have any "trailer.where" or +# "trailer.bug.where" configured. +test_expect_success 'using "--no-where" defaults to harcoded default if nothing configured' ' + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && + test_config trailer.separators ":=#" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + Bug #42 + EOF + git interpret-trailers --where after --trailer "ack: Peff" --no-where \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "where = after"' ' - git config trailer.ack.where "after" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -808,8 +880,11 @@ test_expect_success 'using "where = after"' ' ' test_expect_success 'using "where = end"' ' - git config trailer.review.key "Reviewed-by" && - git config trailer.review.where "end" && + test_config trailer.review.key "Reviewed-by" && + test_config trailer.review.where "end" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -827,8 +902,11 @@ test_expect_success 'using "where = end"' ' ' test_expect_success 'using "where = start"' ' - git config trailer.review.key "Reviewed-by" && - git config trailer.review.where "start" && + test_config trailer.review.key "Reviewed-by" && + test_config trailer.review.where "start" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Reviewed-by: Johannes @@ -846,8 +924,13 @@ test_expect_success 'using "where = start"' ' ' test_expect_success 'using "where = before" for a token in the middle of the message' ' - git config trailer.review.key "Reviewed-by:" && - git config trailer.review.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.review.where "before" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -864,6 +947,12 @@ test_expect_success 'using "where = before" for a token in the middle of the mes ' test_expect_success 'using "where = before" and --trim-empty' ' + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && cat >>expected <<-\EOF && Bug #46 @@ -878,6 +967,13 @@ test_expect_success 'using "where = before" and --trim-empty' ' ' test_expect_success 'the default is "ifExists = addIfDifferentNeighbor"' ' + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.review.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -896,7 +992,13 @@ test_expect_success 'the default is "ifExists = addIfDifferentNeighbor"' ' ' test_expect_success 'default "ifExists" is now "addIfDifferent"' ' - git config trailer.ifexists "addIfDifferent" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -914,8 +1016,14 @@ test_expect_success 'default "ifExists" is now "addIfDifferent"' ' ' test_expect_success 'using "ifExists = addIfDifferent" with "where = end"' ' - git config trailer.ack.ifExists "addIfDifferent" && - git config trailer.ack.where "end" && + test_config trailer.ack.ifExists "addIfDifferent" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "end" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -932,8 +1040,14 @@ test_expect_success 'using "ifExists = addIfDifferent" with "where = end"' ' ' test_expect_success 'using "ifExists = addIfDifferent" with "where = before"' ' - git config trailer.ack.ifExists "addIfDifferent" && - git config trailer.ack.where "before" && + test_config trailer.ack.ifExists "addIfDifferent" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "before" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -950,8 +1064,14 @@ test_expect_success 'using "ifExists = addIfDifferent" with "where = before"' ' ' test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = end"' ' - git config trailer.ack.ifExists "addIfDifferentNeighbor" && - git config trailer.ack.where "end" && + test_config trailer.ack.ifExists "addIfDifferentNeighbor" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "end" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -973,8 +1093,14 @@ test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = end ' test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = after"' ' - git config trailer.ack.ifExists "addIfDifferentNeighbor" && - git config trailer.ack.where "after" && + test_config trailer.ack.ifExists "addIfDifferentNeighbor" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -995,7 +1121,11 @@ test_expect_success 'using "ifExists = addIfDifferentNeighbor" with "where = af ' test_expect_success 'using "ifExists = addIfDifferentNeighbor" and --trim-empty' ' - git config trailer.ack.ifExists "addIfDifferentNeighbor" && + test_config trailer.ack.ifExists "addIfDifferentNeighbor" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && cat >>expected <<-\EOF && Bug #42 @@ -1011,8 +1141,14 @@ test_expect_success 'using "ifExists = addIfDifferentNeighbor" and --trim-empty' ' test_expect_success 'using "ifExists = add" with "where = end"' ' - git config trailer.ack.ifExists "add" && - git config trailer.ack.where "end" && + test_config trailer.ack.ifExists "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "end" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1036,8 +1172,14 @@ test_expect_success 'using "ifExists = add" with "where = end"' ' ' test_expect_success 'using "ifExists = add" with "where = after"' ' - git config trailer.ack.ifExists "add" && - git config trailer.ack.where "after" && + test_config trailer.ack.ifExists "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1058,8 +1200,15 @@ test_expect_success 'using "ifExists = add" with "where = after"' ' ' test_expect_success 'overriding configuration with "--if-exists replace"' ' - git config trailer.fix.key "Fixes: " && - git config trailer.fix.ifExists "add" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.review.where "before" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1074,9 +1223,66 @@ test_expect_success 'overriding configuration with "--if-exists replace"' ' test_cmp expected actual ' +# "trailer.ifexists" is set to "doNothing", so using "--no-if-exists" defaults +# to this "doNothing" behavior. So the "Fixes: 53" trailer does not get added. +test_expect_success 'using "--if-exists replace" with "--no-if-exists" defaults to configuration' ' + test_config trailer.ifexists "doNothing" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --if-exists replace --no-if-exists --trailer "Fixes: 53" \ + <complex_message >actual && + test_cmp expected actual +' + +# No "ifexists" configuration is set, so using "--no-if-exists" makes it default +# to addIfDifferentNeighbor. Because we do have a different neighbor "Fixes: 53" +# (because it got added by overriding with "--if-exists replace" earlier in the +# arguments list), we add "Signed-off-by: addme". +test_expect_success 'using "--no-if-exists" defaults to hardcoded default if nothing configured' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + Fixes: 53 + Signed-off-by: addme + EOF + git interpret-trailers --if-exists replace --trailer "Fixes: 53" --no-if-exists \ + --trailer "Signed-off-by: addme" <complex_message >actual && + test_cmp expected actual +' + +# The second "Fixes: 53" trailer is discarded, because the "--no-if-exists" here +# makes us default to addIfDifferentNeighbor, and we already added the "Fixes: +# 53" trailer earlier in the argument list. +test_expect_success 'using "--no-if-exists" defaults to hardcoded default if nothing configured (no addition)' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + Fixes: 53 + EOF + git interpret-trailers --if-exists replace --trailer "Fixes: 53" --no-if-exists \ + --trailer "Fixes: 53" <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "ifExists = replace"' ' - git config trailer.fix.key "Fixes: " && - git config trailer.fix.ifExists "replace" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "replace" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1095,7 +1301,16 @@ test_expect_success 'using "ifExists = replace"' ' ' test_expect_success 'using "ifExists = replace" with "where = after"' ' - git config trailer.fix.where "after" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "replace" && + test_config trailer.fix.where "after" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1114,7 +1329,15 @@ test_expect_success 'using "ifExists = replace" with "where = after"' ' ' test_expect_success 'using "ifExists = doNothing"' ' - git config trailer.fix.ifExists "doNothing" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1133,8 +1356,17 @@ test_expect_success 'using "ifExists = doNothing"' ' ' test_expect_success 'the default is "ifMissing = add"' ' - git config trailer.cc.key "Cc: " && - git config trailer.cc.where "before" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.cc.key "Cc: " && + test_config trailer.cc.where "before" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1154,7 +1386,14 @@ test_expect_success 'the default is "ifMissing = add"' ' ' test_expect_success 'overriding configuration with "--if-missing doNothing"' ' - git config trailer.ifmissing "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.ifmissing "add" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -1173,7 +1412,13 @@ test_expect_success 'overriding configuration with "--if-missing doNothing"' ' ' test_expect_success 'when default "ifMissing" is "doNothing"' ' - git config trailer.ifmissing "doNothing" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.ifmissing "doNothing" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -1187,14 +1432,21 @@ test_expect_success 'when default "ifMissing" is "doNothing"' ' --trailer "cc=Linus" --trailer "ack: Junio" \ --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ <complex_message >actual && - test_cmp expected actual && - git config trailer.ifmissing "add" + test_cmp expected actual ' test_expect_success 'using "ifMissing = add" with "where = end"' ' - git config trailer.cc.key "Cc: " && - git config trailer.cc.where "end" && - git config trailer.cc.ifMissing "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.cc.key "Cc: " && + test_config trailer.cc.ifMissing "add" && + test_config trailer.cc.where "end" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1214,9 +1466,17 @@ test_expect_success 'using "ifMissing = add" with "where = end"' ' ' test_expect_success 'using "ifMissing = add" with "where = before"' ' - git config trailer.cc.key "Cc: " && - git config trailer.cc.where "before" && - git config trailer.cc.ifMissing "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.cc.key "Cc: " && + test_config trailer.cc.ifMissing "add" && + test_config trailer.cc.where "before" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Cc: Linus @@ -1236,7 +1496,15 @@ test_expect_success 'using "ifMissing = add" with "where = before"' ' ' test_expect_success 'using "ifMissing = doNothing"' ' - git config trailer.cc.ifMissing "doNothing" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.cc.ifMissing "doNothing" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1254,9 +1522,50 @@ test_expect_success 'using "ifMissing = doNothing"' ' test_cmp expected actual ' +# Ignore the "IgnoredTrailer" because of "--if-missing doNothing", but also +# ignore the "StillIgnoredTrailer" because we set "trailer.ifMissing" to +# "doNothing" in configuration. +test_expect_success 'using "--no-if-missing" defaults to configuration' ' + test_config trailer.ifMissing "doNothing" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --if-missing doNothing --trailer "IgnoredTrailer: ignoreme" --no-if-missing \ + --trailer "StillIgnoredTrailer: ignoreme" <complex_message >actual && + test_cmp expected actual +' + +# Add the "AddedTrailer" because the "--no-if-missing" clears the "--if-missing +# doNothing" from earlier in the argument list. +test_expect_success 'using "--no-if-missing" defaults to hardcoded default if nothing configured' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by: Z + Reviewed-by: Z + Signed-off-by: Z + AddedTrailer: addme + EOF + git interpret-trailers --if-missing doNothing --trailer "IgnoredTrailer: ignoreme" --no-if-missing \ + --trailer "AddedTrailer: addme" <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'default "where" is now "after"' ' git config trailer.where "after" && - git config --unset trailer.ack.where && + test_config trailer.ack.ifExists "add" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.ack.where "after" && + test_config trailer.bug.key "Bug #" && + test_config trailer.bug.where "before" && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=#" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Bug #42 @@ -1280,10 +1589,15 @@ test_expect_success 'default "where" is now "after"' ' ' test_expect_success 'with simple command' ' - git config trailer.sign.key "Signed-off-by: " && - git config trailer.sign.where "after" && - git config trailer.sign.ifExists "addIfDifferentNeighbor" && - git config trailer.sign.command "echo \"A U Thor <author@example.com>\"" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"A U Thor <author@example.com>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.sign.ifExists "addIfDifferentNeighbor" && + test_config trailer.sign.where "after" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -1298,8 +1612,14 @@ test_expect_success 'with simple command' ' ' test_expect_success 'with command using committer information' ' - git config trailer.sign.ifExists "addIfDifferent" && - git config trailer.sign.command "echo \"\$GIT_COMMITTER_NAME <\$GIT_COMMITTER_EMAIL>\"" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"\$GIT_COMMITTER_NAME <\$GIT_COMMITTER_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.sign.ifExists "addIfDifferent" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -1314,10 +1634,15 @@ test_expect_success 'with command using committer information' ' ' test_expect_success 'with command using author information' ' - git config trailer.sign.key "Signed-off-by: " && - git config trailer.sign.where "after" && - git config trailer.sign.ifExists "addIfDifferentNeighbor" && - git config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.ifExists "doNothing" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.sign.ifExists "addIfDifferentNeighbor" && + test_config trailer.sign.where "after" && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-\EOF && Fixes: Z @@ -1338,12 +1663,19 @@ test_expect_success 'setup a commit' ' ' test_expect_success 'cmd takes precedence over command' ' - test_when_finished "git config --unset trailer.fix.cmd" && - git config trailer.fix.ifExists "replace" && - git config trailer.fix.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%aN)\" \ - --abbrev-commit --abbrev=14 \"\$1\" || true" && - git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \ + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \ --abbrev-commit --abbrev=14 \$ARG" && + test_config trailer.fix.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%aN)\" \ + --abbrev-commit --abbrev=14 \"\$1\" || true" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "replace" && + test_config trailer.fix.where "after" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && FIXED=$(git log -1 --oneline --format="%h (%aN)" --abbrev-commit --abbrev=14 HEAD) && cat complex_message_body >expected2 && sed -e "s/ Z\$/ /" >>expected2 <<-EOF && @@ -1359,8 +1691,16 @@ test_expect_success 'cmd takes precedence over command' ' ' test_expect_success 'with command using $ARG' ' - git config trailer.fix.ifExists "replace" && - git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "replace" && + test_config trailer.fix.where "after" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-EOF && @@ -1376,8 +1716,16 @@ test_expect_success 'with command using $ARG' ' ' test_expect_success 'with failing command using $ARG' ' - git config trailer.fix.ifExists "replace" && - git config trailer.fix.command "false \$ARG" && + test_config trailer.ack.key "Acked-by= " && + test_config trailer.fix.command "false \$ARG" && + test_config trailer.fix.key "Fixes: " && + test_config trailer.fix.ifExists "replace" && + test_config trailer.fix.where "after" && + test_config trailer.review.key "Reviewed-by:" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.ifexists "addIfDifferent" && + test_config trailer.separators ":=" && cat complex_message_body >expected && sed -e "s/ Z\$/ /" >>expected <<-EOF && Fixes: Z @@ -1392,7 +1740,9 @@ test_expect_success 'with failing command using $ARG' ' ' test_expect_success 'with empty tokens' ' - git config --unset trailer.fix.command && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.sign.key "Signed-off-by: " && + test_config trailer.ifexists "addIfDifferent" && cat >expected <<-EOF && Signed-off-by: A U Thor <author@example.com> @@ -1403,7 +1753,8 @@ test_expect_success 'with empty tokens' ' ' test_expect_success 'with command but no key' ' - git config --unset trailer.sign.key && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.ifexists "addIfDifferent" && cat >expected <<-EOF && sign: A U Thor <author@example.com> @@ -1414,7 +1765,9 @@ test_expect_success 'with command but no key' ' ' test_expect_success 'with no command and no key' ' - git config --unset trailer.review.key && + test_config trailer.review.where "before" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && + test_config trailer.ifexists "addIfDifferent" && cat >expected <<-EOF && review: Junio @@ -1426,6 +1779,8 @@ test_expect_success 'with no command and no key' ' ' test_expect_success 'with cut line' ' + test_config trailer.review.where "before" && + test_config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" && cat >expected <<-\EOF && my subject @@ -1443,7 +1798,8 @@ test_expect_success 'with cut line' ' ' test_expect_success 'only trailers' ' - git config trailer.sign.command "echo config-value" && + test_config trailer.sign.command "echo config-value" && + test_config trailer.ifexists "addIfDifferent" && cat >expected <<-\EOF && existing: existing-value sign: config-value @@ -1462,7 +1818,7 @@ test_expect_success 'only trailers' ' ' test_expect_success 'only-trailers omits non-trailer in middle of block' ' - git config trailer.sign.command "echo config-value" && + test_config trailer.sign.command "echo config-value" && cat >expected <<-\EOF && Signed-off-by: nobody <nobody@nowhere> Signed-off-by: somebody <somebody@somewhere> @@ -1482,7 +1838,7 @@ test_expect_success 'only-trailers omits non-trailer in middle of block' ' ' test_expect_success 'only input' ' - git config trailer.sign.command "echo config-value" && + test_config trailer.sign.command "echo config-value" && cat >expected <<-\EOF && existing: existing-value EOF diff --git a/t/t7516-commit-races.sh b/t/t7516-commit-races.sh index 2d38a16480..bb95f09810 100755 --- a/t/t7516-commit-races.sh +++ b/t/t7516-commit-races.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git commit races' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'race to create orphan commit' ' diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index 0c241d6c14..78503158fd 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh @@ -809,6 +809,11 @@ my_match_and_clean () { status --porcelain=v2 >actual.without && test_cmp actual.with actual.without && + git -C super --no-optional-locks diff-index --name-status HEAD >actual.with && + git -C super --no-optional-locks -c core.fsmonitor=false \ + diff-index --name-status HEAD >actual.without && + test_cmp actual.with actual.without && + git -C super/dir_1/dir_2/sub reset --hard && git -C super/dir_1/dir_2/sub clean -d -f } diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index ff085b086c..3669d33bd5 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -4,6 +4,7 @@ test_description='git merge Testing octopus merge with more than 25 refs.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh index 4887ca705b..0e85b21ec8 100755 --- a/t/t7603-merge-reduce-heads.sh +++ b/t/t7603-merge-reduce-heads.sh @@ -4,6 +4,7 @@ test_description='git merge Testing octopus merge when reducing parents to independent branches.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # 0 - 1 diff --git a/t/t7607-merge-state.sh b/t/t7607-merge-state.sh index 89a62ac53b..9001674f2e 100755 --- a/t/t7607-merge-state.sh +++ b/t/t7607-merge-state.sh @@ -4,6 +4,7 @@ test_description="Test that merge state is as expected after failed merge" GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'Ensure we restore original state if no merge strategy handles it' ' diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh index 0b908ab2e7..2179938c43 100755 --- a/t/t7608-merge-messages.sh +++ b/t/t7608-merge-messages.sh @@ -4,6 +4,7 @@ test_description='test auto-generated merge messages' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_oneline() { diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 27b66807cd..d2975e6c93 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -327,6 +327,203 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' ' test_must_be_empty actual ' +test_expect_success 'repacking with a filter works' ' + git -C bare.git repack -a -d && + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + git -C bare.git -c repack.writebitmaps=false repack -a -d --filter=blob:none && + test_stdout_line_count = 2 ls bare.git/objects/pack/*.pack && + commit_pack=$(test-tool -C bare.git find-pack -c 1 HEAD) && + blob_pack=$(test-tool -C bare.git find-pack -c 1 HEAD:file1) && + test "$commit_pack" != "$blob_pack" && + tree_pack=$(test-tool -C bare.git find-pack -c 1 HEAD^{tree}) && + test "$tree_pack" = "$commit_pack" && + blob_pack2=$(test-tool -C bare.git find-pack -c 1 HEAD:file2) && + test "$blob_pack2" = "$blob_pack" +' + +test_expect_success '--filter fails with --write-bitmap-index' ' + test_must_fail \ + env GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \ + git -C bare.git repack -a -d --write-bitmap-index --filter=blob:none +' + +test_expect_success 'repacking with two filters works' ' + git init two-filters && + ( + cd two-filters && + mkdir subdir && + test_commit foo && + test_commit subdir_bar subdir/bar && + test_commit subdir_baz subdir/baz + ) && + git clone --no-local --bare two-filters two-filters.git && + ( + cd two-filters.git && + test_stdout_line_count = 1 ls objects/pack/*.pack && + git -c repack.writebitmaps=false repack -a -d \ + --filter=blob:none --filter=tree:1 && + test_stdout_line_count = 2 ls objects/pack/*.pack && + commit_pack=$(test-tool find-pack -c 1 HEAD) && + blob_pack=$(test-tool find-pack -c 1 HEAD:foo.t) && + root_tree_pack=$(test-tool find-pack -c 1 HEAD^{tree}) && + subdir_tree_hash=$(git ls-tree --object-only HEAD -- subdir) && + subdir_tree_pack=$(test-tool find-pack -c 1 "$subdir_tree_hash") && + + # Root tree and subdir tree are not in the same packfiles + test "$commit_pack" != "$blob_pack" && + test "$commit_pack" = "$root_tree_pack" && + test "$blob_pack" = "$subdir_tree_pack" + ) +' + +prepare_for_keep_packs () { + git init keep-packs && + ( + cd keep-packs && + test_commit foo && + test_commit bar + ) && + git clone --no-local --bare keep-packs keep-packs.git && + ( + cd keep-packs.git && + + # Create two packs + # The first pack will contain all of the objects except one blob + git rev-list --objects --all >objs && + grep -v "bar.t" objs | git pack-objects pack && + # The second pack will contain the excluded object and be kept + packid=$(grep "bar.t" objs | git pack-objects pack) && + >pack-$packid.keep && + + # Replace the existing pack with the 2 new ones + rm -f objects/pack/pack* && + mv pack-* objects/pack/ + ) +} + +test_expect_success '--filter works with .keep packs' ' + prepare_for_keep_packs && + ( + cd keep-packs.git && + + foo_pack=$(test-tool find-pack -c 1 HEAD:foo.t) && + bar_pack=$(test-tool find-pack -c 1 HEAD:bar.t) && + head_pack=$(test-tool find-pack -c 1 HEAD) && + + test "$foo_pack" != "$bar_pack" && + test "$foo_pack" = "$head_pack" && + + git -c repack.writebitmaps=false repack -a -d --filter=blob:none && + + foo_pack_1=$(test-tool find-pack -c 1 HEAD:foo.t) && + bar_pack_1=$(test-tool find-pack -c 1 HEAD:bar.t) && + head_pack_1=$(test-tool find-pack -c 1 HEAD) && + + # Object bar is still only in the old .keep pack + test "$foo_pack_1" != "$foo_pack" && + test "$bar_pack_1" = "$bar_pack" && + test "$head_pack_1" != "$head_pack" && + + test "$foo_pack_1" != "$bar_pack_1" && + test "$foo_pack_1" != "$head_pack_1" && + test "$bar_pack_1" != "$head_pack_1" + ) +' + +test_expect_success '--filter works with --pack-kept-objects and .keep packs' ' + rm -rf keep-packs keep-packs.git && + prepare_for_keep_packs && + ( + cd keep-packs.git && + + foo_pack=$(test-tool find-pack -c 1 HEAD:foo.t) && + bar_pack=$(test-tool find-pack -c 1 HEAD:bar.t) && + head_pack=$(test-tool find-pack -c 1 HEAD) && + + test "$foo_pack" != "$bar_pack" && + test "$foo_pack" = "$head_pack" && + + git -c repack.writebitmaps=false repack -a -d --filter=blob:none \ + --pack-kept-objects && + + foo_pack_1=$(test-tool find-pack -c 1 HEAD:foo.t) && + test-tool find-pack -c 2 HEAD:bar.t >bar_pack_1 && + head_pack_1=$(test-tool find-pack -c 1 HEAD) && + + test "$foo_pack_1" != "$foo_pack" && + test "$foo_pack_1" != "$bar_pack" && + test "$head_pack_1" != "$head_pack" && + + # Object bar is in both the old .keep pack and the new + # pack that contained the filtered out objects + grep "$bar_pack" bar_pack_1 && + grep "$foo_pack_1" bar_pack_1 && + test "$foo_pack_1" != "$head_pack_1" + ) +' + +test_expect_success '--filter-to stores filtered out objects' ' + git -C bare.git repack -a -d && + test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack && + + git init --bare filtered.git && + git -C bare.git -c repack.writebitmaps=false repack -a -d \ + --filter=blob:none \ + --filter-to=../filtered.git/objects/pack/pack && + test_stdout_line_count = 1 ls bare.git/objects/pack/pack-*.pack && + test_stdout_line_count = 1 ls filtered.git/objects/pack/pack-*.pack && + + commit_pack=$(test-tool -C bare.git find-pack -c 1 HEAD) && + blob_pack=$(test-tool -C bare.git find-pack -c 0 HEAD:file1) && + blob_hash=$(git -C bare.git rev-parse HEAD:file1) && + test -n "$blob_hash" && + blob_pack=$(test-tool -C filtered.git find-pack -c 1 $blob_hash) && + + echo $(pwd)/filtered.git/objects >bare.git/objects/info/alternates && + blob_pack=$(test-tool -C bare.git find-pack -c 1 HEAD:file1) && + blob_content=$(git -C bare.git show $blob_hash) && + test "$blob_content" = "content1" +' + +test_expect_success '--filter works with --max-pack-size' ' + rm -rf filtered.git && + git init --bare filtered.git && + git init max-pack-size && + ( + cd max-pack-size && + test_commit base && + # two blobs which exceed the maximum pack size + test-tool genrandom foo 1048576 >foo && + git hash-object -w foo && + test-tool genrandom bar 1048576 >bar && + git hash-object -w bar && + git add foo bar && + git commit -m "adding foo and bar" + ) && + git clone --no-local --bare max-pack-size max-pack-size.git && + ( + cd max-pack-size.git && + git -c repack.writebitmaps=false repack -a -d --filter=blob:none \ + --max-pack-size=1M \ + --filter-to=../filtered.git/objects/pack/pack && + echo $(cd .. && pwd)/filtered.git/objects >objects/info/alternates && + + # Check that the 3 blobs are in different packfiles in filtered.git + test_stdout_line_count = 3 ls ../filtered.git/objects/pack/pack-*.pack && + test_stdout_line_count = 1 ls objects/pack/pack-*.pack && + foo_pack=$(test-tool find-pack -c 1 HEAD:foo) && + bar_pack=$(test-tool find-pack -c 1 HEAD:bar) && + base_pack=$(test-tool find-pack -c 1 HEAD:base.t) && + test "$foo_pack" != "$bar_pack" && + test "$foo_pack" != "$base_pack" && + test "$bar_pack" != "$base_pack" && + for pack in "$foo_pack" "$bar_pack" "$base_pack" + do + case "$foo_pack" in */filtered.git/objects/pack/*) true ;; *) return 1 ;; esac + done + ) +' + objdir=.git/objects midx=$objdir/pack/multi-pack-index @@ -633,125 +830,4 @@ test_expect_success '-n overrides repack.updateServerInfo=true' ' test_server_info_missing ' -test_expect_success '--expire-to stores pruned objects (now)' ' - git init expire-to-now && - ( - cd expire-to-now && - - git branch -M main && - - test_commit base && - - git checkout -b cruft && - test_commit --no-tag cruft && - - git rev-list --objects --no-object-names main..cruft >moved.raw && - sort moved.raw >moved.want && - - git rev-list --all --objects --no-object-names >expect.raw && - sort expect.raw >expect && - - git checkout main && - git branch -D cruft && - git reflog expire --all --expire=all && - - git init --bare expired.git && - git repack -d \ - --cruft --cruft-expiration="now" \ - --expire-to="expired.git/objects/pack/pack" && - - expired="$(ls expired.git/objects/pack/pack-*.idx)" && - test_path_is_file "${expired%.idx}.mtimes" && - - # Since the `--cruft-expiration` is "now", the effective - # behavior is to move _all_ unreachable objects out to - # the location in `--expire-to`. - git show-index <$expired >expired.raw && - cut -d" " -f2 expired.raw | sort >expired.objects && - git rev-list --all --objects --no-object-names \ - >remaining.objects && - - # ...in other words, the combined contents of this - # repository and expired.git should be the same as the - # set of objects we started with. - cat expired.objects remaining.objects | sort >actual && - test_cmp expect actual && - - # The "moved" objects (i.e., those in expired.git) - # should be the same as the cruft objects which were - # expired in the previous step. - test_cmp moved.want expired.objects - ) -' - -test_expect_success '--expire-to stores pruned objects (5.minutes.ago)' ' - git init expire-to-5.minutes.ago && - ( - cd expire-to-5.minutes.ago && - - git branch -M main && - - test_commit base && - - # Create two classes of unreachable objects, one which - # is older than 5 minutes (stale), and another which is - # newer (recent). - for kind in stale recent - do - git checkout -b $kind main && - test_commit --no-tag $kind || return 1 - done && - - git rev-list --objects --no-object-names main..stale >in && - stale="$(git pack-objects $objdir/pack/pack <in)" && - mtime="$(test-tool chmtime --get =-600 $objdir/pack/pack-$stale.pack)" && - - # expect holds the set of objects we expect to find in - # this repository after repacking - git rev-list --objects --no-object-names recent >expect.raw && - sort expect.raw >expect && - - # moved.want holds the set of objects we expect to find - # in expired.git - git rev-list --objects --no-object-names main..stale >out && - sort out >moved.want && - - git checkout main && - git branch -D stale recent && - git reflog expire --all --expire=all && - git prune-packed && - - git init --bare expired.git && - git repack -d \ - --cruft --cruft-expiration=5.minutes.ago \ - --expire-to="expired.git/objects/pack/pack" && - - # Some of the remaining objects in this repository are - # unreachable, so use `cat-file --batch-all-objects` - # instead of `rev-list` to get their names - git cat-file --batch-all-objects --batch-check="%(objectname)" \ - >remaining.objects && - sort remaining.objects >actual && - test_cmp expect actual && - - ( - cd expired.git && - - expired="$(ls objects/pack/pack-*.mtimes)" && - test-tool pack-mtimes $(basename $expired) >out && - cut -d" " -f1 out | sort >../moved.got && - - # Ensure that there are as many objects with the - # expected mtime as were moved to expired.git. - # - # In other words, ensure that the recorded - # mtimes of any moved objects was written - # correctly. - grep " $mtime$" out >matching && - test_line_count = $(wc -l <../moved.want) matching - ) && - test_cmp moved.want moved.got - ) -' - test_done diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh new file mode 100755 index 0000000000..be3735dff0 --- /dev/null +++ b/t/t7704-repack-cruft.sh @@ -0,0 +1,414 @@ +#!/bin/sh + +test_description='git repack works correctly' + +. ./test-lib.sh + +objdir=.git/objects +packdir=$objdir/pack + +test_expect_success '--expire-to stores pruned objects (now)' ' + git init expire-to-now && + ( + cd expire-to-now && + + git branch -M main && + + test_commit base && + + git checkout -b cruft && + test_commit --no-tag cruft && + + git rev-list --objects --no-object-names main..cruft >moved.raw && + sort moved.raw >moved.want && + + git rev-list --all --objects --no-object-names >expect.raw && + sort expect.raw >expect && + + git checkout main && + git branch -D cruft && + git reflog expire --all --expire=all && + + git init --bare expired.git && + git repack -d \ + --cruft --cruft-expiration="now" \ + --expire-to="expired.git/objects/pack/pack" && + + expired="$(ls expired.git/objects/pack/pack-*.idx)" && + test_path_is_file "${expired%.idx}.mtimes" && + + # Since the `--cruft-expiration` is "now", the effective + # behavior is to move _all_ unreachable objects out to + # the location in `--expire-to`. + git show-index <$expired >expired.raw && + cut -d" " -f2 expired.raw | sort >expired.objects && + git rev-list --all --objects --no-object-names \ + >remaining.objects && + + # ...in other words, the combined contents of this + # repository and expired.git should be the same as the + # set of objects we started with. + cat expired.objects remaining.objects | sort >actual && + test_cmp expect actual && + + # The "moved" objects (i.e., those in expired.git) + # should be the same as the cruft objects which were + # expired in the previous step. + test_cmp moved.want expired.objects + ) +' + +test_expect_success '--expire-to stores pruned objects (5.minutes.ago)' ' + git init expire-to-5.minutes.ago && + ( + cd expire-to-5.minutes.ago && + + git branch -M main && + + test_commit base && + + # Create two classes of unreachable objects, one which + # is older than 5 minutes (stale), and another which is + # newer (recent). + for kind in stale recent + do + git checkout -b $kind main && + test_commit --no-tag $kind || return 1 + done && + + git rev-list --objects --no-object-names main..stale >in && + stale="$(git pack-objects $objdir/pack/pack <in)" && + mtime="$(test-tool chmtime --get =-600 $objdir/pack/pack-$stale.pack)" && + + # expect holds the set of objects we expect to find in + # this repository after repacking + git rev-list --objects --no-object-names recent >expect.raw && + sort expect.raw >expect && + + # moved.want holds the set of objects we expect to find + # in expired.git + git rev-list --objects --no-object-names main..stale >out && + sort out >moved.want && + + git checkout main && + git branch -D stale recent && + git reflog expire --all --expire=all && + git prune-packed && + + git init --bare expired.git && + git repack -d \ + --cruft --cruft-expiration=5.minutes.ago \ + --expire-to="expired.git/objects/pack/pack" && + + # Some of the remaining objects in this repository are + # unreachable, so use `cat-file --batch-all-objects` + # instead of `rev-list` to get their names + git cat-file --batch-all-objects --batch-check="%(objectname)" \ + >remaining.objects && + sort remaining.objects >actual && + test_cmp expect actual && + + ( + cd expired.git && + + expired="$(ls objects/pack/pack-*.mtimes)" && + test-tool pack-mtimes $(basename $expired) >out && + cut -d" " -f1 out | sort >../moved.got && + + # Ensure that there are as many objects with the + # expected mtime as were moved to expired.git. + # + # In other words, ensure that the recorded + # mtimes of any moved objects was written + # correctly. + grep " $mtime$" out >matching && + test_line_count = $(wc -l <../moved.want) matching + ) && + test_cmp moved.want moved.got + ) +' + +generate_random_blob() { + test-tool genrandom "$@" >blob && + git hash-object -w -t blob blob && + rm blob +} + +pack_random_blob () { + generate_random_blob "$@" && + git repack -d -q >/dev/null +} + +generate_cruft_pack () { + pack_random_blob "$@" >/dev/null && + + ls $packdir/pack-*.pack | xargs -n 1 basename >in && + pack="$(git pack-objects --cruft $packdir/pack <in)" && + git prune-packed && + + echo "$packdir/pack-$pack.mtimes" +} + +test_expect_success '--max-cruft-size creates new packs when above threshold' ' + git init max-cruft-size-large && + ( + cd max-cruft-size-large && + test_commit base && + + foo="$(pack_random_blob foo $((1*1024*1024)))" && + git repack --cruft -d && + cruft_foo="$(ls $packdir/pack-*.mtimes)" && + + bar="$(pack_random_blob bar $((1*1024*1024)))" && + git repack --cruft -d --max-cruft-size=1M && + cruft_bar="$(ls $packdir/pack-*.mtimes | grep -v $cruft_foo)" && + + test-tool pack-mtimes $(basename "$cruft_foo") >foo.objects && + test-tool pack-mtimes $(basename "$cruft_bar") >bar.objects && + + grep "^$foo" foo.objects && + test_line_count = 1 foo.objects && + grep "^$bar" bar.objects && + test_line_count = 1 bar.objects + ) +' + +test_expect_success '--max-cruft-size combines existing packs when below threshold' ' + git init max-cruft-size-small && + ( + cd max-cruft-size-small && + test_commit base && + + foo="$(pack_random_blob foo $((1*1024*1024)))" && + git repack --cruft -d && + + bar="$(pack_random_blob bar $((1*1024*1024)))" && + git repack --cruft -d --max-cruft-size=10M && + + cruft=$(ls $packdir/pack-*.mtimes) && + test-tool pack-mtimes $(basename "$cruft") >cruft.objects && + + grep "^$foo" cruft.objects && + grep "^$bar" cruft.objects && + test_line_count = 2 cruft.objects + ) +' + +test_expect_success '--max-cruft-size combines smaller packs first' ' + git init max-cruft-size-consume-small && + ( + cd max-cruft-size-consume-small && + + test_commit base && + git repack -ad && + + cruft_foo="$(generate_cruft_pack foo 524288)" && # 0.5 MiB + cruft_bar="$(generate_cruft_pack bar 524288)" && # 0.5 MiB + cruft_baz="$(generate_cruft_pack baz 1048576)" && # 1.0 MiB + cruft_quux="$(generate_cruft_pack quux 1572864)" && # 1.5 MiB + + test-tool pack-mtimes "$(basename $cruft_foo)" >expect.raw && + test-tool pack-mtimes "$(basename $cruft_bar)" >>expect.raw && + sort expect.raw >expect.objects && + + # repacking with `--max-cruft-size=2M` should combine + # both 0.5 MiB packs together, instead of, say, one of + # the 0.5 MiB packs with the 1.0 MiB pack + ls $packdir/pack-*.mtimes | sort >cruft.before && + git repack -d --cruft --max-cruft-size=2M && + ls $packdir/pack-*.mtimes | sort >cruft.after && + + comm -13 cruft.before cruft.after >cruft.new && + comm -23 cruft.before cruft.after >cruft.removed && + + test_line_count = 1 cruft.new && + test_line_count = 2 cruft.removed && + + # the two smaller packs should be rolled up first + printf "%s\n" $cruft_foo $cruft_bar | sort >expect.removed && + test_cmp expect.removed cruft.removed && + + # ...and contain the set of objects rolled up + test-tool pack-mtimes "$(basename $(cat cruft.new))" >actual.raw && + sort actual.raw >actual.objects && + + test_cmp expect.objects actual.objects + ) +' + +test_expect_success 'setup --max-cruft-size with freshened objects' ' + git init max-cruft-size-freshen && + ( + cd max-cruft-size-freshen && + + test_commit base && + git repack -ad && + + foo="$(generate_random_blob foo 64)" && + test-tool chmtime --get -10000 \ + "$objdir/$(test_oid_to_path "$foo")" >foo.mtime && + + git repack --cruft -d && + + cruft="$(ls $packdir/pack-*.mtimes)" && + test-tool pack-mtimes "$(basename $cruft)" >actual && + echo "$foo $(cat foo.mtime)" >expect && + test_cmp expect actual + ) +' + +test_expect_success '--max-cruft-size with freshened objects (loose)' ' + ( + cd max-cruft-size-freshen && + + # regenerate the object, setting its mtime to be more recent + foo="$(generate_random_blob foo 64)" && + test-tool chmtime --get -100 \ + "$objdir/$(test_oid_to_path "$foo")" >foo.mtime && + + git repack --cruft -d && + + cruft="$(ls $packdir/pack-*.mtimes)" && + test-tool pack-mtimes "$(basename $cruft)" >actual && + echo "$foo $(cat foo.mtime)" >expect && + test_cmp expect actual + ) +' + +test_expect_success '--max-cruft-size with freshened objects (packed)' ' + ( + cd max-cruft-size-freshen && + + # regenerate the object and store it in a packfile, + # setting its mtime to be more recent + # + # store it alongside another cruft object so that we + # do not create an identical copy of the existing + # cruft pack (which contains $foo). + foo="$(generate_random_blob foo 64)" && + bar="$(generate_random_blob bar 64)" && + foo_pack="$(printf "%s\n" $foo $bar | git pack-objects $packdir/pack)" && + git prune-packed && + + test-tool chmtime --get -10 \ + "$packdir/pack-$foo_pack.pack" >foo.mtime && + + git repack --cruft -d && + + cruft="$(ls $packdir/pack-*.mtimes)" && + test-tool pack-mtimes "$(basename $cruft)" >actual && + echo "$foo $(cat foo.mtime)" >expect.raw && + echo "$bar $(cat foo.mtime)" >>expect.raw && + sort expect.raw >expect && + test_cmp expect actual + ) +' + +test_expect_success '--max-cruft-size with pruning' ' + git init max-cruft-size-prune && + ( + cd max-cruft-size-prune && + + test_commit base && + foo="$(generate_random_blob foo $((1024*1024)))" && + bar="$(generate_random_blob bar $((1024*1024)))" && + baz="$(generate_random_blob baz $((1024*1024)))" && + + test-tool chmtime -10000 "$objdir/$(test_oid_to_path "$foo")" && + + git repack -d --cruft --max-cruft-size=1M && + + # backdate the mtimes of all cruft packs to validate + # that they were rewritten as a result of pruning + ls $packdir/pack-*.mtimes | sort >cruft.before && + for cruft in $(cat cruft.before) + do + mtime="$(test-tool chmtime --get -10000 "$cruft")" && + echo $cruft $mtime >>mtimes || return 1 + done && + + # repack (and prune) with a --max-cruft-size to ensure + # that we appropriately split the resulting set of packs + git repack -d --cruft --max-cruft-size=1M \ + --cruft-expiration=10.seconds.ago && + ls $packdir/pack-*.mtimes | sort >cruft.after && + + for cruft in $(cat cruft.after) + do + old_mtime="$(grep $cruft mtimes | cut -d" " -f2)" && + new_mtime="$(test-tool chmtime --get $cruft)" && + test $old_mtime -lt $new_mtime || return 1 + done && + + test_line_count = 3 cruft.before && + test_line_count = 2 cruft.after && + test_must_fail git cat-file -e $foo && + git cat-file -e $bar && + git cat-file -e $baz + ) +' + +test_expect_success '--max-cruft-size ignores non-local packs' ' + repo="max-cruft-size-non-local" && + git init $repo && + ( + cd $repo && + test_commit base && + generate_random_blob foo 64 && + git repack --cruft -d + ) && + + git clone --reference=$repo $repo $repo-alt && + ( + cd $repo-alt && + + test_commit other && + generate_random_blob bar 64 && + + # ensure that we do not attempt to pick up packs from + # the non-alternated repository, which would result in a + # crash + git repack --cruft --max-cruft-size=1M -d + ) +' + +test_expect_success 'reachable packs are preferred over cruft ones' ' + repo="cruft-preferred-packs" && + git init "$repo" && + ( + cd "$repo" && + + # This test needs to exercise careful control over when a MIDX + # is and is not written. Unset the corresponding TEST variable + # accordingly. + sane_unset GIT_TEST_MULTI_PACK_INDEX && + + test_commit base && + test_commit --no-tag cruft && + + non_cruft="$(echo base | git pack-objects --revs $packdir/pack)" && + # Write a cruft pack which both (a) sorts ahead of the non-cruft + # pack in lexical order, and (b) has an older mtime to appease + # the MIDX preferred pack selection routine. + cruft="$(echo pack-$non_cruft.pack | git pack-objects --cruft $packdir/pack-A)" && + test-tool chmtime -1000 $packdir/pack-A-$cruft.pack && + + test_commit other && + git repack -d && + + git repack --geometric 2 -d --write-midx --write-bitmap-index && + + # After repacking, there are two packs left: one reachable one + # (which is the result of combining both of the existing two + # non-cruft packs), and one cruft pack. + find .git/objects/pack -type f -name "*.pack" >packs && + test_line_count = 2 packs && + + # Make sure that the pack we just wrote is marked as preferred, + # not the cruft one. + pack="$(test-tool read-midx --preferred-pack $objdir)" && + test_path_is_missing "$packdir/$(basename "$pack" ".idx").mtimes" + ) +' + +test_done diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 487e326b3f..e56f5980dc 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -744,7 +744,15 @@ test_expect_success 'start and stop Linux/systemd maintenance' ' # start registers the repo git config --get --global --fixed-value maintenance.repo "$(pwd)" && - test_systemd_analyze_verify "systemd/user/git-maintenance@.service" && + for schedule in hourly daily weekly + do + test_path_is_file "systemd/user/git-maintenance@$schedule.timer" || return 1 + done && + test_path_is_file "systemd/user/git-maintenance@.service" && + + test_systemd_analyze_verify "systemd/user/git-maintenance@hourly.service" && + test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" && + test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" && printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect && test_cmp expect args && @@ -755,7 +763,10 @@ test_expect_success 'start and stop Linux/systemd maintenance' ' # stop does not unregister the repo git config --get --global --fixed-value maintenance.repo "$(pwd)" && - test_path_is_missing "systemd/user/git-maintenance@.timer" && + for schedule in hourly daily weekly + do + test_path_is_missing "systemd/user/git-maintenance@$schedule.timer" || return 1 + done && test_path_is_missing "systemd/user/git-maintenance@.service" && printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect && @@ -838,4 +849,17 @@ test_expect_success 'register and unregister bare repo' ' ) ' +test_expect_success 'failed schedule prevents config change' ' + git init --bare failcase && + + for scheduler in crontab launchctl schtasks systemctl + do + GIT_TEST_MAINT_SCHEDULER="$scheduler:false" && + export GIT_TEST_MAINT_SCHEDULER && + test_must_fail \ + git -C failcase maintenance start && + test_must_fail git -C failcase config maintenance.auto || return 1 + done +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index a60b05ad3f..263db3ad17 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -61,8 +61,8 @@ test_no_confirm () { --smtp-server="$(pwd)/fake.sendmail" \ $@ \ $patches >stdout && - ! grep "Send this email" stdout && - >no_confirm_okay + ! grep "Send this email" stdout && + >no_confirm_okay } # Exit immediately to prevent hang if a no-confirm test fails diff --git a/t/t9004-example.sh b/t/t9004-example.sh index 7e8894a4a7..590aab0304 100755 --- a/t/t9004-example.sh +++ b/t/t9004-example.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='check that example code compiles and runs' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'decorate' ' diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh index 872ad1c9c2..7869f45ee6 100755 --- a/t/t9211-scalar-clone.sh +++ b/t/t9211-scalar-clone.sh @@ -180,4 +180,16 @@ test_expect_success 'scalar clone warns when background maintenance fails' ' grep "could not turn on maintenance" err ' +test_expect_success '`scalar clone --no-src`' ' + scalar clone --src "file://$(pwd)/to-clone" with-src && + scalar clone --no-src "file://$(pwd)/to-clone" without-src && + + test_path_is_dir with-src/src && + test_path_is_missing without-src/src && + + (cd with-src/src && ls ?*) >with && + (cd without-src && ls ?*) >without && + test_cmp with without +' + test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 8835e16e81..a7c3b4eb63 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1622,14 +1622,22 @@ test_expect_success 'git checkout - with -d, complete only references' ' ' test_expect_success 'git switch - with --track, complete only remote branches' ' - test_completion "git switch --track " <<-\EOF + test_completion "git switch --track " <<-\EOF && + other/branch-in-other Z + other/main-in-other Z + EOF + test_completion "git switch -t " <<-\EOF other/branch-in-other Z other/main-in-other Z EOF ' test_expect_success 'git checkout - with --track, complete only remote branches' ' - test_completion "git checkout --track " <<-\EOF + test_completion "git checkout --track " <<-\EOF && + other/branch-in-other Z + other/main-in-other Z + EOF + test_completion "git checkout -t " <<-\EOF other/branch-in-other Z other/main-in-other Z EOF @@ -2456,6 +2464,24 @@ test_expect_success 'completion used <cmd> completion for alias: !f() { : git <c EOF ' +test_expect_success 'completion used <cmd> completion for alias: !f() { : <cmd> ; ... }' ' + test_config alias.co "!f() { : checkout ; if ... } f" && + test_completion "git co m" <<-\EOF + main Z + mybranch Z + mytag Z + EOF +' + +test_expect_success 'completion used <cmd> completion for alias: !f() { : <cmd>; ... }' ' + test_config alias.co "!f() { : checkout; if ... } f" && + test_completion "git co m" <<-\EOF + main Z + mybranch Z + mytag Z + EOF +' + test_expect_success 'completion without explicit _git_xxx function' ' test_completion "git version --" <<-\EOF --build-options Z diff --git a/t/test-lib.sh b/t/test-lib.sh index 293caf0f20..1656c9eed0 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -89,6 +89,9 @@ prepend_var LSAN_OPTIONS : $GIT_SAN_OPTIONS prepend_var LSAN_OPTIONS : fast_unwind_on_malloc=0 export LSAN_OPTIONS +prepend_var UBSAN_OPTIONS : $GIT_SAN_OPTIONS +export UBSAN_OPTIONS + if test ! -f "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS then echo >&2 'error: GIT-BUILD-OPTIONS missing (has Git been built?).' @@ -334,6 +337,7 @@ nr_san_dir_leaks_ () { find "$TEST_RESULTS_SAN_DIR" \ -type f \ -name "$TEST_RESULTS_SAN_FILE_PFX.*" 2>/dev/null | + xargs grep -lv "Unable to get registers from thread" | wc -l } diff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c index f26ec95ab4..d3ecac2772 100644 --- a/trace2/tr2_sysenv.c +++ b/trace2/tr2_sysenv.c @@ -58,7 +58,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = { /* clang-format on */ static int tr2_sysenv_cb(const char *key, const char *value, - const struct config_context *ctx UNUSED, void *d) + const struct config_context *ctx UNUSED, + void *d UNUSED) { int k; diff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c index 53091781ec..59910a1a4f 100644 --- a/trace2/tr2_tgt_event.c +++ b/trace2/tr2_tgt_event.c @@ -335,7 +335,7 @@ static void fn_alias_fl(const char *file, int line, const char *alias, } static void fn_child_start_fl(const char *file, int line, - uint64_t us_elapsed_absolute, + uint64_t us_elapsed_absolute UNUSED, const struct child_process *cmd) { const char *event_name = "child_start"; @@ -367,7 +367,8 @@ static void fn_child_start_fl(const char *file, int line, } static void fn_child_exit_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int cid, int pid, + uint64_t us_elapsed_absolute UNUSED, + int cid, int pid, int code, uint64_t us_elapsed_child) { const char *event_name = "child_exit"; @@ -388,7 +389,8 @@ static void fn_child_exit_fl(const char *file, int line, } static void fn_child_ready_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int cid, int pid, + uint64_t us_elapsed_absolute UNUSED, + int cid, int pid, const char *ready, uint64_t us_elapsed_child) { const char *event_name = "child_ready"; @@ -409,7 +411,7 @@ static void fn_child_ready_fl(const char *file, int line, } static void fn_thread_start_fl(const char *file, int line, - uint64_t us_elapsed_absolute) + uint64_t us_elapsed_absolute UNUSED) { const char *event_name = "thread_start"; struct json_writer jw = JSON_WRITER_INIT; @@ -423,7 +425,7 @@ static void fn_thread_start_fl(const char *file, int line, } static void fn_thread_exit_fl(const char *file, int line, - uint64_t us_elapsed_absolute, + uint64_t us_elapsed_absolute UNUSED, uint64_t us_elapsed_thread) { const char *event_name = "thread_exit"; @@ -439,7 +441,8 @@ static void fn_thread_exit_fl(const char *file, int line, jw_release(&jw); } -static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute, +static void fn_exec_fl(const char *file, int line, + uint64_t us_elapsed_absolute UNUSED, int exec_id, const char *exe, const char **argv) { const char *event_name = "exec"; @@ -460,8 +463,8 @@ static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute, } static void fn_exec_result_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int exec_id, - int code) + uint64_t us_elapsed_absolute UNUSED, + int exec_id, int code) { const char *event_name = "exec_result"; struct json_writer jw = JSON_WRITER_INIT; @@ -511,7 +514,7 @@ static void fn_repo_fl(const char *file, int line, } static void fn_region_enter_printf_va_fl(const char *file, int line, - uint64_t us_elapsed_absolute, + uint64_t us_elapsed_absolute UNUSED, const char *category, const char *label, const struct repository *repo, @@ -538,7 +541,7 @@ static void fn_region_enter_printf_va_fl(const char *file, int line, } static void fn_region_leave_printf_va_fl( - const char *file, int line, uint64_t us_elapsed_absolute, + const char *file, int line, uint64_t us_elapsed_absolute UNUSED, uint64_t us_elapsed_region, const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap) { diff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c index d25ea13164..38d5ebddf6 100644 --- a/trace2/tr2_tgt_normal.c +++ b/trace2/tr2_tgt_normal.c @@ -86,7 +86,7 @@ static void fn_version_fl(const char *file, int line) } static void fn_start_fl(const char *file, int line, - uint64_t us_elapsed_absolute, const char **argv) + uint64_t us_elapsed_absolute UNUSED, const char **argv) { struct strbuf buf_payload = STRBUF_INIT; @@ -215,7 +215,7 @@ static void fn_alias_fl(const char *file, int line, const char *alias, } static void fn_child_start_fl(const char *file, int line, - uint64_t us_elapsed_absolute, + uint64_t us_elapsed_absolute UNUSED, const struct child_process *cmd) { struct strbuf buf_payload = STRBUF_INIT; @@ -243,7 +243,8 @@ static void fn_child_start_fl(const char *file, int line, } static void fn_child_exit_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int cid, int pid, + uint64_t us_elapsed_absolute UNUSED, + int cid, int pid, int code, uint64_t us_elapsed_child) { struct strbuf buf_payload = STRBUF_INIT; @@ -256,7 +257,8 @@ static void fn_child_exit_fl(const char *file, int line, } static void fn_child_ready_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int cid, int pid, + uint64_t us_elapsed_absolute UNUSED, + int cid, int pid, const char *ready, uint64_t us_elapsed_child) { struct strbuf buf_payload = STRBUF_INIT; @@ -268,7 +270,8 @@ static void fn_child_ready_fl(const char *file, int line, strbuf_release(&buf_payload); } -static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute, +static void fn_exec_fl(const char *file, int line, + uint64_t us_elapsed_absolute UNUSED, int exec_id, const char *exe, const char **argv) { struct strbuf buf_payload = STRBUF_INIT; @@ -284,8 +287,8 @@ static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute, } static void fn_exec_result_fl(const char *file, int line, - uint64_t us_elapsed_absolute, int exec_id, - int code) + uint64_t us_elapsed_absolute UNUSED, + int exec_id, int code) { struct strbuf buf_payload = STRBUF_INIT; @@ -321,7 +324,8 @@ static void fn_repo_fl(const char *file, int line, } static void fn_printf_va_fl(const char *file, int line, - uint64_t us_elapsed_absolute, const char *fmt, + uint64_t us_elapsed_absolute UNUSED, + const char *fmt, va_list ap) { struct strbuf buf_payload = STRBUF_INIT; @@ -711,30 +711,35 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val, list_add_tail(&new_item->list, arg_head); } -static void process_command_line_args(struct list_head *arg_head, - struct list_head *new_trailer_head) +static void parse_trailers_from_config(struct list_head *config_head) { struct arg_item *item; - struct strbuf tok = STRBUF_INIT; - struct strbuf val = STRBUF_INIT; - const struct conf_info *conf; struct list_head *pos; - /* - * In command-line arguments, '=' is accepted (in addition to the - * separators that are defined). - */ - char *cl_separators = xstrfmt("=%s", separators); - /* Add an arg item for each configured trailer with a command */ list_for_each(pos, &conf_head) { item = list_entry(pos, struct arg_item, list); if (item->conf.command) - add_arg_item(arg_head, + add_arg_item(config_head, xstrdup(token_from_item(item, NULL)), xstrdup(""), &item->conf, NULL); } +} + +static void parse_trailers_from_command_line_args(struct list_head *arg_head, + struct list_head *new_trailer_head) +{ + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + const struct conf_info *conf; + struct list_head *pos; + + /* + * In command-line arguments, '=' is accepted (in addition to the + * separators that are defined). + */ + char *cl_separators = xstrfmt("=%s", separators); /* Add an arg item for each trailer on the command line */ list_for_each(pos, new_trailer_head) { @@ -961,28 +966,24 @@ static void unfold_value(struct strbuf *val) strbuf_release(&out); } -static size_t process_input_file(FILE *outfile, - const char *str, - struct list_head *head, - const struct process_trailer_options *opts) +/* + * Parse trailers in "str", populating the trailer info and "head" + * linked list structure. + */ +static void parse_trailers(struct trailer_info *info, + const char *str, + struct list_head *head, + const struct process_trailer_options *opts) { - struct trailer_info info; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; size_t i; - trailer_info_get(&info, str, opts); - - /* Print lines before the trailers as is */ - if (!opts->only_trailers) - fwrite(str, 1, info.trailer_start - str, outfile); + trailer_info_get(info, str, opts); - if (!opts->only_trailers && !info.blank_line_before_trailer) - fprintf(outfile, "\n"); - - for (i = 0; i < info.trailer_nr; i++) { + for (i = 0; i < info->trailer_nr; i++) { int separator_pos; - char *trailer = info.trailers[i]; + char *trailer = info->trailers[i]; if (trailer[0] == comment_line_char) continue; separator_pos = find_separator(trailer, separators); @@ -1002,10 +1003,6 @@ static size_t process_input_file(FILE *outfile, strbuf_detach(&val, NULL)); } } - - trailer_info_release(&info); - - return info.trailer_end - str; } static void free_all(struct list_head *head) @@ -1054,6 +1051,7 @@ void process_trailers(const char *file, { LIST_HEAD(head); struct strbuf sb = STRBUF_INIT; + struct trailer_info info; size_t trailer_end; FILE *outfile = stdout; @@ -1064,18 +1062,30 @@ void process_trailers(const char *file, if (opts->in_place) outfile = create_in_place_tempfile(file); + parse_trailers(&info, sb.buf, &head, opts); + trailer_end = info.trailer_end - sb.buf; + /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, sb.buf, &head, opts); + if (!opts->only_trailers) + fwrite(sb.buf, 1, info.trailer_start - sb.buf, outfile); + + if (!opts->only_trailers && !info.blank_line_before_trailer) + fprintf(outfile, "\n"); + if (!opts->only_input) { + LIST_HEAD(config_head); LIST_HEAD(arg_head); - process_command_line_args(&arg_head, new_trailer_head); + parse_trailers_from_config(&config_head); + parse_trailers_from_command_line_args(&arg_head, new_trailer_head); + list_splice(&config_head, &arg_head); process_trailers_lists(&head, &arg_head); } print_all(outfile, &head, opts); free_all(&head); + trailer_info_release(&info); /* Print the lines after the trailers as is */ if (!opts->only_trailers) @@ -1220,14 +1230,14 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg) strbuf_init(&iter->key, 0); strbuf_init(&iter->val, 0); opts.no_divider = 1; - trailer_info_get(&iter->info, msg, &opts); - iter->cur = 0; + trailer_info_get(&iter->internal.info, msg, &opts); + iter->internal.cur = 0; } int trailer_iterator_advance(struct trailer_iterator *iter) { - while (iter->cur < iter->info.trailer_nr) { - char *trailer = iter->info.trailers[iter->cur++]; + while (iter->internal.cur < iter->internal.info.trailer_nr) { + char *trailer = iter->internal.info.trailers[iter->internal.cur++]; int separator_pos = find_separator(trailer, separators); if (separator_pos < 1) @@ -1245,7 +1255,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter) void trailer_iterator_release(struct trailer_iterator *iter) { - trailer_info_release(&iter->info); + trailer_info_release(&iter->internal.info); strbuf_release(&iter->val); strbuf_release(&iter->key); } @@ -119,8 +119,10 @@ struct trailer_iterator { struct strbuf val; /* private */ - struct trailer_info info; - size_t cur; + struct { + struct trailer_info info; + size_t cur; + } internal; }; /* diff --git a/tree-diff.c b/tree-diff.c index 8fc159b86e..46107772d1 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -7,6 +7,7 @@ #include "hash.h" #include "tree.h" #include "tree-walk.h" +#include "environment.h" /* * Some mode bits are also used internally for computations. @@ -45,7 +46,8 @@ static struct combine_diff_path *ll_diff_tree_paths( struct combine_diff_path *p, const struct object_id *oid, const struct object_id **parents_oid, int nparent, - struct strbuf *base, struct diff_options *opt); + struct strbuf *base, struct diff_options *opt, + int depth); static void ll_diff_tree_oid(const struct object_id *old_oid, const struct object_id *new_oid, struct strbuf *base, struct diff_options *opt); @@ -196,7 +198,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last, static struct combine_diff_path *emit_path(struct combine_diff_path *p, struct strbuf *base, struct diff_options *opt, int nparent, struct tree_desc *t, struct tree_desc *tp, - int imin) + int imin, int depth) { unsigned short mode; const char *path; @@ -302,7 +304,8 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, strbuf_add(base, path, pathlen); strbuf_addch(base, '/'); - p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt); + p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt, + depth + 1); FAST_ARRAY_FREE(parents_oid, nparent); } @@ -423,12 +426,16 @@ static inline void update_tp_entries(struct tree_desc *tp, int nparent) static struct combine_diff_path *ll_diff_tree_paths( struct combine_diff_path *p, const struct object_id *oid, const struct object_id **parents_oid, int nparent, - struct strbuf *base, struct diff_options *opt) + struct strbuf *base, struct diff_options *opt, + int depth) { struct tree_desc t, *tp; void *ttree, **tptree; int i; + if (depth > max_allowed_tree_depth) + die("exceeded maximum allowed tree depth"); + FAST_ARRAY_ALLOC(tp, nparent); FAST_ARRAY_ALLOC(tptree, nparent); @@ -522,7 +529,7 @@ static struct combine_diff_path *ll_diff_tree_paths( /* D += {δ(t,pi) if pi=p[imin]; "+a" if pi > p[imin]} */ p = emit_path(p, base, opt, nparent, - &t, tp, imin); + &t, tp, imin, depth); skip_emit_t_tp: /* t↓, ∀ pi=p[imin] pi↓ */ @@ -534,7 +541,7 @@ static struct combine_diff_path *ll_diff_tree_paths( else if (cmp < 0) { /* D += "+t" */ p = emit_path(p, base, opt, nparent, - &t, /*tp=*/NULL, -1); + &t, /*tp=*/NULL, -1, depth); /* t↓ */ update_tree_entry(&t); @@ -550,7 +557,7 @@ static struct combine_diff_path *ll_diff_tree_paths( } p = emit_path(p, base, opt, nparent, - /*t=*/NULL, tp, imin); + /*t=*/NULL, tp, imin, depth); skip_emit_tp: /* ∀ pi=p[imin] pi↓ */ @@ -572,7 +579,7 @@ struct combine_diff_path *diff_tree_paths( const struct object_id **parents_oid, int nparent, struct strbuf *base, struct diff_options *opt) { - p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt); + p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt, 0); /* * free pre-allocated last element, if any diff --git a/tree-walk.c b/tree-walk.c index 29ead71be1..b517792ba2 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -9,6 +9,7 @@ #include "tree.h" #include "pathspec.h" #include "json-writer.h" +#include "environment.h" static const char *get_mode(const char *str, unsigned int *modep) { @@ -441,22 +442,25 @@ int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info) { - int error = 0; - struct name_entry entry[MAX_TRAVERSE_TREES]; + int ret = 0; + struct name_entry *entry; int i; - struct tree_desc_x tx[ARRAY_SIZE(entry)]; + struct tree_desc_x *tx; struct strbuf base = STRBUF_INIT; int interesting = 1; char *traverse_path; + if (traverse_trees_cur_depth > max_allowed_tree_depth) + return error("exceeded maximum allowed tree depth"); + traverse_trees_count++; traverse_trees_cur_depth++; if (traverse_trees_cur_depth > traverse_trees_max_depth) traverse_trees_max_depth = traverse_trees_cur_depth; - if (n >= ARRAY_SIZE(entry)) - BUG("traverse_trees() called with too many trees (%d)", n); + ALLOC_ARRAY(entry, n); + ALLOC_ARRAY(tx, n); for (i = 0; i < n; i++) { tx[i].d = t[i]; @@ -539,7 +543,7 @@ int traverse_trees(struct index_state *istate, if (interesting) { trees_used = info->fn(n, mask, dirmask, entry, info); if (trees_used < 0) { - error = trees_used; + ret = trees_used; if (!info->show_all_errors) break; } @@ -551,12 +555,14 @@ int traverse_trees(struct index_state *istate, } for (i = 0; i < n; i++) free_extended_entry(tx + i); + free(tx); + free(entry); free(traverse_path); info->traverse_path = NULL; strbuf_release(&base); traverse_trees_cur_depth--; - return error; + return ret; } struct dir_state { diff --git a/tree-walk.h b/tree-walk.h index 74cdceb3fe..a6bfa3da3a 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -6,8 +6,6 @@ struct index_state; struct repository; -#define MAX_TRAVERSE_TREES 8 - /** * The tree walking API is used to traverse and inspect trees. */ @@ -10,11 +10,13 @@ #include "alloc.h" #include "tree-walk.h" #include "repository.h" +#include "environment.h" const char *tree_type = "tree"; int read_tree_at(struct repository *r, struct tree *tree, struct strbuf *base, + int depth, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) { @@ -24,6 +26,9 @@ int read_tree_at(struct repository *r, int len, oldlen = base->len; enum interesting retval = entry_not_interesting; + if (depth > max_allowed_tree_depth) + return error("exceeded maximum allowed tree depth"); + if (parse_tree(tree)) return -1; @@ -74,7 +79,7 @@ int read_tree_at(struct repository *r, strbuf_add(base, entry.path, len); strbuf_addch(base, '/'); retval = read_tree_at(r, lookup_tree(r, &oid), - base, pathspec, + base, depth + 1, pathspec, fn, context); strbuf_setlen(base, oldlen); if (retval) @@ -89,7 +94,7 @@ int read_tree(struct repository *r, read_tree_fn_t fn, void *context) { struct strbuf sb = STRBUF_INIT; - int ret = read_tree_at(r, tree, &sb, pathspec, fn, context); + int ret = read_tree_at(r, tree, &sb, 0, pathspec, fn, context); strbuf_release(&sb); return ret; } @@ -44,6 +44,7 @@ typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const c int read_tree_at(struct repository *r, struct tree *tree, struct strbuf *base, + int depth, const struct pathspec *pathspec, read_tree_fn_t fn, void *context); diff --git a/unicode-width.h b/unicode-width.h index e15fb0455b..be5bf8c4f2 100644 --- a/unicode-width.h +++ b/unicode-width.h @@ -396,14 +396,13 @@ static const struct interval double_width[] = { { 0x2E80, 0x2E99 }, { 0x2E9B, 0x2EF3 }, { 0x2F00, 0x2FD5 }, -{ 0x2FF0, 0x2FFB }, -{ 0x3000, 0x303E }, +{ 0x2FF0, 0x303E }, { 0x3041, 0x3096 }, { 0x3099, 0x30FF }, { 0x3105, 0x312F }, { 0x3131, 0x318E }, { 0x3190, 0x31E3 }, -{ 0x31F0, 0x321E }, +{ 0x31EF, 0x321E }, { 0x3220, 0x3247 }, { 0x3250, 0x4DBF }, { 0x4E00, 0xA48C }, diff --git a/unpack-trees.c b/unpack-trees.c index 87517364dc..c2b20b80d5 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -2,7 +2,7 @@ #include "advice.h" #include "strvec.h" #include "repository.h" -#include "config.h" +#include "parse.h" #include "dir.h" #include "environment.h" #include "gettext.h" @@ -864,8 +864,8 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, struct unpack_trees_options *o = info->data; int i, ret, bottom; int nr_buf = 0; - struct tree_desc t[MAX_UNPACK_TREES]; - void *buf[MAX_UNPACK_TREES]; + struct tree_desc *t; + void **buf; struct traverse_info newinfo; struct name_entry *p; int nr_entries; @@ -902,6 +902,9 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, newinfo.pathlen = st_add3(newinfo.pathlen, tree_entry_len(p), 1); newinfo.df_conflicts |= df_conflicts; + ALLOC_ARRAY(t, n); + ALLOC_ARRAY(buf, n); + /* * Fetch the tree from the ODB for each peer directory in the * n commits. @@ -937,6 +940,8 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, for (i = 0; i < nr_buf; i++) free(buf[i]); + free(buf); + free(t); return ret; } diff --git a/unpack-trees.h b/unpack-trees.h index 9b827c307f..5867e26e17 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -7,7 +7,7 @@ #include "string-list.h" #include "tree-walk.h" -#define MAX_UNPACK_TREES MAX_TRAVERSE_TREES +#define MAX_UNPACK_TREES 8 struct cache_entry; struct unpack_trees_options; diff --git a/upload-pack.c b/upload-pack.c index 94751477ab..83f3d2651a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -801,11 +801,12 @@ error: for (i = 0; i < data->want_obj.nr; i++) { struct object *o = data->want_obj.objects[i].item; if (!is_our_ref(o, data->allow_uor)) { + error("git upload-pack: not our ref %s", + oid_to_hex(&o->oid)); packet_writer_error(&data->writer, "upload-pack: not our ref %s", oid_to_hex(&o->oid)); - die("git upload-pack: not our ref %s", - oid_to_hex(&o->oid)); + exit(128); } } } @@ -1,5 +1,5 @@ #include "git-compat-util.h" -#include "hex.h" +#include "hex-ll.h" #include "strbuf.h" #include "url.h" diff --git a/urlmatch.c b/urlmatch.c index 1c45f23adf..1d0254abac 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -1,6 +1,6 @@ #include "git-compat-util.h" #include "gettext.h" -#include "hex.h" +#include "hex-ll.h" #include "strbuf.h" #include "urlmatch.h" diff --git a/worktree.c b/worktree.c index b8cf29e6a1..a56a6c2a3d 100644 --- a/worktree.c +++ b/worktree.c @@ -581,8 +581,10 @@ static void repair_gitfile(struct worktree *wt, strbuf_release(&dotgit); } -static void repair_noop(int iserr, const char *path, const char *msg, - void *cb_data) +static void repair_noop(int iserr UNUSED, + const char *path UNUSED, + const char *msg UNUSED, + void *cb_data UNUSED) { /* nothing */ } @@ -3,9 +3,8 @@ */ #include "git-compat-util.h" #include "abspath.h" -#include "config.h" +#include "parse.h" #include "gettext.h" -#include "object.h" #include "repository.h" #include "strbuf.h" #include "trace2.h" @@ -632,11 +631,6 @@ int rmdir_or_warn(const char *file) return warn_if_unremovable("rmdir", file, rmdir(file)); } -int remove_or_warn(unsigned int mode, const char *file) -{ - return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); -} - static int access_error_is_ok(int err, unsigned flag) { return (is_missing_file_error(err) || @@ -819,3 +813,13 @@ int csprng_bytes(void *buf, size_t len) return 0; #endif } + +uint32_t git_rand(void) +{ + uint32_t result; + + if (csprng_bytes(&result, sizeof(result)) < 0) + die(_("unable to get random bytes")); + + return result; +} @@ -106,11 +106,6 @@ int unlink_or_msg(const char *file, struct strbuf *err); * not exist. */ int rmdir_or_warn(const char *path); -/* - * Calls the correct function out of {unlink,rmdir}_or_warn based on - * the supplied file mode. - */ -int remove_or_warn(unsigned int mode, const char *path); /* * Call access(2), but warn for any error except "missing file" @@ -139,4 +134,10 @@ void sleep_millisec(int millisec); */ int csprng_bytes(void *buf, size_t len); +/* + * Returns a random uint32_t, uniformly distributed across all possible + * values. + */ +uint32_t git_rand(void); + #endif /* WRAPPER_H */ diff --git a/write-or-die.c b/write-or-die.c index d8355c0c3e..42a2dc73cd 100644 --- a/write-or-die.c +++ b/write-or-die.c @@ -1,5 +1,5 @@ #include "git-compat-util.h" -#include "config.h" +#include "parse.h" #include "run-command.h" #include "write-or-die.h" diff --git a/wt-status.c b/wt-status.c index 5b1378965c..9f45bf6949 100644 --- a/wt-status.c +++ b/wt-status.c @@ -675,7 +675,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) rev.diffopt.flags.recursive = 1; copy_pathspec(&rev.prune_data, &s->pathspec); - run_diff_index(&rev, 1); + run_diff_index(&rev, DIFF_INDEX_CACHED); release_revisions(&rev); } @@ -739,7 +739,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s) ps.max_depth = -1; strbuf_add(&base, ce->name, ce->ce_namelen); - read_tree_at(istate->repo, tree, &base, &ps, + read_tree_at(istate->repo, tree, &base, 0, &ps, add_file_to_list, s); continue; } @@ -1156,7 +1156,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s) rev.diffopt.a_prefix = "c/"; rev.diffopt.b_prefix = "i/"; } /* else use prefix as per user config */ - run_diff_index(&rev, 1); + run_diff_index(&rev, DIFF_INDEX_CACHED); if (s->verbose > 1 && wt_status_check_worktree_changes(s, &dirty_submodules)) { status_printf_ln(s, c, @@ -2580,8 +2580,8 @@ int has_unstaged_changes(struct repository *r, int ignore_submodules) } rev_info.diffopt.flags.quick = 1; diff_setup_done(&rev_info.diffopt); - result = run_diff_files(&rev_info, 0); - result = diff_result_code(&rev_info.diffopt, result); + run_diff_files(&rev_info, 0); + result = diff_result_code(&rev_info.diffopt); release_revisions(&rev_info); return result; } @@ -2614,8 +2614,8 @@ int has_uncommitted_changes(struct repository *r, } diff_setup_done(&rev_info.diffopt); - result = run_diff_index(&rev_info, 1); - result = diff_result_code(&rev_info.diffopt, result); + run_diff_index(&rev_info, DIFF_INDEX_CACHED); + result = diff_result_code(&rev_info.diffopt); release_revisions(&rev_info); return result; } @@ -2655,8 +2655,12 @@ int require_clean_work_tree(struct repository *r, } if (err) { - if (hint) + if (hint) { + if (!*hint) + BUG("empty hint passed to require_clean_work_tree();" + " use NULL instead"); error("%s", hint); + } if (!gently) exit(128); } |