diff options
264 files changed, 6085 insertions, 4677 deletions
diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml new file mode 100644 index 0000000000..27f72f0ff3 --- /dev/null +++ b/.github/workflows/l10n.yml @@ -0,0 +1,105 @@ +name: git-l10n + +on: [push, pull_request_target] + +jobs: + git-po-helper: + if: >- + endsWith(github.repository, '/git-po') || + contains(github.head_ref, 'l10n') || + contains(github.ref, 'l10n') + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Setup base and head objects + id: setup-tips + run: | + if test "${{ github.event_name }}" = "pull_request_target" + then + base=${{ github.event.pull_request.base.sha }} + head=${{ github.event.pull_request.head.sha }} + else + base=${{ github.event.before }} + head=${{ github.event.after }} + fi + echo "::set-output name=base::$base" + echo "::set-output name=head::$head" + - name: Run partial clone + run: | + git -c init.defaultBranch=master init --bare . + git remote add \ + --mirror=fetch \ + origin \ + https://github.com/${{ github.repository }} + # Fetch tips that may be unreachable from github.ref: + # - For a forced push, "$base" may be unreachable. + # - For a "pull_request_target" event, "$head" may be unreachable. + args= + for commit in \ + ${{ steps.setup-tips.outputs.base }} \ + ${{ steps.setup-tips.outputs.head }} + do + case $commit in + *[^0]*) + args="$args $commit" + ;; + *) + # Should not fetch ZERO-OID. + ;; + esac + done + git -c protocol.version=2 fetch \ + --progress \ + --no-tags \ + --no-write-fetch-head \ + --filter=blob:none \ + origin \ + ${{ github.ref }} \ + $args + - uses: actions/setup-go@v2 + with: + go-version: '>=1.16' + - name: Install git-po-helper + run: go install github.com/git-l10n/git-po-helper@main + - name: Install other dependencies + run: | + sudo apt-get update -q && + sudo apt-get install -q -y gettext + - name: Run git-po-helper + id: check-commits + run: | + exit_code=0 + git-po-helper check-commits \ + --github-action-event="${{ github.event_name }}" -- \ + ${{ steps.setup-tips.outputs.base }}..${{ steps.setup-tips.outputs.head }} \ + >git-po-helper.out 2>&1 || exit_code=$? + if test $exit_code -ne 0 || grep -q WARNING git-po-helper.out + then + # Remove ANSI colors which are proper for console logs but not + # proper for PR comment. + echo "COMMENT_BODY<<EOF" >>$GITHUB_ENV + perl -pe 's/\e\[[0-9;]*m//g; s/\bEOF$//g' git-po-helper.out >>$GITHUB_ENV + echo "EOF" >>$GITHUB_ENV + fi + cat git-po-helper.out + exit $exit_code + - name: Create comment in pull request for report + uses: mshick/add-pr-comment@v1 + if: >- + always() && + github.event_name == 'pull_request_target' && + env.COMMENT_BODY != '' + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'github-actions[bot]' + message: > + ${{ steps.check-commits.outcome == 'failure' && 'Errors and warnings' || 'Warnings' }} + found by [git-po-helper](https://github.com/git-l10n/git-po-helper#readme) in workflow + [#${{ github.run_number }}](${{ env.GITHUB_SERVER_URL }}/${{ github.repository }}/actions/runs/${{ github.run_id }}): + + ``` + + ${{ env.COMMENT_BODY }} + + ``` diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b053b01c66..6ed6a9e807 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,7 +88,7 @@ jobs: env: HOME: ${{runner.workspace}} NO_PERL: 1 - run: ci/make-test-artifacts.sh artifacts + run: . /etc/profile && ci/make-test-artifacts.sh artifacts - name: zip up tracked files run: git archive -o artifacts/tracked.tar.gz HEAD - name: upload tracked files and build artifacts @@ -115,7 +115,7 @@ jobs: - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: test shell: bash - run: ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 - name: ci/print-test-failures.sh if: failure() shell: bash @@ -198,8 +198,7 @@ jobs: shell: bash env: NO_SVN_TESTS: 1 - GIT_TEST_SKIP_REBASE_P: 1 - run: ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 - name: ci/print-test-failures.sh if: failure() shell: bash @@ -232,6 +231,9 @@ jobs: - jobname: linux-gcc-default cc: gcc pool: ubuntu-latest + - jobname: linux-leaks + cc: gcc + pool: ubuntu-latest env: CC: ${{matrix.vector.cc}} jobname: ${{matrix.vector.jobname}} diff --git a/.gitignore b/.gitignore index 311841f9be..054249b20a 100644 --- a/.gitignore +++ b/.gitignore @@ -125,7 +125,6 @@ /git-range-diff /git-read-tree /git-rebase -/git-rebase--preserve-merges /git-receive-pack /git-reflog /git-remote @@ -190,6 +189,7 @@ /gitweb/static/gitweb.min.* /config-list.h /command-list.h +/hook-list.h *.tar.gz *.dsc *.deb @@ -224,6 +224,7 @@ *.lib *.res *.sln +*.sp *.suo *.ncb *.vcproj diff --git a/Documentation/Makefile b/Documentation/Makefile index f5605b7767..2021568cd5 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -90,6 +90,7 @@ SP_ARTICLES += $(API_DOCS) TECH_DOCS += MyFirstContribution TECH_DOCS += MyFirstObjectWalk TECH_DOCS += SubmittingPatches +TECH_DOCS += technical/bundle-format TECH_DOCS += technical/hash-function-transition TECH_DOCS += technical/http-protocol TECH_DOCS += technical/index-format diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index 015cf24631..b20bc8e914 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -1029,22 +1029,42 @@ kidding - be patient!) [[v2-git-send-email]] === Sending v2 -Skip ahead to <<reviewing,Responding to Reviews>> for information on how to -handle comments from reviewers. Continue this section when your topic branch is -shaped the way you want it to look for your patchset v2. +This section will focus on how to send a v2 of your patchset. To learn what +should go into v2, skip ahead to <<reviewing,Responding to Reviews>> for +information on how to handle comments from reviewers. + +We'll reuse our `psuh` topic branch for v2. Before we make any changes, we'll +mark the tip of our v1 branch for easy reference: -When you're ready with the next iteration of your patch, the process is fairly -similar. +---- +$ git checkout psuh +$ git branch psuh-v1 +---- -First, generate your v2 patches again: +Refine your patch series by using `git rebase -i` to adjust commits based upon +reviewer comments. Once the patch series is ready for submission, generate your +patches again, but with some new flags: ---- -$ git format-patch -v2 --cover-letter -o psuh/ master..psuh +$ git format-patch -v2 --cover-letter -o psuh/ --range-diff master..psuh-v1 master.. ---- -This will add your v2 patches, all named like `v2-000n-my-commit-subject.patch`, -to the `psuh/` directory. You may notice that they are sitting alongside the v1 -patches; that's fine, but be careful when you are ready to send them. +The `--range-diff master..psuh-v1` parameter tells `format-patch` to include a +range-diff between `psuh-v1` and `psuh` in the cover letter (see +linkgit:git-range-diff[1]). This helps tell reviewers about the differences +between your v1 and v2 patches. + +The `-v2` parameter tells `format-patch` to output your patches +as version "2". For instance, you may notice that your v2 patches are +all named like `v2-000n-my-commit-subject.patch`. `-v2` will also format +your patches by prefixing them with "[PATCH v2]" instead of "[PATCH]", +and your range-diff will be prefaced with "Range-diff against v1". + +Afer you run this command, `format-patch` will output the patches to the `psuh/` +directory, alongside the v1 patches. Using a single directory makes it easy to +refer to the old v1 patches while proofreading the v2 patches, but you will need +to be careful to send out only the v2 patches. We will use a pattern like +"psuh/v2-*.patch" (not "psuh/*.patch", which would match v1 and v2 patches). Edit your cover letter again. Now is a good time to mention what's different between your last version and now, if it's something significant. You do not @@ -1082,7 +1102,7 @@ to the command: ---- $ git send-email --to=target@example.com --in-reply-to="<foo.12345.author@example.com>" - psuh/v2* + psuh/v2-*.patch ---- [[single-patch]] diff --git a/Documentation/RelNotes/2.33.1.txt b/Documentation/RelNotes/2.33.1.txt new file mode 100644 index 0000000000..b71738e654 --- /dev/null +++ b/Documentation/RelNotes/2.33.1.txt @@ -0,0 +1,138 @@ +Git 2.33.1 Release Notes +======================== + +This primarily is to backport various fixes accumulated during the +development towards Git 2.34, the next feature release. + + +Fixes since v2.33 +----------------- + + * The unicode character width table (used for output alignment) has + been updated. + + * Input validation of "git pack-objects --stdin-packs" has been + corrected. + + * Bugfix for common ancestor negotiation recently introduced in "git + push" codepath. + + * "git pull" had various corner cases that were not well thought out + around its --rebase backend, e.g. "git pull --ff-only" did not stop + but went ahead and rebased when the history on other side is not a + descendant of our history. The series tries to fix them up. + + * "git apply" miscounted the bytes and failed to read to the end of + binary hunks. + + * "git range-diff" code clean-up. + + * "git commit --fixup" now works with "--edit" again, after it was + broken in v2.32. + + * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the + new version has a blocker bug for that architecture. + + * Checking out all the paths from HEAD during the last conflicted + step in "git rebase" and continuing would cause the step to be + skipped (which is expected), but leaves MERGE_MSG file behind in + $GIT_DIR and confuses the next "git commit", which has been + corrected. + + * Various bugs in "git rebase -r" have been fixed. + + * mmap() imitation used to call xmalloc() that dies upon malloc() + failure, which has been corrected to just return an error to the + caller to be handled. + + * "git diff --relative" segfaulted and/or produced incorrect result + when there are unmerged paths. + + * The delayed checkout code path in "git checkout" etc. were chatty + even when --quiet and/or --no-progress options were given. + + * "git branch -D <branch>" used to refuse to remove a broken branch + ref that points at a missing commit, which has been corrected. + + * Build update for Apple clang. + + * The parser for the "--nl" option of "git column" has been + corrected. + + * "git upload-pack" which runs on the other side of "git fetch" + forgot to take the ref namespaces into account when handling + want-ref requests. + + * The sparse-index support can corrupt the index structure by storing + a stale and/or uninitialized data, which has been corrected. + + * Buggy tests could damage repositories outside the throw-away test + area we created. We now by default export GIT_CEILING_DIRECTORIES + to limit the damage from such a stray test. + + * Even when running "git send-email" without its own threaded + discussion support, a threading related header in one message is + carried over to the subsequent message to result in an unwanted + threading, which has been corrected. + + * The output from "git fast-export", when its anonymization feature + is in use, showed an annotated tag incorrectly. + + * Recent "diff -m" changes broke "gitk", which has been corrected. + + * "git maintenance" scheduler fix for macOS. + + * A pathname in an advice message has been made cut-and-paste ready. + + * The "git apply -3" code path learned not to bother the lower level + merge machinery when the three-way merge can be trivially resolved + without the content level merge. + + * The code that optionally creates the *.rev reverse index file has + been optimized to avoid needless computation when it is not writing + the file out. + + * "git range-diff -I... <range> <range>" segfaulted, which has been + corrected. + + * The order in which various files that make up a single (conceptual) + packfile has been reevaluated and straightened up. This matters in + correctness, as an incomplete set of files must not be shown to a + running Git. + + * The "mode" word is useless in a call to open(2) that does not + create a new file. Such a call in the files backend of the ref + subsystem has been cleaned up. + + * "git update-ref --stdin" failed to flush its output as needed, + which potentially led the conversation to a deadlock. + + * When "git am --abort" fails to abort correctly, it still exited + with exit status of 0, which has been corrected. + + * Correct nr and alloc members of strvec struct to be of type size_t. + + * "git stash", where the tentative change involves changing a + directory to a file (or vice versa), was confused, which has been + corrected. + + * "git clone" from a repository whose HEAD is unborn into a bare + repository didn't follow the branch name the other side used, which + is corrected. + + * "git cvsserver" had a long-standing bug in its authentication code, + which has finally been corrected (it is unclear and is a separate + question if anybody is seriously using it, though). + + * "git difftool --dir-diff" mishandled symbolic links. + + * Sensitive data in the HTTP trace were supposed to be redacted, but + we failed to do so in HTTP/2 requests. + + * "make clean" has been updated to remove leftover .depend/ + directories, even when it is not told to use them to compute header + dependencies. + + * Protocol v0 clients can get stuck parsing a malformed feature line. + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.34.0.txt b/Documentation/RelNotes/2.34.0.txt index fa8ecedb9e..c85385dc03 100644 --- a/Documentation/RelNotes/2.34.0.txt +++ b/Documentation/RelNotes/2.34.0.txt @@ -4,6 +4,11 @@ Git 2.34 Release Notes Updates since Git 2.33 ---------------------- +Backward compatibility notes + + * The "--preserve-merges" option of "git rebase" has been removed. + + UI, Workflows & Features * Pathname expansion (like "~username/") learned a way to specify a @@ -56,6 +61,19 @@ UI, Workflows & Features * The error in "git help no-such-git-command" is handled better. + * The unicode character width table (used for output alignment) has + been updated. + + * The ref iteration code used to optionally allow dangling refs to be + shown, which has been tightened up. + + * "git add", "git mv", and "git rm" have been adjusted to avoid + updating paths outside of the sparse-checkout definition unless + the user specifies a "--sparse" option. + + * "git repack" has been taught to generate multi-pack reachability + bitmaps. + Performance, Internal Implementation, Development Support etc. @@ -130,176 +148,198 @@ Performance, Internal Implementation, Development Support etc. * An oddball OPTION_ARGUMENT feature has been removed from the parse-options API. + * The mergesort implementation used to sort linked list has been + optimized. + + * Remove external declaration of functions that no longer exist. + + * "git multi-pack-index write --bitmap" learns to propagate the + hashcache from original bitmap to resulting bitmap. + + * CI learns to run the leak sanitizer builds. + + * "git grep --recurse-submodules" takes trees and blobs from the + submodule repository, but the textconv settings when processing a + blob from the submodule is not taken from the submodule repository. + A test is added to demonstrate the issue, without fixing it. + + * Teach "git help -c" into helping the command line completion of + configuration variables. + + * When "git cmd -h" shows more than one line of usage text (e.g. + the cmd subcommand may take sub-sub-command), parse-options API + learned to align these lines, even across i18n/l10n. + + * Prevent "make sparse" from running for the source files that + haven't been modified. + Fixes since v2.33 ----------------- * Input validation of "git pack-objects --stdin-packs" has been corrected. - (merge 561fa03529 ab/pack-stdin-packs-fix later to maint). * Bugfix for common ancestor negotiation recently introduced in "git push" code path. - (merge 82823118b9 jt/push-negotiation-fixes later to maint). * "git pull" had various corner cases that were not well thought out around its --rebase backend, e.g. "git pull --ff-only" did not stop but went ahead and rebased when the history on other side is not a descendant of our history. The series tries to fix them up. - (merge 6f843a3355 en/pull-conflicting-options later to maint). * "git apply" miscounted the bytes and failed to read to the end of binary hunks. - (merge 46d723ce57 jk/apply-binary-hunk-parsing-fix later to maint). * "git range-diff" code clean-up. - (merge c4d5907324 jk/range-diff-fixes later to maint). * "git commit --fixup" now works with "--edit" again, after it was broken in v2.32. - (merge 8ef6aad664 jk/commit-edit-fixup-fix later to maint). * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the new version has a blocker bug for that architecture. - (merge 3cf9bb36bf cb/ci-use-upload-artifacts-v1 later to maint). * Checking out all the paths from HEAD during the last conflicted step in "git rebase" and continuing would cause the step to be skipped (which is expected), but leaves MERGE_MSG file behind in $GIT_DIR and confuses the next "git commit", which has been corrected. - (merge e5ee33e855 pw/rebase-skip-final-fix later to maint). * Various bugs in "git rebase -r" have been fixed. - (merge f2563c9ef3 pw/rebase-r-fixes later to maint). * mmap() imitation used to call xmalloc() that dies upon malloc() failure, which has been corrected to just return an error to the caller to be handled. - (merge 95b4ff3931 rs/git-mmap-uses-malloc later to maint). * "git diff --relative" segfaulted and/or produced incorrect result when there are unmerged paths. - (merge 8174627b3d dd/diff-files-unmerged-fix later to maint). * The delayed checkout code path in "git checkout" etc. were chatty even when --quiet and/or --no-progress options were given. - (merge 7a132c628e mt/quiet-with-delayed-checkout later to maint). * "git branch -D <branch>" used to refuse to remove a broken branch ref that points at a missing commit, which has been corrected. - (merge 597a977489 rs/branch-allow-deleting-dangling later to maint). * Build update for Apple clang. - (merge f32c5d3716 cb/makefile-apple-clang later to maint). * The parser for the "--nl" option of "git column" has been corrected. - (merge c93ca46cf5 sg/column-nl later to maint). * "git upload-pack" which runs on the other side of "git fetch" forgot to take the ref namespaces into account when handling want-ref requests. - (merge 53a66ec37c ka/want-ref-in-namespace later to maint). * The sparse-index support can corrupt the index structure by storing a stale and/or uninitialized data, which has been corrected. - (merge d9e9b44d7a jh/sparse-index-resize-fix later to maint). * Buggy tests could damage repositories outside the throw-away test area we created. We now by default export GIT_CEILING_DIRECTORIES to limit the damage from such a stray test. - (merge 614c3d8f2e sg/set-ceiling-during-tests later to maint). * Even when running "git send-email" without its own threaded discussion support, a threading related header in one message is carried over to the subsequent message to result in an unwanted threading, which has been corrected. - (merge e082113484 mh/send-email-reset-in-reply-to later to maint). * The output from "git fast-export", when its anonymization feature is in use, showed an annotated tag incorrectly. - (merge 2f040a9671 tk/fast-export-anonymized-tag-fix later to maint). * Doc update plus improved error reporting. - (merge 1e93770888 jk/log-warn-on-bogus-encoding later to maint). * Recent "diff -m" changes broke "gitk", which has been corrected. - (merge 5acffd3473 so/diff-index-regression-fix later to maint). * Regression fix. - (merge b996f84989 ab/send-email-config-fix later to maint). * The "git apply -3" code path learned not to bother the lower level merge machinery when the three-way merge can be trivially resolved without the content level merge. This fixes a regression caused by recent "-3way first and fall back to direct application" change. - (merge 57f183b698 jc/trivial-threeway-binary-merge later to maint). * The code that optionally creates the *.rev reverse index file has been optimized to avoid needless computation when it is not writing the file out. - (merge 8fe8bae9d2 ab/reverse-midx-optim later to maint). * "git range-diff -I... <range> <range>" segfaulted, which has been corrected. - (merge 709b3f32d3 rs/range-diff-avoid-segfault-with-I later to maint). * The order in which various files that make up a single (conceptual) packfile has been reevaluated and straightened up. This matters in correctness, as an incomplete set of files must not be shown to a running Git. - (merge 4bc1fd6e39 tb/pack-finalize-ordering later to maint). * The "mode" word is useless in a call to open(2) that does not create a new file. Such a call in the files backend of the ref subsystem has been cleaned up. - (merge 35cf94eaf6 rs/no-mode-to-open-when-appending later to maint). * "git update-ref --stdin" failed to flush its output as needed, which potentially led the conversation to a deadlock. - (merge 7c1200745b ps/update-ref-batch-flush later to maint). * When "git am --abort" fails to abort correctly, it still exited with exit status of 0, which has been corrected. - (merge c5ead19ea2 en/am-abort-fix later to maint). * Correct nr and alloc members of strvec struct to be of type size_t. - (merge 8d133a4653 jk/strvec-typefix later to maint). + + * "git stash", where the tentative change involves changing a + directory to a file (or vice versa), was confused, which has been + corrected. + + * "git clone" from a repository whose HEAD is unborn into a bare + repository didn't follow the branch name the other side used, which + is corrected. + + * "git cvsserver" had a long-standing bug in its authentication code, + which has finally been corrected (it is unclear and is a separate + question if anybody is seriously using it, though). + + * "git difftool --dir-diff" mishandled symbolic links. + + * Sensitive data in the HTTP trace were supposed to be redacted, but + we failed to do so in HTTP/2 requests. + + * "make clean" has been updated to remove leftover .depend/ + directories, even when it is not told to use them to compute header + dependencies. + + * Protocol v0 clients can get stuck parsing a malformed feature line. + + * A few kinds of changes "git status" can show were not documented. + (merge d2a534c515 ja/doc-status-types-and-copies later to maint). + + * The mergesort implementation used to sort linked list has been + optimized. + (merge c90cfc225b rs/mergesort later to maint). + + * An editor session launched during a Git operation (e.g. during 'git + commit') can leave the terminal in a funny state. The code path + has updated to save the terminal state before, and restore it + after, it spawns an editor. + (merge 3d411afabc cm/save-restore-terminal later to maint). + + * "git cat-file --batch" with the "--batch-all-objects" option is + supposed to iterate over all the objects found in a repository, but + it used to translate these object names using the replace mechanism, + which defeats the point of enumerating all objects in the repository. + This has been corrected. + (merge bf972896d7 jk/cat-file-batch-all-wo-replace later to maint). + + * Recent sparse-index work broke safety against attempts to add paths + with trailing slashes to the index, which has been corrected. + (merge c8ad9d04c6 rs/make-verify-path-really-verify-again later to maint). + + * The "--color-lines" and "--color-by-age" options of "git blame" + have been missing, which are now documented. + (merge 8c32856133 bs/doc-blame-color-lines later to maint). + + * The PATH used in CI job may be too wide and let incompatible dlls + to be grabbed, which can cause the build&test to fail. Tighten it. + (merge 7491ef6198 js/windows-ci-path-fix later to maint). * Other code cleanup, docfix, build fix, etc. - (merge 1d9c8daef8 ab/bundle-doc later to maint). - (merge 81483fe613 en/merge-strategy-docs later to maint). - (merge 626beebdf8 js/log-protocol-version later to maint). - (merge 00e302da76 cb/builtin-merge-format-string-fix later to maint). - (merge ad51ae4dc0 cb/ci-freebsd-update later to maint). - (merge be6444d1ca fc/completion-updates later to maint). - (merge ff7b83f562 ti/tcsh-completion-regression-fix later to maint). - (merge 325b06deda sg/make-fix-ar-invocation later to maint). - (merge bd72824c60 me/t5582-cleanup later to maint). - (merge f6a5af0f62 ga/send-email-sendmail-cmd later to maint). - (merge f58c7468cd ab/ls-remote-packet-trace later to maint). - (merge 0160f7e725 ab/rebase-fatal-fatal-fix later to maint). - (merge a16eb6b1ff js/maintenance-launchctl-fix later to maint). - (merge c21b2511c2 jk/t5323-no-pack-test-fix later to maint). - (merge 5146c2f148 mh/credential-leakfix later to maint). - (merge 1549577338 dd/t6300-wo-gpg-fix later to maint). - (merge 66e905b7dd rs/xopen-reports-open-failures later to maint). - (merge 469888e6a5 es/walken-tutorial-fix later to maint). - (merge 88682b016d ba/object-info later to maint). - (merge b45c172e51 ab/gc-log-rephrase later to maint). - (merge ccdd5d1eb1 ab/mailmap-leakfix later to maint). - (merge 6540b71614 cb/remote-ndebug-fix later to maint). - (merge e4f8d27585 rs/show-branch-simplify later to maint). - (merge e124ecf7f7 rs/archive-use-object-id later to maint). - (merge cebead1ebf cb/ci-build-pedantic later to maint). - (merge ca0cc98e03 bs/doc-bugreport-outdir later to maint). - (merge 72b113e562 ab/no-more-check-bindir later to maint). - (merge 92a5d1c9b4 jc/prefix-filename-allocates later to maint). - (merge d9a65b6c0a rs/setup-use-xopen-and-xdup later to maint). - (merge e8f55568de jk/t5562-racefix later to maint). - (merge 8f0f110156 rs/drop-core-compression-vars later to maint). - (merge b6d8887d3d ma/doc-git-version later to maint). - (merge 66c0c44df6 cb/plug-leaks-in-alloca-emu-users later to maint). - (merge afb32e8101 kz/revindex-comment-fix later to maint). - (merge ae578de926 po/git-config-doc-mentions-help-c later to maint). + (merge f188160be9 ab/bundle-remove-verbose-option later to maint). + (merge 8c6b4332b4 rs/close-pack-leakfix later to maint). + (merge 51b04c05b7 bs/difftool-msg-tweak later to maint). + (merge dd20e4a6db ab/make-compdb-fix later to maint). + (merge 6ffb990dc4 os/status-docfix later to maint). + (merge 100c2da2d3 rs/p3400-lose-tac later to maint). + (merge 76f3b69896 tb/aggregate-ignore-leading-whitespaces later to maint). + (merge 6e4fd8bfcd tz/doc-link-to-bundle-format-fix later to maint). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 117f4cf806..9a663535f4 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -136,5 +136,16 @@ take effect. option. An empty file name, `""`, will clear the list of revs from previously processed files. +--color-lines:: + Color line annotations in the default format differently if they come from + the same commit as the preceding line. This makes it easier to distinguish + code blocks introduced by different commits. The color defaults to cyan and + can be adjusted using the `color.blame.repeatedLines` config option. + +--color-by-age:: + Color line annotations depending on the age of the line in the default format. + The `color.blame.highlightRecent` config option controls what color is used for + each range of age. + -h:: Show help message. diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt index cc5f3249fc..d323d7327f 100644 --- a/Documentation/config/branch.txt +++ b/Documentation/config/branch.txt @@ -85,10 +85,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase' so that the local merge commits are included in the rebase (see linkgit:git-rebase[1] for details). + -When `preserve` (or just 'p', deprecated in favor of `merges`), also pass -`--preserve-merges` along to 'git rebase' so that locally committed merge -commits will not be flattened by running 'git pull'. -+ When the value is `interactive` (or just 'i'), the rebase is run in interactive mode. + diff --git a/Documentation/config/color.txt b/Documentation/config/color.txt index e05d520a86..dd2d2e0d84 100644 --- a/Documentation/config/color.txt +++ b/Documentation/config/color.txt @@ -9,26 +9,29 @@ color.advice.hint:: Use customized color for hints. color.blame.highlightRecent:: - This can be used to color the metadata of a blame line depending - on age of the line. + Specify the line annotation color for `git blame --color-by-age` + depending upon the age of the line. + -This setting should be set to a comma-separated list of color and date settings, -starting and ending with a color, the dates should be set from oldest to newest. -The metadata will be colored given the colors if the line was introduced -before the given timestamp, overwriting older timestamped colors. +This setting should be set to a comma-separated list of color and +date settings, starting and ending with a color, the dates should be +set from oldest to newest. The metadata will be colored with the +specified colors if the line was introduced before the given +timestamp, overwriting older timestamped colors. + + -Instead of an absolute timestamp relative timestamps work as well, e.g. -2.weeks.ago is valid to address anything older than 2 weeks. +Instead of an absolute timestamp relative timestamps work as well, +e.g. `2.weeks.ago` is valid to address anything older than 2 weeks. + + -It defaults to 'blue,12 month ago,white,1 month ago,red', which colors -everything older than one year blue, recent changes between one month and -one year old are kept white, and lines introduced within the last month are -colored red. +It defaults to `blue,12 month ago,white,1 month ago,red`, which +colors everything older than one year blue, recent changes between +one month and one year old are kept white, and lines introduced +within the last month are colored red. color.blame.repeatedLines:: - Use the customized color for the part of git-blame output that - is repeated meta information per line (such as commit id, - author name, date and timezone). Defaults to cyan. + Use the specified color to colorize line annotations for + `git blame --color-lines`, if they come from the same commit as the + preceding line. Defaults to cyan. color.branch:: A boolean to enable/disable color in the output of diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt index 763f7af7c4..ad7f73a1ea 100644 --- a/Documentation/config/pack.txt +++ b/Documentation/config/pack.txt @@ -159,6 +159,10 @@ pack.writeBitmapHashCache:: between an older, bitmapped pack and objects that have been pushed since the last gc). The downside is that it consumes 4 bytes per object of disk space. Defaults to true. ++ +When writing a multi-pack reachability bitmap, no new namehashes are +computed; instead, any namehashes stored in an existing bitmap are +permuted into their appropriate location when writing a new bitmap. pack.writeReverseIndex:: When true, git will write a corresponding .rev file (see: diff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt index 5404830609..9349e09261 100644 --- a/Documentation/config/pull.txt +++ b/Documentation/config/pull.txt @@ -18,10 +18,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase' so that the local merge commits are included in the rebase (see linkgit:git-rebase[1] for details). + -When `preserve` (or just 'p', deprecated in favor of `merges`), also pass -`--preserve-merges` along to 'git rebase' so that locally committed merge -commits will not be flattened by running 'git pull'. -+ When the value is `interactive` (or just 'i'), the rebase is run in interactive mode. + diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index fbbd410a84..7a9c3b6ff4 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -59,7 +59,7 @@ Possible status letters are: - D: deletion of a file - M: modification of the contents or mode of a file - R: renaming of a file -- T: change in the type of the file +- T: change in the type of the file (regular file, symbolic link or submodule) - U: file is unmerged (you must complete the merge before it can be committed) - X: "unknown" change type (most probably a bug, please report it) diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index be5e3ac54b..11eb70f16c 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p] - [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] + [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize] [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...] @@ -79,6 +79,13 @@ in linkgit:gitglossary[7]. --force:: Allow adding otherwise ignored files. +--sparse:: + Allow updating index entries outside of the sparse-checkout cone. + Normally, `git add` refuses to update index entries whose paths do + not fit within the sparse-checkout cone, since those files might + be removed from the working tree without warning. See + linkgit:git-sparse-checkout[1] for more details. + -i:: --interactive:: Add modified contents in the working tree interactively to diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 3bf5d5d8b4..d7a46cc674 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -11,8 +11,8 @@ SYNOPSIS 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--ignore-rev <rev>] [--ignore-revs-file <file>] - [--progress] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>..<rev>] - [--] <file> + [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>] + [<rev> | --contents <file> | --reverse <rev>..<rev>] [--] <file> DESCRIPTION ----------- @@ -93,6 +93,19 @@ include::blame-options.txt[] is used for a caret to mark the boundary commit. +THE DEFAULT FORMAT +------------------ + +When neither `--porcelain` nor `--incremental` option is specified, +`git blame` will output annotation for each line with: + +- abbreviated object name for the commit the line came from; +- author ident (by default author name and date, unless `-s` or `-e` + is specified); and +- line number + +before the line contents. + THE PORCELAIN FORMAT -------------------- diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 4eb0421b3f..27b27e2b30 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -94,8 +94,10 @@ OPTIONS Instead of reading a list of objects on stdin, perform the requested batch operation on all objects in the repository and any alternate object stores (not just reachable objects). - Requires `--batch` or `--batch-check` be specified. Note that - the objects are visited in order sorted by their hashes. + Requires `--batch` or `--batch-check` be specified. By default, + the objects are visited in order sorted by their hashes; see + also `--unordered` below. Objects are presented as-is, without + respecting the "replace" mechanism of linkgit:git-replace[1]. --buffer:: Normally batch output is flushed after each object is output, so diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index b1a6fe4499..d473c9bf38 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -118,8 +118,9 @@ OPTIONS -f:: --force:: When switching branches, proceed even if the index or the - working tree differs from `HEAD`. This is used to throw away - local changes. + working tree differs from `HEAD`, and even if there are untracked + files in the way. This is used to throw away local changes and + any untracked files or directories that are in the way. + When checking out paths from the index, do not fail upon unmerged entries; instead, unmerged entries are ignored. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index f2e4a47ebe..4dc57ed254 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -99,7 +99,7 @@ looks like ------ -Only anonymous access is provided by pserve by default. To commit you +Only anonymous access is provided by pserver by default. To commit you will have to create pserver accounts, simply add a gitcvs.authdb setting in the config file of the repositories you want the cvsserver to allow writes to, for example: @@ -114,21 +114,20 @@ The format of these files is username followed by the encrypted password, for example: ------ - myuser:$1Oyx5r9mdGZ2 - myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./ + myuser:sqkNi8zPf01HI + myuser:$1$9K7FzU28$VfF6EoPYCJEYcVQwATgOP/ + myuser:$5$.NqmNH1vwfzGpV8B$znZIcumu1tNLATgV2l6e1/mY8RzhUDHMOaVOeL1cxV3 ------ You can use the 'htpasswd' facility that comes with Apache to make these -files, but Apache's MD5 crypt method differs from the one used by most C -library's crypt() function, so don't use the -m option. +files, but only with the -d option (or -B if your system suports it). -Alternatively you can produce the password with perl's crypt() operator: ------ - perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password ------ +Preferably use the system specific utility that manages password hash +creation in your platform (e.g. mkpasswd in Linux, encrypt in OpenBSD or +pwhash in NetBSD) and paste it in the right location. Then provide your password via the pserver method, for example: ------ - cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name> + cvs -d:pserver:someuser:somepassword@server:/path/repo.git co <HEAD_name> ------ No special setup is needed for SSH access, other than having Git tools in the PATH. If you have clients that do not accept the CVS_SERVER @@ -138,7 +137,7 @@ Note: Newer CVS versions (>= 1.12.11) also support specifying CVS_SERVER directly in CVSROOT like ------ -cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name> + cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name> ------ This has the advantage that it will be saved in your 'CVS/Root' files and you don't need to worry about always setting the correct environment @@ -186,8 +185,8 @@ allowing access over SSH. + -- ------ - export CVSROOT=:ext:user@server:/var/git/project.git - export CVS_SERVER="git cvsserver" + export CVSROOT=:ext:user@server:/var/git/project.git + export CVS_SERVER="git cvsserver" ------ -- 4. For SSH clients that will make commits, make sure their server-side @@ -203,7 +202,7 @@ allowing access over SSH. `project-master` directory: + ------ - cvs co -d project-master master + cvs co -d project-master master ------ [[dbbackend]] diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index 44fe8860b3..96d5f598b4 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -8,8 +8,10 @@ git-help - Display help information about Git SYNOPSIS -------- [verse] -'git help' [-a|--all [--[no-]verbose]] [-g|--guides] - [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE] +'git help' [-a|--all [--[no-]verbose]] + [[-i|--info] [-m|--man] [-w|--web]] [COMMAND|GUIDE] +'git help' [-g|--guides] +'git help' [-c|--config] DESCRIPTION ----------- @@ -58,8 +60,7 @@ OPTIONS -g:: --guides:: - Prints a list of the Git concept guides on the standard output. This - option overrides any given command or guide name. + Prints a list of the Git concept guides on the standard output. -i:: --info:: diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index a9df3dbd32..b008ce2850 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -9,8 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes SYNOPSIS -------- [verse] -'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress] - [--preferred-pack=<pack>] [--[no-]bitmap] <subcommand> +'git multi-pack-index' [--object-dir=<dir>] [--[no-]bitmap] <sub-command> DESCRIPTION ----------- @@ -28,7 +27,8 @@ OPTIONS --[no-]progress:: Turn progress on/off explicitly. If neither is specified, progress is - shown if standard error is connected to a terminal. + shown if standard error is connected to a terminal. Supported by + sub-commands `write`, `verify`, `expire`, and `repack. The following subcommands are available: @@ -45,6 +45,25 @@ write:: --[no-]bitmap:: Control whether or not a multi-pack bitmap is written. + + --stdin-packs:: + Write a multi-pack index containing only the set of + line-delimited pack index basenames provided over stdin. + + --refs-snapshot=<path>:: + With `--bitmap`, optionally specify a file which + contains a "refs snapshot" taken prior to repacking. ++ +A reference snapshot is composed of line-delimited OIDs corresponding to +the reference tips, usually taken by `git repack` prior to generating a +new pack. A line may optionally start with a `+` character to indicate +that the reference which corresponds to that OID is "preferred" (see +linkgit:git-config[1]'s `pack.preferBitmapTips`.) ++ +The file given at `<path>` is expected to be readable, and can contain +duplicates. (If a given OID is given more than once, it is marked as +preferred if at least one instance of it begins with the special `+` +marker). -- verify:: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index aef757ec89..0e14f8b5b2 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -105,7 +105,7 @@ Options related to merging include::merge-options.txt[] -r:: ---rebase[=false|true|merges|preserve|interactive]:: +--rebase[=false|true|merges|interactive]:: When true, rebase the current branch on top of the upstream branch after fetching. If there is a remote-tracking branch corresponding to the upstream branch and the upstream branch @@ -116,10 +116,6 @@ When set to `merges`, rebase using `git rebase --rebase-merges` so that the local merge commits are included in the rebase (see linkgit:git-rebase[1] for details). + -When set to `preserve` (deprecated in favor of `merges`), rebase with the -`--preserve-merges` option passed to `git rebase` so that locally created -merge commits will not be flattened. -+ When false, merge the upstream branch into the current branch. + When `interactive`, enable the interactive mode of rebase. diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 5fa8bab64c..8c3aceb832 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -10,8 +10,7 @@ SYNOPSIS -------- [verse] 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] - [-u [--exclude-per-directory=<gitignore>] | -i]] - [--index-output=<file>] [--no-sparse-checkout] + [-u | -i]] [--index-output=<file>] [--no-sparse-checkout] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]]) @@ -39,8 +38,9 @@ OPTIONS --reset:: Same as -m, except that unmerged entries are discarded instead - of failing. When used with `-u`, updates leading to loss of - working tree changes will not abort the operation. + of failing. When used with `-u`, updates leading to loss of + working tree changes or untracked files or directories will not + abort the operation. -u:: After a successful merge, update the files in the work @@ -88,21 +88,6 @@ OPTIONS The command will refuse to overwrite entries that already existed in the original index file. ---exclude-per-directory=<gitignore>:: - When running the command with `-u` and `-m` options, the - merge result may need to overwrite paths that are not - tracked in the current branch. The command usually - refuses to proceed with the merge to avoid losing such a - path. However this safety valve sometimes gets in the - way. For example, it often happens that the other - branch added a file that used to be a generated file in - your branch, and the safety valve triggers when you try - to switch to that branch after you ran `make` but before - running `make clean` to remove the generated file. This - option tells the command to read per-directory exclude - file (usually '.gitignore') and allows such an untracked - but explicitly ignored file to be overwritten. - --index-output=<file>:: Instead of writing the results out to `$GIT_INDEX_FILE`, write the resulting index in the named file. While the diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 506345cb0e..a1af21fcef 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -446,7 +446,8 @@ When --fork-point is active, 'fork_point' will be used instead of ends up being empty, the <upstream> will be used as a fallback. + If <upstream> is given on the command line, then the default is -`--no-fork-point`, otherwise the default is `--fork-point`. +`--no-fork-point`, otherwise the default is `--fork-point`. See also +`rebase.forkpoint` in linkgit:git-config[1]. + If your branch was based on <upstream> but <upstream> was rewound and your branch contains commits which were dropped, this option can be used @@ -526,29 +527,12 @@ i.e. commits that would be excluded by linkgit:git-log[1]'s the `rebase-cousins` mode is turned on, such commits are instead rebased onto `<upstream>` (or `<onto>`, if specified). + -The `--rebase-merges` mode is similar in spirit to the deprecated -`--preserve-merges` but works with interactive rebases, -where commits can be reordered, inserted and dropped at will. -+ It is currently only possible to recreate the merge commits using the `ort` merge strategy; different merge strategies can be used only via explicit `exec git merge -s <strategy> [...]` commands. + See also REBASING MERGES and INCOMPATIBLE OPTIONS below. --p:: ---preserve-merges:: - [DEPRECATED: use `--rebase-merges` instead] Recreate merge commits - instead of flattening the history by replaying commits a merge commit - introduces. Merge conflict resolutions or manual amendments to merge - commits are not preserved. -+ -This uses the `--interactive` machinery internally, but combining it -with the `--interactive` option explicitly is generally not a good -idea unless you know what you are doing (see BUGS below). -+ -See also INCOMPATIBLE OPTIONS below. - -x <cmd>:: --exec <cmd>:: Append "exec <cmd>" after each line creating a commit in the @@ -580,9 +564,6 @@ See also INCOMPATIBLE OPTIONS below. the root commit(s) on a branch. When used with --onto, it will skip changes already contained in <newbase> (instead of <upstream>) whereas without --onto it will operate on every change. - When used together with both --onto and --preserve-merges, - 'all' root commits will be rewritten to have <newbase> as parent - instead. + See also INCOMPATIBLE OPTIONS below. @@ -644,7 +625,6 @@ are incompatible with the following options: * --allow-empty-message * --[no-]autosquash * --rebase-merges - * --preserve-merges * --interactive * --exec * --no-keep-empty @@ -655,13 +635,6 @@ are incompatible with the following options: In addition, the following pairs of options are incompatible: - * --preserve-merges and --interactive - * --preserve-merges and --signoff - * --preserve-merges and --rebase-merges - * --preserve-merges and --empty= - * --preserve-merges and --ignore-whitespace - * --preserve-merges and --committer-date-is-author-date - * --preserve-merges and --ignore-date * --keep-base and --onto * --keep-base and --root * --fork-point and --root @@ -1279,29 +1252,6 @@ CONFIGURATION include::config/rebase.txt[] include::config/sequencer.txt[] -BUGS ----- -The todo list presented by the deprecated `--preserve-merges --interactive` -does not represent the topology of the revision graph (use `--rebase-merges` -instead). Editing commits and rewording their commit messages should work -fine, but attempts to reorder commits tend to produce counterintuitive results. -Use `--rebase-merges` in such scenarios instead. - -For example, an attempt to rearrange ------------- -1 --- 2 --- 3 --- 4 --- 5 ------------- -to ------------- -1 --- 2 --- 4 --- 3 --- 5 ------------- -by moving the "pick 4" line will result in the following history: ------------- - 3 - / -1 --- 2 --- 4 --- 5 ------------- - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 24c00c9384..7183fb498f 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -9,7 +9,7 @@ git-repack - Pack unpacked objects in a repository SYNOPSIS -------- [verse] -'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>] +'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>] [--write-midx] DESCRIPTION ----------- @@ -128,10 +128,11 @@ depth is 4095. -b:: --write-bitmap-index:: Write a reachability bitmap index as part of the repack. This - only makes sense when used with `-a` or `-A`, as the bitmaps + only makes sense when used with `-a`, `-A` or `-m`, as the bitmaps must be able to refer to all reachable objects. This option - overrides the setting of `repack.writeBitmaps`. This option - has no effect if multiple packfiles are created. + overrides the setting of `repack.writeBitmaps`. This option + has no effect if multiple packfiles are created, unless writing a + MIDX (in which case a multi-pack bitmap is created). --pack-kept-objects:: Include objects in `.keep` files when repacking. Note that we @@ -189,6 +190,15 @@ this "roll-up", without respect to their reachability. This is subject to change in the future. This option (implying a drastically different repack mode) is not guaranteed to work with all other combinations of option to `git repack`. ++ +When writing a multi-pack bitmap, `git repack` selects the largest resulting +pack as the preferred pack for object selection by the MIDX (see +linkgit:git-multi-pack-index[1]). + +-m:: +--write-midx:: + Write a multi-pack index (see linkgit:git-multi-pack-index[1]) + containing the non-redundant packs. CONFIGURATION ------------- diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 252e2d4e47..6f7685f53d 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -69,7 +69,8 @@ linkgit:git-add[1]). --hard:: Resets the index and working tree. Any changes to tracked files in the - working tree since `<commit>` are discarded. + working tree since `<commit>` are discarded. Any untracked files or + directories in the way of writing any tracked files are simply deleted. --merge:: Resets the index and updates the files in the working tree that are diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 26e9b28470..81bc23f3cd 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -72,6 +72,12 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. --ignore-unmatch:: Exit with a zero status even if no files matched. +--sparse:: + Allow updating index entries outside of the sparse-checkout cone. + Normally, `git rm` refuses to update index entries whose paths do + not fit within the sparse-checkout cone. See + linkgit:git-sparse-checkout[1] for more. + -q:: --quiet:: `git rm` normally outputs one line (in the form of an `rm` command) diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 44fd146b91..be41f11974 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -9,10 +9,10 @@ git-send-pack - Push objects over Git protocol to another repository SYNOPSIS -------- [verse] -'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] +'git send-pack' [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [--[no-]signed|--signed=(true|false|if-asked)] - [<host>:]<directory> [<ref>...] + [<host>:]<directory> (--all | <ref>...) DESCRIPTION ----------- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 83f38e3198..4a2c3e0408 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -207,26 +207,29 @@ show tracked paths: * ' ' = unmodified * 'M' = modified +* 'T' = file type changed (regular file, symbolic link or submodule) * 'A' = added * 'D' = deleted * 'R' = renamed -* 'C' = copied +* 'C' = copied (if config option status.renames is set to "copies") * 'U' = updated but unmerged .... X Y Meaning ------------------------------------------------- [AMD] not updated -M [ MD] updated in index -A [ MD] added to index +M [ MTD] updated in index +T [ MTD] type changed in index +A [ MTD] added to index D deleted from index -R [ MD] renamed in index -C [ MD] copied in index -[MARC] index and work tree matches -[ MARC] M work tree changed since index -[ MARC] D deleted in work tree -[ D] R renamed in work tree -[ D] C copied in work tree +R [ MTD] renamed in index +C [ MTD] copied in index +[MTARC] index and work tree matches +[ MTARC] M work tree changed since index +[ MTARC] T type changed in work tree since index +[ MTARC] D deleted in work tree + R renamed in work tree + C copied in work tree ------------------------------------------------- D D unmerged, both deleted A U unmerged, added by us @@ -363,7 +366,7 @@ Field Meaning Unmerged entries have the following format; the first character is a "u" to distinguish from ordinary changed entries. - u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> + u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> .... Field Meaning diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index d5776ffcfd..222b556d7a 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -678,7 +678,6 @@ config key: svn.authorsProg --strategy=<strategy>:: -p:: --rebase-merges:: ---preserve-merges (DEPRECATED):: These are only used with the 'dcommit' and 'rebase' commands. + Passed directly to 'git rebase' when using 'dcommit' if a diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt index b9f3198fbe..ef7fe02a8f 100644 --- a/Documentation/technical/api-trace2.txt +++ b/Documentation/technical/api-trace2.txt @@ -613,6 +613,46 @@ stopping after the waitpid() and includes OS process creation overhead). So this time will be slightly larger than the atexit time reported by the child process itself. +`"child_ready"`:: + This event is generated after the current process has started + a background process and released all handles to it. ++ +------------ +{ + "event":"child_ready", + ... + "child_id":2, + "pid":14708, # child PID + "ready":"ready", # child ready state + "t_rel":0.110605 # observed run-time of child process +} +------------ ++ +Note that the session-id of the child process is not available to +the current/spawning process, so the child's PID is reported here as +a hint for post-processing. (But it is only a hint because the child +process may be a shell script which doesn't have a session-id.) ++ +This event is generated after the child is started in the background +and given a little time to boot up and start working. If the child +startups normally and while the parent is still waiting, the "ready" +field will have the value "ready". +If the child is too slow to start and the parent times out, the field +will have the value "timeout". +If the child starts but the parent is unable to probe it, the field +will have the value "error". ++ +After the parent process emits this event, it will release all of its +handles to the child process and treat the child as a background +daemon. So even if the child does eventually finish booting up, +the parent will not emit an updated event. ++ +Note that the `t_rel` field contains the observed run time in seconds +when the parent released the child process into the background. +The child is assumed to be a long-running daemon process and may +outlive the parent process. So the parent's child event times should +not be compared to the child's atexit times. + `"exec"`:: This event is generated before git attempts to `exec()` another command rather than starting a child process. diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt index 1a73c3ee20..86f40f2490 100644 --- a/Documentation/technical/multi-pack-index.txt +++ b/Documentation/technical/multi-pack-index.txt @@ -36,7 +36,9 @@ Design Details directory of an alternate. It refers only to packfiles in that same directory. -- The core.multiPackIndex config setting must be on to consume MIDX files. +- The core.multiPackIndex config setting must be on (which is the + default) to consume MIDX files. Setting it to `false` prevents + Git from reading a MIDX file, even if one exists. - The file format includes parameters for the object ID hash function, so a future change of hash algorithm does not require diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 59b86fcf97..21e8258ccf 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -199,7 +199,11 @@ ls-refs takes in the following arguments: Show peeled tags. ref-prefix <prefix> When specified, only references having a prefix matching one of - the provided prefixes are displayed. + the provided prefixes are displayed. Multiple instances may be + given, in which case references matching any prefix will be + shown. Note that this is purely for optimization; a server MAY + show refs not matching the prefix if it chooses, and clients + should filter the result themselves. If the 'unborn' feature is advertised the following argument can be included in the client's request. @@ -609,7 +609,6 @@ SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib -SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-sh-i18n SCRIPT_LIB += git-sh-setup @@ -817,6 +816,10 @@ XDIFF_LIB = xdiff/lib.a GENERATED_H += command-list.h GENERATED_H += config-list.h +GENERATED_H += hook-list.h + +.PHONY: generated-hdrs +generated-hdrs: $(GENERATED_H) LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \ $(FIND) . \ @@ -902,6 +905,7 @@ LIB_OBJS += hash-lookup.o LIB_OBJS += hashmap.o LIB_OBJS += help.o LIB_OBJS += hex.o +LIB_OBJS += hook.o LIB_OBJS += ident.o LIB_OBJS += json-writer.o LIB_OBJS += kwset.o @@ -1214,6 +1218,9 @@ PTHREAD_CFLAGS = SPARSE_FLAGS ?= SP_EXTRA_FLAGS = -Wno-universal-initializer +# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak target +SANITIZE_LEAK = + # For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will # usually result in less CPU usage at the cost of higher peak memory. # Setting it to 0 will feed all files in a single spatch invocation. @@ -1258,6 +1265,7 @@ BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS endif ifneq ($(filter leak,$(SANITIZERS)),) BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS +SANITIZE_LEAK = YesCompiledWithIt endif ifneq ($(filter address,$(SANITIZERS)),) NO_REGEX = NeededForASAN @@ -1278,6 +1286,7 @@ endif ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto) dep_check = $(shell $(CC) $(ALL_CFLAGS) \ + -Wno-pedantic \ -c -MF /dev/null -MQ /dev/null -MMD -MP \ -x c /dev/null -o /dev/null 2>&1; \ echo $$?) @@ -1303,6 +1312,7 @@ endif ifeq ($(GENERATE_COMPILATION_DATABASE),yes) compdb_check = $(shell $(CC) $(ALL_CFLAGS) \ + -Wno-pedantic \ -c -MJ /dev/null \ -x c /dev/null -o /dev/null 2>&1; \ echo $$?) @@ -2205,8 +2215,9 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) $(filter %.o,$^) $(LIBS) help.sp help.s help.o: command-list.h +hook.sp hook.s hook.o: hook-list.h -builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX +builtin/help.sp builtin/help.s builtin/help.o: config-list.h hook-list.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ @@ -2229,15 +2240,17 @@ $(BUILT_INS): git$X config-list.h: generate-configlist.sh config-list.h: Documentation/*config.txt Documentation/config/*.txt - $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \ - >$@+ && mv $@+ $@ + $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh >$@ command-list.h: generate-cmdlist.sh command-list.txt command-list.h: $(wildcard Documentation/git*.txt) $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh \ $(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \ - command-list.txt >$@+ && mv $@+ $@ + command-list.txt >$@ + +hook-list.h: generate-hooklist.sh Documentation/githooks.txt + $(QUIET_GEN)$(SHELL_PATH) ./generate-hooklist.sh >$@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\ @@ -2459,7 +2472,6 @@ dep_args = -MF $(dep_file) -MQ $@ -MMD -MP endif ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes) -dep_dirs = missing_dep_dirs = dep_args = endif @@ -2500,13 +2512,6 @@ ifneq ($(dep_files_present),) include $(dep_files_present) endif else -# Dependencies on header files, for platforms that do not support -# the gcc -MMD option. -# -# Dependencies on automatically generated headers such as command-list.h -# should _not_ be included here, since they are necessary even when -# building an object for the first time. - $(OBJECTS): $(LIB_H) $(GENERATED_H) endif @@ -2631,7 +2636,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ --keyword=__ --keyword=N__ --keyword="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) -LOCALIZED_SH += git-rebase--preserve-merges.sh LOCALIZED_SH += git-sh-setup.sh LOCALIZED_PERL = $(SCRIPT_PERL) @@ -2796,6 +2800,7 @@ GIT-BUILD-OPTIONS: FORCE @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+ @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+ @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+ + @echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+ @echo X=\'$(X)\' >>$@+ ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ @@ -2896,14 +2901,16 @@ check-sha1:: t/helper/test-tool$X SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ)) -$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE +$(SP_OBJ): %.sp: %.c %.o GIT-CFLAGS $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ - $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< + -Wsparse-error \ + $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \ + >$@ -.PHONY: sparse $(SP_OBJ) +.PHONY: sparse sparse: $(SP_OBJ) -EXCEPT_HDRS := command-list.h config-list.h unicode-width.h compat/% xdiff/% +EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% ifndef GCRYPT_SHA256 EXCEPT_HDRS += sha256/gcrypt.h endif @@ -2925,7 +2932,8 @@ hdr-check: $(HCO) style: git clang-format --style file --diff --extensions c,h -check: config-list.h command-list.h +.PHONY: check +check: $(GENERATED_H) @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \ @@ -3227,6 +3235,7 @@ clean: profile-clean coverage-clean cocciclean $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) $(FUZZ_PROGRAMS) + $(RM) $(SP_OBJ) $(RM) $(HCC) $(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json $(RM) -r po/build/ diff --git a/add-interactive.c b/add-interactive.c index 36ebdbdf7e..6498ae196f 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -102,8 +102,12 @@ struct prefix_item_list { int *selected; /* for multi-selections */ size_t min_length, max_length; }; -#define PREFIX_ITEM_LIST_INIT \ - { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 } +#define PREFIX_ITEM_LIST_INIT { \ + .items = STRING_LIST_INIT_DUP, \ + .sorted = STRING_LIST_INIT_NODUP, \ + .min_length = 1, \ + .max_length = 4, \ +} static void prefix_item_list_clear(struct prefix_item_list *list) { @@ -224,15 +224,16 @@ void advise_on_updating_sparse_paths(struct string_list *pathspec_list) if (!pathspec_list->nr) return; - fprintf(stderr, _("The following pathspecs didn't match any" - " eligible path, but they do match index\n" - "entries outside the current sparse checkout:\n")); + fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n" + "outside of your sparse-checkout definition, so will not be\n" + "updated in the index:\n")); for_each_string_list_item(item, pathspec_list) fprintf(stderr, "%s\n", item->string); advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH, - _("Disable or modify the sparsity rules if you intend" - " to update such entries.")); + _("If you intend to update such entries, try one of the following:\n" + "* Use the --sparse option.\n" + "* Disable or modify the sparsity rules.")); } void detach_advice(const char *new_name) @@ -225,7 +225,6 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix); int cmd_switch(int argc, const char **argv, const char *prefix); int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); int cmd_tag(int argc, const char **argv, const char *prefix); -int cmd_tar_tree(int argc, const char **argv, const char *prefix); int cmd_unpack_file(int argc, const char **argv, const char *prefix); int cmd_unpack_objects(int argc, const char **argv, const char *prefix); int cmd_update_index(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 24da07578f..ef6b619c45 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static int add_renormalize; static int pathspec_file_nul; +static int include_sparse; static const char *pathspec_from_file; static int legacy_stash_p; /* support for the scripted `git stash` */ @@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) struct cache_entry *ce = active_cache[i]; int err; - if (ce_skip_worktree(ce)) + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) @@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + + if (!include_sparse && !path_in_sparse_checkout(path, &the_index)) + continue; + switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -147,7 +154,9 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (ce_skip_worktree(ce)) + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (ce_stage(ce)) continue; /* do not touch unmerged paths */ @@ -377,6 +386,7 @@ static struct option builtin_add_options[] = { OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), + OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x", N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, @@ -442,6 +452,7 @@ static void check_embedded_repo(const char *path) static int add_files(struct dir_struct *dir, int flags) { int i, exit_status = 0; + struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP; if (dir->ignored_nr) { fprintf(stderr, _(ignore_error)); @@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags) } for (i = 0; i < dir->nr; i++) { + if (!include_sparse && + !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) { + string_list_append(&matched_sparse_paths, + dir->entries[i]->name); + continue; + } if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); @@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags) check_embedded_repo(dir->entries[i]->name); } } + + if (matched_sparse_paths.nr) { + advise_on_updating_sparse_paths(&matched_sparse_paths); + exit_status = 1; + } + + string_list_clear(&matched_sparse_paths, 0); + return exit_status; } @@ -627,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (seen[i]) continue; - if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) { + if (!include_sparse && + matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) { string_list_append(&only_match_skip_worktree, pathspec.items[i].original); continue; diff --git a/builtin/am.c b/builtin/am.c index e4a0ff9cd7..8677ea2348 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "dir.h" #include "run-command.h" +#include "hook.h" #include "quote.h" #include "tempfile.h" #include "lockfile.h" @@ -1917,7 +1918,8 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) opts.dst_index = &the_index; opts.update = 1; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = twoway_merge; init_tree_desc(&t[0], head->buffer, head->size); init_tree_desc(&t[1], remote->buffer, remote->size); diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index bc210b23c8..28a2e6a575 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -1157,7 +1157,7 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) printf(_("bisect found first bad commit")); res = BISECT_OK; } else if (res) { - error(_("bisect run failed:'git bisect--helper --bisect-state" + error(_("bisect run failed: 'git bisect--helper --bisect-state" " %s' exited with error code %d"), args.v[0], res); } else { continue; diff --git a/builtin/blame.c b/builtin/blame.c index 641523ff9a..1c31a99640 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -101,6 +101,16 @@ struct commit_info { struct strbuf summary; }; +#define COMMIT_INFO_INIT { \ + .author = STRBUF_INIT, \ + .author_mail = STRBUF_INIT, \ + .author_tz = STRBUF_INIT, \ + .committer = STRBUF_INIT, \ + .committer_mail = STRBUF_INIT, \ + .committer_tz = STRBUF_INIT, \ + .summary = STRBUF_INIT, \ +} + /* * Parse author/committer line in the commit object buffer */ @@ -160,18 +170,6 @@ static void get_ac_line(const char *inbuf, const char *what, strbuf_add(name, namebuf, namelen); } -static void commit_info_init(struct commit_info *ci) -{ - - strbuf_init(&ci->author, 0); - strbuf_init(&ci->author_mail, 0); - strbuf_init(&ci->author_tz, 0); - strbuf_init(&ci->committer, 0); - strbuf_init(&ci->committer_mail, 0); - strbuf_init(&ci->committer_tz, 0); - strbuf_init(&ci->summary, 0); -} - static void commit_info_destroy(struct commit_info *ci) { @@ -192,8 +190,6 @@ static void get_commit_info(struct commit *commit, const char *subject, *encoding; const char *message; - commit_info_init(ret); - encoding = get_log_output_encoding(); message = logmsg_reencode(commit, NULL, encoding); get_ac_line(message, "\nauthor ", @@ -246,7 +242,7 @@ static void write_filename_info(struct blame_origin *suspect) */ static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; @@ -440,7 +436,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int int cnt; const char *cp; struct blame_origin *suspect = ent->suspect; - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); const char *default_color = NULL, *color = NULL, *reset = NULL; @@ -630,7 +626,7 @@ static void find_alignment(struct blame_scoreboard *sb, int *option) if (longest_file < num) longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 06ed10dc92..9de32bc96e 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -3,7 +3,8 @@ #include "strbuf.h" #include "help.h" #include "compat/compiler.h" -#include "run-command.h" +#include "hook.h" +#include "hook-list.h" static void get_system_info(struct strbuf *sys_info) @@ -41,39 +42,7 @@ static void get_system_info(struct strbuf *sys_info) static void get_populated_hooks(struct strbuf *hook_info, int nongit) { - /* - * NEEDSWORK: Doesn't look like there is a list of all possible hooks; - * so below is a transcription of `git help hooks`. Later, this should - * be replaced with some programmatically generated list (generated from - * doc or else taken from some library which tells us about all the - * hooks) - */ - static const char *hook[] = { - "applypatch-msg", - "pre-applypatch", - "post-applypatch", - "pre-commit", - "pre-merge-commit", - "prepare-commit-msg", - "commit-msg", - "post-commit", - "pre-rebase", - "post-checkout", - "post-merge", - "pre-push", - "pre-receive", - "update", - "post-receive", - "post-update", - "push-to-checkout", - "pre-auto-gc", - "post-rewrite", - "sendemail-validate", - "fsmonitor-watchman", - "p4-pre-submit", - "post-index-change", - }; - int i; + const char **p; if (nongit) { strbuf_addstr(hook_info, @@ -81,9 +50,12 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit) return; } - for (i = 0; i < ARRAY_SIZE(hook); i++) - if (find_hook(hook[i])) - strbuf_addf(hook_info, "%s\n", hook[i]); + for (p = hook_name_list; *p; p++) { + const char *hook = *p; + + if (hook_exists(hook)) + strbuf_addf(hook_info, "%s\n", hook); + } } static const char * const bugreport_usage[] = { diff --git a/builtin/bundle.c b/builtin/bundle.c index 91975def2d..5a85d7cd0f 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -39,8 +39,6 @@ static const char * const builtin_bundle_unbundle_usage[] = { NULL }; -static int verbose; - static int parse_options_cmd_bundle(int argc, const char **argv, const char* prefix, @@ -197,7 +195,6 @@ cleanup: int cmd_bundle(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), OPT_END() }; int result; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 243fe6844b..86fc03242b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -355,18 +355,34 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } } +/* + * If "pack" is non-NULL, then "offset" is the byte offset within the pack from + * which the object may be accessed (though note that we may also rely on + * data->oid, too). If "pack" is NULL, then offset is ignored. + */ static void batch_object_write(const char *obj_name, struct strbuf *scratch, struct batch_options *opt, - struct expand_data *data) + struct expand_data *data, + struct packed_git *pack, + off_t offset) { - if (!data->skip_object_info && - oid_object_info_extended(the_repository, &data->oid, &data->info, - OBJECT_INFO_LOOKUP_REPLACE) < 0) { - printf("%s missing\n", - obj_name ? obj_name : oid_to_hex(&data->oid)); - fflush(stdout); - return; + if (!data->skip_object_info) { + int ret; + + if (pack) + ret = packed_object_info(the_repository, pack, offset, + &data->info); + else + ret = oid_object_info_extended(the_repository, + &data->oid, &data->info, + OBJECT_INFO_LOOKUP_REPLACE); + if (ret < 0) { + printf("%s missing\n", + obj_name ? obj_name : oid_to_hex(&data->oid)); + fflush(stdout); + return; + } } strbuf_reset(scratch); @@ -428,7 +444,7 @@ static void batch_one_object(const char *obj_name, return; } - batch_object_write(obj_name, scratch, opt, data); + batch_object_write(obj_name, scratch, opt, data, NULL, 0); } struct object_cb_data { @@ -442,7 +458,8 @@ static int batch_object_cb(const struct object_id *oid, void *vdata) { struct object_cb_data *data = vdata; oidcpy(&data->expand->oid, oid); - batch_object_write(NULL, data->scratch, data->opt, data->expand); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + NULL, 0); return 0; } @@ -463,21 +480,26 @@ static int collect_packed_object(const struct object_id *oid, return 0; } -static int batch_unordered_object(const struct object_id *oid, void *vdata) +static int batch_unordered_object(const struct object_id *oid, + struct packed_git *pack, off_t offset, + void *vdata) { struct object_cb_data *data = vdata; if (oidset_insert(data->seen, oid)) return 0; - return batch_object_cb(oid, data); + oidcpy(&data->expand->oid, oid); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + pack, offset); + return 0; } static int batch_unordered_loose(const struct object_id *oid, const char *path, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, NULL, 0, data); } static int batch_unordered_packed(const struct object_id *oid, @@ -485,7 +507,9 @@ static int batch_unordered_packed(const struct object_id *oid, uint32_t pos, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, pack, + nth_packed_object_offset(pack, pos), + data); } static int batch_objects(struct batch_options *opt) @@ -529,6 +553,8 @@ static int batch_objects(struct batch_options *opt) if (has_promisor_remote()) warning("This repository uses promisor remotes. Some objects may not be loaded."); + read_replace_refs = 0; + cb.opt = opt; cb.expand = &data; cb.scratch = &output; diff --git a/builtin/checkout.c b/builtin/checkout.c index 8c69dcdf72..cbf73b8c9f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -646,7 +646,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.head_idx = -1; opts.update = worktree; opts.skip_unmerged = !worktree; - opts.reset = 1; + opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED : + UNPACK_RESET_PROTECT_UNTRACKED; + opts.preserve_ignored = (!o->force && !o->overwrite_ignore); opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = o->show_progress; @@ -746,11 +748,7 @@ static int merge_working_tree(const struct checkout_opts *opts, new_branch_info->commit ? &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); - if (opts->overwrite_ignore) { - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(topts.dir); - } + topts.preserve_ignored = !opts->overwrite_ignore; tree = parse_tree_indirect(old_branch_info->commit ? &old_branch_info->commit->object.oid : the_hash_algo->empty_tree); diff --git a/builtin/clone.c b/builtin/clone.c index ff1d3d447a..559acf9e03 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -687,6 +687,7 @@ static int checkout(int submodule_progress) opts.update = 1; opts.merge = 1; opts.clone = 1; + opts.preserve_ignored = 0; opts.fn = oneway_merge; opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; @@ -1229,6 +1230,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { + const char *branch; + char *ref; + if (option_branch) die(_("Remote branch %s not found in upstream %s"), option_branch, remote_name); @@ -1239,24 +1243,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; - if (!option_bare) { - const char *branch; - char *ref; - - if (transport_ls_refs_options.unborn_head_target && - skip_prefix(transport_ls_refs_options.unborn_head_target, - "refs/heads/", &branch)) { - ref = transport_ls_refs_options.unborn_head_target; - transport_ls_refs_options.unborn_head_target = NULL; - create_symref("HEAD", ref, reflog_msg.buf); - } else { - branch = git_default_branch_name(0); - ref = xstrfmt("refs/heads/%s", branch); - } - install_branch_config(0, branch, remote_name, ref); - free(ref); + if (transport_ls_refs_options.unborn_head_target && + skip_prefix(transport_ls_refs_options.unborn_head_target, + "refs/heads/", &branch)) { + ref = transport_ls_refs_options.unborn_head_target; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); } + + if (!option_bare) + install_branch_config(0, branch, remote_name, ref); + + free(ref); } write_refspec_config(src_ref_prefix, our_head_points_at, diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 0386f5c775..3c3de3a156 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -50,8 +50,6 @@ static struct option common_opts[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), N_("the object directory to store the graph")), - OPT_BOOL(0, "progress", &opts.progress, - N_("force progress reporting")), OPT_END() }; @@ -73,6 +71,8 @@ static int graph_verify(int argc, const char **argv) static struct option builtin_commit_graph_verify_options[] = { OPT_BOOL(0, "shallow", &opts.shallow, N_("if the commit-graph is split, only verify the tip file")), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; struct option *options = add_common_options(builtin_commit_graph_verify_options); @@ -224,6 +224,8 @@ static int graph_write(int argc, const char **argv) OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters, NULL, N_("maximum number of changed-path Bloom filters to compute"), 0, write_option_max_new_filters), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; struct option *options = add_common_options(builtin_commit_graph_write_options); diff --git a/builtin/commit.c b/builtin/commit.c index e7320f66f9..883c16256c 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -19,6 +19,7 @@ #include "revision.h" #include "wt-status.h" #include "run-command.h" +#include "hook.h" #include "refs.h" #include "log-tree.h" #include "strbuf.h" @@ -1051,7 +1052,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (!no_verify && find_hook("pre-commit")) { + if (!no_verify && hook_exists("pre-commit")) { /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke diff --git a/builtin/config.c b/builtin/config.c index 865fddd6ce..542d8d02b2 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -575,7 +575,7 @@ static int get_urlmatch(const char *var, const char *url) int ret; char *section_tail; struct string_list_item *item; - struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct urlmatch_config config = URLMATCH_CONFIG_INIT; struct string_list values = STRING_LIST_INIT_DUP; config.collect_fn = urlmatch_collect_fn; diff --git a/builtin/difftool.c b/builtin/difftool.c index bb9fe7245a..4931c10845 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -252,16 +252,6 @@ static void changed_files(struct hashmap *result, const char *index_path, strbuf_release(&buf); } -static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) -{ - struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, tmpdir); - remove_dir_recursively(&buf, 0); - if (exit_code) - warning(_("failed: %d"), exit_code); - exit(exit_code); -} - static int ensure_leading_directories(char *path) { switch (safe_create_leading_directories(path)) { @@ -330,19 +320,44 @@ static int checkout_path(unsigned mode, struct object_id *oid, return ret; } +static void write_file_in_directory(struct strbuf *dir, size_t dir_len, + const char *path, const char *content) +{ + add_path(dir, dir_len, path); + ensure_leading_directories(dir->buf); + unlink(dir->buf); + write_file(dir->buf, "%s", content); +} + +/* Write the file contents for the left and right sides of the difftool + * dir-diff representation for submodules and symlinks. Symlinks and submodules + * are written as regular text files so that external diff tools can diff them + * as text files, resulting in behavior that is analogous to to what "git diff" + * displays for symlink and submodule diffs. + */ +static void write_standin_files(struct pair_entry *entry, + struct strbuf *ldir, size_t ldir_len, + struct strbuf *rdir, size_t rdir_len) +{ + if (*entry->left) + write_file_in_directory(ldir, ldir_len, entry->path, entry->left); + if (*entry->right) + write_file_in_directory(rdir, rdir_len, entry->path, entry->right); +} + static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct child_process *child) { - char tmpdir[PATH_MAX]; struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; struct strbuf wtdir = STRBUF_INIT; - char *lbase_dir, *rbase_dir; + struct strbuf tmpdir = STRBUF_INIT; + char *lbase_dir = NULL, *rbase_dir = NULL; size_t ldir_len, rdir_len, wtdir_len; const char *workdir, *tmp; int ret = 0, i; - FILE *fp; + FILE *fp = NULL; struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp, NULL); struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL); @@ -351,7 +366,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct pair_entry *entry; struct index_state wtindex; struct checkout lstate, rstate; - int rc, flags = RUN_GIT_CMD, err = 0; + int flags = RUN_GIT_CMD, err = 0; const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; struct hashmap wt_modified, tmp_modified; int indices_loaded = 0; @@ -360,11 +375,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, /* Setup temp directories */ tmp = getenv("TMPDIR"); - xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); - if (!mkdtemp(tmpdir)) - return error("could not create '%s'", tmpdir); - strbuf_addf(&ldir, "%s/left/", tmpdir); - strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp"); + strbuf_trim_trailing_dir_sep(&tmpdir); + strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX"); + if (!mkdtemp(tmpdir.buf)) { + ret = error("could not create '%s'", tmpdir.buf); + goto finish; + } + strbuf_addf(&ldir, "%s/left/", tmpdir.buf); + strbuf_addf(&rdir, "%s/right/", tmpdir.buf); strbuf_addstr(&wtdir, workdir); if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) strbuf_addch(&wtdir, '/'); @@ -405,9 +424,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, const char *src_path, *dst_path; if (starts_with(info.buf, "::")) - die(N_("combined diff formats('-c' and '--cc') are " + die(N_("combined diff formats ('-c' and '--cc') are " "not supported in\n" - "directory diff mode('-d' and '--dir-diff').")); + "directory diff mode ('-d' and '--dir-diff').")); if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, &status)) @@ -535,38 +554,19 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, */ hashmap_for_each_entry(&submodules, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); } /* - * Symbolic links require special treatment.The standard "git diff" + * Symbolic links require special treatment. The standard "git diff" * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ hashmap_for_each_entry(&symlinks2, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } - } - strbuf_release(&buf); + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); + } strbuf_setlen(&ldir, ldir_len); helper_argv[1] = ldir.buf; @@ -578,7 +578,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, flags = 0; } else setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); - rc = run_command_v_opt(helper_argv, flags); + ret = run_command_v_opt(helper_argv, flags); /* TODO: audit for interaction with sparse-index. */ ensure_full_index(&wtindex); @@ -612,7 +612,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, if (!indices_loaded) { struct lock_file lock = LOCK_INIT; strbuf_reset(&buf); - strbuf_addf(&buf, "%s/wtindex", tmpdir); + strbuf_addf(&buf, "%s/wtindex", tmpdir.buf); if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); @@ -642,11 +642,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, } if (err) { - warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("temporary files exist in '%s'."), tmpdir.buf); warning(_("you may want to cleanup or recover these.")); - exit(1); - } else - exit_cleanup(tmpdir, rc); + ret = 1; + } else { + remove_dir_recursively(&tmpdir, 0); + if (ret) + warning(_("failed: %d"), ret); + } finish: if (fp) @@ -658,8 +661,9 @@ finish: strbuf_release(&rdir); strbuf_release(&wtdir); strbuf_release(&buf); + strbuf_release(&tmpdir); - return ret; + return (ret < 0) ? 1 : ret; } static int run_file_diff(int prompt, const char *prefix, @@ -706,7 +710,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) "`--tool`")), OPT_BOOL(0, "trust-exit-code", &trust_exit_code, N_("make 'git-difftool' exit when an invoked diff " - "tool returns a non - zero exit code")), + "tool returns a non-zero exit code")), OPT_STRING('x', "extcmd", &extcmd, N_("command"), N_("specify a custom command for viewing diffs")), OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")), diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 95e8e89e81..8e2caf7281 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -312,7 +312,7 @@ static void export_blob(const struct object_id *oid) if (!buf) die("could not read blob %s", oid_to_hex(oid)); if (check_object_signature(the_repository, oid, buf, size, - type_name(type)) < 0) + type_name(type), NULL) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); diff --git a/builtin/fsck.c b/builtin/fsck.c index b42b6fe21f..30a516da29 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -593,18 +593,43 @@ static void get_default_heads(void) } } +struct for_each_loose_cb +{ + struct progress *progress; + struct strbuf obj_type; +}; + static int fsck_loose(const struct object_id *oid, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; struct object *obj; - enum object_type type; + enum object_type type = OBJ_NONE; unsigned long size; void *contents; int eaten; + struct object_info oi = OBJECT_INFO_INIT; + struct object_id real_oid = *null_oid(); + int err = 0; - if (read_loose_object(path, oid, &type, &size, &contents) < 0) { + strbuf_reset(&cb_data->obj_type); + oi.type_name = &cb_data->obj_type; + oi.sizep = &size; + oi.typep = &type; + + if (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) { + if (contents && !oideq(&real_oid, oid)) + err = error(_("%s: hash-path mismatch, found at: %s"), + oid_to_hex(&real_oid), path); + else + err = error(_("%s: object corrupt or missing: %s"), + oid_to_hex(oid), path); + } + if (type != OBJ_NONE && type < 0) + err = error(_("%s: object is of unknown type '%s': %s"), + oid_to_hex(&real_oid), cb_data->obj_type.buf, + path); + if (err < 0) { errors_found |= ERROR_OBJECT; - error(_("%s: object corrupt or missing: %s"), - oid_to_hex(oid), path); return 0; /* keep checking other objects */ } @@ -640,8 +665,10 @@ static int fsck_cruft(const char *basename, const char *path, void *data) return 0; } -static int fsck_subdir(unsigned int nr, const char *path, void *progress) +static int fsck_subdir(unsigned int nr, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; + struct progress *progress = cb_data->progress; display_progress(progress, nr + 1); return 0; } @@ -649,6 +676,10 @@ static int fsck_subdir(unsigned int nr, const char *path, void *progress) static void fsck_object_dir(const char *path) { struct progress *progress = NULL; + struct for_each_loose_cb cb_data = { + .obj_type = STRBUF_INIT, + .progress = progress, + }; if (verbose) fprintf_ln(stderr, _("Checking object directory")); @@ -657,9 +688,10 @@ static void fsck_object_dir(const char *path) progress = start_progress(_("Checking object directories"), 256); for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, - progress); + &cb_data); display_progress(progress, 256); stop_progress(&progress); + strbuf_release(&cb_data.obj_type); } static int fsck_head_link(const char *head_ref_name, diff --git a/builtin/help.c b/builtin/help.c index 7731659765..75cd2fb407 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -7,7 +7,6 @@ #include "exec-cmd.h" #include "parse-options.h" #include "run-command.h" -#include "column.h" #include "config-list.h" #include "help.h" #include "alias.h" @@ -34,32 +33,52 @@ enum help_format { HELP_FORMAT_WEB }; -static const char *html_path; +enum show_config_type { + SHOW_CONFIG_HUMAN, + SHOW_CONFIG_VARS, + SHOW_CONFIG_SECTIONS, +}; + +static enum help_action { + HELP_ACTION_ALL = 1, + HELP_ACTION_GUIDES, + HELP_ACTION_CONFIG, + HELP_ACTION_CONFIG_FOR_COMPLETION, + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, +} cmd_mode; -static int show_all = 0; -static int show_guides = 0; -static int show_config; +static const char *html_path; static int verbose = 1; -static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; static struct option builtin_help_options[] = { - OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"), + HELP_ACTION_ALL), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), - OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), - OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")), - OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), OPT__VERBOSE(&verbose, N_("print command description")), + + OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), + HELP_ACTION_GUIDES), + OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), + HELP_ACTION_CONFIG), + OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_END(), }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"), + N_("git help [-a|--all] [--[no-]verbose]]\n" + " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), + N_("git help [-g|--guides]"), + N_("git help [-c|--config]"), NULL }; @@ -70,7 +89,7 @@ struct slot_expansion { int found; }; -static void list_config_help(int for_human) +static void list_config_help(enum show_config_type type) { struct slot_expansion slot_expansions[] = { { "advice", "*", list_config_advices }, @@ -88,6 +107,8 @@ static void list_config_help(int for_human) const char **p; struct slot_expansion *e; struct string_list keys = STRING_LIST_INIT_DUP; + struct string_list keys_uniq = STRING_LIST_INIT_DUP; + struct string_list_item *item; int i; for (p = config_name_list; *p; p++) { @@ -118,34 +139,46 @@ static void list_config_help(int for_human) for (i = 0; i < keys.nr; i++) { const char *var = keys.items[i].string; const char *wildcard, *tag, *cut; + const char *dot = NULL; + struct strbuf sb = STRBUF_INIT; - if (for_human) { + switch (type) { + case SHOW_CONFIG_HUMAN: puts(var); continue; + case SHOW_CONFIG_SECTIONS: + dot = strchr(var, '.'); + break; + case SHOW_CONFIG_VARS: + break; } - wildcard = strchr(var, '*'); tag = strchr(var, '<'); - if (!wildcard && !tag) { - puts(var); + if (!dot && !wildcard && !tag) { + string_list_append(&keys_uniq, var); continue; } - if (wildcard && !tag) + if (dot) + cut = dot; + else if (wildcard && !tag) cut = wildcard; else if (!wildcard && tag) cut = tag; else cut = wildcard < tag ? wildcard : tag; - /* - * We may produce duplicates, but that's up to - * git-completion.bash to handle - */ - printf("%.*s\n", (int)(cut - var), var); + strbuf_add(&sb, var, cut - var); + string_list_append(&keys_uniq, sb.buf); + strbuf_release(&sb); + } string_list_clear(&keys, 0); + string_list_remove_duplicates(&keys_uniq, 0); + for_each_string_list_item(item, &keys_uniq) + puts(item->string); + string_list_clear(&keys_uniq, 0); } static enum help_format parse_help_format(const char *format) @@ -349,8 +382,6 @@ static int add_man_viewer_info(const char *var, const char *value) static int git_help_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "column.")) - return git_column_config(var, value, "help", &colopts); if (!strcmp(var, "help.format")) { if (!value) return config_error_nonbool(var); @@ -544,6 +575,13 @@ static const char *check_git_cmd(const char* cmd) return cmd; } +static void no_extra_argc(int argc) +{ + if (argc) + usage_msg_opt(_("this option doesn't take any other arguments"), + builtin_help_usage, builtin_help_options); +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; @@ -554,8 +592,8 @@ int cmd_help(int argc, const char **argv, const char *prefix) builtin_help_usage, 0); parsed_help_format = help_format; - if (show_all) { - git_config(git_help_config, NULL); + switch (cmd_mode) { + case HELP_ACTION_ALL: if (verbose) { setup_pager(); list_all_cmds_help(); @@ -563,30 +601,27 @@ int cmd_help(int argc, const char **argv, const char *prefix) } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); load_command_list("git-", &main_cmds, &other_cmds); - list_commands(colopts, &main_cmds, &other_cmds); - } - - if (show_config) { - int for_human = show_config == 1; - - if (!for_human) { - list_config_help(for_human); - return 0; - } - setup_pager(); - list_config_help(for_human); - printf("\n%s\n", _("'git help config' for more information")); - return 0; - } - - if (show_guides) + list_commands(&main_cmds, &other_cmds); + printf("%s\n", _(git_more_info_string)); + break; + case HELP_ACTION_GUIDES: + no_extra_argc(argc); list_guides_help(); - - if (show_all || show_guides) { printf("%s\n", _(git_more_info_string)); - /* - * We're done. Ignore any remaining args - */ + return 0; + case HELP_ACTION_CONFIG_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_VARS); + return 0; + case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_SECTIONS); + return 0; + case HELP_ACTION_CONFIG: + no_extra_argc(argc); + setup_pager(); + list_config_help(SHOW_CONFIG_HUMAN); + printf("\n%s\n", _("'git help config' for more information")); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 7ce69c087e..15ae406e6b 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1415,7 +1415,7 @@ static void fix_unresolved_deltas(struct hashfile *f) if (check_object_signature(the_repository, &d->oid, data, size, - type_name(type))) + type_name(type), NULL)) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); /* diff --git a/builtin/ls-files.c b/builtin/ls-files.c index ec19bf54b2..a2000ed6bf 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -612,7 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) struct option builtin_ls_files_options[] = { /* Think twice before adding "--nul" synonym to this */ OPT_SET_INT('z', NULL, &line_terminator, - N_("paths are separated with NUL character"), '\0'), + N_("separate paths with the NUL character"), '\0'), OPT_BOOL('t', NULL, &show_tag, N_("identify the file status with tags")), OPT_BOOL('v', NULL, &show_valid_bit, @@ -649,7 +649,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("skip files matching pattern"), PARSE_OPT_NONEG, option_parse_exclude), OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"), - N_("exclude patterns are read from <file>"), + N_("read exclude patterns from <file>"), PARSE_OPT_NONEG, option_parse_exclude_from), OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"), N_("read additional per-directory exclude patterns in <file>")), diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index f4fd823af8..318949c3d7 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -7,8 +7,8 @@ static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" - " [-q | --quiet] [--exit-code] [--get-url]\n" - " [--symref] [<repository> [<refs>...]]"), + " [-q | --quiet] [--exit-code] [--get-url]\n" + " [--symref] [<repository> [<refs>...]]"), NULL }; diff --git a/builtin/merge.c b/builtin/merge.c index 3fbdacc7db..cc4a910c69 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -13,6 +13,7 @@ #include "builtin.h" #include "lockfile.h" #include "run-command.h" +#include "hook.h" #include "diff.h" #include "diff-merges.h" #include "refs.h" @@ -680,6 +681,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ trees[nr_trees] = parse_tree_indirect(common); if (!trees[nr_trees++]) return -1; @@ -848,7 +850,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) * and write it out as a tree. We must do this before we invoke * the editor and after we invoke run_status above. */ - if (find_hook("pre-merge-commit")) + if (hook_exists("pre-merge-commit")) discard_cache(); read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); diff --git a/builtin/mktag.c b/builtin/mktag.c index dddcccdd36..3b2dbbb37e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -62,7 +62,8 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) repl = lookup_replace_object(the_repository, tagged_oid); ret = check_object_signature(the_repository, repl, - buffer, size, type_name(*tagged_type)); + buffer, size, type_name(*tagged_type), + NULL); free(buffer); return ret; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 66de6efd41..075d15d706 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -7,7 +7,8 @@ #include "object-store.h" #define BUILTIN_MIDX_WRITE_USAGE \ - N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]") + N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \ + "[--refs-snapshot=<path>]") #define BUILTIN_MIDX_VERIFY_USAGE \ N_("git multi-pack-index [<options>] verify") @@ -45,14 +46,15 @@ static char const * const builtin_multi_pack_index_usage[] = { static struct opts_multi_pack_index { const char *object_dir; const char *preferred_pack; + const char *refs_snapshot; unsigned long batch_size; unsigned flags; + int stdin_packs; } opts; static struct option common_opts[] = { OPT_FILENAME(0, "object-dir", &opts.object_dir, N_("object directory containing set of packfile and pack-index pairs")), - OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; @@ -61,6 +63,33 @@ static struct option *add_common_options(struct option *prev) return parse_options_concat(common_opts, prev); } +static int git_multi_pack_index_write_config(const char *var, const char *value, + void *cb) +{ + if (!strcmp(var, "pack.writebitmaphashcache")) { + if (git_config_bool(var, value)) + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + else + opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE; + } + + /* + * We should never make a fall-back call to 'git_default_config', since + * this was already called in 'cmd_multi_pack_index()'. + */ + return 0; +} + +static void read_packs_from_stdin(struct string_list *to) +{ + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) + string_list_append(to, buf.buf); + string_list_sort(to); + + strbuf_release(&buf); +} + static int cmd_multi_pack_index_write(int argc, const char **argv) { struct option *options; @@ -70,13 +99,25 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) N_("pack for reuse when computing a multi-pack bitmap")), OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, + N_("write multi-pack index containing only given indexes")), + OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot, + N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + + git_config(git_multi_pack_index_write_config, NULL); + options = add_common_options(builtin_multi_pack_index_write_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_write_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -86,16 +127,39 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) FREE_AND_NULL(options); + if (opts.stdin_packs) { + struct string_list packs = STRING_LIST_INIT_DUP; + int ret; + + read_packs_from_stdin(&packs); + + ret = write_midx_file_only(opts.object_dir, &packs, + opts.preferred_pack, + opts.refs_snapshot, opts.flags); + + string_list_clear(&packs, 0); + + return ret; + + } return write_midx_file(opts.object_dir, opts.preferred_pack, - opts.flags); + opts.refs_snapshot, opts.flags); } static int cmd_multi_pack_index_verify(int argc, const char **argv) { - struct option *options = common_opts; + struct option *options; + static struct option builtin_multi_pack_index_verify_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_verify_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_verify_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -108,10 +172,18 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) static int cmd_multi_pack_index_expire(int argc, const char **argv) { - struct option *options = common_opts; + struct option *options; + static struct option builtin_multi_pack_index_expire_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_expire_options); trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_expire_usage, PARSE_OPT_KEEP_UNKNOWN); @@ -128,6 +200,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) static struct option builtin_multi_pack_index_repack_options[] = { OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; @@ -135,6 +209,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) trace2_cmd_mode(argv[0]); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_repack_usage, @@ -156,8 +232,6 @@ int cmd_multi_pack_index(int argc, const char **argv, git_config(git_default_config, NULL); - if (isatty(2)) - opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, diff --git a/builtin/mv.c b/builtin/mv.c index c2f96c8e89..83a465ba83 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length, int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT__DRY_RUN(&show_only, N_("dry run")), OPT__FORCE(&force, N_("force move/rename even if target exists"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), + OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; + enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; struct cache_entry *ce; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); @@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) const char *src = source[i], *dst = destination[i]; int length, src_is_dir; const char *bad = NULL; + int skip_sparse = 0; if (show_only) printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); - if (lstat(src, &st) < 0) - bad = _("bad source"); - else if (!strncmp(src, dst, length) && + if (lstat(src, &st) < 0) { + /* only error if existence is expected. */ + if (modes[i] != SPARSE) + bad = _("bad source"); + } else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) @@ -212,11 +217,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_len = strlen(dst); for (j = 0; j < last - first; j++) { - const char *path = active_cache[first + j]->name; + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; source[argc + j] = path; destination[argc + j] = prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = INDEX; + modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; } argc += last - first; @@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix) bad = _("multiple sources for the same target"); else if (is_dir_sep(dst[strlen(dst) - 1])) bad = _("destination directory does not exist"); - else + else { + /* + * We check if the paths are in the sparse-checkout + * definition as a very final check, since that + * allows us to point the user to the --sparse + * option as a way to have a successful run. + */ + if (!ignore_sparse && + !path_in_sparse_checkout(src, &the_index)) { + string_list_append(&only_match_skip_worktree, src); + skip_sparse = 1; + } + if (!ignore_sparse && + !path_in_sparse_checkout(dst, &the_index)) { + string_list_append(&only_match_skip_worktree, dst); + skip_sparse = 1; + } + + if (skip_sparse) + goto remove_entry; + string_list_insert(&src_for_dst, dst); + } if (!bad) continue; if (!ignore_errors) die(_("%s, source=%s, destination=%s"), bad, src, dst); +remove_entry: if (--argc > 0) { int n = argc - i; memmove(source + i, source + i + 1, @@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } } + if (only_match_skip_worktree.nr) { + advise_on_updating_sparse_paths(&only_match_skip_worktree); + if (!ignore_errors) + return 1; + } + for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; @@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && rename(src, dst) < 0) { + if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); diff --git a/builtin/pull.c b/builtin/pull.c index cf6c56e2d8..ae9f5bd7cc 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -31,9 +31,8 @@ /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is - * "merges", returns REBASE_MERGES. If value is "preserve", returns - * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if - * fatal is true, otherwise returns REBASE_INVALID. + * "merges", returns REBASE_MERGES. If value is a invalid value, dies with + * a fatal error if fatal is true, otherwise returns REBASE_INVALID. */ static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) @@ -127,7 +126,7 @@ static struct option pull_options[] = { /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), OPT_CALLBACK_F('r', "rebase", &opt_rebase, - "(false|true|merges|preserve|interactive)", + "(false|true|merges|interactive)", N_("incorporate changes by rebasing rather than merging"), PARSE_OPT_OPTARG, parse_opt_rebase), OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, @@ -884,8 +883,6 @@ static int run_rebase(const struct object_id *newbase, /* Options passed to git-rebase */ if (opt_rebase == REBASE_MERGES) strvec_push(&args, "--rebase-merges"); - else if (opt_rebase == REBASE_PRESERVE) - strvec_push(&args, "--preserve-merges"); else if (opt_rebase == REBASE_INTERACTIVE) strvec_push(&args, "--interactive"); if (opt_diffstat) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 485e7b0479..2109c4c9e5 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -38,7 +38,7 @@ static int list_tree(struct object_id *oid) } static const char * const read_tree_usage[] = { - N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), + N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"), NULL }; @@ -53,24 +53,16 @@ static int index_output_cb(const struct option *opt, const char *arg, static int exclude_per_directory_cb(const struct option *opt, const char *arg, int unset) { - struct dir_struct *dir; struct unpack_trees_options *opts; BUG_ON_OPT_NEG(unset); opts = (struct unpack_trees_options *)opt->value; - if (opts->dir) - die("more than one --exclude-per-directory given."); - - dir = xcalloc(1, sizeof(*opts->dir)); - dir->flags |= DIR_SHOW_IGNORED; - dir->exclude_per_dir = arg; - opts->dir = dir; - /* We do not need to nor want to do read-directory - * here; we are merely interested in reusing the - * per directory ignore stack mechanism. - */ + if (!opts->update) + die("--exclude-per-directory is meaningless unless -u"); + if (strcmp(arg, ".gitignore")) + die("--exclude-per-directory argument must be .gitignore"); return 0; } @@ -174,6 +166,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + if (opts.reset) + opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + /* * NEEDSWORK * @@ -209,8 +204,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if ((opts.update || opts.index_only) && !opts.merge) die("%s is meaningless without -m, --reset, or --prefix", opts.update ? "-u" : "-i"); - if ((opts.dir && !opts.update)) - die("--exclude-per-directory is meaningless unless -u"); + if (opts.update && !opts.reset) + opts.preserve_ignored = 0; + /* otherwise, opts.preserve_ignored is irrelevant */ if (opts.merge && !opts.index_only) setup_work_tree(); diff --git a/builtin/rebase.c b/builtin/rebase.c index 6c2463037f..34b4744e5f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -48,8 +48,7 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge") enum rebase_type { REBASE_UNSPECIFIED = -1, REBASE_APPLY, - REBASE_MERGE, - REBASE_PRESERVE_MERGES + REBASE_MERGE }; enum empty_type { @@ -163,12 +162,7 @@ enum action { ACTION_ABORT, ACTION_QUIT, ACTION_EDIT_TODO, - ACTION_SHOW_CURRENT_PATCH, - ACTION_SHORTEN_OIDS, - ACTION_EXPAND_OIDS, - ACTION_CHECK_TODO_LIST, - ACTION_REARRANGE_SQUASH, - ACTION_ADD_EXEC + ACTION_SHOW_CURRENT_PATCH }; static const char *action_names[] = { "undefined", @@ -179,81 +173,6 @@ static const char *action_names[] = { "undefined", "edit_todo", "show_current_patch" }; -static int add_exec_commands(struct string_list *commands) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - todo_list_add_exec_commands(&todo_list, commands); - res = todo_list_write_to_file(the_repository, &todo_list, - todo_file, NULL, NULL, -1, 0); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - -static int rearrange_squash_in_todo_file(void) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res = 0; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - res = todo_list_rearrange_squash(&todo_list); - if (!res) - res = todo_list_write_to_file(the_repository, &todo_list, - todo_file, NULL, NULL, -1, 0); - - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - -static int transform_todo_file(unsigned flags) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, - &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - res = todo_list_write_to_file(the_repository, &todo_list, todo_file, - NULL, NULL, -1, flags); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - static int edit_todo_file(unsigned flags) { const char *todo_file = rebase_path_todo(); @@ -403,7 +322,6 @@ static int run_sequencer_rebase(struct rebase_options *opts, flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; - flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0; flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0; @@ -439,24 +357,6 @@ static int run_sequencer_rebase(struct rebase_options *opts, break; } - case ACTION_SHORTEN_OIDS: - case ACTION_EXPAND_OIDS: - ret = transform_todo_file(flags); - break; - case ACTION_CHECK_TODO_LIST: - ret = check_todo_list_from_file(the_repository); - break; - case ACTION_REARRANGE_SQUASH: - ret = rearrange_squash_in_todo_file(); - break; - case ACTION_ADD_EXEC: { - struct string_list commands = STRING_LIST_INIT_DUP; - - split_exec_commands(opts->cmd, &commands); - ret = add_exec_commands(&commands); - string_list_clear(&commands, 0); - break; - } default: BUG("invalid command '%d'", command); } @@ -478,105 +378,9 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg, return 0; } -static const char * const builtin_rebase_interactive_usage[] = { - N_("git rebase--interactive [<options>]"), - NULL -}; - -int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) -{ - struct rebase_options opts = REBASE_OPTIONS_INIT; - struct object_id squash_onto = *null_oid(); - enum action command = ACTION_NONE; - struct option options[] = { - OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), - REBASE_FORCE), - OPT_CALLBACK_F('k', "keep-empty", &options, NULL, - N_("keep commits which start empty"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, - parse_opt_keep_empty), - OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages"), - PARSE_OPT_HIDDEN), - OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), - OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins, - N_("keep original branch points of cousins")), - OPT_BOOL(0, "autosquash", &opts.autosquash, - N_("move commits that begin with squash!/fixup!")), - OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")), - OPT_BIT('v', "verbose", &opts.flags, - N_("display a diffstat of what changed upstream"), - REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), - OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), - ACTION_CONTINUE), - OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP), - OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"), - ACTION_EDIT_TODO), - OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"), - ACTION_SHOW_CURRENT_PATCH), - OPT_CMDMODE(0, "shorten-ids", &command, - N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS), - OPT_CMDMODE(0, "expand-ids", &command, - N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS), - OPT_CMDMODE(0, "check-todo-list", &command, - N_("check the todo list"), ACTION_CHECK_TODO_LIST), - OPT_CMDMODE(0, "rearrange-squash", &command, - N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH), - OPT_CMDMODE(0, "add-exec-commands", &command, - N_("insert exec commands in todo list"), ACTION_ADD_EXEC), - { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"), - PARSE_OPT_NONEG, parse_opt_commit, 0 }, - { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision, - N_("restrict-revision"), N_("restrict revision"), - PARSE_OPT_NONEG, parse_opt_commit, 0 }, - { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"), - N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 }, - { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"), - N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit, - 0 }, - OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")), - { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"), - N_("GPG-sign commits"), - PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"), - N_("rebase strategy")), - OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"), - N_("strategy options")), - OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"), - N_("the branch or commit to checkout")), - OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")), - OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")), - OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate), - OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, - N_("automatically re-schedule any `exec` that fails")), - OPT_END() - }; - - opts.rebase_cousins = -1; - - if (argc == 1) - usage_with_options(builtin_rebase_interactive_usage, options); - - argc = parse_options(argc, argv, prefix, options, - builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); - - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; - - if (!is_null_oid(&squash_onto)) - opts.squash_onto = &squash_onto; - - if (opts.rebase_cousins >= 0 && !opts.rebase_merges) - warning(_("--[no-]rebase-cousins has no effect without " - "--rebase-merges")); - - return !!run_sequencer_rebase(&opts, command); -} - static int is_merge(struct rebase_options *opts) { - return opts->type == REBASE_MERGE || - opts->type == REBASE_PRESERVE_MERGES; + return opts->type == REBASE_MERGE; } static void imply_merge(struct rebase_options *opts, const char *option) @@ -586,7 +390,6 @@ static void imply_merge(struct rebase_options *opts, const char *option) die(_("%s requires the merge backend"), option); break; case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: break; default: opts->type = REBASE_MERGE; /* implied */ @@ -765,28 +568,6 @@ static int finish_rebase(struct rebase_options *opts) return ret; } -static struct commit *peel_committish(const char *name) -{ - struct object *obj; - struct object_id oid; - - if (get_oid(name, &oid)) - return NULL; - obj = parse_object(the_repository, &oid); - return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); -} - -static void add_var(struct strbuf *buf, const char *name, const char *value) -{ - if (!value) - strbuf_addf(buf, "unset %s; ", name); - else { - strbuf_addf(buf, "%s=", name); - sq_quote_buf(buf, value); - strbuf_addstr(buf, "; "); - } -} - static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; @@ -943,10 +724,7 @@ static int run_am(struct rebase_options *opts) static int run_specific_rebase(struct rebase_options *opts, enum action action) { - const char *argv[] = { NULL, NULL }; - struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT; int status; - const char *backend, *backend_func; if (opts->type == REBASE_MERGE) { /* Run sequencer-based rebase */ @@ -963,87 +741,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) } status = run_sequencer_rebase(opts, action); - goto finished_rebase; - } - - if (opts->type == REBASE_APPLY) { + } else if (opts->type == REBASE_APPLY) status = run_am(opts); - goto finished_rebase; - } - - add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); - add_var(&script_snippet, "state_dir", opts->state_dir); - - add_var(&script_snippet, "upstream_name", opts->upstream_name); - add_var(&script_snippet, "upstream", opts->upstream ? - oid_to_hex(&opts->upstream->object.oid) : NULL); - add_var(&script_snippet, "head_name", - opts->head_name ? opts->head_name : "detached HEAD"); - add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); - add_var(&script_snippet, "onto", opts->onto ? - oid_to_hex(&opts->onto->object.oid) : NULL); - add_var(&script_snippet, "onto_name", opts->onto_name); - add_var(&script_snippet, "revisions", opts->revisions); - add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? - oid_to_hex(&opts->restrict_revision->object.oid) : NULL); - sq_quote_argv_pretty(&buf, opts->git_am_opts.v); - add_var(&script_snippet, "git_am_opt", buf.buf); - strbuf_release(&buf); - add_var(&script_snippet, "verbose", - opts->flags & REBASE_VERBOSE ? "t" : ""); - add_var(&script_snippet, "diffstat", - opts->flags & REBASE_DIFFSTAT ? "t" : ""); - add_var(&script_snippet, "force_rebase", - opts->flags & REBASE_FORCE ? "t" : ""); - if (opts->switch_to) - add_var(&script_snippet, "switch_to", opts->switch_to); - add_var(&script_snippet, "action", opts->action ? opts->action : ""); - add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); - add_var(&script_snippet, "allow_rerere_autoupdate", - opts->allow_rerere_autoupdate ? - opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? - "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); - add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); - add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); - add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); - add_var(&script_snippet, "cmd", opts->cmd); - add_var(&script_snippet, "allow_empty_message", - opts->allow_empty_message ? "--allow-empty-message" : ""); - add_var(&script_snippet, "rebase_merges", - opts->rebase_merges ? "t" : ""); - add_var(&script_snippet, "rebase_cousins", - opts->rebase_cousins ? "t" : ""); - add_var(&script_snippet, "strategy", opts->strategy); - add_var(&script_snippet, "strategy_opts", opts->strategy_opts); - add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); - add_var(&script_snippet, "squash_onto", - opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); - add_var(&script_snippet, "git_format_patch_opt", - opts->git_format_patch_opt.buf); - - if (is_merge(opts) && - !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { - strbuf_addstr(&script_snippet, - "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); - opts->autosquash = 0; - } - - switch (opts->type) { - case REBASE_PRESERVE_MERGES: - backend = "git-rebase--preserve-merges"; - backend_func = "git_rebase__preserve_merges"; - break; - default: + else BUG("Unhandled rebase type %d", opts->type); - break; - } - strbuf_addf(&script_snippet, - ". git-sh-setup && . %s && %s", backend, backend_func); - argv[0] = script_snippet.buf; - - status = run_command_v_opt(argv, RUN_USING_SHELL); -finished_rebase: if (opts->dont_finish_rebase) ; /* do nothing */ else if (opts->type == REBASE_MERGE) @@ -1061,8 +763,6 @@ finished_rebase: die("Nothing to do"); } - strbuf_release(&script_snippet); - return status ? -1 : 0; } @@ -1198,7 +898,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) return 0; } -/* -i followed by -p is still explicitly interactive, but -p alone is not */ +/* -i followed by -r is still explicitly interactive, but -r alone is not */ static int parse_opt_interactive(const struct option *opt, const char *arg, int unset) { @@ -1316,6 +1016,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) char *squash_onto_name = NULL; int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; + int preserve_merges_selected = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1380,10 +1081,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("let the user edit the list of commits to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_interactive), - OPT_SET_INT_F('p', "preserve-merges", &options.type, + OPT_SET_INT_F('p', "preserve-merges", &preserve_merges_selected, N_("(DEPRECATED) try to recreate merges instead of " "ignoring them"), - REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), + 1, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}", N_("how to handle commits that become empty"), @@ -1454,8 +1155,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); strbuf_addf(&buf, "%s/rewritten", merge_dir()); if (is_directory(buf.buf)) { - options.type = REBASE_PRESERVE_MERGES; - options.flags |= REBASE_INTERACTIVE_EXPLICIT; + die("`rebase -p` is no longer supported"); } else { strbuf_reset(&buf); strbuf_addf(&buf, "%s/interactive", merge_dir()); @@ -1476,6 +1176,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) builtin_rebase_options, builtin_rebase_usage, 0); + if (preserve_merges_selected) + die(_("--preserve-merges was replaced by --rebase-merges")); + if (action != ACTION_NONE && total_argc != 2) { usage_with_options(builtin_rebase_usage, builtin_rebase_options); @@ -1485,10 +1188,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); - if (options.type == REBASE_PRESERVE_MERGES) - warning(_("git rebase --preserve-merges is deprecated. " - "Use --rebase-merges instead.")); - if (keep_base) { if (options.onto_name) die(_("cannot combine '--keep-base' with '--onto'")); @@ -1580,7 +1279,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } case ACTION_QUIT: { @@ -1589,11 +1288,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; - ret = !!sequencer_remove_state(&replay); + ret = sequencer_remove_state(&replay); } else { strbuf_reset(&buf); strbuf_addstr(&buf, options.state_dir); - ret = !!remove_dir_recursively(&buf, 0); + ret = remove_dir_recursively(&buf, 0); if (ret) error(_("could not remove '%s'"), options.state_dir); @@ -1708,7 +1407,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.ignore_date) strvec_push(&options.git_am_opts, "--ignore-date"); } else { - /* REBASE_MERGE and PRESERVE_MERGES */ + /* REBASE_MERGE */ if (ignore_whitespace) { string_list_append(&strategy_options, "ignore-space-change"); @@ -1734,7 +1433,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) case REBASE_APPLY: die(_("--strategy requires --merge or --interactive")); case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: /* compatible */ break; case REBASE_UNSPECIFIED: @@ -1786,7 +1484,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) switch (options.type) { case REBASE_MERGE: - case REBASE_PRESERVE_MERGES: options.state_dir = merge_dir(); break; case REBASE_APPLY: @@ -1811,28 +1508,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.reschedule_failed_exec = reschedule_failed_exec; if (options.signoff) { - if (options.type == REBASE_PRESERVE_MERGES) - die("cannot combine '--signoff' with " - "'--preserve-merges'"); strvec_push(&options.git_am_opts, "--signoff"); options.flags |= REBASE_FORCE; } - if (options.type == REBASE_PRESERVE_MERGES) { - /* - * Note: incompatibility with --signoff handled in signoff block above - * Note: incompatibility with --interactive is just a strong warning; - * git-rebase.txt caveats with "unless you know what you are doing" - */ - if (options.rebase_merges) - die(_("cannot combine '--preserve-merges' with " - "'--rebase-merges'")); - - if (options.reschedule_failed_exec) - die(_("error: cannot combine '--preserve-merges' with " - "'--reschedule-failed-exec'")); - } - if (!options.root) { if (argc < 1) { struct branch *branch; @@ -1851,7 +1530,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!strcmp(options.upstream_name, "-")) options.upstream_name = "@{-1}"; } - options.upstream = peel_committish(options.upstream_name); + options.upstream = + lookup_commit_reference_by_name(options.upstream_name); if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); options.upstream_arg = options.upstream_name; @@ -1894,7 +1574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { - options.onto = peel_committish(options.onto_name); + options.onto = + lookup_commit_reference_by_name(options.onto_name); if (!options.onto) die(_("Does not point to a valid commit '%s'"), options.onto_name); @@ -1919,13 +1600,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die_if_checked_out(buf.buf, 1); options.head_name = xstrdup(buf.buf); /* If not is it a valid ref (branch or commit)? */ - } else if (!get_oid(branch_name, &options.orig_head) && - lookup_commit_reference(the_repository, - &options.orig_head)) + } else { + struct commit *commit = + lookup_commit_reference_by_name(branch_name); + if (!commit) + die(_("no such branch/commit '%s'"), + branch_name); + oidcpy(&options.orig_head, &commit->object.oid); options.head_name = NULL; - else - die(_("no such branch/commit '%s'"), - branch_name); + } } else if (argc == 0) { /* Do not need to switch branches, we are already on it. */ options.head_name = @@ -1965,7 +1648,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (require_clean_work_tree(the_repository, "rebase", _("Please commit or stash them."), 1, 1)) { - ret = 1; + ret = -1; goto cleanup; } @@ -2000,7 +1683,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, buf.buf, DEFAULT_REFLOG_ACTION) < 0) { - ret = !!error(_("could not switch to " + ret = error(_("could not switch to " "%s"), options.switch_to); goto cleanup; @@ -2015,7 +1698,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) else printf(_("Current branch %s is up to date.\n"), branch_name); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } else if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ @@ -2093,7 +1776,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, DEFAULT_REFLOG_ACTION); strbuf_release(&msg); - ret = !!finish_rebase(&options); + ret = finish_rebase(&options); goto cleanup; } @@ -2107,7 +1790,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.revisions = revisions.buf; run_rebase: - ret = !!run_specific_rebase(&options, action); + ret = run_specific_rebase(&options, action); cleanup: strbuf_release(&buf); @@ -2118,5 +1801,5 @@ cleanup: free(options.strategy); strbuf_release(&options.git_format_patch_opt); free(squash_onto_name); - return ret; + return !!ret; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 48960a9575..25cc0c907e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "hook.h" #include "exec-cmd.h" #include "commit.h" #include "object.h" @@ -1463,7 +1464,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); - if (!find_hook(push_to_checkout_hook)) + if (!hook_exists(push_to_checkout_hook)) retval = push_to_deploy(sha1, &env, work_tree); else retval = push_to_checkout(sha1, &env, work_tree); diff --git a/builtin/reflog.c b/builtin/reflog.c index 09541d1c80..bd4c669918 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -629,8 +629,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) free_worktrees(worktrees); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; + set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); - status |= reflog_expire(e->reflog, &e->oid, flags, + status |= reflog_expire(e->reflog, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -642,13 +643,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (; i < argc; i++) { char *ref; - struct object_id oid; - if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) { + if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) { status |= error(_("%s points nowhere!"), argv[i]); continue; } set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); - status |= reflog_expire(ref, &oid, flags, + status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -700,7 +700,6 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) for ( ; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); - struct object_id oid; char *ep, *ref; int recno; @@ -709,7 +708,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) continue; } - if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) { + if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) { status |= error(_("no reflog for '%s'"), argv[i]); continue; } @@ -724,7 +723,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) cb.cmd.expire_total = 0; } - status |= reflog_expire(ref, &oid, flags, + status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, diff --git a/builtin/remote.c b/builtin/remote.c index 7f88e6ce9d..299c466116 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -318,6 +318,9 @@ static int config_read_branches(const char *key, const char *value, void *cb) * truth value with >= REBASE_TRUE. */ info->rebase = rebase_parse_value(value); + if (info->rebase == REBASE_INVALID) + warning(_("unhandled branch.%s.rebase=%s; assuming " + "'true'"), name, value); break; case PUSH_REMOTE: if (info->push_remote_name) @@ -344,6 +347,14 @@ struct ref_states { int queried; }; +#define REF_STATES_INIT { \ + .new_refs = STRING_LIST_INIT_DUP, \ + .stale = STRING_LIST_INIT_DUP, \ + .tracked = STRING_LIST_INIT_DUP, \ + .heads = STRING_LIST_INIT_DUP, \ + .push = STRING_LIST_INIT_DUP, \ +} + static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; @@ -355,9 +366,6 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat die(_("Could not get fetch map for refspec %s"), states->remote->fetch.raw[i]); - states->new_refs.strdup_strings = 1; - states->tracked.strdup_strings = 1; - states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new_refs, abbrev_branch(ref->name)); @@ -406,7 +414,6 @@ static int get_push_ref_states(const struct ref *remote_refs, match_push_refs(local_refs, &push_map, &remote->push, MATCH_REFS_NONE); - states->push.strdup_strings = 1; for (ref = push_map; ref; ref = ref->next) { struct string_list_item *item; struct push_info *info; @@ -449,7 +456,6 @@ static int get_push_ref_states_noquery(struct ref_states *states) if (remote->mirror) return 0; - states->push.strdup_strings = 1; if (!remote->push.nr) { item = string_list_append(&states->push, _("(matching)")); info = item->util = xcalloc(1, sizeof(struct push_info)); @@ -483,7 +489,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat refspec.force = 0; refspec.pattern = 1; refspec.src = refspec.dst = "refs/heads/*"; - states->heads.strdup_strings = 1; get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), fetch_map, 1); @@ -970,26 +975,31 @@ static int get_remote_ref_states(const char *name, } struct show_info { - struct string_list *list; - struct ref_states *states; + struct string_list list; + struct ref_states states; int width, width2; int any_rebase; }; +#define SHOW_INFO_INIT { \ + .list = STRING_LIST_INIT_DUP, \ + .states = REF_STATES_INIT, \ +} + static int add_remote_to_show_info(struct string_list_item *item, void *cb_data) { struct show_info *info = cb_data; int n = strlen(item->string); if (n > info->width) info->width = n; - string_list_insert(info->list, item->string); + string_list_insert(&info->list, item->string); return 0; } static int show_remote_info_item(struct string_list_item *item, void *cb_data) { struct show_info *info = cb_data; - struct ref_states *states = info->states; + struct ref_states *states = &info->states; const char *name = item->string; if (states->queried) { @@ -1016,7 +1026,7 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data) { struct show_info *show_info = cb_data; - struct ref_states *states = show_info->states; + struct ref_states *states = &show_info->states; struct branch_info *branch_info = branch_item->util; struct string_list_item *item; int n; @@ -1029,7 +1039,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb if (branch_info->rebase >= REBASE_TRUE) show_info->any_rebase = 1; - item = string_list_insert(show_info->list, branch_item->string); + item = string_list_insert(&show_info->list, branch_item->string); item->util = branch_info; return 0; @@ -1084,7 +1094,7 @@ static int add_push_to_show_info(struct string_list_item *push_item, void *cb_da show_info->width = n; if ((n = strlen(push_info->dest)) > show_info->width2) show_info->width2 = n; - item = string_list_append(show_info->list, push_item->string); + item = string_list_append(&show_info->list, push_item->string); item->util = push_item->util; return 0; } @@ -1212,9 +1222,7 @@ static int show(int argc, const char **argv) OPT_BOOL('n', NULL, &no_query, N_("do not query remotes")), OPT_END() }; - struct ref_states states; - struct string_list info_list = STRING_LIST_INIT_NODUP; - struct show_info info; + struct show_info info = SHOW_INFO_INIT; argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, 0); @@ -1225,26 +1233,22 @@ static int show(int argc, const char **argv) if (!no_query) query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES); - memset(&states, 0, sizeof(states)); - memset(&info, 0, sizeof(info)); - info.states = &states; - info.list = &info_list; for (; argc; argc--, argv++) { int i; const char **url; int url_nr; - get_remote_ref_states(*argv, &states, query_flag); + get_remote_ref_states(*argv, &info.states, query_flag); printf_ln(_("* remote %s"), *argv); - printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ? - states.remote->url[0] : _("(no URL)")); - if (states.remote->pushurl_nr) { - url = states.remote->pushurl; - url_nr = states.remote->pushurl_nr; + printf_ln(_(" Fetch URL: %s"), info.states.remote->url_nr > 0 ? + info.states.remote->url[0] : _("(no URL)")); + if (info.states.remote->pushurl_nr) { + url = info.states.remote->pushurl; + url_nr = info.states.remote->pushurl_nr; } else { - url = states.remote->url; - url_nr = states.remote->url_nr; + url = info.states.remote->url; + url_nr = info.states.remote->url_nr; } for (i = 0; i < url_nr; i++) /* @@ -1257,57 +1261,57 @@ static int show(int argc, const char **argv) printf_ln(_(" Push URL: %s"), _("(no URL)")); if (no_query) printf_ln(_(" HEAD branch: %s"), _("(not queried)")); - else if (!states.heads.nr) + else if (!info.states.heads.nr) printf_ln(_(" HEAD branch: %s"), _("(unknown)")); - else if (states.heads.nr == 1) - printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string); + else if (info.states.heads.nr == 1) + printf_ln(_(" HEAD branch: %s"), info.states.heads.items[0].string); else { printf(_(" HEAD branch (remote HEAD is ambiguous," " may be one of the following):\n")); - for (i = 0; i < states.heads.nr; i++) - printf(" %s\n", states.heads.items[i].string); + for (i = 0; i < info.states.heads.nr; i++) + printf(" %s\n", info.states.heads.items[i].string); } /* remote branch info */ info.width = 0; - for_each_string_list(&states.new_refs, add_remote_to_show_info, &info); - for_each_string_list(&states.tracked, add_remote_to_show_info, &info); - for_each_string_list(&states.stale, add_remote_to_show_info, &info); - if (info.list->nr) + for_each_string_list(&info.states.new_refs, add_remote_to_show_info, &info); + for_each_string_list(&info.states.tracked, add_remote_to_show_info, &info); + for_each_string_list(&info.states.stale, add_remote_to_show_info, &info); + if (info.list.nr) printf_ln(Q_(" Remote branch:%s", " Remote branches:%s", - info.list->nr), + info.list.nr), no_query ? _(" (status not queried)") : ""); - for_each_string_list(info.list, show_remote_info_item, &info); - string_list_clear(info.list, 0); + for_each_string_list(&info.list, show_remote_info_item, &info); + string_list_clear(&info.list, 0); /* git pull info */ info.width = 0; info.any_rebase = 0; for_each_string_list(&branch_list, add_local_to_show_info, &info); - if (info.list->nr) + if (info.list.nr) printf_ln(Q_(" Local branch configured for 'git pull':", " Local branches configured for 'git pull':", - info.list->nr)); - for_each_string_list(info.list, show_local_info_item, &info); - string_list_clear(info.list, 0); + info.list.nr)); + for_each_string_list(&info.list, show_local_info_item, &info); + string_list_clear(&info.list, 0); /* git push info */ - if (states.remote->mirror) + if (info.states.remote->mirror) printf_ln(_(" Local refs will be mirrored by 'git push'")); info.width = info.width2 = 0; - for_each_string_list(&states.push, add_push_to_show_info, &info); - QSORT(info.list->items, info.list->nr, cmp_string_with_push); - if (info.list->nr) + for_each_string_list(&info.states.push, add_push_to_show_info, &info); + QSORT(info.list.items, info.list.nr, cmp_string_with_push); + if (info.list.nr) printf_ln(Q_(" Local ref configured for 'git push'%s:", " Local refs configured for 'git push'%s:", - info.list->nr), + info.list.nr), no_query ? _(" (status not queried)") : ""); - for_each_string_list(info.list, show_push_info_item, &info); - string_list_clear(info.list, 0); + for_each_string_list(&info.list, show_push_info_item, &info); + string_list_clear(&info.list, 0); - free_remote_ref_states(&states); + free_remote_ref_states(&info.states); } return result; @@ -1334,8 +1338,7 @@ static int set_head(int argc, const char **argv) if (!opt_a && !opt_d && argc == 2) { head_name = xstrdup(argv[1]); } else if (opt_a && !opt_d && argc == 1) { - struct ref_states states; - memset(&states, 0, sizeof(states)); + struct ref_states states = REF_STATES_INIT; get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES); if (!states.heads.nr) result |= error(_("Cannot determine remote HEAD")); @@ -1374,14 +1377,13 @@ static int set_head(int argc, const char **argv) static int prune_remote(const char *remote, int dry_run) { int result = 0; - struct ref_states states; + struct ref_states states = REF_STATES_INIT; struct string_list refs_to_prune = STRING_LIST_INIT_NODUP; struct string_list_item *item; const char *dangling_msg = dry_run ? _(" %s will become dangling!") : _(" %s has become dangling!"); - memset(&states, 0, sizeof(states)); get_remote_ref_states(remote, &states, GET_REF_STATES); if (!states.stale.nr) { diff --git a/builtin/repack.c b/builtin/repack.c index cb9f4bfed3..0b2d1e5d82 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -15,6 +15,8 @@ #include "promisor-remote.h" #include "shallow.h" #include "pack.h" +#include "pack-bitmap.h" +#include "refs.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; @@ -94,12 +96,14 @@ static void remove_pack_on_signal(int signo) } /* - * Adds all packs hex strings to the fname list, which do not - * have a corresponding .keep file. These packs are not to - * be kept if we are going to pack everything into one file. + * Adds all packs hex strings to either fname_nonkept_list or + * fname_kept_list 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 get_non_kept_pack_filenames(struct string_list *fname_list, - const struct string_list *extra_keep) +static void collect_pack_filenames(struct string_list *fname_nonkept_list, + struct string_list *fname_kept_list, + const struct string_list *extra_keep) { DIR *dir; struct dirent *e; @@ -112,21 +116,20 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list, size_t len; int i; + if (!strip_suffix(e->d_name, ".pack", &len)) + continue; + for (i = 0; i < extra_keep->nr; i++) if (!fspathcmp(e->d_name, extra_keep->items[i].string)) break; - if (extra_keep->nr > 0 && i < extra_keep->nr) - continue; - - if (!strip_suffix(e->d_name, ".pack", &len)) - continue; fname = xmemdupz(e->d_name, len); - if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) - string_list_append_nodup(fname_list, fname); + if ((extra_keep->nr > 0 && i < extra_keep->nr) || + (file_exists(mkpath("%s/%s.keep", packdir, fname)))) + string_list_append_nodup(fname_kept_list, fname); else - free(fname); + string_list_append_nodup(fname_nonkept_list, fname); } closedir(dir); } @@ -422,6 +425,25 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor) geometry->split = split; } +static struct packed_git *get_largest_active_pack(struct pack_geometry *geometry) +{ + if (!geometry) { + /* + * No geometry means either an all-into-one repack (in which + * case there is only one pack left and it is the largest) or an + * incremental one. + * + * If repacking incrementally, then we could check the size of + * all packs to determine which should be preferred, but leave + * this for later. + */ + return NULL; + } + if (geometry->split == geometry->pack_nr) + return NULL; + return geometry->pack[geometry->pack_nr - 1]; +} + static void clear_pack_geometry(struct pack_geometry *geometry) { if (!geometry) @@ -433,17 +455,162 @@ static void clear_pack_geometry(struct pack_geometry *geometry) geometry->split = 0; } +struct midx_snapshot_ref_data { + struct tempfile *f; + struct oidset seen; + int preferred; +}; + +static int midx_snapshot_ref_one(const char *refname, + const struct object_id *oid, + int flag, void *_data) +{ + struct midx_snapshot_ref_data *data = _data; + struct object_id peeled; + + if (!peel_iterated_oid(oid, &peeled)) + oid = &peeled; + + if (oidset_insert(&data->seen, oid)) + return 0; /* already seen */ + + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "", + oid_to_hex(oid)); + + return 0; +} + +static void midx_snapshot_refs(struct tempfile *f) +{ + struct midx_snapshot_ref_data data; + const struct string_list *preferred = bitmap_preferred_tips(the_repository); + + data.f = f; + data.preferred = 0; + oidset_init(&data.seen, 0); + + if (!fdopen_tempfile(f, "w")) + die(_("could not open tempfile %s for writing"), + get_tempfile_path(f)); + + if (preferred) { + struct string_list_item *item; + + data.preferred = 1; + for_each_string_list_item(item, preferred) + for_each_ref_in(item->string, midx_snapshot_ref_one, &data); + data.preferred = 0; + } + + for_each_ref(midx_snapshot_ref_one, &data); + + if (close_tempfile_gently(f)) { + int save_errno = errno; + delete_tempfile(&f); + errno = save_errno; + die_errno(_("could not close refs snapshot tempfile")); + } + + oidset_clear(&data.seen); +} + +static void midx_included_packs(struct string_list *include, + struct string_list *existing_nonkept_packs, + struct string_list *existing_kept_packs, + struct string_list *names, + struct pack_geometry *geometry) +{ + struct string_list_item *item; + + 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) { + struct strbuf buf = STRBUF_INIT; + uint32_t i; + for (i = geometry->split; i < geometry->pack_nr; i++) { + struct packed_git *p = geometry->pack[i]; + + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + strbuf_addstr(&buf, ".idx"); + + string_list_insert(include, strbuf_detach(&buf, NULL)); + } + } else { + for_each_string_list_item(item, existing_nonkept_packs) { + if (item->util) + continue; + string_list_insert(include, xstrfmt("%s.idx", item->string)); + } + } +} + +static int write_midx_included_packs(struct string_list *include, + struct pack_geometry *geometry, + const char *refs_snapshot, + int show_progress, int write_bitmaps) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct string_list_item *item; + struct packed_git *largest = get_largest_active_pack(geometry); + FILE *in; + int ret; + + if (!include->nr) + return 0; + + cmd.in = -1; + cmd.git_cmd = 1; + + strvec_push(&cmd.args, "multi-pack-index"); + strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL); + + if (show_progress) + strvec_push(&cmd.args, "--progress"); + else + strvec_push(&cmd.args, "--no-progress"); + + if (write_bitmaps) + strvec_push(&cmd.args, "--bitmap"); + + if (largest) + strvec_pushf(&cmd.args, "--preferred-pack=%s", + pack_basename(largest)); + + if (refs_snapshot) + strvec_pushf(&cmd.args, "--refs-snapshot=%s", refs_snapshot); + + ret = start_command(&cmd); + if (ret) + return ret; + + in = xfdopen(cmd.in, "w"); + for_each_string_list_item(item, include) + fprintf(in, "%s\n", item->string); + fclose(in); + + return finish_command(&cmd); +} + 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 rollback = STRING_LIST_INIT_NODUP; - struct string_list existing_packs = 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 tempfile *refs_snapshot = NULL; int i, ext, ret; FILE *out; + int show_progress = isatty(2); /* variables to be filled by option parsing */ int pack_everything = 0; @@ -454,6 +621,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int no_update_server_info = 0; struct pack_objects_args po_args = {NULL}; int geometric_factor = 0; + int write_midx = 0; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -496,6 +664,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("do not repack this pack")), OPT_INTEGER('g', "geometric", &geometric_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_END() }; @@ -512,8 +682,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) die(_("--keep-unreachable and -A are incompatible")); if (write_bitmaps < 0) { - if (!(pack_everything & ALL_INTO_ONE) || - !is_bare_repository()) + if (!write_midx && + (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository())) write_bitmaps = 0; } else if (write_bitmaps && git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0) && @@ -523,9 +693,21 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (pack_kept_objects < 0) pack_kept_objects = write_bitmaps > 0; - if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) + if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx) die(_(incremental_bitmap_conflict_error)); + if (write_midx && write_bitmaps) { + struct strbuf path = STRBUF_INIT; + + strbuf_addf(&path, "%s/%s_XXXXXX", get_object_directory(), + "bitmap-ref-tips"); + + refs_snapshot = xmks_tempfile(path.buf); + midx_snapshot_refs(refs_snapshot); + + strbuf_release(&path); + } + if (geometric_factor) { if (pack_everything) die(_("--geometric is incompatible with -A, -a")); @@ -565,19 +747,22 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } if (has_promisor_remote()) strvec_push(&cmd.args, "--exclude-promisor-objects"); - if (write_bitmaps > 0) - strvec_push(&cmd.args, "--write-bitmap-index"); - else if (write_bitmaps < 0) - strvec_push(&cmd.args, "--write-bitmap-index-quiet"); + if (!write_midx) { + if (write_bitmaps > 0) + strvec_push(&cmd.args, "--write-bitmap-index"); + else if (write_bitmaps < 0) + strvec_push(&cmd.args, "--write-bitmap-index-quiet"); + } if (use_delta_islands) strvec_push(&cmd.args, "--delta-islands"); - if (pack_everything & ALL_INTO_ONE) { - get_non_kept_pack_filenames(&existing_packs, &keep_pack_list); + collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs, + &keep_pack_list); + if (pack_everything & ALL_INTO_ONE) { repack_promisor_objects(&po_args, &names); - if (existing_packs.nr && delete_redundant) { + if (existing_nonkept_packs.nr && delete_redundant) { for_each_string_list_item(item, &names) { strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack", packtmp_name, item->string); @@ -677,20 +862,48 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } /* End of pack replacement. */ - reprepare_packed_git(the_repository); - - if (delete_redundant) { + if (delete_redundant && pack_everything & ALL_INTO_ONE) { const int hexsz = the_hash_algo->hexsz; - int opts = 0; string_list_sort(&names); - for_each_string_list_item(item, &existing_packs) { + 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; - if (!string_list_has_string(&names, sha1)) - remove_redundant_pack(packdir, item->string); + /* + * 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). + */ + item->util = (void*)(intptr_t)!string_list_has_string(&names, sha1); + } + } + + if (write_midx) { + struct string_list include = STRING_LIST_INIT_NODUP; + midx_included_packs(&include, &existing_nonkept_packs, + &existing_kept_packs, &names, geometry); + + ret = write_midx_included_packs(&include, geometry, + refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL, + show_progress, write_bitmaps > 0); + + string_list_clear(&include, 0); + + if (ret) + return ret; + } + + reprepare_packed_git(the_repository); + + if (delete_redundant) { + int opts = 0; + for_each_string_list_item(item, &existing_nonkept_packs) { + if (!item->util) + continue; + remove_redundant_pack(packdir, item->string); } if (geometry) { @@ -711,7 +924,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); } - if (!po_args.quiet && isatty(2)) + if (!po_args.quiet && show_progress) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -730,12 +943,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) unsigned flags = 0; if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0)) flags |= MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX; - write_midx_file(get_object_directory(), NULL, flags); + write_midx_file(get_object_directory(), NULL, NULL, flags); } string_list_clear(&names, 0); string_list_clear(&rollback, 0); - string_list_clear(&existing_packs, 0); + string_list_clear(&existing_nonkept_packs, 0); + string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); strbuf_release(&line); diff --git a/builtin/reset.c b/builtin/reset.c index 51c9e2f43f..7393595349 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -67,12 +67,18 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t case KEEP: case MERGE: opts.update = 1; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ break; case HARD: opts.update = 1; - /* fallthrough */ + opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + break; + case MIXED: + opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; + /* but opts.update=0, so working tree not updated */ + break; default: - opts.reset = 1; + BUG("invalid reset_type passed to reset_index"); } read_cache_unmerged(); diff --git a/builtin/rm.c b/builtin/rm.c index 3b44b807e5..3d0967cdc1 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only) static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; static int ignore_unmatch = 0, pathspec_file_nul; +static int include_sparse; static char *pathspec_from_file; static struct option builtin_rm_options[] = { @@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = { OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), + OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), @@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; - if (ce_skip_worktree(ce)) + + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) continue; if (!ce_path_match(&the_index, ce, &pathspec, seen)) continue; @@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen_any = 1; else if (ignore_unmatch) continue; - else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) + else if (!include_sparse && + matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) string_list_append(&only_match_skip_worktree, original); else die(_("pathspec '%s' did not match any files"), original); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 729dea1d25..8932142312 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -17,10 +17,10 @@ #include "protocol.h" static const char * const send_pack_usage[] = { - N_("git send-pack [--all | --mirror] [--dry-run] [--force] " - "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] " - "[<host>:]<directory> [<ref>...]\n" - " --all and explicit <ref> specification are mutually exclusive."), + N_("git send-pack [--mirror] [--dry-run] [--force]\n" + " [--receive-pack=<git-receive-pack>]\n" + " [--verbose] [--thin] [--atomic]\n" + " [<host>:]<directory> (--all | <ref>...)"), NULL, }; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index bea4bbf468..082449293b 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -11,9 +11,9 @@ static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" - " [--current] [--color[=<when>] | --no-color] [--sparse]\n" - " [--more=<n> | --list | --independent | --merge-base]\n" - " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), + " [--current] [--color[=<when>] | --no-color] [--sparse]\n" + " [--more=<n> | --list | --independent | --merge-base]\n" + " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"), NULL }; diff --git a/builtin/stash.c b/builtin/stash.c index 8f42360ca9..a0ccc8654d 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -85,7 +85,7 @@ static const char * const git_stash_push_usage[] = { static const char * const git_stash_save_usage[] = { N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [<message>]"), + " [-u|--include-untracked] [-a|--all] [<message>]"), NULL }; @@ -256,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) opts.src_index = &the_index; opts.dst_index = &the_index; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; opts.update = update; + if (update) + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = oneway_merge; if (unpack_trees(nr_trees, t, &opts)) @@ -313,6 +315,17 @@ static int reset_head(void) return run_command(&cp); } +static int is_path_a_directory(const char *path) +{ + /* + * This function differs from abspath.c:is_directory() in that + * here we use lstat() instead of stat(); we do not want to + * follow symbolic links here. + */ + struct stat st; + return (!lstat(path, &st) && S_ISDIR(st.st_mode)); +} + static void add_diff_to_buf(struct diff_queue_struct *q, struct diff_options *options, void *data) @@ -320,6 +333,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q, int i; for (i = 0; i < q->nr; i++) { + if (is_path_a_directory(q->queue[i]->one->path)) + continue; + strbuf_addstr(data, q->queue[i]->one->path); /* NUL-terminate: will be fed to update-index -z */ @@ -521,9 +537,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, } } - if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("could not restore untracked files from stash")); - init_merge_options(&o, the_repository); o.branch1 = "Updated upstream"; @@ -558,6 +571,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, unstage_changes_unless_new(&c_tree); } + if (info->has_u && restore_untracked(&info->u_tree)) + return error(_("could not restore untracked files from stash")); + if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1519,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } else { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + /* BUG: this nukes untracked files in the way */ strvec_pushl(&cp.args, "reset", "--hard", "-q", "--no-recurse-submodules", NULL); if (run_command(&cp)) { diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 88ce6be69c..6298cbdd4e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -307,7 +307,7 @@ struct module_list { const struct cache_entry **entries; int alloc, nr; }; -#define MODULE_LIST_INIT { NULL, 0, 0 } +#define MODULE_LIST_INIT { 0 } static int module_list_compute(int argc, const char **argv, const char *prefix, @@ -588,7 +588,7 @@ struct init_cb { const char *prefix; unsigned int flags; }; -#define INIT_CB_INIT { NULL, 0 } +#define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, unsigned int flags) @@ -717,7 +717,7 @@ struct status_cb { const char *prefix; unsigned int flags; }; -#define STATUS_CB_INIT { NULL, 0 } +#define STATUS_CB_INIT { 0 } static void print_status(unsigned int flags, char state, const char *path, const struct object_id *oid, const char *displaypath) @@ -911,13 +911,13 @@ struct module_cb { char status; const char *sm_path; }; -#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL } +#define MODULE_CB_INIT { 0 } struct module_cb_list { struct module_cb **entries; int alloc, nr; }; -#define MODULE_CB_LIST_INIT { NULL, 0, 0 } +#define MODULE_CB_LIST_INIT { 0 } struct summary_cb { int argc; @@ -928,7 +928,7 @@ struct summary_cb { unsigned int files: 1; int summary_limit; }; -#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 } +#define SUMMARY_CB_INIT { 0 } enum diff_cmd { DIFF_INDEX, @@ -1334,7 +1334,7 @@ struct sync_cb { const char *prefix; unsigned int flags; }; -#define SYNC_CB_INIT { NULL, 0 } +#define SYNC_CB_INIT { 0 } static void sync_submodule(const char *path, const char *prefix, unsigned int flags) @@ -1480,7 +1480,7 @@ struct deinit_cb { const char *prefix; unsigned int flags; }; -#define DEINIT_CB_INIT { NULL, 0 } +#define DEINIT_CB_INIT { 0 } static void deinit_submodule(const char *path, const char *prefix, unsigned int flags) @@ -1647,8 +1647,9 @@ struct submodule_alternate_setup { } error_mode; struct string_list *reference; }; -#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ - SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +#define SUBMODULE_ALTERNATE_SETUP_INIT { \ + .error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE, \ +} static const char alternate_error_advice[] = N_( "An alternate computed from a superproject's alternate is invalid.\n" @@ -3085,6 +3086,10 @@ static int add_submodule(const struct add_data *add_data) prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.dir = add_data->sm_path; + /* + * NOTE: we only get here if add_data->force is true, so + * passing --force to checkout is reasonable. + */ strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL); if (add_data->branch) { diff --git a/builtin/tag.c b/builtin/tag.c index 065b6bf093..6535ed27ee 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -23,10 +23,10 @@ static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n" - "\t\t<tagname> [<head>]"), + " <tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n" - "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), + " [--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), N_("git tag -v [--format=<format>] <tagname>..."), NULL }; diff --git a/builtin/worktree.c b/builtin/worktree.c index 0d0a80da61..d22ece93e1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -8,6 +8,7 @@ #include "branch.h" #include "refs.h" #include "run-command.h" +#include "hook.h" #include "sigchain.h" #include "submodule.h" #include "utf8.h" @@ -1202,49 +1202,6 @@ enum scld_error safe_create_leading_directories(char *path); enum scld_error safe_create_leading_directories_const(const char *path); enum scld_error safe_create_leading_directories_no_share(char *path); -/* - * Callback function for raceproof_create_file(). This function is - * expected to do something that makes dirname(path) permanent despite - * the fact that other processes might be cleaning up empty - * directories at the same time. Usually it will create a file named - * path, but alternatively it could create another file in that - * directory, or even chdir() into that directory. The function should - * return 0 if the action was completed successfully. On error, it - * should return a nonzero result and set errno. - * raceproof_create_file() treats two errno values specially: - * - * - ENOENT -- dirname(path) does not exist. In this case, - * raceproof_create_file() tries creating dirname(path) - * (and any parent directories, if necessary) and calls - * the function again. - * - * - EISDIR -- the file already exists and is a directory. In this - * case, raceproof_create_file() removes the directory if - * it is empty (and recursively any empty directories that - * it contains) and calls the function again. - * - * Any other errno causes raceproof_create_file() to fail with the - * callback's return value and errno. - * - * Obviously, this function should be OK with being called again if it - * fails with ENOENT or EISDIR. In other scenarios it will not be - * called again. - */ -typedef int create_file_fn(const char *path, void *cb); - -/* - * Create a file in dirname(path) by calling fn, creating leading - * directories if necessary. Retry a few times in case we are racing - * with another process that is trying to clean up the directory that - * contains path. See the documentation for create_file_fn for more - * details. - * - * Return the value and set the errno that resulted from the most - * recent call of fn. fn is always called at least once, and will be - * called more than once if it returns ENOENT or EISDIR. - */ -int raceproof_create_file(const char *path, create_file_fn fn, void *cb); - int mkdir_in_gitdir(const char *path); char *interpolate_path(const char *path, int real_home); /* NEEDSWORK: remove this synonym once in-flight topics have migrated */ @@ -1311,11 +1268,50 @@ char *xdg_cache_home(const char *filename); int git_open_cloexec(const char *name, int flags); #define git_open(name) git_open_cloexec(name, O_RDONLY) -int unpack_loose_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); -int parse_loose_header(const char *hdr, unsigned long *sizep); + +/** + * unpack_loose_header() initializes the data stream needed to unpack + * a loose object header. + * + * Returns: + * + * - ULHR_OK on success + * - ULHR_BAD on error + * - ULHR_TOO_LONG if the header was too long + * + * It will only parse up to MAX_HEADER_LEN bytes unless an optional + * "hdrbuf" argument is non-NULL. This is intended for use with + * OBJECT_INFO_ALLOW_UNKNOWN_TYPE to extract the bad type for (error) + * reporting. The full header will be extracted to "hdrbuf" for use + * with parse_loose_header(), ULHR_TOO_LONG will still be returned + * from this function to indicate that the header was too long. + */ +enum unpack_loose_header_result { + ULHR_OK, + ULHR_BAD, + ULHR_TOO_LONG, +}; +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, + unsigned char *map, + unsigned long mapsize, + void *buffer, + unsigned long bufsiz, + struct strbuf *hdrbuf); + +/** + * parse_loose_header() parses the starting "<type> <len>\0" of an + * object. If it doesn't follow that format -1 is returned. To check + * the validity of the <type> populate the "typep" in the "struct + * object_info". It will be OBJ_BAD if the object type is unknown. The + * parsed <len> can be retrieved via "oi->sizep", and from there + * passed to unpack_loose_rest(). + */ +struct object_info; +int parse_loose_header(const char *hdr, struct object_info *oi); int check_object_signature(struct repository *r, const struct object_id *oid, - void *buf, unsigned long size, const char *type); + void *buf, unsigned long size, const char *type, + struct object_id *real_oidp); int finalize_object_file(const char *tmpfile, const char *filename); @@ -1660,7 +1656,9 @@ struct cache_def { int track_flags; int prefix_len_stat_func; }; -#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 } +#define CACHE_DEF_INIT { \ + .path = STRBUF_INIT, \ +} static inline void cache_def_clear(struct cache_def *cache) { strbuf_release(&cache->path); @@ -1717,13 +1715,6 @@ int update_server_info(int); const char *get_log_output_encoding(void); const char *get_commit_output_encoding(void); -/* - * This is a hack for test programs like test-dump-untracked-cache to - * ensure that they do not modify the untracked cache when reading it. - * Do not use it otherwise! - */ -extern int ignore_untracked_cache_config; - int committer_ident_sufficiently_given(void); int author_ident_sufficiently_given(void); @@ -37,11 +37,12 @@ enum cb_next { CB_BREAK = 1 }; -#define CBTREE_INIT { .root = NULL } +#define CBTREE_INIT { 0 } static inline void cb_init(struct cb_tree *t) { - t->root = NULL; + struct cb_tree blank = CBTREE_INIT; + memcpy(t, &blank, sizeof(*t)); } struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen); diff --git a/checkout.c b/checkout.c index 6586e30ca5..2e39dae684 100644 --- a/checkout.c +++ b/checkout.c @@ -14,7 +14,7 @@ struct tracking_name_data { struct object_id *default_dst_oid; }; -#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL } +#define TRACKING_NAME_DATA_INIT { 0 } static int check_tracking_name(struct remote *remote, void *cb_data) { diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 5772081b6e..1d0e48f451 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -12,7 +12,7 @@ UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl" case "$jobname" in -linux-clang|linux-gcc) +linux-clang|linux-gcc|linux-leaks) sudo apt-add-repository -y "ppa:ubuntu-toolchain-r/test" sudo apt-get -q update sudo apt-get -q -y install language-pack-is libsvn-perl apache2 \ @@ -183,7 +183,7 @@ export GIT_TEST_CLONE_2GB=true export SKIP_DASHED_BUILT_INS=YesPlease case "$jobname" in -linux-clang|linux-gcc) +linux-clang|linux-gcc|linux-leaks) if [ "$jobname" = linux-gcc ] then export CC=gcc-8 @@ -233,4 +233,11 @@ linux-musl) ;; esac +case "$jobname" in +linux-leaks) + export SANITIZE=leak + export GIT_TEST_PASSING_SANITIZE_LEAK=true + ;; +esac + MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}" diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c index 1927e6ef4b..4e28857a0a 100644 --- a/compat/simple-ipc/ipc-unix-socket.c +++ b/compat/simple-ipc/ipc-unix-socket.c @@ -168,7 +168,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection) int ipc_client_send_command_to_connection( struct ipc_client_connection *connection, - const char *message, struct strbuf *answer) + const char *message, size_t message_len, + struct strbuf *answer) { int ret = 0; @@ -176,7 +177,7 @@ int ipc_client_send_command_to_connection( trace2_region_enter("ipc-client", "send-command", NULL); - if (write_packetized_from_buf_no_flush(message, strlen(message), + if (write_packetized_from_buf_no_flush(message, message_len, connection->fd) < 0 || packet_flush_gently(connection->fd) < 0) { ret = error(_("could not send IPC command")); @@ -197,7 +198,8 @@ done: int ipc_client_send_command(const char *path, const struct ipc_client_connect_options *options, - const char *message, struct strbuf *answer) + const char *message, size_t message_len, + struct strbuf *answer) { int ret = -1; enum ipc_active_state state; @@ -208,7 +210,9 @@ int ipc_client_send_command(const char *path, if (state != IPC_STATE__LISTENING) return ret; - ret = ipc_client_send_command_to_connection(connection, message, answer); + ret = ipc_client_send_command_to_connection(connection, + message, message_len, + answer); ipc_client_close_connection(connection); @@ -503,7 +507,7 @@ static int worker_thread__do_io( if (ret >= 0) { ret = worker_thread_data->server_data->application_cb( worker_thread_data->server_data->application_data, - buf.buf, do_io_reply_callback, &reply_data); + buf.buf, buf.len, do_io_reply_callback, &reply_data); packet_flush_gently(reply_data.fd); } diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c index 8dc7bda087..20ea7b65e0 100644 --- a/compat/simple-ipc/ipc-win32.c +++ b/compat/simple-ipc/ipc-win32.c @@ -3,6 +3,8 @@ #include "strbuf.h" #include "pkt-line.h" #include "thread-utils.h" +#include "accctrl.h" +#include "aclapi.h" #ifndef SUPPORTS_SIMPLE_IPC /* @@ -49,6 +51,9 @@ static enum ipc_active_state get_active_state(wchar_t *pipe_path) if (GetLastError() == ERROR_FILE_NOT_FOUND) return IPC_STATE__PATH_NOT_FOUND; + trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle", + (intmax_t)GetLastError()); + return IPC_STATE__OTHER_ERROR; } @@ -109,9 +114,15 @@ static enum ipc_active_state connect_to_server( t_start_ms = (DWORD)(getnanotime() / 1000000); if (!WaitNamedPipeW(wpath, timeout_ms)) { - if (GetLastError() == ERROR_SEM_TIMEOUT) + DWORD gleWait = GetLastError(); + + if (gleWait == ERROR_SEM_TIMEOUT) return IPC_STATE__NOT_LISTENING; + trace2_data_intmax("ipc-debug", NULL, + "connect/waitpipe/gle", + (intmax_t)gleWait); + return IPC_STATE__OTHER_ERROR; } @@ -133,17 +144,31 @@ static enum ipc_active_state connect_to_server( break; /* try again */ default: + trace2_data_intmax("ipc-debug", NULL, + "connect/createfile/gle", + (intmax_t)gle); + return IPC_STATE__OTHER_ERROR; } } if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) { + gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, + "connect/setpipestate/gle", + (intmax_t)gle); + CloseHandle(hPipe); return IPC_STATE__OTHER_ERROR; } *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY); if (*pfd < 0) { + gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, + "connect/openosfhandle/gle", + (intmax_t)gle); + CloseHandle(hPipe); return IPC_STATE__OTHER_ERROR; } @@ -208,7 +233,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection) int ipc_client_send_command_to_connection( struct ipc_client_connection *connection, - const char *message, struct strbuf *answer) + const char *message, size_t message_len, + struct strbuf *answer) { int ret = 0; @@ -216,7 +242,7 @@ int ipc_client_send_command_to_connection( trace2_region_enter("ipc-client", "send-command", NULL); - if (write_packetized_from_buf_no_flush(message, strlen(message), + if (write_packetized_from_buf_no_flush(message, message_len, connection->fd) < 0 || packet_flush_gently(connection->fd) < 0) { ret = error(_("could not send IPC command")); @@ -239,7 +265,8 @@ done: int ipc_client_send_command(const char *path, const struct ipc_client_connect_options *options, - const char *message, struct strbuf *response) + const char *message, size_t message_len, + struct strbuf *response) { int ret = -1; enum ipc_active_state state; @@ -250,7 +277,9 @@ int ipc_client_send_command(const char *path, if (state != IPC_STATE__LISTENING) return ret; - ret = ipc_client_send_command_to_connection(connection, message, response); + ret = ipc_client_send_command_to_connection(connection, + message, message_len, + response); ipc_client_close_connection(connection); @@ -458,7 +487,7 @@ static int do_io(struct ipc_server_thread_data *server_thread_data) if (ret >= 0) { ret = server_thread_data->server_data->application_cb( server_thread_data->server_data->application_data, - buf.buf, do_io_reply_callback, &reply_data); + buf.buf, buf.len, do_io_reply_callback, &reply_data); packet_flush_gently(reply_data.fd); @@ -565,11 +594,132 @@ finished: return NULL; } +/* + * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it + * to apply an ACL when we create the initial instance of the Named + * Pipe. The construction is somewhat involved and consists of + * several sequential steps and intermediate objects. + * + * We use this structure to hold these intermediate pointers so that + * we can free them as a group. (It is unclear from the docs whether + * some of these intermediate pointers can be freed before we are + * finished using the "lpSA" member.) + */ +struct my_sa_data +{ + PSID pEveryoneSID; + PACL pACL; + PSECURITY_DESCRIPTOR pSD; + LPSECURITY_ATTRIBUTES lpSA; +}; + +static void init_sa(struct my_sa_data *d) +{ + memset(d, 0, sizeof(*d)); +} + +static void release_sa(struct my_sa_data *d) +{ + if (d->pEveryoneSID) + FreeSid(d->pEveryoneSID); + if (d->pACL) + LocalFree(d->pACL); + if (d->pSD) + LocalFree(d->pSD); + if (d->lpSA) + LocalFree(d->lpSA); + + memset(d, 0, sizeof(*d)); +} + +/* + * Create SECURITY_ATTRIBUTES to apply to the initial named pipe. The + * creator of the first server instance gets to set the ACLs on it. + * + * We allow the well-known group `EVERYONE` to have read+write access + * to the named pipe so that clients can send queries to the daemon + * and receive the response. + * + * Normally, this is not necessary since the daemon is usually + * automatically started by a foreground command like `git status`, + * but in those cases where an elevated Git command started the daemon + * (such that the daemon itself runs with elevation), we need to add + * the ACL so that non-elevated commands can write to it. + * + * The following document was helpful: + * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c-- + * + * Returns d->lpSA set to a SA or NULL. + */ +static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d) +{ + SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY; +#define NR_EA (1) + EXPLICIT_ACCESS ea[NR_EA]; + DWORD dwResult; + + if (!AllocateAndInitializeSid(&sid_auth_world, 1, + SECURITY_WORLD_RID, 0,0,0,0,0,0,0, + &d->pEveryoneSID)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle", + (intmax_t)gle); + goto fail; + } + + memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS)); + + ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID; + + dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL); + if (dwResult != ERROR_SUCCESS) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle", + (intmax_t)gle); + trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw", + (intmax_t)dwResult); + goto fail; + } + + d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc( + LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle); + goto fail; + } + + if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) { + DWORD gle = GetLastError(); + trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle); + goto fail; + } + + d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES)); + d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES); + d->lpSA->lpSecurityDescriptor = d->pSD; + d->lpSA->bInheritHandle = FALSE; + + return d->lpSA; + +fail: + release_sa(d); + return NULL; +} + static HANDLE create_new_pipe(wchar_t *wpath, int is_first) { HANDLE hPipe; DWORD dwOpenMode, dwPipeMode; - LPSECURITY_ATTRIBUTES lpsa = NULL; + struct my_sa_data my_sa_data; + + init_sa(&my_sa_data); dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED; @@ -585,20 +735,15 @@ static HANDLE create_new_pipe(wchar_t *wpath, int is_first) * set the ACL / Security Attributes on the named * pipe; subsequent instances inherit and cannot * change them. - * - * TODO Should we allow the application layer to - * specify security attributes, such as `LocalService` - * or `LocalSystem`, when we create the named pipe? - * This question is probably not important when the - * daemon is started by a foreground user process and - * only needs to talk to the current user, but may be - * if the daemon is run via the Control Panel as a - * System Service. */ + get_sa(&my_sa_data); } hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, - PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, + my_sa_data.lpSA); + + release_sa(&my_sa_data); return hPipe; } diff --git a/compat/terminal.c b/compat/terminal.c index 43b73ddc75..5b903e7c7e 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -8,8 +8,6 @@ #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE) -static void restore_term(void); - static void restore_term_on_signal(int sig) { restore_term(); @@ -25,7 +23,7 @@ static void restore_term_on_signal(int sig) static int term_fd = -1; static struct termios old_term; -static void restore_term(void) +void restore_term(void) { if (term_fd < 0) return; @@ -35,15 +33,22 @@ static void restore_term(void) term_fd = -1; } +int save_term(int full_duplex) +{ + if (term_fd < 0) + term_fd = open("/dev/tty", O_RDWR); + + return (term_fd < 0) ? -1 : tcgetattr(term_fd, &old_term); +} + static int disable_bits(tcflag_t bits) { struct termios t; - term_fd = open("/dev/tty", O_RDWR); - if (tcgetattr(term_fd, &t) < 0) + if (save_term(0) < 0) goto error; - old_term = t; + t = old_term; sigchain_push_common(restore_term_on_signal); t.c_lflag &= ~bits; @@ -75,9 +80,10 @@ static int enable_non_canonical(void) static int use_stty = 1; static struct string_list stty_restore = STRING_LIST_INIT_DUP; static HANDLE hconin = INVALID_HANDLE_VALUE; -static DWORD cmode; +static HANDLE hconout = INVALID_HANDLE_VALUE; +static DWORD cmode_in, cmode_out; -static void restore_term(void) +void restore_term(void) { if (use_stty) { int i; @@ -97,9 +103,42 @@ static void restore_term(void) if (hconin == INVALID_HANDLE_VALUE) return; - SetConsoleMode(hconin, cmode); + SetConsoleMode(hconin, cmode_in); + CloseHandle(hconin); + if (cmode_out) { + assert(hconout != INVALID_HANDLE_VALUE); + SetConsoleMode(hconout, cmode_out); + CloseHandle(hconout); + } + + hconin = hconout = INVALID_HANDLE_VALUE; +} + +int save_term(int full_duplex) +{ + hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hconin == INVALID_HANDLE_VALUE) + return -1; + + if (full_duplex) { + hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hconout == INVALID_HANDLE_VALUE) + goto error; + + GetConsoleMode(hconout, &cmode_out); + } + + GetConsoleMode(hconin, &cmode_in); + use_stty = 0; + return 0; +error: CloseHandle(hconin); hconin = INVALID_HANDLE_VALUE; + return -1; } static int disable_bits(DWORD bits) @@ -135,15 +174,11 @@ static int disable_bits(DWORD bits) use_stty = 0; } - hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (hconin == INVALID_HANDLE_VALUE) + if (save_term(0) < 0) return -1; - GetConsoleMode(hconin, &cmode); sigchain_push_common(restore_term_on_signal); - if (!SetConsoleMode(hconin, cmode & ~bits)) { + if (!SetConsoleMode(hconin, cmode_in & ~bits)) { CloseHandle(hconin); hconin = INVALID_HANDLE_VALUE; return -1; @@ -361,6 +396,16 @@ int read_key_without_echo(struct strbuf *buf) #else +int save_term(int full_duplex) +{ + /* full_duplex == 1, but no support available */ + return -full_duplex; +} + +void restore_term(void) +{ +} + char *git_terminal_prompt(const char *prompt, int echo) { return getpass(prompt); diff --git a/compat/terminal.h b/compat/terminal.h index a9d52b8464..e1770c575b 100644 --- a/compat/terminal.h +++ b/compat/terminal.h @@ -1,6 +1,9 @@ #ifndef COMPAT_TERMINAL_H #define COMPAT_TERMINAL_H +int save_term(int full_duplex); +void restore_term(void); + char *git_terminal_prompt(const char *prompt, int echo); /* Read a single keystroke, without echoing it to the terminal */ diff --git a/compat/vcbuild/README b/compat/vcbuild/README index 51fb083dbb..29ec1d0f10 100644 --- a/compat/vcbuild/README +++ b/compat/vcbuild/README @@ -92,7 +92,7 @@ The Steps of Build Git with VS2008 the git operations. 3. Inside Git's directory run the command: - make command-list.h config-list.h + make generated-hdrs to generate the header file needed to compile git. 4. Then either build Git with the GNU Make Makefile in the Git projects diff --git a/compat/win32/lazyload.h b/compat/win32/lazyload.h index d2056cdadf..2b3637135f 100644 --- a/compat/win32/lazyload.h +++ b/compat/win32/lazyload.h @@ -15,10 +15,12 @@ * source, target); */ +typedef void (*FARVOIDPROC)(void); + struct proc_addr { const char *const dll; const char *const function; - FARPROC pfunction; + FARVOIDPROC pfunction; unsigned initialized : 1; }; @@ -26,7 +28,8 @@ struct proc_addr { #define DECLARE_PROC_ADDR(dll, rettype, function, ...) \ static struct proc_addr proc_addr_##function = \ { #dll, #function, NULL, 0 }; \ - static rettype (WINAPI *function)(__VA_ARGS__) + typedef rettype (WINAPI *proc_type_##function)(__VA_ARGS__); \ + static proc_type_##function function /* * Loads a function from a DLL (once-only). @@ -35,9 +38,9 @@ struct proc_addr { * This function is not thread-safe. */ #define INIT_PROC_ADDR(function) \ - (function = get_proc_addr(&proc_addr_##function)) + (function = (proc_type_##function)get_proc_addr(&proc_addr_##function)) -static inline FARPROC get_proc_addr(struct proc_addr *proc) +static inline FARVOIDPROC get_proc_addr(struct proc_addr *proc) { /* only do this once */ if (!proc->initialized) { @@ -46,7 +49,8 @@ static inline FARPROC get_proc_addr(struct proc_addr *proc) hnd = LoadLibraryExA(proc->dll, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (hnd) - proc->pfunction = GetProcAddress(hnd, proc->function); + proc->pfunction = (FARVOIDPROC)GetProcAddress(hnd, + proc->function); } /* set ENOSYS if DLL or function was not found */ if (!proc->pfunction) @@ -425,7 +425,7 @@ static inline int iskeychar(int c) * baselen - pointer to size_t which will hold the length of the * section + subsection part, can be NULL */ -static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet) +int git_config_parse_key(const char *key, char **store_key, size_t *baselen_) { size_t i, baselen; int dot; @@ -437,14 +437,12 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas */ if (last_dot == NULL || last_dot == key) { - if (!quiet) - error(_("key does not contain a section: %s"), key); + error(_("key does not contain a section: %s"), key); return -CONFIG_NO_SECTION_OR_NAME; } if (!last_dot[1]) { - if (!quiet) - error(_("key does not contain variable name: %s"), key); + error(_("key does not contain variable name: %s"), key); return -CONFIG_NO_SECTION_OR_NAME; } @@ -455,8 +453,7 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas /* * Validate the key and while at it, lower case it for matching. */ - if (store_key) - *store_key = xmallocz(strlen(key)); + *store_key = xmallocz(strlen(key)); dot = 0; for (i = 0; key[i]; i++) { @@ -467,39 +464,24 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas if (!dot || i > baselen) { if (!iskeychar(c) || (i == baselen + 1 && !isalpha(c))) { - if (!quiet) - error(_("invalid key: %s"), key); + error(_("invalid key: %s"), key); goto out_free_ret_1; } c = tolower(c); } else if (c == '\n') { - if (!quiet) - error(_("invalid key (newline): %s"), key); + error(_("invalid key (newline): %s"), key); goto out_free_ret_1; } - if (store_key) - (*store_key)[i] = c; + (*store_key)[i] = c; } return 0; out_free_ret_1: - if (store_key) { - FREE_AND_NULL(*store_key); - } + FREE_AND_NULL(*store_key); return -CONFIG_INVALID_KEY; } -int git_config_parse_key(const char *key, char **store_key, size_t *baselen) -{ - return git_config_parse_key_1(key, store_key, baselen, 0); -} - -int git_config_key_is_valid(const char *key) -{ - return !git_config_parse_key_1(key, NULL, NULL, 1); -} - static int config_parse_pair(const char *key, const char *value, config_fn_t fn, void *data) { @@ -259,7 +259,6 @@ int git_config_set_gently(const char *, const char *); void git_config_set(const char *, const char *); int git_config_parse_key(const char *, char **, size_t *); -int git_config_key_is_valid(const char *key); /* * The following macros specify flag bits that alter the behavior @@ -609,7 +608,6 @@ int git_config_get_maybe_bool(const char *key, int *dest); int git_config_get_pathname(const char *key, const char **dest); int git_config_get_index_threads(int *dest); -int git_config_get_untracked_cache(void); int git_config_get_split_index(void); int git_config_get_max_percent_split_change(void); int git_config_get_fsmonitor(void); diff --git a/config.mak.dev b/config.mak.dev index c080ac0231..7673fed114 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -6,13 +6,17 @@ ifeq ($(filter no-error,$(DEVOPTS)),) DEVELOPER_CFLAGS += -Werror SPARSE_FLAGS += -Wsparse-error endif + DEVELOPER_CFLAGS += -Wall ifeq ($(filter no-pedantic,$(DEVOPTS)),) DEVELOPER_CFLAGS += -pedantic +ifneq (($or $(filter gcc5,$(COMPILER_FEATURES)),$(filter clang4,$(COMPILER_FEATURES))),) DEVELOPER_CFLAGS += -Wpedantic -ifneq ($(filter gcc5,$(COMPILER_FEATURES)),) +ifneq ($(filter gcc10,$(COMPILER_FEATURES)),) +ifeq ($(uname_S),MINGW) DEVELOPER_CFLAGS += -Wno-pedantic-ms-format -DEVELOPER_CFLAGS += -Wno-incompatible-pointer-types +endif +endif endif endif DEVELOPER_CFLAGS += -Wdeclaration-after-statement diff --git a/config.mak.uname b/config.mak.uname index 76516aaa9a..3236a4918a 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -11,6 +11,10 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') +ifneq ($(findstring MINGW,$(uname_S)),) + uname_S := MINGW +endif + ifdef MSVC # avoid the MingW and Cygwin configuration sections uname_S := Windows @@ -588,7 +592,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL) SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin SHELL_PATH = /usr/coreutils/bin/bash endif -ifneq (,$(findstring MINGW,$(uname_S))) +ifeq ($(uname_S),MINGW) pathsep = ; HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease @@ -735,9 +739,9 @@ vcxproj: echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets - # Add command-list.h and config-list.h - $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 config-list.h command-list.h - git add -f config-list.h command-list.h + # Add generated headers + $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(GENERATED_H) + git add -f $(GENERATED_H) # Add scripts rm -f perl/perl.mak @@ -557,6 +557,8 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i if (!*value || isspace(*value)) { if (lenp) *lenp = 0; + if (offset) + *offset = found + len - feature_list; return value; } /* feature with a value (e.g., "agent=git/1.2.3") */ diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 171b4124af..fd1399c440 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -624,6 +624,13 @@ if(NOT EXISTS ${CMAKE_BINARY_DIR}/config-list.h) OUTPUT_FILE ${CMAKE_BINARY_DIR}/config-list.h) endif() +if(NOT EXISTS ${CMAKE_BINARY_DIR}/hook-list.h) + message("Generating hook-list.h") + execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-hooklist.sh + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_FILE ${CMAKE_BINARY_DIR}/hook-list.h) +endif() + include_directories(${CMAKE_BINARY_DIR}) #build diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8108eda1e8..eb5fd4783d 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2503,7 +2503,14 @@ __git_config_vars= __git_compute_config_vars () { test -n "$__git_config_vars" || - __git_config_vars="$(git help --config-for-completion | sort -u)" + __git_config_vars="$(git help --config-for-completion)" +} + +__git_config_sections= +__git_compute_config_sections () +{ + test -n "$__git_config_sections" || + __git_config_sections="$(git help --config-sections-for-completion)" } # Completes possible values of various configuration variables. @@ -2543,7 +2550,7 @@ __git_complete_config_variable_value () return ;; branch.*.rebase) - __gitcomp "false true merges preserve interactive" "" "$cur_" + __gitcomp "false true merges interactive" "" "$cur_" return ;; remote.pushdefault) @@ -2717,16 +2724,8 @@ __git_complete_config_variable_name () __gitcomp "$__git_config_vars" "" "$cur_" "$sfx" ;; *) - __git_compute_config_vars - __gitcomp "$(echo "$__git_config_vars" | - awk -F . '{ - sections[$1] = 1 - } - END { - for (s in sections) - print s "." - } - ')" "" "$cur_" + __git_compute_config_sections + __gitcomp "$__git_config_sections" "" "$cur_" "." ;; esac } diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c index d389bfadce..5927e27ae6 100644 --- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c +++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c @@ -138,7 +138,7 @@ struct credential { char *password; }; -#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL } +#define CREDENTIAL_INIT { 0 } typedef int (*credential_op_cb)(struct credential *); diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c index e6598b6383..2c5d76d789 100644 --- a/contrib/credential/libsecret/git-credential-libsecret.c +++ b/contrib/credential/libsecret/git-credential-libsecret.c @@ -41,7 +41,7 @@ struct credential { char *password; }; -#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL } +#define CREDENTIAL_INIT { 0 } typedef int (*credential_op_cb)(struct credential *); diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh index eeee45dd34..75125d6ae0 100755 --- a/contrib/rerere-train.sh +++ b/contrib/rerere-train.sh @@ -91,7 +91,7 @@ do git checkout -q $commit -- . git rerere fi - git reset -q --hard + git reset -q --hard # Might nuke untracked files... done if test -z "$branch" diff --git a/credential.c b/credential.c index 000ac7a8d4..e7240f3f63 100644 --- a/credential.c +++ b/credential.c @@ -105,7 +105,7 @@ static int match_partial_url(const char *url, void *cb) static void credential_apply_config(struct credential *c) { char *normalized_url; - struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct urlmatch_config config = URLMATCH_CONFIG_INIT; struct strbuf url = STRBUF_INIT; if (!c->host) @@ -63,6 +63,12 @@ struct hostinfo { unsigned int hostname_lookup_done:1; unsigned int saw_extended_args:1; }; +#define HOSTINFO_INIT { \ + .hostname = STRBUF_INIT, \ + .canon_hostname = STRBUF_INIT, \ + .ip_address = STRBUF_INIT, \ + .tcp_port = STRBUF_INIT, \ +} static void lookup_hostname(struct hostinfo *hi); @@ -727,15 +733,6 @@ static void lookup_hostname(struct hostinfo *hi) } } -static void hostinfo_init(struct hostinfo *hi) -{ - memset(hi, 0, sizeof(*hi)); - strbuf_init(&hi->hostname, 0); - strbuf_init(&hi->canon_hostname, 0); - strbuf_init(&hi->ip_address, 0); - strbuf_init(&hi->tcp_port, 0); -} - static void hostinfo_clear(struct hostinfo *hi) { strbuf_release(&hi->hostname); @@ -760,11 +757,9 @@ static int execute(void) char *line = packet_buffer; int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); - struct hostinfo hi; + struct hostinfo hi = HOSTINFO_INIT; struct strvec env = STRVEC_INIT; - hostinfo_init(&hi); - if (addr) loginfo("Connection from %s:%s", addr, port); @@ -775,13 +775,13 @@ struct emitted_diff_symbol { int indent_width; /* The visual width of the indentation */ enum diff_symbol s; }; -#define EMITTED_DIFF_SYMBOL_INIT {NULL} +#define EMITTED_DIFF_SYMBOL_INIT { 0 } struct emitted_diff_symbols { struct emitted_diff_symbol *buf; int nr, alloc; }; -#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0} +#define EMITTED_DIFF_SYMBOLS_INIT { 0 } static void append_emitted_diff_symbol(struct diff_options *o, struct emitted_diff_symbol *e) @@ -1294,7 +1294,7 @@ int match_pathname(const char *pathname, int pathlen, * then our prefix match is all we need; we * do not need to call fnmatch at all. */ - if (!patternlen && !namelen) + if (!patternlen && (!namelen || (flags & PATTERN_FLAG_MUSTBEDIR))) return 1; } @@ -1303,6 +1303,44 @@ int match_pathname(const char *pathname, int pathlen, WM_PATHNAME) == 0; } +static int path_matches_dir_pattern(const char *pathname, + int pathlen, + struct strbuf **path_parent, + int *dtype, + struct path_pattern *pattern, + struct index_state *istate) +{ + if (!*path_parent) { + char *slash; + CALLOC_ARRAY(*path_parent, 1); + strbuf_add(*path_parent, pathname, pathlen); + slash = find_last_dir_sep((*path_parent)->buf); + + if (slash) + strbuf_setlen(*path_parent, slash - (*path_parent)->buf); + else + strbuf_setlen(*path_parent, 0); + } + + /* + * If the parent directory matches the pattern, then we do not + * need to check for dtype. + */ + if ((*path_parent)->len && + match_pathname((*path_parent)->buf, (*path_parent)->len, + pattern->base, + pattern->baselen ? pattern->baselen - 1 : 0, + pattern->pattern, pattern->nowildcardlen, + pattern->patternlen, pattern->flags)) + return 1; + + *dtype = resolve_dtype(*dtype, istate, pathname, pathlen); + if (*dtype != DT_DIR) + return 0; + + return 1; +} + /* * Scan the given exclude list in reverse to see whether pathname * should be ignored. The first match (i.e. the last on the list), if @@ -1318,6 +1356,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname { struct path_pattern *res = NULL; /* undecided */ int i; + struct strbuf *path_parent = NULL; if (!pl->nr) return NULL; /* undefined */ @@ -1327,11 +1366,10 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname const char *exclude = pattern->pattern; int prefix = pattern->nowildcardlen; - if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) { - *dtype = resolve_dtype(*dtype, istate, pathname, pathlen); - if (*dtype != DT_DIR) - continue; - } + if (pattern->flags & PATTERN_FLAG_MUSTBEDIR && + !path_matches_dir_pattern(pathname, pathlen, &path_parent, + dtype, pattern, istate)) + continue; if (pattern->flags & PATTERN_FLAG_NODIR) { if (match_basename(basename, @@ -1355,6 +1393,12 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname break; } } + + if (path_parent) { + strbuf_release(path_parent); + free(path_parent); + } + return res; } @@ -3,6 +3,7 @@ #include "strbuf.h" #include "run-command.h" #include "sigchain.h" +#include "compat/terminal.h" #ifndef DEFAULT_EDITOR #define DEFAULT_EDITOR "vi" @@ -50,6 +51,8 @@ const char *git_sequence_editor(void) static int launch_specified_editor(const char *editor, const char *path, struct strbuf *buffer, const char *const *env) { + int term_fail; + if (!editor) return error("Terminal is dumb, but EDITOR unset"); @@ -83,7 +86,10 @@ static int launch_specified_editor(const char *editor, const char *path, p.env = env; p.use_shell = 1; p.trace2_child_class = "editor"; + term_fail = save_term(1); if (start_command(&p) < 0) { + if (!term_fail) + restore_term(); strbuf_release(&realpath); return error("unable to start editor '%s'", editor); } @@ -91,6 +97,8 @@ static int launch_specified_editor(const char *editor, const char *path, sigchain_push(SIGINT, SIG_IGN); sigchain_push(SIGQUIT, SIG_IGN); ret = finish_command(&p); + if (!term_fail) + restore_term(); strbuf_release(&realpath); sig = ret - 128; sigchain_pop(SIGINT); @@ -16,7 +16,7 @@ struct checkout { clone:1, refresh_cache:1; }; -#define CHECKOUT_INIT { NULL, "" } +#define CHECKOUT_INIT { .base_dir = "" } #define TEMPORARY_FILENAME_LENGTH 25 /* diff --git a/environment.c b/environment.c index 7923ab21dd..9da7f3c1a1 100644 --- a/environment.c +++ b/environment.c @@ -94,13 +94,6 @@ int auto_comment_line_char; /* Parallel index stat data preload? */ int core_preload_index = 1; -/* - * This is a hack for test programs like test-dump-untracked-cache to - * ensure that they do not modify the untracked cache when reading it. - * Do not use it otherwise! - */ -int ignore_untracked_cache_config; - /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; @@ -328,8 +321,7 @@ char *get_graft_file(struct repository *r) static void set_git_dir_1(const char *path) { - if (setenv(GIT_DIR_ENVIRONMENT, path, 1)) - die(_("could not set GIT_DIR to '%s'"), path); + xsetenv(GIT_DIR_ENVIRONMENT, path, 1); setup_git_env(path); } diff --git a/fetch-negotiator.c b/fetch-negotiator.c index 57ed5784e1..273390229f 100644 --- a/fetch-negotiator.c +++ b/fetch-negotiator.c @@ -19,7 +19,6 @@ void fetch_negotiator_init(struct repository *r, return; case FETCH_NEGOTIATION_DEFAULT: - default: default_negotiator_init(negotiator); return; } diff --git a/generate-hooklist.sh b/generate-hooklist.sh new file mode 100755 index 0000000000..2f9f54eb54 --- /dev/null +++ b/generate-hooklist.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Usage: ./generate-hooklist.sh >hook-list.h + +cat <<EOF +/* Automatically generated by generate-hooklist.sh */ + +static const char *hook_name_list[] = { +EOF + +sed -n \ + -e '/^~~~~*$/ {x; s/^.*$/ "&",/; p;}' \ + -e 'x' \ + <Documentation/githooks.txt | + LC_ALL=C sort + +cat <<EOF + NULL, +}; +EOF diff --git a/git-compat-util.h b/git-compat-util.h index 7c99eef661..141bb86351 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -878,6 +878,8 @@ void *xmemdupz(const void *data, size_t len); char *xstrndup(const char *str, size_t len); void *xrealloc(void *ptr, size_t size); void *xcalloc(size_t nmemb, size_t size); +void xsetenv(const char *name, const char *value, int overwrite); +void xunsetenv(const char *name); void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); const char *mmap_os_err(void); void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset); diff --git a/git-curl-compat.h b/git-curl-compat.h index a308bdb3b9..56a83b6bbd 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h @@ -67,10 +67,11 @@ /** * CURLOPT_PINNEDPUBLICKEY was added in 7.39.0, released in November - * 2014. + * 2014. CURLE_SSL_PINNEDPUBKEYNOTMATCH was added in that same version. */ #if LIBCURL_VERSION_NUM >= 0x072c00 #define GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY 1 +#define GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH 1 #endif /** diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ed035f32c2..64319bed43 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -222,10 +222,11 @@ if ($state->{method} eq 'pserver') { open my $passwd, "<", $authdb or die $!; while (<$passwd>) { if (m{^\Q$user\E:(.*)}) { - if (crypt($user, descramble($password)) eq $1) { + my $hash = crypt(descramble($password), $1); + if (defined $hash and $hash eq $1) { $auth_ok = 1; } - }; + } } close $passwd; diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh deleted file mode 100644 index b9c71d2a71..0000000000 --- a/git-rebase--preserve-merges.sh +++ /dev/null @@ -1,1057 +0,0 @@ -# This shell script fragment is sourced by git-rebase to implement its -# preserve-merges mode. -# -# Copyright (c) 2006 Johannes E. Schindelin -# -# The file containing rebase commands, comments, and empty lines. -# This file is created by "git rebase -i" then edited by the user. As -# the lines are processed, they are removed from the front of this -# file and written to the tail of $done. -todo="$state_dir"/git-rebase-todo - -# The rebase command lines that have already been processed. A line -# is moved here when it is first handled, before any associated user -# actions. -done="$state_dir"/done - -# The commit message that is planned to be used for any changes that -# need to be committed following a user interaction. -msg="$state_dir"/message - -# The file into which is accumulated the suggested commit message for -# squash/fixup commands. When the first of a series of squash/fixups -# is seen, the file is created and the commit message from the -# previous commit and from the first squash/fixup commit are written -# to it. The commit message for each subsequent squash/fixup commit -# is appended to the file as it is processed. -# -# The first line of the file is of the form -# # This is a combination of $count commits. -# where $count is the number of commits whose messages have been -# written to the file so far (including the initial "pick" commit). -# Each time that a commit message is processed, this line is read and -# updated. It is deleted just before the combined commit is made. -squash_msg="$state_dir"/message-squash - -# If the current series of squash/fixups has not yet included a squash -# command, then this file exists and holds the commit message of the -# original "pick" commit. (If the series ends without a "squash" -# command, then this can be used as the commit message of the combined -# commit without opening the editor.) -fixup_msg="$state_dir"/message-fixup - -# $rewritten is the name of a directory containing files for each -# commit that is reachable by at least one merge base of $head and -# $upstream. They are not necessarily rewritten, but their children -# might be. This ensures that commits on merged, but otherwise -# unrelated side branches are left alone. (Think "X" in the man page's -# example.) -rewritten="$state_dir"/rewritten - -dropped="$state_dir"/dropped - -end="$state_dir"/end -msgnum="$state_dir"/msgnum - -# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and -# GIT_AUTHOR_DATE that will be used for the commit that is currently -# being rebased. -author_script="$state_dir"/author-script - -# When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase -# --continue" is executed, if there are any staged changes then they -# will be amended to the HEAD commit, but only provided the HEAD -# commit is still the commit to be edited. When any other rebase -# command is processed, this file is deleted. -amend="$state_dir"/amend - -# 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, -# e.g. because they are waiting for a 'squash' command. -rewritten_list="$state_dir"/rewritten-list -rewritten_pending="$state_dir"/rewritten-pending - -# Work around Git for Windows' Bash whose "read" does not strip CRLF -# and leaves CR at the end instead. -cr=$(printf "\015") - -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm <conflicted_files>", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" - -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - test t = "$GIT_QUIET" && : > "$state_dir"/quiet - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff - test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m "autostash" -q $stash_sha1 || - die "$(eval_gettext "Cannot store \$stash_sha1")" - gettext 'Applying autostash resulted in conflicts. -Your changes are safe in the stash. -You can run "git stash pop" or "git stash drop" at any time. -' >&2 - fi - fi -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -strategy_args=${strategy:+--strategy=$strategy} -test -n "$strategy_opts" && -eval ' - for strategy_opt in '"$strategy_opts"' - do - strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")" - done -' - -GIT_CHERRY_PICK_HELP="$resolvemsg" -export GIT_CHERRY_PICK_HELP - -comment_char=$(git config --get core.commentchar 2>/dev/null) -case "$comment_char" in -'' | auto) - comment_char="#" - ;; -?) - ;; -*) - comment_char=$(echo "$comment_char" | cut -c1) - ;; -esac - -warn () { - printf '%s\n' "$*" >&2 -} - -# Output the commit message for the specified commit. -commit_message () { - git cat-file commit "$1" | sed "1,/^$/d" -} - -orig_reflog_action="$GIT_REFLOG_ACTION" - -comment_for_reflog () { - case "$orig_reflog_action" in - ''|rebase*) - GIT_REFLOG_ACTION="rebase -i ($1)" - export GIT_REFLOG_ACTION - ;; - esac -} - -last_count= -mark_action_done () { - sed -e 1q < "$todo" >> "$done" - sed -e 1d < "$todo" >> "$todo".new - mv -f "$todo".new "$todo" - new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) )) - echo $new_count >"$msgnum" - total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l))) - echo $total >"$end" - if test "$last_count" != "$new_count" - then - last_count=$new_count - eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r" - test -z "$verbose" || echo - fi -} - -append_todo_help () { - gettext " -Commands: -p, pick <commit> = use commit -r, reword <commit> = use commit, but edit the commit message -e, edit <commit> = use commit, but stop for amending -s, squash <commit> = use commit, but meld into previous commit -f, fixup <commit> = like \"squash\", but discard this commit's log message -x, exec <commit> = run command (the rest of the line) using shell -d, drop <commit> = remove commit -l, label <label> = label current HEAD with a name -t, reset <label> = reset HEAD to a label -m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] -. create a merge commit using the original merge commit's -. message (or the oneline, if no original merge commit was -. specified). Use -c <commit> to reword the commit message. - -These lines can be re-ordered; they are executed from top to bottom. -" | git stripspace --comment-lines >>"$todo" - - if test $(get_missing_commit_check_level) = error - then - gettext " -Do not remove any line. Use 'drop' explicitly to remove a commit. -" | git stripspace --comment-lines >>"$todo" - else - gettext " -If you remove a line here THAT COMMIT WILL BE LOST. -" | git stripspace --comment-lines >>"$todo" - fi -} - -make_patch () { - sha1_and_parents="$(git rev-list --parents -1 "$1")" - case "$sha1_and_parents" in - ?*' '?*' '?*) - git diff --cc $sha1_and_parents - ;; - ?*' '?*) - git diff-tree -p "$1^!" - ;; - *) - echo "Root commit" - ;; - esac > "$state_dir"/patch - test -f "$msg" || - commit_message "$1" > "$msg" - test -f "$author_script" || - get_author_ident_from_commit "$1" > "$author_script" -} - -die_with_patch () { - echo "$1" > "$state_dir"/stopped-sha - git update-ref REBASE_HEAD "$1" - make_patch "$1" - die "$2" -} - -exit_with_patch () { - echo "$1" > "$state_dir"/stopped-sha - git update-ref REBASE_HEAD "$1" - make_patch $1 - git rev-parse --verify HEAD > "$amend" - gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} - warn "$(eval_gettext "\ -You can amend the commit now, with - - git commit --amend \$gpg_sign_opt_quoted - -Once you are satisfied with your changes, run - - git rebase --continue")" - warn - exit $2 -} - -die_abort () { - apply_autostash - rm -rf "$state_dir" - die "$1" -} - -has_action () { - test -n "$(git stripspace --strip-comments <"$1")" -} - -is_empty_commit() { - tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || { - sha1=$1 - die "$(eval_gettext "\$sha1: not a commit that can be picked")" - } - ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) || - ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - test "$tree" = "$ptree" -} - -is_merge_commit() -{ - git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1 -} - -# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and -# GIT_AUTHOR_DATE exported from the current environment. -do_with_author () { - ( - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE - "$@" - ) -} - -git_sequence_editor () { - if test -z "$GIT_SEQUENCE_EDITOR" - then - GIT_SEQUENCE_EDITOR="$(git config sequence.editor)" - if [ -z "$GIT_SEQUENCE_EDITOR" ] - then - GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $? - fi - fi - - eval "$GIT_SEQUENCE_EDITOR" '"$@"' -} - -pick_one () { - ff=--ff - - case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac - case "$force_rebase" in '') ;; ?*) ff= ;; esac - output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")" - - if is_empty_commit "$sha1" - then - empty_args="--allow-empty" - fi - - pick_one_preserving_merges "$@" -} - -pick_one_preserving_merges () { - fast_forward=t - case "$1" in - -n) - fast_forward=f - sha1=$2 - ;; - *) - sha1=$1 - ;; - esac - sha1=$(git rev-parse $sha1) - - if test -f "$state_dir"/current-commit && test "$fast_forward" = t - then - while read current_commit - do - git rev-parse HEAD > "$rewritten"/$current_commit - done <"$state_dir"/current-commit - rm "$state_dir"/current-commit || - die "$(gettext "Cannot write current commit's replacement sha1")" - fi - - echo $sha1 >> "$state_dir"/current-commit - - # rewrite parents; if none were rewritten, we can fast-forward. - new_parents= - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" - if test "$pend" = " " - then - pend=" root" - fi - while [ "$pend" != "" ] - do - p=$(expr "$pend" : ' \([^ ]*\)') - pend="${pend# $p}" - - if test -f "$rewritten"/$p - then - new_p=$(cat "$rewritten"/$p) - - # If the todo reordered commits, and our parent is marked for - # rewriting, but hasn't been gotten to yet, assume the user meant to - # drop it on top of the current HEAD - if test -z "$new_p" - then - new_p=$(git rev-parse HEAD) - fi - - test $p != $new_p && fast_forward=f - case "$new_parents" in - *$new_p*) - ;; # do nothing; that parent is already there - *) - new_parents="$new_parents $new_p" - ;; - esac - else - if test -f "$dropped"/$p - then - fast_forward=f - replacement="$(cat "$dropped"/$p)" - test -z "$replacement" && replacement=root - pend=" $replacement$pend" - else - new_parents="$new_parents $p" - fi - fi - done - case $fast_forward in - t) - output warn "$(eval_gettext "Fast-forward to \$sha1")" - output git reset --hard $sha1 || - die "$(eval_gettext "Cannot fast-forward to \$sha1")" - ;; - f) - first_parent=$(expr "$new_parents" : ' \([^ ]*\)') - - if [ "$1" != "-n" ] - then - # detach HEAD to current parent - output git checkout $first_parent 2> /dev/null || - die "$(eval_gettext "Cannot move HEAD to \$first_parent")" - fi - - case "$new_parents" in - ' '*' '*) - test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")" - - # redo merge - author_script_content=$(get_author_ident_from_commit $sha1) - eval "$author_script_content" - msg_content="$(commit_message $sha1)" - # No point in merging the first parent, that's HEAD - new_parents=${new_parents# $first_parent} - merge_args="--no-log --no-ff" - if ! do_with_author output eval \ - git merge ${gpg_sign_opt:+$(git rev-parse \ - --sq-quote "$gpg_sign_opt")} \ - $allow_rerere_autoupdate "$merge_args" \ - "$strategy_args" \ - -m "$(git rev-parse --sq-quote "$msg_content")" \ - "$new_parents" - then - printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG - die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")" - fi - echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" - ;; - *) - output eval git cherry-pick $allow_rerere_autoupdate \ - $allow_empty_message \ - ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ - "$strategy_args" "$@" || - die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")" - ;; - esac - ;; - esac -} - -this_nth_commit_message () { - n=$1 - eval_gettext "This is the commit message #\${n}:" -} - -skip_nth_commit_message () { - n=$1 - eval_gettext "The commit message #\${n} will be skipped:" -} - -update_squash_messages () { - if test -f "$squash_msg"; then - mv "$squash_msg" "$squash_msg".bak || exit - count=$(($(sed -n \ - -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \ - -e "q" < "$squash_msg".bak)+1)) - { - printf '%s\n' "$comment_char $(eval_ngettext \ - "This is a combination of \$count commit." \ - "This is a combination of \$count commits." \ - $count)" - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$squash_msg".bak - } >"$squash_msg" - else - commit_message HEAD >"$fixup_msg" || - die "$(eval_gettext "Cannot write \$fixup_msg")" - count=2 - { - printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")" - printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")" - echo - cat "$fixup_msg" - } >"$squash_msg" - fi - case $1 in - squash) - rm -f "$fixup_msg" - echo - printf '%s\n' "$comment_char $(this_nth_commit_message $count)" - echo - commit_message $2 - ;; - fixup) - echo - printf '%s\n' "$comment_char $(skip_nth_commit_message $count)" - echo - # Change the space after the comment character to TAB: - commit_message $2 | git stripspace --comment-lines | sed -e 's/ / /' - ;; - esac >>"$squash_msg" -} - -peek_next_command () { - git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q -} - -# A squash/fixup has failed. Prepare the long version of the squash -# commit message, then die_with_patch. This code path requires the -# user to edit the combined commit message for all commits that have -# been squashed/fixedup so far. So also erase the old squash -# messages, effectively causing the combined commit to be used as the -# new basis for any further squash/fixups. Args: sha1 rest -die_failed_squash() { - sha1=$1 - rest=$2 - mv "$squash_msg" "$msg" || exit - rm -f "$fixup_msg" - cp "$msg" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "$(eval_gettext "Could not apply \$sha1... \$rest")" - die_with_patch $sha1 "" -} - -flush_rewritten_pending() { - test -s "$rewritten_pending" || return - newsha1="$(git rev-parse HEAD^0)" - sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list" - rm -f "$rewritten_pending" -} - -record_in_rewritten() { - oldsha1="$(git rev-parse $1)" - echo "$oldsha1" >> "$rewritten_pending" - - case "$(peek_next_command)" in - squash|s|fixup|f) - ;; - *) - flush_rewritten_pending - ;; - esac -} - -do_pick () { - sha1=$1 - rest=$2 - if test "$(git rev-parse HEAD)" = "$squash_onto" - then - # Set the correct commit message and author info on the - # sentinel root before cherry-picking the original changes - # without committing (-n). Finally, update the sentinel again - # to include these changes. If the cherry-pick results in a - # conflict, this means our behaviour is similar to a standard - # failed cherry-pick during rebase, with a dirty index to - # resolve before manually running git commit --amend then git - # rebase --continue. - git commit --allow-empty --allow-empty-message --amend \ - --no-post-rewrite -n -q -C $sha1 $signoff && - pick_one -n $sha1 && - git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -q -C $sha1 $signoff \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" - else - pick_one $sha1 || - die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")" - fi -} - -do_next () { - rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit - read -r command sha1 rest < "$todo" - case "$command" in - "$comment_char"*|''|noop|drop|d) - mark_action_done - ;; - "$cr") - # Work around CR left by "read" (e.g. with Git for Windows' Bash). - mark_action_done - ;; - pick|p) - comment_for_reflog pick - - mark_action_done - do_pick $sha1 "$rest" - record_in_rewritten $sha1 - ;; - reword|r) - comment_for_reflog reword - - mark_action_done - do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \ - $allow_empty_message || { - warn "$(eval_gettext "\ -Could not amend commit after successfully picking \$sha1... \$rest -This is most likely due to an empty commit message, or the pre-commit hook -failed. If the pre-commit hook failed, you may need to resolve the issue before -you are able to reword the commit.")" - exit_with_patch $sha1 1 - } - record_in_rewritten $sha1 - ;; - edit|e) - comment_for_reflog edit - - mark_action_done - do_pick $sha1 "$rest" - sha1_abbrev=$(git rev-parse --short $sha1) - warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")" - exit_with_patch $sha1 0 - ;; - squash|s|fixup|f) - case "$command" in - squash|s) - squash_style=squash - ;; - fixup|f) - squash_style=fixup - ;; - esac - comment_for_reflog $squash_style - - test -f "$done" && has_action "$done" || - die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")" - - mark_action_done - update_squash_messages $squash_style $sha1 - author_script_content=$(get_author_ident_from_commit HEAD) - echo "$author_script_content" > "$author_script" - eval "$author_script_content" - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi - case "$(peek_next_command)" in - squash|s|fixup|f) - # This is an intermediate commit; its message will only be - # used in case of trouble. So use the long version: - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - ;; - *) - # This is the final command of this squash/fixup group - if test -f "$fixup_msg" - then - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - else - cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die_failed_squash $sha1 "$rest" - fi - rm -f "$squash_msg" "$fixup_msg" - ;; - esac - record_in_rewritten $sha1 - ;; - x|"exec") - read -r command rest < "$todo" - mark_action_done - eval_gettextln "Executing: \$rest" - "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution - status=$? - # Run in subshell because require_clean_work_tree can die. - dirty=f - (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t - if test "$status" -ne 0 - then - warn "$(eval_gettext "Execution failed: \$rest")" - test "$dirty" = f || - warn "$(gettext "and made changes to the index and/or the working tree")" - - warn "$(gettext "\ -You can fix the problem, and then run - - git rebase --continue")" - warn - if test $status -eq 127 # command not found - then - status=1 - fi - exit "$status" - elif test "$dirty" = t - then - # TRANSLATORS: after these lines is a command to be issued by the user - warn "$(eval_gettext "\ -Execution succeeded: \$rest -but left changes to the index and/or the working tree -Commit or stash your changes, and then run - - git rebase --continue")" - warn - exit 1 - fi - ;; - *) - warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")" - fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")" - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi - ;; - esac - test -s "$todo" && return - - comment_for_reflog finish && - newhead=$(git rev-parse HEAD) && - case $head_name in - refs/*) - message="$GIT_REFLOG_ACTION: $head_name onto $onto" && - git update-ref -m "$message" $head_name $newhead $orig_head && - git symbolic-ref \ - -m "$GIT_REFLOG_ACTION: returning to $head_name" \ - HEAD $head_name - ;; - esac && { - test ! -f "$state_dir"/verbose || - git diff-tree --stat $orig_head..HEAD - } && - { - test -s "$rewritten_list" && - git notes copy --for-rewrite=rebase < "$rewritten_list" || - true # we don't care if this copying failed - } && - hook="$(git rev-parse --git-path hooks/post-rewrite)" - if test -x "$hook" && test -s "$rewritten_list"; then - "$hook" rebase < "$rewritten_list" - true # we don't care if this hook failed - fi && - warn "$(eval_gettext "Successfully rebased and updated \$head_name.")" - - return 1 # not failure; just to break the do_rest loop -} - -# can only return 0, when the infinite loop breaks -do_rest () { - while : - do - do_next || break - done -} - -expand_todo_ids() { - git rebase--interactive --expand-ids -} - -collapse_todo_ids() { - git rebase--interactive --shorten-ids -} - -# Switch to the branch in $into and notify it in the reflog -checkout_onto () { - GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" - output git checkout $onto || die_abort "$(gettext "could not detach HEAD")" - git update-ref ORIG_HEAD $orig_head -} - -get_missing_commit_check_level () { - check_level=$(git config --get rebase.missingCommitsCheck) - check_level=${check_level:-ignore} - # Don't be case sensitive - printf '%s' "$check_level" | tr 'A-Z' 'a-z' -} - -# Initiate an action. If the cannot be any -# further action it may exec a command -# or exit and not return. -# -# TODO: Consider a cleaner return model so it -# never exits and always return 0 if process -# is complete. -# -# Parameter 1 is the action to initiate. -# -# Returns 0 if the action was able to complete -# and if 1 if further processing is required. -initiate_action () { - case "$1" in - continue) - # do we have anything to commit? - if git diff-index --cached --quiet HEAD -- - then - # Nothing to commit -- skip this commit - - test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD || - rm "$GIT_DIR"/CHERRY_PICK_HEAD || - die "$(gettext "Could not remove CHERRY_PICK_HEAD")" - else - if ! test -f "$author_script" - then - gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} - die "$(eval_gettext "\ -You have staged changes in your working tree. -If these changes are meant to be -squashed into the previous commit, run: - - git commit --amend \$gpg_sign_opt_quoted - -If they are meant to go into a new commit, run: - - git commit \$gpg_sign_opt_quoted - -In both cases, once you're done, continue with: - - git rebase --continue -")" - fi - . "$author_script" || - die "$(gettext "Error trying to find the author identity to amend commit")" - if test -f "$amend" - then - current_head=$(git rev-parse --verify HEAD) - test "$current_head" = $(cat "$amend") || - die "$(gettext "\ -You have uncommitted changes in your working tree. Please commit them -first and then run 'git rebase --continue' again.")" - do_with_author git commit --amend --no-verify -F "$msg" -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die "$(gettext "Could not commit staged changes.")" - else - do_with_author git commit --no-verify -F "$msg" -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message || - die "$(gettext "Could not commit staged changes.")" - fi - fi - - if test -r "$state_dir"/stopped-sha - then - record_in_rewritten "$(cat "$state_dir"/stopped-sha)" - fi - - require_clean_work_tree "rebase" - do_rest - return 0 - ;; - skip) - git rerere clear - do_rest - return 0 - ;; - edit-todo) - git stripspace --strip-comments <"$todo" >"$todo".new - mv -f "$todo".new "$todo" - collapse_todo_ids - append_todo_help - gettext " -You are editing the todo file of an ongoing interactive rebase. -To continue rebase after editing, run: - git rebase --continue - -" | git stripspace --comment-lines >>"$todo" - - git_sequence_editor "$todo" || - die "$(gettext "Could not execute editor")" - expand_todo_ids - - exit - ;; - show-current-patch) - exec git show REBASE_HEAD -- - ;; - *) - return 1 # continue - ;; - esac -} - -setup_reflog_action () { - comment_for_reflog start - - if test ! -z "$switch_to" - then - GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" - output git checkout "$switch_to" -- || - die "$(eval_gettext "Could not checkout \$switch_to")" - - comment_for_reflog start - fi -} - -init_basic_state () { - orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")" - mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")" - rm -f "$(git rev-parse --git-path REBASE_HEAD)" - - : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")" - write_basic_state -} - -init_revisions_and_shortrevisions () { - shorthead=$(git rev-parse --short $orig_head) - shortonto=$(git rev-parse --short $onto) - if test -z "$rebase_root" - # this is now equivalent to ! -z "$upstream" - then - shortupstream=$(git rev-parse --short $upstream) - revisions=$upstream...$orig_head - shortrevisions=$shortupstream..$shorthead - else - revisions=$onto...$orig_head - shortrevisions=$shorthead - test -z "$squash_onto" || - echo "$squash_onto" >"$state_dir"/squash-onto - fi -} - -complete_action() { - test -s "$todo" || echo noop >> "$todo" - test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit - test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd" - - todocount=$(git stripspace --strip-comments <"$todo" | wc -l) - todocount=${todocount##* } - -cat >>"$todo" <<EOF - -$comment_char $(eval_ngettext \ - "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \ - "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \ - "$todocount") -EOF - append_todo_help - gettext " -However, if you remove everything, the rebase will be aborted. - -" | git stripspace --comment-lines >>"$todo" - - if test -z "$keep_empty" - then - printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo" - fi - - - has_action "$todo" || - return 2 - - cp "$todo" "$todo".backup - collapse_todo_ids - git_sequence_editor "$todo" || - die_abort "$(gettext "Could not execute editor")" - - has_action "$todo" || - return 2 - - git rebase--interactive --check-todo-list || { - ret=$? - checkout_onto - exit $ret - } - - expand_todo_ids - checkout_onto - do_rest -} - -git_rebase__preserve_merges () { - initiate_action "$action" - ret=$? - if test $ret = 0; then - return 0 - fi - - setup_reflog_action - init_basic_state - - if test -z "$rebase_root" - then - mkdir "$rewritten" && - for c in $(git merge-base --all $orig_head $upstream) - do - echo $onto > "$rewritten"/$c || - die "$(gettext "Could not init rewritten commits")" - done - else - mkdir "$rewritten" && - echo $onto > "$rewritten"/root || - die "$(gettext "Could not init rewritten commits")" - fi - - init_revisions_and_shortrevisions - - format=$(git config --get rebase.instructionFormat) - # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse - git rev-list --format="%m%H ${format:-%s}" \ - --reverse --left-right --topo-order \ - $revisions ${restrict_revision+^$restrict_revision} | \ - sed -n "s/^>//p" | - while read -r sha1 rest - do - if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 - then - comment_out="$comment_char " - else - comment_out= - fi - - if test -z "$rebase_root" - then - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) - do - if test -f "$rewritten"/$p - then - preserve=f - fi - done - else - preserve=f - fi - if test f = "$preserve" - then - touch "$rewritten"/$sha1 - printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" - fi - done - - # Watch for commits that been dropped by --cherry-pick - mkdir "$dropped" - # Save all non-cherry-picked changes - git rev-list $revisions --left-right --cherry-pick | \ - sed -n "s/^>//p" > "$state_dir"/not-cherry-picks - # Now all commits and note which ones are missing in - # not-cherry-picks and hence being dropped - git rev-list $revisions | - while read rev - do - if test -f "$rewritten"/$rev && - ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null - then - # Use -f2 because if rev-list is telling us this commit is - # not worthwhile, we don't want to track its multiple heads, - # just the history of its first-parent for others that will - # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev - sha1=$(git rev-list -1 $rev) - sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo" - rm "$rewritten"/$rev - fi - done - - complete_action -} diff --git a/git-svn.perl b/git-svn.perl index 70cb5e2a83..be987e316f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -273,7 +273,6 @@ my %cmd = ( 'fetch-all|all' => \$_fetch_all, 'dry-run|n' => \$_dry_run, 'rebase-merges|p' => \$_rebase_merges, - 'preserve-merges|p' => \$_rebase_merges, %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, 'Commit a diff between two trees', @@ -577,7 +577,6 @@ static struct cmd_struct commands[] = { { "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER }, { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, { "rebase", cmd_rebase, RUN_SETUP | NEED_WORK_TREE }, - { "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, @@ -867,7 +867,7 @@ void free_grep_patterns(struct grep_opt *opt) free_pattern_expr(opt->pattern_expression); } -static char *end_of_line(char *cp, unsigned long *left) +static const char *end_of_line(const char *cp, unsigned long *left) { unsigned long l = *left; while (l && *cp != '\n') { @@ -908,7 +908,8 @@ static void show_name(struct grep_opt *opt, const char *name) opt->output(opt, opt->null_following_name ? "\0" : "\n", 1); } -static int patmatch(struct grep_pat *p, char *line, char *eol, +static int patmatch(struct grep_pat *p, + const char *line, const char *eol, regmatch_t *match, int eflags) { int hit; @@ -922,20 +923,16 @@ static int patmatch(struct grep_pat *p, char *line, char *eol, return hit; } -static int strip_timestamp(char *bol, char **eol_p) +static void strip_timestamp(const char *bol, const char **eol_p) { - char *eol = *eol_p; - int ch; + const char *eol = *eol_p; while (bol < --eol) { if (*eol != '>') continue; *eol_p = ++eol; - ch = *eol; - *eol = '\0'; - return ch; + break; } - return 0; } static struct { @@ -947,12 +944,12 @@ static struct { { "reflog ", 7 }, }; -static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, +static int match_one_pattern(struct grep_pat *p, + const char *bol, const char *eol, enum grep_context ctx, regmatch_t *pmatch, int eflags) { int hit = 0; - int saved_ch = 0; const char *start = bol; if ((p->token != GREP_PATTERN) && @@ -971,7 +968,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, switch (p->field) { case GREP_HEADER_AUTHOR: case GREP_HEADER_COMMITTER: - saved_ch = strip_timestamp(bol, &eol); + strip_timestamp(bol, &eol); break; default: break; @@ -1021,8 +1018,6 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, goto again; } } - if (p->token == GREP_PATTERN_HEAD && saved_ch) - *eol = saved_ch; if (hit) { pmatch[0].rm_so += bol - start; pmatch[0].rm_eo += bol - start; @@ -1030,8 +1025,9 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, return hit; } -static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol, - char *eol, enum grep_context ctx, ssize_t *col, +static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, + const char *bol, const char *eol, + enum grep_context ctx, ssize_t *col, ssize_t *icol, int collect_hits) { int h = 0; @@ -1098,7 +1094,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol, return h; } -static int match_expr(struct grep_opt *opt, char *bol, char *eol, +static int match_expr(struct grep_opt *opt, + const char *bol, const char *eol, enum grep_context ctx, ssize_t *col, ssize_t *icol, int collect_hits) { @@ -1106,7 +1103,8 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol, return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits); } -static int match_line(struct grep_opt *opt, char *bol, char *eol, +static int match_line(struct grep_opt *opt, + const char *bol, const char *eol, ssize_t *col, ssize_t *icol, enum grep_context ctx, int collect_hits) { @@ -1138,7 +1136,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol, return hit; } -static int match_next_pattern(struct grep_pat *p, char *bol, char *eol, +static int match_next_pattern(struct grep_pat *p, + const char *bol, const char *eol, enum grep_context ctx, regmatch_t *pmatch, int eflags) { @@ -1159,7 +1158,8 @@ static int match_next_pattern(struct grep_pat *p, char *bol, char *eol, return 1; } -static int next_match(struct grep_opt *opt, char *bol, char *eol, +static int next_match(struct grep_opt *opt, + const char *bol, const char *eol, enum grep_context ctx, regmatch_t *pmatch, int eflags) { struct grep_pat *p; @@ -1215,7 +1215,8 @@ static void show_line_header(struct grep_opt *opt, const char *name, } } -static void show_line(struct grep_opt *opt, char *bol, char *eol, +static void show_line(struct grep_opt *opt, + const char *bol, const char *eol, const char *name, unsigned lno, ssize_t cno, char sign) { int rest = eol - bol; @@ -1246,7 +1247,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, if (opt->color || opt->only_matching) { regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; - int ch = *eol; int eflags = 0; if (opt->color) { @@ -1261,7 +1261,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, else if (sign == '=') line_color = opt->colors[GREP_COLOR_FUNCTION]; } - *eol = '\0'; while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) break; @@ -1279,7 +1278,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, rest -= match.rm_eo; eflags = REG_NOTBOL; } - *eol = ch; } if (!opt->only_matching) { output_color(opt, bol, rest, line_color); @@ -1307,7 +1305,8 @@ static inline void grep_attr_unlock(void) pthread_mutex_unlock(&grep_attr_mutex); } -static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol) +static int match_funcname(struct grep_opt *opt, struct grep_source *gs, + const char *bol, const char *eol) { xdemitconf_t *xecfg = opt->priv; if (xecfg && !xecfg->find_func) { @@ -1334,10 +1333,10 @@ static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bo } static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs, - char *bol, unsigned lno) + const char *bol, unsigned lno) { while (bol > gs->buf) { - char *eol = --bol; + const char *eol = --bol; while (bol > gs->buf && bol[-1] != '\n') bol--; @@ -1356,7 +1355,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs, static int is_empty_line(const char *bol, const char *eol); static void show_pre_context(struct grep_opt *opt, struct grep_source *gs, - char *bol, char *end, unsigned lno) + const char *bol, const char *end, unsigned lno) { unsigned cur = lno, from = 1, funcname_lno = 0, orig_from; int funcname_needed = !!opt->funcname, comment_needed = 0; @@ -1376,8 +1375,8 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs, /* Rewind. */ while (bol > gs->buf && cur > from) { - char *next_bol = bol; - char *eol = --bol; + const char *next_bol = bol; + const char *eol = --bol; while (bol > gs->buf && bol[-1] != '\n') bol--; @@ -1408,7 +1407,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs, /* Back forward. */ while (cur < lno) { - char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-'; + const char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-'; while (*eol != '\n') eol++; @@ -1436,12 +1435,12 @@ static int should_lookahead(struct grep_opt *opt) static int look_ahead(struct grep_opt *opt, unsigned long *left_p, unsigned *lno_p, - char **bol_p) + const char **bol_p) { unsigned lno = *lno_p; - char *bol = *bol_p; + const char *bol = *bol_p; struct grep_pat *p; - char *sp, *last_bol; + const char *sp, *last_bol; regoff_t earliest = -1; for (p = opt->pattern_list; p; p = p->next) { @@ -1543,8 +1542,8 @@ static int is_empty_line(const char *bol, const char *eol) static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits) { - char *bol; - char *peek_bol = NULL; + const char *bol; + const char *peek_bol = NULL; unsigned long left; unsigned lno = 1; unsigned last_hit = 0; @@ -1626,7 +1625,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle bol = gs->buf; left = gs->size; while (left) { - char *eol, ch; + const char *eol; int hit; ssize_t cno; ssize_t col = -1, icol = -1; @@ -1647,14 +1646,11 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle && look_ahead(opt, &left, &lno, &bol)) break; eol = end_of_line(bol, &left); - ch = *eol; - *eol = 0; if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) ctx = GREP_CONTEXT_BODY; hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits); - *eol = ch; if (collect_hits) goto next_line; @@ -1713,7 +1709,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle } if (show_function && (!peek_bol || peek_bol < bol)) { unsigned long peek_left = left; - char *peek_eol = eol; + const char *peek_eol = eol; /* * Trailing empty lines are not interesting. @@ -1825,7 +1821,8 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs) return grep_source_1(opt, gs, 0); } -static void grep_source_init_buf(struct grep_source *gs, char *buf, +static void grep_source_init_buf(struct grep_source *gs, + const char *buf, unsigned long size) { gs->type = GREP_SOURCE_BUF; @@ -1837,7 +1834,7 @@ static void grep_source_init_buf(struct grep_source *gs, char *buf, gs->identifier = NULL; } -int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size) +int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size) { struct grep_source gs; int r; @@ -1889,7 +1886,9 @@ void grep_source_clear_data(struct grep_source *gs) switch (gs->type) { case GREP_SOURCE_FILE: case GREP_SOURCE_OID: - FREE_AND_NULL(gs->buf); + /* these types own the buffer */ + free((char *)gs->buf); + gs->buf = NULL; gs->size = 0; break; case GREP_SOURCE_BUF: @@ -128,9 +128,9 @@ struct grep_opt { * instead. * * This is potentially the cause of at least one bug - "git grep" - * ignoring the textconv attributes from submodules. See [1] for more - * information. - * [1] https://lore.kernel.org/git/CAHd-oW5iEQarYVxEXoTG-ua2zdoybTrSjCBKtO0YT292fm0NQQ@mail.gmail.com/ + * using the textconv attributes from the superproject on the + * submodules. See the failing "git grep --textconv" tests in + * t7814-grep-recurse-submodules.sh for more information. */ struct repository *repo; @@ -189,7 +189,7 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *orig void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *); void compile_grep_patterns(struct grep_opt *opt); void free_grep_patterns(struct grep_opt *opt); -int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size); +int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size); struct grep_source { char *name; @@ -202,7 +202,7 @@ struct grep_source { void *identifier; struct repository *repo; /* if GREP_SOURCE_OID */ - char *buf; + const char *buf; unsigned long size; char *path; /* for attribute lookups */ @@ -223,7 +223,6 @@ void grep_source_load_driver(struct grep_source *gs, int grep_source(struct grep_opt *opt, struct grep_source *gs); struct grep_opt *grep_opt_dup(const struct grep_opt *opt); -int grep_threads_ok(const struct grep_opt *opt); /* * Mutex used around access to the attributes machinery if @@ -293,9 +293,21 @@ void load_command_list(const char *prefix, exclude_cmds(other_cmds, main_cmds); } -void list_commands(unsigned int colopts, - struct cmdnames *main_cmds, struct cmdnames *other_cmds) +static int get_colopts(const char *var, const char *value, void *data) { + unsigned int *colopts = data; + + if (starts_with(var, "column.")) + return git_column_config(var, value, "help", colopts); + + return 0; +} + +void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds) +{ + unsigned int colopts = 0; + git_config(get_colopts, &colopts); + if (main_cmds->cnt) { const char *exec_path = git_exec_path(); printf_ln(_("available git commands in '%s'"), exec_path); @@ -37,7 +37,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len); /* Here we require that excludes is a sorted list. */ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); int is_in_cmdlist(struct cmdnames *cmds, const char *name); -void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds); +void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds); void get_version_info(struct strbuf *buf, int show_build_options); /* diff --git a/hook.c b/hook.c new file mode 100644 index 0000000000..55e1145a4b --- /dev/null +++ b/hook.c @@ -0,0 +1,42 @@ +#include "cache.h" +#include "hook.h" +#include "run-command.h" + +const char *find_hook(const char *name) +{ + static struct strbuf path = STRBUF_INIT; + + strbuf_reset(&path); + strbuf_git_path(&path, "hooks/%s", name); + if (access(path.buf, X_OK) < 0) { + int err = errno; + +#ifdef STRIP_EXTENSION + strbuf_addstr(&path, STRIP_EXTENSION); + if (access(path.buf, X_OK) >= 0) + return path.buf; + if (errno == EACCES) + err = errno; +#endif + + if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) { + static struct string_list advise_given = STRING_LIST_INIT_DUP; + + if (!string_list_lookup(&advise_given, name)) { + string_list_insert(&advise_given, name); + advise(_("The '%s' hook was ignored because " + "it's not set as executable.\n" + "You can disable this warning with " + "`git config advice.ignoredHook false`."), + path.buf); + } + } + return NULL; + } + return path.buf; +} + +int hook_exists(const char *name) +{ + return !!find_hook(name); +} diff --git a/hook.h b/hook.h new file mode 100644 index 0000000000..6aa36fc7ff --- /dev/null +++ b/hook.h @@ -0,0 +1,16 @@ +#ifndef HOOK_H +#define HOOK_H + +/* + * Returns the path to the hook file, or NULL if the hook is missing + * or disabled. Note that this points to static storage that will be + * overwritten by further calls to find_hook and run_hook_*. + */ +const char *find_hook(const char *name); + +/** + * A boolean version of find_hook() + */ +int hook_exists(const char *hookname); + +#endif @@ -551,8 +551,8 @@ static void redact_sensitive_header(struct strbuf *header) const char *sensitive_header; if (trace_curl_redact && - (skip_prefix(header->buf, "Authorization:", &sensitive_header) || - skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { + (skip_iprefix(header->buf, "Authorization:", &sensitive_header) || + skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { /* The first token is the type, which is OK to log */ while (isspace(*sensitive_header)) sensitive_header++; @@ -562,7 +562,7 @@ static void redact_sensitive_header(struct strbuf *header) strbuf_setlen(header, sensitive_header - header->buf); strbuf_addstr(header, " <redacted>"); } else if (trace_curl_redact && - skip_prefix(header->buf, "Cookie:", &sensitive_header)) { + skip_iprefix(header->buf, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; const char *cookie; @@ -990,7 +990,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) char *low_speed_limit; char *low_speed_time; char *normalized_url; - struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct urlmatch_config config = URLMATCH_CONFIG_INIT; config.section = "http"; config.key = NULL; @@ -1489,6 +1489,10 @@ static int handle_curl_result(struct slot_results *results) */ credential_reject(&cert_auth); return HTTP_NOAUTH; +#ifdef GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH + } else if (results->curl_result == CURLE_SSL_PINNEDPUBKEYNOTMATCH) { + return HTTP_NOMATCHPUBLICKEY; +#endif } else if (missing_target(results)) return HTTP_MISSING_TARGET; else if (results->http_code == 401) { @@ -154,6 +154,7 @@ struct http_get_options { #define HTTP_START_FAILED 3 #define HTTP_REAUTH 4 #define HTTP_NOAUTH 5 +#define HTTP_NOMATCHPUBLICKEY 6 /* * Requests a URL and stores the result in a strbuf. @@ -46,7 +46,10 @@ struct list_head { #define INIT_LIST_HEAD(ptr) \ (ptr)->next = (ptr)->prev = (ptr) -#define LIST_HEAD_INIT(name) { &(name), &(name) } +#define LIST_HEAD_INIT(name) { \ + .next = &(name), \ + .prev = &(name), \ +} /* Add new element at the head of the list. */ static inline void list_add(struct list_head *newp, struct list_head *head) diff --git a/lockfile.h b/lockfile.h index db93e6ba73..90af4e66b2 100644 --- a/lockfile.h +++ b/lockfile.h @@ -121,7 +121,7 @@ struct lock_file { struct tempfile *tempfile; }; -#define LOCK_INIT { NULL } +#define LOCK_INIT { 0 } /* String appended to a filename to derive the lockfile name: */ #define LOCK_SUFFIX ".lock" diff --git a/log-tree.h b/log-tree.h index 1e8c91dbf2..e7e4641cf8 100644 --- a/log-tree.h +++ b/log-tree.h @@ -14,10 +14,8 @@ struct decoration_filter { }; int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); -void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); -int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt); void format_decorations_extended(struct strbuf *sb, const struct commit *commit, int use_color, @@ -41,6 +41,12 @@ static void ensure_config_read(void) } /* + * If we see this many or more "ref-prefix" lines from the client, we consider + * it "too many" and will avoid using the prefix feature entirely. + */ +#define TOO_MANY_PREFIXES 65536 + +/* * Check if one of the prefixes is a prefix of the ref. * If no prefixes were provided, all refs match. */ @@ -158,15 +164,27 @@ int ls_refs(struct repository *r, struct packet_reader *request) data.peel = 1; else if (!strcmp("symrefs", arg)) data.symrefs = 1; - else if (skip_prefix(arg, "ref-prefix ", &out)) - strvec_push(&data.prefixes, out); + else if (skip_prefix(arg, "ref-prefix ", &out)) { + if (data.prefixes.nr < TOO_MANY_PREFIXES) + strvec_push(&data.prefixes, out); + } else if (!strcmp("unborn", arg)) data.unborn = allow_unborn; + else + die(_("unexpected line: '%s'"), arg); } if (request->status != PACKET_READ_FLUSH) die(_("expected flush after ls-refs arguments")); + /* + * If we saw too many prefixes, we must avoid using them at all; as + * soon as we have any prefix, they are meant to form a comprehensive + * list. + */ + if (data.prefixes.nr >= TOO_MANY_PREFIXES) + strvec_clear(&data.prefixes); + send_possibly_unborn_head(&data); if (!data.prefixes.nr) strvec_push(&data.prefixes, ""); diff --git a/merge-ort.c b/merge-ort.c index fbc5c204c1..0342f10483 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -4052,11 +4052,7 @@ static int checkout(struct merge_options *opt, unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */ unpack_opts.verbose_update = (opt->verbosity > 2); unpack_opts.fn = twoway_merge; - if (1/* FIXME: opts->overwrite_ignore*/) { - CALLOC_ARRAY(unpack_opts.dir, 1); - unpack_opts.dir->flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(unpack_opts.dir); - } + unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */ parse_tree(prev); init_tree_desc(&trees[0], prev->buffer, prev->size); parse_tree(next); @@ -4064,8 +4060,6 @@ static int checkout(struct merge_options *opt, ret = unpack_trees(2, trees, &unpack_opts); clear_unpack_trees_porcelain(&unpack_opts); - dir_clear(unpack_opts.dir); - FREE_AND_NULL(unpack_opts.dir); return ret; } @@ -4103,7 +4097,7 @@ static int record_conflicted_index_entries(struct merge_options *opt) state.istate = index; original_cache_nr = index->cache_nr; - /* Put every entry from paths into plist, then sort */ + /* Append every entry from conflicted into index, then sort */ strmap_for_each_entry(&opt->priv->conflicted, &iter, e) { const char *path = e->key; struct conflict_info *ci = e->value; diff --git a/merge-recursive.c b/merge-recursive.c index 80594153f1..d9457797db 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -417,8 +417,11 @@ static int unpack_trees_start(struct merge_options *opt, memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts)); if (opt->priv->call_depth) opt->priv->unpack_opts.index_only = 1; - else + else { opt->priv->unpack_opts.update = 1; + /* FIXME: should only do this if !overwrite_ignore */ + opt->priv->unpack_opts.preserve_ignored = 0; + } opt->priv->unpack_opts.merge = 1; opt->priv->unpack_opts.head_idx = 2; opt->priv->unpack_opts.fn = threeway_merge; @@ -53,7 +53,6 @@ int checkout_fast_forward(struct repository *r, struct unpack_trees_options opts; struct tree_desc t[MAX_UNPACK_TREES]; int i, nr_trees = 0; - struct dir_struct dir = DIR_INIT; struct lock_file lock_file = LOCK_INIT; refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); @@ -80,11 +79,7 @@ int checkout_fast_forward(struct repository *r, } memset(&opts, 0, sizeof(opts)); - if (overwrite_ignore) { - dir.flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(&dir); - opts.dir = &dir; - } + opts.preserve_ignored = !overwrite_ignore; opts.head_idx = 1; opts.src_index = r->index; @@ -101,7 +96,6 @@ int checkout_fast_forward(struct repository *r, clear_unpack_trees_porcelain(&opts); return -1; } - dir_clear(&dir); clear_unpack_trees_porcelain(&opts); if (write_locked_index(r->index, &lock_file, COMMIT_LOCK)) diff --git a/mergesort.c b/mergesort.c index e5fdf2ee4a..6216835566 100644 --- a/mergesort.c +++ b/mergesort.c @@ -1,73 +1,84 @@ #include "cache.h" #include "mergesort.h" -struct mergesort_sublist { - void *ptr; - unsigned long len; -}; - -static void *get_nth_next(void *list, unsigned long n, - void *(*get_next_fn)(const void *)) +/* Combine two sorted lists. Take from `list` on equality. */ +static void *llist_merge(void *list, void *other, + void *(*get_next_fn)(const void *), + void (*set_next_fn)(void *, void *), + int (*compare_fn)(const void *, const void *)) { - while (n-- && list) - list = get_next_fn(list); - return list; -} + void *result = list, *tail; -static void *pop_item(struct mergesort_sublist *l, - void *(*get_next_fn)(const void *)) -{ - void *p = l->ptr; - l->ptr = get_next_fn(l->ptr); - l->len = l->ptr ? (l->len - 1) : 0; - return p; + if (compare_fn(list, other) > 0) { + result = other; + goto other; + } + for (;;) { + do { + tail = list; + list = get_next_fn(list); + if (!list) { + set_next_fn(tail, other); + return result; + } + } while (compare_fn(list, other) <= 0); + set_next_fn(tail, other); + other: + do { + tail = other; + other = get_next_fn(other); + if (!other) { + set_next_fn(tail, list); + return result; + } + } while (compare_fn(list, other) > 0); + set_next_fn(tail, list); + } } +/* + * Perform an iterative mergesort using an array of sublists. + * + * n is the number of items. + * ranks[i] is undefined if n & 2^i == 0, and assumed empty. + * ranks[i] contains a sublist of length 2^i otherwise. + * + * The number of bits in a void pointer limits the number of objects + * that can be created, and thus the number of array elements necessary + * to be able to sort any valid list. + * + * Adding an item to this array is like incrementing a binary number; + * positional values for set bits correspond to sublist lengths. + */ void *llist_mergesort(void *list, void *(*get_next_fn)(const void *), void (*set_next_fn)(void *, void *), int (*compare_fn)(const void *, const void *)) { - unsigned long l; - - if (!list) - return NULL; - for (l = 1; ; l *= 2) { - void *curr; - struct mergesort_sublist p, q; + void *ranks[bitsizeof(void *)]; + size_t n = 0; + int i; - p.ptr = list; - q.ptr = get_nth_next(p.ptr, l, get_next_fn); - if (!q.ptr) - break; - p.len = q.len = l; + while (list) { + void *next = get_next_fn(list); + if (next) + set_next_fn(list, NULL); + for (i = 0; n & (1 << i); i++) + list = llist_merge(ranks[i], list, get_next_fn, + set_next_fn, compare_fn); + n++; + ranks[i] = list; + list = next; + } - if (compare_fn(p.ptr, q.ptr) > 0) - list = curr = pop_item(&q, get_next_fn); + for (i = 0; n; i++, n >>= 1) { + if (!(n & 1)) + continue; + if (list) + list = llist_merge(ranks[i], list, get_next_fn, + set_next_fn, compare_fn); else - list = curr = pop_item(&p, get_next_fn); - - while (p.ptr) { - while (p.len || q.len) { - void *prev = curr; - - if (!p.len) - curr = pop_item(&q, get_next_fn); - else if (!q.len) - curr = pop_item(&p, get_next_fn); - else if (compare_fn(p.ptr, q.ptr) > 0) - curr = pop_item(&q, get_next_fn); - else - curr = pop_item(&p, get_next_fn); - set_next_fn(prev, curr); - } - p.ptr = q.ptr; - p.len = l; - q.ptr = get_nth_next(p.ptr, l, get_next_fn); - q.len = q.ptr ? l : 0; - - } - set_next_fn(curr, NULL); + list = ranks[i]; } return list; } @@ -460,6 +460,8 @@ struct write_midx_context { uint32_t num_large_offsets; int preferred_pack_idx; + + struct string_list *to_include; }; static void add_pack_to_midx(const char *full_path, size_t full_path_len, @@ -469,8 +471,26 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, if (ends_with(file_name, ".idx")) { display_progress(ctx->progress, ++ctx->pack_paths_checked); + /* + * Note that at most one of ctx->m and ctx->to_include are set, + * so we are testing midx_contains_pack() and + * string_list_has_string() independently (guarded by the + * appropriate NULL checks). + * + * We could support passing to_include while reusing an existing + * MIDX, but don't currently since the reuse process drags + * forward all packs from an existing MIDX (without checking + * whether or not they appear in the to_include list). + * + * If we added support for that, these next two conditional + * should be performed independently (likely checking + * to_include before the existing MIDX). + */ if (ctx->m && midx_contains_pack(ctx->m, file_name)) return; + else if (ctx->to_include && + !string_list_has_string(ctx->to_include, file_name)) + return; ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); @@ -948,7 +968,43 @@ static void bitmap_show_commit(struct commit *commit, void *_data) data->commits[data->commits_nr++] = commit; } +static int read_refs_snapshot(const char *refs_snapshot, + struct rev_info *revs) +{ + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + FILE *f = xfopen(refs_snapshot, "r"); + + while (strbuf_getline(&buf, f) != EOF) { + struct object *object; + int preferred = 0; + char *hex = buf.buf; + const char *end = NULL; + + if (buf.len && *buf.buf == '+') { + preferred = 1; + hex = &buf.buf[1]; + } + + if (parse_oid_hex(hex, &oid, &end) < 0) + die(_("could not parse line: %s"), buf.buf); + if (*end) + die(_("malformed line: %s"), buf.buf); + + object = parse_object_or_die(&oid, NULL); + if (preferred) + object->flags |= NEEDS_BITMAP; + + add_pending_object(revs, object, ""); + } + + fclose(f); + strbuf_release(&buf); + return 0; +} + static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p, + const char *refs_snapshot, struct write_midx_context *ctx) { struct rev_info revs; @@ -957,8 +1013,12 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr cb.ctx = ctx; repo_init_revisions(the_repository, &revs, NULL); - setup_revisions(0, NULL, &revs, NULL); - for_each_ref(add_ref_to_pending, &revs); + if (refs_snapshot) { + read_refs_snapshot(refs_snapshot, &revs); + } else { + setup_revisions(0, NULL, &revs, NULL); + for_each_ref(add_ref_to_pending, &revs); + } /* * Skipping promisor objects here is intentional, since it only excludes @@ -987,18 +1047,23 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash, struct write_midx_context *ctx, + const char *refs_snapshot, unsigned flags) { struct packing_data pdata; struct pack_idx_entry **index; struct commit **commits = NULL; uint32_t i, commits_nr; + uint16_t options = 0; char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash)); int ret; + if (flags & MIDX_WRITE_BITMAP_HASH_CACHE) + options |= BITMAP_OPT_HASH_CACHE; + prepare_midx_packing_data(&pdata, ctx); - commits = find_commits_for_midx_bitmap(&commits_nr, ctx); + commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, ctx); /* * Build the MIDX-order index based on pdata.objects (which is already @@ -1034,7 +1099,7 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash, goto cleanup; bitmap_writer_set_checksum(midx_hash); - bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, 0); + bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, options); cleanup: free(index); @@ -1043,8 +1108,10 @@ cleanup: } static int write_midx_internal(const char *object_dir, + struct string_list *packs_to_include, struct string_list *packs_to_drop, const char *preferred_pack_name, + const char *refs_snapshot, unsigned flags) { char *midx_name; @@ -1067,10 +1134,17 @@ static int write_midx_internal(const char *object_dir, die_errno(_("unable to create leading directories of %s"), midx_name); - for (cur = get_multi_pack_index(the_repository); cur; cur = cur->next) { - if (!strcmp(object_dir, cur->object_dir)) { - ctx.m = cur; - break; + if (!packs_to_include) { + /* + * Only reference an existing MIDX when not filtering which + * packs to include, since all packs and objects are copied + * blindly from an existing MIDX if one is present. + */ + for (cur = get_multi_pack_index(the_repository); cur; cur = cur->next) { + if (!strcmp(object_dir, cur->object_dir)) { + ctx.m = cur; + break; + } } } @@ -1121,10 +1195,13 @@ static int write_midx_internal(const char *object_dir, else ctx.progress = NULL; + ctx.to_include = packs_to_include; + for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx); stop_progress(&ctx.progress); - if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop) { + if ((ctx.m && ctx.nr == ctx.m->num_packs) && + !(packs_to_include || packs_to_drop)) { struct bitmap_index *bitmap_git; int bitmap_exists; int want_bitmap = flags & MIDX_WRITE_BITMAP; @@ -1328,7 +1405,8 @@ static int write_midx_internal(const char *object_dir, if (flags & MIDX_WRITE_REV_INDEX) write_midx_reverse_index(midx_name, midx_hash, &ctx); if (flags & MIDX_WRITE_BITMAP) { - if (write_midx_bitmap(midx_name, midx_hash, &ctx, flags) < 0) { + if (write_midx_bitmap(midx_name, midx_hash, &ctx, + refs_snapshot, flags) < 0) { error(_("could not write multi-pack bitmap")); result = 1; goto cleanup; @@ -1363,9 +1441,21 @@ cleanup: int write_midx_file(const char *object_dir, const char *preferred_pack_name, + const char *refs_snapshot, unsigned flags) { - return write_midx_internal(object_dir, NULL, preferred_pack_name, flags); + return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name, + refs_snapshot, flags); +} + +int write_midx_file_only(const char *object_dir, + struct string_list *packs_to_include, + const char *preferred_pack_name, + const char *refs_snapshot, + unsigned flags) +{ + return write_midx_internal(object_dir, packs_to_include, NULL, + preferred_pack_name, refs_snapshot, flags); } struct clear_midx_data { @@ -1645,7 +1735,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla free(count); if (packs_to_drop.nr) { - result = write_midx_internal(object_dir, &packs_to_drop, NULL, flags); + result = write_midx_internal(object_dir, NULL, &packs_to_drop, NULL, NULL, flags); m = NULL; } @@ -1836,7 +1926,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, goto cleanup; } - result = write_midx_internal(object_dir, NULL, NULL, flags); + result = write_midx_internal(object_dir, NULL, NULL, NULL, NULL, flags); m = NULL; cleanup: @@ -2,6 +2,7 @@ #define MIDX_H #include "repository.h" +#include "string-list.h" struct object_id; struct pack_entry; @@ -44,6 +45,7 @@ struct multi_pack_index { #define MIDX_PROGRESS (1 << 0) #define MIDX_WRITE_REV_INDEX (1 << 1) #define MIDX_WRITE_BITMAP (1 << 2) +#define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3) const unsigned char *get_midx_checksum(struct multi_pack_index *m); char *get_midx_filename(const char *object_dir); @@ -61,7 +63,19 @@ int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pa int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local); -int write_midx_file(const char *object_dir, const char *preferred_pack_name, unsigned flags); +/* + * Variant of write_midx_file which writes a MIDX containing only the packs + * specified in packs_to_include. + */ +int write_midx_file(const char *object_dir, + const char *preferred_pack_name, + const char *refs_snapshot, + unsigned flags); +int write_midx_file_only(const char *object_dir, + struct string_list *packs_to_include, + const char *preferred_pack_name, + const char *refs_snapshot, + unsigned flags); void clear_midx_file(struct repository *r); int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags); int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags); diff --git a/object-file.c b/object-file.c index 0a1835fe30..b3e1718885 100644 --- a/object-file.c +++ b/object-file.c @@ -415,74 +415,6 @@ enum scld_error safe_create_leading_directories_const(const char *path) return result; } -int raceproof_create_file(const char *path, create_file_fn fn, void *cb) -{ - /* - * The number of times we will try to remove empty directories - * in the way of path. This is only 1 because if another - * process is racily creating directories that conflict with - * us, we don't want to fight against them. - */ - int remove_directories_remaining = 1; - - /* - * The number of times that we will try to create the - * directories containing path. We are willing to attempt this - * more than once, because another process could be trying to - * clean up empty directories at the same time as we are - * trying to create them. - */ - int create_directories_remaining = 3; - - /* A scratch copy of path, filled lazily if we need it: */ - struct strbuf path_copy = STRBUF_INIT; - - int ret, save_errno; - - /* Sanity check: */ - assert(*path); - -retry_fn: - ret = fn(path, cb); - save_errno = errno; - if (!ret) - goto out; - - if (errno == EISDIR && remove_directories_remaining-- > 0) { - /* - * A directory is in the way. Maybe it is empty; try - * to remove it: - */ - if (!path_copy.len) - strbuf_addstr(&path_copy, path); - - if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) - goto retry_fn; - } else if (errno == ENOENT && create_directories_remaining-- > 0) { - /* - * Maybe the containing directory didn't exist, or - * maybe it was just deleted by a process that is - * racing with us to clean up empty directories. Try - * to create it: - */ - enum scld_error scld_result; - - if (!path_copy.len) - strbuf_addstr(&path_copy, path); - - do { - scld_result = safe_create_leading_directories(path_copy.buf); - if (scld_result == SCLD_OK) - goto retry_fn; - } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); - } - -out: - strbuf_release(&path_copy); - errno = save_errno; - return ret; -} - static void fill_loose_path(struct strbuf *buf, const struct object_id *oid) { int i; @@ -1084,9 +1016,11 @@ void *xmmap(void *start, size_t length, * the streaming interface and rehash it to do the same. */ int check_object_signature(struct repository *r, const struct object_id *oid, - void *map, unsigned long size, const char *type) + void *map, unsigned long size, const char *type, + struct object_id *real_oidp) { - struct object_id real_oid; + struct object_id tmp; + struct object_id *real_oid = real_oidp ? real_oidp : &tmp; enum object_type obj_type; struct git_istream *st; git_hash_ctx c; @@ -1094,8 +1028,8 @@ int check_object_signature(struct repository *r, const struct object_id *oid, int hdrlen; if (map) { - hash_object_file(r->hash_algo, map, size, type, &real_oid); - return !oideq(oid, &real_oid) ? -1 : 0; + hash_object_file(r->hash_algo, map, size, type, real_oid); + return !oideq(oid, real_oid) ? -1 : 0; } st = open_istream(r, oid, &obj_type, &size, NULL); @@ -1120,9 +1054,9 @@ int check_object_signature(struct repository *r, const struct object_id *oid, break; r->hash_algo->update_fn(&c, buf, readlen); } - r->hash_algo->final_oid_fn(&real_oid, &c); + r->hash_algo->final_oid_fn(real_oid, &c); close_istream(st); - return !oideq(oid, &real_oid) ? -1 : 0; + return !oideq(oid, real_oid) ? -1 : 0; } int git_open_cloexec(const char *name, int flags) @@ -1255,11 +1189,14 @@ void *map_loose_object(struct repository *r, return map_loose_object_1(r, NULL, oid, size); } -static int unpack_loose_short_header(git_zstream *stream, - unsigned char *map, unsigned long mapsize, - void *buffer, unsigned long bufsiz) +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, + unsigned char *map, + unsigned long mapsize, + void *buffer, + unsigned long bufsiz, + struct strbuf *header) { - int ret; + int status; /* Get the data stream */ memset(stream, 0, sizeof(*stream)); @@ -1270,43 +1207,24 @@ static int unpack_loose_short_header(git_zstream *stream, git_inflate_init(stream); obj_read_unlock(); - ret = git_inflate(stream, 0); + status = git_inflate(stream, 0); obj_read_lock(); - - return ret; -} - -int unpack_loose_header(git_zstream *stream, - unsigned char *map, unsigned long mapsize, - void *buffer, unsigned long bufsiz) -{ - int status = unpack_loose_short_header(stream, map, mapsize, - buffer, bufsiz); - if (status < Z_OK) - return status; - - /* Make sure we have the terminating NUL */ - if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer)) - return -1; - return 0; -} - -static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map, - unsigned long mapsize, void *buffer, - unsigned long bufsiz, struct strbuf *header) -{ - int status; - - status = unpack_loose_short_header(stream, map, mapsize, buffer, bufsiz); - if (status < Z_OK) - return -1; + return ULHR_BAD; /* * Check if entire header is unpacked in the first iteration. */ if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer)) - return 0; + return ULHR_OK; + + /* + * We have a header longer than MAX_HEADER_LEN. The "header" + * here is only non-NULL when we run "cat-file + * --allow-unknown-type". + */ + if (!header) + return ULHR_TOO_LONG; /* * buffer[0..bufsiz] was not large enough. Copy the partial @@ -1327,7 +1245,7 @@ static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map stream->next_out = buffer; stream->avail_out = bufsiz; } while (status != Z_STREAM_END); - return -1; + return ULHR_TOO_LONG; } static void *unpack_loose_rest(git_zstream *stream, @@ -1385,8 +1303,7 @@ static void *unpack_loose_rest(git_zstream *stream, * too permissive for what we want to check. So do an anal * object header parse by hand. */ -static int parse_loose_header_extended(const char *hdr, struct object_info *oi, - unsigned int flags) +int parse_loose_header(const char *hdr, struct object_info *oi) { const char *type_buf = hdr; unsigned long size; @@ -1408,15 +1325,6 @@ static int parse_loose_header_extended(const char *hdr, struct object_info *oi, type = type_from_string_gently(type_buf, type_len, 1); if (oi->type_name) strbuf_add(oi->type_name, type_buf, type_len); - /* - * Set type to 0 if its an unknown object and - * we're obtaining the type using '--allow-unknown-type' - * option. - */ - if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE) && (type < 0)) - type = 0; - else if (type < 0) - die(_("invalid object type")); if (oi->typep) *oi->typep = type; @@ -1443,15 +1351,14 @@ static int parse_loose_header_extended(const char *hdr, struct object_info *oi, /* * The length must be followed by a zero byte */ - return *hdr ? -1 : type; -} - -int parse_loose_header(const char *hdr, unsigned long *sizep) -{ - struct object_info oi = OBJECT_INFO_INIT; + if (*hdr) + return -1; - oi.sizep = sizep; - return parse_loose_header_extended(hdr, &oi, 0); + /* + * The format is valid, but the type may still be bogus. The + * Caller needs to check its oi->typep. + */ + return 0; } static int loose_object_info(struct repository *r, @@ -1465,6 +1372,8 @@ static int loose_object_info(struct repository *r, char hdr[MAX_HEADER_LEN]; struct strbuf hdrbuf = STRBUF_INIT; unsigned long size_scratch; + enum object_type type_scratch; + int allow_unknown = flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE; if (oi->delta_base_oid) oidclr(oi->delta_base_oid); @@ -1495,43 +1404,48 @@ static int loose_object_info(struct repository *r, if (!oi->sizep) oi->sizep = &size_scratch; + if (!oi->typep) + oi->typep = &type_scratch; if (oi->disk_sizep) *oi->disk_sizep = mapsize; - if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE)) { - if (unpack_loose_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0) - status = error(_("unable to unpack %s header with --allow-unknown-type"), - oid_to_hex(oid)); - } else if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) + + switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr), + allow_unknown ? &hdrbuf : NULL)) { + case ULHR_OK: + if (parse_loose_header(hdrbuf.len ? hdrbuf.buf : hdr, oi) < 0) + status = error(_("unable to parse %s header"), oid_to_hex(oid)); + else if (!allow_unknown && *oi->typep < 0) + die(_("invalid object type")); + + if (!oi->contentp) + break; + *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); + if (*oi->contentp) + goto cleanup; + + status = -1; + break; + case ULHR_BAD: status = error(_("unable to unpack %s header"), oid_to_hex(oid)); - if (status < 0) - ; /* Do nothing */ - else if (hdrbuf.len) { - if ((status = parse_loose_header_extended(hdrbuf.buf, oi, flags)) < 0) - status = error(_("unable to parse %s header with --allow-unknown-type"), - oid_to_hex(oid)); - } else if ((status = parse_loose_header_extended(hdr, oi, flags)) < 0) - status = error(_("unable to parse %s header"), oid_to_hex(oid)); - - if (status >= 0 && oi->contentp) { - *oi->contentp = unpack_loose_rest(&stream, hdr, - *oi->sizep, oid); - if (!*oi->contentp) { - git_inflate_end(&stream); - status = -1; - } - } else - git_inflate_end(&stream); + break; + case ULHR_TOO_LONG: + status = error(_("header for %s too long, exceeds %d bytes"), + oid_to_hex(oid), MAX_HEADER_LEN); + break; + } + git_inflate_end(&stream); +cleanup: munmap(map, mapsize); - if (status && oi->typep) - *oi->typep = status; if (oi->sizep == &size_scratch) oi->sizep = NULL; strbuf_release(&hdrbuf); + if (oi->typep == &type_scratch) + oi->typep = NULL; oi->whence = OI_LOOSE; - return (status < 0) ? status : 0; + return status; } int obj_read_use_lock = 0; @@ -2599,17 +2513,16 @@ static int check_stream_oid(git_zstream *stream, int read_loose_object(const char *path, const struct object_id *expected_oid, - enum object_type *type, - unsigned long *size, - void **contents) + struct object_id *real_oid, + void **contents, + struct object_info *oi) { int ret = -1; void *map = NULL; unsigned long mapsize; git_zstream stream; char hdr[MAX_HEADER_LEN]; - - *contents = NULL; + unsigned long *size = oi->sizep; map = map_loose_object_1(the_repository, path, NULL, &mapsize); if (!map) { @@ -2617,19 +2530,19 @@ int read_loose_object(const char *path, goto out; } - if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) { + if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr), + NULL) < 0) { error(_("unable to unpack header of %s"), path); goto out; } - *type = parse_loose_header(hdr, size); - if (*type < 0) { + if (parse_loose_header(hdr, oi) < 0) { error(_("unable to parse header of %s"), path); git_inflate_end(&stream); goto out; } - if (*type == OBJ_BLOB && *size > big_file_threshold) { + if (*oi->typep == OBJ_BLOB && *size > big_file_threshold) { if (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0) goto out; } else { @@ -2640,10 +2553,7 @@ int read_loose_object(const char *path, goto out; } if (check_object_signature(the_repository, expected_oid, - *contents, *size, - type_name(*type))) { - error(_("hash mismatch for %s (expected %s)"), path, - oid_to_hex(expected_oid)); + *contents, *size, oi->type_name->buf, real_oid)) { free(*contents); goto out; } diff --git a/object-store.h b/object-store.h index c5130d8bae..07387dc48f 100644 --- a/object-store.h +++ b/object-store.h @@ -245,6 +245,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime); /* * Open the loose object at path, check its hash, and return the contents, + * use the "oi" argument to assert things about the object, or e.g. populate its * type, and size. If the object is a blob, then "contents" may return NULL, * to allow streaming of large blobs. * @@ -252,9 +253,9 @@ int force_object_loose(const struct object_id *oid, time_t mtime); */ int read_loose_object(const char *path, const struct object_id *expected_oid, - enum object_type *type, - unsigned long *size, - void **contents); + struct object_id *real_oid, + void **contents, + struct object_info *oi); /* Retry packed storage after checking packed and loose storage */ #define HAS_OBJECT_RECHECK_PACKED 1 @@ -371,7 +372,7 @@ struct object_info { * Initializer for a "struct object_info" that wants no items. You may * also memset() the memory to all-zeroes. */ -#define OBJECT_INFO_INIT {NULL} +#define OBJECT_INFO_INIT { 0 } /* Invoke lookup_replace_object() on the given hash */ #define OBJECT_INFO_LOOKUP_REPLACE 1 @@ -279,7 +279,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) if ((obj && obj->type == OBJ_BLOB && repo_has_object_file(r, oid)) || (!obj && repo_has_object_file(r, oid) && oid_object_info(r, oid, NULL) == OBJ_BLOB)) { - if (check_object_signature(r, repl, NULL, 0, NULL) < 0) { + if (check_object_signature(r, repl, NULL, 0, NULL, NULL) < 0) { error(_("hash mismatch %s"), oid_to_hex(oid)); return NULL; } @@ -290,7 +290,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) buffer = repo_read_object_file(r, oid, &type, &size); if (buffer) { if (check_object_signature(r, repl, buffer, size, - type_name(type)) < 0) { + type_name(type), NULL) < 0) { free(buffer); error(_("hash mismatch %s"), oid_to_hex(repl)); return NULL; @@ -55,7 +55,7 @@ struct object_array { } *objects; }; -#define OBJECT_ARRAY_INIT { 0, 0, NULL } +#define OBJECT_ARRAY_INIT { 0 } /* * object flag allocation: diff --git a/oid-array.h b/oid-array.h index 72bca78b7d..f60f9af674 100644 --- a/oid-array.h +++ b/oid-array.h @@ -56,7 +56,7 @@ struct oid_array { int sorted; }; -#define OID_ARRAY_INIT { NULL, 0, 0, 0 } +#define OID_ARRAY_INIT { 0 } /** * Add an item to the set. The object ID will be placed at the end of the array diff --git a/pack-bitmap.c b/pack-bitmap.c index 8504110a4d..f47a0a7db4 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -1418,7 +1418,7 @@ static int try_partial_reuse(struct packed_git *pack, return 0; } -static uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git) +uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git) { struct multi_pack_index *m = bitmap_git->midx; if (!m) @@ -1742,6 +1742,33 @@ int test_bitmap_commits(struct repository *r) return 0; } +int test_bitmap_hashes(struct repository *r) +{ + struct bitmap_index *bitmap_git = prepare_bitmap_git(r); + struct object_id oid; + uint32_t i, index_pos; + + if (!bitmap_git->hashes) + goto cleanup; + + for (i = 0; i < bitmap_num_objects(bitmap_git); i++) { + if (bitmap_is_midx(bitmap_git)) + index_pos = pack_pos_to_midx(bitmap_git->midx, i); + else + index_pos = pack_pos_to_index(bitmap_git->pack, i); + + nth_bitmap_object_oid(bitmap_git, &oid, index_pos); + + printf("%s %"PRIu32"\n", + oid_to_hex(&oid), get_be32(bitmap_git->hashes + index_pos)); + } + +cleanup: + free_bitmap_index(bitmap_git); + + return 0; +} + int rebuild_bitmap(const uint32_t *reposition, struct ewah_bitmap *source, struct bitmap *dest) @@ -1791,18 +1818,20 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git, for (i = 0; i < num_objects; ++i) { struct object_id oid; struct object_entry *oe; + uint32_t index_pos; if (bitmap_is_midx(bitmap_git)) - nth_midxed_object_oid(&oid, - bitmap_git->midx, - pack_pos_to_midx(bitmap_git->midx, i)); + index_pos = pack_pos_to_midx(bitmap_git->midx, i); else - nth_packed_object_id(&oid, bitmap_git->pack, - pack_pos_to_index(bitmap_git->pack, i)); + index_pos = pack_pos_to_index(bitmap_git->pack, i); + nth_bitmap_object_oid(bitmap_git, &oid, index_pos); oe = packlist_find(mapping, &oid); - if (oe) + if (oe) { reposition[i] = oe_in_pack_pos(mapping, oe) + 1; + if (bitmap_git->hashes && !oe->hash) + oe->hash = get_be32(bitmap_git->hashes + index_pos); + } } return reposition; diff --git a/pack-bitmap.h b/pack-bitmap.h index 469090bad2..19a63fa1ab 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -52,9 +52,11 @@ void traverse_bitmap_commit_list(struct bitmap_index *, show_reachable_fn show_reachable); void test_bitmap_walk(struct rev_info *revs); int test_bitmap_commits(struct repository *r); +int test_bitmap_hashes(struct repository *r); struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, struct list_objects_filter_options *filter, int filter_provided_objects); +uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git); int reuse_partial_packfile_from_bitmap(struct bitmap_index *, struct packed_git **packfile, uint32_t *entries, diff --git a/pack-check.c b/pack-check.c index c8e560d71a..3f418e3a6a 100644 --- a/pack-check.c +++ b/pack-check.c @@ -142,7 +142,8 @@ static int verify_packfile(struct repository *r, err = error("cannot unpack %s from %s at offset %"PRIuMAX"", oid_to_hex(&oid), p->pack_name, (uintmax_t)entries[i].offset); - else if (check_object_signature(r, &oid, data, size, type_name(type))) + else if (check_object_signature(r, &oid, data, size, + type_name(type), NULL)) err = error("packed %s from %s is corrupt", oid_to_hex(&oid), p->pack_name); else if (fn) { diff --git a/packfile.c b/packfile.c index 0e92bd7bd2..89402cfc69 100644 --- a/packfile.c +++ b/packfile.c @@ -339,6 +339,7 @@ void close_pack(struct packed_git *p) close_pack_fd(p); close_pack_index(p); close_pack_revindex(p); + oidset_clear(&p->bad_objects); } void close_object_store(struct raw_object_store *o) diff --git a/parse-options.c b/parse-options.c index 55c5821b08..6e0535bdaa 100644 --- a/parse-options.c +++ b/parse-options.c @@ -904,25 +904,77 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, FILE *outfile = err ? stderr : stdout; int need_newline; + const char *usage_prefix = _("usage: %s"); + /* + * The translation could be anything, but we can count on + * msgfmt(1)'s --check option to have asserted that "%s" is in + * the translation. So compute the length of the "usage: " + * part. We are assuming that the translator wasn't overly + * clever and used e.g. "%1$s" instead of "%s", there's only + * one "%s" in "usage_prefix" above, so there's no reason to + * do so even with a RTL language. + */ + size_t usage_len = strlen(usage_prefix) - strlen("%s"); + /* + * TRANSLATORS: the colon here should align with the + * one in "usage: %s" translation. + */ + const char *or_prefix = _(" or: %s"); + /* + * TRANSLATORS: You should only need to translate this format + * string if your language is a RTL language (e.g. Arabic, + * Hebrew etc.), not if it's a LTR language (e.g. German, + * Russian, Chinese etc.). + * + * When a translated usage string has an embedded "\n" it's + * because options have wrapped to the next line. The line + * after the "\n" will then be padded to align with the + * command name, such as N_("git cmd [opt]\n<8 + * spaces>[opt2]"), where the 8 spaces are the same length as + * "git cmd ". + * + * This format string prints out that already-translated + * line. The "%*s" is whitespace padding to account for the + * padding at the start of the line that we add in this + * function. The "%s" is a line in the (hopefully already + * translated) N_() usage string, which contained embedded + * newlines before we split it up. + */ + const char *usage_continued = _("%*s%s"); + const char *prefix = usage_prefix; + int saw_empty_line = 0; + if (!usagestr) return PARSE_OPT_HELP; if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL) fprintf(outfile, "cat <<\\EOF\n"); - fprintf_ln(outfile, _("usage: %s"), _(*usagestr++)); - while (*usagestr && **usagestr) - /* - * TRANSLATORS: the colon here should align with the - * one in "usage: %s" translation. - */ - fprintf_ln(outfile, _(" or: %s"), _(*usagestr++)); while (*usagestr) { - if (**usagestr) - fprintf_ln(outfile, _(" %s"), _(*usagestr)); - else - fputc('\n', outfile); - usagestr++; + const char *str = _(*usagestr++); + struct string_list list = STRING_LIST_INIT_DUP; + unsigned int j; + + if (!saw_empty_line && !*str) + saw_empty_line = 1; + + string_list_split(&list, str, '\n', -1); + for (j = 0; j < list.nr; j++) { + const char *line = list.items[j].string; + + if (saw_empty_line && *line) + fprintf_ln(outfile, _(" %s"), line); + else if (saw_empty_line) + fputc('\n', outfile); + else if (!j) + fprintf_ln(outfile, prefix, line); + else + fprintf_ln(outfile, usage_continued, + (int)usage_len, "", line); + } + string_list_clear(&list, 0); + + prefix = or_prefix; } need_newline = 1; diff --git a/parse-options.h b/parse-options.h index 39d9088254..13405472ee 100644 --- a/parse-options.h +++ b/parse-options.h @@ -166,8 +166,10 @@ struct option { #define OPT_BOOL(s, l, v, h) OPT_BOOL_F(s, l, v, h, 0) #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1} -#define OPT_CMDMODE(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, \ - (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) } +#define OPT_CMDMODE_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ + (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), NULL, (i) } +#define OPT_CMDMODE(s, l, v, h, i) OPT_CMDMODE_F(s, l, v, h, i, 0) + #define OPT_INTEGER(s, l, v, h) OPT_INTEGER_F(s, l, v, h, 0) #define OPT_MAGNITUDE(s, l, v, h) { OPTION_MAGNITUDE, (s), (l), (v), \ N_("n"), (h), PARSE_OPT_NONEG } @@ -181,10 +181,7 @@ struct path_cache { const char *shallow; }; -#define PATH_CACHE_INIT \ - { \ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \ - } +#define PATH_CACHE_INIT { 0 } const char *git_path_squash_msg(struct repository *r); const char *git_path_merge_msg(struct repository *r); diff --git a/pathspec.c b/pathspec.c index 44306fdaca..ddeeba7911 100644 --- a/pathspec.c +++ b/pathspec.c @@ -39,7 +39,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, return; for (i = 0; i < istate->cache_nr; i++) { const struct cache_entry *ce = istate->cache[i]; - if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce)) + if (sw_action == PS_IGNORE_SKIP_WORKTREE && + (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))) continue; ce_path_match(istate, ce, pathspec, seen); } @@ -70,7 +71,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec) for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; - if (ce_skip_worktree(ce)) + if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)) ce_path_match(istate, ce, pathspec, seen); } diff --git a/read-cache.c b/read-cache.c index f5d4385c40..8a50ff66b3 100644 --- a/read-cache.c +++ b/read-cache.c @@ -849,6 +849,19 @@ struct cache_entry *make_empty_transient_cache_entry(size_t len, return xcalloc(1, cache_entry_size(len)); } +enum verify_path_result { + PATH_OK, + PATH_INVALID, + PATH_DIR_WITH_SEP, +}; + +static enum verify_path_result verify_path_internal(const char *, unsigned); + +int verify_path(const char *path, unsigned mode) +{ + return verify_path_internal(path, mode) == PATH_OK; +} + struct cache_entry *make_cache_entry(struct index_state *istate, unsigned int mode, const struct object_id *oid, @@ -859,7 +872,7 @@ struct cache_entry *make_cache_entry(struct index_state *istate, struct cache_entry *ce, *ret; int len; - if (!verify_path(path, mode)) { + if (verify_path_internal(path, mode) == PATH_INVALID) { error(_("invalid path '%s'"), path); return NULL; } @@ -993,60 +1006,62 @@ static int verify_dotfile(const char *rest, unsigned mode) return 1; } -int verify_path(const char *path, unsigned mode) +static enum verify_path_result verify_path_internal(const char *path, + unsigned mode) { char c = 0; if (has_dos_drive_prefix(path)) - return 0; + return PATH_INVALID; if (!is_valid_path(path)) - return 0; + return PATH_INVALID; goto inside; for (;;) { if (!c) - return 1; + return PATH_OK; if (is_dir_sep(c)) { inside: if (protect_hfs) { if (is_hfs_dotgit(path)) - return 0; + return PATH_INVALID; if (S_ISLNK(mode)) { if (is_hfs_dotgitmodules(path)) - return 0; + return PATH_INVALID; } } if (protect_ntfs) { #if defined GIT_WINDOWS_NATIVE || defined __CYGWIN__ if (c == '\\') - return 0; + return PATH_INVALID; #endif if (is_ntfs_dotgit(path)) - return 0; + return PATH_INVALID; if (S_ISLNK(mode)) { if (is_ntfs_dotgitmodules(path)) - return 0; + return PATH_INVALID; } } c = *path++; if ((c == '.' && !verify_dotfile(path, mode)) || is_dir_sep(c)) - return 0; + return PATH_INVALID; /* * allow terminating directory separators for * sparse directory entries. */ if (c == '\0') - return S_ISDIR(mode); + return S_ISDIR(mode) ? PATH_DIR_WITH_SEP : + PATH_INVALID; } else if (c == '\\' && protect_ntfs) { if (is_ntfs_dotgit(path)) - return 0; + return PATH_INVALID; if (S_ISLNK(mode)) { if (is_ntfs_dotgitmodules(path)) - return 0; + return PATH_INVALID; } } @@ -1349,7 +1364,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e if (!ok_to_add) return -1; - if (!verify_path(ce->name, ce->ce_mode)) + if (verify_path_internal(ce->name, ce->ce_mode) == PATH_INVALID) return error(_("invalid path '%s'"), ce->name); if (!skip_df_check && @@ -1944,13 +1959,22 @@ static void tweak_untracked_cache(struct index_state *istate) prepare_repo_settings(r); - if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE) { + switch (r->settings.core_untracked_cache) { + case UNTRACKED_CACHE_REMOVE: remove_untracked_cache(istate); - return; - } - - if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE) + break; + case UNTRACKED_CACHE_WRITE: add_untracked_cache(istate); + break; + case UNTRACKED_CACHE_KEEP: + /* + * Either an explicit "core.untrackedCache=keep", the + * default if "core.untrackedCache" isn't configured, + * or a fallback on an unknown "core.untrackedCache" + * value. + */ + break; + } } static void tweak_split_index(struct index_state *istate) @@ -2391,9 +2415,21 @@ int read_index_from(struct index_state *istate, const char *path, base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex); trace2_region_enter_printf("index", "shared/do_read_index", the_repository, "%s", base_path); - ret = do_read_index(split_index->base, base_path, 1); + ret = do_read_index(split_index->base, base_path, 0); trace2_region_leave_printf("index", "shared/do_read_index", the_repository, "%s", base_path); + if (!ret) { + char *path_copy = xstrdup(path); + const char *base_path2 = xstrfmt("%s/sharedindex.%s", + dirname(path_copy), + base_oid_hex); + free(path_copy); + trace2_region_enter_printf("index", "shared/do_read_index", + the_repository, "%s", base_path2); + ret = do_read_index(split_index->base, base_path2, 1); + trace2_region_leave_printf("index", "shared/do_read_index", + the_repository, "%s", base_path2); + } if (!oideq(&split_index->base_oid, &split_index->base->oid)) die(_("broken index, expect %s in %s, got %s"), base_oid_hex, base_path, @@ -2812,11 +2848,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, } } - if (!istate->version) { + if (!istate->version) istate->version = get_index_format_default(the_repository); - if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) - init_split_index(istate); - } /* demote version 3 to version 2 when the latter suffices */ if (istate->version == 3 || istate->version == 2) @@ -3243,7 +3276,7 @@ static int too_many_not_shared_entries(struct index_state *istate) int write_locked_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { - int new_shared_index, ret; + int new_shared_index, ret, test_split_index_env; struct split_index *si = istate->split_index; if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0)) @@ -3258,7 +3291,10 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, if (istate->fsmonitor_last_update) fill_fsmonitor_bitmap(istate); - if (!si || alternate_index_output || + test_split_index_env = git_env_bool("GIT_TEST_SPLIT_INDEX", 0); + + if ((!si && !test_split_index_env) || + alternate_index_output || (istate->cache_changed & ~EXTMASK)) { if (si) oidclr(&si->base_oid); @@ -3266,10 +3302,15 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, goto out; } - if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) { - int v = si->base_oid.hash[0]; - if ((v & 15) < 6) + if (test_split_index_env) { + if (!si) { + si = init_split_index(istate); istate->cache_changed |= SPLIT_INDEX_ORDERED; + } else { + int v = si->base_oid.hash[0]; + if ((v & 15) < 6) + istate->cache_changed |= SPLIT_INDEX_ORDERED; + } } if (too_many_not_shared_entries(istate)) istate->cache_changed |= SPLIT_INDEX_ORDERED; diff --git a/rebase-interactive.c b/rebase-interactive.c index b6cbd16a17..87649d0c01 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -226,32 +226,3 @@ int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_ todo_list_release(&backup); return res; } - -int check_todo_list_from_file(struct repository *r) -{ - struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT; - int res = 0; - - if (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) { - res = error(_("could not read '%s'."), rebase_path_todo()); - goto out; - } - - if (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) { - res = error(_("could not read '%s'."), rebase_path_todo_backup()); - goto out; - } - - res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo); - if (!res) - res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo); - if (res) - fprintf(stderr, _(edit_todo_list_advice)); - if (!res) - res = todo_list_check(&old_todo, &new_todo); -out: - todo_list_release(&old_todo); - todo_list_release(&new_todo); - - return res; -} diff --git a/rebase-interactive.h b/rebase-interactive.h index dc2cf0ee12..7239c60f79 100644 --- a/rebase-interactive.h +++ b/rebase-interactive.h @@ -16,6 +16,4 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo); int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list); -int check_todo_list_from_file(struct repository *r); - #endif @@ -1,5 +1,6 @@ #include "rebase.h" #include "config.h" +#include "gettext.h" /* * Parses textual value for pull.rebase, branch.<name>.rebase, etc. @@ -20,12 +21,12 @@ enum rebase_type rebase_parse_value(const char *value) return REBASE_FALSE; else if (v > 0) return REBASE_TRUE; - else if (!strcmp(value, "preserve") || !strcmp(value, "p")) - return REBASE_PRESERVE; else if (!strcmp(value, "merges") || !strcmp(value, "m")) return REBASE_MERGES; else if (!strcmp(value, "interactive") || !strcmp(value, "i")) return REBASE_INTERACTIVE; + else if (!strcmp(value, "preserve") || !strcmp(value, "p")) + error(_("%s: 'preserve' superseded by 'merges'"), value); /* * Please update _git_config() in git-completion.bash when you * add new rebase modes. @@ -5,7 +5,6 @@ enum rebase_type { REBASE_INVALID = -1, REBASE_FALSE = 0, REBASE_TRUE, - REBASE_PRESERVE, REBASE_MERGES, REBASE_INTERACTIVE }; diff --git a/ref-filter.c b/ref-filter.c index e15f0c4bac..add429be79 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -633,7 +633,7 @@ static struct { */ }; -#define REF_FORMATTING_STATE_INIT { 0, NULL } +#define REF_FORMATTING_STATE_INIT { 0 } struct ref_formatting_stack { struct ref_formatting_stack *prev; diff --git a/reflog-walk.c b/reflog-walk.c index e9cd328369..8ac4b284b6 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -158,10 +158,9 @@ int add_reflog_for_walk(struct reflog_walk_info *info, } reflogs = read_complete_reflog(branch); if (!reflogs || reflogs->nr == 0) { - struct object_id oid; char *b; int ret = dwim_log(branch, strlen(branch), - &oid, &b); + NULL, &b); if (ret > 1) free(b); else if (ret == 1) { @@ -10,6 +10,7 @@ #include "refs.h" #include "refs/refs-internal.h" #include "run-command.h" +#include "hook.h" #include "object-store.h" #include "object.h" #include "tag.h" @@ -33,11 +34,6 @@ static struct ref_storage_be *find_ref_storage_backend(const char *name) return NULL; } -int ref_storage_backend_exists(const char *name) -{ - return find_ref_storage_backend(name) != NULL; -} - /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -699,7 +695,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len, strbuf_addf(&path, *p, len, str); ref = refs_resolve_ref_unsafe(refs, path.buf, RESOLVE_REF_READING, - &hash, NULL); + oid ? &hash : NULL, NULL); if (!ref) continue; if (refs_reflog_exists(refs, path.buf)) @@ -711,7 +707,8 @@ int repo_dwim_log(struct repository *r, const char *str, int len, continue; if (!logs_found++) { *log = xstrdup(it); - oidcpy(oid, &hash); + if (oid) + oidcpy(oid, &hash); } if (!warn_ambiguous_refs) break; @@ -1680,7 +1677,7 @@ int refs_read_raw_ref(struct ref_store *ref_store, } return ref_store->be->read_raw_ref(ref_store, refname, oid, referent, - type); + type, &errno); } /* This function needs to return a meaningful errno on failure */ @@ -2384,19 +2381,19 @@ int delete_reflog(const char *refname) } int refs_reflog_expire(struct ref_store *refs, - const char *refname, const struct object_id *oid, + const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data) { - return refs->be->reflog_expire(refs, refname, oid, flags, + return refs->be->reflog_expire(refs, refname, flags, prepare_fn, should_prune_fn, cleanup_fn, policy_cb_data); } -int reflog_expire(const char *refname, const struct object_id *oid, +int reflog_expire(const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, @@ -2404,7 +2401,7 @@ int reflog_expire(const char *refname, const struct object_id *oid, void *policy_cb_data) { return refs_reflog_expire(get_main_ref_store(the_repository), - refname, oid, flags, + refname, flags, prepare_fn, should_prune_fn, cleanup_fn, policy_cb_data); } @@ -793,7 +793,7 @@ enum expire_reflog_flags { * expiration policy that is desired. * * reflog_expiry_prepare_fn -- Called once after the reference is - * locked. + * locked. Called with the OID of the locked reference. * * reflog_expiry_should_prune_fn -- Called once for each entry in the * existing reflog. It should return true iff that entry should be @@ -813,28 +813,25 @@ typedef int reflog_expiry_should_prune_fn(struct object_id *ooid, typedef void reflog_expiry_cleanup_fn(void *cb_data); /* - * Expire reflog entries for the specified reference. oid is the old - * value of the reference. flags is a combination of the constants in + * Expire reflog entries for the specified reference. + * flags is a combination of the constants in * enum expire_reflog_flags. The three function pointers are described * above. On success, return zero. */ int refs_reflog_expire(struct ref_store *refs, const char *refname, - const struct object_id *oid, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data); -int reflog_expire(const char *refname, const struct object_id *oid, +int reflog_expire(const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data); -int ref_storage_backend_exists(const char *name); - struct ref_store *get_main_ref_store(struct repository *r); /** diff --git a/refs/debug.c b/refs/debug.c index 1a7a9e11cf..8667c64023 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -239,15 +239,14 @@ debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix, static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname, struct object_id *oid, struct strbuf *referent, - unsigned int *type) + unsigned int *type, int *failure_errno) { struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; int res = 0; oidcpy(oid, null_oid()); - errno = 0; res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent, - type); + type, failure_errno); if (res == 0) { trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n", @@ -255,7 +254,7 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname, } else { trace_printf_key(&trace_refs, "read_raw_ref: %s: %d (errno %d)\n", refname, - res, errno); + res, *failure_errno); } return res; } @@ -365,8 +364,8 @@ struct debug_reflog_expiry_should_prune { }; static void debug_reflog_expiry_prepare(const char *refname, - const struct object_id *oid, - void *cb_data) + const struct object_id *oid, + void *cb_data) { struct debug_reflog_expiry_should_prune *prune = cb_data; trace_printf_key(&trace_refs, "reflog_expire_prepare: %s\n", refname); @@ -392,7 +391,7 @@ static void debug_reflog_expiry_cleanup(void *cb_data) } static int debug_reflog_expire(struct ref_store *ref_store, const char *refname, - const struct object_id *oid, unsigned int flags, + unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, @@ -405,7 +404,7 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname, .should_prune = should_prune_fn, .cb_data = policy_cb_data, }; - int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid, + int res = drefs->refs->be->reflog_expire(drefs->refs, refname, flags, &debug_reflog_expiry_prepare, &debug_reflog_expiry_should_prune_fn, &debug_reflog_expiry_cleanup, diff --git a/refs/files-backend.c b/refs/files-backend.c index 8ee6ac2103..151b0056fe 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -229,7 +229,7 @@ static void add_per_worktree_entries_to_dir(struct ref_dir *dir, const char *dir pos = search_ref_dir(dir, prefix, prefix_len); if (pos >= 0) continue; - child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1); + child_entry = create_dir_entry(dir->cache, prefix, prefix_len); add_entry_to_dir(dir, child_entry); } } @@ -280,7 +280,7 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, strbuf_addch(&refname, '/'); add_entry_to_dir(dir, create_dir_entry(dir->cache, refname.buf, - refname.len, 1)); + refname.len)); } else { if (!refs_resolve_ref_unsafe(&refs->base, refname.buf, @@ -338,14 +338,14 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs) * lazily): */ add_entry_to_dir(get_ref_dir(refs->loose->root), - create_dir_entry(refs->loose, "refs/", 5, 1)); + create_dir_entry(refs->loose, "refs/", 5)); } return refs->loose; } -static int files_read_raw_ref(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type) +static int files_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); @@ -356,7 +356,6 @@ static int files_read_raw_ref(struct ref_store *ref_store, struct stat st; int fd; int ret = -1; - int save_errno; int remaining_retries = 3; *type = 0; @@ -461,10 +460,9 @@ stat_ref: ret = parse_loose_ref_contents(buf, oid, referent, type); out: - save_errno = errno; + *failure_errno = errno; strbuf_release(&sb_path); strbuf_release(&sb_contents); - errno = save_errno; return ret; } @@ -533,7 +531,6 @@ static void unlock_ref(struct ref_lock *lock) static int lock_raw_ref(struct files_ref_store *refs, const char *refname, int mustexist, const struct string_list *extras, - const struct string_list *skip, struct ref_lock **lock_p, struct strbuf *referent, unsigned int *type, @@ -543,6 +540,7 @@ static int lock_raw_ref(struct files_ref_store *refs, struct strbuf ref_file = STRBUF_INIT; int attempts_remaining = 3; int ret = TRANSACTION_GENERIC_ERROR; + int failure_errno; assert(err); files_assert_main_repository(refs, "lock_raw_ref"); @@ -570,7 +568,7 @@ retry: * reason to expect this error to be transitory. */ if (refs_verify_refname_available(&refs->base, refname, - extras, skip, err)) { + extras, NULL, err)) { if (mustexist) { /* * To the user the relevant error is @@ -613,7 +611,9 @@ retry: if (hold_lock_file_for_update_timeout( &lock->lk, ref_file.buf, LOCK_NO_DEREF, get_files_ref_lock_timeout_ms()) < 0) { - if (errno == ENOENT && --attempts_remaining > 0) { + int myerr = errno; + errno = 0; + if (myerr == ENOENT && --attempts_remaining > 0) { /* * Maybe somebody just deleted one of the * directories leading to ref_file. Try @@ -621,7 +621,7 @@ retry: */ goto retry; } else { - unable_to_lock_message(ref_file.buf, errno, err); + unable_to_lock_message(ref_file.buf, myerr, err); goto error_return; } } @@ -631,9 +631,9 @@ retry: * fear that its value will change. */ - if (files_read_raw_ref(&refs->base, refname, - &lock->old_oid, referent, type)) { - if (errno == ENOENT) { + if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent, + type, &failure_errno)) { + if (failure_errno == ENOENT) { if (mustexist) { /* Garden variety missing reference. */ strbuf_addf(err, "unable to resolve reference '%s'", @@ -657,7 +657,7 @@ retry: * reference named "refs/foo/bar/baz". */ } - } else if (errno == EISDIR) { + } else if (failure_errno == EISDIR) { /* * There is a directory in the way. It might have * contained references that have been deleted. If @@ -675,7 +675,7 @@ retry: REMOVE_DIR_EMPTY_ONLY)) { if (refs_verify_refname_available( &refs->base, refname, - extras, skip, err)) { + extras, NULL, err)) { /* * The error message set by * verify_refname_available() is OK. @@ -695,13 +695,13 @@ retry: goto error_return; } } - } else if (errno == EINVAL && (*type & REF_ISBROKEN)) { + } else if (failure_errno == EINVAL && (*type & REF_ISBROKEN)) { strbuf_addf(err, "unable to resolve reference '%s': " "reference broken", refname); goto error_return; } else { strbuf_addf(err, "unable to resolve reference '%s': %s", - refname, strerror(errno)); + refname, strerror(failure_errno)); goto error_return; } @@ -712,7 +712,7 @@ retry: */ if (refs_verify_refname_available( refs->packed_ref_store, refname, - extras, skip, err)) + extras, NULL, err)) goto error_return; } @@ -864,39 +864,112 @@ static struct ref_iterator *files_ref_iterator_begin( } /* - * Verify that the reference locked by lock has the value old_oid - * (unless it is NULL). Fail if the reference doesn't exist and - * mustexist is set. Return 0 on success. On error, write an error - * message to err, set errno, and return a negative value. + * Callback function for raceproof_create_file(). This function is + * expected to do something that makes dirname(path) permanent despite + * the fact that other processes might be cleaning up empty + * directories at the same time. Usually it will create a file named + * path, but alternatively it could create another file in that + * directory, or even chdir() into that directory. The function should + * return 0 if the action was completed successfully. On error, it + * should return a nonzero result and set errno. + * raceproof_create_file() treats two errno values specially: + * + * - ENOENT -- dirname(path) does not exist. In this case, + * raceproof_create_file() tries creating dirname(path) + * (and any parent directories, if necessary) and calls + * the function again. + * + * - EISDIR -- the file already exists and is a directory. In this + * case, raceproof_create_file() removes the directory if + * it is empty (and recursively any empty directories that + * it contains) and calls the function again. + * + * Any other errno causes raceproof_create_file() to fail with the + * callback's return value and errno. + * + * Obviously, this function should be OK with being called again if it + * fails with ENOENT or EISDIR. In other scenarios it will not be + * called again. + */ +typedef int create_file_fn(const char *path, void *cb); + +/* + * Create a file in dirname(path) by calling fn, creating leading + * directories if necessary. Retry a few times in case we are racing + * with another process that is trying to clean up the directory that + * contains path. See the documentation for create_file_fn for more + * details. + * + * Return the value and set the errno that resulted from the most + * recent call of fn. fn is always called at least once, and will be + * called more than once if it returns ENOENT or EISDIR. */ -static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock, - const struct object_id *old_oid, int mustexist, - struct strbuf *err) +static int raceproof_create_file(const char *path, create_file_fn fn, void *cb) { - assert(err); + /* + * The number of times we will try to remove empty directories + * in the way of path. This is only 1 because if another + * process is racily creating directories that conflict with + * us, we don't want to fight against them. + */ + int remove_directories_remaining = 1; - if (refs_read_ref_full(ref_store, lock->ref_name, - mustexist ? RESOLVE_REF_READING : 0, - &lock->old_oid, NULL)) { - if (old_oid) { - int save_errno = errno; - strbuf_addf(err, "can't verify ref '%s'", lock->ref_name); - errno = save_errno; - return -1; - } else { - oidclr(&lock->old_oid); - return 0; - } - } - if (old_oid && !oideq(&lock->old_oid, old_oid)) { - strbuf_addf(err, "ref '%s' is at %s but expected %s", - lock->ref_name, - oid_to_hex(&lock->old_oid), - oid_to_hex(old_oid)); - errno = EBUSY; - return -1; + /* + * The number of times that we will try to create the + * directories containing path. We are willing to attempt this + * more than once, because another process could be trying to + * clean up empty directories at the same time as we are + * trying to create them. + */ + int create_directories_remaining = 3; + + /* A scratch copy of path, filled lazily if we need it: */ + struct strbuf path_copy = STRBUF_INIT; + + int ret, save_errno; + + /* Sanity check: */ + assert(*path); + +retry_fn: + ret = fn(path, cb); + save_errno = errno; + if (!ret) + goto out; + + if (errno == EISDIR && remove_directories_remaining-- > 0) { + /* + * A directory is in the way. Maybe it is empty; try + * to remove it: + */ + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) + goto retry_fn; + } else if (errno == ENOENT && create_directories_remaining-- > 0) { + /* + * Maybe the containing directory didn't exist, or + * maybe it was just deleted by a process that is + * racing with us to clean up empty directories. Try + * to create it: + */ + enum scld_error scld_result; + + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + do { + scld_result = safe_create_leading_directories(path_copy.buf); + if (scld_result == SCLD_OK) + goto retry_fn; + } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); } - return 0; + +out: + strbuf_release(&path_copy); + errno = save_errno; + return ret; } static int remove_empty_directories(struct strbuf *path) @@ -920,64 +993,27 @@ static int create_reflock(const char *path, void *cb) /* * Locks a ref returning the lock on success and NULL on failure. - * On failure errno is set to something meaningful. */ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, - const char *refname, - const struct object_id *old_oid, - const struct string_list *extras, - const struct string_list *skip, - unsigned int flags, int *type, + const char *refname, int *type, struct strbuf *err) { struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; - int last_errno = 0; - int mustexist = (old_oid && !is_null_oid(old_oid)); - int resolve_flags = RESOLVE_REF_NO_RECURSE; - int resolved; files_assert_main_repository(refs, "lock_ref_oid_basic"); assert(err); CALLOC_ARRAY(lock, 1); - if (mustexist) - resolve_flags |= RESOLVE_REF_READING; - if (flags & REF_DELETING) - resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; - files_ref_path(refs, &ref_file, refname); - resolved = !!refs_resolve_ref_unsafe(&refs->base, - refname, resolve_flags, - &lock->old_oid, type); - if (!resolved && errno == EISDIR) { - /* - * we are trying to lock foo but we used to - * have foo/bar which now does not exist; - * it is normal for the empty directory 'foo' - * to remain. - */ - if (remove_empty_directories(&ref_file)) { - last_errno = errno; - if (!refs_verify_refname_available( - &refs->base, - refname, extras, skip, err)) - strbuf_addf(err, "there are still refs under '%s'", - refname); - goto error_return; - } - resolved = !!refs_resolve_ref_unsafe(&refs->base, - refname, resolve_flags, - &lock->old_oid, type); - } - if (!resolved) { - last_errno = errno; - if (last_errno != ENOTDIR || - !refs_verify_refname_available(&refs->base, refname, - extras, skip, err)) + if (!refs_resolve_ref_unsafe(&refs->base, refname, + RESOLVE_REF_NO_RECURSE, + &lock->old_oid, type)) { + if (!refs_verify_refname_available(&refs->base, refname, + NULL, NULL, err)) strbuf_addf(err, "unable to resolve reference '%s': %s", - refname, strerror(last_errno)); + refname, strerror(errno)); goto error_return; } @@ -990,23 +1026,20 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, */ if (is_null_oid(&lock->old_oid) && refs_verify_refname_available(refs->packed_ref_store, refname, - extras, skip, err)) { - last_errno = ENOTDIR; + NULL, NULL, err)) goto error_return; - } lock->ref_name = xstrdup(refname); if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) { - last_errno = errno; unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } - if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) { - last_errno = errno; - goto error_return; - } + if (refs_read_ref_full(&refs->base, lock->ref_name, + 0, + &lock->old_oid, NULL)) + oidclr(&lock->old_oid); goto out; error_return: @@ -1015,7 +1048,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, out: strbuf_release(&ref_file); - errno = last_errno; return lock; } @@ -1426,8 +1458,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, logmoved = log; - lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL, - REF_NO_DEREF, NULL, &err); + lock = lock_ref_oid_basic(refs, newrefname, NULL, &err); if (!lock) { if (copy) error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf); @@ -1449,8 +1480,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, goto out; rollback: - lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL, - REF_NO_DEREF, NULL, &err); + lock = lock_ref_oid_basic(refs, oldrefname, NULL, &err); if (!lock) { error("unable to lock %s for rollback: %s", oldrefname, err.buf); strbuf_release(&err); @@ -1857,9 +1887,7 @@ static int files_create_symref(struct ref_store *ref_store, struct ref_lock *lock; int ret; - lock = lock_ref_oid_basic(refs, refname, NULL, - NULL, NULL, REF_NO_DEREF, NULL, - &err); + lock = lock_ref_oid_basic(refs, refname, NULL, &err); if (!lock) { error("%s", err.buf); strbuf_release(&err); @@ -2427,7 +2455,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, } ret = lock_raw_ref(refs, update->refname, mustexist, - affected_refnames, NULL, + affected_refnames, &lock, &referent, &update->type, err); if (ret) { @@ -3048,7 +3076,7 @@ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, } static int files_reflog_expire(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, + const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, @@ -3065,6 +3093,7 @@ static int files_reflog_expire(struct ref_store *ref_store, int status = 0; int type; struct strbuf err = STRBUF_INIT; + const struct object_id *oid; memset(&cb, 0, sizeof(cb)); cb.flags = flags; @@ -3076,14 +3105,26 @@ static int files_reflog_expire(struct ref_store *ref_store, * reference itself, plus we might need to update the * reference if --updateref was specified: */ - lock = lock_ref_oid_basic(refs, refname, oid, - NULL, NULL, REF_NO_DEREF, - &type, &err); + lock = lock_ref_oid_basic(refs, refname, &type, &err); if (!lock) { error("cannot lock ref '%s': %s", refname, err.buf); strbuf_release(&err); return -1; } + oid = &lock->old_oid; + + /* + * When refs are deleted, their reflog is deleted before the + * ref itself is deleted. This is because there is no separate + * lock for reflog; instead we take a lock on the ref with + * lock_ref_oid_basic(). + * + * If a race happens and the reflog doesn't exist after we've + * acquired the lock that's OK. We've got nothing more to do; + * We were asked to delete the reflog, but someone else + * deleted it! The caller doesn't care that we deleted it, + * just that it is deleted. So we can return successfully. + */ if (!refs_reflog_exists(ref_store, refname)) { unlock_ref(lock); return 0; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 2161218719..1c5211b03e 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -726,9 +726,9 @@ static struct snapshot *get_snapshot(struct packed_ref_store *refs) return refs->snapshot; } -static int packed_read_raw_ref(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type) +static int packed_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno) { struct packed_ref_store *refs = packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); @@ -741,7 +741,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store, if (!rec) { /* refname is not a packed reference. */ - errno = ENOENT; + *failure_errno = ENOENT; return -1; } @@ -1607,6 +1607,7 @@ static int packed_for_each_reflog_ent(struct ref_store *ref_store, const char *refname, each_reflog_ent_fn fn, void *cb_data) { + BUG("packed reference store does not support reflogs"); return 0; } @@ -1615,12 +1616,14 @@ static int packed_for_each_reflog_ent_reverse(struct ref_store *ref_store, each_reflog_ent_fn fn, void *cb_data) { + BUG("packed reference store does not support reflogs"); return 0; } static int packed_reflog_exists(struct ref_store *ref_store, const char *refname) { + BUG("packed reference store does not support reflogs"); return 0; } @@ -1634,17 +1637,19 @@ static int packed_create_reflog(struct ref_store *ref_store, static int packed_delete_reflog(struct ref_store *ref_store, const char *refname) { + BUG("packed reference store does not support reflogs"); return 0; } static int packed_reflog_expire(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, + const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data) { + BUG("packed reference store does not support reflogs"); return 0; } diff --git a/refs/ref-cache.c b/refs/ref-cache.c index 97a6ac349e..be4aa5e098 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -49,7 +49,7 @@ struct ref_cache *create_ref_cache(struct ref_store *refs, ret->ref_store = refs; ret->fill_ref_dir = fill_ref_dir; - ret->root = create_dir_entry(ret, "", 0, 1); + ret->root = create_dir_entry(ret, "", 0); return ret; } @@ -86,14 +86,13 @@ static void clear_ref_dir(struct ref_dir *dir) } struct ref_entry *create_dir_entry(struct ref_cache *cache, - const char *dirname, size_t len, - int incomplete) + const char *dirname, size_t len) { struct ref_entry *direntry; FLEX_ALLOC_MEM(direntry, name, dirname, len); direntry->u.subdir.cache = cache; - direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); + direntry->flag = REF_DIR | REF_INCOMPLETE; return direntry; } @@ -144,30 +143,19 @@ int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) /* * Search for a directory entry directly within dir (without * recursing). Sort dir if necessary. subdirname must be a directory - * name (i.e., end in '/'). If mkdir is set, then create the - * directory if it is missing; otherwise, return NULL if the desired + * name (i.e., end in '/'). Returns NULL if the desired * directory cannot be found. dir must already be complete. */ static struct ref_dir *search_for_subdir(struct ref_dir *dir, - const char *subdirname, size_t len, - int mkdir) + const char *subdirname, size_t len) { int entry_index = search_ref_dir(dir, subdirname, len); struct ref_entry *entry; - if (entry_index == -1) { - if (!mkdir) - return NULL; - /* - * Since dir is complete, the absence of a subdir - * means that the subdir really doesn't exist; - * therefore, create an empty record for it but mark - * the record complete. - */ - entry = create_dir_entry(dir->cache, subdirname, len, 0); - add_entry_to_dir(dir, entry); - } else { - entry = dir->entries[entry_index]; - } + + if (entry_index == -1) + return NULL; + + entry = dir->entries[entry_index]; return get_ref_dir(entry); } @@ -176,18 +164,17 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, * tree that should hold refname. If refname is a directory name * (i.e., it ends in '/'), then return that ref_dir itself. dir must * represent the top-level directory and must already be complete. - * Sort ref_dirs and recurse into subdirectories as necessary. If - * mkdir is set, then create any missing directories; otherwise, + * Sort ref_dirs and recurse into subdirectories as necessary. Will * return NULL if the desired directory cannot be found. */ static struct ref_dir *find_containing_dir(struct ref_dir *dir, - const char *refname, int mkdir) + const char *refname) { const char *slash; for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { size_t dirnamelen = slash - refname + 1; struct ref_dir *subdir; - subdir = search_for_subdir(dir, refname, dirnamelen, mkdir); + subdir = search_for_subdir(dir, refname, dirnamelen); if (!subdir) { dir = NULL; break; @@ -202,7 +189,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname) { int entry_index; struct ref_entry *entry; - dir = find_containing_dir(dir, refname, 0); + dir = find_containing_dir(dir, refname); if (!dir) return NULL; entry_index = search_ref_dir(dir, refname, strlen(refname)); @@ -212,50 +199,6 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname) return (entry->flag & REF_DIR) ? NULL : entry; } -int remove_entry_from_dir(struct ref_dir *dir, const char *refname) -{ - int refname_len = strlen(refname); - int entry_index; - struct ref_entry *entry; - int is_dir = refname[refname_len - 1] == '/'; - if (is_dir) { - /* - * refname represents a reference directory. Remove - * the trailing slash; otherwise we will get the - * directory *representing* refname rather than the - * one *containing* it. - */ - char *dirname = xmemdupz(refname, refname_len - 1); - dir = find_containing_dir(dir, dirname, 0); - free(dirname); - } else { - dir = find_containing_dir(dir, refname, 0); - } - if (!dir) - return -1; - entry_index = search_ref_dir(dir, refname, refname_len); - if (entry_index == -1) - return -1; - entry = dir->entries[entry_index]; - - MOVE_ARRAY(&dir->entries[entry_index], - &dir->entries[entry_index + 1], dir->nr - entry_index - 1); - dir->nr--; - if (dir->sorted > entry_index) - dir->sorted--; - free_ref_entry(entry); - return dir->nr; -} - -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref) -{ - dir = find_containing_dir(dir, ref->name, 1); - if (!dir) - return -1; - add_entry_to_dir(dir, ref); - return 0; -} - /* * Emit a warning and return true iff ref1 and ref2 have the same name * and the same oid. Die if they have the same name but different @@ -530,7 +473,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, dir = get_ref_dir(cache->root); if (prefix && *prefix) - dir = find_containing_dir(dir, prefix, 0); + dir = find_containing_dir(dir, prefix); if (!dir) /* There's nothing to iterate over. */ return empty_ref_iterator_begin(); diff --git a/refs/ref-cache.h b/refs/ref-cache.h index 7877bf86ed..850d9d3744 100644 --- a/refs/ref-cache.h +++ b/refs/ref-cache.h @@ -169,8 +169,7 @@ struct ref_dir *get_ref_dir(struct ref_entry *entry); * "refs/heads/") or "" for the top-level directory. */ struct ref_entry *create_dir_entry(struct ref_cache *cache, - const char *dirname, size_t len, - int incomplete); + const char *dirname, size_t len); struct ref_entry *create_ref_entry(const char *refname, const struct object_id *oid, int flag); @@ -200,29 +199,6 @@ void free_ref_cache(struct ref_cache *cache); void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); /* - * Remove the entry with the given name from dir, recursing into - * subdirectories as necessary. If refname is the name of a directory - * (i.e., ends with '/'), then remove the directory and its contents. - * If the removal was successful, return the number of entries - * remaining in the directory entry that contained the deleted entry. - * If the name was not found, return -1. Please note that this - * function only deletes the entry from the cache; it does not delete - * it from the filesystem or ensure that other cache entries (which - * might be symbolic references to the removed entry) are updated. - * Nor does it remove any containing dir entries that might be made - * empty by the removal. dir must represent the top-level directory - * and must already be complete. - */ -int remove_entry_from_dir(struct ref_dir *dir, const char *refname); - -/* - * Add a ref_entry to the ref_dir (unsorted), recursing into - * subdirectories as necessary. dir must represent the top-level - * directory. Return 0 on success. - */ -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref); - -/* * Find the value entry with the given name in dir, sorting ref_dirs * and recursing into subdirectories as necessary. If the name is not * found or it corresponds to a directory entry, return NULL. diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 500d77864d..12224742ed 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -609,7 +609,7 @@ typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname, int force_create, struct strbuf *err); typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname); typedef int reflog_expire_fn(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, + const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, @@ -636,11 +636,15 @@ typedef int reflog_expire_fn(struct ref_store *ref_store, * properly-formatted or even safe reference name. NEITHER INPUT NOR * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION. * - * Return 0 on success. If the ref doesn't exist, set errno to ENOENT - * and return -1. If the ref exists but is neither a symbolic ref nor - * an object ID, it is broken; set REF_ISBROKEN in type, set errno to - * EINVAL, and return -1. If there is another error reading the ref, - * set errno appropriately and return -1. + * Return 0 on success, or -1 on failure. If the ref exists but is neither a + * symbolic ref nor an object ID, it is broken. In this case set REF_ISBROKEN in + * type, and return -1 (failure_errno should not be ENOENT) + * + * failure_errno provides errno codes that are interpreted beyond error + * reporting. The following error codes have special meaning: + * * ENOENT: the ref doesn't exist + * * EISDIR: ref name is a directory + * * ENOTDIR: ref prefix is not a directory * * Backend-specific flags might be set in type as well, regardless of * outcome. @@ -654,9 +658,9 @@ typedef int reflog_expire_fn(struct ref_store *ref_store, * - in all other cases, referent will be untouched, and therefore * refname will still be valid and unchanged. */ -typedef int read_raw_ref_fn(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type); +typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno); struct ref_storage_be { struct ref_storage_be *next; diff --git a/remote-curl.c b/remote-curl.c index 598cff7cde..5975103b96 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -499,6 +499,10 @@ static struct discovery *discover_refs(const char *service, int for_push) show_http_message(&type, &charset, &buffer); die(_("Authentication failed for '%s'"), transport_anonymize_url(url.buf)); + case HTTP_NOMATCHPUBLICKEY: + show_http_message(&type, &charset, &buffer); + die(_("unable to access '%s' with http.pinnedPubkey configuration: %s"), + transport_anonymize_url(url.buf), curl_errorstr); default: show_http_message(&type, &charset, &buffer); die(_("unable to access '%s': %s"), @@ -1478,8 +1482,8 @@ int cmd_main(int argc, const char **argv) options.verbosity = 1; options.progress = !!isatty(2); options.thin = 1; - string_list_init(&options.deepen_not, 1); - string_list_init(&options.push_options, 1); + string_list_init_dup(&options.deepen_not); + string_list_init_dup(&options.push_options); /* * Just report "remote-curl" here (folding all the various aliases @@ -2403,7 +2403,7 @@ struct reflog_commit_array { size_t nr, alloc; }; -#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 } +#define REFLOG_COMMIT_ARRAY_INIT { 0 } /* Append a commit to the array. */ static void append_commit(struct reflog_commit_array *arr, diff --git a/repo-settings.c b/repo-settings.c index 0cfe8b787d..b93e91a212 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -3,40 +3,77 @@ #include "repository.h" #include "midx.h" -#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0) +static void repo_cfg_bool(struct repository *r, const char *key, int *dest, + int def) +{ + if (repo_config_get_bool(r, key, dest)) + *dest = def; +} void prepare_repo_settings(struct repository *r) { + int experimental; int value; char *strval; + int manyfiles; - if (r->settings.initialized) + if (r->settings.initialized++) return; /* Defaults */ - memset(&r->settings, -1, sizeof(r->settings)); + r->settings.index_version = -1; + r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP; + r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT; + + /* Booleans config or default, cascades to other settings */ + repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0); + repo_cfg_bool(r, "feature.experimental", &experimental, 0); + + /* Defaults modified by feature.* */ + if (experimental) { + r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING; + } + if (manyfiles) { + r->settings.index_version = 4; + r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE; + } - if (!repo_config_get_bool(r, "core.commitgraph", &value)) - r->settings.core_commit_graph = value; - if (!repo_config_get_bool(r, "commitgraph.readchangedpaths", &value)) - r->settings.commit_graph_read_changed_paths = value; - if (!repo_config_get_bool(r, "gc.writecommitgraph", &value)) - r->settings.gc_write_commit_graph = value; - UPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1); - UPDATE_DEFAULT_BOOL(r->settings.commit_graph_read_changed_paths, 1); - UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1); + /* Boolean config or default, does not cascade (simple) */ + repo_cfg_bool(r, "core.commitgraph", &r->settings.core_commit_graph, 1); + repo_cfg_bool(r, "commitgraph.readchangedpaths", &r->settings.commit_graph_read_changed_paths, 1); + repo_cfg_bool(r, "gc.writecommitgraph", &r->settings.gc_write_commit_graph, 1); + repo_cfg_bool(r, "fetch.writecommitgraph", &r->settings.fetch_write_commit_graph, 0); + repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1); + repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1); + repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0); + /* + * The GIT_TEST_MULTI_PACK_INDEX variable is special in that + * either it *or* the config sets + * r->settings.core_multi_pack_index if true. We don't take + * the environment variable if it exists (even if false) over + * any config, as in most other cases. + */ + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) + r->settings.core_multi_pack_index = 1; + + /* + * Non-boolean config + */ if (!repo_config_get_int(r, "index.version", &value)) r->settings.index_version = value; - if (!repo_config_get_maybe_bool(r, "core.untrackedcache", &value)) { - if (value == 0) - r->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE; - else - r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE; - } else if (!repo_config_get_string(r, "core.untrackedcache", &strval)) { - if (!strcasecmp(strval, "keep")) - r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP; + if (!repo_config_get_string(r, "core.untrackedcache", &strval)) { + int v = git_parse_maybe_bool(strval); + + /* + * If it's set to "keep", or some other non-boolean + * value then "v < 0". Then we do nothing and keep it + * at the default of UNTRACKED_CACHE_KEEP. + */ + if (v >= 0) + r->settings.core_untracked_cache = v ? + UNTRACKED_CACHE_WRITE : UNTRACKED_CACHE_REMOVE; free(strval); } @@ -45,39 +82,8 @@ void prepare_repo_settings(struct repository *r) r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING; else if (!strcasecmp(strval, "noop")) r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP; - else - r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT; } - if (!repo_config_get_bool(r, "pack.usesparse", &value)) - r->settings.pack_use_sparse = value; - UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1); - - value = git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0); - if (value || !repo_config_get_bool(r, "core.multipackindex", &value)) - r->settings.core_multi_pack_index = value; - UPDATE_DEFAULT_BOOL(r->settings.core_multi_pack_index, 1); - - if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) { - UPDATE_DEFAULT_BOOL(r->settings.index_version, 4); - UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE); - } - - if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value)) - r->settings.fetch_write_commit_graph = value; - UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 0); - - if (!repo_config_get_bool(r, "feature.experimental", &value) && value) - UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING); - - /* Hack for test programs like test-dump-untracked-cache */ - if (ignore_untracked_cache_config) - r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP; - else - UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP); - - UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT); - /* * This setting guards all index reads to require a full index * over a sparse index. After suitable guards are placed in the @@ -85,11 +91,4 @@ void prepare_repo_settings(struct repository *r) * removed. */ r->settings.command_requires_full_index = 1; - - /* - * Initialize this as off. - */ - r->settings.sparse_index = 0; - if (!repo_config_get_bool(r, "index.sparse", &value) && value) - r->settings.sparse_index = 1; } diff --git a/repository.h b/repository.h index c24e177c7e..a057653981 100644 --- a/repository.h +++ b/repository.h @@ -13,18 +13,15 @@ struct submodule_cache; struct promisor_remote_config; enum untracked_cache_setting { - UNTRACKED_CACHE_UNSET = -1, - UNTRACKED_CACHE_REMOVE = 0, - UNTRACKED_CACHE_KEEP = 1, - UNTRACKED_CACHE_WRITE = 2 + UNTRACKED_CACHE_KEEP, + UNTRACKED_CACHE_REMOVE, + UNTRACKED_CACHE_WRITE, }; enum fetch_negotiation_setting { - FETCH_NEGOTIATION_UNSET = -1, - FETCH_NEGOTIATION_NONE = 0, - FETCH_NEGOTIATION_DEFAULT = 1, - FETCH_NEGOTIATION_SKIPPING = 2, - FETCH_NEGOTIATION_NOOP = 3, + FETCH_NEGOTIATION_DEFAULT, + FETCH_NEGOTIATION_SKIPPING, + FETCH_NEGOTIATION_NOOP, }; struct repo_settings { @@ -34,6 +31,8 @@ struct repo_settings { int commit_graph_read_changed_paths; int gc_write_commit_graph; int fetch_write_commit_graph; + int command_requires_full_index; + int sparse_index; int index_version; enum untracked_cache_setting core_untracked_cache; @@ -42,9 +41,6 @@ struct repo_settings { enum fetch_negotiation_setting fetch_negotiation_algorithm; int core_multi_pack_index; - - unsigned command_requires_full_index:1, - sparse_index:1; }; struct repository { @@ -56,9 +56,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; unpack_tree_opts.update = 1; unpack_tree_opts.merge = 1; + unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL); if (!detach_head) - unpack_tree_opts.reset = 1; + unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; if (repo_read_index_unmerged(r) < 0) { ret = error(_("could not read index")); diff --git a/revision.c b/revision.c index 3ad217f2ff..ab7c135804 100644 --- a/revision.c +++ b/revision.c @@ -249,7 +249,7 @@ struct commit_stack { struct commit **items; size_t nr, alloc; }; -#define COMMIT_STACK_INIT { NULL, 0, 0 } +#define COMMIT_STACK_INIT { 0 } static void commit_stack_push(struct commit_stack *stack, struct commit *commit) { diff --git a/run-command.c b/run-command.c index 04a07e6366..7ef5cc712a 100644 --- a/run-command.c +++ b/run-command.c @@ -9,6 +9,7 @@ #include "quote.h" #include "config.h" #include "packfile.h" +#include "hook.h" void child_process_init(struct child_process *child) { @@ -1322,40 +1323,6 @@ int async_with_fork(void) #endif } -const char *find_hook(const char *name) -{ - static struct strbuf path = STRBUF_INIT; - - strbuf_reset(&path); - strbuf_git_path(&path, "hooks/%s", name); - if (access(path.buf, X_OK) < 0) { - int err = errno; - -#ifdef STRIP_EXTENSION - strbuf_addstr(&path, STRIP_EXTENSION); - if (access(path.buf, X_OK) >= 0) - return path.buf; - if (errno == EACCES) - err = errno; -#endif - - if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) { - static struct string_list advise_given = STRING_LIST_INIT_DUP; - - if (!string_list_lookup(&advise_given, name)) { - string_list_insert(&advise_given, name); - advise(_("The '%s' hook was ignored because " - "it's not set as executable.\n" - "You can disable this warning with " - "`git config advice.ignoredHook false`."), - path.buf); - } - } - return NULL; - } - return path.buf; -} - int run_hook_ve(const char *const *env, const char *name, va_list args) { struct child_process hook = CHILD_PROCESS_INIT; @@ -1907,3 +1874,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir) } strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir); } + +enum start_bg_result start_bg_command(struct child_process *cmd, + start_bg_wait_cb *wait_cb, + void *cb_data, + unsigned int timeout_sec) +{ + enum start_bg_result sbgr = SBGR_ERROR; + int ret; + int wait_status; + pid_t pid_seen; + time_t time_limit; + + /* + * We do not allow clean-on-exit because the child process + * should persist in the background and possibly/probably + * after this process exits. So we don't want to kill the + * child during our atexit routine. + */ + if (cmd->clean_on_exit) + BUG("start_bg_command() does not allow non-zero clean_on_exit"); + + if (!cmd->trace2_child_class) + cmd->trace2_child_class = "background"; + + ret = start_command(cmd); + if (ret) { + /* + * We assume that if `start_command()` fails, we + * either get a complete `trace2_child_start() / + * trace2_child_exit()` pair or it fails before the + * `trace2_child_start()` is emitted, so we do not + * need to worry about it here. + * + * We also assume that `start_command()` does not add + * us to the cleanup list. And that it calls + * calls `child_process_clear()`. + */ + sbgr = SBGR_ERROR; + goto done; + } + + time(&time_limit); + time_limit += timeout_sec; + +wait: + pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG); + + if (!pid_seen) { + /* + * The child is currently running. Ask the callback + * if the child is ready to do work or whether we + * should keep waiting for it to boot up. + */ + ret = (*wait_cb)(cmd, cb_data); + if (!ret) { + /* + * The child is running and "ready". + */ + trace2_child_ready(cmd, "ready"); + sbgr = SBGR_READY; + goto done; + } else if (ret > 0) { + /* + * The callback said to give it more time to boot up + * (subject to our timeout limit). + */ + time_t now; + + time(&now); + if (now < time_limit) + goto wait; + + /* + * Our timeout has expired. We don't try to + * kill the child, but rather let it continue + * (hopefully) trying to startup. + */ + trace2_child_ready(cmd, "timeout"); + sbgr = SBGR_TIMEOUT; + goto done; + } else { + /* + * The cb gave up on this child. It is still running, + * but our cb got an error trying to probe it. + */ + trace2_child_ready(cmd, "error"); + sbgr = SBGR_CB_ERROR; + goto done; + } + } + + else if (pid_seen == cmd->pid) { + int child_code = -1; + + /* + * The child started, but exited or was terminated + * before becoming "ready". + * + * We try to match the behavior of `wait_or_whine()` + * WRT the handling of WIFSIGNALED() and WIFEXITED() + * and convert the child's status to a return code for + * tracing purposes and emit the `trace2_child_exit()` + * event. + * + * We do not want the wait_or_whine() error message + * because we will be called by client-side library + * routines. + */ + if (WIFEXITED(wait_status)) + child_code = WEXITSTATUS(wait_status); + else if (WIFSIGNALED(wait_status)) + child_code = WTERMSIG(wait_status) + 128; + trace2_child_exit(cmd, child_code); + + sbgr = SBGR_DIED; + goto done; + } + + else if (pid_seen < 0 && errno == EINTR) + goto wait; + + trace2_child_exit(cmd, -1); + sbgr = SBGR_ERROR; + +done: + child_process_clear(cmd); + invalidate_lstat_cache(); + return sbgr; +} diff --git a/run-command.h b/run-command.h index b9aff74914..4987826258 100644 --- a/run-command.h +++ b/run-command.h @@ -224,13 +224,6 @@ int finish_command_in_signal(struct child_process *); */ int run_command(struct child_process *); -/* - * Returns the path to the hook file, or NULL if the hook is missing - * or disabled. Note that this points to static storage that will be - * overwritten by further calls to find_hook and run_hook_*. - */ -const char *find_hook(const char *name); - /** * Run a hook. * The first argument is a pathname to an index file, or NULL @@ -517,4 +510,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn, */ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir); +/** + * Possible return values for start_bg_command(). + */ +enum start_bg_result { + /* child process is "ready" */ + SBGR_READY = 0, + + /* child process could not be started */ + SBGR_ERROR, + + /* callback error when testing for "ready" */ + SBGR_CB_ERROR, + + /* timeout expired waiting for child to become "ready" */ + SBGR_TIMEOUT, + + /* child process exited or was signalled before becomming "ready" */ + SBGR_DIED, +}; + +/** + * Callback used by start_bg_command() to ask whether the + * child process is ready or needs more time to become "ready". + * + * The callback will receive the cmd and cb_data arguments given to + * start_bg_command(). + * + * Returns 1 is child needs more time (subject to the requested timeout). + * Returns 0 if child is "ready". + * Returns -1 on any error and cause start_bg_command() to also error out. + */ +typedef int(start_bg_wait_cb)(const struct child_process *cmd, void *cb_data); + +/** + * Start a command in the background. Wait long enough for the child + * to become "ready" (as defined by the provided callback). Capture + * immediate errors (like failure to start) and any immediate exit + * status (such as a shutdown/signal before the child became "ready") + * and return this like start_command(). + * + * We run a custom wait loop using the provided callback to wait for + * the child to start and become "ready". This is limited by the given + * timeout value. + * + * If the child does successfully start and become "ready", we orphan + * it into the background. + * + * The caller must not call finish_command(). + * + * The opaque cb_data argument will be forwarded to the callback for + * any instance data that it might require. This may be NULL. + */ +enum start_bg_result start_bg_command(struct child_process *cmd, + start_bg_wait_cb *wait_cb, + void *cb_data, + unsigned int timeout_sec); + #endif diff --git a/sequencer.c b/sequencer.c index 614d56f5e2..cd2aabf1f7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -8,6 +8,7 @@ #include "sequencer.h" #include "tag.h" #include "run-command.h" +#include "hook.h" #include "exec-cmd.h" #include "utf8.h" #include "cache-tree.h" @@ -1459,7 +1460,7 @@ static int try_to_commit(struct repository *r, } } - if (find_hook("prepare-commit-msg")) { + if (hook_exists("prepare-commit-msg")) { res = run_prepare_commit_msg_hook(r, msg, hook_commit); if (res) goto out; @@ -2693,7 +2694,6 @@ static int read_populate_todo(struct repository *r, struct todo_list *todo_list, struct replay_opts *opts) { - struct stat st; const char *todo_file = get_todo_path(opts); int res; @@ -2701,11 +2701,6 @@ static int read_populate_todo(struct repository *r, if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0) return -1; - res = stat(todo_file, &st); - if (res) - return error(_("could not stat '%s'"), todo_file); - fill_stat_data(&todo_list->stat, &st); - res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list); if (res) { if (is_rebase_i(opts)) @@ -3650,9 +3645,9 @@ static int do_reset(struct repository *r, struct strbuf ref_name = STRBUF_INIT; struct object_id oid; struct lock_file lock = LOCK_INIT; - struct tree_desc desc; + struct tree_desc desc = { 0 }; struct tree *tree; - struct unpack_trees_options unpack_tree_opts; + struct unpack_trees_options unpack_tree_opts = { 0 }; int ret = 0; if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) @@ -3684,14 +3679,11 @@ static int do_reset(struct repository *r, strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); if (get_oid(ref_name.buf, &oid) && get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) { - error(_("could not read '%s'"), ref_name.buf); - rollback_lock_file(&lock); - strbuf_release(&ref_name); - return -1; + ret = error(_("could not read '%s'"), ref_name.buf); + goto cleanup; } } - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); setup_unpack_trees_porcelain(&unpack_tree_opts, "reset"); unpack_tree_opts.head_idx = 1; unpack_tree_opts.src_index = r->index; @@ -3699,27 +3691,22 @@ static int do_reset(struct repository *r, unpack_tree_opts.fn = oneway_merge; unpack_tree_opts.merge = 1; unpack_tree_opts.update = 1; + unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL); if (repo_read_index_unmerged(r)) { - rollback_lock_file(&lock); - strbuf_release(&ref_name); - return error_resolve_conflict(_(action_name(opts))); + ret = error_resolve_conflict(_(action_name(opts))); + goto cleanup; } if (!fill_tree_descriptor(r, &desc, &oid)) { - error(_("failed to find tree of %s"), oid_to_hex(&oid)); - rollback_lock_file(&lock); - free((void *)desc.buffer); - strbuf_release(&ref_name); - return -1; + ret = error(_("failed to find tree of %s"), oid_to_hex(&oid)); + goto cleanup; } if (unpack_trees(1, &desc, &unpack_tree_opts)) { - rollback_lock_file(&lock); - free((void *)desc.buffer); - strbuf_release(&ref_name); - return -1; + ret = -1; + goto cleanup; } tree = parse_tree_indirect(&oid); @@ -3727,14 +3714,17 @@ static int do_reset(struct repository *r, if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) ret = error(_("could not write index")); - free((void *)desc.buffer); if (!ret) ret = update_ref(reflog_message(opts, "reset", "'%.*s'", len, name), "HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); - +cleanup: + free((void *)desc.buffer); + if (ret < 0) + rollback_lock_file(&lock); strbuf_release(&ref_name); + clear_unpack_trees_porcelain(&unpack_tree_opts); return ret; } @@ -4284,6 +4274,30 @@ static int stopped_at_head(struct repository *r) } +static int reread_todo_if_changed(struct repository *r, + struct todo_list *todo_list, + struct replay_opts *opts) +{ + int offset; + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read_file_or_whine(&buf, get_todo_path(opts)) < 0) + return -1; + offset = get_item_line_offset(todo_list, todo_list->current + 1); + if (buf.len != todo_list->buf.len - offset || + memcmp(buf.buf, todo_list->buf.buf + offset, buf.len)) { + /* Reread the todo file if it has changed. */ + todo_list_release(todo_list); + if (read_populate_todo(r, todo_list, opts)) + return -1; /* message was printed */ + /* `current` will be incremented on return */ + todo_list->current = -1; + } + strbuf_release(&buf); + + return 0; +} + static const char rescheduled_advice[] = N_("Could not execute the todo command\n" "\n" @@ -4462,20 +4476,9 @@ static int pick_commits(struct repository *r, item->commit, arg, item->arg_len, opts, res, 0); - } else if (is_rebase_i(opts) && check_todo && !res) { - struct stat st; - - if (stat(get_todo_path(opts), &st)) { - res = error_errno(_("could not stat '%s'"), - get_todo_path(opts)); - } else if (match_stat_data(&todo_list->stat, &st)) { - /* Reread the todo file if it has changed. */ - todo_list_release(todo_list); - if (read_populate_todo(r, todo_list, opts)) - res = -1; /* message was printed */ - /* `current` will be incremented below */ - todo_list->current = -1; - } + } else if (is_rebase_i(opts) && check_todo && !res && + reread_todo_if_changed(r, todo_list, opts)) { + return -1; } todo_list->current++; @@ -5437,8 +5440,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, * Add commands after pick and (series of) squash/fixup commands * in the todo list. */ -void todo_list_add_exec_commands(struct todo_list *todo_list, - struct string_list *commands) +static void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands) { struct strbuf *buf = &todo_list->buf; size_t base_offset = buf->len; diff --git a/sequencer.h b/sequencer.h index 2088344cb3..05a7d2ba6b 100644 --- a/sequencer.h +++ b/sequencer.h @@ -116,10 +116,11 @@ struct todo_list { struct todo_item *items; int nr, alloc, current; int done_nr, total_nr; - struct stat_data stat; }; -#define TODO_LIST_INIT { STRBUF_INIT } +#define TODO_LIST_INIT { \ + .buf = STRBUF_INIT, \ +} int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); @@ -161,8 +162,6 @@ int sequencer_remove_state(struct replay_opts *opts); int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, const char **argv, unsigned flags); -void todo_list_add_exec_commands(struct todo_list *todo_list, - struct string_list *commands); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, struct commit *onto, const struct object_id *orig_head, @@ -10,6 +10,7 @@ #include "upload-pack.h" static int advertise_sid = -1; +static int client_hash_algo = GIT_HASH_SHA1; static int always_advertise(struct repository *r, struct strbuf *value) @@ -33,6 +34,17 @@ static int object_format_advertise(struct repository *r, return 1; } +static void object_format_receive(struct repository *r, + const char *algo_name) +{ + if (!algo_name) + die("object-format capability requires an argument"); + + client_hash_algo = hash_algo_by_name(algo_name); + if (client_hash_algo == GIT_HASH_UNKNOWN) + die("unknown object format '%s'", algo_name); +} + static int session_id_advertise(struct repository *r, struct strbuf *value) { if (advertise_sid == -1 && @@ -45,6 +57,14 @@ static int session_id_advertise(struct repository *r, struct strbuf *value) return 1; } +static void session_id_receive(struct repository *r, + const char *client_sid) +{ + if (!client_sid) + client_sid = ""; + trace2_data_string("transfer", NULL, "client-sid", client_sid); +} + struct protocol_capability { /* * The name of the capability. The server uses this name when @@ -70,6 +90,16 @@ struct protocol_capability { * This field should be NULL for capabilities which are not commands. */ int (*command)(struct repository *r, struct packet_reader *request); + + /* + * Function called when a client requests the capability as a + * non-command. This may be NULL if the capability does nothing. + * + * For a capability of the form "foo=bar", the value string points to + * the content after the "=" (i.e., "bar"). For simple capabilities + * (just "foo"), it is NULL. + */ + void (*receive)(struct repository *r, const char *value); }; static struct protocol_capability capabilities[] = { @@ -94,10 +124,12 @@ static struct protocol_capability capabilities[] = { { .name = "object-format", .advertise = object_format_advertise, + .receive = object_format_receive, }, { .name = "session-id", .advertise = session_id_advertise, + .receive = session_id_receive, }, { .name = "object-info", @@ -139,7 +171,7 @@ void protocol_v2_advertise_capabilities(void) strbuf_release(&value); } -static struct protocol_capability *get_capability(const char *key) +static struct protocol_capability *get_capability(const char *key, const char **value) { int i; @@ -149,31 +181,46 @@ static struct protocol_capability *get_capability(const char *key) for (i = 0; i < ARRAY_SIZE(capabilities); i++) { struct protocol_capability *c = &capabilities[i]; const char *out; - if (skip_prefix(key, c->name, &out) && (!*out || *out == '=')) + if (!skip_prefix(key, c->name, &out)) + continue; + if (!*out) { + *value = NULL; return c; + } + if (*out++ == '=') { + *value = out; + return c; + } } return NULL; } -static int is_valid_capability(const char *key) +static int receive_client_capability(const char *key) { - const struct protocol_capability *c = get_capability(key); + const char *value; + const struct protocol_capability *c = get_capability(key, &value); - return c && c->advertise(the_repository, NULL); + if (!c || c->command || !c->advertise(the_repository, NULL)) + return 0; + + if (c->receive) + c->receive(the_repository, value); + return 1; } -static int is_command(const char *key, struct protocol_capability **command) +static int parse_command(const char *key, struct protocol_capability **command) { const char *out; if (skip_prefix(key, "command=", &out)) { - struct protocol_capability *cmd = get_capability(out); + const char *value; + struct protocol_capability *cmd = get_capability(out, &value); if (*command) die("command '%s' requested after already requesting command '%s'", out, (*command)->name); - if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command) + if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command || value) die("invalid command '%s'", out); *command = cmd; @@ -183,42 +230,6 @@ static int is_command(const char *key, struct protocol_capability **command) return 0; } -static int has_capability(const struct strvec *keys, const char *capability, - const char **value) -{ - int i; - for (i = 0; i < keys->nr; i++) { - const char *out; - if (skip_prefix(keys->v[i], capability, &out) && - (!*out || *out == '=')) { - if (value) { - if (*out == '=') - out++; - *value = out; - } - return 1; - } - } - - return 0; -} - -static void check_algorithm(struct repository *r, struct strvec *keys) -{ - int client = GIT_HASH_SHA1, server = hash_algo_by_ptr(r->hash_algo); - const char *algo_name; - - if (has_capability(keys, "object-format", &algo_name)) { - client = hash_algo_by_name(algo_name); - if (client == GIT_HASH_UNKNOWN) - die("unknown object format '%s'", algo_name); - } - - if (client != server) - die("mismatched object format: server %s; client %s\n", - r->hash_algo->name, hash_algos[client].name); -} - enum request_state { PROCESS_REQUEST_KEYS, PROCESS_REQUEST_DONE, @@ -228,9 +239,8 @@ static int process_request(void) { enum request_state state = PROCESS_REQUEST_KEYS; struct packet_reader reader; - struct strvec keys = STRVEC_INIT; + int seen_capability_or_command = 0; struct protocol_capability *command = NULL; - const char *client_sid; packet_reader_init(&reader, 0, NULL, 0, PACKET_READ_CHOMP_NEWLINE | @@ -250,10 +260,9 @@ static int process_request(void) case PACKET_READ_EOF: BUG("Should have already died when seeing EOF"); case PACKET_READ_NORMAL: - /* collect request; a sequence of keys and values */ - if (is_command(reader.line, &command) || - is_valid_capability(reader.line)) - strvec_push(&keys, reader.line); + if (parse_command(reader.line, &command) || + receive_client_capability(reader.line)) + seen_capability_or_command = 1; else die("unknown capability '%s'", reader.line); @@ -265,7 +274,7 @@ static int process_request(void) * If no command and no keys were given then the client * wanted to terminate the connection. */ - if (!keys.nr) + if (!seen_capability_or_command) return 1; /* @@ -292,14 +301,13 @@ static int process_request(void) if (!command) die("no command requested"); - check_algorithm(the_repository, &keys); - - if (has_capability(&keys, "session-id", &client_sid)) - trace2_data_string("transfer", NULL, "client-sid", client_sid); + if (client_hash_algo != hash_algo_by_ptr(the_repository->hash_algo)) + die("mismatched object format: server %s; client %s\n", + the_repository->hash_algo->name, + hash_algos[client_hash_algo].name); command->command(the_repository, &reader); - strvec_clear(&keys); return 0; } @@ -23,7 +23,9 @@ int is_repository_shallow(struct repository *r); struct shallow_lock { struct lock_file lock; }; -#define SHALLOW_LOCK_INIT { LOCK_INIT } +#define SHALLOW_LOCK_INIT { \ + .lock = LOCK_INIT, \ +} /* commit $GIT_DIR/shallow and reset stat-validity checks */ int commit_shallow_file(struct repository *r, struct shallow_lock *lk); diff --git a/simple-ipc.h b/simple-ipc.h index 2c48a5ee00..a849d9f841 100644 --- a/simple-ipc.h +++ b/simple-ipc.h @@ -5,13 +5,6 @@ * See Documentation/technical/api-simple-ipc.txt */ -#ifdef SUPPORTS_SIMPLE_IPC -#include "pkt-line.h" - -/* - * Simple IPC Client Side API. - */ - enum ipc_active_state { /* * The pipe/socket exists and the daemon is waiting for connections. @@ -43,6 +36,13 @@ enum ipc_active_state { IPC_STATE__OTHER_ERROR, }; +#ifdef SUPPORTS_SIMPLE_IPC +#include "pkt-line.h" + +/* + * Simple IPC Client Side API. + */ + struct ipc_client_connect_options { /* * Spin under timeout if the server is running but can't @@ -65,11 +65,7 @@ struct ipc_client_connect_options { unsigned int uds_disallow_chdir:1; }; -#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \ - .wait_if_busy = 0, \ - .wait_if_not_found = 0, \ - .uds_disallow_chdir = 0, \ -} +#define IPC_CLIENT_CONNECT_OPTIONS_INIT { 0 } /* * Determine if a server is listening on this named pipe or socket using @@ -107,7 +103,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection); */ int ipc_client_send_command_to_connection( struct ipc_client_connection *connection, - const char *message, struct strbuf *answer); + const char *message, size_t message_len, + struct strbuf *answer); /* * Used by the client to synchronously connect and send and receive a @@ -119,7 +116,8 @@ int ipc_client_send_command_to_connection( */ int ipc_client_send_command(const char *path, const struct ipc_client_connect_options *options, - const char *message, struct strbuf *answer); + const char *message, size_t message_len, + struct strbuf *answer); /* * Simple IPC Server Side API. @@ -144,6 +142,7 @@ typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *, */ typedef int (ipc_server_application_cb)(void *application_data, const char *request, + size_t request_len, ipc_server_reply_cb *reply_cb, struct ipc_server_reply_data *reply_data); @@ -70,7 +70,7 @@ struct strbuf { }; extern char strbuf_slopbuf[]; -#define STRBUF_INIT { .alloc = 0, .len = 0, .buf = strbuf_slopbuf } +#define STRBUF_INIT { .buf = strbuf_slopbuf } /* * Predeclare this here, since cache.h includes this file before it defines the diff --git a/streaming.c b/streaming.c index 5f480ad50c..fe54665d86 100644 --- a/streaming.c +++ b/streaming.c @@ -223,19 +223,24 @@ static int open_istream_loose(struct git_istream *st, struct repository *r, const struct object_id *oid, enum object_type *type) { + struct object_info oi = OBJECT_INFO_INIT; + oi.sizep = &st->size; + oi.typep = type; + st->u.loose.mapped = map_loose_object(r, oid, &st->u.loose.mapsize); if (!st->u.loose.mapped) return -1; - if ((unpack_loose_header(&st->z, - st->u.loose.mapped, - st->u.loose.mapsize, - st->u.loose.hdr, - sizeof(st->u.loose.hdr)) < 0) || - (parse_loose_header(st->u.loose.hdr, &st->size) < 0)) { - git_inflate_end(&st->z); - munmap(st->u.loose.mapped, st->u.loose.mapsize); - return -1; + switch (unpack_loose_header(&st->z, st->u.loose.mapped, + st->u.loose.mapsize, st->u.loose.hdr, + sizeof(st->u.loose.hdr), NULL)) { + case ULHR_OK: + break; + case ULHR_BAD: + case ULHR_TOO_LONG: + goto error; } + if (parse_loose_header(st->u.loose.hdr, &oi) < 0 || *type < 0) + goto error; st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1; st->u.loose.hdr_avail = st->z.total_out; @@ -244,6 +249,10 @@ static int open_istream_loose(struct git_istream *st, struct repository *r, st->read = read_istream_loose; return 0; +error: + git_inflate_end(&st->z); + munmap(st->u.loose.mapped, st->u.loose.mapsize); + return -1; } diff --git a/string-list.c b/string-list.c index 43576ad126..549fc416d6 100644 --- a/string-list.c +++ b/string-list.c @@ -13,14 +13,6 @@ void string_list_init_dup(struct string_list *list) memcpy(list, &blank, sizeof(*list)); } -void string_list_init(struct string_list *list, int strdup_strings) -{ - if (strdup_strings) - string_list_init_dup(list); - else - string_list_init_nodup(list); -} - /* if there is no exact match, point to the index where the entry could be * inserted */ static int get_entry_index(const struct string_list *list, const char *string, diff --git a/string-list.h b/string-list.h index 0d6b469239..267d6e5769 100644 --- a/string-list.h +++ b/string-list.h @@ -104,11 +104,6 @@ struct string_list { void string_list_init_nodup(struct string_list *list); void string_list_init_dup(struct string_list *list); -/** - * TODO remove: For compatibility with any in-flight older API users - */ -void string_list_init(struct string_list *list, int strdup_strings); - /** Callback function type for for_each_string_list */ typedef int (*string_list_each_func_t)(struct string_list_item *, void *); @@ -33,7 +33,9 @@ struct strvec { size_t alloc; }; -#define STRVEC_INIT { empty_strvec, 0, 0 } +#define STRVEC_INIT { \ + .v = empty_strvec, \ +} /** * Initialize an array. This is no different than assigning from diff --git a/submodule-config.h b/submodule-config.h index c11e22cf50..65875b94ea 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -45,10 +45,6 @@ struct submodule { struct object_id gitmodules_oid; int recommend_shallow; }; - -#define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \ - NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, { { 0 } }, -1 }; - struct submodule_cache; struct repository; diff --git a/submodule.c b/submodule.c index 61575e5a56..c689070524 100644 --- a/submodule.c +++ b/submodule.c @@ -1330,9 +1330,11 @@ struct submodule_parallel_fetch { struct strbuf submodules_with_errors; }; -#define SPF_INIT {0, STRVEC_INIT, NULL, NULL, 0, 0, 0, 0, \ - STRING_LIST_INIT_DUP, \ - NULL, 0, 0, STRBUF_INIT} +#define SPF_INIT { \ + .args = STRVEC_INIT, \ + .changed_submodule_names = STRING_LIST_INIT_DUP, \ + .submodules_with_errors = STRBUF_INIT, \ +} static int get_fetch_recurse_config(const struct submodule *submodule, struct submodule_parallel_fetch *spf) @@ -1906,6 +1908,7 @@ static void submodule_reset_index(const char *path) strvec_pushf(&cp.args, "--super-prefix=%s%s/", get_super_prefix_or_empty(), path); + /* TODO: determine if this might overwright untracked files */ strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL); strvec_push(&cp.args, empty_tree_oid_hex()); diff --git a/submodule.h b/submodule.h index 4578e501b8..6bd2c99fd9 100644 --- a/submodule.h +++ b/submodule.h @@ -37,7 +37,9 @@ struct submodule_update_strategy { enum submodule_update_type type; const char *command; }; -#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} +#define SUBMODULE_UPDATE_STRATEGY_INIT { \ + .type = SM_UPDATE_UNSPECIFIED, \ +} int is_gitmodules_unmerged(struct index_state *istate); int is_writing_gitmodules_ok(void); @@ -366,6 +366,13 @@ excluded as so much relies on it, but this might change in the future. GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole test suite. Accept any boolean values that are accepted by git-config. +GIT_TEST_PASSING_SANITIZE_LEAK=<boolean> when compiled with +SANITIZE=leak will run only those tests that have whitelisted +themselves as passing with no memory leaks. Tests can be whitelisted +by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing +"test-lib.sh" itself at the top of the test script. This test mode is +used by the "linux-leaks" CI target. + GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version' default to n. diff --git a/t/helper/test-bitmap.c b/t/helper/test-bitmap.c index 134a1e9d76..ff35f5999b 100644 --- a/t/helper/test-bitmap.c +++ b/t/helper/test-bitmap.c @@ -7,6 +7,11 @@ static int bitmap_list_commits(void) return test_bitmap_commits(the_repository); } +static int bitmap_dump_hashes(void) +{ + return test_bitmap_hashes(the_repository); +} + int cmd__bitmap(int argc, const char **argv) { setup_git_directory(); @@ -16,9 +21,12 @@ int cmd__bitmap(int argc, const char **argv) if (!strcmp(argv[1], "list-commits")) return bitmap_list_commits(); + if (!strcmp(argv[1], "dump-hashes")) + return bitmap_dump_hashes(); usage: - usage("\ttest-tool bitmap list-commits"); + usage("\ttest-tool bitmap list-commits\n" + "\ttest-tool bitmap dump-hashes"); return -1; } diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index cf0f2c7228..99010614f6 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -45,8 +45,10 @@ int cmd__dump_untracked_cache(int ac, const char **av) struct untracked_cache *uc; struct strbuf base = STRBUF_INIT; - /* Hack to avoid modifying the untracked cache when we read it */ - ignore_untracked_cache_config = 1; + /* Set core.untrackedCache=keep before setup_git_directory() */ + xsetenv("GIT_CONFIG_COUNT", "1", 1); + xsetenv("GIT_CONFIG_KEY_0", "core.untrackedCache", 1); + xsetenv("GIT_CONFIG_VALUE_0", "keep", 1); setup_git_directory(); if (read_cache() < 0) diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c index c5cffaa4b7..ebf68f7de8 100644 --- a/t/helper/test-mergesort.c +++ b/t/helper/test-mergesort.c @@ -2,6 +2,12 @@ #include "cache.h" #include "mergesort.h" +static uint32_t minstd_rand(uint32_t *state) +{ + *state = (uint64_t)*state * 48271 % 2147483647; + return *state; +} + struct line { char *text; struct line *next; @@ -23,14 +29,12 @@ static int compare_strings(const void *a, const void *b) return strcmp(x->text, y->text); } -int cmd__mergesort(int argc, const char **argv) +static int sort_stdin(void) { struct line *line, *p = NULL, *lines = NULL; struct strbuf sb = STRBUF_INIT; - for (;;) { - if (strbuf_getwholeline(&sb, stdin, '\n')) - break; + while (!strbuf_getline(&sb, stdin)) { line = xmalloc(sizeof(struct line)); line->text = strbuf_detach(&sb, NULL); if (p) { @@ -46,8 +50,362 @@ int cmd__mergesort(int argc, const char **argv) lines = llist_mergesort(lines, get_next, set_next, compare_strings); while (lines) { - printf("%s", lines->text); + puts(lines->text); lines = lines->next; } return 0; } + +static void dist_sawtooth(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = i % m; +} + +static void dist_rand(int *arr, int n, int m) +{ + int i; + uint32_t seed = 1; + for (i = 0; i < n; i++) + arr[i] = minstd_rand(&seed) % m; +} + +static void dist_stagger(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = (i * m + i) % n; +} + +static void dist_plateau(int *arr, int n, int m) +{ + int i; + for (i = 0; i < n; i++) + arr[i] = (i < m) ? i : m; +} + +static void dist_shuffle(int *arr, int n, int m) +{ + int i, j, k; + uint32_t seed = 1; + for (i = j = 0, k = 1; i < n; i++) + arr[i] = minstd_rand(&seed) % m ? (j += 2) : (k += 2); +} + +#define DIST(name) { #name, dist_##name } + +static struct dist { + const char *name; + void (*fn)(int *arr, int n, int m); +} dist[] = { + DIST(sawtooth), + DIST(rand), + DIST(stagger), + DIST(plateau), + DIST(shuffle), +}; + +static const struct dist *get_dist_by_name(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(dist); i++) { + if (!strcmp(dist[i].name, name)) + return &dist[i]; + } + return NULL; +} + +static void mode_copy(int *arr, int n) +{ + /* nothing */ +} + +static void mode_reverse(int *arr, int n) +{ + int i, j; + for (i = 0, j = n - 1; i < j; i++, j--) + SWAP(arr[i], arr[j]); +} + +static void mode_reverse_1st_half(int *arr, int n) +{ + mode_reverse(arr, n / 2); +} + +static void mode_reverse_2nd_half(int *arr, int n) +{ + int half = n / 2; + mode_reverse(arr + half, n - half); +} + +static int compare_ints(const void *av, const void *bv) +{ + const int *ap = av, *bp = bv; + int a = *ap, b = *bp; + return (a > b) - (a < b); +} + +static void mode_sort(int *arr, int n) +{ + QSORT(arr, n, compare_ints); +} + +static void mode_dither(int *arr, int n) +{ + int i; + for (i = 0; i < n; i++) + arr[i] += i % 5; +} + +static void unriffle(int *arr, int n, int *tmp) +{ + int i, j; + COPY_ARRAY(tmp, arr, n); + for (i = j = 0; i < n; i += 2) + arr[j++] = tmp[i]; + for (i = 1; i < n; i += 2) + arr[j++] = tmp[i]; +} + +static void unriffle_recursively(int *arr, int n, int *tmp) +{ + if (n > 1) { + int half = n / 2; + unriffle(arr, n, tmp); + unriffle_recursively(arr, half, tmp); + unriffle_recursively(arr + half, n - half, tmp); + } +} + +static void mode_unriffle(int *arr, int n) +{ + int *tmp; + ALLOC_ARRAY(tmp, n); + unriffle_recursively(arr, n, tmp); + free(tmp); +} + +static unsigned int prev_pow2(unsigned int n) +{ + unsigned int pow2 = 1; + while (pow2 * 2 < n) + pow2 *= 2; + return pow2; +} + +static void unriffle_recursively_skewed(int *arr, int n, int *tmp) +{ + if (n > 1) { + int pow2 = prev_pow2(n); + int rest = n - pow2; + unriffle(arr + pow2 - rest, rest * 2, tmp); + unriffle_recursively_skewed(arr, pow2, tmp); + unriffle_recursively_skewed(arr + pow2, rest, tmp); + } +} + +static void mode_unriffle_skewed(int *arr, int n) +{ + int *tmp; + ALLOC_ARRAY(tmp, n); + unriffle_recursively_skewed(arr, n, tmp); + free(tmp); +} + +#define MODE(name) { #name, mode_##name } + +static struct mode { + const char *name; + void (*fn)(int *arr, int n); +} mode[] = { + MODE(copy), + MODE(reverse), + MODE(reverse_1st_half), + MODE(reverse_2nd_half), + MODE(sort), + MODE(dither), + MODE(unriffle), + MODE(unriffle_skewed), +}; + +static const struct mode *get_mode_by_name(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(mode); i++) { + if (!strcmp(mode[i].name, name)) + return &mode[i]; + } + return NULL; +} + +static int generate(int argc, const char **argv) +{ + const struct dist *dist = NULL; + const struct mode *mode = NULL; + int i, n, m, *arr; + + if (argc != 4) + return 1; + + dist = get_dist_by_name(argv[0]); + mode = get_mode_by_name(argv[1]); + n = strtol(argv[2], NULL, 10); + m = strtol(argv[3], NULL, 10); + if (!dist || !mode) + return 1; + + ALLOC_ARRAY(arr, n); + dist->fn(arr, n, m); + mode->fn(arr, n); + for (i = 0; i < n; i++) + printf("%08x\n", arr[i]); + free(arr); + return 0; +} + +static struct stats { + int get_next, set_next, compare; +} stats; + +struct number { + int value, rank; + struct number *next; +}; + +static void *get_next_number(const void *a) +{ + stats.get_next++; + return ((const struct number *)a)->next; +} + +static void set_next_number(void *a, void *b) +{ + stats.set_next++; + ((struct number *)a)->next = b; +} + +static int compare_numbers(const void *av, const void *bv) +{ + const struct number *an = av, *bn = bv; + int a = an->value, b = bn->value; + stats.compare++; + return (a > b) - (a < b); +} + +static void clear_numbers(struct number *list) +{ + while (list) { + struct number *next = list->next; + free(list); + list = next; + } +} + +static int test(const struct dist *dist, const struct mode *mode, int n, int m) +{ + int *arr; + size_t i; + struct number *curr, *list, **tail; + int is_sorted = 1; + int is_stable = 1; + const char *verdict; + int result = -1; + + ALLOC_ARRAY(arr, n); + dist->fn(arr, n, m); + mode->fn(arr, n); + for (i = 0, tail = &list; i < n; i++) { + curr = xmalloc(sizeof(*curr)); + curr->value = arr[i]; + curr->rank = i; + *tail = curr; + tail = &curr->next; + } + *tail = NULL; + + stats.get_next = stats.set_next = stats.compare = 0; + list = llist_mergesort(list, get_next_number, set_next_number, + compare_numbers); + + QSORT(arr, n, compare_ints); + for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) { + if (arr[i] != curr->value) + is_sorted = 0; + if (curr->next && curr->value == curr->next->value && + curr->rank >= curr->next->rank) + is_stable = 0; + } + if (i < n) { + verdict = "too short"; + } else if (curr) { + verdict = "too long"; + } else if (!is_sorted) { + verdict = "not sorted"; + } else if (!is_stable) { + verdict = "unstable"; + } else { + verdict = "OK"; + result = 0; + } + + printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n", + dist->name, mode->name, n, m, stats.get_next, stats.set_next, + stats.compare, verdict); + + clear_numbers(list); + free(arr); + + return result; +} + +/* + * A version of the qsort certification program from "Engineering a Sort + * Function" by Bentley and McIlroy, Software—Practice and Experience, + * Volume 23, Issue 11, 1249–1265 (November 1993). + */ +static int run_tests(int argc, const char **argv) +{ + const char *argv_default[] = { "100", "1023", "1024", "1025" }; + if (!argc) + return run_tests(ARRAY_SIZE(argv_default), argv_default); + printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n", + "distribut", "mode", "n", "m", "get_next", "set_next", + "compare", "verdict"); + while (argc--) { + int i, j, m, n = strtol(*argv++, NULL, 10); + for (i = 0; i < ARRAY_SIZE(dist); i++) { + for (j = 0; j < ARRAY_SIZE(mode); j++) { + for (m = 1; m < 2 * n; m *= 2) { + if (test(&dist[i], &mode[j], n, m)) + return 1; + } + } + } + } + return 0; +} + +int cmd__mergesort(int argc, const char **argv) +{ + int i; + const char *sep; + + if (argc == 6 && !strcmp(argv[1], "generate")) + return generate(argc - 2, argv + 2); + if (argc == 2 && !strcmp(argv[1], "sort")) + return sort_stdin(); + if (argc > 1 && !strcmp(argv[1], "test")) + return run_tests(argc - 2, argv + 2); + fprintf(stderr, "usage: test-tool mergesort generate <distribution> <mode> <n> <m>\n"); + fprintf(stderr, " or: test-tool mergesort sort\n"); + fprintf(stderr, " or: test-tool mergesort test [<n>...]\n"); + fprintf(stderr, "\n"); + for (i = 0, sep = "distributions: "; i < ARRAY_SIZE(dist); i++, sep = ", ") + fprintf(stderr, "%s%s", sep, dist[i].name); + fprintf(stderr, "\n"); + for (i = 0, sep = "modes: "; i < ARRAY_SIZE(mode); i++, sep = ", ") + fprintf(stderr, "%s%s", sep, mode[i].name); + fprintf(stderr, "\n"); + return 129; +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index cb0d27049a..9d6fa7a377 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -3,6 +3,7 @@ #include "midx.h" #include "repository.h" #include "object-store.h" +#include "pack-bitmap.h" static int read_midx_file(const char *object_dir, int show_objects) { @@ -72,14 +73,40 @@ static int read_midx_checksum(const char *object_dir) return 0; } +static int read_midx_preferred_pack(const char *object_dir) +{ + struct multi_pack_index *midx = NULL; + struct bitmap_index *bitmap = NULL; + + setup_git_directory(); + + midx = load_multi_pack_index(object_dir, 1); + if (!midx) + return 1; + + bitmap = prepare_bitmap_git(the_repository); + if (!bitmap) + return 1; + if (!bitmap_is_midx(bitmap)) { + free_bitmap_index(bitmap); + return 1; + } + + printf("%s\n", midx->pack_names[midx_preferred_pack(bitmap)]); + free_bitmap_index(bitmap); + return 0; +} + int cmd__read_midx(int argc, const char **argv) { if (!(argc == 2 || argc == 3)) - usage("read-midx [--show-objects|--checksum] <object-dir>"); + usage("read-midx [--show-objects|--checksum|--preferred-pack] <object-dir>"); if (!strcmp(argv[1], "--show-objects")) return read_midx_file(argv[2], 1); else if (!strcmp(argv[1], "--checksum")) return read_midx_checksum(argv[2]); + else if (!strcmp(argv[1], "--preferred-pack")) + return read_midx_preferred_pack(argv[2]); return read_midx_file(argv[1], 0); } diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index 14c57365e7..3c4fb86223 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -60,8 +60,10 @@ struct testsuite { int next; int quiet, immediate, verbose, verbose_log, trace, write_junit_xml; }; -#define TESTSUITE_INIT \ - { STRING_LIST_INIT_DUP, STRING_LIST_INIT_DUP, 0, 0, 0, 0, 0, 0, 0 } +#define TESTSUITE_INIT { \ + .tests = STRING_LIST_INIT_DUP, \ + .failed = STRING_LIST_INIT_DUP, \ +} static int next_test(struct child_process *cp, struct strbuf *err, void *cb, void **task_cb) diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c index 42040ef81b..28365ff85b 100644 --- a/t/helper/test-simple-ipc.c +++ b/t/helper/test-simple-ipc.c @@ -9,6 +9,7 @@ #include "parse-options.h" #include "thread-utils.h" #include "strvec.h" +#include "run-command.h" #ifndef SUPPORTS_SIMPLE_IPC int cmd__simple_ipc(int argc, const char **argv) @@ -112,7 +113,7 @@ static int app__slow_command(ipc_server_reply_cb *reply_cb, /* * The client sent a command followed by a (possibly very) large buffer. */ -static int app__sendbytes_command(const char *received, +static int app__sendbytes_command(const char *received, size_t received_len, ipc_server_reply_cb *reply_cb, struct ipc_server_reply_data *reply_data) { @@ -123,6 +124,13 @@ static int app__sendbytes_command(const char *received, int errs = 0; int ret; + /* + * The test is setup to send: + * "sendbytes" SP <n * char> + */ + if (received_len < strlen("sendbytes ")) + BUG("received_len is short in app__sendbytes_command"); + if (skip_prefix(received, "sendbytes ", &p)) len_ballast = strlen(p); @@ -160,7 +168,7 @@ static ipc_server_application_cb test_app_cb; * by this application. */ static int test_app_cb(void *application_data, - const char *command, + const char *command, size_t command_len, ipc_server_reply_cb *reply_cb, struct ipc_server_reply_data *reply_data) { @@ -173,7 +181,7 @@ static int test_app_cb(void *application_data, if (application_data != (void*)&my_app_data) BUG("application_cb: application_data pointer wrong"); - if (!strcmp(command, "quit")) { + if (command_len == 4 && !strncmp(command, "quit", 4)) { /* * The client sent a "quit" command. This is an async * request for the server to shutdown. @@ -193,22 +201,23 @@ static int test_app_cb(void *application_data, return SIMPLE_IPC_QUIT; } - if (!strcmp(command, "ping")) { + if (command_len == 4 && !strncmp(command, "ping", 4)) { const char *answer = "pong"; return reply_cb(reply_data, answer, strlen(answer)); } - if (!strcmp(command, "big")) + if (command_len == 3 && !strncmp(command, "big", 3)) return app__big_command(reply_cb, reply_data); - if (!strcmp(command, "chunk")) + if (command_len == 5 && !strncmp(command, "chunk", 5)) return app__chunk_command(reply_cb, reply_data); - if (!strcmp(command, "slow")) + if (command_len == 4 && !strncmp(command, "slow", 4)) return app__slow_command(reply_cb, reply_data); - if (starts_with(command, "sendbytes ")) - return app__sendbytes_command(command, reply_cb, reply_data); + if (command_len >= 10 && starts_with(command, "sendbytes ")) + return app__sendbytes_command(command, command_len, + reply_cb, reply_data); return app__unhandled_command(command, reply_cb, reply_data); } @@ -259,186 +268,72 @@ static int daemon__run_server(void) */ ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data); if (ret == -2) - error(_("socket/pipe already in use: '%s'"), cl_args.path); + error("socket/pipe already in use: '%s'", cl_args.path); else if (ret == -1) - error_errno(_("could not start server on: '%s'"), cl_args.path); + error_errno("could not start server on: '%s'", cl_args.path); return ret; } -#ifndef GIT_WINDOWS_NATIVE -/* - * This is adapted from `daemonize()`. Use `fork()` to directly create and - * run the daemon in a child process. - */ -static int spawn_server(pid_t *pid) -{ - struct ipc_server_opts opts = { - .nr_threads = cl_args.nr_threads, - }; - - *pid = fork(); +static start_bg_wait_cb bg_wait_cb; - switch (*pid) { - case 0: - if (setsid() == -1) - error_errno(_("setsid failed")); - close(0); - close(1); - close(2); - sanitize_stdfds(); +static int bg_wait_cb(const struct child_process *cp, void *cb_data) +{ + int s = ipc_get_active_state(cl_args.path); - return ipc_server_run(cl_args.path, &opts, test_app_cb, - (void*)&my_app_data); + switch (s) { + case IPC_STATE__LISTENING: + /* child is "ready" */ + return 0; - case -1: - return error_errno(_("could not spawn daemon in the background")); + case IPC_STATE__NOT_LISTENING: + case IPC_STATE__PATH_NOT_FOUND: + /* give child more time */ + return 1; default: - return 0; + case IPC_STATE__INVALID_PATH: + case IPC_STATE__OTHER_ERROR: + /* all the time in world won't help */ + return -1; } } -#else -/* - * Conceptually like `daemonize()` but different because Windows does not - * have `fork(2)`. Spawn a normal Windows child process but without the - * limitations of `start_command()` and `finish_command()`. - */ -static int spawn_server(pid_t *pid) -{ - char test_tool_exe[MAX_PATH]; - struct strvec args = STRVEC_INIT; - int in, out; - - GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH); - - in = open("/dev/null", O_RDONLY); - out = open("/dev/null", O_WRONLY); - - strvec_push(&args, test_tool_exe); - strvec_push(&args, "simple-ipc"); - strvec_push(&args, "run-daemon"); - strvec_pushf(&args, "--name=%s", cl_args.path); - strvec_pushf(&args, "--threads=%d", cl_args.nr_threads); - *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out); - close(in); - close(out); - - strvec_clear(&args); - - if (*pid < 0) - return error(_("could not spawn daemon in the background")); - - return 0; -} -#endif - -/* - * This is adapted from `wait_or_whine()`. Watch the child process and - * let it get started and begin listening for requests on the socket - * before reporting our success. - */ -static int wait_for_server_startup(pid_t pid_child) +static int daemon__start_server(void) { - int status; - pid_t pid_seen; - enum ipc_active_state s; - time_t time_limit, now; + struct child_process cp = CHILD_PROCESS_INIT; + enum start_bg_result sbgr; - time(&time_limit); - time_limit += cl_args.max_wait_sec; + strvec_push(&cp.args, "test-tool"); + strvec_push(&cp.args, "simple-ipc"); + strvec_push(&cp.args, "run-daemon"); + strvec_pushf(&cp.args, "--name=%s", cl_args.path); + strvec_pushf(&cp.args, "--threads=%d", cl_args.nr_threads); - for (;;) { - pid_seen = waitpid(pid_child, &status, WNOHANG); - - if (pid_seen == -1) - return error_errno(_("waitpid failed")); - - else if (pid_seen == 0) { - /* - * The child is still running (this should be - * the normal case). Try to connect to it on - * the socket and see if it is ready for - * business. - * - * If there is another daemon already running, - * our child will fail to start (possibly - * after a timeout on the lock), but we don't - * care (who responds) if the socket is live. - */ - s = ipc_get_active_state(cl_args.path); - if (s == IPC_STATE__LISTENING) - return 0; + cp.no_stdin = 1; + cp.no_stdout = 1; + cp.no_stderr = 1; - time(&now); - if (now > time_limit) - return error(_("daemon not online yet")); + sbgr = start_bg_command(&cp, bg_wait_cb, NULL, cl_args.max_wait_sec); - continue; - } + switch (sbgr) { + case SBGR_READY: + return 0; - else if (pid_seen == pid_child) { - /* - * The new child daemon process shutdown while - * it was starting up, so it is not listening - * on the socket. - * - * Try to ping the socket in the odd chance - * that another daemon started (or was already - * running) while our child was starting. - * - * Again, we don't care who services the socket. - */ - s = ipc_get_active_state(cl_args.path); - if (s == IPC_STATE__LISTENING) - return 0; + default: + case SBGR_ERROR: + case SBGR_CB_ERROR: + return error("daemon failed to start"); - /* - * We don't care about the WEXITSTATUS() nor - * any of the WIF*(status) values because - * `cmd__simple_ipc()` does the `!!result` - * trick on all function return values. - * - * So it is sufficient to just report the - * early shutdown as an error. - */ - return error(_("daemon failed to start")); - } + case SBGR_TIMEOUT: + return error("daemon not online yet"); - else - return error(_("waitpid is confused")); + case SBGR_DIED: + return error("daemon terminated"); } } /* - * This process will start a simple-ipc server in a background process and - * wait for it to become ready. This is like `daemonize()` but gives us - * more control and better error reporting (and makes it easier to write - * unit tests). - */ -static int daemon__start_server(void) -{ - pid_t pid_child; - int ret; - - /* - * Run the actual daemon in a background process. - */ - ret = spawn_server(&pid_child); - if (pid_child <= 0) - return ret; - - /* - * Let the parent wait for the child process to get started - * and begin listening for requests on the socket. - */ - ret = wait_for_server_startup(pid_child); - - return ret; -} - -/* * This process will run a quick probe to see if a simple-ipc server * is active on this path. * @@ -488,7 +383,9 @@ static int client__send_ipc(void) options.wait_if_busy = 1; options.wait_if_not_found = 0; - if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) { + if (!ipc_client_send_command(cl_args.path, &options, + command, strlen(command), + &buf)) { if (buf.len) { printf("%s\n", buf.buf); fflush(stdout); @@ -538,7 +435,7 @@ static int client__stop_server(void) time(&now); if (now > time_limit) - return error(_("daemon has not shutdown yet")); + return error("daemon has not shutdown yet"); } } @@ -556,7 +453,9 @@ static int do_sendbytes(int bytecount, char byte, const char *path, strbuf_addstr(&buf_send, "sendbytes "); strbuf_addchars(&buf_send, byte, bytecount); - if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) { + if (!ipc_client_send_command(path, options, + buf_send.buf, buf_send.len, + &buf_resp)) { strbuf_rtrim(&buf_resp); printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf); fflush(stdout); diff --git a/t/lib-midx.sh b/t/lib-midx.sh new file mode 100644 index 0000000000..1261994744 --- /dev/null +++ b/t/lib-midx.sh @@ -0,0 +1,8 @@ +# test_midx_consistent <objdir> +test_midx_consistent () { + ls $1/pack/pack-*.idx | xargs -n 1 basename | sort >expect && + test-tool read-midx $1 | grep ^pack-.*\.idx$ | sort >actual && + + test_cmp expect actual && + git multi-pack-index --object-dir=$1 verify +} diff --git a/t/lib-subtest.sh b/t/lib-subtest.sh new file mode 100644 index 0000000000..56ee927f0c --- /dev/null +++ b/t/lib-subtest.sh @@ -0,0 +1,95 @@ +write_sub_test_lib_test () { + name="$1" # stdin is the body of the test code + mkdir "$name" && + write_script "$name/$name.sh" "$TEST_SHELL_PATH" <<-EOF && + test_description='A test of test-lib.sh itself' + + # Point to the t/test-lib.sh, which isn't in ../ as usual + . "\$TEST_DIRECTORY"/test-lib.sh + EOF + cat >>"$name/$name.sh" +} + +_run_sub_test_lib_test_common () { + cmp_op="$1" want_code="$2" name="$3" # stdin is the body of the test code + shift 3 + + # intercept pseudo-options at the front of the argument list that we + # will not pass to child script + skip= + while test $# -gt 0 + do + case "$1" in + --skip=*) + skip=${1#--*=} + shift + ;; + *) + break + ;; + esac + done + + ( + cd "$name" && + + # Pretend we're not running under a test harness, whether we + # are or not. The test-lib output depends on the setting of + # this variable, so we need a stable setting under which to run + # the sub-test. + sane_unset HARNESS_ACTIVE && + + export TEST_DIRECTORY && + # The child test re-sources GIT-BUILD-OPTIONS and may thus + # override the test output directory. We thus pass it as an + # explicit override to the child. + TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) && + export TEST_OUTPUT_DIRECTORY_OVERRIDE && + GIT_SKIP_TESTS=$skip && + export GIT_SKIP_TESTS && + sane_unset GIT_TEST_FAIL_PREREQS && + ./"$name.sh" "$@" >out 2>err; + ret=$? && + test "$ret" "$cmp_op" "$want_code" + ) +} + +write_and_run_sub_test_lib_test () { + name="$1" descr="$2" # stdin is the body of the test code + write_sub_test_lib_test "$@" || return 1 + _run_sub_test_lib_test_common -eq 0 "$@" +} + +write_and_run_sub_test_lib_test_err () { + name="$1" descr="$2" # stdin is the body of the test code + write_sub_test_lib_test "$@" || return 1 + _run_sub_test_lib_test_common -eq 1 "$@" +} + +run_sub_test_lib_test () { + _run_sub_test_lib_test_common -eq 0 "$@" +} + +run_sub_test_lib_test_err () { + _run_sub_test_lib_test_common -eq 1 "$@" +} + +_check_sub_test_lib_test_common () { + name="$1" && + sed -e 's/^> //' -e 's/Z$//' >"$name"/expect.out && + test_cmp "$name"/expect.out "$name"/out +} + +check_sub_test_lib_test () { + name="$1" # stdin is the expected output from the test + _check_sub_test_lib_test_common "$name" && + test_must_be_empty "$name"/err +} + +check_sub_test_lib_test_err () { + name="$1" # stdin is the expected output from the test + _check_sub_test_lib_test_common "$name" && + # expected error output is in descriptor 3 + sed -e 's/^> //' -e 's/Z$//' <&3 >"$name"/expect.err && + test_cmp "$name"/expect.err "$name"/err +} diff --git a/t/oid-info/oid b/t/oid-info/oid index a754970523..7547d2c790 100644 --- a/t/oid-info/oid +++ b/t/oid-info/oid @@ -27,3 +27,5 @@ numeric sha1:0123456789012345678901234567890123456789 numeric sha256:0123456789012345678901234567890123456789012345678901234567890123 deadbeef sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef deadbeef sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef +deadbeef_short sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbee +deadbeef_short sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbee diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 82c0df4553..575d2000cc 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -17,8 +17,8 @@ sub get_times { my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3; return ($rt, $4, $5); # size - } elsif ($line =~ /^\d+$/) { - return $&; + } elsif ($line =~ /^\s*(\d+)$/) { + return $1; } else { die "bad input line: $line"; } diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh index 6e924f5fa3..ed366e2e12 100755 --- a/t/perf/p0071-sort.sh +++ b/t/perf/p0071-sort.sh @@ -11,16 +11,42 @@ test_expect_success 'setup' ' git cat-file --batch >unsorted ' -test_perf 'sort(1)' ' - sort <unsorted >expect +test_perf 'sort(1) unsorted' ' + sort <unsorted >sorted ' -test_perf 'string_list_sort()' ' - test-tool string-list sort <unsorted >actual +test_expect_success 'reverse' ' + sort -r <unsorted >reversed ' -test_expect_success 'string_list_sort() sorts like sort(1)' ' - test_cmp_bin expect actual -' +for file in sorted reversed +do + test_perf "sort(1) $file" " + sort <$file >actual + " +done + +for file in unsorted sorted reversed +do + + test_perf "string_list_sort() $file" " + test-tool string-list sort <$file >actual + " + + test_expect_success "string_list_sort() $file sorts like sort(1)" " + test_cmp_bin sorted actual + " +done + +for file in unsorted sorted reversed +do + test_perf "llist_mergesort() $file" " + test-tool mergesort sort <$file >actual + " + + test_expect_success "llist_mergesort() $file sorts like sort(1)" " + test_cmp_bin sorted actual + " +done test_done diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index 7a0bb29448..43d5a34e8c 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -18,7 +18,7 @@ test_expect_success 'setup rebasing on top of a lot of changes' ' test_tick && git commit -m commit$i unrelated-file$i && echo change$i >unrelated-file$i && - test_seq 1000 | tac >>unrelated-file$i && + test_seq 1000 | sort -nr >>unrelated-file$i && git add unrelated-file$i && test_tick && git commit -m commit$i-reverse unrelated-file$i || diff --git a/t/perf/p5326-multi-pack-bitmaps.sh b/t/perf/p5326-multi-pack-bitmaps.sh index 5845109ac7..f2fa228f16 100755 --- a/t/perf/p5326-multi-pack-bitmaps.sh +++ b/t/perf/p5326-multi-pack-bitmaps.sh @@ -6,15 +6,24 @@ test_description='Tests performance using midx bitmaps' test_perf_large_repo -test_expect_success 'enable multi-pack index' ' - git config core.multiPackIndex true +# we need to create the tag up front such that it is covered by the repack and +# thus by generated bitmaps. +test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD +' + +test_expect_success 'start with bitmapped pack' ' + git repack -adb ' test_perf 'setup multi-pack index' ' - git repack -ad && git multi-pack-index write --bitmap ' +test_expect_success 'drop pack bitmap' ' + rm -f .git/objects/pack/pack-*.bitmap +' + test_full_bitmap test_expect_success 'create partial bitmap state' ' diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index f5ed092ee5..a1b5d2804d 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -230,6 +230,7 @@ test_perf_ () { test_ok_ "$1" fi "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".result + rm test_time.* } test_perf () { diff --git a/t/perf/run b/t/perf/run index d19dec258a..55219aa405 100755 --- a/t/perf/run +++ b/t/perf/run @@ -74,7 +74,7 @@ set_git_test_installed () { mydir=$1 mydir_abs=$(cd $mydir && pwd) - mydir_abs_wrappers="$mydir_abs_wrappers/bin-wrappers" + mydir_abs_wrappers="$mydir_abs/bin-wrappers" if test -d "$mydir_abs_wrappers" then GIT_TEST_INSTALLED=$mydir_abs_wrappers diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 5c342de713..b007f0efef 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,6 +19,7 @@ modification *should* take notice and update the test vectors here. ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-subtest.sh try_local_xy () { local x="local" y="alsolocal" && @@ -66,95 +67,8 @@ test_expect_success 'success is reported like this' ' : ' -_run_sub_test_lib_test_common () { - neg="$1" name="$2" descr="$3" # stdin is the body of the test code - shift 3 - - # intercept pseudo-options at the front of the argument list that we - # will not pass to child script - skip= - while test $# -gt 0 - do - case "$1" in - --skip=*) - skip=${1#--*=} - shift - ;; - *) - break - ;; - esac - done - - mkdir "$name" && - ( - # Pretend we're not running under a test harness, whether we - # are or not. The test-lib output depends on the setting of - # this variable, so we need a stable setting under which to run - # the sub-test. - sane_unset HARNESS_ACTIVE && - cd "$name" && - write_script "$name.sh" "$TEST_SHELL_PATH" <<-EOF && - test_description='$descr (run in sub test-lib) - - This is run in a sub test-lib so that we do not get incorrect - passing metrics - ' - - # Point to the t/test-lib.sh, which isn't in ../ as usual - . "\$TEST_DIRECTORY"/test-lib.sh - EOF - cat >>"$name.sh" && - export TEST_DIRECTORY && - # The child test re-sources GIT-BUILD-OPTIONS and may thus - # override the test output directory. We thus pass it as an - # explicit override to the child. - TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) && - export TEST_OUTPUT_DIRECTORY_OVERRIDE && - GIT_SKIP_TESTS=$skip && - export GIT_SKIP_TESTS && - sane_unset GIT_TEST_FAIL_PREREQS && - if test -z "$neg" - then - ./"$name.sh" "$@" >out 2>err - else - ! ./"$name.sh" "$@" >out 2>err - fi - ) -} - -run_sub_test_lib_test () { - _run_sub_test_lib_test_common '' "$@" -} - -run_sub_test_lib_test_err () { - _run_sub_test_lib_test_common '!' "$@" -} - -check_sub_test_lib_test () { - name="$1" # stdin is the expected output from the test - ( - cd "$name" && - test_must_be_empty err && - sed -e 's/^> //' -e 's/Z$//' >expect && - test_cmp expect out - ) -} - -check_sub_test_lib_test_err () { - name="$1" # stdin is the expected output from the test - # expected error output is in descriptor 3 - ( - cd "$name" && - sed -e 's/^> //' -e 's/Z$//' >expect.out && - test_cmp expect.out out && - sed -e 's/^> //' -e 's/Z$//' <&3 >expect.err && - test_cmp expect.err err - ) -} - -test_expect_success 'pretend we have a fully passing test suite' ' - run_sub_test_lib_test full-pass "3 passing tests" <<-\EOF && +test_expect_success 'subtest: 3 passing tests' ' + write_and_run_sub_test_lib_test full-pass <<-\EOF && for i in 1 2 3 do test_expect_success "passing test #$i" "true" @@ -170,9 +84,8 @@ test_expect_success 'pretend we have a fully passing test suite' ' EOF ' -test_expect_success 'pretend we have a partially passing test suite' ' - run_sub_test_lib_test_err \ - partial-pass "2/3 tests passing" <<-\EOF && +test_expect_success 'subtest: 2/3 tests passing' ' + write_and_run_sub_test_lib_test_err partial-pass <<-\EOF && test_expect_success "passing test #1" "true" test_expect_success "failing test #2" "false" test_expect_success "passing test #3" "true" @@ -188,8 +101,8 @@ test_expect_success 'pretend we have a partially passing test suite' ' EOF ' -test_expect_success 'pretend we have a known breakage' ' - run_sub_test_lib_test failing-todo "A failing TODO test" <<-\EOF && +test_expect_success 'subtest: a failing TODO test' ' + write_and_run_sub_test_lib_test failing-todo <<-\EOF && test_expect_success "passing test" "true" test_expect_failure "pretend we have a known breakage" "false" test_done @@ -203,8 +116,8 @@ test_expect_success 'pretend we have a known breakage' ' EOF ' -test_expect_success 'pretend we have fixed a known breakage' ' - run_sub_test_lib_test passing-todo "A passing TODO test" <<-\EOF && +test_expect_success 'subtest: a passing TODO test' ' + write_and_run_sub_test_lib_test passing-todo <<-\EOF && test_expect_failure "pretend we have fixed a known breakage" "true" test_done EOF @@ -215,9 +128,8 @@ test_expect_success 'pretend we have fixed a known breakage' ' EOF ' -test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' ' - run_sub_test_lib_test partially-passing-todos \ - "2 TODO tests, one passing" <<-\EOF && +test_expect_success 'subtest: 2 TODO tests, one passin' ' + write_and_run_sub_test_lib_test partially-passing-todos <<-\EOF && test_expect_failure "pretend we have a known breakage" "false" test_expect_success "pretend we have a passing test" "true" test_expect_failure "pretend we have fixed another known breakage" "true" @@ -234,9 +146,8 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su EOF ' -test_expect_success 'pretend we have a pass, fail, and known breakage' ' - run_sub_test_lib_test_err \ - mixed-results1 "mixed results #1" <<-\EOF && +test_expect_success 'subtest: mixed results: pass, failure and a TODO test' ' + write_and_run_sub_test_lib_test_err mixed-results1 <<-\EOF && test_expect_success "passing test" "true" test_expect_success "failing test" "false" test_expect_failure "pretend we have a known breakage" "false" @@ -253,9 +164,8 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' ' EOF ' -test_expect_success 'pretend we have a mix of all possible results' ' - run_sub_test_lib_test_err \ - mixed-results2 "mixed results #2" <<-\EOF && +test_expect_success 'subtest: mixed results: a mixture of all possible results' ' + write_and_run_sub_test_lib_test_err mixed-results2 <<-\EOF && test_expect_success "passing test" "true" test_expect_success "passing test" "true" test_expect_success "passing test" "true" @@ -289,9 +199,8 @@ test_expect_success 'pretend we have a mix of all possible results' ' EOF ' -test_expect_success 'test --verbose' ' - run_sub_test_lib_test_err \ - t1234-verbose "test verbose" --verbose <<-\EOF && +test_expect_success 'subtest: --verbose option' ' + write_and_run_sub_test_lib_test_err t1234-verbose --verbose <<-\EOF && test_expect_success "passing test" true test_expect_success "test with output" "echo foo" test_expect_success "failing test" false @@ -316,19 +225,14 @@ test_expect_success 'test --verbose' ' EOF ' -test_expect_success 'test --verbose-only' ' +test_expect_success 'subtest: --verbose-only option' ' run_sub_test_lib_test_err \ - t2345-verbose-only-2 "test verbose-only=2" \ - --verbose-only=2 <<-\EOF && - test_expect_success "passing test" true - test_expect_success "test with output" "echo foo" - test_expect_success "failing test" false - test_done - EOF - check_sub_test_lib_test t2345-verbose-only-2 <<-\EOF + t1234-verbose \ + --verbose-only=2 && + check_sub_test_lib_test t1234-verbose <<-\EOF > ok 1 - passing test > Z - > expecting success of 2345.2 '\''test with output'\'': echo foo + > expecting success of 1234.2 '\''test with output'\'': echo foo > foo > ok 2 - test with output > Z @@ -339,18 +243,11 @@ test_expect_success 'test --verbose-only' ' EOF ' -test_expect_success 'GIT_SKIP_TESTS' ' +test_expect_success 'subtest: skip one with GIT_SKIP_TESTS' ' ( - run_sub_test_lib_test git-skip-tests-basic \ - "GIT_SKIP_TESTS" \ - --skip="git.2" <<-\EOF && - for i in 1 2 3 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test git-skip-tests-basic <<-\EOF + run_sub_test_lib_test full-pass \ + --skip="full.2" && + check_sub_test_lib_test full-pass <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) > ok 3 - passing test #3 @@ -360,10 +257,9 @@ test_expect_success 'GIT_SKIP_TESTS' ' ) ' -test_expect_success 'GIT_SKIP_TESTS several tests' ' +test_expect_success 'subtest: skip several with GIT_SKIP_TESTS' ' ( - run_sub_test_lib_test git-skip-tests-several \ - "GIT_SKIP_TESTS several tests" \ + write_and_run_sub_test_lib_test git-skip-tests-several \ --skip="git.2 git.5" <<-\EOF && for i in 1 2 3 4 5 6 do @@ -384,18 +280,11 @@ test_expect_success 'GIT_SKIP_TESTS several tests' ' ) ' -test_expect_success 'GIT_SKIP_TESTS sh pattern' ' +test_expect_success 'subtest: sh pattern skipping with GIT_SKIP_TESTS' ' ( - run_sub_test_lib_test git-skip-tests-sh-pattern \ - "GIT_SKIP_TESTS sh pattern" \ - --skip="git.[2-5]" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test git-skip-tests-sh-pattern <<-\EOF + run_sub_test_lib_test git-skip-tests-several \ + --skip="git.[2-5]" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) > ok 3 # skip passing test #3 (GIT_SKIP_TESTS) @@ -408,35 +297,23 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' ' ) ' -test_expect_success 'GIT_SKIP_TESTS entire suite' ' +test_expect_success 'subtest: skip entire test suite with GIT_SKIP_TESTS' ' ( - run_sub_test_lib_test git-skip-tests-entire-suite \ - "GIT_SKIP_TESTS entire suite" \ - --skip="git" <<-\EOF && - for i in 1 2 3 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test git-skip-tests-entire-suite <<-\EOF + GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS && + run_sub_test_lib_test git-skip-tests-several \ + --skip="git" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > 1..0 # SKIP skip all tests in git EOF ) ' -test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' ' +test_expect_success 'subtest: GIT_SKIP_TESTS does not skip unmatched suite' ' ( - run_sub_test_lib_test git-skip-tests-unmatched-suite \ - "GIT_SKIP_TESTS does not skip unmatched suite" \ - --skip="notgit" <<-\EOF && - for i in 1 2 3 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\EOF + GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS && + run_sub_test_lib_test full-pass \ + --skip="notfull" && + check_sub_test_lib_test full-pass <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -446,16 +323,9 @@ test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' ' ) ' -test_expect_success '--run basic' ' - run_sub_test_lib_test run-basic \ - "--run basic" --run="1,3,5" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-basic <<-\EOF +test_expect_success 'subtest: --run basic' ' + run_sub_test_lib_test git-skip-tests-several --run="1,3,5" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -467,16 +337,10 @@ test_expect_success '--run basic' ' EOF ' -test_expect_success '--run with a range' ' - run_sub_test_lib_test run-range \ - "--run with a range" --run="1-3" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-range <<-\EOF +test_expect_success 'subtest: --run with a range' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="1-3" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -488,16 +352,10 @@ test_expect_success '--run with a range' ' EOF ' -test_expect_success '--run with two ranges' ' - run_sub_test_lib_test run-two-ranges \ - "--run with two ranges" --run="1-2,5-6" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-two-ranges <<-\EOF +test_expect_success 'subtest: --run with two ranges' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="1-2,5-6" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -509,16 +367,10 @@ test_expect_success '--run with two ranges' ' EOF ' -test_expect_success '--run with a left open range' ' - run_sub_test_lib_test run-left-open-range \ - "--run with a left open range" --run="-3" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-left-open-range <<-\EOF +test_expect_success 'subtest: --run with a left open range' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="-3" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -530,16 +382,10 @@ test_expect_success '--run with a left open range' ' EOF ' -test_expect_success '--run with a right open range' ' - run_sub_test_lib_test run-right-open-range \ - "--run with a right open range" --run="4-" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-right-open-range <<-\EOF +test_expect_success 'subtest: --run with a right open range' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="4-" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 # skip passing test #2 (--run) > ok 3 # skip passing test #3 (--run) @@ -551,16 +397,10 @@ test_expect_success '--run with a right open range' ' EOF ' -test_expect_success '--run with basic negation' ' - run_sub_test_lib_test run-basic-neg \ - "--run with basic negation" --run="!3" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-basic-neg <<-\EOF +test_expect_success 'subtest: --run with basic negation' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="!3" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -572,16 +412,10 @@ test_expect_success '--run with basic negation' ' EOF ' -test_expect_success '--run with two negations' ' - run_sub_test_lib_test run-two-neg \ - "--run with two negations" --run="!3,!6" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-two-neg <<-\EOF +test_expect_success 'subtest: --run with two negations' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="!3,!6" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -593,16 +427,10 @@ test_expect_success '--run with two negations' ' EOF ' -test_expect_success '--run a range and negation' ' - run_sub_test_lib_test run-range-and-neg \ - "--run a range and negation" --run="-4,!2" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-range-and-neg <<-\EOF +test_expect_success 'subtest: --run a range and negation' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="-4,!2" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -614,16 +442,10 @@ test_expect_success '--run a range and negation' ' EOF ' -test_expect_success '--run range negation' ' - run_sub_test_lib_test run-range-neg \ - "--run range negation" --run="!1-3" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-range-neg <<-\EOF +test_expect_success 'subtest: --run range negation' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="!1-3" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 # skip passing test #2 (--run) > ok 3 # skip passing test #3 (--run) @@ -635,17 +457,10 @@ test_expect_success '--run range negation' ' EOF ' -test_expect_success '--run include, exclude and include' ' - run_sub_test_lib_test run-inc-neg-inc \ - "--run include, exclude and include" \ - --run="1-5,!1-3,2" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-inc-neg-inc <<-\EOF +test_expect_success 'subtest: --run include, exclude and include' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="1-5,!1-3,2" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -657,17 +472,10 @@ test_expect_success '--run include, exclude and include' ' EOF ' -test_expect_success '--run include, exclude and include, comma separated' ' - run_sub_test_lib_test run-inc-neg-inc-comma \ - "--run include, exclude and include, comma separated" \ - --run=1-5,!1-3,2 <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-inc-neg-inc-comma <<-\EOF +test_expect_success 'subtest: --run include, exclude and include, comma separated' ' + run_sub_test_lib_test git-skip-tests-several \ + --run=1-5,!1-3,2 && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -679,17 +487,10 @@ test_expect_success '--run include, exclude and include, comma separated' ' EOF ' -test_expect_success '--run exclude and include' ' - run_sub_test_lib_test run-neg-inc \ - "--run exclude and include" \ - --run="!3-,5" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-neg-inc <<-\EOF +test_expect_success 'subtest: --run exclude and include' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="!3-,5" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -701,17 +502,10 @@ test_expect_success '--run exclude and include' ' EOF ' -test_expect_success '--run empty selectors' ' - run_sub_test_lib_test run-empty-sel \ - "--run empty selectors" \ - --run="1,,3,,,5" <<-\EOF && - for i in 1 2 3 4 5 6 - do - test_expect_success "passing test #$i" "true" - done - test_done - EOF - check_sub_test_lib_test run-empty-sel <<-\EOF +test_expect_success 'subtest: --run empty selectors' ' + run_sub_test_lib_test git-skip-tests-several \ + --run="1,,3,,,5" && + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -723,9 +517,8 @@ test_expect_success '--run empty selectors' ' EOF ' -test_expect_success '--run substring selector' ' - run_sub_test_lib_test run-substring-selector \ - "--run empty selectors" \ +test_expect_success 'subtest: --run substring selector' ' + write_and_run_sub_test_lib_test run-substring-selector \ --run="relevant" <<-\EOF && test_expect_success "relevant test" "true" for i in 1 2 3 4 5 6 @@ -747,9 +540,8 @@ test_expect_success '--run substring selector' ' EOF ' -test_expect_success '--run keyword selection' ' - run_sub_test_lib_test_err run-inv-range-start \ - "--run invalid range start" \ +test_expect_success 'subtest: --run keyword selection' ' + write_and_run_sub_test_lib_test_err run-inv-range-start \ --run="a-5" <<-\EOF && test_expect_success "passing test #1" "true" test_done @@ -762,14 +554,10 @@ test_expect_success '--run keyword selection' ' EOF_ERR ' -test_expect_success '--run invalid range end' ' - run_sub_test_lib_test_err run-inv-range-end \ - "--run invalid range end" \ - --run="1-z" <<-\EOF && - test_expect_success "passing test #1" "true" - test_done - EOF - check_sub_test_lib_test_err run-inv-range-end \ +test_expect_success 'subtest: --run invalid range end' ' + run_sub_test_lib_test_err run-inv-range-start \ + --run="1-z" && + check_sub_test_lib_test_err run-inv-range-start \ <<-\EOF_OUT 3<<-EOF_ERR > FATAL: Unexpected exit with code 1 EOF_OUT @@ -777,8 +565,8 @@ test_expect_success '--run invalid range end' ' EOF_ERR ' -test_expect_success 'tests respect prerequisites' ' - run_sub_test_lib_test prereqs "tests respect prereqs" <<-\EOF && +test_expect_success 'subtest: tests respect prerequisites' ' + write_and_run_sub_test_lib_test prereqs <<-\EOF && test_set_prereq HAVEIT test_expect_success HAVEIT "prereq is satisfied" "true" @@ -807,8 +595,8 @@ test_expect_success 'tests respect prerequisites' ' EOF ' -test_expect_success 'tests respect lazy prerequisites' ' - run_sub_test_lib_test lazy-prereqs "respect lazy prereqs" <<-\EOF && +test_expect_success 'subtest: tests respect lazy prerequisites' ' + write_and_run_sub_test_lib_test lazy-prereqs <<-\EOF && test_lazy_prereq LAZY_TRUE true test_expect_success LAZY_TRUE "lazy prereq is satisifed" "true" @@ -831,8 +619,8 @@ test_expect_success 'tests respect lazy prerequisites' ' EOF ' -test_expect_success 'nested lazy prerequisites' ' - run_sub_test_lib_test nested-lazy "nested lazy prereqs" <<-\EOF && +test_expect_success 'subtest: nested lazy prerequisites' ' + write_and_run_sub_test_lib_test nested-lazy <<-\EOF && test_lazy_prereq NESTED_INNER " >inner && @@ -857,9 +645,9 @@ test_expect_success 'nested lazy prerequisites' ' EOF ' -test_expect_success 'lazy prereqs do not turn off tracing' ' - run_sub_test_lib_test lazy-prereq-and-tracing \ - "lazy prereqs and -x" -v -x <<-\EOF && +test_expect_success 'subtest: lazy prereqs do not turn off tracing' ' + write_and_run_sub_test_lib_test lazy-prereq-and-tracing \ + -v -x <<-\EOF && test_lazy_prereq LAZY true test_expect_success lazy "test_have_prereq LAZY && echo trace" @@ -870,8 +658,8 @@ test_expect_success 'lazy prereqs do not turn off tracing' ' grep "echo trace" lazy-prereq-and-tracing/err ' -test_expect_success 'tests clean up after themselves' ' - run_sub_test_lib_test cleanup "test with cleanup" <<-\EOF && +test_expect_success 'subtest: tests clean up after themselves' ' + write_and_run_sub_test_lib_test cleanup <<-\EOF && clean=no test_expect_success "do cleanup" " test_when_finished clean=yes @@ -890,9 +678,9 @@ test_expect_success 'tests clean up after themselves' ' EOF ' -test_expect_success 'tests clean up even on failures' ' - run_sub_test_lib_test_err \ - failing-cleanup "Failing tests with cleanup commands" <<-\EOF && +test_expect_success 'subtest: tests clean up even on failures' ' + write_and_run_sub_test_lib_test_err \ + failing-cleanup <<-\EOF && test_expect_success "tests clean up even after a failure" " touch clean-after-failure && test_when_finished rm clean-after-failure && @@ -919,9 +707,9 @@ test_expect_success 'tests clean up even on failures' ' EOF ' -test_expect_success 'test_atexit is run' ' - run_sub_test_lib_test_err \ - atexit-cleanup "Run atexit commands" -i <<-\EOF && +test_expect_success 'subtest: test_atexit is run' ' + write_and_run_sub_test_lib_test_err \ + atexit-cleanup -i <<-\EOF && test_expect_success "tests clean up even after a failure" " > ../../clean-atexit && test_atexit rm ../../clean-atexit && diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh index e3137d638e..37d68ef03b 100755 --- a/t/t0004-unwritable.sh +++ b/t/t0004-unwritable.sh @@ -2,6 +2,7 @@ test_description='detect unwritable repository and fail correctly' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' @@ -21,7 +22,7 @@ test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable reposi test_must_fail git write-tree ' -test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' ' +test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' ' test_when_finished "chmod 775 .git/objects .git/objects/??" && chmod a-w .git/objects .git/objects/?? && test_must_fail git commit -m second diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh index 5343ffd3f9..e094975b13 100755 --- a/t/t0011-hashmap.sh +++ b/t/t0011-hashmap.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test hashmap and string hash functions' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_hashmap() { diff --git a/t/t0012-help.sh b/t/t0012-help.sh index 913f34c8e9..91b68c74a1 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -34,6 +34,18 @@ test_expect_success 'basic help commands' ' git help -a >/dev/null ' +test_expect_success 'invalid usage' ' + test_expect_code 129 git help -g add && + test_expect_code 129 git help -a -c && + + test_expect_code 129 git help -g add && + test_expect_code 129 git help -a -g && + + test_expect_code 129 git help -g -c && + test_expect_code 129 git help --config-for-completion add && + test_expect_code 129 git help --config-sections-for-completion add +' + test_expect_success "works for commands and guides by default" ' configure_help && git help status && @@ -89,6 +101,43 @@ test_expect_success 'git help succeeds without git.html' ' test_cmp expect test-browser.log ' +test_expect_success 'git help -c' ' + git help -c >help.output && + cat >expect <<-\EOF && + + '\''git help config'\'' for more information + EOF + grep -v -E \ + -e "^[^.]+\.[^.]+$" \ + -e "^[^.]+\.[^.]+\.[^.]+$" \ + help.output >actual && + test_cmp expect actual +' + +test_expect_success 'git help --config-for-completion' ' + git help -c >human && + grep -E \ + -e "^[^.]+\.[^.]+$" \ + -e "^[^.]+\.[^.]+\.[^.]+$" human | + sed -e "s/\*.*//" -e "s/<.*//" | + sort -u >human.munged && + + git help --config-for-completion >vars && + test_cmp human.munged vars +' + +test_expect_success 'git help --config-sections-for-completion' ' + git help -c >human && + grep -E \ + -e "^[^.]+\.[^.]+$" \ + -e "^[^.]+\.[^.]+\.[^.]+$" human | + sed -e "s/\..*//" | + sort -u >human.munged && + + git help --config-sections-for-completion >sections && + test_cmp human.munged sections +' + test_expect_success 'generate builtin list' ' git --list-cmds=builtins >builtins ' diff --git a/t/t0016-oidmap.sh b/t/t0016-oidmap.sh index 31f8276ba8..0faef1f4f1 100755 --- a/t/t0016-oidmap.sh +++ b/t/t0016-oidmap.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test oidmap' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # This purposefully is very similar to t0011-hashmap.sh diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh index 4a159f99e4..2e42fba956 100755 --- a/t/t0017-env-helper.sh +++ b/t/t0017-env-helper.sh @@ -2,6 +2,7 @@ test_description='test env--helper' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh index 39e5e4b34f..c13057a4ca 100755 --- a/t/t0018-advice.sh +++ b/t/t0018-advice.sh @@ -2,6 +2,7 @@ test_description='Test advise_if_enabled functionality' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'advice should be printed when config variable is unset' ' diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index 0c24a0f9a3..ae1ca380c1 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -5,6 +5,7 @@ test_description='git stripspace' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh t40='A quick brown fox jumps over the lazy do' diff --git a/t/t0063-string-list.sh b/t/t0063-string-list.sh index c6ee9f66b1..46d4839194 100755 --- a/t/t0063-string-list.sh +++ b/t/t0063-string-list.sh @@ -5,6 +5,7 @@ test_description='Test string list functionality' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_split () { diff --git a/t/t0071-sort.sh b/t/t0071-sort.sh new file mode 100755 index 0000000000..a8ab174879 --- /dev/null +++ b/t/t0071-sort.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +test_description='verify sort functions' + +. ./test-lib.sh + +test_expect_success 'llist_mergesort()' ' + test-tool mergesort test +' + +test_done diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh index 526304ff95..eeedbfa919 100755 --- a/t/t0091-bugreport.sh +++ b/t/t0091-bugreport.sh @@ -2,6 +2,7 @@ test_description='git bugreport' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Headers "[System Info]" will be followed by a non-empty line if we put some diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 1057a96b24..d1115528cb 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -20,6 +20,8 @@ In the test, these paths are used: rezrov - in H, deleted in M yomin - not in H or M ' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-read-tree.sh diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 18b3779ccb..658628375c 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -315,39 +315,238 @@ test_expect_success '%(deltabase) reports packed delta bases' ' } ' -bogus_type="bogus" -bogus_content="bogus" -bogus_size=$(strlen "$bogus_content") -bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin) +test_expect_success 'setup bogus data' ' + bogus_short_type="bogus" && + bogus_short_content="bogus" && + bogus_short_size=$(strlen "$bogus_short_content") && + bogus_short_sha1=$(echo_without_newline "$bogus_short_content" | git hash-object -t $bogus_short_type --literally -w --stdin) && + + bogus_long_type="abcdefghijklmnopqrstuvwxyz1234679" && + bogus_long_content="bogus" && + bogus_long_size=$(strlen "$bogus_long_content") && + bogus_long_sha1=$(echo_without_newline "$bogus_long_content" | git hash-object -t $bogus_long_type --literally -w --stdin) +' + +for arg1 in '' --allow-unknown-type +do + for arg2 in -s -t -p + do + if test "$arg1" = "--allow-unknown-type" && test "$arg2" = "-p" + then + continue + fi + + + test_expect_success "cat-file $arg1 $arg2 error on bogus short OID" ' + cat >expect <<-\EOF && + fatal: invalid object type + EOF + + if test "$arg1" = "--allow-unknown-type" + then + git cat-file $arg1 $arg2 $bogus_short_sha1 + else + test_must_fail git cat-file $arg1 $arg2 $bogus_short_sha1 >out 2>actual && + test_must_be_empty out && + test_cmp expect actual + fi + ' + + test_expect_success "cat-file $arg1 $arg2 error on bogus full OID" ' + if test "$arg2" = "-p" + then + cat >expect <<-EOF + error: header for $bogus_long_sha1 too long, exceeds 32 bytes + fatal: Not a valid object name $bogus_long_sha1 + EOF + else + cat >expect <<-EOF + error: header for $bogus_long_sha1 too long, exceeds 32 bytes + fatal: git cat-file: could not get object info + EOF + fi && + + if test "$arg1" = "--allow-unknown-type" + then + git cat-file $arg1 $arg2 $bogus_short_sha1 + else + test_must_fail git cat-file $arg1 $arg2 $bogus_long_sha1 >out 2>actual && + test_must_be_empty out && + test_cmp expect actual + fi + ' + + test_expect_success "cat-file $arg1 $arg2 error on missing short OID" ' + cat >expect.err <<-EOF && + fatal: Not a valid object name $(test_oid deadbeef_short) + EOF + test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef_short) >out 2>err.actual && + test_must_be_empty out + ' + + test_expect_success "cat-file $arg1 $arg2 error on missing full OID" ' + if test "$arg2" = "-p" + then + cat >expect.err <<-EOF + fatal: Not a valid object name $(test_oid deadbeef) + EOF + else + cat >expect.err <<-\EOF + fatal: git cat-file: could not get object info + EOF + fi && + test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef) >out 2>err.actual && + test_must_be_empty out && + test_cmp expect.err err.actual + ' + done +done + +test_expect_success '-e is OK with a broken object without --allow-unknown-type' ' + git cat-file -e $bogus_short_sha1 +' + +test_expect_success '-e can not be combined with --allow-unknown-type' ' + test_expect_code 128 git cat-file -e --allow-unknown-type $bogus_short_sha1 +' + +test_expect_success '-p cannot print a broken object even with --allow-unknown-type' ' + test_must_fail git cat-file -p $bogus_short_sha1 && + test_expect_code 128 git cat-file -p --allow-unknown-type $bogus_short_sha1 +' + +test_expect_success '<type> <hash> does not work with objects of broken types' ' + cat >err.expect <<-\EOF && + fatal: invalid object type "bogus" + EOF + test_must_fail git cat-file $bogus_short_type $bogus_short_sha1 2>err.actual && + test_cmp err.expect err.actual +' + +test_expect_success 'broken types combined with --batch and --batch-check' ' + echo $bogus_short_sha1 >bogus-oid && + + cat >err.expect <<-\EOF && + fatal: invalid object type + EOF + + test_must_fail git cat-file --batch <bogus-oid 2>err.actual && + test_cmp err.expect err.actual && + + test_must_fail git cat-file --batch-check <bogus-oid 2>err.actual && + test_cmp err.expect err.actual +' + +test_expect_success 'the --batch and --batch-check options do not combine with --allow-unknown-type' ' + test_expect_code 128 git cat-file --batch --allow-unknown-type <bogus-oid && + test_expect_code 128 git cat-file --batch-check --allow-unknown-type <bogus-oid +' + +test_expect_success 'the --allow-unknown-type option does not consider replacement refs' ' + cat >expect <<-EOF && + $bogus_short_type + EOF + git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && + test_cmp expect actual && + + # Create it manually, as "git replace" will die on bogus + # types. + head=$(git rev-parse --verify HEAD) && + test_when_finished "rm -rf .git/refs/replace" && + mkdir -p .git/refs/replace && + echo $head >.git/refs/replace/$bogus_short_sha1 && + + cat >expect <<-EOF && + commit + EOF + git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && + test_cmp expect actual +' test_expect_success "Type of broken object is correct" ' - echo $bogus_type >expect && - git cat-file -t --allow-unknown-type $bogus_sha1 >actual && + echo $bogus_short_type >expect && + git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && test_cmp expect actual ' test_expect_success "Size of broken object is correct" ' - echo $bogus_size >expect && - git cat-file -s --allow-unknown-type $bogus_sha1 >actual && + echo $bogus_short_size >expect && + git cat-file -s --allow-unknown-type $bogus_short_sha1 >actual && test_cmp expect actual ' -bogus_type="abcdefghijklmnopqrstuvwxyz1234679" -bogus_content="bogus" -bogus_size=$(strlen "$bogus_content") -bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin) + +test_expect_success 'clean up broken object' ' + rm .git/objects/$(test_oid_to_path $bogus_short_sha1) +' test_expect_success "Type of broken object is correct when type is large" ' - echo $bogus_type >expect && - git cat-file -t --allow-unknown-type $bogus_sha1 >actual && + echo $bogus_long_type >expect && + git cat-file -t --allow-unknown-type $bogus_long_sha1 >actual && test_cmp expect actual ' test_expect_success "Size of large broken object is correct when type is large" ' - echo $bogus_size >expect && - git cat-file -s --allow-unknown-type $bogus_sha1 >actual && + echo $bogus_long_size >expect && + git cat-file -s --allow-unknown-type $bogus_long_sha1 >actual && test_cmp expect actual ' +test_expect_success 'clean up broken object' ' + rm .git/objects/$(test_oid_to_path $bogus_long_sha1) +' + +test_expect_success 'cat-file -t and -s on corrupt loose object' ' + git init --bare corrupt-loose.git && + ( + cd corrupt-loose.git && + + # Setup and create the empty blob and its path + empty_path=$(git rev-parse --git-path objects/$(test_oid_to_path "$EMPTY_BLOB")) && + git hash-object -w --stdin </dev/null && + + # Create another blob and its path + echo other >other.blob && + other_blob=$(git hash-object -w --stdin <other.blob) && + other_path=$(git rev-parse --git-path objects/$(test_oid_to_path "$other_blob")) && + + # Before the swap the size is 0 + cat >out.expect <<-EOF && + 0 + EOF + git cat-file -s "$EMPTY_BLOB" >out.actual 2>err.actual && + test_must_be_empty err.actual && + test_cmp out.expect out.actual && + + # Swap the two to corrupt the repository + mv -f "$other_path" "$empty_path" && + test_must_fail git fsck 2>err.fsck && + grep "hash-path mismatch" err.fsck && + + # confirm that cat-file is reading the new swapped-in + # blob... + cat >out.expect <<-EOF && + blob + EOF + git cat-file -t "$EMPTY_BLOB" >out.actual 2>err.actual && + test_must_be_empty err.actual && + test_cmp out.expect out.actual && + + # ... since it has a different size now. + cat >out.expect <<-EOF && + 6 + EOF + git cat-file -s "$EMPTY_BLOB" >out.actual 2>err.actual && + test_must_be_empty err.actual && + test_cmp out.expect out.actual && + + # So far "cat-file" has been happy to spew the found + # content out as-is. Try to make it zlib-invalid. + mv -f other.blob "$empty_path" && + test_must_fail git fsck 2>err.fsck && + grep "^error: inflate: data stream error (" err.fsck + ) +' + # Tests for git cat-file --follow-symlinks test_expect_success 'prep for symlink tests' ' echo_without_newline "$hello_content" >morx && @@ -608,4 +807,70 @@ test_expect_success 'cat-file --batch="batman" with --batch-all-objects will wor cmp expect actual ' +test_expect_success 'set up replacement object' ' + orig=$(git rev-parse HEAD) && + git cat-file commit $orig >orig && + { + cat orig && + echo extra + } >fake && + fake=$(git hash-object -t commit -w fake) && + orig_size=$(git cat-file -s $orig) && + fake_size=$(git cat-file -s $fake) && + git replace $orig $fake +' + +test_expect_success 'cat-file --batch respects replace objects' ' + git cat-file --batch >actual <<-EOF && + $orig + EOF + { + echo "$orig commit $fake_size" && + cat fake && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success 'cat-file --batch-check respects replace objects' ' + git cat-file --batch-check >actual <<-EOF && + $orig + EOF + echo "$orig commit $fake_size" >expect && + test_cmp expect actual +' + +# Pull the entry for object with oid "$1" out of the output of +# "cat-file --batch", including its object content (which requires +# parsing and reading a set amount of bytes, hence perl). +extract_batch_output () { + perl -ne ' + BEGIN { $oid = shift } + if (/^$oid \S+ (\d+)$/) { + print; + read STDIN, my $buf, $1; + print $buf; + print "\n"; + } + ' "$@" +} + +test_expect_success 'cat-file --batch-all-objects --batch ignores replace' ' + git cat-file --batch-all-objects --batch >actual.raw && + extract_batch_output $orig <actual.raw >actual && + { + echo "$orig commit $orig_size" && + cat orig && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success 'cat-file --batch-all-objects --batch-check ignores replace' ' + git cat-file --batch-all-objects --batch-check >actual.raw && + grep ^$orig actual.raw >actual && + echo "$orig commit $orig_size" >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index b6df7444c0..bfc90d4cf2 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -6,7 +6,6 @@ test_description='read-tree can handle submodules' . "$TEST_DIRECTORY"/lib-submodule-update.sh KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 -KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 test_submodule_switch_recursing_with_args "read-tree -u -m" diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 71236981e6..272ba1b566 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -206,16 +206,21 @@ test_expect_success 'sparse-checkout disable' ' ' test_expect_success 'sparse-index enabled and disabled' ' - git -C repo sparse-checkout init --cone --sparse-index && - test_cmp_config -C repo true index.sparse && - test-tool -C repo read-cache --table >cache && - grep " tree " cache && - - git -C repo sparse-checkout disable && - test-tool -C repo read-cache --table >cache && - ! grep " tree " cache && - git -C repo config --list >config && - ! grep index.sparse config + ( + sane_unset GIT_TEST_SPLIT_INDEX && + git -C repo update-index --no-split-index && + + git -C repo sparse-checkout init --cone --sparse-index && + test_cmp_config -C repo true index.sparse && + test-tool -C repo read-cache --table >cache && + grep " tree " cache && + + git -C repo sparse-checkout disable && + test-tool -C repo read-cache --table >cache && + ! grep " tree " cache && + git -C repo config --list >config && + ! grep index.sparse config + ) ' test_expect_success 'cone mode: init and set' ' @@ -406,7 +411,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat git -C unmerged sparse-checkout disable ' -test_expect_success 'sparse-checkout reapply' ' +test_expect_failure 'sparse-checkout reapply' ' git clone repo tweak && echo dirty >tweak/deep/deeper2/a && @@ -438,6 +443,8 @@ test_expect_success 'sparse-checkout reapply' ' test_i18ngrep "warning.*The following paths are unmerged" err && test_path_is_file tweak/folder1/a && + # NEEDSWORK: We are asking to update a file outside of the + # sparse-checkout cone, but this is no longer allowed. git -C tweak add folder1/a && git -C tweak sparse-checkout reapply 2>err && test_must_be_empty err && diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 886e78715f..ca91c6a67f 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -187,6 +187,16 @@ test_sparse_match () { test_cmp sparse-checkout-err sparse-index-err } +test_sparse_unstaged () { + file=$1 && + for repo in sparse-checkout sparse-index + do + # Skip "unmerged" paths + git -C $repo diff --staged --diff-filter=u -- "$file" >diff && + test_must_be_empty diff || return 1 + done +} + test_expect_success 'sparse-index contents' ' init_repos && @@ -291,6 +301,20 @@ test_expect_success 'add, commit, checkout' ' test_all_match git checkout - ' +test_expect_success 'add outside sparse cone' ' + init_repos && + + run_on_sparse mkdir folder1 && + run_on_sparse ../edit-contents folder1/a && + run_on_sparse ../edit-contents folder1/newfile && + test_sparse_match test_must_fail git add folder1/a && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/a && + test_sparse_match test_must_fail git add folder1/newfile && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/newfile +' + test_expect_success 'commit including unstaged changes' ' init_repos && @@ -339,18 +363,24 @@ test_expect_success 'status/add: outside sparse cone' ' # Adding the path outside of the sparse-checkout cone should fail. test_sparse_match test_must_fail git add folder1/a && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/a && test_sparse_match test_must_fail git add --refresh folder1/a && - - # NEEDSWORK: Adding a newly-tracked file outside the cone succeeds - test_sparse_match git add folder1/new && - - test_all_match git add . && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/a && + test_sparse_match test_must_fail git add folder1/new && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/new && + test_sparse_match git add --sparse folder1/a && + test_sparse_match git add --sparse folder1/new && + + test_all_match git add --sparse . && test_all_match git status --porcelain=v2 && test_all_match git commit -m folder1/new && test_all_match git rev-parse HEAD^{tree} && run_on_all ../edit-contents folder1/newer && - test_all_match git add folder1/ && + test_all_match git add --sparse folder1/ && test_all_match git status --porcelain=v2 && test_all_match git commit -m folder1/newer && test_all_match git rev-parse HEAD^{tree} @@ -494,11 +524,6 @@ test_expect_success 'merge, cherry-pick, and rebase' ' done ' -# NEEDSWORK: This test is documenting current behavior, but that -# behavior can be confusing to users so there is desire to change it. -# Right now, users might be using this flow to work through conflicts, -# so any solution should present advice to users who try this sequence -# of commands to follow whatever new method we create. test_expect_success 'merge with conflict outside cone' ' init_repos && @@ -513,13 +538,19 @@ test_expect_success 'merge with conflict outside cone' ' test_all_match git status --porcelain=v2 && # 2. Add the file with conflict markers - test_all_match git add folder1/a && + test_sparse_match test_must_fail git add folder1/a && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/a && + test_all_match git add --sparse folder1/a && test_all_match git status --porcelain=v2 && # 3. Rename the file to another sparse filename and # accept conflict markers as resolved content. run_on_all mv folder2/a folder2/z && - test_all_match git add folder2 && + test_sparse_match test_must_fail git add folder2 && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder2/z && + test_all_match git add --sparse folder2 && test_all_match git status --porcelain=v2 && test_all_match git merge --continue && @@ -544,13 +575,25 @@ test_expect_success 'cherry-pick/rebase with conflict outside cone' ' test_all_match git status --porcelain=v2 && # 2. Add the file with conflict markers - test_all_match git add folder1/a && + # NEEDSWORK: Even though the merge conflict removed the + # SKIP_WORKTREE bit from the index entry for folder1/a, we should + # warn that this is a problematic add. + test_sparse_match test_must_fail git add folder1/a && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder1/a && + test_all_match git add --sparse folder1/a && test_all_match git status --porcelain=v2 && # 3. Rename the file to another sparse filename and # accept conflict markers as resolved content. + # NEEDSWORK: This mode now fails, because folder2/z is + # outside of the sparse-checkout cone and does not match an + # existing index entry with the SKIP_WORKTREE bit cleared. run_on_all mv folder2/a folder2/z && - test_all_match git add folder2 && + test_sparse_match test_must_fail git add folder2 && + grep "Disable or modify the sparsity rules" sparse-checkout-err && + test_sparse_unstaged folder2/z && + test_all_match git add --sparse folder2 && test_all_match git status --porcelain=v2 && test_all_match git $OPERATION --continue && @@ -626,6 +669,7 @@ test_expect_success 'clean' ' test_expect_success 'submodule handling' ' init_repos && + test_sparse_match git sparse-checkout add modules && test_all_match mkdir modules && test_all_match touch modules/a && test_all_match git add modules && @@ -635,6 +679,7 @@ test_expect_success 'submodule handling' ' test_all_match git commit -m "add submodule" && # having a submodule prevents "modules" from collapse + test_sparse_match git sparse-checkout set deep/deeper1 && test-tool -C sparse-index read-cache --table >cache && grep "100644 blob .* modules/a" cache && grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 5071ac63a5..6337236fd8 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -48,24 +48,70 @@ remove_object () { rm "$(sha1_file "$1")" } -test_expect_success 'object with bad sha1' ' - sha=$(echo blob | git hash-object -w --stdin) && - old=$(test_oid_to_path "$sha") && - new=$(dirname $old)/$(test_oid ff_2) && - sha="$(dirname $new)$(basename $new)" && - mv .git/objects/$old .git/objects/$new && - test_when_finished "remove_object $sha" && - git update-index --add --cacheinfo 100644 $sha foo && - test_when_finished "git read-tree -u --reset HEAD" && - tree=$(git write-tree) && - test_when_finished "remove_object $tree" && - cmt=$(echo bogus | git commit-tree $tree) && - test_when_finished "remove_object $cmt" && - git update-ref refs/heads/bogus $cmt && - test_when_finished "git update-ref -d refs/heads/bogus" && +test_expect_success 'object with hash mismatch' ' + git init --bare hash-mismatch && + ( + cd hash-mismatch && - test_must_fail git fsck 2>out && - test_i18ngrep "$sha.*corrupt" out + oid=$(echo blob | git hash-object -w --stdin) && + oldoid=$oid && + old=$(test_oid_to_path "$oid") && + new=$(dirname $old)/$(test_oid ff_2) && + oid="$(dirname $new)$(basename $new)" && + + mv objects/$old objects/$new && + git update-index --add --cacheinfo 100644 $oid foo && + tree=$(git write-tree) && + cmt=$(echo bogus | git commit-tree $tree) && + git update-ref refs/heads/bogus $cmt && + + test_must_fail git fsck 2>out && + grep "$oldoid: hash-path mismatch, found at: .*$new" out + ) +' + +test_expect_success 'object with hash and type mismatch' ' + git init --bare hash-type-mismatch && + ( + cd hash-type-mismatch && + + oid=$(echo blob | git hash-object -w --stdin -t garbage --literally) && + oldoid=$oid && + old=$(test_oid_to_path "$oid") && + new=$(dirname $old)/$(test_oid ff_2) && + oid="$(dirname $new)$(basename $new)" && + + mv objects/$old objects/$new && + git update-index --add --cacheinfo 100644 $oid foo && + tree=$(git write-tree) && + cmt=$(echo bogus | git commit-tree $tree) && + git update-ref refs/heads/bogus $cmt && + + + test_must_fail git fsck 2>out && + grep "^error: $oldoid: hash-path mismatch, found at: .*$new" out && + grep "^error: $oldoid: object is of unknown type '"'"'garbage'"'"'" out + ) +' + +test_expect_success POSIXPERM 'zlib corrupt loose object output ' ' + git init --bare corrupt-loose-output && + ( + cd corrupt-loose-output && + oid=$(git hash-object -w --stdin --literally </dev/null) && + oidf=objects/$(test_oid_to_path "$oid") && + chmod 755 $oidf && + echo extra garbage >>$oidf && + + cat >expect.error <<-EOF && + error: garbage at end of loose object '\''$oid'\'' + error: unable to unpack contents of ./$oidf + error: $oid: object corrupt or missing: ./$oidf + EOF + test_must_fail git fsck 2>actual && + grep ^error: actual >error && + test_cmp expect.error error + ) ' test_expect_success 'branch pointing to non-commit' ' @@ -865,4 +911,21 @@ test_expect_success 'detect corrupt index file in fsck' ' test_i18ngrep "bad index file" errors ' +test_expect_success 'fsck error and recovery on invalid object type' ' + git init --bare garbage-type && + ( + cd garbage-type && + + garbage_blob=$(git hash-object --stdin -w -t garbage --literally </dev/null) && + + cat >err.expect <<-\EOF && + fatal: invalid object type + EOF + test_must_fail git fsck >out 2>err && + grep -e "^error" -e "^fatal" err >errors && + test_line_count = 1 errors && + grep "$garbage_blob: object is of unknown type '"'"'garbage'"'"':" err + ) +' + test_done diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index b29563fc99..284fe18e72 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -282,4 +282,58 @@ test_expect_success 'test --parseopt --stuck-long and short option with unset op test_cmp expect output ' +test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" lines' ' + sed -e "s/^|//" >spec <<-\EOF && + |cmd [--some-option] + | [--another-option] + |cmd [--yet-another-option] + |-- + |h,help show the help + EOF + + sed -e "s/^|//" >expect <<-\END_EXPECT && + |cat <<\EOF + |usage: cmd [--some-option] + | or: [--another-option] + | or: cmd [--yet-another-option] + | + | -h, --help show the help + | + |EOF + END_EXPECT + + test_must_fail git rev-parse --parseopt -- -h >out <spec >actual && + test_cmp expect actual +' + +test_expect_success 'test --parseopt help output: multi-line blurb after empty line' ' + sed -e "s/^|//" >spec <<-\EOF && + |cmd [--some-option] + | [--another-option] + | + |multi + |line + |blurb + |-- + |h,help show the help + EOF + + sed -e "s/^|//" >expect <<-\END_EXPECT && + |cat <<\EOF + |usage: cmd [--some-option] + | or: [--another-option] + | + | multi + | line + | blurb + | + | -h, --help show the help + | + |EOF + END_EXPECT + + test_must_fail git rev-parse --parseopt -- -h >out <spec >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1600-index.sh b/t/t1600-index.sh index c9b9e718b8..46329c488b 100755 --- a/t/t1600-index.sh +++ b/t/t1600-index.sh @@ -4,6 +4,8 @@ test_description='index file specific tests' . ./test-lib.sh +sane_unset GIT_TEST_SPLIT_INDEX + test_expect_success 'setup' ' echo 1 >a ' @@ -13,7 +15,8 @@ test_expect_success 'bogus GIT_INDEX_VERSION issues warning' ' rm -f .git/index && GIT_INDEX_VERSION=2bogus && export GIT_INDEX_VERSION && - git add a 2>&1 | sed "s/[0-9]//" >actual.err && + git add a 2>err && + sed "s/[0-9]//" err >actual.err && sed -e "s/ Z$/ /" <<-\EOF >expect.err && warning: GIT_INDEX_VERSION set, but the value is invalid. Using version Z @@ -27,7 +30,8 @@ test_expect_success 'out of bounds GIT_INDEX_VERSION issues warning' ' rm -f .git/index && GIT_INDEX_VERSION=1 && export GIT_INDEX_VERSION && - git add a 2>&1 | sed "s/[0-9]//" >actual.err && + git add a 2>err && + sed "s/[0-9]//" err >actual.err && sed -e "s/ Z$/ /" <<-\EOF >expect.err && warning: GIT_INDEX_VERSION set, but the value is invalid. Using version Z @@ -50,7 +54,8 @@ test_expect_success 'out of bounds index.version issues warning' ' sane_unset GIT_INDEX_VERSION && rm -f .git/index && git config --add index.version 1 && - git add a 2>&1 | sed "s/[0-9]//" >actual.err && + git add a 2>err && + sed "s/[0-9]//" err >actual.err && sed -e "s/ Z$/ /" <<-\EOF >expect.err && warning: index.version set, but the value is invalid. Using version Z @@ -79,7 +84,7 @@ test_index_version () { else unset GIT_INDEX_VERSION fi && - git add a 2>&1 && + git add a && echo $EXPECTED_OUTPUT_VERSION >expect && test-tool index-version <.git/index >actual && test_cmp expect actual diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index 986baa612e..decd2527ed 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -510,4 +510,38 @@ test_expect_success 'do not refresh null base index' ' ) ' +test_expect_success 'reading split index at alternate location' ' + git init reading-alternate-location && + ( + cd reading-alternate-location && + >file-in-alternate && + git update-index --split-index --add file-in-alternate + ) && + echo file-in-alternate >expect && + + # Should be able to find the shared index both right next to + # the specified split index file ... + GIT_INDEX_FILE=./reading-alternate-location/.git/index \ + git ls-files --cached >actual && + test_cmp expect actual && + + # ... and, for backwards compatibility, in the current GIT_DIR + # as well. + mv -v ./reading-alternate-location/.git/sharedindex.* .git && + GIT_INDEX_FILE=./reading-alternate-location/.git/index \ + git ls-files --cached >actual && + test_cmp expect actual +' + +test_expect_success 'GIT_TEST_SPLIT_INDEX works' ' + git init git-test-split-index && + ( + cd git-test-split-index && + >file && + GIT_TEST_SPLIT_INDEX=1 git update-index --add file && + ls -l .git/sharedindex.* >actual && + test_line_count = 1 actual + ) +' + test_done diff --git a/t/t2500-untracked-overwriting.sh b/t/t2500-untracked-overwriting.sh new file mode 100755 index 0000000000..5c0bf4d21f --- /dev/null +++ b/t/t2500-untracked-overwriting.sh @@ -0,0 +1,244 @@ +#!/bin/sh + +test_description='Test handling of overwriting untracked files' + +. ./test-lib.sh + +test_setup_reset () { + git init reset_$1 && + ( + cd reset_$1 && + test_commit init && + + git branch stable && + git branch work && + + git checkout work && + test_commit foo && + + git checkout stable + ) +} + +test_expect_success 'reset --hard will nuke untracked files/dirs' ' + test_setup_reset hard && + ( + cd reset_hard && + git ls-tree -r stable && + git log --all --name-status --oneline && + git ls-tree -r work && + + mkdir foo.t && + echo precious >foo.t/file && + echo foo >expect && + + git reset --hard work && + + # check that untracked directory foo.t/ was nuked + test_path_is_file foo.t && + test_cmp expect foo.t + ) +' + +test_expect_success 'reset --merge will preserve untracked files/dirs' ' + test_setup_reset merge && + ( + cd reset_merge && + + mkdir foo.t && + echo precious >foo.t/file && + cp foo.t/file expect && + + test_must_fail git reset --merge work 2>error && + test_cmp expect foo.t/file && + grep "Updating .foo.t. would lose untracked files" error + ) +' + +test_expect_success 'reset --keep will preserve untracked files/dirs' ' + test_setup_reset keep && + ( + cd reset_keep && + + mkdir foo.t && + echo precious >foo.t/file && + cp foo.t/file expect && + + test_must_fail git reset --merge work 2>error && + test_cmp expect foo.t/file && + grep "Updating.*foo.t.*would lose untracked files" error + ) +' + +test_setup_checkout_m () { + git init checkout && + ( + cd checkout && + test_commit init && + + test_write_lines file has some >filler && + git add filler && + git commit -m filler && + + git branch stable && + + git switch -c work && + echo stuff >notes.txt && + test_write_lines file has some words >filler && + git add notes.txt filler && + git commit -m filler && + + git checkout stable + ) +} + +test_expect_success 'checkout -m does not nuke untracked file' ' + test_setup_checkout_m && + ( + cd checkout && + + # Tweak filler + test_write_lines this file has some >filler && + # Make an untracked file, save its contents in "expect" + echo precious >notes.txt && + cp notes.txt expect && + + test_must_fail git checkout -m work && + test_cmp expect notes.txt + ) +' + +test_setup_sequencing () { + git init sequencing_$1 && + ( + cd sequencing_$1 && + test_commit init && + + test_write_lines this file has some words >filler && + git add filler && + git commit -m filler && + + mkdir -p foo/bar && + test_commit foo/bar/baz && + + git branch simple && + git branch fooey && + + git checkout fooey && + git rm foo/bar/baz.t && + echo stuff >>filler && + git add -u && + git commit -m "changes" && + + git checkout simple && + echo items >>filler && + echo newstuff >>newfile && + git add filler newfile && + git commit -m another + ) +} + +test_expect_success 'git rebase --abort and untracked files' ' + test_setup_sequencing rebase_abort_and_untracked && + ( + cd sequencing_rebase_abort_and_untracked && + git checkout fooey && + test_must_fail git rebase simple && + + cat init.t && + git rm init.t && + echo precious >init.t && + cp init.t expect && + git status --porcelain && + test_must_fail git rebase --abort && + test_cmp expect init.t + ) +' + +test_expect_success 'git rebase fast forwarding and untracked files' ' + test_setup_sequencing rebase_fast_forward_and_untracked && + ( + cd sequencing_rebase_fast_forward_and_untracked && + git checkout init && + echo precious >filler && + cp filler expect && + test_must_fail git rebase init simple && + test_cmp expect filler + ) +' + +test_expect_failure 'git rebase --autostash and untracked files' ' + test_setup_sequencing rebase_autostash_and_untracked && + ( + cd sequencing_rebase_autostash_and_untracked && + git checkout simple && + git rm filler && + mkdir filler && + echo precious >filler/file && + cp filler/file expect && + git rebase --autostash init && + test_path_is_file filler/file + ) +' + +test_expect_failure 'git stash and untracked files' ' + test_setup_sequencing stash_and_untracked_files && + ( + cd sequencing_stash_and_untracked_files && + git checkout simple && + git rm filler && + mkdir filler && + echo precious >filler/file && + cp filler/file expect && + git status --porcelain && + git stash push && + git status --porcelain && + test_path_is_file filler/file + ) +' + +test_expect_success 'git am --abort and untracked dir vs. unmerged file' ' + test_setup_sequencing am_abort_and_untracked && + ( + cd sequencing_am_abort_and_untracked && + git format-patch -1 --stdout fooey >changes.mbox && + test_must_fail git am --3way changes.mbox && + + # Delete the conflicted file; we will stage and commit it later + rm filler && + + # Put an unrelated untracked directory there + mkdir filler && + echo foo >filler/file1 && + echo bar >filler/file2 && + + test_must_fail git am --abort 2>errors && + test_path_is_dir filler && + grep "Updating .filler. would lose untracked files in it" errors + ) +' + +test_expect_success 'git am --skip and untracked dir vs deleted file' ' + test_setup_sequencing am_skip_and_untracked && + ( + cd sequencing_am_skip_and_untracked && + git checkout fooey && + git format-patch -1 --stdout simple >changes.mbox && + test_must_fail git am --3way changes.mbox && + + # Delete newfile + rm newfile && + + # Put an unrelated untracked directory there + mkdir newfile && + echo foo >newfile/file1 && + echo bar >newfile/file2 && + + # Change our mind about resolutions, just skip this patch + test_must_fail git am --skip 2>errors && + test_path_is_dir newfile && + grep "Updating .newfile. would lose untracked files in it" errors + ) +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 972ce026bb..12eb226957 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -352,82 +352,6 @@ test_expect_success 'retain authorship when squashing' ' git show HEAD | grep "^Author: Twerp Snog" ' -test_expect_success REBASE_P '-p handles "no changes" gracefully' ' - HEAD=$(git rev-parse HEAD) && - git rebase -i -p HEAD^ && - git update-index --refresh && - git diff-files --quiet && - git diff-index --quiet --cached HEAD -- && - test $HEAD = $(git rev-parse HEAD) -' - -test_expect_failure REBASE_P 'exchange two commits with -p' ' - git checkout H && - ( - set_fake_editor && - FAKE_LINES="2 1" git rebase -i -p HEAD~2 - ) && - test H = $(git cat-file commit HEAD^ | sed -ne \$p) && - test G = $(git cat-file commit HEAD | sed -ne \$p) -' - -test_expect_success REBASE_P 'preserve merges with -p' ' - git checkout -b to-be-preserved primary^ && - : > unrelated-file && - git add unrelated-file && - test_tick && - git commit -m "unrelated" && - git checkout -b another-branch primary && - echo B > file1 && - test_tick && - git commit -m J file1 && - test_tick && - git merge to-be-preserved && - echo C > file1 && - test_tick && - git commit -m K file1 && - echo D > file1 && - test_tick && - git commit -m L1 file1 && - git checkout HEAD^ && - echo 1 > unrelated-file && - test_tick && - git commit -m L2 unrelated-file && - test_tick && - git merge another-branch && - echo E > file1 && - test_tick && - git commit -m M file1 && - git checkout -b to-be-rebased && - test_tick && - git rebase -i -p --onto branch1 primary && - git update-index --refresh && - git diff-files --quiet && - git diff-index --quiet --cached HEAD -- && - test_cmp_rev HEAD~6 branch1 && - test_cmp_rev HEAD~4^2 to-be-preserved && - test_cmp_rev HEAD^^2^ HEAD^^^ && - test $(git show HEAD~5:file1) = B && - test $(git show HEAD~3:file1) = C && - test $(git show HEAD:file1) = E && - test $(git show HEAD:unrelated-file) = 1 -' - -test_expect_success REBASE_P 'edit ancestor with -p' ' - ( - set_fake_editor && - FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 - ) && - echo 2 > unrelated-file && - test_tick && - git commit -m L2-modified --amend unrelated-file && - git rebase --continue && - git update-index --refresh && - git diff-files --quiet && - git diff-index --quiet --cached HEAD -- && - test $(git show HEAD:unrelated-file) = 2 -' - test_expect_success '--continue tries to commit' ' git reset --hard D && test_tick && diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index 7c381fbc89..ebbaed147a 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -7,77 +7,77 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh -### Test that we handle space characters properly -work_dir="$(pwd)/test dir" - test_expect_success setup ' - mkdir -p "$work_dir" && - cd "$work_dir" && - git init && - echo a > a && - git add a && - git commit -m a && + test_commit a a a && git branch to-rebase && - echo b > a && - git commit -a -m b && - echo c > a && - git commit -a -m c && + test_commit --annotate b a b && + test_commit --annotate c a c && git checkout to-rebase && - echo d > a && - git commit -a -m "merge should fail on this" && - echo e > a && - git commit -a -m "merge should fail on this, too" && - git branch pre-rebase + test_commit "merge should fail on this" a d d && + test_commit --annotate "merge should fail on this, too" a e pre-rebase ' +# Check that HEAD is equal to "pre-rebase" and the current branch is +# "to-rebase" +check_head() { + test_cmp_rev HEAD pre-rebase^{commit} && + test "$(git symbolic-ref HEAD)" = refs/heads/to-rebase +} + testrebase() { type=$1 - dotest=$2 + state_dir=$2 test_expect_success "rebase$type --abort" ' - cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && test_must_fail git rebase$type main && - test_path_is_dir "$dotest" && + test_path_is_dir "$state_dir" && git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && - test ! -d "$dotest" + check_head && + test_path_is_missing "$state_dir" ' test_expect_success "rebase$type --abort after --skip" ' - cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && test_must_fail git rebase$type main && - test_path_is_dir "$dotest" && + test_path_is_dir "$state_dir" && test_must_fail git rebase --skip && - test $(git rev-parse HEAD) = $(git rev-parse main) && + test_cmp_rev HEAD main && git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && - test ! -d "$dotest" + check_head && + test_path_is_missing "$state_dir" ' test_expect_success "rebase$type --abort after --continue" ' - cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && test_must_fail git rebase$type main && - test_path_is_dir "$dotest" && + test_path_is_dir "$state_dir" && echo c > a && echo d >> a && git add a && test_must_fail git rebase --continue && - test $(git rev-parse HEAD) != $(git rev-parse main) && + test_cmp_rev ! HEAD main && + git rebase --abort && + check_head && + test_path_is_missing "$state_dir" + ' + + test_expect_success "rebase$type --abort when checking out a tag" ' + test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" && + git reset --hard a -- && + test_must_fail git rebase$type --onto b c pre-rebase && + test_cmp_rev HEAD b^{commit} && git rebase --abort && - test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && - test ! -d "$dotest" + test_cmp_rev HEAD pre-rebase^{commit} && + ! git symbolic-ref HEAD ' test_expect_success "rebase$type --abort does not update reflog" ' - cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && git reflog show to-rebase > reflog_before && @@ -89,7 +89,6 @@ testrebase() { ' test_expect_success 'rebase --abort can not be used with other options' ' - cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && test_must_fail git rebase$type main && @@ -97,33 +96,21 @@ testrebase() { test_must_fail git rebase --abort -v && git rebase --abort ' + + test_expect_success "rebase$type --quit" ' + test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase$type main && + test_path_is_dir $state_dir && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test_cmp_rev HEAD $head_before && + test_path_is_missing .git/rebase-apply + ' } testrebase " --apply" .git/rebase-apply testrebase " --merge" .git/rebase-merge -test_expect_success 'rebase --apply --quit' ' - cd "$work_dir" && - # Clean up the state from the previous one - git reset --hard pre-rebase && - test_must_fail git rebase --apply main && - test_path_is_dir .git/rebase-apply && - head_before=$(git rev-parse HEAD) && - git rebase --quit && - test $(git rev-parse HEAD) = $head_before && - test ! -d .git/rebase-apply -' - -test_expect_success 'rebase --merge --quit' ' - cd "$work_dir" && - # Clean up the state from the previous one - git reset --hard pre-rebase && - test_must_fail git rebase --merge main && - test_path_is_dir .git/rebase-merge && - head_before=$(git rev-parse HEAD) && - git rebase --quit && - test $(git rev-parse HEAD) = $head_before && - test ! -d .git/rebase-merge -' - test_done diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh index ab0960e6d9..cde3562e3a 100755 --- a/t/t3408-rebase-multi-line.sh +++ b/t/t3408-rebase-multi-line.sh @@ -55,14 +55,4 @@ test_expect_success rebase ' test_cmp expect actual ' -test_expect_success REBASE_P rebasep ' - - git checkout side-merge && - git rebase -p side && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && - git cat-file commit side-merge-original | sed -e "1,/^\$/d" >expect && - test_cmp expect actual - -' - test_done diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh deleted file mode 100755 index ec8062a66a..0000000000 --- a/t/t3409-rebase-preserve-merges.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/sh -# -# Copyright(C) 2008 Stephen Habermann & Andreas Ericsson -# -test_description='git rebase -p should preserve merges - -Run "git rebase -p" and check that merges are properly carried along -' -GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main -export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME - -. ./test-lib.sh - -if ! test_have_prereq REBASE_P; then - skip_all='skipping git rebase -p tests, as asked for' - test_done -fi - -GIT_AUTHOR_EMAIL=bogus_email_address -export GIT_AUTHOR_EMAIL - -# Clone 2 (conflicting merge): -# -# A1--A2--B3 <-- origin/main -# \ \ -# B1------M <-- topic -# \ -# B2 <-- origin/topic -# -# Clone 3 (no-ff merge): -# -# A1--A2--B3 <-- origin/main -# \ -# B1------M <-- topic -# \ / -# \--A3 <-- topic2 -# \ -# B2 <-- origin/topic -# -# Clone 4 (same as Clone 3) - -test_expect_success 'setup for merge-preserving rebase' \ - 'echo First > A && - git add A && - git commit -m "Add A1" && - git checkout -b topic && - echo Second > B && - git add B && - git commit -m "Add B1" && - git checkout -f main && - echo Third >> A && - git commit -a -m "Modify A2" && - echo Fifth > B && - git add B && - git commit -m "Add different B" && - - git clone ./. clone2 && - ( - cd clone2 && - git checkout -b topic origin/topic && - test_must_fail git merge origin/main && - echo Resolved >B && - git add B && - git commit -m "Merge origin/main into topic" - ) && - - git clone ./. clone3 && - ( - cd clone3 && - git checkout -b topic2 origin/topic && - echo Sixth > A && - git commit -a -m "Modify A3" && - git checkout -b topic origin/topic && - git merge --no-ff topic2 - ) && - - git clone ./. clone4 && - ( - cd clone4 && - git checkout -b topic2 origin/topic && - echo Sixth > A && - git commit -a -m "Modify A3" && - git checkout -b topic origin/topic && - git merge --no-ff topic2 - ) && - - git checkout topic && - echo Fourth >> B && - git commit -a -m "Modify B2" -' - -test_expect_success '--continue works after a conflict' ' - ( - cd clone2 && - git fetch && - test_must_fail git rebase -p origin/topic && - test 2 = $(git ls-files B | wc -l) && - echo Resolved again > B && - test_must_fail git rebase --continue && - grep "^@@@ " .git/rebase-merge/patch && - git add B && - git rebase --continue && - test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Add different" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Merge origin" | wc -l) - ) -' - -test_expect_success 'rebase -p preserves no-ff merges' ' - ( - cd clone3 && - git fetch && - git rebase -p origin/topic && - test 3 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Merge branch" | wc -l) - ) -' - -test_expect_success 'rebase -p ignores merge.log config' ' - ( - cd clone4 && - git fetch && - git -c merge.log=1 rebase -p origin/topic && - echo >expected && - git log --format="%b" -1 >current && - test_cmp expected current - ) -' - -test_done diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh deleted file mode 100755 index 2e29866993..0000000000 --- a/t/t3410-rebase-preserve-dropped-merges.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2008 Stephen Haberman -# - -test_description='git rebase preserve merges - -This test runs git rebase with preserve merges and ensures commits -dropped by the --cherry-pick flag have their childrens parents -rewritten. -' -. ./test-lib.sh - -if ! test_have_prereq REBASE_P; then - skip_all='skipping git rebase -p tests, as asked for' - test_done -fi - -# set up two branches like this: -# -# A - B - C - D - E -# \ -# F - G - H -# \ -# I -# -# where B, D and G touch the same file. - -test_expect_success 'setup' ' - test_commit A file1 && - test_commit B file1 1 && - test_commit C file2 && - test_commit D file1 2 && - test_commit E file3 && - git checkout A && - test_commit F file4 && - test_commit G file1 3 && - test_commit H file5 && - git checkout F && - test_commit I file6 -' - -# A - B - C - D - E -# \ \ \ -# F - G - H -- L \ --> L -# \ | \ -# I -- G2 -- J -- K I -- K -# G2 = same changes as G -test_expect_success 'skip same-resolution merges with -p' ' - git checkout H && - test_must_fail git merge E && - test_commit L file1 23 && - git checkout I && - test_commit G2 file1 3 && - test_must_fail git merge E && - test_commit J file1 23 && - test_commit K file7 file7 && - git rebase -i -p L && - test $(git rev-parse HEAD^^) = $(git rev-parse L) && - test "23" = "$(cat file1)" && - test "I" = "$(cat file6)" && - test "file7" = "$(cat file7)" -' - -# A - B - C - D - E -# \ \ \ -# F - G - H -- L2 \ --> L2 -# \ | \ -# I -- G3 --- J2 -- K2 I -- G3 -- K2 -# G2 = different changes as G -test_expect_success 'keep different-resolution merges with -p' ' - git checkout H && - test_must_fail git merge E && - test_commit L2 file1 23 && - git checkout I && - test_commit G3 file1 4 && - test_must_fail git merge E && - test_commit J2 file1 24 && - test_commit K2 file7 file7 && - test_must_fail git rebase -i -p L2 && - echo 234 > file1 && - git add file1 && - git rebase --continue && - test $(git rev-parse HEAD^^^) = $(git rev-parse L2) && - test "234" = "$(cat file1)" && - test "I" = "$(cat file6)" && - test "file7" = "$(cat file7)" -' - -test_done diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh deleted file mode 100755 index fb45e7bf7b..0000000000 --- a/t/t3411-rebase-preserve-around-merges.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2008 Stephen Haberman -# - -test_description='git rebase preserve merges - -This test runs git rebase with -p and tries to squash a commit from after -a merge to before the merge. -' -. ./test-lib.sh - -if ! test_have_prereq REBASE_P; then - skip_all='skipping git rebase -p tests, as asked for' - test_done -fi - -. "$TEST_DIRECTORY"/lib-rebase.sh - -set_fake_editor - -# set up two branches like this: -# -# A1 - B1 - D1 - E1 - F1 -# \ / -# -- C1 -- - -test_expect_success 'setup' ' - test_commit A1 && - test_commit B1 && - test_commit C1 && - git reset --hard B1 && - test_commit D1 && - test_merge E1 C1 && - test_commit F1 -' - -# Should result in: -# -# A1 - B1 - D2 - E2 -# \ / -# -- C1 -- -# -test_expect_success 'squash F1 into D1' ' - FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" && - test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" && - git tag E2 -' - -# Start with: -# -# A1 - B1 - D2 - E2 -# \ -# G1 ---- L1 ---- M1 -# \ / -# H1 -- J1 -- K1 -# \ / -# -- I1 -- -# -# And rebase G1..M1 onto E2 - -test_expect_success 'rebase two levels of merge' ' - git checkout A1 && - test_commit G1 && - test_commit H1 && - test_commit I1 && - git checkout -b branch3 H1 && - test_commit J1 && - test_merge K1 I1 && - git checkout -b branch2 G1 && - test_commit L1 && - test_merge M1 K1 && - GIT_EDITOR=: git rebase -i -p E2 && - test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" && - test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" && - test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)" -' - -test_done diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index fda62c65bd..19c6f4acbf 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -89,17 +89,6 @@ test_expect_success 'pre-rebase got correct input (4)' ' test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4 ' -test_expect_success REBASE_P 'rebase -i -p with linear history' ' - git checkout -b work5 other && - git rebase -i -p --root --onto main && - git log --pretty=tformat:"%s" > rebased5 && - test_cmp expect rebased5 -' - -test_expect_success REBASE_P 'pre-rebase got correct input (5)' ' - test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, -' - test_expect_success 'set up merge history' ' git checkout other^ && git checkout -b side && @@ -123,13 +112,6 @@ commit work6~4 1 EOF -test_expect_success REBASE_P 'rebase -i -p with merge' ' - git checkout -b work6 other && - git rebase -i -p --root --onto main && - log_with_names work6 > rebased6 && - test_cmp expect-side rebased6 -' - test_expect_success 'set up second root and merge' ' git symbolic-ref HEAD refs/heads/third && rm .git/index && @@ -158,13 +140,6 @@ commit work7~5 1 EOF -test_expect_success REBASE_P 'rebase -i -p with two roots' ' - git checkout -b work7 other && - git rebase -i -p --root --onto main && - log_with_names work7 > rebased7 && - test_cmp expect-third rebased7 -' - test_expect_success 'setup pre-rebase hook that fails' ' mkdir -p .git/hooks && cat >.git/hooks/pre-rebase <<EOF && @@ -264,21 +239,9 @@ commit conflict3~6 1 EOF -test_expect_success REBASE_P 'rebase -i -p --root with conflict (first part)' ' - git checkout -b conflict3 other && - test_must_fail git rebase -i -p --root --onto main && - git ls-files -u | grep "B$" -' - test_expect_success 'fix the conflict' ' echo 3 > B && git add B ' -test_expect_success REBASE_P 'rebase -i -p --root with conflict (second part)' ' - git rebase --continue && - log_with_names conflict3 >out && - test_cmp expect-conflict-p out -' - test_done diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh deleted file mode 100755 index 72e04b5386..0000000000 --- a/t/t3414-rebase-preserve-onto.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2009 Greg Price -# - -test_description='git rebase -p should respect --onto - -In a rebase with --onto, we should rewrite all the commits that -aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM. -' -. ./test-lib.sh - -if ! test_have_prereq REBASE_P; then - skip_all='skipping git rebase -p tests, as asked for' - test_done -fi - -. "$TEST_DIRECTORY"/lib-rebase.sh - -# Set up branches like this: -# A1---B1---E1---F1---G1 -# \ \ / -# \ \--C1---D1--/ -# H1 - -test_expect_success 'setup' ' - test_commit A1 && - test_commit B1 && - test_commit C1 && - test_commit D1 && - git reset --hard B1 && - test_commit E1 && - test_commit F1 && - test_merge G1 D1 && - git reset --hard A1 && - test_commit H1 -' - -# Now rebase merge G1 from both branches' base B1, both should move: -# A1---B1---E1---F1---G1 -# \ \ / -# \ \--C1---D1--/ -# \ -# H1---E2---F2---G2 -# \ / -# \--C2---D2--/ - -test_expect_success 'rebase from B1 onto H1' ' - git checkout G1 && - git rebase -p --onto H1 B1 && - test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" && - test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)" -' - -# On the other hand if rebase from E1 which is within one branch, -# then the other branch stays: -# A1---B1---E1---F1---G1 -# \ \ / -# \ \--C1---D1--/ -# \ \ -# H1-----F3-----G3 - -test_expect_success 'rebase from E1 onto H1' ' - git checkout G1 && - git rebase -p --onto H1 E1 && - test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)" -' - -# And the same if we rebase from a commit in the second-parent branch. -# A1---B1---E1---F1----G1 -# \ \ \ / -# \ \--C1---D1-\-/ -# \ \ -# H1------D3------G4 - -test_expect_success 'rebase from C1 onto H1' ' - git checkout G1 && - git rev-list --first-parent --pretty=oneline C1..G1 && - git rebase -p --onto H1 C1 && - test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" && - test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)" -' - -test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 738fbae9b2..22eca73aa3 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -119,20 +119,6 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' ' test -f funny.was.run ' -test_expect_success REBASE_P 'rebase passes merge strategy options correctly' ' - rm -fr .git/rebase-* && - git reset --hard commit-new-file-F3-on-topic-branch && - test_commit theirs-to-merge && - git reset --hard HEAD^ && - test_commit some-commit && - test_tick && - git merge --no-ff theirs-to-merge && - FAKE_LINES="1 edit 2 3" git rebase -i -f -p -m \ - -s recursive --strategy-option=theirs HEAD~2 && - test_commit force-change && - git rebase --continue -' - test_expect_success 'rebase -r passes merge strategy options correctly' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && @@ -268,7 +254,6 @@ test_rerere_autoupdate --apply test_rerere_autoupdate -m GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR test_rerere_autoupdate -i -test_have_prereq !REBASE_P || test_rerere_autoupdate --preserve-merges unset GIT_SEQUENCE_EDITOR test_expect_success 'the todo command "break" works' ' diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 4a9204b4b6..62d86d557d 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -29,7 +29,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_expect_success 'setup branches and remote tracking' ' git tag -l >tags && @@ -53,7 +52,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -70,7 +68,6 @@ test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -87,7 +84,6 @@ test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -102,7 +98,6 @@ test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p # f # / @@ -142,7 +137,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -157,7 +151,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -172,7 +165,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -187,7 +179,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p # a---b---c---j! # \ @@ -215,7 +206,6 @@ test_run_rebase () { test_run_rebase failure --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -229,7 +219,6 @@ test_run_rebase () { } test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -243,7 +232,6 @@ test_run_rebase () { } test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase success --rebase-merges # m @@ -283,7 +271,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -298,7 +285,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -313,7 +299,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -329,7 +314,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -344,7 +328,6 @@ test_run_rebase () { test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -358,7 +341,6 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -373,6 +355,5 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p test_done diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh index c8234062c6..eb0a3d9d48 100755 --- a/t/t3422-rebase-incompatible-options.sh +++ b/t/t3422-rebase-incompatible-options.sh @@ -63,15 +63,4 @@ test_rebase_am_only () { test_rebase_am_only --whitespace=fix test_rebase_am_only -C4 -test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' ' - git checkout B^0 && - test_must_fail git rebase --preserve-merges --signoff A -' - -test_expect_success REBASE_P \ - '--preserve-merges incompatible with --rebase-merges' ' - git checkout B^0 && - test_must_fail git rebase --preserve-merges --rebase-merges A -' - test_done diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh index e42faa44e7..63acc1ea4d 100755 --- a/t/t3425-rebase-topology-merges.sh +++ b/t/t3425-rebase-topology-merges.sh @@ -106,155 +106,4 @@ test_run_rebase success 'd n o e' --apply test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i -if ! test_have_prereq REBASE_P; then - skip_all='skipping git rebase -p tests, as asked for' - test_done -fi - -test_expect_success "rebase -p is no-op in non-linear history" " - reset_rebase && - git rebase -p d w && - test_cmp_rev w HEAD -" - -test_expect_success "rebase -p is no-op when base inside second parent" " - reset_rebase && - git rebase -p e w && - test_cmp_rev w HEAD -" - -test_expect_failure "rebase -p --root on non-linear history is a no-op" " - reset_rebase && - git rebase -p --root w && - test_cmp_rev w HEAD -" - -test_expect_success "rebase -p re-creates merge from side branch" " - reset_rebase && - git rebase -p z w && - test_cmp_rev z HEAD^ && - test_cmp_rev w^2 HEAD^2 -" - -test_expect_success "rebase -p re-creates internal merge" " - reset_rebase && - git rebase -p c w && - test_cmp_rev c HEAD~4 && - test_cmp_rev HEAD^2^ HEAD~3 && - test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD -" - -test_expect_success "rebase -p can re-create two branches on onto" " - reset_rebase && - git rebase -p --onto c d w && - test_cmp_rev c HEAD~3 && - test_cmp_rev c HEAD^2^ && - test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD -" - -# f -# / -# a---b---c---g---h -# \ -# d---gp--i -# \ \ -# e-------u -# -# gp = cherry-picked g -# h = reverted g -test_expect_success 'setup of non-linear-history for patch-equivalence tests' ' - git checkout e && - test_merge u i -' - -test_expect_success "rebase -p re-creates history around dropped commit matching upstream" " - reset_rebase && - git rebase -p h u && - test_cmp_rev h HEAD~3 && - test_cmp_rev HEAD^2^ HEAD~2 && - test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD -" - -test_expect_success "rebase -p --onto in merged history drops patches in upstream" " - reset_rebase && - git rebase -p --onto f h u && - test_cmp_rev f HEAD~3 && - test_cmp_rev HEAD^2^ HEAD~2 && - test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD -" - -test_expect_success "rebase -p --onto in merged history does not drop patches in onto" " - reset_rebase && - git rebase -p --onto h f u && - test_cmp_rev h HEAD~3 && - test_cmp_rev HEAD^2~2 HEAD~2 && - test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD -" - -# a---b---c---g---h -# \ -# d---gp--s -# \ \ / -# \ X -# \ / \ -# e---t -# -# gp = cherry-picked g -# h = reverted g -test_expect_success 'setup of non-linear-history for dropping whole side' ' - git checkout gp && - test_merge s e && - git checkout e && - test_merge t gp -' - -test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" " - reset_rebase && - git rebase -p h s && - test_cmp_rev h HEAD~2 && - test_linear_range 'd e' h.. -" - -test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" " - reset_rebase && - git rebase -p h t && - test_cmp_rev h HEAD~2 && - test_linear_range 'd e' h.. -" - -# a---b---c -# \ -# d---e -# \ \ -# n---r -# \ -# o -# -# r = tree-same with n -test_expect_success 'setup of non-linear-history for empty commits' ' - git checkout n && - git merge --no-commit e && - git reset n . && - git commit -m r && - git reset --hard && - git clean -f && - git tag r -' - -test_expect_success "rebase -p re-creates empty internal merge commit" " - reset_rebase && - git rebase -p c r && - test_cmp_rev c HEAD~3 && - test_cmp_rev HEAD^2^ HEAD~2 && - test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD -" - -test_expect_success "rebase -p re-creates empty merge commit" " - reset_rebase && - git rebase -p o r && - test_cmp_rev e HEAD^2 && - test_cmp_rev o HEAD^ && - test_revision_subjects 'r' HEAD -" - test_done diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh index e78c7e3796..48b76f8232 100755 --- a/t/t3427-rebase-subtree.sh +++ b/t/t3427-rebase-subtree.sh @@ -36,11 +36,10 @@ commit_message() { # where the root commit adds three files: topic_1.t, topic_2.t and topic_3.t. # # This commit history is then rebased onto `topic_3` with the -# `-Xsubtree=files_subtree` option in three different ways: +# `-Xsubtree=files_subtree` option in two different ways: # -# 1. using `--preserve-merges` -# 2. using `--preserve-merges` and --keep-empty -# 3. without specifying a rebase backend +# 1. without specifying a rebase backend +# 2. using the `--rebase-merges` backend test_expect_success 'setup' ' test_commit README && @@ -69,25 +68,6 @@ test_expect_success 'setup' ' git commit -m "Empty commit" --allow-empty ' -# FAILURE: Does not preserve topic_4. -test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' ' - reset_rebase && - git checkout -b rebase-preserve-merges to-rebase && - git rebase -Xsubtree=files_subtree --preserve-merges --onto files-main main && - verbose test "$(commit_message HEAD~)" = "topic_4" && - verbose test "$(commit_message HEAD)" = "files_subtree/topic_5" -' - -# FAILURE: Does not preserve topic_4. -test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit' ' - reset_rebase && - git checkout -b rebase-keep-empty to-rebase && - git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-main main && - verbose test "$(commit_message HEAD~2)" = "topic_4" && - verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" && - verbose test "$(commit_message HEAD)" = "Empty commit" -' - test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' ' reset_rebase && git checkout -b rebase-onto to-rebase && diff --git a/t/t3602-rm-sparse-checkout.sh b/t/t3602-rm-sparse-checkout.sh index e9e9a15c74..ecce497a9c 100755 --- a/t/t3602-rm-sparse-checkout.sh +++ b/t/t3602-rm-sparse-checkout.sh @@ -11,12 +11,15 @@ test_expect_success 'setup' " git commit -m files && cat >sparse_error_header <<-EOF && - The following pathspecs didn't match any eligible path, but they do match index - entries outside the current sparse checkout: + The following paths and/or pathspecs matched paths that exist + outside of your sparse-checkout definition, so will not be + updated in the index: EOF cat >sparse_hint <<-EOF && - hint: Disable or modify the sparsity rules if you intend to update such entries. + hint: If you intend to update such entries, try one of the following: + hint: * Use the --sparse option. + hint: * Disable or modify the sparsity rules. hint: Disable this message with \"git config advice.updateSparsePath false\" EOF @@ -37,9 +40,25 @@ done test_expect_success 'recursive rm does not remove sparse entries' ' git reset --hard && git sparse-checkout set sub/dir && - git rm -r sub && + test_must_fail git rm -r sub && + git rm --sparse -r sub && git status --porcelain -uno >actual && - echo "D sub/dir/e" >expected && + cat >expected <<-\EOF && + D sub/d + D sub/dir/e + EOF + test_cmp expected actual +' + +test_expect_success 'recursive rm --sparse removes sparse entries' ' + git reset --hard && + git sparse-checkout set "sub/dir" && + git rm --sparse -r sub && + git status --porcelain -uno >actual && + cat >expected <<-\EOF && + D sub/d + D sub/dir/e + EOF test_cmp expected actual ' @@ -75,4 +94,15 @@ test_expect_success 'do not warn about sparse entries with --ignore-unmatch' ' git ls-files --error-unmatch b ' +test_expect_success 'refuse to rm a non-skip-worktree path outside sparse cone' ' + git reset --hard && + git sparse-checkout set a && + git update-index --no-skip-worktree b && + test_must_fail git rm b 2>stderr && + test_cmp b_error_and_hint stderr && + git rm --sparse b 2>stderr && + test_must_be_empty stderr && + test_path_is_missing b +' + test_done diff --git a/t/t3705-add-sparse-checkout.sh b/t/t3705-add-sparse-checkout.sh index 2b1fd0d0ee..5b904988d4 100755 --- a/t/t3705-add-sparse-checkout.sh +++ b/t/t3705-add-sparse-checkout.sh @@ -19,6 +19,7 @@ setup_sparse_entry () { fi && git add sparse_entry && git update-index --skip-worktree sparse_entry && + git commit --allow-empty -m "ensure sparse_entry exists at HEAD" && SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry) } @@ -36,14 +37,22 @@ setup_gitignore () { EOF } +test_sparse_entry_unstaged () { + git diff --staged -- sparse_entry >diff && + test_must_be_empty diff +} + test_expect_success 'setup' " cat >sparse_error_header <<-EOF && - The following pathspecs didn't match any eligible path, but they do match index - entries outside the current sparse checkout: + The following paths and/or pathspecs matched paths that exist + outside of your sparse-checkout definition, so will not be + updated in the index: EOF cat >sparse_hint <<-EOF && - hint: Disable or modify the sparsity rules if you intend to update such entries. + hint: If you intend to update such entries, try one of the following: + hint: * Use the --sparse option. + hint: * Disable or modify the sparsity rules. hint: Disable this message with \"git config advice.updateSparsePath false\" EOF @@ -55,6 +64,7 @@ test_expect_success 'git add does not remove sparse entries' ' setup_sparse_entry && rm sparse_entry && test_must_fail git add sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && test_sparse_entry_unchanged ' @@ -73,6 +83,7 @@ test_expect_success 'git add . does not remove sparse entries' ' rm sparse_entry && setup_gitignore && test_must_fail git add . 2>stderr && + test_sparse_entry_unstaged && cat sparse_error_header >expect && echo . >>expect && @@ -88,6 +99,7 @@ do setup_sparse_entry && echo modified >sparse_entry && test_must_fail git add $opt sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && test_sparse_entry_unchanged ' @@ -98,6 +110,7 @@ test_expect_success 'git add --refresh does not update sparse entries' ' git ls-files --debug sparse_entry | grep mtime >before && test-tool chmtime -60 sparse_entry && test_must_fail git add --refresh sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && git ls-files --debug sparse_entry | grep mtime >after && test_cmp before after @@ -106,6 +119,7 @@ test_expect_success 'git add --refresh does not update sparse entries' ' test_expect_success 'git add --chmod does not update sparse entries' ' setup_sparse_entry && test_must_fail git add --chmod=+x sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && test_sparse_entry_unchanged && ! test -x sparse_entry @@ -116,6 +130,7 @@ test_expect_success 'git add --renormalize does not update sparse entries' ' setup_sparse_entry "LINEONE\r\nLINETWO\r\n" && echo "sparse_entry text=auto" >.gitattributes && test_must_fail git add --renormalize sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && test_sparse_entry_unchanged ' @@ -124,6 +139,7 @@ test_expect_success 'git add --dry-run --ignore-missing warn on sparse path' ' setup_sparse_entry && rm sparse_entry && test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp error_and_hint stderr && test_sparse_entry_unchanged ' @@ -145,11 +161,57 @@ test_expect_success 'do not warn when pathspec matches dense entries' ' git ls-files --error-unmatch dense_entry ' +test_expect_success 'git add fails outside of sparse-checkout definition' ' + test_when_finished git sparse-checkout disable && + test_commit a && + git sparse-checkout init && + git sparse-checkout set a && + echo >>sparse_entry && + + git update-index --no-skip-worktree sparse_entry && + test_must_fail git add sparse_entry && + test_sparse_entry_unstaged && + + test_must_fail git add --chmod=+x sparse_entry && + test_sparse_entry_unstaged && + + test_must_fail git add --renormalize sparse_entry && + test_sparse_entry_unstaged && + + # Avoid munging CRLFs to avoid an error message + git -c core.autocrlf=input add --sparse sparse_entry 2>stderr && + test_must_be_empty stderr && + test-tool read-cache --table >actual && + grep "^100644 blob.*sparse_entry\$" actual && + + git add --sparse --chmod=+x sparse_entry 2>stderr && + test_must_be_empty stderr && + test-tool read-cache --table >actual && + grep "^100755 blob.*sparse_entry\$" actual && + + git reset && + + # This will print a message over stderr on Windows. + git add --sparse --renormalize sparse_entry && + git status --porcelain >actual && + grep "^M sparse_entry\$" actual +' + test_expect_success 'add obeys advice.updateSparsePath' ' setup_sparse_entry && test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr && + test_sparse_entry_unstaged && test_cmp sparse_entry_error stderr ' +test_expect_success 'add allows sparse entries with --sparse' ' + git sparse-checkout set a && + echo modified >sparse_entry && + test_must_fail git add sparse_entry && + test_sparse_entry_unchanged && + git add --sparse sparse_entry 2>stderr && + test_must_be_empty stderr +' + test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 873aa56e35..f0a82be9de 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1307,4 +1307,62 @@ test_expect_success 'stash -c stash.useBuiltin=false warning ' ' test_must_be_empty err ' +test_expect_success 'git stash succeeds despite directory/file change' ' + test_create_repo directory_file_switch_v1 && + ( + cd directory_file_switch_v1 && + test_commit init && + + test_write_lines this file has some words >filler && + git add filler && + git commit -m filler && + + git rm filler && + mkdir filler && + echo contents >filler/file && + git stash push + ) +' + +test_expect_success 'git stash can pop file -> directory saved changes' ' + test_create_repo directory_file_switch_v2 && + ( + cd directory_file_switch_v2 && + test_commit init && + + test_write_lines this file has some words >filler && + git add filler && + git commit -m filler && + + git rm filler && + mkdir filler && + echo contents >filler/file && + cp filler/file expect && + git stash push --include-untracked && + git stash apply --index && + test_cmp expect filler/file + ) +' + +test_expect_success 'git stash can pop directory -> file saved changes' ' + test_create_repo directory_file_switch_v3 && + ( + cd directory_file_switch_v3 && + test_commit init && + + mkdir filler && + test_write_lines some words >filler/file1 && + test_write_lines and stuff >filler/file2 && + git add filler && + git commit -m filler && + + git rm -rf filler && + echo contents >filler && + cp filler expect && + git stash push --include-untracked && + git stash apply --index && + test_cmp expect filler + ) +' + test_done diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index dd2cdcc114..5390eec4e3 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -422,4 +422,10 @@ test_expect_success 'stash show --{include,only}-untracked on stashes without un test_must_be_empty actual ' +test_expect_success 'stash -u ignores sub-repository' ' + test_when_finished "rm -rf sub-repo" && + git init sub-repo && + git stash -u +' + test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index bb04f0f23b..a3c72b68f7 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -168,18 +168,33 @@ test_expect_success 'write midx with two packs' ' compare_results_with_midx "two packs" +test_expect_success 'write midx with --stdin-packs' ' + rm -fr $objdir/pack/multi-pack-index && + + idx="$(find $objdir/pack -name "test-2-*.idx")" && + basename "$idx" >in && + + git multi-pack-index write --stdin-packs <in && + + test-tool read-midx $objdir | grep "\.idx$" >packs && + + test_cmp packs in +' + +compare_results_with_midx "mixed mode (one pack + extra)" + test_expect_success 'write progress off for redirected stderr' ' git multi-pack-index --object-dir=$objdir write 2>err && test_line_count = 0 err ' test_expect_success 'write force progress on for stderr' ' - GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --progress 2>err && test_file_not_empty err ' test_expect_success 'write with the --no-progress option' ' - GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress write 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --no-progress 2>err && test_line_count = 0 err ' @@ -474,12 +489,12 @@ test_expect_success 'repack progress off for redirected stderr' ' ' test_expect_success 'repack force progress on for stderr' ' - GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --progress 2>err && test_file_not_empty err ' test_expect_success 'repack with the --no-progress option' ' - GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress repack 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --no-progress 2>err && test_line_count = 0 err ' @@ -672,7 +687,7 @@ test_expect_success 'expire progress off for redirected stderr' ' test_expect_success 'expire force progress on for stderr' ' ( cd dup && - GIT_PROGRESS_DELAY=0 git multi-pack-index --progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index expire --progress 2>err && test_file_not_empty err ) ' @@ -680,7 +695,7 @@ test_expect_success 'expire force progress on for stderr' ' test_expect_success 'expire with the --no-progress option' ' ( cd dup && - GIT_PROGRESS_DELAY=0 git multi-pack-index --no-progress expire 2>err && + GIT_PROGRESS_DELAY=0 git multi-pack-index expire --no-progress 2>err && test_line_count = 0 err ) ' diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index 4ad7c2c969..e187f90f29 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -283,4 +283,116 @@ test_expect_success 'pack.preferBitmapTips' ' ) ' +test_expect_success 'writing a bitmap with --refs-snapshot' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit one && + test_commit two && + + git rev-parse one >snapshot && + + git repack -ad && + + # First, write a MIDX which see both refs/tags/one and + # refs/tags/two (causing both of those commits to receive + # bitmaps). + git multi-pack-index write --bitmap && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + test-tool bitmap list-commits | sort >bitmaps && + grep "$(git rev-parse one)" bitmaps && + grep "$(git rev-parse two)" bitmaps && + + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx-$(midx_checksum $objdir).rev && + rm -fr $midx && + + # Then again, but with a refs snapshot which only sees + # refs/tags/one. + git multi-pack-index write --bitmap --refs-snapshot=snapshot && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + test-tool bitmap list-commits | sort >bitmaps && + grep "$(git rev-parse one)" bitmaps && + ! grep "$(git rev-parse two)" bitmaps + ) +' + +test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit_bulk --message="%s" 103 && + + git log --format="%H" >commits.raw && + sort <commits.raw >commits && + + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && + + git multi-pack-index write --bitmap && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >before && + test_line_count = 1 before && + + ( + grep -vf before commits.raw && + # mark missing commits as preferred + sed "s/^/+/" before + ) >snapshot && + + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx-$(midx_checksum $objdir).rev && + rm -fr $midx && + + git multi-pack-index write --bitmap --refs-snapshot=snapshot && + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && + + ! test_cmp before after + ) +' + +test_expect_success 'hash-cache values are propagated from pack bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + test_commit base2 && + git repack -adb && + + test-tool bitmap dump-hashes >pack.raw && + test_file_not_empty pack.raw && + sort pack.raw >pack.hashes && + + test_commit new && + git repack && + git multi-pack-index write --bitmap && + + test-tool bitmap dump-hashes >midx.raw && + sort midx.raw >midx.hashes && + + # ensure that every namehash in the pack bitmap can be found in + # the midx bitmap (i.e., that there are no oid-namehash pairs + # unique to the pack bitmap). + comm -23 pack.hashes midx.hashes >dropped.hashes && + test_must_be_empty dropped.hashes + ) +' + test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 672001a18b..93ecfcdd24 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -546,15 +546,6 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' ' test_cmp expect actual ' -test_expect_success REBASE_P \ - 'pull.rebase=preserve rebases and merges keep-merge' ' - git reset --hard before-preserve-rebase && - test_config pull.rebase preserve && - git pull . copy && - test_cmp_rev HEAD^^ copy && - test_cmp_rev HEAD^2 keep-merge -' - test_expect_success 'pull.rebase=interactive' ' write_script "$TRASH_DIRECTORY/fake-editor" <<-\EOF && echo I was here >fake.out && @@ -598,7 +589,7 @@ test_expect_success '--rebase=false create a new merge commit' ' test_expect_success '--rebase=true rebases and flattens keep-merge' ' git reset --hard before-preserve-rebase && - test_config pull.rebase preserve && + test_config pull.rebase merges && git pull --rebase=true . copy && test_cmp_rev HEAD^^ copy && echo file3 >expect && @@ -606,23 +597,14 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' ' test_cmp expect actual ' -test_expect_success REBASE_P \ - '--rebase=preserve rebases and merges keep-merge' ' - git reset --hard before-preserve-rebase && - test_config pull.rebase true && - git pull --rebase=preserve . copy && - test_cmp_rev HEAD^^ copy && - test_cmp_rev HEAD^2 keep-merge -' - test_expect_success '--rebase=invalid fails' ' git reset --hard before-preserve-rebase && test_must_fail git pull --rebase=invalid . copy ' -test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' ' +test_expect_success '--rebase overrides pull.rebase=merges and flattens keep-merge' ' git reset --hard before-preserve-rebase && - test_config pull.rebase preserve && + test_config pull.rebase merges && git pull --rebase . copy && test_cmp_rev HEAD^^ copy && echo file3 >expect && diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index cffc47a8e3..f92c79c132 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -196,8 +196,8 @@ test_expect_success 'GIT_TRACE_CURL redacts auth details' ' # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted - ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace && - grep "Authorization: Basic <redacted>" trace + ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace && + grep -i "Authorization: Basic <redacted>" trace ' test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' @@ -208,8 +208,8 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted - ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace && - grep "Authorization: Basic <redacted>" trace + ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace && + grep -i "Authorization: Basic <redacted>" trace ' test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' ' @@ -219,7 +219,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && expect_askpass both user@host && - grep "Authorization: Basic [0-9a-zA-Z+/]" trace + grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace ' test_expect_success 'disable dumb http on server' ' @@ -474,10 +474,10 @@ test_expect_success 'cookies are redacted by default' ' GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=<redacted>" err && - grep "Cookie:.*Bar=<redacted>" err && - ! grep "Cookie:.*Foo=1" err && - ! grep "Cookie:.*Bar=2" err + grep -i "Cookie:.*Foo=<redacted>" err && + grep -i "Cookie:.*Bar=<redacted>" err && + ! grep -i "Cookie:.*Foo=1" err && + ! grep -i "Cookie:.*Bar=2" err ' test_expect_success 'empty values of cookies are also redacted' ' @@ -486,7 +486,7 @@ test_expect_success 'empty values of cookies are also redacted' ' GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=<redacted>" err + grep -i "Cookie:.*Foo=<redacted>" err ' test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' ' @@ -496,8 +496,8 @@ test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' ' GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=1" err && - grep "Cookie:.*Bar=2" err + grep -i "Cookie:.*Foo=1" err && + grep -i "Cookie:.*Bar=2" err ' test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' ' diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 930721f053..aa1827d841 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -72,6 +72,37 @@ test_expect_success 'request invalid command' ' test_i18ngrep "invalid command" err ' +test_expect_success 'request capability as command' ' + test-tool pkt-line pack >in <<-EOF && + command=agent + object-format=$(test_oid algo) + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && + grep invalid.command.*agent err +' + +test_expect_success 'request command as capability' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs + object-format=$(test_oid algo) + fetch + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && + grep unknown.capability err +' + +test_expect_success 'requested command is command=value' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs=whatever + object-format=$(test_oid algo) + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && + grep invalid.command.*ls-refs=whatever err +' + test_expect_success 'wrong object-format' ' test-tool pkt-line pack >in <<-EOF && command=fetch @@ -116,6 +147,19 @@ test_expect_success 'basics of ls-refs' ' test_cmp expect actual ' +test_expect_success 'ls-refs complains about unknown options' ' + test-tool pkt-line pack >in <<-EOF && + command=ls-refs + object-format=$(test_oid algo) + 0001 + no-such-arg + 0000 + EOF + + test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && + grep unexpected.line.*no-such-arg err +' + test_expect_success 'basic ref-prefixes' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs @@ -158,6 +202,37 @@ test_expect_success 'refs/heads prefix' ' test_cmp expect actual ' +test_expect_success 'ignore very large set of prefixes' ' + # generate a large number of ref-prefixes that we expect + # to match nothing; the value here exceeds TOO_MANY_PREFIXES + # from ls-refs.c. + { + echo command=ls-refs && + echo object-format=$(test_oid algo) && + echo 0001 && + perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" && + echo 0000 + } | + test-tool pkt-line pack >in && + + # and then confirm that we see unmatched prefixes anyway (i.e., + # that the prefix was not applied). + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/main) refs/heads/main + $(git rev-parse refs/heads/release) refs/heads/release + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + test-tool serve-v2 --stateless-rpc <in >out && + test-tool pkt-line unpack <out >actual && + test_cmp expect actual +' + test_expect_success 'peel parameter' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index d3687b1a2e..d527cf6c49 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -237,6 +237,19 @@ test_expect_success '...but not if explicitly forbidden by config' ' ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD ' +test_expect_success 'bare clone propagates empty default branch' ' + test_when_finished "rm -rf file_empty_parent file_empty_child.git" && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=mydefaultbranch init file_empty_parent && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=main -c protocol.version=2 \ + clone --bare \ + "file://$(pwd)/file_empty_parent" file_empty_child.git && + grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD +' + test_expect_success 'fetch with file:// using protocol v2' ' test_when_finished "rm -f log" && diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh index 5c941949b9..bc393d7c31 100755 --- a/t/t5704-protocol-violations.sh +++ b/t/t5704-protocol-violations.sh @@ -32,4 +32,19 @@ test_expect_success 'extra delim packet in v2 fetch args' ' test_i18ngrep "expected flush after fetch arguments" err ' +test_expect_success 'bogus symref in v0 capabilities' ' + test_commit foo && + oid=$(git rev-parse HEAD) && + dst=refs/heads/foo && + { + printf "%s HEAD\0symref object-format=%s symref=HEAD:%s\n" \ + "$oid" "$GIT_DEFAULT_HASH" "$dst" | + test-tool pkt-line pack-raw-stdin && + printf "0000" + } >input && + git ls-remote --symref --upload-pack="cat input; read junk;:" . >actual && + printf "ref: %s\tHEAD\n%s\tHEAD\n" "$dst" "$oid" >expect && + test_cmp expect actual +' + test_done diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh new file mode 100755 index 0000000000..545748949a --- /dev/null +++ b/t/t7002-mv-sparse-checkout.sh @@ -0,0 +1,189 @@ +#!/bin/sh + +test_description='git mv in sparse working trees' + +. ./test-lib.sh + +test_expect_success 'setup' " + mkdir -p sub/dir sub/dir2 && + touch a b c sub/d sub/dir/e sub/dir2/e && + git add -A && + git commit -m files && + + cat >sparse_error_header <<-EOF && + The following paths and/or pathspecs matched paths that exist + outside of your sparse-checkout definition, so will not be + updated in the index: + EOF + + cat >sparse_hint <<-EOF + hint: If you intend to update such entries, try one of the following: + hint: * Use the --sparse option. + hint: * Disable or modify the sparsity rules. + hint: Disable this message with \"git config advice.updateSparsePath false\" + EOF +" + +test_expect_success 'mv refuses to move sparse-to-sparse' ' + test_when_finished rm -f e && + git reset --hard && + git sparse-checkout set a && + touch b && + test_must_fail git mv b e 2>stderr && + cat sparse_error_header >expect && + echo b >>expect && + echo e >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse b e 2>stderr && + test_must_be_empty stderr +' + +test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' ' + test_when_finished rm -f b c e && + git reset --hard && + git sparse-checkout set a && + + # tracked-to-untracked + touch b && + git mv -k b e 2>stderr && + test_path_exists b && + test_path_is_missing e && + cat sparse_error_header >expect && + echo b >>expect && + echo e >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse b e 2>stderr && + test_must_be_empty stderr && + test_path_is_missing b && + test_path_exists e && + + # tracked-to-tracked + git reset --hard && + touch b && + git mv -k b c 2>stderr && + test_path_exists b && + test_path_is_missing c && + cat sparse_error_header >expect && + echo b >>expect && + echo c >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse b c 2>stderr && + test_must_be_empty stderr && + test_path_is_missing b && + test_path_exists c +' + +test_expect_success 'mv refuses to move non-sparse-to-sparse' ' + test_when_finished rm -f b c e && + git reset --hard && + git sparse-checkout set a && + + # tracked-to-untracked + test_must_fail git mv a e 2>stderr && + test_path_exists a && + test_path_is_missing e && + cat sparse_error_header >expect && + echo e >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse a e 2>stderr && + test_must_be_empty stderr && + test_path_is_missing a && + test_path_exists e && + + # tracked-to-tracked + rm e && + git reset --hard && + test_must_fail git mv a c 2>stderr && + test_path_exists a && + test_path_is_missing c && + cat sparse_error_header >expect && + echo c >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse a c 2>stderr && + test_must_be_empty stderr && + test_path_is_missing a && + test_path_exists c +' + +test_expect_success 'mv refuses to move sparse-to-non-sparse' ' + test_when_finished rm -f b c e && + git reset --hard && + git sparse-checkout set a e && + + # tracked-to-untracked + touch b && + test_must_fail git mv b e 2>stderr && + cat sparse_error_header >expect && + echo b >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse b e 2>stderr && + test_must_be_empty stderr +' + +test_expect_success 'recursive mv refuses to move (possible) sparse' ' + test_when_finished rm -rf b c e sub2 && + git reset --hard && + # Without cone mode, "sub" and "sub2" do not match + git sparse-checkout set sub/dir sub2/dir && + + # Add contained contents to ensure we avoid non-existence errors + mkdir sub/dir2 && + touch sub/d sub/dir2/e && + + test_must_fail git mv sub sub2 2>stderr && + cat sparse_error_header >expect && + cat >>expect <<-\EOF && + sub/d + sub2/d + sub/dir/e + sub2/dir/e + sub/dir2/e + sub2/dir2/e + EOF + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse sub sub2 2>stderr && + test_must_be_empty stderr && + git commit -m "moved sub to sub2" && + git rev-parse HEAD~1:sub >expect && + git rev-parse HEAD:sub2 >actual && + test_cmp expect actual && + git reset --hard HEAD~1 +' + +test_expect_success 'recursive mv refuses to move sparse' ' + git reset --hard && + # Use cone mode so "sub/" matches the sparse-checkout patterns + git sparse-checkout init --cone && + git sparse-checkout set sub/dir sub2/dir && + + # Add contained contents to ensure we avoid non-existence errors + mkdir sub/dir2 && + touch sub/dir2/e && + + test_must_fail git mv sub sub2 2>stderr && + cat sparse_error_header >expect && + cat >>expect <<-\EOF && + sub/dir2/e + sub2/dir2/e + EOF + cat sparse_hint >>expect && + test_cmp expect stderr && + git mv --sparse sub sub2 2>stderr && + test_must_be_empty stderr && + git commit -m "moved sub to sub2" && + git rev-parse HEAD~1:sub >expect && + git rev-parse HEAD:sub2 >actual && + test_cmp expect actual && + git reset --hard HEAD~1 +' + +test_done diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh index 19830d9036..a3e2413bc3 100755 --- a/t/t7112-reset-submodule.sh +++ b/t/t7112-reset-submodule.sh @@ -6,7 +6,6 @@ test_description='reset can handle submodules' . "$TEST_DIRECTORY"/lib-submodule-update.sh KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 -KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 test_submodule_switch_recursing_with_args "reset --keep" diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index 7a8194ce72..2a07c70867 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -250,7 +250,6 @@ test_rebase () { } test_rebase success -test_have_prereq !REBASE_P || test_rebase success -p test_expect_success 'with hook (cherry-pick)' ' test_when_finished "git checkout -f main" && diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh index 405420ae4d..163ae80468 100755 --- a/t/t7517-per-repo-email.sh +++ b/t/t7517-per-repo-email.sh @@ -75,19 +75,6 @@ test_expect_success 'noop interactive rebase does not care about ident' ' git rebase -i HEAD^ ' -test_expect_success REBASE_P \ - 'fast-forward rebase does not care about ident (preserve)' ' - git checkout -B tmp side-without-commit && - git rebase -p main -' - -test_expect_success REBASE_P \ - 'non-fast-forward rebase refuses to write commits (preserve)' ' - test_when_finished "git rebase --abort || true" && - git checkout -B tmp side-with-commit && - test_must_fail git rebase -p main -' - test_expect_success 'author.name overrides user.name' ' test_config user.name user && test_config user.email user@example.com && diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index f1463197b9..f488d930df 100755 --- a/t/t7519-status-fsmonitor.sh +++ b/t/t7519-status-fsmonitor.sh @@ -399,41 +399,45 @@ check_sparse_index_behavior () { } test_expect_success 'status succeeds with sparse index' ' - git clone . full && - git clone --sparse . sparse && - git -C sparse sparse-checkout init --cone --sparse-index && - git -C sparse sparse-checkout set dir1 dir2 && + ( + sane_unset GIT_TEST_SPLIT_INDEX && - write_script .git/hooks/fsmonitor-test <<-\EOF && - printf "last_update_token\0" - EOF - git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test && - git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test && - check_sparse_index_behavior ! && + git clone . full && + git clone --sparse . sparse && + git -C sparse sparse-checkout init --cone --sparse-index && + git -C sparse sparse-checkout set dir1 dir2 && - write_script .git/hooks/fsmonitor-test <<-\EOF && - printf "last_update_token\0" - printf "dir1/modified\0" - EOF - check_sparse_index_behavior ! && - - git -C sparse sparse-checkout add dir1a && + write_script .git/hooks/fsmonitor-test <<-\EOF && + printf "last_update_token\0" + EOF + git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test && + git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test && + check_sparse_index_behavior ! && - for repo in full sparse - do - cp -r $repo/dir1 $repo/dir1a && - git -C $repo add dir1a && - git -C $repo commit -m "add dir1a" || return 1 - done && - git -C sparse sparse-checkout set dir1 dir2 && - - # This one modifies outside the sparse-checkout definition - # and hence we expect to expand the sparse-index. - write_script .git/hooks/fsmonitor-test <<-\EOF && - printf "last_update_token\0" - printf "dir1a/modified\0" - EOF - check_sparse_index_behavior + write_script .git/hooks/fsmonitor-test <<-\EOF && + printf "last_update_token\0" + printf "dir1/modified\0" + EOF + check_sparse_index_behavior ! && + + git -C sparse sparse-checkout add dir1a && + + for repo in full sparse + do + cp -r $repo/dir1 $repo/dir1a && + git -C $repo add dir1a && + git -C $repo commit -m "add dir1a" || return 1 + done && + git -C sparse sparse-checkout set dir1 dir2 && + + # This one modifies outside the sparse-checkout definition + # and hence we expect to expand the sparse-index. + write_script .git/hooks/fsmonitor-test <<-\EOF && + printf "last_update_token\0" + printf "dir1a/modified\0" + EOF + check_sparse_index_behavior + ) ' test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 98eda3bfeb..0260ad6f0e 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -3,6 +3,8 @@ test_description='git repack works correctly' . ./test-lib.sh +. "${TEST_DIRECTORY}/lib-bitmap.sh" +. "${TEST_DIRECTORY}/lib-midx.sh" commit_and_pack () { test_commit "$@" 1>&2 && @@ -234,4 +236,140 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' ' test_must_be_empty actual ' +objdir=.git/objects +midx=$objdir/pack/multi-pack-index + +test_expect_success 'setup for --write-midx tests' ' + git init midx && + ( + cd midx && + git config core.multiPackIndex true && + + test_commit base + ) +' + +test_expect_success '--write-midx unchanged' ' + ( + cd midx && + GIT_TEST_MULTI_PACK_INDEX=0 git repack && + test_path_is_missing $midx && + test_path_is_missing $midx-*.bitmap && + + GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx && + + test_path_is_file $midx && + test_path_is_missing $midx-*.bitmap && + test_midx_consistent $objdir + ) +' + +test_expect_success '--write-midx with a new pack' ' + ( + cd midx && + test_commit loose && + + GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx && + + test_path_is_file $midx && + test_path_is_missing $midx-*.bitmap && + test_midx_consistent $objdir + ) +' + +test_expect_success '--write-midx with -b' ' + ( + cd midx && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -mb && + + test_path_is_file $midx && + test_path_is_file $midx-*.bitmap && + test_midx_consistent $objdir + ) +' + +test_expect_success '--write-midx with -d' ' + ( + cd midx && + test_commit repack && + + GIT_TEST_MULTI_PACK_INDEX=0 git repack -Ad --write-midx && + + test_path_is_file $midx && + test_path_is_missing $midx-*.bitmap && + test_midx_consistent $objdir + ) +' + +test_expect_success 'cleans up MIDX when appropriate' ' + ( + cd midx && + + test_commit repack-2 && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx && + + checksum=$(midx_checksum $objdir) && + test_path_is_file $midx && + test_path_is_file $midx-$checksum.bitmap && + test_path_is_file $midx-$checksum.rev && + + test_commit repack-3 && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx && + + test_path_is_file $midx && + test_path_is_missing $midx-$checksum.bitmap && + test_path_is_missing $midx-$checksum.rev && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test_path_is_file $midx-$(midx_checksum $objdir).rev && + + test_commit repack-4 && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb && + + find $objdir/pack -type f -name "multi-pack-index*" >files && + test_must_be_empty files + ) +' + +test_expect_success '--write-midx with preferred bitmap tips' ' + git init midx-preferred-tips && + test_when_finished "rm -fr midx-preferred-tips" && + ( + cd midx-preferred-tips && + + test_commit_bulk --message="%s" 103 && + + git log --format="%H" >commits.raw && + sort <commits.raw >commits && + + git log --format="create refs/tags/%s/%s %H" HEAD >refs && + git update-ref --stdin <refs && + + git repack --write-midx --write-bitmap-index && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >before && + test_line_count = 1 before && + + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx-$(midx_checksum $objdir).rev && + rm -fr $midx && + + # instead of constructing the snapshot ourselves (c.f., the test + # "write a bitmap with --refs-snapshot (preferred tips)" in + # t5326), mark the missing commit as preferred by adding it to + # the pack.preferBitmapTips configuration. + git for-each-ref --format="%(refname:rstrip=1)" \ + --points-at="$(cat before)" >missing && + git config pack.preferBitmapTips "$(cat missing)" && + git repack --write-midx --write-bitmap-index && + + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && + + ! test_cmp before after + ) +' + test_done diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh index 5ccaa440e0..bdbbcbf1ec 100755 --- a/t/t7703-repack-geometric.sh +++ b/t/t7703-repack-geometric.sh @@ -15,7 +15,7 @@ test_expect_success '--geometric with no packs' ' ( cd geometric && - git repack --geometric 2 >out && + git repack --write-midx --geometric 2 >out && test_i18ngrep "Nothing new to pack" out ) ' @@ -180,4 +180,26 @@ test_expect_success '--geometric ignores kept packs' ' ) ' +test_expect_success '--geometric chooses largest MIDX preferred pack' ' + git init geometric && + test_when_finished "rm -fr geometric" && + ( + cd geometric && + + # These packs already form a geometric progression. + test_commit_bulk --start=1 1 && # 3 objects + test_commit_bulk --start=2 2 && # 6 objects + ls $objdir/pack/pack-*.idx >before && + test_commit_bulk --start=4 4 && # 12 objects + ls $objdir/pack/pack-*.idx >after && + + git repack --geometric 2 -dbm && + + comm -3 before after | xargs -n 1 basename >expect && + test-tool read-midx --preferred-pack $objdir >actual && + + test_cmp expect actual + ) +' + test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index a173f564bc..096456292c 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -453,6 +453,13 @@ run_dir_diff_test 'difftool --dir-diff' ' grep "^file$" output ' +run_dir_diff_test 'difftool --dir-diff avoids repeated slashes in TMPDIR' ' + TMPDIR="${TMPDIR:-/tmp}////" \ + git difftool --dir-diff $symlinks --extcmd echo branch >output && + grep -v // output >actual && + test_line_count = 1 actual +' + run_dir_diff_test 'difftool --dir-diff ignores --prompt' ' git difftool --dir-diff $symlinks --prompt --extcmd ls branch >output && grep "^sub$" output && @@ -674,7 +681,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' ' rm c && ln -s d c && cat >expect <<-EOF && - b c c @@ -710,7 +716,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' ' # Deleted symlinks rm -f c && cat >expect <<-EOF && - b c EOF @@ -723,6 +728,71 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' ' test_cmp expect actual ' +test_expect_success SYMLINKS 'difftool --dir-diff writes symlinks as raw text' ' + # Start out on a branch called "branch-init". + git init -b branch-init symlink-files && + ( + cd symlink-files && + # This test ensures that symlinks are written as raw text. + # The "cat" tools output link and file contents. + git config difftool.cat-left-link.cmd "cat \"\$LOCAL/link\"" && + git config difftool.cat-left-a.cmd "cat \"\$LOCAL/file-a\"" && + git config difftool.cat-right-link.cmd "cat \"\$REMOTE/link\"" && + git config difftool.cat-right-b.cmd "cat \"\$REMOTE/file-b\"" && + + # Record the empty initial state so that we can come back here + # later and not have to consider the any cases where difftool + # will create symlinks back into the worktree. + test_tick && + git commit --allow-empty -m init && + + # Create a file called "file-a" with a symlink pointing to it. + git switch -c branch-a && + echo a >file-a && + ln -s file-a link && + git add file-a link && + test_tick && + git commit -m link-to-file-a && + + # Create a file called "file-b" and point the symlink to it. + git switch -c branch-b && + echo b >file-b && + rm link && + ln -s file-b link && + git add file-b link && + git rm file-a && + test_tick && + git commit -m link-to-file-b && + + # Checkout the initial branch so that the --symlinks behavior is + # not activated. The two directories should be completely + # independent with no symlinks pointing back here. + git switch branch-init && + + # The left link must be "file-a" and "file-a" must contain "a". + echo file-a >expect && + git difftool --symlinks --dir-diff --tool cat-left-link \ + branch-a branch-b >actual && + test_cmp expect actual && + + echo a >expect && + git difftool --symlinks --dir-diff --tool cat-left-a \ + branch-a branch-b >actual && + test_cmp expect actual && + + # The right link must be "file-b" and "file-b" must contain "b". + echo file-b >expect && + git difftool --symlinks --dir-diff --tool cat-right-link \ + branch-a branch-b >actual && + test_cmp expect actual && + + echo b >expect && + git difftool --symlinks --dir-diff --tool cat-right-b \ + branch-a branch-b >actual && + test_cmp expect actual + ) +' + test_expect_success 'add -N and difftool -d' ' test_when_finished git reset --hard && diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 3172f5b936..058e5d0c96 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -441,4 +441,107 @@ test_expect_success 'grep --recurse-submodules with --cached ignores worktree mo test_must_fail git grep --recurse-submodules --cached "A modified line in submodule" >actual 2>&1 && test_must_be_empty actual ' + +test_expect_failure 'grep --textconv: superproject .gitattributes does not affect submodules' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + echo "a diff=d2x" >.gitattributes && + + cat >expect <<-\EOF && + a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +test_expect_failure 'grep --textconv: superproject .gitattributes (from index) does not affect submodules' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + echo "a diff=d2x" >.gitattributes && + git add .gitattributes && + rm .gitattributes && + + cat >expect <<-\EOF && + a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +test_expect_failure 'grep --textconv: superproject .git/info/attributes does not affect submodules' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + super_attr="$(git rev-parse --git-path info/attributes)" && + test_when_finished "rm -f \"$super_attr\"" && + echo "a diff=d2x" >"$super_attr" && + + cat >expect <<-\EOF && + a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +# Note: what currently prevents this test from passing is not that the +# .gitattributes file from "./submodule" is being ignored, but that it is being +# propagated to the nested "./submodule/sub" files. +# +test_expect_failure 'grep --textconv correctly reads submodule .gitattributes' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + echo "a diff=d2x" >submodule/.gitattributes && + + cat >expect <<-\EOF && + submodule/a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +test_expect_failure 'grep --textconv correctly reads submodule .gitattributes (from index)' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + echo "a diff=d2x" >submodule/.gitattributes && + git -C submodule add .gitattributes && + rm submodule/.gitattributes && + + cat >expect <<-\EOF && + submodule/a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +test_expect_failure 'grep --textconv correctly reads submodule .git/info/attributes' ' + reset_and_clean && + test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && + + submodule_attr="$(git -C submodule rev-parse --path-format=absolute --git-path info/attributes)" && + test_when_finished "rm -f \"$submodule_attr\"" && + echo "a diff=d2x" >"$submodule_attr" && + + cat >expect <<-\EOF && + submodule/a:(1|2)x(3|4) + EOF + git grep --textconv --recurse-submodules x >actual && + test_cmp expect actual +' + +test_expect_failure 'grep saves textconv cache in the appropriate repository' ' + reset_and_clean && + test_config_global diff.d2x_cached.textconv "sed -e \"s/d/x/\"" && + test_config_global diff.d2x_cached.cachetextconv true && + echo "a diff=d2x_cached" >submodule/.gitattributes && + + # We only read/write to the textconv cache when grepping from an OID, + # as the working tree file might have modifications. + git grep --textconv --cached --recurse-submodules x && + + super_textconv_cache="$(git rev-parse --git-path refs/notes/textconv/d2x_cached)" && + sub_textconv_cache="$(git -C submodule rev-parse \ + --path-format=absolute --git-path refs/notes/textconv/d2x_cached)" && + test_path_is_missing "$super_textconv_cache" && + test_path_is_file "$sub_textconv_cache" +' + test_done diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 31245f6276..9b9f11a8e7 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -21,8 +21,7 @@ test_xmllint () { } test_lazy_prereq SYSTEMD_ANALYZE ' - systemd-analyze --help >out && - grep verify out + systemd-analyze verify /lib/systemd/system/basic.target ' test_systemd_analyze_verify () { diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 2d29d486ee..17f988edd2 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -36,6 +36,13 @@ CVSWORK="$PWD/cvswork" CVS_SERVER=git-cvsserver export CVSROOT CVS_SERVER +if perl -e 'exit(1) if not defined crypt("", "cv")' +then + PWDHASH='lac2ItudM3.KM' +else + PWDHASH='$2b$10$t8fGvE/a9eLmfOLzsZme2uOa2QtoMYwIxq9wZA6aBKtF1Yb7FJIzi' +fi + rm -rf "$CVSWORK" "$SERVERDIR" test_expect_success 'setup' ' git config push.default matching && @@ -54,7 +61,7 @@ test_expect_success 'setup' ' GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" && GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" && - echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db" + echo "cvsuser:$PWDHASH" >"$SERVERDIR/auth.db" ' # note that cvs doesn't accept absolute pathnames diff --git a/t/test-lib.sh b/t/test-lib.sh index aa1ad8180e..151da80c56 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1381,6 +1381,26 @@ then test_done fi +# skip non-whitelisted tests when compiled with SANITIZE=leak +if test -n "$SANITIZE_LEAK" +then + if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false + then + # We need to see it in "git env--helper" (via + # test_bool_env) + export TEST_PASSES_SANITIZE_LEAK + + if ! test_bool_env TEST_PASSES_SANITIZE_LEAK false + then + skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true" + test_done + fi + fi +elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false +then + error "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak" +fi + # Last-minute variable setup USER_HOME="$HOME" HOME="$TRASH_DIRECTORY" @@ -1534,6 +1554,7 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON test -n "$USE_LIBPCRE2" && test_set_prereq PCRE test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT +test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK if test -z "$GIT_TEST_CHECK_CACHE_TREE" then @@ -1709,10 +1730,6 @@ test_lazy_prereq SHA1 ' esac ' -test_lazy_prereq REBASE_P ' - test -z "$GIT_TEST_SKIP_REBASE_P" -' - # Ensure that no test accidentally triggers a Git command # that runs the actual maintenance scheduler, affecting a user's # system permanently. @@ -89,7 +89,7 @@ struct trace_key { extern struct trace_key trace_default_key; -#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 } +#define TRACE_KEY_INIT(name) { .key = "GIT_TRACE_" #name } extern struct trace_key trace_perf_key; extern struct trace_key trace_setup_key; @@ -394,6 +394,37 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd, us_elapsed_child); } +void trace2_child_ready_fl(const char *file, int line, + struct child_process *cmd, + const char *ready) +{ + struct tr2_tgt *tgt_j; + int j; + uint64_t us_now; + uint64_t us_elapsed_absolute; + uint64_t us_elapsed_child; + + if (!trace2_enabled) + return; + + us_now = getnanotime() / 1000; + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now); + + if (cmd->trace2_child_us_start) + us_elapsed_child = us_now - cmd->trace2_child_us_start; + else + us_elapsed_child = 0; + + for_each_wanted_builtin (j, tgt_j) + if (tgt_j->pfn_child_ready_fl) + tgt_j->pfn_child_ready_fl(file, line, + us_elapsed_absolute, + cmd->trace2_child_id, + cmd->pid, + ready, + us_elapsed_child); +} + int trace2_exec_fl(const char *file, int line, const char *exe, const char **argv) { @@ -254,6 +254,31 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd, trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code)) /** + * Emits a "child_ready" message containing the "child-id" and a flag + * indicating whether the child was considered "ready" when we + * released it. + * + * This function should be called after starting a daemon process in + * the background (and after giving it sufficient time to boot + * up) to indicate that we no longer control or own it. + * + * The "ready" argument should contain one of { "ready", "timeout", + * "error" } to indicate the state of the running daemon when we + * released it. + * + * If the daemon process fails to start or it exits or is terminated + * while we are still waiting for it, the caller should emit a + * regular "child_exit" to report the normal process exit information. + * + */ +void trace2_child_ready_fl(const char *file, int line, + struct child_process *cmd, + const char *ready); + +#define trace2_child_ready(cmd, ready) \ + trace2_child_ready_fl(__FILE__, __LINE__, (cmd), (ready)) + +/** * Emit an 'exec' event prior to calling one of exec(), execv(), * execvp(), and etc. On Unix-derived systems, this will be the * last event emitted for the current process, unless the exec @@ -350,7 +375,7 @@ void trace2_def_repo_fl(const char *file, int line, struct repository *repo); * being started, such as "read_recursive" or "do_read_index". * * The `repo` field, if set, will be used to get the "repo-id", so that - * recursive oerations can be attributed to the correct repository. + * recursive operations can be attributed to the correct repository. */ void trace2_region_enter_fl(const char *file, int line, const char *category, const char *label, const struct repository *repo, ...); diff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h index 1f66fd6573..65f94e1574 100644 --- a/trace2/tr2_tgt.h +++ b/trace2/tr2_tgt.h @@ -45,6 +45,10 @@ typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line, uint64_t us_elapsed_absolute, int cid, int pid, int code, uint64_t us_elapsed_child); +typedef void(tr2_tgt_evt_child_ready_fl_t)(const char *file, int line, + uint64_t us_elapsed_absolute, + int cid, int pid, const char *ready, + uint64_t us_elapsed_child); typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line, uint64_t us_elapsed_absolute); @@ -116,6 +120,7 @@ struct tr2_tgt { tr2_tgt_evt_alias_fl_t *pfn_alias_fl; tr2_tgt_evt_child_start_fl_t *pfn_child_start_fl; tr2_tgt_evt_child_exit_fl_t *pfn_child_exit_fl; + tr2_tgt_evt_child_ready_fl_t *pfn_child_ready_fl; tr2_tgt_evt_thread_start_fl_t *pfn_thread_start_fl; tr2_tgt_evt_thread_exit_fl_t *pfn_thread_exit_fl; tr2_tgt_evt_exec_fl_t *pfn_exec_fl; diff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c index 578a9a5287..70cfc2f77c 100644 --- a/trace2/tr2_tgt_event.c +++ b/trace2/tr2_tgt_event.c @@ -383,6 +383,27 @@ static void fn_child_exit_fl(const char *file, int line, jw_release(&jw); } +static void fn_child_ready_fl(const char *file, int line, + uint64_t us_elapsed_absolute, int cid, int pid, + const char *ready, uint64_t us_elapsed_child) +{ + const char *event_name = "child_ready"; + struct json_writer jw = JSON_WRITER_INIT; + double t_rel = (double)us_elapsed_child / 1000000.0; + + jw_object_begin(&jw, 0); + event_fmt_prepare(event_name, file, line, NULL, &jw); + jw_object_intmax(&jw, "child_id", cid); + jw_object_intmax(&jw, "pid", pid); + jw_object_string(&jw, "ready", ready); + jw_object_double(&jw, "t_rel", 6, t_rel); + jw_end(&jw); + + tr2_dst_write_line(&tr2dst_event, &jw.json); + + jw_release(&jw); +} + static void fn_thread_start_fl(const char *file, int line, uint64_t us_elapsed_absolute) { @@ -610,6 +631,7 @@ struct tr2_tgt tr2_tgt_event = { fn_alias_fl, fn_child_start_fl, fn_child_exit_fl, + fn_child_ready_fl, fn_thread_start_fl, fn_thread_exit_fl, fn_exec_fl, diff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c index a5751c8864..58d9e430f0 100644 --- a/trace2/tr2_tgt_normal.c +++ b/trace2/tr2_tgt_normal.c @@ -251,6 +251,19 @@ static void fn_child_exit_fl(const char *file, int line, strbuf_release(&buf_payload); } +static void fn_child_ready_fl(const char *file, int line, + uint64_t us_elapsed_absolute, int cid, int pid, + const char *ready, uint64_t us_elapsed_child) +{ + struct strbuf buf_payload = STRBUF_INIT; + double elapsed = (double)us_elapsed_child / 1000000.0; + + strbuf_addf(&buf_payload, "child_ready[%d] pid:%d ready:%s elapsed:%.6f", + cid, pid, ready, elapsed); + normal_io_write_fl(file, line, &buf_payload); + strbuf_release(&buf_payload); +} + static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute, int exec_id, const char *exe, const char **argv) { @@ -330,6 +343,7 @@ struct tr2_tgt tr2_tgt_normal = { fn_alias_fl, fn_child_start_fl, fn_child_exit_fl, + fn_child_ready_fl, NULL, /* thread_start */ NULL, /* thread_exit */ fn_exec_fl, diff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c index af4d65a0a5..e4acca13d6 100644 --- a/trace2/tr2_tgt_perf.c +++ b/trace2/tr2_tgt_perf.c @@ -360,6 +360,20 @@ static void fn_child_exit_fl(const char *file, int line, strbuf_release(&buf_payload); } +static void fn_child_ready_fl(const char *file, int line, + uint64_t us_elapsed_absolute, int cid, int pid, + const char *ready, uint64_t us_elapsed_child) +{ + const char *event_name = "child_ready"; + struct strbuf buf_payload = STRBUF_INIT; + + strbuf_addf(&buf_payload, "[ch%d] pid:%d ready:%s", cid, pid, ready); + + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute, + &us_elapsed_child, NULL, &buf_payload); + strbuf_release(&buf_payload); +} + static void fn_thread_start_fl(const char *file, int line, uint64_t us_elapsed_absolute) { @@ -553,6 +567,7 @@ struct tr2_tgt tr2_tgt_perf = { fn_alias_fl, fn_child_start_fl, fn_child_exit_fl, + fn_child_ready_fl, fn_thread_start_fl, fn_thread_exit_fl, fn_exec_fl, diff --git a/transport.c b/transport.c index b37664ba87..e4f1decae2 100644 --- a/transport.c +++ b/transport.c @@ -1,7 +1,7 @@ #include "cache.h" #include "config.h" #include "transport.h" -#include "run-command.h" +#include "hook.h" #include "pkt-line.h" #include "fetch-pack.h" #include "remote.h" diff --git a/transport.h b/transport.h index 1cbab11373..8bb4c8bbc8 100644 --- a/transport.h +++ b/transport.h @@ -262,7 +262,9 @@ struct transport_ls_refs_options { */ char *unborn_head_target; }; -#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT } +#define TRANSPORT_LS_REFS_OPTIONS_INIT { \ + .ref_prefixes = STRVEC_INIT, \ +} /* * Retrieve refs from a remote. diff --git a/unicode-width.h b/unicode-width.h index b50e686bae..97c851b27d 100644 --- a/unicode-width.h +++ b/unicode-width.h @@ -26,7 +26,9 @@ static const struct interval zero_width[] = { { 0x0825, 0x0827 }, { 0x0829, 0x082D }, { 0x0859, 0x085B }, -{ 0x08D3, 0x0902 }, +{ 0x0890, 0x0891 }, +{ 0x0898, 0x089F }, +{ 0x08CA, 0x0902 }, { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, @@ -66,6 +68,7 @@ static const struct interval zero_width[] = { { 0x0BCD, 0x0BCD }, { 0x0C00, 0x0C00 }, { 0x0C04, 0x0C04 }, +{ 0x0C3C, 0x0C3C }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, @@ -116,7 +119,7 @@ static const struct interval zero_width[] = { { 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 }, -{ 0x1732, 0x1734 }, +{ 0x1732, 0x1733 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, @@ -124,7 +127,7 @@ static const struct interval zero_width[] = { { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, -{ 0x180B, 0x180E }, +{ 0x180B, 0x180F }, { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, @@ -140,7 +143,7 @@ static const struct interval zero_width[] = { { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C }, { 0x1A7F, 0x1A7F }, -{ 0x1AB0, 0x1AC0 }, +{ 0x1AB0, 0x1ACE }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, @@ -163,8 +166,7 @@ static const struct interval zero_width[] = { { 0x1CED, 0x1CED }, { 0x1CF4, 0x1CF4 }, { 0x1CF8, 0x1CF9 }, -{ 0x1DC0, 0x1DF9 }, -{ 0x1DFB, 0x1DFF }, +{ 0x1DC0, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2064 }, @@ -227,12 +229,16 @@ static const struct interval zero_width[] = { { 0x10D24, 0x10D27 }, { 0x10EAB, 0x10EAC }, { 0x10F46, 0x10F50 }, +{ 0x10F82, 0x10F85 }, { 0x11001, 0x11001 }, { 0x11038, 0x11046 }, +{ 0x11070, 0x11070 }, +{ 0x11073, 0x11074 }, { 0x1107F, 0x11081 }, { 0x110B3, 0x110B6 }, { 0x110B9, 0x110BA }, { 0x110BD, 0x110BD }, +{ 0x110C2, 0x110C2 }, { 0x110CD, 0x110CD }, { 0x11100, 0x11102 }, { 0x11127, 0x1112B }, @@ -315,6 +321,8 @@ static const struct interval zero_width[] = { { 0x16FE4, 0x16FE4 }, { 0x1BC9D, 0x1BC9E }, { 0x1BCA0, 0x1BCA3 }, +{ 0x1CF00, 0x1CF2D }, +{ 0x1CF30, 0x1CF46 }, { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, @@ -332,6 +340,7 @@ static const struct interval zero_width[] = { { 0x1E023, 0x1E024 }, { 0x1E026, 0x1E02A }, { 0x1E130, 0x1E136 }, +{ 0x1E2AE, 0x1E2AE }, { 0x1E2EC, 0x1E2EF }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A }, @@ -404,7 +413,10 @@ static const struct interval double_width[] = { { 0x17000, 0x187F7 }, { 0x18800, 0x18CD5 }, { 0x18D00, 0x18D08 }, -{ 0x1B000, 0x1B11E }, +{ 0x1AFF0, 0x1AFF3 }, +{ 0x1AFF5, 0x1AFFB }, +{ 0x1AFFD, 0x1AFFE }, +{ 0x1B000, 0x1B122 }, { 0x1B150, 0x1B152 }, { 0x1B164, 0x1B167 }, { 0x1B170, 0x1B2FB }, @@ -439,21 +451,23 @@ static const struct interval double_width[] = { { 0x1F6CC, 0x1F6CC }, { 0x1F6D0, 0x1F6D2 }, { 0x1F6D5, 0x1F6D7 }, +{ 0x1F6DD, 0x1F6DF }, { 0x1F6EB, 0x1F6EC }, { 0x1F6F4, 0x1F6FC }, { 0x1F7E0, 0x1F7EB }, +{ 0x1F7F0, 0x1F7F0 }, { 0x1F90C, 0x1F93A }, { 0x1F93C, 0x1F945 }, -{ 0x1F947, 0x1F978 }, -{ 0x1F97A, 0x1F9CB }, -{ 0x1F9CD, 0x1F9FF }, +{ 0x1F947, 0x1F9FF }, { 0x1FA70, 0x1FA74 }, -{ 0x1FA78, 0x1FA7A }, +{ 0x1FA78, 0x1FA7C }, { 0x1FA80, 0x1FA86 }, -{ 0x1FA90, 0x1FAA8 }, -{ 0x1FAB0, 0x1FAB6 }, -{ 0x1FAC0, 0x1FAC2 }, -{ 0x1FAD0, 0x1FAD6 }, +{ 0x1FA90, 0x1FAAC }, +{ 0x1FAB0, 0x1FABA }, +{ 0x1FAC0, 0x1FAC5 }, +{ 0x1FAD0, 0x1FAD9 }, +{ 0x1FAE0, 0x1FAE7 }, +{ 0x1FAF0, 0x1FAF6 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD } }; diff --git a/unpack-trees.c b/unpack-trees.c index 8ea0a542da..89ca95ce90 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1694,9 +1694,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options static struct cache_entry *dfc; struct pattern_list pl; int free_pattern_list = 0; + struct dir_struct dir = DIR_INIT; + + if (o->reset == UNPACK_RESET_INVALID) + BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED"); if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); + if (o->dir) + BUG("o->dir is for internal use only"); trace_performance_enter(); trace2_region_enter("unpack_trees", "unpack_trees", the_repository); @@ -1707,6 +1713,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ensure_full_index(o->dst_index); } + if (o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED && + o->preserve_ignored) + BUG("UNPACK_RESET_OVERWRITE_UNTRACKED incompatible with preserved ignored files"); + + if (!o->preserve_ignored) { + o->dir = &dir; + o->dir->flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(o->dir); + } + if (!core_apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout && !o->pl) { @@ -1868,6 +1884,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options done: if (free_pattern_list) clear_pattern_list(&pl); + if (o->dir) { + dir_clear(o->dir); + o->dir = NULL; + } trace2_region_leave("unpack_trees", "unpack_trees", the_repository); trace_performance_leave("unpack_trees"); return ret; @@ -2136,9 +2156,10 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, if (o->dir) d.exclude_per_dir = o->dir->exclude_per_dir; i = read_directory(&d, o->src_index, pathbuf, namelen+1, NULL); + dir_clear(&d); + free(pathbuf); if (i) return add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name); - free(pathbuf); return cnt; } @@ -2158,9 +2179,15 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } +enum absent_checking_type { + COMPLETELY_ABSENT, + ABSENT_ANY_DIRECTORY +}; + static int check_ok_to_remove(const char *name, int len, int dtype, const struct cache_entry *ce, struct stat *st, enum unpack_trees_error_types error_type, + enum absent_checking_type absent_type, struct unpack_trees_options *o) { const struct cache_entry *result; @@ -2195,6 +2222,10 @@ static int check_ok_to_remove(const char *name, int len, int dtype, return 0; } + /* If we only care about directories, then we can remove */ + if (absent_type == ABSENT_ANY_DIRECTORY) + return 0; + /* * The previous round may already have decided to * delete this path, which is in a subdirectory that @@ -2215,12 +2246,14 @@ static int check_ok_to_remove(const char *name, int len, int dtype, */ static int verify_absent_1(const struct cache_entry *ce, enum unpack_trees_error_types error_type, + enum absent_checking_type absent_type, struct unpack_trees_options *o) { int len; struct stat st; - if (o->index_only || o->reset || !o->update) + if (o->index_only || !o->update || + o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED) return 0; len = check_leading_path(ce->name, ce_namelen(ce), 0); @@ -2240,7 +2273,8 @@ static int verify_absent_1(const struct cache_entry *ce, NULL, o); else ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL, - &st, error_type, o); + &st, error_type, + absent_type, o); } free(path); return ret; @@ -2255,7 +2289,7 @@ static int verify_absent_1(const struct cache_entry *ce, return check_ok_to_remove(ce->name, ce_namelen(ce), ce_to_dtype(ce), ce, &st, - error_type, o); + error_type, absent_type, o); } } @@ -2265,14 +2299,23 @@ static int verify_absent(const struct cache_entry *ce, { if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE)) return 0; - return verify_absent_1(ce, error_type, o); + return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o); +} + +static int verify_absent_if_directory(const struct cache_entry *ce, + enum unpack_trees_error_types error_type, + struct unpack_trees_options *o) +{ + if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE)) + return 0; + return verify_absent_1(ce, error_type, ABSENT_ANY_DIRECTORY, o); } static int verify_absent_sparse(const struct cache_entry *ce, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { - return verify_absent_1(ce, error_type, o); + return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o); } static int merged_entry(const struct cache_entry *ce, @@ -2346,6 +2389,12 @@ static int merged_entry(const struct cache_entry *ce, * Previously unmerged entry left as an existence * marker by read_index_unmerged(); */ + if (verify_absent_if_directory(merge, + ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) { + discard_cache_entry(merge); + return -1; + } + invalidate_ce_path(old, o); } @@ -2363,7 +2412,10 @@ static int deleted_entry(const struct cache_entry *ce, if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) return -1; return 0; + } else if (verify_absent_if_directory(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) { + return -1; } + if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o)) return -1; add_entry(o, ce, CE_REMOVE, 0); diff --git a/unpack-trees.h b/unpack-trees.h index 2d88b19dca..71ffb7eeb0 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -45,10 +45,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, */ void clear_unpack_trees_porcelain(struct unpack_trees_options *opts); +enum unpack_trees_reset_type { + UNPACK_RESET_NONE = 0, /* traditional "false" value; still valid */ + UNPACK_RESET_INVALID = 1, /* "true" no longer valid; use below values */ + UNPACK_RESET_PROTECT_UNTRACKED, + UNPACK_RESET_OVERWRITE_UNTRACKED +}; + struct unpack_trees_options { - unsigned int reset, - merge, + unsigned int merge, update, + preserve_ignored, clone, index_only, nontrivial_merge, @@ -64,9 +71,9 @@ struct unpack_trees_options { exiting_early, show_all_errors, dry_run; + enum unpack_trees_reset_type reset; const char *prefix; int cache_bottom; - struct dir_struct *dir; struct pathspec *pathspec; merge_fn_t fn; const char *msgs[NB_UNPACK_TREES_WARNING_TYPES]; @@ -88,6 +95,7 @@ struct unpack_trees_options { struct index_state result; struct pattern_list *pl; /* for internal use */ + struct dir_struct *dir; /* for internal use only */ struct checkout_metadata meta; }; diff --git a/urlmatch.h b/urlmatch.h index 6ff42f81b0..34a3ba6d19 100644 --- a/urlmatch.h +++ b/urlmatch.h @@ -66,6 +66,10 @@ struct urlmatch_config { int (*fallback_match_fn)(const char *url, void *cb); }; +#define URLMATCH_CONFIG_INIT { \ + .vars = STRING_LIST_INIT_DUP, \ +} + int urlmatch_config_entry(const char *var, const char *value, void *cb); #endif /* URL_MATCH_H */ @@ -145,6 +145,18 @@ void *xcalloc(size_t nmemb, size_t size) return ret; } +void xsetenv(const char *name, const char *value, int overwrite) +{ + if (setenv(name, value, overwrite)) + die_errno(_("could not setenv '%s'"), name ? name : "(null)"); +} + +void xunsetenv(const char *name) +{ + if (!unsetenv(name)) + die_errno(_("could not unsetenv '%s'"), name ? name : "(null)"); +} + /* * Limit size of IO chunks, because huge chunks only cause pain. OS X * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in |