summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml230
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml12
-rw-r--r--Documentation/CodingGuidelines2
-rw-r--r--Documentation/Makefile24
-rw-r--r--Documentation/MyFirstObjectWalk.txt3
-rw-r--r--Documentation/RelNotes/2.17.5.txt22
-rw-r--r--Documentation/RelNotes/2.18.4.txt5
-rw-r--r--Documentation/RelNotes/2.19.5.txt5
-rw-r--r--Documentation/RelNotes/2.20.4.txt5
-rw-r--r--Documentation/RelNotes/2.21.3.txt5
-rw-r--r--Documentation/RelNotes/2.22.4.txt5
-rw-r--r--Documentation/RelNotes/2.23.3.txt5
-rw-r--r--Documentation/RelNotes/2.24.3.txt5
-rw-r--r--Documentation/RelNotes/2.25.4.txt5
-rw-r--r--Documentation/RelNotes/2.26.2.txt5
-rw-r--r--Documentation/RelNotes/2.27.0.txt289
-rw-r--r--Documentation/asciidoc.conf19
-rw-r--r--Documentation/config.txt21
-rw-r--r--Documentation/config/fetch.txt13
-rw-r--r--Documentation/config/format.txt5
-rw-r--r--Documentation/config/log.txt6
-rw-r--r--Documentation/config/merge.txt10
-rw-r--r--Documentation/config/protocol.txt2
-rw-r--r--Documentation/config/push.txt2
-rw-r--r--Documentation/config/submodule.txt12
-rw-r--r--Documentation/config/trace2.txt9
-rw-r--r--Documentation/fetch-options.txt5
-rw-r--r--Documentation/git-am.txt5
-rw-r--r--Documentation/git-bugreport.txt52
-rw-r--r--Documentation/git-checkout.txt4
-rw-r--r--Documentation/git-cherry-pick.txt5
-rw-r--r--Documentation/git-clone.txt13
-rw-r--r--Documentation/git-commit-graph.txt22
-rw-r--r--Documentation/git-commit-tree.txt8
-rw-r--r--Documentation/git-commit.txt9
-rw-r--r--Documentation/git-format-patch.txt8
-rw-r--r--Documentation/git-grep.txt8
-rw-r--r--Documentation/git-log.txt6
-rw-r--r--Documentation/git-ls-files.txt2
-rw-r--r--Documentation/git-merge.txt11
-rw-r--r--Documentation/git-p4.txt45
-rw-r--r--Documentation/git-pull.txt20
-rw-r--r--Documentation/git-read-tree.txt4
-rw-r--r--Documentation/git-rebase.txt101
-rw-r--r--Documentation/git-reset.txt6
-rw-r--r--Documentation/git-restore.txt11
-rw-r--r--Documentation/git-revert.txt5
-rw-r--r--Documentation/git-sparse-checkout.txt10
-rw-r--r--Documentation/git-switch.txt4
-rw-r--r--Documentation/git-update-ref.txt28
-rw-r--r--Documentation/gitfaq.txt337
-rw-r--r--Documentation/githooks.txt51
-rw-r--r--Documentation/gitsubmodules.txt3
-rw-r--r--Documentation/manpage-1.72.xsl14
-rw-r--r--Documentation/manpage-base.xsl35
-rw-r--r--Documentation/manpage-bold-literal.xsl6
-rw-r--r--Documentation/manpage-normal.xsl25
-rw-r--r--Documentation/manpage-suppress-sp.xsl21
-rw-r--r--Documentation/merge-options.txt13
-rw-r--r--Documentation/pretty-formats.txt6
-rw-r--r--Documentation/pull-fetch-param.txt3
-rw-r--r--Documentation/rev-list-options.txt134
-rw-r--r--Documentation/revisions.txt2
-rw-r--r--Documentation/technical/api-trace2.txt3
-rw-r--r--Documentation/technical/commit-graph-format.txt30
-rw-r--r--Documentation/user-manual.conf10
-rw-r--r--INSTALL4
-rw-r--r--Makefile121
-rw-r--r--README.md2
-rw-r--r--add-interactive.c4
-rw-r--r--add-patch.c4
-rw-r--r--archive-tar.c4
-rw-r--r--azure-pipelines.yml558
-rw-r--r--bisect.c8
-rw-r--r--blame.c137
-rw-r--r--blame.h6
-rw-r--r--bloom.c276
-rw-r--r--bloom.h90
-rw-r--r--branch.c1
-rw-r--r--bugreport.c140
-rw-r--r--builtin.h16
-rw-r--r--builtin/blame.c10
-rw-r--r--builtin/cat-file.c2
-rw-r--r--builtin/clean.c20
-rw-r--r--builtin/clone.c13
-rw-r--r--builtin/commit-graph.c60
-rw-r--r--builtin/commit.c6
-rw-r--r--builtin/diff-tree.c9
-rw-r--r--builtin/diff.c2
-rw-r--r--builtin/fetch-pack.c2
-rw-r--r--builtin/fetch.c7
-rw-r--r--builtin/fmt-merge-msg.c658
-rw-r--r--builtin/grep.c48
-rw-r--r--builtin/help.c88
-rw-r--r--builtin/index-pack.c5
-rw-r--r--builtin/log.c24
-rw-r--r--builtin/ls-files.c5
-rw-r--r--builtin/merge.c40
-rw-r--r--builtin/pack-objects.c2
-rw-r--r--builtin/prune-packed.c44
-rw-r--r--builtin/prune.c1
-rw-r--r--builtin/pull.c34
-rw-r--r--builtin/push.c6
-rw-r--r--builtin/rebase.c343
-rw-r--r--builtin/receive-pack.c29
-rw-r--r--builtin/reflog.c2
-rw-r--r--builtin/repack.c1
-rw-r--r--builtin/send-pack.c2
-rw-r--r--builtin/sparse-checkout.c55
-rw-r--r--builtin/stash.c19
-rw-r--r--builtin/tag.c2
-rw-r--r--builtin/update-ref.c245
-rw-r--r--cache.h2
-rw-r--r--ci/git-problem-matcher.json16
-rwxr-xr-xci/install-dependencies.sh16
-rwxr-xr-xci/install-docker-dependencies.sh18
-rwxr-xr-xci/lib.sh39
-rwxr-xr-xci/print-test-failures.sh7
-rwxr-xr-xci/run-build-and-tests.sh1
-rwxr-xr-xci/run-docker-build.sh (renamed from ci/run-linux32-build.sh)39
-rwxr-xr-xci/run-docker.sh47
-rwxr-xr-xci/run-linux32-docker.sh31
-rw-r--r--combine-diff.c2
-rw-r--r--command-list.txt1
-rw-r--r--commit-graph.c379
-rw-r--r--commit-graph.h33
-rw-r--r--commit.h2
-rw-r--r--compat/compiler.h41
-rw-r--r--compat/mingw.c62
-rw-r--r--compat/regex/regex.c1
-rw-r--r--compat/regex/regex_internal.h1
-rw-r--r--compat/vcbuild/README4
-rw-r--r--compat/win32/path-utils.h11
-rw-r--r--config.c42
-rw-r--r--config.h4
-rw-r--r--config.mak.uname20
-rw-r--r--connect.c2
-rw-r--r--connected.c9
-rw-r--r--connected.h9
-rw-r--r--contrib/completion/git-completion.bash2
-rw-r--r--contrib/completion/git-completion.zsh2
-rwxr-xr-xcontrib/fast-import/import-tars.perl2
-rw-r--r--contrib/subtree/Makefile6
-rw-r--r--convert.c2
-rw-r--r--credential.c52
-rw-r--r--delta-islands.c2
-rw-r--r--diff.c157
-rw-r--r--diff.h5
-rw-r--r--diffcore-break.c12
-rw-r--r--diffcore-rename.c64
-rw-r--r--diffcore.h30
-rw-r--r--dir.c422
-rw-r--r--fast-import.c61
-rw-r--r--fetch-pack.c62
-rw-r--r--fmt-merge-msg.c656
-rw-r--r--fmt-merge-msg.h13
-rw-r--r--fsck.c143
-rw-r--r--fuzz-commit-graph.c5
-rwxr-xr-xgenerate-cmdlist.sh19
-rwxr-xr-xgenerate-configlist.sh21
-rw-r--r--git-compat-util.h8
-rwxr-xr-xgit-p4.py236
-rwxr-xr-xgit-submodule.sh2
-rw-r--r--git.c3
-rwxr-xr-xgitweb/gitweb.perl18
-rw-r--r--graph.c2
-rw-r--r--help.c131
-rw-r--r--help.h2
-rw-r--r--http.c2
-rw-r--r--line-log.c6
-rw-r--r--ll-merge.c2
-rw-r--r--log-tree.c58
-rw-r--r--log-tree.h4
-rw-r--r--ls-refs.c5
-rw-r--r--mailinfo.c11
-rw-r--r--midx.c19
-rw-r--r--midx.h2
-rw-r--r--object-store.h2
-rw-r--r--object.h2
-rw-r--r--oid-array.c (renamed from sha1-array.c)10
-rw-r--r--oid-array.h (renamed from sha1-array.h)6
-rw-r--r--oidset.c5
-rw-r--r--oidset.h7
-rw-r--r--parse-options-cb.c2
-rw-r--r--parse-options.c9
-rw-r--r--parse-options.h1
-rw-r--r--path.c1
-rw-r--r--path.h4
-rw-r--r--pretty.c6
-rw-r--r--pretty.h1
-rw-r--r--progress.c2
-rw-r--r--progress.h8
-rw-r--r--promisor-remote.c5
-rw-r--r--promisor-remote.h8
-rw-r--r--prompt.c12
-rw-r--r--prompt.h2
-rw-r--r--protocol.c2
-rw-r--r--prune-packed.c43
-rw-r--r--prune-packed.h9
-rw-r--r--range-diff.c18
-rw-r--r--ref-filter.c7
-rw-r--r--ref-filter.h2
-rw-r--r--refs.c52
-rw-r--r--refs.h12
-rw-r--r--refs/files-backend.c18
-rw-r--r--remote-curl.c2
-rw-r--r--remote.c37
-rw-r--r--repository.h8
-rw-r--r--reset.c141
-rw-r--r--reset.h20
-rw-r--r--revision.c174
-rw-r--r--revision.h20
-rw-r--r--run-command.c10
-rw-r--r--send-pack.c34
-rw-r--r--sequencer.c217
-rw-r--r--sequencer.h24
-rw-r--r--sha1-file.c4
-rw-r--r--sha1-name.c6
-rw-r--r--shallow.c32
-rw-r--r--shell.c4
-rw-r--r--strbuf.c22
-rw-r--r--strbuf.h14
-rw-r--r--submodule-config.c3
-rw-r--r--submodule.c2
-rw-r--r--t/README43
-rw-r--r--t/helper/test-bloom.c93
-rw-r--r--t/helper/test-oid-array.c (renamed from t/helper/test-sha1-array.c)8
-rw-r--r--t/helper/test-parse-pathspec-file.c6
-rw-r--r--t/helper/test-pkt-line.c2
-rw-r--r--t/helper/test-progress.c9
-rw-r--r--t/helper/test-read-graph.c17
-rw-r--r--t/helper/test-tool.c4
-rw-r--r--t/helper/test-tool.h3
-rw-r--r--[-rwxr-xr-x]t/lib-credential.sh3
-rw-r--r--[-rwxr-xr-x]t/lib-gpg.sh110
-rw-r--r--[-rwxr-xr-x]t/lib-log-graph.sh0
-rw-r--r--[-rwxr-xr-x]t/lib-submodule-update.sh0
-rwxr-xr-xt/perf/p5310-pack-bitmaps.sh4
-rwxr-xr-xt/perf/p9300-fast-import-export.sh23
-rwxr-xr-xt/t0000-basic.sh13
-rwxr-xr-xt/t0001-init.sh9
-rwxr-xr-xt/t0007-git-var.sh2
-rwxr-xr-xt/t0040-parse-options.sh2
-rwxr-xr-xt/t0060-path-utils.sh2
-rwxr-xr-xt/t0064-sha1-array.sh16
-rwxr-xr-xt/t0091-bugreport.sh61
-rwxr-xr-xt/t0095-bloom.sh117
-rwxr-xr-xt/t0212-trace2-event.sh37
-rwxr-xr-xt/t0300-credentials.sh232
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh11
-rwxr-xr-xt/t1091-sparse-checkout-builtin.sh105
-rwxr-xr-xt/t1400-update-ref.sh140
-rwxr-xr-xt/t2018-checkout-branch.sh22
-rwxr-xr-xt/t2402-worktree-list.sh2
-rwxr-xr-xt/t3000-ls-files-others.sh121
-rwxr-xr-xt/t3033-merge-toplevel.sh22
-rwxr-xr-xt/t3206-range-diff.sh10
-rwxr-xr-xt/t3402-rebase-merge.sh77
-rwxr-xr-xt/t3406-rebase-message.sh16
-rwxr-xr-xt/t3417-rebase-whitespace-fix.sh4
-rwxr-xr-xt/t3419-rebase-patch-id.sh114
-rwxr-xr-xt/t3420-rebase-autostash.sh20
-rwxr-xr-xt/t3421-rebase-topology-linear.sh10
-rwxr-xr-xt/t3424-rebase-empty.sh36
-rwxr-xr-xt/t3432-rebase-fast-forward.sh24
-rwxr-xr-xt/t3435-rebase-gpg-sign.sh71
-rwxr-xr-xt/t3514-cherry-pick-revert-gpg.sh86
-rwxr-xr-xt/t3700-add.sh2
-rwxr-xr-xt/t3701-add-interactive.sh2
-rwxr-xr-xt/t3904-stash-patch.sh6
-rwxr-xr-xt/t4013-diff-various.sh12
-rw-r--r--t/t4013/diff.diff-tree_--format=%N_note6
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--notes_note12
-rw-r--r--t/t4013/diff.diff-tree_--pretty_note9
-rw-r--r--t/t4013/diff.log_--decorate=full_--all15
-rw-r--r--t/t4013/diff.log_--decorate_--all15
-rwxr-xr-xt/t4014-format-patch.sh53
-rwxr-xr-xt/t4057-diff-combined-paths.sh2
-rwxr-xr-xt/t4061-diff-indent.sh2
-rwxr-xr-xt/t4067-diff-partial-clone.sh48
-rwxr-xr-xt/t4124-apply-ws-rule.sh7
-rwxr-xr-xt/t4150-am.sh2
-rwxr-xr-xt/t4202-log.sh51
-rwxr-xr-xt/t4208-log-magic-pathspec.sh4
-rwxr-xr-xt/t4216-log-bloom.sh155
-rwxr-xr-xt/t4254-am-corrupt.sh53
-rwxr-xr-xt/t5003-archive-zip.sh24
-rwxr-xr-xt/t5318-commit-graph.sh4
-rwxr-xr-xt/t5319-multi-pack-index.sh21
-rw-r--r--t/t5319/no-objects.midxbin0 -> 1116 bytes
-rwxr-xr-xt/t5324-split-commit-graph.sh52
-rwxr-xr-xt/t5500-fetch-pack.sh48
-rwxr-xr-xt/t5504-fetch-receive-strict.sh1
-rwxr-xr-xt/t5512-ls-remote.sh80
-rwxr-xr-xt/t5516-fetch-push.sh1
-rwxr-xr-xt/t5520-pull.sh57
-rwxr-xr-xt/t5537-fetch-shallow.sh101
-rwxr-xr-xt/t5541-http-push-smart.sh12
-rwxr-xr-xt/t5543-atomic-push.sh89
-rwxr-xr-xt/t5548-push-porcelain.sh279
-rwxr-xr-xt/t5550-http-fetch-dumb.sh20
-rwxr-xr-xt/t5562-http-backend-content-length.sh19
-rwxr-xr-xt/t5604-clone-reference.sh2
-rwxr-xr-xt/t5607-clone-bundle.sh2
-rwxr-xr-xt/t5611-clone-config.sh13
-rwxr-xr-xt/t5612-clone-refspec.sh26
-rwxr-xr-xt/t5616-partial-clone.sh10
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh5
-rwxr-xr-xt/t5704-protocol-violations.sh33
-rwxr-xr-xt/t5801-remote-helpers.sh10
-rwxr-xr-xt/t6012-rev-list-simplify.sh120
-rwxr-xr-xt/t6030-bisect-porcelain.sh8
-rwxr-xr-xt/t7063-status-untracked-cache.sh54
-rwxr-xr-xt/t7400-submodule-basic.sh4
-rwxr-xr-xt/t7408-submodule-reference.sh8
-rwxr-xr-xt/t7416-submodule-dash-url.sh125
-rwxr-xr-xt/t7508-status.sh2
-rwxr-xr-xt/t7600-merge.sh154
-rwxr-xr-xt/t7609-merge-co-error-msgs.sh2
-rwxr-xr-xt/t7810-grep.sh47
-rwxr-xr-xt/t9010-svn-fe.sh2
-rwxr-xr-xt/t9141-git-svn-multiple-branches.sh8
-rwxr-xr-xt/t9160-git-svn-preserve-empty-dirs.sh4
-rwxr-xr-xt/t9164-git-svn-dcommit-concurrent.sh4
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh70
-rwxr-xr-xt/t9819-git-p4-case-folding.sh2
-rwxr-xr-xt/t9831-git-p4-triggers.sh2
-rwxr-xr-xt/t9902-completion.sh5
-rw-r--r--t/test-lib-functions.sh34
-rw-r--r--t/test-lib.sh91
-rw-r--r--trace2.c9
-rw-r--r--trace2.h13
-rw-r--r--trace2/tr2_cfg.c58
-rw-r--r--trace2/tr2_cfg.h8
-rw-r--r--trace2/tr2_sysenv.c2
-rw-r--r--trace2/tr2_sysenv.h1
-rw-r--r--transport-helper.c23
-rw-r--r--transport.c26
-rw-r--r--transport.h3
-rw-r--r--tree-diff.c6
-rw-r--r--unpack-trees.c260
-rw-r--r--unpack-trees.h28
-rw-r--r--upload-pack.c5
-rw-r--r--userdiff.c4
-rw-r--r--wrapper.c4
-rw-r--r--wt-status.c6
347 files changed, 9769 insertions, 3591 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000000..fd4df939b5
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,230 @@
+name: CI/PR
+
+on: [push, pull_request]
+
+env:
+ DEVELOPER: 1
+
+jobs:
+ windows-build:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: download git-sdk-64-minimal
+ shell: bash
+ run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+ - name: build
+ shell: powershell
+ env:
+ HOME: ${{runner.workspace}}
+ MSYSTEM: MINGW64
+ NO_PERL: 1
+ run: |
+ & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+ printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
+
+ ci/make-test-artifacts.sh artifacts
+ "@
+ - name: upload build artifacts
+ uses: actions/upload-artifact@v1
+ with:
+ name: windows-artifacts
+ path: artifacts
+ windows-test:
+ runs-on: windows-latest
+ needs: [windows-build]
+ strategy:
+ matrix:
+ nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ steps:
+ - uses: actions/checkout@v1
+ - name: download git-sdk-64-minimal
+ shell: bash
+ run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+ - name: download build artifacts
+ uses: actions/download-artifact@v1
+ with:
+ name: windows-artifacts
+ path: ${{github.workspace}}
+ - name: extract build artifacts
+ shell: bash
+ run: tar xf artifacts.tar.gz
+ - name: test
+ shell: powershell
+ run: |
+ & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+ # Let Git ignore the SDK
+ printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
+
+ ci/run-test-slice.sh ${{matrix.nr}} 10
+ "@
+ - name: ci/print-test-failures.sh
+ if: failure()
+ shell: powershell
+ run: |
+ & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh
+ - name: Upload failed tests' directories
+ if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+ uses: actions/upload-artifact@v1
+ with:
+ name: failed-tests-windows
+ path: ${{env.FAILED_TEST_ARTIFACTS}}
+ vs-build:
+ env:
+ MSYSTEM: MINGW64
+ NO_PERL: 1
+ GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: download git-sdk-64-minimal
+ shell: bash
+ run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+ - name: generate Visual Studio solution
+ shell: powershell
+ run: |
+ & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+ make NDEBUG=1 DEVELOPER=1 vcxproj
+ "@
+ if (!$?) { exit(1) }
+ - name: download vcpkg artifacts
+ shell: powershell
+ run: |
+ $urlbase = "https://dev.azure.com/git/git/_apis/build/builds"
+ $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
+ $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl
+ (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip")
+ Expand-Archive compat.zip -DestinationPath . -Force
+ Remove-Item compat.zip
+ - name: add msbuild to PATH
+ uses: microsoft/setup-msbuild@v1.0.0
+ - name: MSBuild
+ run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142
+ - name: bundle artifact tar
+ shell: powershell
+ env:
+ MSVC: 1
+ VCPKG_ROOT: ${{github.workspace}}\compat\vcbuild\vcpkg
+ run: |
+ & compat\vcbuild\vcpkg_copy_dlls.bat release
+ if (!$?) { exit(1) }
+ & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+ mkdir -p artifacts &&
+ eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts 2>&1 | grep ^tar)\"
+ "@
+ - name: upload build artifacts
+ uses: actions/upload-artifact@v1
+ with:
+ name: vs-artifacts
+ path: artifacts
+ vs-test:
+ runs-on: windows-latest
+ needs: [vs-build]
+ strategy:
+ matrix:
+ nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ steps:
+ - uses: actions/checkout@v1
+ - name: download git-64-portable
+ shell: bash
+ run: a=git-64-portable && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+ - name: download build artifacts
+ uses: actions/download-artifact@v1
+ with:
+ name: vs-artifacts
+ path: ${{github.workspace}}
+ - name: extract build artifacts
+ shell: bash
+ run: tar xf artifacts.tar.gz
+ - name: test (parallel)
+ shell: powershell
+ env:
+ MSYSTEM: MINGW64
+ NO_SVN_TESTS: 1
+ GIT_TEST_SKIP_REBASE_P: 1
+ run: |
+ & git-64-portable\git-cmd.exe --command=usr\bin\bash.exe -lc @"
+ # Let Git ignore the SDK and the test-cache
+ printf '%s\n' /git-64-portable/ /test-cache/ >>.git/info/exclude
+
+ cd t &&
+ PATH=\"`$PWD/helper:`$PATH\" &&
+ test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \
+ `$(test-tool.exe path-utils slice-tests \
+ ${{matrix.nr}} 10 t[0-9]*.sh)
+ "@
+ regular:
+ strategy:
+ matrix:
+ vector:
+ - jobname: linux-clang
+ cc: clang
+ pool: ubuntu-latest
+ - jobname: linux-gcc
+ cc: gcc
+ pool: ubuntu-latest
+ - jobname: osx-clang
+ cc: clang
+ pool: macos-latest
+ - jobname: osx-gcc
+ cc: gcc
+ pool: macos-latest
+ - jobname: GETTEXT_POISON
+ cc: gcc
+ pool: ubuntu-latest
+ env:
+ CC: ${{matrix.vector.cc}}
+ jobname: ${{matrix.vector.jobname}}
+ runs-on: ${{matrix.vector.pool}}
+ steps:
+ - uses: actions/checkout@v1
+ - run: ci/install-dependencies.sh
+ - run: ci/run-build-and-tests.sh
+ - run: ci/print-test-failures.sh
+ if: failure()
+ - name: Upload failed tests' directories
+ if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+ uses: actions/upload-artifact@v1
+ with:
+ name: failed-tests-${{matrix.vector.jobname}}
+ path: ${{env.FAILED_TEST_ARTIFACTS}}
+ dockerized:
+ strategy:
+ matrix:
+ vector:
+ - jobname: linux-musl
+ image: alpine
+ - jobname: Linux32
+ image: daald/ubuntu32:xenial
+ env:
+ jobname: ${{matrix.vector.jobname}}
+ runs-on: ubuntu-latest
+ container: ${{matrix.vector.image}}
+ steps:
+ - uses: actions/checkout@v1
+ - run: ci/install-docker-dependencies.sh
+ - run: ci/run-build-and-tests.sh
+ - run: ci/print-test-failures.sh
+ if: failure()
+ - name: Upload failed tests' directories
+ if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+ uses: actions/upload-artifact@v1
+ with:
+ name: failed-tests-${{matrix.vector.jobname}}
+ path: ${{env.FAILED_TEST_ARTIFACTS}}
+ static-analysis:
+ env:
+ jobname: StaticAnalysis
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - run: ci/install-dependencies.sh
+ - run: ci/run-static-analysis.sh
+ documentation:
+ env:
+ jobname: Documentation
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - run: ci/install-dependencies.sh
+ - run: ci/test-documentation.sh
diff --git a/.gitignore b/.gitignore
index 188bd1c3de..ee509a2ad2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@
/git-bisect--helper
/git-blame
/git-branch
+/git-bugreport
/git-bundle
/git-cat-file
/git-check-attr
@@ -188,6 +189,7 @@
/gitweb/gitweb.cgi
/gitweb/static/gitweb.js
/gitweb/static/gitweb.min.*
+/config-list.h
/command-list.h
*.tar.gz
*.dsc
diff --git a/.travis.yml b/.travis.yml
index fc5730b085..05f3e3f8d7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@ compiler:
matrix:
include:
- - env: jobname=GIT_TEST_GETTEXT_POISON
+ - env: jobname=GETTEXT_POISON
os: linux
compiler:
addons:
@@ -32,7 +32,15 @@ matrix:
services:
- docker
before_install:
- script: ci/run-linux32-docker.sh
+ script: ci/run-docker.sh
+ - env: jobname=linux-musl
+ os: linux
+ compiler:
+ addons:
+ services:
+ - docker
+ before_install:
+ script: ci/run-docker.sh
- env: jobname=StaticAnalysis
os: linux
compiler:
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index ed4e443a3c..390ceece52 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -91,8 +91,6 @@ For shell scripts specifically (not exhaustive):
- No shell arrays.
- - No strlen ${#parameter}.
-
- No pattern replacement ${parameter/pattern/string}.
- We use Arithmetic Expansion $(( ... )).
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 8fe829cc1b..15d9d04f31 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -30,6 +30,7 @@ MAN7_TXT += gitcredentials.txt
MAN7_TXT += gitcvs-migration.txt
MAN7_TXT += gitdiffcore.txt
MAN7_TXT += giteveryday.txt
+MAN7_TXT += gitfaq.txt
MAN7_TXT += gitglossary.txt
MAN7_TXT += gitnamespaces.txt
MAN7_TXT += gitremote-helpers.txt
@@ -149,32 +150,9 @@ endif
-include ../config.mak.autogen
-include ../config.mak
-#
-# For docbook-xsl ...
-# -1.68.1, no extra settings are needed?
-# 1.69.0, set ASCIIDOC_ROFF?
-# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP?
-# 1.71.1, set ASCIIDOC_ROFF?
-# 1.72.0, set DOCBOOK_XSL_172.
-# 1.73.0-, no extra settings are needed
-#
-
-ifdef DOCBOOK_XSL_172
-ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
-MANPAGE_XSL = manpage-1.72.xsl
-else
- ifndef ASCIIDOC_ROFF
- # docbook-xsl after 1.72 needs the regular XSL, but will not
- # pass-thru raw roff codes from asciidoc.conf, so turn them off.
- ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
- endif
-endif
ifndef NO_MAN_BOLD_LITERAL
XMLTO_EXTRA += -m manpage-bold-literal.xsl
endif
-ifdef DOCBOOK_SUPPRESS_SP
-XMLTO_EXTRA += -m manpage-suppress-sp.xsl
-endif
# Newer DocBook stylesheet emits warning cruft in the output when
# this is not set, and if set it shows an absolute link. Older
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt
index aa828dfdc4..c3f2d1a831 100644
--- a/Documentation/MyFirstObjectWalk.txt
+++ b/Documentation/MyFirstObjectWalk.txt
@@ -357,9 +357,6 @@ static void walken_commit_walk(struct rev_info *rev)
...
while ((commit = get_revision(rev))) {
- if (!commit)
- continue;
-
strbuf_reset(&prettybuf);
pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf);
puts(prettybuf.buf);
diff --git a/Documentation/RelNotes/2.17.5.txt b/Documentation/RelNotes/2.17.5.txt
new file mode 100644
index 0000000000..2abb821a73
--- /dev/null
+++ b/Documentation/RelNotes/2.17.5.txt
@@ -0,0 +1,22 @@
+Git v2.17.5 Release Notes
+=========================
+
+This release is to address a security issue: CVE-2020-11008
+
+Fixes since v2.17.4
+-------------------
+
+ * With a crafted URL that contains a newline or empty host, or lacks
+ a scheme, the credential helper machinery can be fooled into
+ providing credential information that is not appropriate for the
+ protocol in use and host being contacted.
+
+ Unlike the vulnerability CVE-2020-5260 fixed in v2.17.4, the
+ credentials are not for a host of the attacker's choosing; instead,
+ they are for some unspecified host (based on how the configured
+ credential helper handles an absent "host" parameter).
+
+ The attack has been made impossible by refusing to work with
+ under-specified credential patterns.
+
+Credit for finding the vulnerability goes to Carlo Arenas.
diff --git a/Documentation/RelNotes/2.18.4.txt b/Documentation/RelNotes/2.18.4.txt
new file mode 100644
index 0000000000..e8ef858a00
--- /dev/null
+++ b/Documentation/RelNotes/2.18.4.txt
@@ -0,0 +1,5 @@
+Git v2.18.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.19.5.txt b/Documentation/RelNotes/2.19.5.txt
new file mode 100644
index 0000000000..18a4dcbfd6
--- /dev/null
+++ b/Documentation/RelNotes/2.19.5.txt
@@ -0,0 +1,5 @@
+Git v2.19.5 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.20.4.txt b/Documentation/RelNotes/2.20.4.txt
new file mode 100644
index 0000000000..5a9e24e470
--- /dev/null
+++ b/Documentation/RelNotes/2.20.4.txt
@@ -0,0 +1,5 @@
+Git v2.20.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.21.3.txt b/Documentation/RelNotes/2.21.3.txt
new file mode 100644
index 0000000000..2ca0aa5c62
--- /dev/null
+++ b/Documentation/RelNotes/2.21.3.txt
@@ -0,0 +1,5 @@
+Git v2.21.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.22.4.txt b/Documentation/RelNotes/2.22.4.txt
new file mode 100644
index 0000000000..8b5f3e3f37
--- /dev/null
+++ b/Documentation/RelNotes/2.22.4.txt
@@ -0,0 +1,5 @@
+Git v2.22.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.23.3.txt b/Documentation/RelNotes/2.23.3.txt
new file mode 100644
index 0000000000..2e35490137
--- /dev/null
+++ b/Documentation/RelNotes/2.23.3.txt
@@ -0,0 +1,5 @@
+Git v2.23.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.24.3.txt b/Documentation/RelNotes/2.24.3.txt
new file mode 100644
index 0000000000..5302e0f73b
--- /dev/null
+++ b/Documentation/RelNotes/2.24.3.txt
@@ -0,0 +1,5 @@
+Git v2.24.3 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.25.4.txt b/Documentation/RelNotes/2.25.4.txt
new file mode 100644
index 0000000000..0dbb5daeec
--- /dev/null
+++ b/Documentation/RelNotes/2.25.4.txt
@@ -0,0 +1,5 @@
+Git v2.25.4 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.26.2.txt b/Documentation/RelNotes/2.26.2.txt
new file mode 100644
index 0000000000..d434d0c695
--- /dev/null
+++ b/Documentation/RelNotes/2.26.2.txt
@@ -0,0 +1,5 @@
+Git v2.26.2 Release Notes
+=========================
+
+This release merges the security fix that appears in v2.17.5; see
+the release notes for that version for details.
diff --git a/Documentation/RelNotes/2.27.0.txt b/Documentation/RelNotes/2.27.0.txt
index 7a5c7fff80..3f5c0bd875 100644
--- a/Documentation/RelNotes/2.27.0.txt
+++ b/Documentation/RelNotes/2.27.0.txt
@@ -47,6 +47,55 @@ UI, Workflows & Features
exists, and neither --[no-]rebase nor --ff-only is given (which
would result a merge).
+ * "git p4" learned four new hooks and also "--no-verify" option to
+ bypass them (and the existing "p4-pre-submit" hook).
+
+ * "git pull" shares many options with underlying "git fetch", but
+ some of them were not documented and some of those that would make
+ sense to pass down were not passed down.
+
+ * "git rebase" learned the "--no-gpg-sign" option to countermand
+ commit.gpgSign the user may have.
+
+ * The output from "git format-patch" uses RFC 2047 encoding for
+ non-ASCII letters on From: and Subject: headers, so that it can
+ directly be fed to e-mail programs. A new option has been added
+ to produce these headers in raw.
+
+ * "git log" learned "--show-pulls" that helps pathspec limited
+ history views; a merge commit that takes the whole change from a
+ side branch, which is normally omitted from the output, is shown
+ in addition to the commits that introduce real changes.
+
+ * The interactive input from various codepaths are consolidated and
+ any prompt possibly issued earlier are fflush()ed before we read.
+
+ * Allow "git rebase" to reapply all local commits, even if the may be
+ already in the upstream, without checking first.
+
+ * The 'pack.useSparse' configuration variable now defaults to 'true',
+ enabling an optimization that has been experimental since Git 2.21.
+
+ * "git rebase" happens to call some hooks meant for "checkout" and
+ "commit" by this was not a designed behaviour than historical
+ accident. This has been documented.
+
+ * "git merge" learns the "--autostash" option.
+
+ * "sparse-checkout" UI improvements.
+
+ * "git update-ref --stdin" learned a handful of new verbs to let the
+ user control ref update transactions more explicitly, which helps
+ as an ingredient to implement two-phase commit-style atomic
+ ref-updates across multiple repositories.
+
+ * "git commit-graph write" learned different ways to write out split
+ files.
+
+ * Introduce an extension to the commit-graph to make it efficient to
+ check for the paths that were modified at each commit using Bloom
+ filters.
+
Performance, Internal Implementation, Development Support etc.
@@ -60,6 +109,46 @@ Performance, Internal Implementation, Development Support etc.
* "git stash" has kept an escape hatch to use the scripted version
for a few releases, which got stale. It has been removed.
+ * Enable tests that require GnuPG on Windows.
+
+ * Minor test usability improvement.
+
+ * Trace2 enhancement to allow logging of the environment variables.
+
+ * Test clean-up continues.
+
+ * Perf-test update.
+
+ * A Windows-specific test element has been made more robust against
+ misuse from both user's environment and programmer's errors.
+
+ * Various tests have been updated to work around issues found with
+ shell utilities that come with busybox etc.
+
+ * The config API made mixed uses of int and size_t types to represent
+ length of various pieces of text it parsed, which has been updated
+ to use the correct type (i.e. size_t) throughout.
+
+ * The "--decorate-refs" and "--decorate-refs-exclude" options "git
+ log" takes have learned a companion configuration variable
+ log.excludeDecoration that sits at the lowest priority in the
+ family.
+
+ * A new CI job to build and run test suite on linux with musl libc
+ has been added.
+
+ * Update the CI configuration to use GitHub Actions, retiring the one
+ based on Azure Pipelines.
+
+ * The directory traversal code had redundant recursive calls which
+ made its performance characteristics exponential with respect to
+ the depth of the tree, which was corrected.
+
+ * "git blame" learns to take advantage of the "changed-paths" Bloom
+ filter stored in the commit-graph file.
+
+ * The "bugreport" tool has been added.
+
Fixes since v2.26
-----------------
@@ -85,6 +174,187 @@ Fixes since v2.26
was rewritten in C back in 2.20 era, which has been corrected.
(merge f08132f889 at/rebase-fork-point-regression-fix later to maint).
+ * The import-tars importer (in contrib/fast-import/) used to create
+ phony files at the top-level of the repository when the archive
+ contains global PAX headers, which made its own logic to detect and
+ omit the common leading directory ineffective, which has been
+ corrected.
+ (merge c839fcff65 js/import-tars-do-not-make-phony-files-from-pax-headers later to maint).
+
+ * Simplify the commit ancestry connectedness check in a partial clone
+ repository in which "promised" objects are assumed to be obtainable
+ lazily on-demand from promisor remote repositories.
+ (merge 2b98478c6f jt/connectivity-check-optim-in-partial-clone later to maint).
+
+ * The server-end of the v2 protocol to serve "git clone" and "git
+ fetch" was not prepared to see a delim packets at unexpected
+ places, which led to a crash.
+ (merge cacae4329f jk/harden-protocol-v2-delim-handling later to maint).
+
+ * When fed a midx that records no objects, some codepaths tried to
+ loop from 0 through (num_objects-1), which, due to integer
+ arithmetic wrapping around, made it nonsense operation with out of
+ bounds array accesses. The code has been corrected to reject such
+ an midx file.
+ (merge 796d61cdc0 dr/midx-avoid-int-underflow later to maint).
+
+ * Utitiles run via the run_command() API were not spawned correctly
+ on Cygwin, when the paths to them are given as a full path with
+ backslashes.
+ (merge 05ac8582bc ak/run-command-on-cygwin-fix later to maint).
+
+ * "git pull --rebase" tried to run a rebase even after noticing that
+ the pull results in a fast-forward and no rebase is needed nor
+ sensible, for the past few years due to a mistake nobody noticed.
+ (merge fbae70ddc6 en/pull-do-not-rebase-after-fast-forwarding later to maint).
+
+ * "git rebase" with the merge backend did not work well when the
+ rebase.abbreviateCommands configuration was set.
+ (merge de9f1d3ef4 ag/rebase-merge-allow-ff-under-abbrev-command later to maint).
+
+ * The logic to auto-follow tags by "git clone --single-branch" was
+ not careful to avoid lazy-fetching unnecessary tags, which has been
+ corrected.
+ (merge 167a575e2d jk/use-quick-lookup-in-clone-for-tag-following later to maint).
+
+ * "git rebase -i" did not leave the reflog entries correctly.
+ (merge 1f6965f994 en/sequencer-reflog-action later to maint).
+
+ * The more aggressive updates to remote-tracking branches we had for
+ the past 7 years or so were not reflected in the documentation,
+ which has been corrected.
+ (merge a44088435c pb/pull-fetch-doc later to maint).
+
+ * We've left the command line parsing of "git log :/a/b/" broken for
+ about a full year without anybody noticing, which has been
+ corrected.
+ (merge 0220461071 jc/missing-ref-store-fix later to maint).
+
+ * Misc fixes for Windows.
+ (merge 3efc128cd5 js/mingw-fixes later to maint).
+
+ * "git rebase" (again) learns to honor "--no-keep-empty", which lets
+ the user to discard commits that are empty from the beginning (as
+ opposed to the ones that become empty because of rebasing). The
+ interactive rebase also marks commits that are empty in the todo.
+ (merge 50ed76148a en/rebase-no-keep-empty later to maint).
+
+ * Parsing the host part out of URL for the credential helper has been corrected.
+ (merge 4c5971e18a jk/credential-parsing-end-of-host-in-URL later to maint).
+
+ * Document the recommended way to abort a failing test early (e.g. by
+ exiting a loop), which is to say "return 1".
+ (merge 7cc112dc95 jc/doc-test-leaving-early later to maint).
+
+ * The code that refreshes the last access and modified time of
+ on-disk packfiles and loose object files have been updated.
+ (merge 312cd76130 lr/freshen-file-fix later to maint).
+
+ * Validation of push certificate has been made more robust against
+ timing attacks.
+ (merge 719483e547 bc/constant-memequal later to maint).
+
+ * The custom hash function used by "git fast-import" has been
+ replaced with the one from hashmap.c, which gave us a nice
+ performance boost.
+ (merge d8410a816b jk/fast-import-use-hashmap later to maint).
+
+ * The "git submodule" command did not initialize a few variables it
+ internally uses and was affected by variable settings leaked from
+ the environment.
+ (merge 65d100c4dd lx/submodule-clear-variables later to maint).
+
+ * Raise the minimum required version of docbook-xsl package to 1.74,
+ as 1.74.0 was from late 2008, which is more than 10 years old, and
+ drop compatibility cruft from our documentation suite.
+ (merge 3c255ad660 ma/doc-discard-docbook-xsl-1.73 later to maint).
+
+ * "git log" learns "--[no-]mailmap" as a synonym to "--[no-]use-mailmap"
+ (merge 88acccda38 jc/log-no-mailmap later to maint).
+
+ * "git commit-graph write --expire-time=<timestamp>" did not use the
+ given timestamp correctly, which has been corrected.
+ (merge b09b785c78 ds/commit-graph-expiry-fix later to maint).
+
+ * Tests update to use "test-chmtime" instead of "touch -t".
+ (merge e892a56845 ds/t5319-touch-fix later to maint).
+
+ * "git diff" in a partial clone learned to avoid lazy loading blob
+ objects in more casese when they are not needed.
+ (merge 95acf11a3d jt/avoid-prefetch-when-able-in-diff later to maint).
+
+ * "git push --atomic" used to show failures for refs that weren't
+ even pushed, which has been corrected.
+ (merge dfe1b7f19c jx/atomic-push later to maint).
+
+ * Code in builtin/*, i.e. those can only be called from within
+ built-in subcommands, that implements bulk of a couple of
+ subcommands have been moved to libgit.a so that they could be used
+ by others.
+ (merge 9460fd48b5 dl/libify-a-few later to maint).
+
+ * Allowing the user to split a patch hunk while "git stash -p" does
+ not work well; a band-aid has been added to make this (partially)
+ work better.
+
+ * "git diff-tree --pretty --notes" used to hit an assertion failure,
+ as it forgot to initialize the notes subsystem.
+ (merge 5778b22b3d tb/diff-tree-with-notes later to maint).
+
+ * "git range-diff" fixes.
+ (merge 8d1675eb7f vd/range-diff-with-custom-pretty-format-fix later to maint).
+
+ * "git grep" did not quote a path with unusual character like other
+ commands (like "git diff", "git status") do, but did quote when run
+ from a subdirectory, both of which has been corrected.
+ (merge 45115d8490 mt/grep-cquote-path later to maint).
+
+ * GNU/Hurd is also among the ones that need the fopen() wrapper.
+ (merge 274a1328fb jc/gnu-hurd-lets-fread-read-dirs later to maint).
+
+ * Those fetching over protocol v2 from linux-next and other kernel
+ repositories are reporting that v2 often fetches way too much than
+ needed.
+ (merge 11c7f2a30b jn/demote-proto2-from-default later to maint).
+
+ * The upload-pack protocol v2 gave up too early before finding a
+ common ancestor, resulting in a wasteful fetch from a fork of a
+ project. This has been corrected to match the behaviour of v0
+ protocol.
+ (merge 2f0a093dd6 jt/v2-fetch-nego-fix later to maint).
+
+ * The build procedure did not use the libcurl library and its include
+ files correctly for a custom-built installation.
+ (merge 0573831950 jk/build-with-right-curl later to maint).
+
+ * Tighten "git mailinfo" to notice and error out when decoded result
+ contains NUL in it.
+ (merge 3919997447 dd/mailinfo-with-nul later to maint).
+
+ * Fix in-core inconsistency after fetching into a shallow repository
+ that broke the code to write out commit-graph.
+ (merge 37b9dcabfc tb/reset-shallow later to maint).
+
+ * The commit-graph code exhausted file descriptors easily when it
+ does not have to.
+ (merge c8828530b7 tb/commit-graph-fd-exhaustion-fix later to maint).
+
+ * The multi-pack-index left mmapped file descriptors open when it
+ does not have to.
+ (merge 6c7ff7cf7f ds/multi-pack-index later to maint).
+
+ * Recent update to Homebrew used by macOS folks breaks build by
+ moving gettext library and necessary headers.
+ (merge a0b3108618 ds/build-homebrew-gettext-fix later to maint).
+
+ * Incompatible options "--root" and "--fork-point" of "git rebase"
+ have been marked and documented as being incompatible.
+ (merge a35413c378 en/rebase-root-and-fork-point-are-incompatible later to maint).
+
+ * Error and verbose trace messages from "git push" did not redact
+ credential material embedded in URLs.
+ (merge d192fa5006 js/anonymise-push-url-in-errors later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 564956f358 jc/maintain-doc later to maint).
(merge 7422b2a0a1 sg/commit-slab-clarify-peek later to maint).
@@ -92,3 +362,22 @@ Fixes since v2.26
(merge 757c2ba3e2 en/oidset-uninclude-hashmap later to maint).
(merge 8312aa7d74 jc/config-tar later to maint).
(merge d00a5bdd50 ss/submodule-foreach-cb later to maint).
+ (merge 64d1022e14 ar/test-style-fixes later to maint).
+ (merge 4a465443a6 ds/doc-clone-filter later to maint).
+ (merge bb2dbe301b jk/t3419-drop-expensive-tests later to maint).
+ (merge d3507cc712 js/test-junit-finalization-fix later to maint).
+ (merge 2149b6748f bc/faq later to maint).
+ (merge 12dc0879f1 jk/test-cleanup later to maint).
+ (merge 344420bf0f pb/rebase-doc-typofix later to maint).
+ (merge 7cd54d37dc dl/wrapper-fix-indentation later to maint).
+ (merge 78725ebda9 jc/allow-strlen-substitution-in-shell-scripts later to maint).
+ (merge 2ecfcdecc6 jm/gitweb-fastcgi-utf8 later to maint).
+ (merge 0740d0a5d3 jk/oid-array-cleanups later to maint).
+ (merge a1aba0c95c js/t0007-typofix later to maint).
+ (merge 76ba7fa225 ma/config-doc-fix later to maint).
+ (merge 826f0c0df2 js/subtree-doc-update-to-asciidoctor-2 later to maint).
+ (merge 88eaf361e0 eb/mboxrd-doc later to maint).
+ (merge 051cc54941 tm/zsh-complete-switch-restore later to maint).
+ (merge 39102cf4fe ms/doc-revision-illustration-fix later to maint).
+ (merge 4d9378bfad eb/gitweb-more-trailers later to maint).
+ (merge bdccbf7047 mt/doc-worktree-ref later to maint).
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index 8fc4b67081..3e4c13971b 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -31,24 +31,6 @@ ifdef::backend-docbook[]
endif::backend-docbook[]
ifdef::backend-docbook[]
-ifndef::git-asciidoc-no-roff[]
-# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
-# v1.72 breaks with this because it replaces dots not in roff requests.
-[listingblock]
-<example><title>{title}</title>
-<literallayout class="monospaced">
-ifdef::doctype-manpage[]
-&#10;.ft C&#10;
-endif::doctype-manpage[]
-|
-ifdef::doctype-manpage[]
-&#10;.ft&#10;
-endif::doctype-manpage[]
-</literallayout>
-{title#}</example>
-endif::git-asciidoc-no-roff[]
-
-ifdef::git-asciidoc-no-roff[]
ifdef::doctype-manpage[]
# The following two small workarounds insert a simple paragraph after screen
[listingblock]
@@ -67,7 +49,6 @@ ifdef::doctype-manpage[]
{title#}</para></formalpara>
{title%}<simpara></simpara>
endif::doctype-manpage[]
-endif::git-asciidoc-no-roff[]
endif::backend-docbook[]
ifdef::doctype-manpage[]
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 2450589a0e..ef0768b91a 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -3,11 +3,12 @@ CONFIGURATION FILE
The Git configuration file contains a number of variables that affect
the Git commands' behavior. The files `.git/config` and optionally
-`config.worktree` (see `extensions.worktreeConfig` below) in each
-repository are used to store the configuration for that repository, and
-`$HOME/.gitconfig` is used to store a per-user configuration as
-fallback values for the `.git/config` file. The file `/etc/gitconfig`
-can be used to store a system-wide default configuration.
+`config.worktree` (see the "CONFIGURATION FILE" section of
+linkgit:git-worktree[1]) in each repository are used to store the
+configuration for that repository, and `$HOME/.gitconfig` is used to
+store a per-user configuration as fallback values for the `.git/config`
+file. The file `/etc/gitconfig` can be used to store a system-wide
+default configuration.
The configuration variables are used by both the Git plumbing
and the porcelains. The variables are divided into sections, wherein
@@ -220,12 +221,12 @@ Example
; affected by the condition
[includeIf "gitdir:/path/to/group/"]
path = foo.inc
-----
- ; include only if we are in a worktree where foo-branch is
- ; currently checked out
- [includeIf "onbranch:foo-branch"]
- path = foo.inc
+; include only if we are in a worktree where foo-branch is
+; currently checked out
+[includeIf "onbranch:foo-branch"]
+ path = foo.inc
+----
Values
~~~~~~
diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index f11940280f..b1a9b1461d 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -1,11 +1,14 @@
fetch.recurseSubmodules::
- This option can be either set to a boolean value or to 'on-demand'.
+ This option controls whether `git fetch` (and the underlying fetch
+ in `git pull`) will recursively fetch into populated submodules.
+ This option can be set either to a boolean value or to 'on-demand'.
Setting it to a boolean changes the behavior of fetch and pull to
- unconditionally recurse into submodules when set to true or to not
- recurse at all when set to false. When set to 'on-demand' (the default
- value), fetch and pull will only recurse into a populated submodule
- when its superproject retrieves a commit that updates the submodule's
+ recurse unconditionally into submodules when set to true or to not
+ recurse at all when set to false. When set to 'on-demand', fetch and
+ pull will only recurse into a populated submodule when its
+ superproject retrieves a commit that updates the submodule's
reference.
+ Defaults to 'on-demand', or to the value of 'submodule.recurse' if set.
fetch.fsckObjects::
If it is set to true, git-fetch-pack will check all fetched
diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index 45c7bd5a8f..564e8091ba 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -57,6 +57,11 @@ format.suffix::
`.patch`. Use this variable to change that suffix (make sure to
include the dot if you want it).
+format.encodeEmailHeaders::
+ Encode email headers that have non-ASCII characters with
+ "Q-encoding" (described in RFC 2047) for email transmission.
+ Defaults to true.
+
format.pretty::
The default pretty format for log/show/whatchanged command,
See linkgit:git-log[1], linkgit:git-show[1],
diff --git a/Documentation/config/log.txt b/Documentation/config/log.txt
index e9e1e397f3..208d5fdcaa 100644
--- a/Documentation/config/log.txt
+++ b/Documentation/config/log.txt
@@ -18,6 +18,12 @@ log.decorate::
names are shown. This is the same as the `--decorate` option
of the `git log`.
+log.excludeDecoration::
+ Exclude the specified patterns from the log decorations. This is
+ similar to the `--decorate-refs-exclude` command-line option, but
+ the config option can be overridden by the `--decorate-refs`
+ option.
+
log.follow::
If `true`, `git log` will act as if the `--follow` option was used when
a single <path> is given. This has the same limitations as `--follow`,
diff --git a/Documentation/config/merge.txt b/Documentation/config/merge.txt
index 6a313937f8..cb2ed58907 100644
--- a/Documentation/config/merge.txt
+++ b/Documentation/config/merge.txt
@@ -70,6 +70,16 @@ merge.stat::
Whether to print the diffstat between ORIG_HEAD and the merge result
at the end of the merge. True by default.
+merge.autoStash::
+ When set to true, automatically create a temporary stash entry
+ before the operation begins, and apply it after the operation
+ ends. This means that you can run merge on a dirty worktree.
+ However, use with care: the final stash application after a
+ successful merge might result in non-trivial conflicts.
+ This option can be overridden by the `--no-autostash` and
+ `--autostash` options of linkgit:git-merge[1].
+ Defaults to false.
+
merge.tool::
Controls which merge tool is used by linkgit:git-mergetool[1].
The list below shows the valid built-in values.
diff --git a/Documentation/config/protocol.txt b/Documentation/config/protocol.txt
index 756591d77b..0b40141613 100644
--- a/Documentation/config/protocol.txt
+++ b/Documentation/config/protocol.txt
@@ -48,7 +48,7 @@ protocol.version::
If set, clients will attempt to communicate with a server
using the specified protocol version. If the server does
not support it, communication falls back to version 0.
- If unset, the default is `2`.
+ If unset, the default is `0`.
Supported versions:
+
--
diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt
index 0a7aa322a9..f5e5b38c68 100644
--- a/Documentation/config/push.txt
+++ b/Documentation/config/push.txt
@@ -112,3 +112,5 @@ push.recurseSubmodules::
is 'no' then default behavior of ignoring submodules when pushing
is retained. You may override this configuration at time of push by
specifying '--recurse-submodules=check|on-demand|no'.
+ If not set, 'no' is used by default, unless 'submodule.recurse' is
+ set (in which case a 'true' value means 'on-demand').
diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt
index b33177151c..d7a63c8c12 100644
--- a/Documentation/config/submodule.txt
+++ b/Documentation/config/submodule.txt
@@ -59,9 +59,17 @@ submodule.active::
submodule.recurse::
Specifies if commands recurse into submodules by default. This
- applies to all commands that have a `--recurse-submodules` option,
- except `clone`.
+ applies to all commands that have a `--recurse-submodules` option
+ (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
+ `restore` and `switch`) except `clone` and `ls-files`.
Defaults to false.
+ When set to true, it can be deactivated via the
+ `--no-recurse-submodules` option. Note that some Git commands
+ lacking this option may call some of the above commands affected by
+ `submodule.recurse`; for instance `git remote update` will call
+ `git fetch` but does not have a `--no-recurse-submodules` option.
+ For these commands a workaround is to temporarily change the
+ configuration value by using `git -c submodule.recurse=0`.
submodule.fetchJobs::
Specifies how many submodules are fetched/cloned at the same time.
diff --git a/Documentation/config/trace2.txt b/Documentation/config/trace2.txt
index 4ce0b9a6d1..01d3afd8a8 100644
--- a/Documentation/config/trace2.txt
+++ b/Documentation/config/trace2.txt
@@ -48,6 +48,15 @@ trace2.configParams::
May be overridden by the `GIT_TRACE2_CONFIG_PARAMS` environment
variable. Unset by default.
+trace2.envVars::
+ A comma-separated list of "important" environment variables that should
+ be recorded in the trace2 output. For example,
+ `GIT_HTTP_USER_AGENT,GIT_CONFIG` would cause the trace2 output to
+ contain events listing the overrides for HTTP user agent and the
+ location of the Git configuration file (assuming any are set). May be
+ overriden by the `GIT_TRACE2_ENV_VARS` environment variable. Unset by
+ default.
+
trace2.destinationDebug::
Boolean. When true Git will print error messages when a
trace target destination cannot be opened for writing.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index 00d03ec8c3..6e2a160a47 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -163,7 +163,8 @@ ifndef::git-pull[]
value. Use 'on-demand' to only recurse into a populated submodule
when the superproject retrieves a commit that updates the submodule's
reference to a commit that isn't already in the local submodule
- clone.
+ clone. By default, 'on-demand' is used, unless
+ `fetch.recurseSubmodules` is set (see linkgit:git-config[1]).
endif::git-pull[]
-j::
@@ -204,7 +205,6 @@ ifndef::git-pull[]
recursion (such as settings in linkgit:gitmodules[5] and
linkgit:git-config[1]) override this option, as does
specifying --[no-]recurse-submodules directly.
-endif::git-pull[]
-u::
--update-head-ok::
@@ -214,6 +214,7 @@ endif::git-pull[]
to communicate with 'git fetch', and unless you are
implementing your own Porcelain you are not supposed to
use it.
+endif::git-pull[]
--upload-pack <upload-pack>::
When given, and the repository to fetch from is handled
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index ab5754e05d..38c0852139 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -148,9 +148,12 @@ default. You can use `--no-utf8` to override this.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand both `commit.gpgSign` configuration variable, and
+ earlier `--gpg-sign`.
--continue::
-r::
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
new file mode 100644
index 0000000000..643d1b2884
--- /dev/null
+++ b/Documentation/git-bugreport.txt
@@ -0,0 +1,52 @@
+git-bugreport(1)
+================
+
+NAME
+----
+git-bugreport - Collect information for user to file a bug report
+
+SYNOPSIS
+--------
+[verse]
+'git bugreport' [(-o | --output-directory) <path>] [(-s | --suffix) <format>]
+
+DESCRIPTION
+-----------
+Captures information about the user's machine, Git client, and repository state,
+as well as a form requesting information about the behavior the user observed,
+into a single text file which the user can then share, for example to the Git
+mailing list, in order to report an observed bug.
+
+The following information is requested from the user:
+
+ - Reproduction steps
+ - Expected behavior
+ - Actual behavior
+
+The following information is captured automatically:
+
+ - 'git version --build-options'
+ - uname sysname, release, version, and machine strings
+ - Compiler-specific info string
+
+This tool is invoked via the typical Git setup process, which means that in some
+cases, it might not be able to launch - for example, if a relevant config file
+is unreadable. In this kind of scenario, it may be helpful to manually gather
+the kind of information listed above when manually asking for help.
+
+OPTIONS
+-------
+-o <path>::
+--output-directory <path>::
+ Place the resulting bug report file in `<path>` instead of the root of
+ the Git repository.
+
+-s <format>::
+--suffix <format>::
+ Specify an alternate suffix for the bugreport name, to create a file
+ named 'git-bugreport-<formatted suffix>'. This should take the form of a
+ link:strftime[3] format string; the current local time will be used.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index c8fb995fa7..5b697eee1b 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -292,11 +292,11 @@ Note that this option uses the no overlay mode by default (see also
--recurse-submodules::
--no-recurse-submodules::
- Using `--recurse-submodules` will update the content of all initialized
+ Using `--recurse-submodules` will update the content of all active
submodules according to the commit recorded in the superproject. If
local modifications in a submodule would be overwritten the checkout
will fail unless `-f` is used. If nothing (or `--no-recurse-submodules`)
- is used, the work trees of submodules will not be updated.
+ is used, submodules working trees will not be updated.
Just like linkgit:git-submodule[1], this will detach `HEAD` of the
submodule.
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 83ce51aedf..75feeef08a 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -109,9 +109,12 @@ effect to your index in a row.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand both `commit.gpgSign` configuration variable, and
+ earlier `--gpg-sign`.
--ff::
If the current HEAD is the same as the parent of the
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index bf24f1813a..08d6045c4a 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -15,7 +15,8 @@ SYNOPSIS
[--dissociate] [--separate-git-dir <git dir>]
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
- [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--] <repository>
+ [--[no-]remote-submodules] [--jobs <n>] [--sparse]
+ [--filter=<filter>] [--] <repository>
[<directory>]
DESCRIPTION
@@ -162,6 +163,16 @@ objects from the source repository into a pack in the cloned repository.
of the repository. The sparse-checkout file can be
modified to grow the working directory as needed.
+--filter=<filter-spec>::
+ Use the partial clone feature and request that the server sends
+ a subset of reachable objects according to a given object filter.
+ When using `--filter`, the supplied `<filter-spec>` is used for
+ the partial clone filter. For example, `--filter=blob:none` will
+ filter out all blobs (file contents) until needed by Git. Also,
+ `--filter=blob:limit=<size>` will filter out all blobs of size
+ at least `<size>`. For more details on filter specifications, see
+ the `--filter` option in linkgit:git-rev-list[1].
+
--mirror::
Set up a mirror of the source repository. This implies `--bare`.
Compared to `--bare`, `--mirror` not only maps local branches of the
diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt
index 28d1fee505..53a650225a 100644
--- a/Documentation/git-commit-graph.txt
+++ b/Documentation/git-commit-graph.txt
@@ -57,11 +57,23 @@ or `--stdin-packs`.)
With the `--append` option, include all commits that are present in the
existing commit-graph file.
+
-With the `--split` option, write the commit-graph as a chain of multiple
-commit-graph files stored in `<dir>/info/commit-graphs`. The new commits
-not already in the commit-graph are added in a new "tip" file. This file
-is merged with the existing file if the following merge conditions are
-met:
+With the `--changed-paths` option, compute and write information about the
+paths changed between a commit and it's first parent. This operation can
+take a while on large repositories. It provides significant performance gains
+for getting history of a directory or a file with `git log -- <path>`.
++
+With the `--split[=<strategy>]` option, write the commit-graph as a
+chain of multiple commit-graph files stored in
+`<dir>/info/commit-graphs`. Commit-graph layers are merged based on the
+strategy and other splitting options. The new commits not already in the
+commit-graph are added in a new "tip" file. This file is merged with the
+existing file if the following merge conditions are met:
+* If `--split=no-merge` is specified, a merge is never performed, and
+the remaining options are ignored. `--split=replace` overwrites the
+existing chain with a new one. A bare `--split` defers to the remaining
+options. (Note that merging a chain of commit graphs replaces the
+existing chain with a length-1 chain where the first and only
+incremental holds the entire graph).
+
* If `--size-multiple=<X>` is not specified, let `X` equal 2. If the new
tip file would have `N` commits and the previous tip has `M` commits and
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
index ec15ee8d6f..2e2c581098 100644
--- a/Documentation/git-commit-tree.txt
+++ b/Documentation/git-commit-tree.txt
@@ -61,13 +61,11 @@ OPTIONS
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
-
---no-gpg-sign::
- Do not GPG-sign commit, to countermand a `--gpg-sign` option
- given earlier on the command line.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand a `--gpg-sign` option given earlier on the command line.
Commit Information
------------------
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 13f653989f..a3baea32ae 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -348,13 +348,12 @@ changes to tracked files.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
-
---no-gpg-sign::
- Countermand `commit.gpgSign` configuration variable that is
- set to force each and every commit to be signed.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand both `commit.gpgSign` configuration variable, and
+ earlier `--gpg-sign`.
\--::
Do not interpret any more arguments as options.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 0d4f8951bb..0f81d0437b 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
[--range-diff=<previous> [--creation-factor=<percent>]]
@@ -253,6 +254,13 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--encode-email-headers::
+--no-encode-email-headers::
+ Encode email headers that have non-ASCII characters with
+ "Q-encoding" (described in RFC 2047), instead of outputting the
+ headers verbatim. Defaults to the value of the
+ `format.encodeEmailHeaders` configuration variable.
+
--interdiff=<previous>::
As a reviewer aid, insert an interdiff into the cover letter,
or as commentary of the lone patch of a 1-patch series, showing
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index ddb6acc025..a7f9bc99ea 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -93,7 +93,7 @@ OPTIONS
with `--no-index`.
--recurse-submodules::
- Recursively search in each submodule that has been initialized and
+ Recursively search in each submodule that is active and
checked out in the repository. When used in combination with the
<tree> option the prefix of all submodule output will be the name of
the parent project's <tree> object. This option has no effect
@@ -206,8 +206,10 @@ providing this option will cause it to die.
-z::
--null::
- Output \0 instead of the character that normally follows a
- file name.
+ Use \0 as the delimiter for pathnames in the output, and print
+ them verbatim. Without this option, pathnames with "unusual"
+ characters are quoted as explained for the configuration
+ variable core.quotePath (see git-config(1)).
-o::
--only-matching::
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index bed09bb09e..20e6d21a74 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -43,12 +43,16 @@ OPTIONS
If no `--decorate-refs` is given, pretend as if all refs were
included. For each candidate, do not use it for decoration if it
matches any patterns given to `--decorate-refs-exclude` or if it
- doesn't match any of the patterns given to `--decorate-refs`.
+ doesn't match any of the patterns given to `--decorate-refs`. The
+ `log.excludeDecoration` config option allows excluding refs from
+ the decorations, but an explicit `--decorate-refs` pattern will
+ override a match in `log.excludeDecoration`.
--source::
Print out the ref name given on the command line by which each
commit was reached.
+--[no-]mailmap::
--[no-]use-mailmap::
Use mailmap file to map author and committer names and email
addresses to canonical real names and email addresses. See
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 8461c0e83e..3cb2ebb438 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -148,7 +148,7 @@ a space) at the start of each line:
top directory.
--recurse-submodules::
- Recursively calls ls-files on each submodule in the repository.
+ Recursively calls ls-files on each active submodule in the repository.
Currently there is only support for the --cached mode.
--abbrev[=<n>]::
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index 092529c619..ec06b2f8c2 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -94,7 +94,8 @@ will be appended to the specified message.
--abort::
Abort the current conflict resolution process, and
- try to reconstruct the pre-merge state.
+ try to reconstruct the pre-merge state. If an autostash entry is
+ present, apply it to the worktree.
+
If there were uncommitted worktree changes present when the merge
started, 'git merge --abort' will in some cases be unable to
@@ -102,11 +103,15 @@ reconstruct these changes. It is therefore recommended to always
commit or stash your changes before running 'git merge'.
+
'git merge --abort' is equivalent to 'git reset --merge' when
-`MERGE_HEAD` is present.
+`MERGE_HEAD` is present unless `MERGE_AUTOSTASH` is also present in
+which case 'git merge --abort' applies the stash entry to the worktree
+whereas 'git reset --merge' will save the stashed changes in the stash
+reflog.
--quit::
Forget about the current merge in progress. Leave the index
- and the working tree as-is.
+ and the working tree as-is. If `MERGE_AUTOSTASH` is present, the
+ stash entry will be saved to the stash reflog.
--continue::
After a 'git merge' stops due to conflicts you can conclude the
diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt
index 3494a1db3e..dab9609013 100644
--- a/Documentation/git-p4.txt
+++ b/Documentation/git-p4.txt
@@ -374,14 +374,55 @@ These options can be used to modify 'git p4 submit' behavior.
been submitted. Implies --disable-rebase. Can also be set with
git-p4.disableP4Sync. Sync with origin/master still goes ahead if possible.
-Hook for submit
-~~~~~~~~~~~~~~~
+Hooks for submit
+----------------
+
+p4-pre-submit
+~~~~~~~~~~~~~
+
The `p4-pre-submit` hook is executed if it exists and is executable.
The hook takes no parameters and nothing from standard input. Exiting with
non-zero status from this script prevents `git-p4 submit` from launching.
+It can be bypassed with the `--no-verify` command line option.
One usage scenario is to run unit tests in the hook.
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+p4-changelist
+~~~~~~~~~~~~~
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+
+
Rebase options
~~~~~~~~~~~~~~
These options can be used to modify 'git p4 rebase' behavior.
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index dfb901f8b8..5c3fb67c01 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -85,8 +85,9 @@ OPTIONS
Pass --verbose to git-fetch and git-merge.
--[no-]recurse-submodules[=yes|on-demand|no]::
- This option controls if new commits of all populated submodules should
- be fetched and updated, too (see linkgit:git-config[1] and
+ This option controls if new commits of populated submodules should
+ be fetched, and if the working trees of active submodules should be
+ updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and
linkgit:gitmodules[5]).
+
If the checkout is done via rebase, local submodule commits are rebased as well.
@@ -133,15 +134,6 @@ unless you have read linkgit:git-rebase[1] carefully.
--no-rebase::
Override earlier --rebase.
---autostash::
---no-autostash::
- Before starting rebase, stash local modifications away (see
- linkgit:git-stash[1]) if needed, and apply the stash entry when
- done. `--no-autostash` is useful to override the `rebase.autoStash`
- configuration variable (see linkgit:git-config[1]).
-+
-This option is only valid when "--rebase" is used.
-
Options related to fetching
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -229,9 +221,9 @@ branch.<name>.merge options; see linkgit:git-config[1] for details.
$ git pull origin next
------------------------------------------------
+
-This leaves a copy of `next` temporarily in FETCH_HEAD, but
-does not update any remote-tracking branches. Using remote-tracking
-branches, the same can be done by invoking fetch and merge:
+This leaves a copy of `next` temporarily in FETCH_HEAD, and
+updates the remote-tracking branch `origin/next`.
+The same can be done by invoking fetch and merge:
+
------------------------------------------------
$ git fetch origin
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index da33f84f33..5fa8bab64c 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -116,9 +116,9 @@ OPTIONS
located in.
--[no-]recurse-submodules::
- Using --recurse-submodules will update the content of all initialized
+ Using --recurse-submodules will update the content of all active
submodules according to the commit recorded in the superproject by
- calling read-tree recursively, also setting the submodules HEAD to be
+ calling read-tree recursively, also setting the submodules' HEAD to be
detached at that commit.
--no-sparse-checkout::
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index f7a6033607..794f2f39f1 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -256,7 +256,8 @@ See also INCOMPATIBLE OPTIONS below.
--quit::
Abort the rebase operation but HEAD is not reset back to the
original branch. The index and working tree are also left
- unchanged as a result.
+ unchanged as a result. If a temporary stash entry was created
+ using --autostash, it will be saved to the stash reflog.
--apply:
Use applying strategies to rebase (calling `git-am`
@@ -277,20 +278,51 @@ See also INCOMPATIBLE OPTIONS below.
Other options, like --exec, will use the default of drop unless
-i/--interactive is explicitly specified.
+
-Note that commits which start empty are kept, and commits which are
-clean cherry-picks (as determined by `git log --cherry-mark ...`) are
-always dropped.
+Note that commits which start empty are kept (unless --no-keep-empty
+is specified), and commits which are clean cherry-picks (as determined
+by `git log --cherry-mark ...`) are detected and dropped as a
+preliminary step (unless --reapply-cherry-picks is passed).
+
See also INCOMPATIBLE OPTIONS below.
+--no-keep-empty::
--keep-empty::
- No-op. Rebasing commits that started empty (had no change
- relative to their parent) used to fail and this option would
- override that behavior, allowing commits with empty changes to
- be rebased. Now commits with no changes do not cause rebasing
- to halt.
+ Do not keep commits that start empty before the rebase
+ (i.e. that do not change anything from its parent) in the
+ result. The default is to keep commits which start empty,
+ since creating such commits requires passing the --allow-empty
+ override flag to `git commit`, signifying that a user is very
+ intentionally creating such a commit and thus wants to keep
+ it.
+
-See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below.
+Usage of this flag will probably be rare, since you can get rid of
+commits that start empty by just firing up an interactive rebase and
+removing the lines corresponding to the commits you don't want. This
+flag exists as a convenient shortcut, such as for cases where external
+tools generate many empty commits and you want them all removed.
++
+For commits which do not start empty but become empty after rebasing,
+see the --empty flag.
++
+See also INCOMPATIBLE OPTIONS below.
+
+--reapply-cherry-picks::
+--no-reapply-cherry-picks::
+ Reapply all clean cherry-picks of any upstream commit instead
+ of preemptively dropping them. (If these commits then become
+ empty after rebasing, because they contain a subset of already
+ upstream changes, the behavior towards them is controlled by
+ the `--empty` flag.)
++
+By default (or if `--no-reapply-cherry-picks` is given), these commits
+will be automatically dropped. Because this necessitates reading all
+upstream commits, this can be expensive in repos with a large number
+of upstream commits that need to be read.
++
+`--reapply-cherry-picks` allows rebase to forgo reading all upstream
+commits, potentially improving performance.
++
+See also INCOMPATIBLE OPTIONS below.
--allow-empty-message::
No-op. Rebasing commits with an empty message used to fail
@@ -354,9 +386,12 @@ See also INCOMPATIBLE OPTIONS below.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand both `commit.gpgSign` configuration variable, and
+ earlier `--gpg-sign`.
-q::
--quiet::
@@ -414,12 +449,14 @@ When --fork-point is active, 'fork_point' will be used instead of
<branch>` command (see linkgit:git-merge-base[1]). If 'fork_point'
ends up being empty, the <upstream> will be used as a fallback.
+
-If either <upstream> or --root is given on the command line, then the
-default is `--no-fork-point`, otherwise the default is `--fork-point`.
+If <upstream> is given on the command line, then the default is
+`--no-fork-point`, otherwise the default is `--fork-point`.
+
If your branch was based on <upstream> but <upstream> was rewound and
your branch contains commits which were dropped, this option can be used
with `--keep-base` in order to drop those commits from your branch.
++
+See also INCOMPATIBLE OPTIONS below.
--ignore-whitespace::
--whitespace=<option>::
@@ -587,8 +624,9 @@ are incompatible with the following options:
* --preserve-merges
* --interactive
* --exec
- * --keep-empty
+ * --no-keep-empty
* --empty=
+ * --reapply-cherry-picks
* --edit-todo
* --root when used in combination with --onto
@@ -600,12 +638,13 @@ In addition, the following pairs of options are incompatible:
* --preserve-merges and --empty=
* --keep-base and --onto
* --keep-base and --root
+ * --fork-point and --root
BEHAVIORAL DIFFERENCES
-----------------------
git rebase has two primary backends: apply and merge. (The apply
-backend used to known as the 'am' backend, but the name led to
+backend used to be known as the 'am' backend, but the name led to
confusion as it looks like a verb instead of a noun. Also, the merge
backend used to be known as the interactive backend, but it is now
used for non-interactive cases as well. Both were renamed based on
@@ -620,12 +659,15 @@ commits that started empty, though these are rare in practice. It
also drops commits that become empty and has no option for controlling
this behavior.
-The merge backend keeps intentionally empty commits. Similar to the
-apply backend, by default the merge backend drops commits that become
-empty unless -i/--interactive is specified (in which case it stops and
-asks the user what to do). The merge backend also has an
---empty={drop,keep,ask} option for changing the behavior of handling
-commits that become empty.
+The merge backend keeps intentionally empty commits by default (though
+with -i they are marked as empty in the todo list editor, or they can
+be dropped automatically with --no-keep-empty).
+
+Similar to the apply backend, by default the merge backend drops
+commits that become empty unless -i/--interactive is specified (in
+which case it stops and asks the user what to do). The merge backend
+also has an --empty={drop,keep,ask} option for changing the behavior
+of handling commits that become empty.
Directory rename detection
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -684,9 +726,17 @@ Hooks
~~~~~
The apply backend has not traditionally called the post-commit hook,
-while the merge backend has. However, this was by accident of
-implementation rather than by design. Both backends should have the
-same behavior, though it is not clear which one is correct.
+while the merge backend has. Both have called the post-checkout hook,
+though the merge backend has squelched its output. Further, both
+backends only call the post-checkout hook with the starting point
+commit of the rebase, not the intermediate commits nor the final
+commit. In each case, the calling of these hooks was by accident of
+implementation rather than by design (both backends were originally
+implemented as shell scripts and happened to invoke other commands
+like 'git checkout' or 'git commit' that would call the hooks). Both
+backends should have the same behavior, though it is not entirely
+clear which, if any, is correct. We will likely make rebase stop
+calling either of these hooks in the future.
Interruptability
~~~~~~~~~~~~~~~~
@@ -1002,7 +1052,8 @@ Only works if the changes (patch IDs based on the diff contents) on
'subsystem' did.
In that case, the fix is easy because 'git rebase' knows to skip
-changes that are already present in the new upstream. So if you say
+changes that are already present in the new upstream (unless
+`--reapply-cherry-picks` is given). So if you say
(assuming you're on 'topic')
------------
$ git rebase subsystem
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 932080c55d..252e2d4e47 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -87,6 +87,12 @@ but carries forward unmerged index entries.
different between `<commit>` and `HEAD`.
If a file that is different between `<commit>` and `HEAD` has local
changes, reset is aborted.
+
+--[no-]recurse-submodules::
+ When the working tree is updated, using --recurse-submodules will
+ also recursively reset the working tree of all active submodules
+ according to the commit recorded in the superproject, also setting
+ the submodules' HEAD to be detached at that commit.
--
See "Reset, restore and revert" in linkgit:git[1] for the differences
diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt
index 5bf60d4943..8e3b339802 100644
--- a/Documentation/git-restore.txt
+++ b/Documentation/git-restore.txt
@@ -107,6 +107,17 @@ in linkgit:git-checkout[1] for details.
patterns and unconditionally restores any files in
`<pathspec>`.
+--recurse-submodules::
+--no-recurse-submodules::
+ If `<pathspec>` names an active submodule and the restore location
+ includes the working tree, the submodule will only be updated if
+ this option is given, in which case its working tree will be
+ restored to the commit recorded in the superproject, and any local
+ modifications overwritten. If nothing (or
+ `--no-recurse-submodules`) is used, submodules working trees will
+ not be updated. Just like linkgit:git-checkout[1], this will detach
+ `HEAD` of the submodule.
+
--overlay::
--no-overlay::
In overlay mode, the command never removes files when
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 9d22270757..044276e9da 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -90,9 +90,12 @@ effect to your index in a row.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign commits. The `keyid` argument is optional and
defaults to the committer identity; if specified, it must be
- stuck to the option without a space.
+ stuck to the option without a space. `--no-gpg-sign` is useful to
+ countermand both `commit.gpgSign` configuration variable, and
+ earlier `--gpg-sign`.
-s::
--signoff::
diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt
index c0342e5393..1a3ace6082 100644
--- a/Documentation/git-sparse-checkout.txt
+++ b/Documentation/git-sparse-checkout.txt
@@ -70,6 +70,16 @@ C-style quoted strings.
`core.sparseCheckoutCone` is enabled, the given patterns are interpreted
as directory names as in the 'set' subcommand.
+'reapply::
+ Reapply the sparsity pattern rules to paths in the working tree.
+ Commands like merge or rebase can materialize paths to do their
+ work (e.g. in order to show you a conflict), and other
+ sparse-checkout commands might fail to sparsify an individual file
+ (e.g. because it has unstaged changes or conflicts). In such
+ cases, it can make sense to run `git sparse-checkout reapply` later
+ after cleaning up affected paths (e.g. resolving conflicts, undoing
+ or committing changes, etc.).
+
'disable'::
Disable the `core.sparseCheckout` config setting, and restore the
working directory to include all files. Leaves the sparse-checkout
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 197900363b..3759c3a265 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -181,9 +181,9 @@ name, the guessing is aborted. You can explicitly give a name with
--recurse-submodules::
--no-recurse-submodules::
Using `--recurse-submodules` will update the content of all
- initialized submodules according to the commit recorded in the
+ active submodules according to the commit recorded in the
superproject. If nothing (or `--no-recurse-submodules`) is
- used, the work trees of submodules will not be updated. Just
+ used, submodules working trees will not be updated. Just
like linkgit:git-submodule[1], this will detach `HEAD` of the
submodules.
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 9671423117..3e737c2360 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -66,6 +66,10 @@ performs all modifications together. Specify commands of the form:
delete SP <ref> [SP <oldvalue>] LF
verify SP <ref> [SP <oldvalue>] LF
option SP <opt> LF
+ start LF
+ prepare LF
+ commit LF
+ abort LF
With `--create-reflog`, update-ref will create a reflog for each ref
even if one would not ordinarily be created.
@@ -83,6 +87,10 @@ quoting:
delete SP <ref> NUL [<oldvalue>] NUL
verify SP <ref> NUL [<oldvalue>] NUL
option SP <opt> NUL
+ start NUL
+ prepare NUL
+ commit NUL
+ abort NUL
In this format, use 40 "0" to specify a zero value, and use the empty
string to specify a missing value.
@@ -107,13 +115,31 @@ delete::
verify::
Verify <ref> against <oldvalue> but do not change it. If
- <oldvalue> zero or missing, the ref must not exist.
+ <oldvalue> is zero or missing, the ref must not exist.
option::
Modify behavior of the next command naming a <ref>.
The only valid option is `no-deref` to avoid dereferencing
a symbolic ref.
+start::
+ Start a transaction. In contrast to a non-transactional session, a
+ transaction will automatically abort if the session ends without an
+ explicit commit.
+
+prepare::
+ Prepare to commit the transaction. This will create lock files for all
+ queued reference updates. If one reference could not be locked, the
+ transaction will be aborted.
+
+commit::
+ Commit all reference updates queued for the transaction, ending the
+ transaction.
+
+abort::
+ Abort the transaction, releasing all locks if the transaction is in
+ prepared state.
+
If all <ref>s can be locked with matching <oldvalue>s
simultaneously, all modifications are performed. Otherwise, no
modifications are performed. Note that while each individual
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
new file mode 100644
index 0000000000..1cf83df118
--- /dev/null
+++ b/Documentation/gitfaq.txt
@@ -0,0 +1,337 @@
+gitfaq(7)
+=========
+
+NAME
+----
+gitfaq - Frequently asked questions about using Git
+
+SYNOPSIS
+--------
+gitfaq
+
+DESCRIPTION
+-----------
+
+The examples in this FAQ assume a standard POSIX shell, like `bash` or `dash`,
+and a user, A U Thor, who has the account `author` on the hosting provider
+`git.example.org`.
+
+Configuration
+-------------
+
+[[user-name]]
+What should I put in `user.name`?::
+ You should put your personal name, generally a form using a given name
+ and family name. For example, the current maintainer of Git uses "Junio
+ C Hamano". This will be the name portion that is stored in every commit
+ you make.
++
+This configuration doesn't have any effect on authenticating to remote services;
+for that, see `credential.username` in linkgit:git-config[1].
+
+[[http-postbuffer]]
+What does `http.postBuffer` really do?::
+ This option changes the size of the buffer that Git uses when pushing
+ data to a remote over HTTP or HTTPS. If the data is larger than this
+ size, libcurl, which handles the HTTP support for Git, will use chunked
+ transfer encoding since it isn't known ahead of time what the size of
+ the pushed data will be.
++
+Leaving this value at the default size is fine unless you know that either the
+remote server or a proxy in the middle doesn't support HTTP/1.1 (which
+introduced the chunked transfer encoding) or is known to be broken with chunked
+data. This is often (erroneously) suggested as a solution for generic push
+problems, but since almost every server and proxy supports at least HTTP/1.1,
+raising this value usually doesn't solve most push problems. A server or proxy
+that didn't correctly support HTTP/1.1 and chunked transfer encoding wouldn't be
+that useful on the Internet today, since it would break lots of traffic.
++
+Note that increasing this value will increase the memory used on every relevant
+push that Git does over HTTP or HTTPS, since the entire buffer is allocated
+regardless of whether or not it is all used. Thus, it's best to leave it at the
+default unless you are sure you need a different value.
+
+[[configure-editor]]
+How do I configure a different editor?::
+ If you haven't specified an editor specifically for Git, it will by default
+ use the editor you've configured using the `VISUAL` or `EDITOR` environment
+ variables, or if neither is specified, the system default (which is usually
+ `vi`). Since some people find `vi` difficult to use or prefer a different
+ editor, it may be desirable to change the editor used.
++
+If you want to configure a general editor for most programs which need one, you
+can edit your shell configuration (e.g., `~/.bashrc` or `~/.zshenv`) to contain
+a line setting the `EDITOR` or `VISUAL` environment variable to an appropriate
+value. For example, if you prefer the editor `nano`, then you could write the
+following:
++
+----
+export VISUAL=nano
+----
++
+If you want to configure an editor specifically for Git, you can either set the
+`core.editor` configuration value or the `GIT_EDITOR` environment variable. You
+can see linkgit:git-var[1] for details on the order in which these options are
+consulted.
++
+Note that in all cases, the editor value will be passed to the shell, so any
+arguments containing spaces should be appropriately quoted. Additionally, if
+your editor normally detaches from the terminal when invoked, you should specify
+it with an argument that makes it not do that, or else Git will not see any
+changes. An example of a configuration addressing both of these issues on
+Windows would be the configuration `"C:\Program Files\Vim\gvim.exe" --nofork`,
+which quotes the filename with spaces and specifies the `--nofork` option to
+avoid backgrounding the process.
+
+Credentials
+-----------
+
+[[http-credentials]]
+How do I specify my credentials when pushing over HTTP?::
+ The easiest way to do this is to use a credential helper via the
+ `credential.helper` configuration. Most systems provide a standard
+ choice to integrate with the system credential manager. For example,
+ Git for Windows provides the `wincred` credential manager, macOS has the
+ `osxkeychain` credential manager, and Unix systems with a standard
+ desktop environment can use the `libsecret` credential manager. All of
+ these store credentials in an encrypted store to keep your passwords or
+ tokens secure.
++
+In addition, you can use the `store` credential manager which stores in a file
+in your home directory, or the `cache` credential manager, which does not
+permanently store your credentials, but does prevent you from being prompted for
+them for a certain period of time.
++
+You can also just enter your password when prompted. While it is possible to
+place the password (which must be percent-encoded) in the URL, this is not
+particularly secure and can lead to accidental exposure of credentials, so it is
+not recommended.
+
+[[http-credentials-environment]]
+How do I read a password or token from an environment variable?::
+ The `credential.helper` configuration option can also take an arbitrary
+ shell command that produces the credential protocol on standard output.
+ This is useful when passing credentials into a container, for example.
++
+Such a shell command can be specified by starting the option value with an
+exclamation point. If your password or token were stored in the `GIT_TOKEN`,
+you could run the following command to set your credential helper:
++
+----
+$ git config credential.helper \
+ '!f() { echo username=author; echo "password=$GIT_TOKEN"; };f'
+----
+
+[[http-reset-credentials]]
+How do I change the password or token I've saved in my credential manager?::
+ Usually, if the password or token is invalid, Git will erase it and
+ prompt for a new one. However, there are times when this doesn't always
+ happen. To change the password or token, you can erase the existing
+ credentials and then Git will prompt for new ones. To erase
+ credentials, use a syntax like the following (substituting your username
+ and the hostname):
++
+----
+$ echo url=https://author@git.example.org | git credential reject
+----
+
+[[multiple-accounts-http]]
+How do I use multiple accounts with the same hosting provider using HTTP?::
+ Usually the easiest way to distinguish between these accounts is to use
+ the username in the URL. For example, if you have the accounts `author`
+ and `committer` on `git.example.org`, you can use the URLs
+ https://author@git.example.org/org1/project1.git and
+ https://committer@git.example.org/org2/project2.git. This way, when you
+ use a credential helper, it will automatically try to look up the
+ correct credentials for your account. If you already have a remote set
+ up, you can change the URL with something like `git remote set-url
+ origin https://author@git.example.org/org1/project1.git` (see
+ linkgit:git-remote[1] for details).
+
+[[multiple-accounts-ssh]]
+How do I use multiple accounts with the same hosting provider using SSH?::
+ With most hosting providers that support SSH, a single key pair uniquely
+ identifies a user. Therefore, to use multiple accounts, it's necessary
+ to create a key pair for each account. If you're using a reasonably
+ modern OpenSSH version, you can create a new key pair with something
+ like `ssh-keygen -t ed25519 -f ~/.ssh/id_committer`. You can then
+ register the public key (in this case, `~/.ssh/id_committer.pub`; note
+ the `.pub`) with the hosting provider.
++
+Most hosting providers use a single SSH account for pushing; that is, all users
+push to the `git` account (e.g., `git@git.example.org`). If that's the case for
+your provider, you can set up multiple aliases in SSH to make it clear which key
+pair to use. For example, you could write something like the following in
+`~/.ssh/config`, substituting the proper private key file:
++
+----
+# This is the account for author on git.example.org.
+Host example_author
+ HostName git.example.org
+ User git
+ # This is the key pair registered for author with git.example.org.
+ IdentityFile ~/.ssh/id_author
+ IdentitiesOnly yes
+# This is the account for committer on git.example.org.
+Host example_committer
+ HostName git.example.org
+ User git
+ # This is the key pair registered for committer with git.example.org.
+ IdentityFile ~/.ssh/id_committer
+ IdentitiesOnly yes
+----
++
+Then, you can adjust your push URL to use `git@example_author` or
+`git@example_committer` instead of `git@example.org` (e.g., `git remote set-url
+git@example_author:org1/project1.git`).
+
+Common Issues
+-------------
+
+[[last-commit-amend]]
+I've made a mistake in the last commit. How do I change it?::
+ You can make the appropriate change to your working tree, run `git add
+ <file>` or `git rm <file>`, as appropriate, to stage it, and then `git
+ commit --amend`. Your change will be included in the commit, and you'll
+ be prompted to edit the commit message again; if you wish to use the
+ original message verbatim, you can use the `--no-edit` option to `git
+ commit` in addition, or just save and quit when your editor opens.
+
+[[undo-previous-change]]
+I've made a change with a bug and it's been included in the main branch. How should I undo it?::
+ The usual way to deal with this is to use `git revert`. This preserves
+ the history that the original change was made and was a valuable
+ contribution, but also introduces a new commit that undoes those changes
+ because the original had a problem. The commit message of the revert
+ indicates the commit which was reverted and is usually edited to include
+ an explanation as to why the revert was made.
+
+[[ignore-tracked-files]]
+How do I ignore changes to a tracked file?::
+ Git doesn't provide a way to do this. The reason is that if Git needs
+ to overwrite this file, such as during a checkout, it doesn't know
+ whether the changes to the file are precious and should be kept, or
+ whether they are irrelevant and can safely be destroyed. Therefore, it
+ has to take the safe route and always preserve them.
++
+It's tempting to try to use certain features of `git update-index`, namely the
+assume-unchanged and skip-worktree bits, but these don't work properly for this
+purpose and shouldn't be used this way.
++
+If your goal is to modify a configuration file, it can often be helpful to have
+a file checked into the repository which is a template or set of defaults which
+can then be copied alongside and modified as appropriate. This second, modified
+file is usually ignored to prevent accidentally committing it.
+
+Hooks
+-----
+
+[[restrict-with-hooks]]
+How do I use hooks to prevent users from making certain changes?::
+ The only safe place to make these changes is on the remote repository
+ (i.e., the Git server), usually in the `pre-receive` hook or in a
+ continuous integration (CI) system. These are the locations in which
+ policy can be enforced effectively.
++
+It's common to try to use `pre-commit` hooks (or, for commit messages,
+`commit-msg` hooks) to check these things, which is great if you're working as a
+solo developer and want the tooling to help you. However, using hooks on a
+developer machine is not effective as a policy control because a user can bypass
+these hooks with `--no-verify` without being noticed (among various other ways).
+Git assumes that the user is in control of their local repositories and doesn't
+try to prevent this or tattle on the user.
++
+In addition, some advanced users find `pre-commit` hooks to be an impediment to
+workflows that use temporary commits to stage work in progress or that create
+fixup commits, so it's better to push these kinds of checks to the server
+anyway.
+
+Cross-Platform Issues
+---------------------
+
+[[windows-text-binary]]
+I'm on Windows and my text files are detected as binary.::
+ Git works best when you store text files as UTF-8. Many programs on
+ Windows support UTF-8, but some do not and only use the little-endian
+ UTF-16 format, which Git detects as binary. If you can't use UTF-8 with
+ your programs, you can specify a working tree encoding that indicates
+ which encoding your files should be checked out with, while still
+ storing these files as UTF-8 in the repository. This allows tools like
+ linkgit:git-diff[1] to work as expected, while still allowing your tools
+ to work.
++
+To do so, you can specify a linkgit:gitattributes[5] pattern with the
+`working-tree-encoding` attribute. For example, the following pattern sets all
+C files to use UTF-16LE-BOM, which is a common encoding on Windows:
++
+----
+*.c working-tree-encoding=UTF-16LE-BOM
+----
++
+You will need to run `git add --renormalize` to have this take effect. Note
+that if you are making these changes on a project that is used across platforms,
+you'll probably want to make it in a per-user configuration file or in the one
+in `$GIT_DIR/info/attributes`, since making it in a `.gitattributes` file in the
+repository will apply to all users of the repository.
++
+See the following entry for information about normalizing line endings as well,
+and see linkgit:gitattributes[5] for more information about attribute files.
+
+[[windows-diff-control-m]]
+I'm on Windows and git diff shows my files as having a `^M` at the end.::
+ By default, Git expects files to be stored with Unix line endings. As such,
+ the carriage return (`^M`) that is part of a Windows line ending is shown
+ because it is considered to be trailing whitespace. Git defaults to showing
+ trailing whitespace only on new lines, not existing ones.
++
+You can store the files in the repository with Unix line endings and convert
+them automatically to your platform's line endings. To do that, set the
+configuration option `core.eol` to `native` and see the following entry for
+information about how to configure files as text or binary.
++
+You can also control this behavior with the `core.whitespace` setting if you
+don't wish to remove the carriage returns from your line endings.
+
+[[recommended-storage-settings]]
+What's the recommended way to store files in Git?::
+ While Git can store and handle any file of any type, there are some
+ settings that work better than others. In general, we recommend that
+ text files be stored in UTF-8 without a byte-order mark (BOM) with LF
+ (Unix-style) endings. We also recommend the use of UTF-8 (again,
+ without BOM) in commit messages. These are the settings that work best
+ across platforms and with tools such as `git diff` and `git merge`.
++
+Additionally, if you have a choice between storage formats that are text based
+or non-text based, we recommend storing files in the text format and, if
+necessary, transforming them into the other format. For example, a text-based
+SQL dump with one record per line will work much better for diffing and merging
+than an actual database file. Similarly, text-based formats such as Markdown
+and AsciiDoc will work better than binary formats such as Microsoft Word and
+PDF.
++
+Similarly, storing binary dependencies (e.g., shared libraries or JAR files) or
+build products in the repository is generally not recommended. Dependencies and
+build products are best stored on an artifact or package server with only
+references, URLs, and hashes stored in the repository.
++
+We also recommend setting a linkgit:gitattributes[5] file to explicitly mark
+which files are text and which are binary. If you want Git to guess, you can
+set the attribute `text=auto`. For example, the following might be appropriate
+in some projects:
++
+----
+# By default, guess.
+* text=auto
+# Mark all C files as text.
+*.c text
+# Mark all JPEG files as binary.
+*.jpg binary
+----
++
+These settings help tools pick the right format for output such as patches and
+result in files being checked out in the appropriate line ending for the
+platform.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 3dccab5375..81f2a87e88 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -522,12 +522,61 @@ The exit status determines whether git will use the data from the
hook to limit its search. On error, it will fall back to verifying
all files and folders.
+p4-changelist
+~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-changelist` hook is executed after the changelist
+message has been edited by the user. It can be bypassed with the
+`--no-verify` option. It takes a single parameter, the name
+of the file that holds the proposed changelist text. Exiting
+with a non-zero status causes the command to abort.
+
+The hook is allowed to edit the changelist file and can be used
+to normalize the text into some project standard format. It can
+also be used to refuse the Submit after inspect the message file.
+
+Run `git-p4 submit --help` for details.
+
+p4-prepare-changelist
+~~~~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-prepare-changelist` hook is executed right after preparing
+the default changelist message and before the editor is started.
+It takes one parameter, the name of the file that contains the
+changelist text. Exiting with a non-zero status from the script
+will abort the process.
+
+The purpose of the hook is to edit the message file in place,
+and it is not supressed by the `--no-verify` option. This hook
+is called even if `--prepare-p4-only` is set.
+
+Run `git-p4 submit --help` for details.
+
+p4-post-changelist
+~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by `git-p4 submit`.
+
+The `p4-post-changelist` hook is invoked after the submit has
+successfully occured in P4. It takes no parameters and is meant
+primarily for notification and cannot affect the outcome of the
+git p4 submit action.
+
+Run `git-p4 submit --help` for details.
+
p4-pre-submit
~~~~~~~~~~~~~
This hook is invoked by `git-p4 submit`. It takes no parameters and nothing
from standard input. Exiting with non-zero status from this script prevent
-`git-p4 submit` from launching. Run `git-p4 submit --help` for details.
+`git-p4 submit` from launching. It can be bypassed with the `--no-verify`
+command line option. Run `git-p4 submit --help` for details.
+
+
post-index-change
~~~~~~~~~~~~~~~~~
diff --git a/Documentation/gitsubmodules.txt b/Documentation/gitsubmodules.txt
index c476f891b5..f9f4e65c9e 100644
--- a/Documentation/gitsubmodules.txt
+++ b/Documentation/gitsubmodules.txt
@@ -271,7 +271,8 @@ will not be checked out by default; You can instruct 'clone' to recurse
into submodules. The 'init' and 'update' subcommands of 'git submodule'
will maintain submodules checked out and at an appropriate revision in
your working tree. Alternatively you can set 'submodule.recurse' to have
-'checkout' recursing into submodules.
+'checkout' recursing into submodules (note that 'submodule.recurse' also
+affects other git commands, see linkgit:git-config[1] for a complete list).
SEE ALSO
diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl
deleted file mode 100644
index b4d315cb8c..0000000000
--- a/Documentation/manpage-1.72.xsl
+++ /dev/null
@@ -1,14 +0,0 @@
-<!-- manpage-1.72.xsl:
- special settings for manpages rendered from asciidoc+docbook
- handles peculiarities in docbook-xsl 1.72.0 -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- version="1.0">
-
-<xsl:import href="manpage-base.xsl"/>
-
-<!-- these are the special values for the roff control characters
- needed for docbook-xsl 1.72.0 -->
-<xsl:param name="git.docbook.backslash">&#x2593;</xsl:param>
-<xsl:param name="git.docbook.dot" >&#x2302;</xsl:param>
-
-</xsl:stylesheet>
diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl
deleted file mode 100644
index a264fa6160..0000000000
--- a/Documentation/manpage-base.xsl
+++ /dev/null
@@ -1,35 +0,0 @@
-<!-- manpage-base.xsl:
- special formatting for manpages rendered from asciidoc+docbook -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- version="1.0">
-
-<!-- these params silence some output from xmlto -->
-<xsl:param name="man.output.quietly" select="1"/>
-<xsl:param name="refentry.meta.get.quietly" select="1"/>
-
-<!-- convert asciidoc callouts to man page format;
- git.docbook.backslash and git.docbook.dot params
- must be supplied by another XSL file or other means -->
-<xsl:template match="co">
- <xsl:value-of select="concat(
- $git.docbook.backslash,'fB(',
- substring-after(@id,'-'),')',
- $git.docbook.backslash,'fR')"/>
-</xsl:template>
-<xsl:template match="calloutlist">
- <xsl:value-of select="$git.docbook.dot"/>
- <xsl:text>sp&#10;</xsl:text>
- <xsl:apply-templates/>
- <xsl:text>&#10;</xsl:text>
-</xsl:template>
-<xsl:template match="callout">
- <xsl:value-of select="concat(
- $git.docbook.backslash,'fB',
- substring-after(@arearefs,'-'),
- '. ',$git.docbook.backslash,'fR')"/>
- <xsl:apply-templates/>
- <xsl:value-of select="$git.docbook.dot"/>
- <xsl:text>br&#10;</xsl:text>
-</xsl:template>
-
-</xsl:stylesheet>
diff --git a/Documentation/manpage-bold-literal.xsl b/Documentation/manpage-bold-literal.xsl
index 94d6c1b545..e13db85693 100644
--- a/Documentation/manpage-bold-literal.xsl
+++ b/Documentation/manpage-bold-literal.xsl
@@ -8,11 +8,9 @@
this makes literal text easier to distinguish in manpages
viewed on a tty -->
<xsl:template match="literal|d:literal">
- <xsl:value-of select="$git.docbook.backslash"/>
- <xsl:text>fB</xsl:text>
+ <xsl:text>\fB</xsl:text>
<xsl:apply-templates/>
- <xsl:value-of select="$git.docbook.backslash"/>
- <xsl:text>fR</xsl:text>
+ <xsl:text>\fR</xsl:text>
</xsl:template>
</xsl:stylesheet>
diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl
index a48f5b11f3..a9c7ec69f4 100644
--- a/Documentation/manpage-normal.xsl
+++ b/Documentation/manpage-normal.xsl
@@ -1,13 +1,26 @@
<!-- manpage-normal.xsl:
- special settings for manpages rendered from asciidoc+docbook
- handles anything we want to keep away from docbook-xsl 1.72.0 -->
+ special settings for manpages rendered from asciidoc+docbook -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
-<xsl:import href="manpage-base.xsl"/>
-<!-- these are the normal values for the roff control characters -->
-<xsl:param name="git.docbook.backslash">\</xsl:param>
-<xsl:param name="git.docbook.dot" >.</xsl:param>
+<!-- these params silence some output from xmlto -->
+<xsl:param name="man.output.quietly" select="1"/>
+<xsl:param name="refentry.meta.get.quietly" select="1"/>
+
+<!-- convert asciidoc callouts to man page format -->
+<xsl:template match="co">
+ <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+ <xsl:text>.sp&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+ <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
+ <xsl:apply-templates/>
+ <xsl:text>.br&#10;</xsl:text>
+</xsl:template>
</xsl:stylesheet>
diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl
deleted file mode 100644
index a63c7632a8..0000000000
--- a/Documentation/manpage-suppress-sp.xsl
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- manpage-suppress-sp.xsl:
- special settings for manpages rendered from asciidoc+docbook
- handles erroneous, inline .sp in manpage output of some
- versions of docbook-xsl -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- version="1.0">
-
-<!-- attempt to work around spurious .sp at the tail of the line
- that some versions of docbook stylesheets seem to add -->
-<xsl:template match="simpara">
- <xsl:variable name="content">
- <xsl:apply-templates/>
- </xsl:variable>
- <xsl:value-of select="normalize-space($content)"/>
- <xsl:if test="not(ancestor::authorblurb) and
- not(ancestor::personblurb)">
- <xsl:text>&#10;&#10;</xsl:text>
- </xsl:if>
-</xsl:template>
-
-</xsl:stylesheet>
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 40dc4f5e8c..80d4831662 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -61,9 +61,12 @@ When not possible, refuse to merge and exit with a non-zero status.
-S[<keyid>]::
--gpg-sign[=<keyid>]::
+--no-gpg-sign::
GPG-sign the resulting merge commit. The `keyid` argument is
optional and defaults to the committer identity; if specified,
- it must be stuck to the option without a space.
+ it must be stuck to the option without a space. `--no-gpg-sign`
+ is useful to countermand both `commit.gpgSign` configuration variable,
+ and earlier `--gpg-sign`.
--log[=<n>]::
--no-log::
@@ -157,6 +160,14 @@ ifndef::git-pull[]
endif::git-pull[]
+--autostash::
+--no-autostash::
+ Automatically create a temporary stash entry before the operation
+ begins, and apply it after the operation ends. This means
+ that you can run the operation on a dirty worktree. However, use
+ with care: the final stash application after a successful
+ merge might result in non-trivial conflicts.
+
--allow-unrelated-histories::
By default, `git merge` command refuses to merge histories
that do not share a common ancestor. This option can be
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index a4b6f49186..547a552463 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -83,6 +83,12 @@ placeholders, its output is not affected by other options like
<full commit message>
+* 'mboxrd'
++
+Like 'email', but lines in the commit message starting with "From "
+(preceded by zero or more ">") are quoted with ">" so they aren't
+confused as starting a new commit.
+
* 'raw'
+
The 'raw' format shows the entire commit exactly as
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
index 7d3a60f5b9..95ea849902 100644
--- a/Documentation/pull-fetch-param.txt
+++ b/Documentation/pull-fetch-param.txt
@@ -19,7 +19,8 @@ ifndef::git-pull[]
(see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below).
endif::git-pull[]
ifdef::git-pull[]
- (see linkgit:git-fetch[1]).
+ (see the section "CONFIGURED REMOTE-TRACKING BRANCHES"
+ in linkgit:git-fetch[1]).
endif::git-pull[]
+
The format of a <refspec> parameter is an optional plus
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index bfd02ade99..04ad7dd36e 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -342,6 +342,12 @@ Default mode::
branches if the end result is the same (i.e. merging branches
with the same content)
+--show-pulls::
+ Include all commits from the default mode, but also any merge
+ commits that are not TREESAME to the first parent but are
+ TREESAME to a later parent. This mode is helpful for showing
+ the merge commits that "first introduced" a change to a branch.
+
--full-history::
Same as the default mode, but does not prune some history.
@@ -534,7 +540,7 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`:
parent and is TREESAME.
--
-Finally, there is a fifth simplification mode available:
+There is another simplification mode available:
--ancestry-path::
Limit the displayed commits to those directly on the ancestry
@@ -573,6 +579,132 @@ option does. Applied to the 'D..M' range, it results in:
L--M
-----------------------------------------------------------------------
+Before discussing another option, `--show-pulls`, we need to
+create a new example history.
++
+A common problem users face when looking at simplified history is that a
+commit they know changed a file somehow does not appear in the file's
+simplified history. Let's demonstrate a new example and show how options
+such as `--full-history` and `--simplify-merges` works in that case:
++
+-----------------------------------------------------------------------
+ .-A---M-----C--N---O---P
+ / / \ \ \/ / /
+ I B \ R-'`-Z' /
+ \ / \/ /
+ \ / /\ /
+ `---X--' `---Y--'
+-----------------------------------------------------------------------
++
+For this example, suppose `I` created `file.txt` which was modified by
+`A`, `B`, and `X` in different ways. The single-parent commits `C`, `Z`,
+and `Y` do not change `file.txt`. The merge commit `M` was created by
+resolving the merge conflict to include both changes from `A` and `B`
+and hence is not TREESAME to either. The merge commit `R`, however, was
+created by ignoring the contents of `file.txt` at `M` and taking only
+the contents of `file.txt` at `X`. Hence, `R` is TREESAME to `X` but not
+`M`. Finally, the natural merge resolution to create `N` is to take the
+contents of `file.txt` at `R`, so `N` is TREESAME to `R` but not `C`.
+The merge commits `O` and `P` are TREESAME to their first parents, but
+not to their second parents, `Z` and `Y` respectively.
++
+When using the default mode, `N` and `R` both have a TREESAME parent, so
+those edges are walked and the others are ignored. The resulting history
+graph is:
++
+-----------------------------------------------------------------------
+ I---X
+-----------------------------------------------------------------------
++
+When using `--full-history`, Git walks every edge. This will discover
+the commits `A` and `B` and the merge `M`, but also will reveal the
+merge commits `O` and `P`. With parent rewriting, the resulting graph is:
++
+-----------------------------------------------------------------------
+ .-A---M--------N---O---P
+ / / \ \ \/ / /
+ I B \ R-'`--' /
+ \ / \/ /
+ \ / /\ /
+ `---X--' `------'
+-----------------------------------------------------------------------
++
+Here, the merge commits `O` and `P` contribute extra noise, as they did
+not actually contribute a change to `file.txt`. They only merged a topic
+that was based on an older version of `file.txt`. This is a common
+issue in repositories using a workflow where many contributors work in
+parallel and merge their topic branches along a single trunk: manu
+unrelated merges appear in the `--full-history` results.
++
+When using the `--simplify-merges` option, the commits `O` and `P`
+disappear from the results. This is because the rewritten second parents
+of `O` and `P` are reachable from their first parents. Those edges are
+removed and then the commits look like single-parent commits that are
+TREESAME to their parent. This also happens to the commit `N`, resulting
+in a history view as follows:
++
+-----------------------------------------------------------------------
+ .-A---M--.
+ / / \
+ I B R
+ \ / /
+ \ / /
+ `---X--'
+-----------------------------------------------------------------------
++
+In this view, we see all of the important single-parent changes from
+`A`, `B`, and `X`. We also see the carefully-resolved merge `M` and the
+not-so-carefully-resolved merge `R`. This is usually enough information
+to determine why the commits `A` and `B` "disappeared" from history in
+the default view. However, there are a few issues with this approach.
++
+The first issue is performance. Unlike any previous option, the
+`--simplify-merges` option requires walking the entire commit history
+before returning a single result. This can make the option difficult to
+use for very large repositories.
++
+The second issue is one of auditing. When many contributors are working
+on the same repository, it is important which merge commits introduced
+a change into an important branch. The problematic merge `R` above is
+not likely to be the merge commit that was used to merge into an
+important branch. Instead, the merge `N` was used to merge `R` and `X`
+into the important branch. This commit may have information about why
+the change `X` came to override the changes from `A` and `B` in its
+commit message.
++
+The `--show-pulls` option helps with both of these issues by adding more
+merge commits to the history results. If a merge is not TREESAME to its
+first parent but is TREESAME to a later parent, then that merge is
+treated as if it "pulled" the change from another branch. When using
+`--show-pulls` on this example (and no other options) the resulting
+graph is:
++
+-----------------------------------------------------------------------
+ I---X---R---N
+-----------------------------------------------------------------------
++
+Here, the merge commits `R` and `N` are included because they pulled
+the commits `X` and `R` into the base branch, respectively. These
+merges are the reason the commits `A` and `B` do not appear in the
+default history.
++
+When `--show-pulls` is paired with `--simplify-merges`, the
+graph includes all of the necessary information:
++
+-----------------------------------------------------------------------
+ .-A---M--. N
+ / / \ /
+ I B R
+ \ / /
+ \ / /
+ `---X--'
+-----------------------------------------------------------------------
++
+Notice that since `M` is reachable from `R`, the edge from `N` to `M`
+was simplified away. However, `N` still appears in the history as an
+important commit because it "pulled" the change `R` into the main
+branch.
+
The `--simplify-by-decoration` option allows you to view only the
big picture of the topology of the history, by omitting commits
that are not referenced by tags. Commits are marked as !TREESAME
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 97f995e5a9..1ad95065c1 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -233,7 +233,7 @@ G H I J
A = = A^0
B = A^ = A^1 = A~1
- C = A^2 = A^2
+ C = = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt
index 4f07ceadcb..6b6085585d 100644
--- a/Documentation/technical/api-trace2.txt
+++ b/Documentation/technical/api-trace2.txt
@@ -656,7 +656,8 @@ The "exec_id" field is a command-unique id and is only useful if the
------------
`"def_param"`::
- This event is generated to log a global parameter.
+ This event is generated to log a global parameter, such as a config
+ setting, command-line flag, or environment variable.
+
------------
{
diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt
index a4f17441ae..de56f9f1ef 100644
--- a/Documentation/technical/commit-graph-format.txt
+++ b/Documentation/technical/commit-graph-format.txt
@@ -17,6 +17,9 @@ metadata, including:
- The parents of the commit, stored using positional references within
the graph file.
+- The Bloom filter of the commit carrying the paths that were changed between
+ the commit and its first parent, if requested.
+
These positional references are stored as unsigned 32-bit integers
corresponding to the array position within the list of commit OIDs. Due
to some special constants we use to track parents, we can store at most
@@ -93,6 +96,33 @@ CHUNK DATA:
positions for the parents until reaching a value with the most-significant
bit on. The other bits correspond to the position of the last parent.
+ Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional]
+ * The ith entry, BIDX[i], stores the number of 8-byte word blocks in all
+ Bloom filters from commit 0 to commit i (inclusive) in lexicographic
+ order. The Bloom filter for the i-th commit spans from BIDX[i-1] to
+ BIDX[i] (plus header length), where BIDX[-1] is 0.
+ * The BIDX chunk is ignored if the BDAT chunk is not present.
+
+ Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional]
+ * It starts with header consisting of three unsigned 32-bit integers:
+ - Version of the hash algorithm being used. We currently only support
+ value 1 which corresponds to the 32-bit version of the murmur3 hash
+ implemented exactly as described in
+ https://en.wikipedia.org/wiki/MurmurHash#Algorithm and the double
+ hashing technique using seed values 0x293ae76f and 0x7e646e2 as
+ described in https://doi.org/10.1007/978-3-540-30494-4_26 "Bloom Filters
+ in Probabilistic Verification"
+ - The number of times a path is hashed and hence the number of bit positions
+ that cumulatively determine whether a file is present in the commit.
+ - The minimum number of bits 'b' per entry in the Bloom filter. If the filter
+ contains 'n' entries, then the filter size is the minimum number of 64-bit
+ words that contain n*b bits.
+ * The rest of the chunk is the concatenation of all the computed Bloom
+ filters for the commits in lexicographic order.
+ * Note: Commits with no changes or more than 512 changes have Bloom filters
+ of length zero.
+ * The BDAT chunk is present if and only if BIDX is present.
+
Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional]
This list of H-byte hashes describe a set of B commit-graph files that
form a commit-graph chain. The graph position for the ith commit in this
diff --git a/Documentation/user-manual.conf b/Documentation/user-manual.conf
index d87294de2f..0148f126dc 100644
--- a/Documentation/user-manual.conf
+++ b/Documentation/user-manual.conf
@@ -9,13 +9,3 @@ tilde=&#126;
[linkgit-inlinemacro]
<ulink url="{target}.html">{target}{0?({0})}</ulink>
-
-ifdef::backend-docbook[]
-# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
-[listingblock]
-<example><title>{title}</title>
-<literallayout class="monospaced">
-|
-</literallayout>
-{title#}</example>
-endif::backend-docbook[]
diff --git a/INSTALL b/INSTALL
index 22c364f34f..9ba33e6a14 100644
--- a/INSTALL
+++ b/INSTALL
@@ -206,9 +206,7 @@ Issues of note:
clone two separate git-htmldocs and git-manpages repositories next
to the clone of git itself.
- It has been reported that docbook-xsl version 1.72 and 1.73 are
- buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
- the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
+ The minimum supported version of docbook-xsl is 1.74.
Users attempting to build the documentation on Cygwin may need to ensure
that the /etc/xml/catalog file looks something like this:
diff --git a/Makefile b/Makefile
index ef1ff2228f..3d3a39fc19 100644
--- a/Makefile
+++ b/Makefile
@@ -282,12 +282,6 @@ all::
# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
# field that counts the on-disk footprint in 512-byte blocks.
#
-# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
-# (not v1.73 or v1.71).
-#
-# Define ASCIIDOC_ROFF if your DocBook XSL does not escape raw roff directives
-# (versions 1.68.1 through v1.72).
-#
# Define GNU_ROFF if your target system uses GNU groff. This forces
# apostrophes to be ASCII so that cut&pasting examples to the shell
# will work.
@@ -616,8 +610,8 @@ SCRIPT_SH += git-web--browse.sh
SCRIPT_LIB += git-mergetool--lib
SCRIPT_LIB += git-parse-remote
SCRIPT_LIB += git-rebase--preserve-merges
-SCRIPT_LIB += git-sh-setup
SCRIPT_LIB += git-sh-i18n
+SCRIPT_LIB += git-sh-setup
SCRIPT_PERL += git-add--interactive.perl
SCRIPT_PERL += git-archimport.perl
@@ -680,14 +674,15 @@ EXTRA_PROGRAMS =
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS += $(EXTRA_PROGRAMS)
+PROGRAM_OBJS += bugreport.o
PROGRAM_OBJS += credential-store.o
PROGRAM_OBJS += daemon.o
PROGRAM_OBJS += fast-import.o
PROGRAM_OBJS += http-backend.o
PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += remote-testsvn.o
PROGRAM_OBJS += sh-i18n--envsubst.o
PROGRAM_OBJS += shell.o
-PROGRAM_OBJS += remote-testsvn.o
# Binary suffix, set to .exe for Windows builds
X =
@@ -695,6 +690,7 @@ X =
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
TEST_BUILTINS_OBJS += test-advise.o
+TEST_BUILTINS_OBJS += test-bloom.o
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
TEST_BUILTINS_OBJS += test-ctype.o
@@ -709,15 +705,16 @@ TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-genrandom.o
TEST_BUILTINS_OBJS += test-genzeros.o
+TEST_BUILTINS_OBJS += test-hash-speed.o
TEST_BUILTINS_OBJS += test-hash.o
TEST_BUILTINS_OBJS += test-hashmap.o
-TEST_BUILTINS_OBJS += test-hash-speed.o
TEST_BUILTINS_OBJS += test-index-version.o
TEST_BUILTINS_OBJS += test-json-writer.o
TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
TEST_BUILTINS_OBJS += test-match-trees.o
TEST_BUILTINS_OBJS += test-mergesort.o
TEST_BUILTINS_OBJS += test-mktemp.o
+TEST_BUILTINS_OBJS += test-oid-array.o
TEST_BUILTINS_OBJS += test-oidmap.o
TEST_BUILTINS_OBJS += test-online-cpus.o
TEST_BUILTINS_OBJS += test-parse-options.o
@@ -738,7 +735,6 @@ TEST_BUILTINS_OBJS += test-run-command.o
TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
TEST_BUILTINS_OBJS += test-serve-v2.o
TEST_BUILTINS_OBJS += test-sha1.o
-TEST_BUILTINS_OBJS += test-sha1-array.o
TEST_BUILTINS_OBJS += test-sha256.o
TEST_BUILTINS_OBJS += test-sigchain.o
TEST_BUILTINS_OBJS += test-strcmp-offset.o
@@ -748,10 +744,10 @@ TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
TEST_BUILTINS_OBJS += test-subprocess.o
TEST_BUILTINS_OBJS += test-trace2.o
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
-TEST_BUILTINS_OBJS += test-xml-encode.o
TEST_BUILTINS_OBJS += test-wildmatch.o
TEST_BUILTINS_OBJS += test-windows-named-pipe.o
TEST_BUILTINS_OBJS += test-write-cache.o
+TEST_BUILTINS_OBJS += test-xml-encode.o
# Do not add more tests here unless they have extra dependencies. Add
# them in TEST_BUILTINS_OBJS above.
@@ -788,10 +784,10 @@ OTHER_PROGRAMS = git$X
# what test wrappers are needed and 'install' will install, in bindir
BINDIR_PROGRAMS_NEED_X += git
-BINDIR_PROGRAMS_NEED_X += git-upload-pack
BINDIR_PROGRAMS_NEED_X += git-receive-pack
-BINDIR_PROGRAMS_NEED_X += git-upload-archive
BINDIR_PROGRAMS_NEED_X += git-shell
+BINDIR_PROGRAMS_NEED_X += git-upload-archive
+BINDIR_PROGRAMS_NEED_X += git-upload-pack
BINDIR_PROGRAMS_NO_X += git-cvsserver
@@ -815,6 +811,7 @@ LIB_FILE = libgit.a
XDIFF_LIB = xdiff/lib.a
VCSSVN_LIB = vcs-svn/lib.a
+GENERATED_H += config-list.h
GENERATED_H += command-list.h
LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \
@@ -831,15 +828,16 @@ LIB_OBJS += advice.o
LIB_OBJS += alias.o
LIB_OBJS += alloc.o
LIB_OBJS += apply.o
-LIB_OBJS += archive.o
LIB_OBJS += archive-tar.o
LIB_OBJS += archive-zip.o
+LIB_OBJS += archive.o
LIB_OBJS += argv-array.o
LIB_OBJS += attr.o
LIB_OBJS += base85.o
LIB_OBJS += bisect.o
LIB_OBJS += blame.o
LIB_OBJS += blob.o
+LIB_OBJS += bloom.o
LIB_OBJS += branch.o
LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o
@@ -849,9 +847,9 @@ LIB_OBJS += checkout.o
LIB_OBJS += color.o
LIB_OBJS += column.o
LIB_OBJS += combine-diff.o
-LIB_OBJS += commit.o
LIB_OBJS += commit-graph.o
LIB_OBJS += commit-reach.o
+LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/terminal.o
LIB_OBJS += config.o
@@ -865,17 +863,17 @@ LIB_OBJS += ctype.o
LIB_OBJS += date.o
LIB_OBJS += decorate.o
LIB_OBJS += delta-islands.o
+LIB_OBJS += diff-delta.o
+LIB_OBJS += diff-lib.o
+LIB_OBJS += diff-no-index.o
+LIB_OBJS += diff.o
LIB_OBJS += diffcore-break.o
LIB_OBJS += diffcore-delta.o
LIB_OBJS += diffcore-order.o
LIB_OBJS += diffcore-pickaxe.o
LIB_OBJS += diffcore-rename.o
-LIB_OBJS += diff-delta.o
-LIB_OBJS += diff-lib.o
-LIB_OBJS += diff-no-index.o
-LIB_OBJS += diff.o
-LIB_OBJS += dir.o
LIB_OBJS += dir-iterator.o
+LIB_OBJS += dir.o
LIB_OBJS += editor.o
LIB_OBJS += entry.o
LIB_OBJS += environment.o
@@ -886,6 +884,7 @@ LIB_OBJS += ewah/ewah_rlw.o
LIB_OBJS += exec-cmd.o
LIB_OBJS += fetch-negotiator.o
LIB_OBJS += fetch-pack.o
+LIB_OBJS += fmt-merge-msg.o
LIB_OBJS += fsck.o
LIB_OBJS += fsmonitor.o
LIB_OBJS += gettext.o
@@ -893,7 +892,6 @@ LIB_OBJS += gpg-interface.o
LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hashmap.o
-LIB_OBJS += linear-assignment.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
@@ -903,9 +901,10 @@ LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
LIB_OBJS += line-log.o
LIB_OBJS += line-range.o
-LIB_OBJS += list-objects.o
-LIB_OBJS += list-objects-filter.o
+LIB_OBJS += linear-assignment.o
LIB_OBJS += list-objects-filter-options.o
+LIB_OBJS += list-objects-filter.o
+LIB_OBJS += list-objects.o
LIB_OBJS += ll-merge.o
LIB_OBJS += lockfile.o
LIB_OBJS += log-tree.o
@@ -914,31 +913,32 @@ LIB_OBJS += mailinfo.o
LIB_OBJS += mailmap.o
LIB_OBJS += match-trees.o
LIB_OBJS += mem-pool.o
-LIB_OBJS += merge.o
LIB_OBJS += merge-blobs.o
LIB_OBJS += merge-recursive.o
+LIB_OBJS += merge.o
LIB_OBJS += mergesort.o
LIB_OBJS += midx.o
LIB_OBJS += name-hash.o
LIB_OBJS += negotiator/default.o
LIB_OBJS += negotiator/skipping.o
-LIB_OBJS += notes.o
LIB_OBJS += notes-cache.o
LIB_OBJS += notes-merge.o
LIB_OBJS += notes-utils.o
+LIB_OBJS += notes.o
LIB_OBJS += object.o
+LIB_OBJS += oid-array.o
LIB_OBJS += oidmap.o
LIB_OBJS += oidset.o
-LIB_OBJS += packfile.o
-LIB_OBJS += pack-bitmap.o
LIB_OBJS += pack-bitmap-write.o
+LIB_OBJS += pack-bitmap.o
LIB_OBJS += pack-check.o
LIB_OBJS += pack-objects.o
LIB_OBJS += pack-revindex.o
LIB_OBJS += pack-write.o
+LIB_OBJS += packfile.o
LIB_OBJS += pager.o
-LIB_OBJS += parse-options.o
LIB_OBJS += parse-options-cb.o
+LIB_OBJS += parse-options.o
LIB_OBJS += patch-delta.o
LIB_OBJS += patch-ids.o
LIB_OBJS += path.o
@@ -951,12 +951,14 @@ LIB_OBJS += progress.o
LIB_OBJS += promisor-remote.o
LIB_OBJS += prompt.o
LIB_OBJS += protocol.o
+LIB_OBJS += prune-packed.o
LIB_OBJS += quote.o
LIB_OBJS += range-diff.o
LIB_OBJS += reachable.o
LIB_OBJS += read-cache.o
-LIB_OBJS += rebase.o
LIB_OBJS += rebase-interactive.o
+LIB_OBJS += rebase.o
+LIB_OBJS += ref-filter.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
LIB_OBJS += refs/files-backend.o
@@ -964,12 +966,12 @@ LIB_OBJS += refs/iterator.o
LIB_OBJS += refs/packed-backend.o
LIB_OBJS += refs/ref-cache.o
LIB_OBJS += refspec.o
-LIB_OBJS += ref-filter.o
LIB_OBJS += remote.o
LIB_OBJS += replace-object.o
LIB_OBJS += repo-settings.o
LIB_OBJS += repository.o
LIB_OBJS += rerere.o
+LIB_OBJS += reset.o
LIB_OBJS += resolve-undo.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
@@ -978,9 +980,8 @@ LIB_OBJS += sequencer.o
LIB_OBJS += serve.o
LIB_OBJS += server-info.o
LIB_OBJS += setup.o
-LIB_OBJS += sha1-array.o
-LIB_OBJS += sha1-lookup.o
LIB_OBJS += sha1-file.o
+LIB_OBJS += sha1-lookup.o
LIB_OBJS += sha1-name.o
LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
@@ -990,9 +991,9 @@ LIB_OBJS += stable-qsort.o
LIB_OBJS += strbuf.o
LIB_OBJS += streaming.o
LIB_OBJS += string-list.o
-LIB_OBJS += submodule.o
-LIB_OBJS += submodule-config.o
LIB_OBJS += sub-process.o
+LIB_OBJS += submodule-config.o
+LIB_OBJS += submodule.o
LIB_OBJS += symlinks.o
LIB_OBJS += tag.o
LIB_OBJS += tempfile.o
@@ -1011,11 +1012,11 @@ LIB_OBJS += trace2/tr2_tgt_normal.o
LIB_OBJS += trace2/tr2_tgt_perf.o
LIB_OBJS += trace2/tr2_tls.o
LIB_OBJS += trailer.o
-LIB_OBJS += transport.o
LIB_OBJS += transport-helper.o
+LIB_OBJS += transport.o
LIB_OBJS += tree-diff.o
-LIB_OBJS += tree.o
LIB_OBJS += tree-walk.o
+LIB_OBJS += tree.o
LIB_OBJS += unpack-trees.o
LIB_OBJS += upload-pack.o
LIB_OBJS += url.o
@@ -1055,9 +1056,9 @@ BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o
BUILTIN_OBJS += builtin/clone.o
BUILTIN_OBJS += builtin/column.o
+BUILTIN_OBJS += builtin/commit-graph.o
BUILTIN_OBJS += builtin/commit-tree.o
BUILTIN_OBJS += builtin/commit.o
-BUILTIN_OBJS += builtin/commit-graph.o
BUILTIN_OBJS += builtin/config.o
BUILTIN_OBJS += builtin/count-objects.o
BUILTIN_OBJS += builtin/credential.o
@@ -1088,13 +1089,13 @@ BUILTIN_OBJS += builtin/ls-remote.o
BUILTIN_OBJS += builtin/ls-tree.o
BUILTIN_OBJS += builtin/mailinfo.o
BUILTIN_OBJS += builtin/mailsplit.o
-BUILTIN_OBJS += builtin/merge.o
BUILTIN_OBJS += builtin/merge-base.o
BUILTIN_OBJS += builtin/merge-file.o
BUILTIN_OBJS += builtin/merge-index.o
BUILTIN_OBJS += builtin/merge-ours.o
BUILTIN_OBJS += builtin/merge-recursive.o
BUILTIN_OBJS += builtin/merge-tree.o
+BUILTIN_OBJS += builtin/merge.o
BUILTIN_OBJS += builtin/mktag.o
BUILTIN_OBJS += builtin/mktree.o
BUILTIN_OBJS += builtin/multi-pack-index.o
@@ -1114,9 +1115,9 @@ BUILTIN_OBJS += builtin/read-tree.o
BUILTIN_OBJS += builtin/rebase.o
BUILTIN_OBJS += builtin/receive-pack.o
BUILTIN_OBJS += builtin/reflog.o
-BUILTIN_OBJS += builtin/remote.o
BUILTIN_OBJS += builtin/remote-ext.o
BUILTIN_OBJS += builtin/remote-fd.o
+BUILTIN_OBJS += builtin/remote.o
BUILTIN_OBJS += builtin/repack.o
BUILTIN_OBJS += builtin/replace.o
BUILTIN_OBJS += builtin/rerere.o
@@ -1358,17 +1359,22 @@ ifdef NO_CURL
else
ifdef CURLDIR
# Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
- BASIC_CFLAGS += -I$(CURLDIR)/include
+ CURL_CFLAGS = -I$(CURLDIR)/include
CURL_LIBCURL = -L$(CURLDIR)/$(lib) $(CC_LD_DYNPATH)$(CURLDIR)/$(lib)
else
+ CURL_CFLAGS =
CURL_LIBCURL =
endif
-ifdef CURL_LDFLAGS
+ ifndef CURL_LDFLAGS
+ CURL_LDFLAGS = $(eval CURL_LDFLAGS := $$(shell $$(CURL_CONFIG) --libs))$(CURL_LDFLAGS)
+ endif
CURL_LIBCURL += $(CURL_LDFLAGS)
-else
- CURL_LIBCURL += $(shell $(CURL_CONFIG) --libs)
-endif
+
+ ifndef CURL_CFLAGS
+ CURL_CFLAGS = $(eval CURL_CFLAGS := $$(shell $$(CURL_CONFIG) --cflags))$(CURL_CFLAGS)
+ endif
+ BASIC_CFLAGS += $(CURL_CFLAGS)
REMOTE_CURL_PRIMARY = git-remote-http$X
REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X
@@ -2133,7 +2139,7 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
help.sp help.s help.o: command-list.h
-builtin/help.sp builtin/help.s builtin/help.o: command-list.h GIT-PREFIX
+builtin/help.sp builtin/help.s builtin/help.o: config-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)"' \
@@ -2153,6 +2159,12 @@ $(BUILT_INS): git$X
ln -s $< $@ 2>/dev/null || \
cp $< $@
+config-list.h: generate-configlist.sh
+
+config-list.h:
+ $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \
+ >$@+ && mv $@+ $@
+
command-list.h: generate-cmdlist.sh command-list.txt
command-list.h: $(wildcard Documentation/git*.txt) Documentation/*config.txt Documentation/config/*.txt
@@ -2335,16 +2347,16 @@ reconfigure config.mak.autogen: config.status
endif
XDIFF_OBJS += xdiff/xdiffi.o
-XDIFF_OBJS += xdiff/xprepare.o
-XDIFF_OBJS += xdiff/xutils.o
XDIFF_OBJS += xdiff/xemit.o
+XDIFF_OBJS += xdiff/xhistogram.o
XDIFF_OBJS += xdiff/xmerge.o
XDIFF_OBJS += xdiff/xpatience.o
-XDIFF_OBJS += xdiff/xhistogram.o
+XDIFF_OBJS += xdiff/xprepare.o
+XDIFF_OBJS += xdiff/xutils.o
+VCSSVN_OBJS += vcs-svn/fast_export.o
VCSSVN_OBJS += vcs-svn/line_buffer.o
VCSSVN_OBJS += vcs-svn/sliding_window.o
-VCSSVN_OBJS += vcs-svn/fast_export.o
VCSSVN_OBJS += vcs-svn/svndiff.o
VCSSVN_OBJS += vcs-svn/svndump.o
@@ -2455,6 +2467,10 @@ endif
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+git-bugreport$X: bugreport.o GIT-LDFLAGS $(GITLIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+ $(LIBS)
+
git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(IMAP_SEND_LDFLAGS) $(LIBS)
@@ -2786,7 +2802,7 @@ $(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE
.PHONY: sparse $(SP_OBJ)
sparse: $(SP_OBJ)
-EXCEPT_HDRS := command-list.h unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := command-list.h config-list.h unicode-width.h compat/% xdiff/%
ifndef GCRYPT_SHA256
EXCEPT_HDRS += sha256/gcrypt.h
endif
@@ -2808,7 +2824,7 @@ hdr-check: $(HCO)
style:
git clang-format --style file --diff --extensions c,h
-check: command-list.h
+check: config-list.h command-list.h
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
@@ -3152,9 +3168,10 @@ endif
#
ALL_COMMANDS = $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS)
ALL_COMMANDS += git
+ALL_COMMANDS += git-citool
+ALL_COMMANDS += git-gui
ALL_COMMANDS += gitk
ALL_COMMANDS += gitweb
-ALL_COMMANDS += git-gui git-citool
.PHONY: check-docs
check-docs::
diff --git a/README.md b/README.md
index 9d4564c8aa..eb8115e6b0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://dev.azure.com/git/git/_apis/build/status/git.git)](https://dev.azure.com/git/git/_build/latest?definitionId=11)
+[![Build status](https://github.com/git/git/workflows/CI/PR/badge.svg)](https://github.com/git/git/actions?query=branch%3Amaster+event%3Apush)
Git - fast, scalable, distributed revision control system
=========================================================
diff --git a/add-interactive.c b/add-interactive.c
index 4a9bf85cac..29cd2fe020 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -9,6 +9,7 @@
#include "lockfile.h"
#include "dir.h"
#include "run-command.h"
+#include "prompt.h"
static void init_color(struct repository *r, struct add_i_state *s,
const char *slot_name, char *dst,
@@ -289,13 +290,12 @@ static ssize_t list_and_choose(struct add_i_state *s,
fputs(singleton ? "> " : ">> ", stdout);
fflush(stdout);
- if (strbuf_getline(&input, stdin) == EOF) {
+ if (git_read_line_interactively(&input) == EOF) {
putchar('\n');
if (immediate)
res = LIST_AND_CHOOSE_QUIT;
break;
}
- strbuf_trim(&input);
if (!input.len)
break;
diff --git a/add-patch.c b/add-patch.c
index d8dafa8168..d8bfe379be 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -7,6 +7,7 @@
#include "color.h"
#include "diff.h"
#include "compat/terminal.h"
+#include "prompt.h"
enum prompt_mode_type {
PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
@@ -1158,9 +1159,8 @@ static int read_single_character(struct add_p_state *s)
return res;
}
- if (strbuf_getline(&s->answer, stdin) == EOF)
+ if (git_read_line_interactively(&s->answer) == EOF)
return EOF;
- strbuf_trim_trailing_newline(&s->answer);
return 0;
}
diff --git a/archive-tar.c b/archive-tar.c
index 5a77701a15..5ceec3684b 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -364,7 +364,7 @@ static struct archiver **tar_filters;
static int nr_tar_filters;
static int alloc_tar_filters;
-static struct archiver *find_tar_filter(const char *name, int len)
+static struct archiver *find_tar_filter(const char *name, size_t len)
{
int i;
for (i = 0; i < nr_tar_filters; i++) {
@@ -380,7 +380,7 @@ static int tar_filter_config(const char *var, const char *value, void *data)
struct archiver *ar;
const char *name;
const char *type;
- int namelen;
+ size_t namelen;
if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name)
return 0;
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 675c3a43c9..0000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,558 +0,0 @@
-variables:
- Agent.Source.Git.ShallowFetchDepth: 1
-
-jobs:
-- job: windows_build
- displayName: Windows Build
- condition: succeeded()
- pool:
- vmImage: windows-latest
- timeoutInMinutes: 240
- steps:
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
- cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
- }
- displayName: 'Mount test-cache'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - powershell: |
- $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds"
- $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
- $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl
- (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip")
- Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force
- Remove-Item git-sdk-64-minimal.zip
-
- # Let Git ignore the SDK and the test-cache
- "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude"
- displayName: 'Download git-sdk-64-minimal'
- - powershell: |
- & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- ci/make-test-artifacts.sh artifacts
- "@
- if (!$?) { exit(1) }
- displayName: Build
- env:
- HOME: $(Build.SourcesDirectory)
- MSYSTEM: MINGW64
- DEVELOPER: 1
- NO_PERL: 1
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Pipeline Artifact: test artifacts'
- inputs:
- artifactName: 'windows-artifacts'
- targetPath: '$(Build.SourcesDirectory)\artifacts'
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Pipeline Artifact: git-sdk-64-minimal'
- inputs:
- artifactName: 'git-sdk-64-minimal'
- targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal'
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
- }
- displayName: 'Unmount test-cache'
- condition: true
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
-
-- job: windows_test
- displayName: Windows Test
- dependsOn: windows_build
- condition: succeeded()
- pool:
- vmImage: windows-latest
- timeoutInMinutes: 240
- strategy:
- parallel: 10
- steps:
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
- cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
- }
- displayName: 'Mount test-cache'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: DownloadPipelineArtifact@0
- displayName: 'Download Pipeline Artifact: test artifacts'
- inputs:
- artifactName: 'windows-artifacts'
- targetPath: '$(Build.SourcesDirectory)'
- - task: DownloadPipelineArtifact@0
- displayName: 'Download Pipeline Artifact: git-sdk-64-minimal'
- inputs:
- artifactName: 'git-sdk-64-minimal'
- targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal'
- - powershell: |
- & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- test -f artifacts.tar.gz || {
- echo No test artifacts found\; skipping >&2
- exit 0
- }
- tar xf artifacts.tar.gz || exit 1
-
- # Let Git ignore the SDK and the test-cache
- printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude
-
- ci/run-test-slice.sh `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE || {
- ci/print-test-failures.sh
- exit 1
- }
- "@
- if (!$?) { exit(1) }
- displayName: 'Test (parallel)'
- env:
- HOME: $(Build.SourcesDirectory)
- MSYSTEM: MINGW64
- NO_SVN_TESTS: 1
- GIT_TEST_SKIP_REBASE_P: 1
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
- }
- displayName: 'Unmount test-cache'
- condition: true
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'windows'
- platform: Windows
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: vs_build
- displayName: Visual Studio Build
- condition: succeeded()
- pool:
- vmImage: windows-latest
- timeoutInMinutes: 240
- steps:
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
- cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
- }
- displayName: 'Mount test-cache'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - powershell: |
- $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds"
- $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
- $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl
- (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip")
- Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force
- Remove-Item git-sdk-64-minimal.zip
-
- # Let Git ignore the SDK and the test-cache
- "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude"
- displayName: 'Download git-sdk-64-minimal'
- - powershell: |
- & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- make NDEBUG=1 DEVELOPER=1 vcxproj
- "@
- if (!$?) { exit(1) }
- displayName: Generate Visual Studio Solution
- env:
- HOME: $(Build.SourcesDirectory)
- MSYSTEM: MINGW64
- DEVELOPER: 1
- NO_PERL: 1
- GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
- - powershell: |
- $urlbase = "https://dev.azure.com/git/git/_apis/build/builds"
- $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
- $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl
- (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip")
- Expand-Archive compat.zip -DestinationPath . -Force
- Remove-Item compat.zip
- displayName: 'Download vcpkg artifacts'
- - task: MSBuild@1
- inputs:
- solution: git.sln
- platform: x64
- configuration: Release
- maximumCpuCount: 4
- msbuildArguments: /p:PlatformToolset=v142
- - powershell: |
- & compat\vcbuild\vcpkg_copy_dlls.bat release
- if (!$?) { exit(1) }
- & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
- mkdir -p artifacts &&
- eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)\"
- "@
- if (!$?) { exit(1) }
- displayName: Bundle artifact tar
- env:
- HOME: $(Build.SourcesDirectory)
- MSYSTEM: MINGW64
- DEVELOPER: 1
- NO_PERL: 1
- MSVC: 1
- VCPKG_ROOT: $(Build.SourcesDirectory)\compat\vcbuild\vcpkg
- - powershell: |
- $tag = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-tag.txt").content
- $version = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-version.txt").content
- $url = "https://github.com/git-for-windows/git/releases/download/${tag}/PortableGit-${version}-64-bit.7z.exe"
- (New-Object Net.WebClient).DownloadFile($url,"PortableGit.exe")
- & .\PortableGit.exe -y -oartifacts\PortableGit
- # Wait until it is unpacked
- while (-not @(Remove-Item -ErrorAction SilentlyContinue PortableGit.exe; $?)) { sleep 1 }
- displayName: Download & extract portable Git
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Pipeline Artifact: MSVC test artifacts'
- inputs:
- artifactName: 'vs-artifacts'
- targetPath: '$(Build.SourcesDirectory)\artifacts'
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
- }
- displayName: 'Unmount test-cache'
- condition: true
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
-
-- job: vs_test
- displayName: Visual Studio Test
- dependsOn: vs_build
- condition: succeeded()
- pool:
- vmImage: windows-latest
- timeoutInMinutes: 240
- strategy:
- parallel: 10
- steps:
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
- cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
- }
- displayName: 'Mount test-cache'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: DownloadPipelineArtifact@0
- displayName: 'Download Pipeline Artifact: VS test artifacts'
- inputs:
- artifactName: 'vs-artifacts'
- targetPath: '$(Build.SourcesDirectory)'
- - powershell: |
- & PortableGit\git-cmd.exe --command=usr\bin\bash.exe -lc @"
- test -f artifacts.tar.gz || {
- echo No test artifacts found\; skipping >&2
- exit 0
- }
- tar xf artifacts.tar.gz || exit 1
-
- # Let Git ignore the SDK and the test-cache
- printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude
-
- cd t &&
- PATH=\"`$PWD/helper:`$PATH\" &&
- test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \
- `$(test-tool.exe path-utils slice-tests \
- `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh)
- "@
- if (!$?) { exit(1) }
- displayName: 'Test (parallel)'
- env:
- HOME: $(Build.SourcesDirectory)
- MSYSTEM: MINGW64
- NO_SVN_TESTS: 1
- GIT_TEST_SKIP_REBASE_P: 1
- - powershell: |
- if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
- cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
- }
- displayName: 'Unmount test-cache'
- condition: true
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'vs'
- platform: Windows
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-vs-test-artifacts
-
-- job: linux_clang
- displayName: linux-clang
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- sudo apt-get update &&
- sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin &&
-
- export CC=clang || exit 1
-
- ci/install-dependencies.sh || exit 1
- ci/run-build-and-tests.sh || {
- ci/print-test-failures.sh
- exit 1
- }
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-build-and-tests.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'linux-clang'
- platform: Linux
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: linux_gcc
- displayName: linux-gcc
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test &&
- sudo apt-get update &&
- sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2 language-pack-is git-svn gcc-8 || exit 1
-
- ci/install-dependencies.sh || exit 1
- ci/run-build-and-tests.sh || {
- ci/print-test-failures.sh
- exit 1
- }
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-build-and-tests.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'linux-gcc'
- platform: Linux
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: osx_clang
- displayName: osx-clang
- condition: succeeded()
- pool:
- vmImage: macOS-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- export CC=clang
-
- ci/install-dependencies.sh || exit 1
- ci/run-build-and-tests.sh || {
- ci/print-test-failures.sh
- exit 1
- }
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-build-and-tests.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'osx-clang'
- platform: macOS
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: osx_gcc
- displayName: osx-gcc
- condition: succeeded()
- pool:
- vmImage: macOS-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- ci/install-dependencies.sh || exit 1
- ci/run-build-and-tests.sh || {
- ci/print-test-failures.sh
- exit 1
- }
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-build-and-tests.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'osx-gcc'
- platform: macOS
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: gettext_poison
- displayName: GETTEXT_POISON
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- sudo apt-get update &&
- sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev &&
-
- export jobname=GETTEXT_POISON || exit 1
-
- ci/run-build-and-tests.sh || {
- ci/print-test-failures.sh
- exit 1
- }
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-build-and-tests.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'gettext-poison'
- platform: Linux
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: linux32
- displayName: Linux32
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- res=0
- sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" bash -lxc ci/run-linux32-docker.sh || res=1
-
- sudo chmod a+r t/out/TEST-*.xml
- test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || res=1
- exit $res
- displayName: 'ci/run-linux32-docker.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
- - task: PublishTestResults@2
- displayName: 'Publish Test Results **/TEST-*.xml'
- inputs:
- mergeTestResults: true
- testRunTitle: 'linux32'
- platform: Linux
- publishRunAttachments: false
- condition: succeededOrFailed()
- - task: PublishBuildArtifacts@1
- displayName: 'Publish trash directories of failed tests'
- condition: failed()
- inputs:
- PathtoPublish: t/failed-test-artifacts
- ArtifactName: failed-test-artifacts
-
-- job: static_analysis
- displayName: StaticAnalysis
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- sudo apt-get update &&
- sudo apt-get install -y coccinelle libcurl4-openssl-dev libssl-dev libexpat-dev gettext &&
-
- export jobname=StaticAnalysis &&
-
- ci/run-static-analysis.sh || exit 1
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
- displayName: 'ci/run-static-analysis.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
-
-- job: documentation
- displayName: Documentation
- condition: succeeded()
- pool:
- vmImage: ubuntu-latest
- steps:
- - bash: |
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1
-
- sudo apt-get update &&
- sudo apt-get install -y asciidoc xmlto asciidoctor docbook-xsl-ns &&
-
- export ALREADY_HAVE_ASCIIDOCTOR=yes. &&
- export jobname=Documentation &&
-
- ci/test-documentation.sh || exit 1
-
- test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1
- displayName: 'ci/test-documentation.sh'
- env:
- GITFILESHAREPWD: $(gitfileshare.pwd)
diff --git a/bisect.c b/bisect.c
index 9154f810f7..d5e830410f 100644
--- a/bisect.c
+++ b/bisect.c
@@ -10,7 +10,7 @@
#include "run-command.h"
#include "log-tree.h"
#include "bisect.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "argv-array.h"
#include "commit-slab.h"
#include "commit-reach.h"
@@ -473,7 +473,7 @@ static void read_bisect_paths(struct argv_array *array)
fclose(fp);
}
-static char *join_sha1_array_hex(struct oid_array *array, char delim)
+static char *join_oid_array_hex(struct oid_array *array, char delim)
{
struct strbuf joined_hexs = STRBUF_INIT;
int i;
@@ -765,7 +765,7 @@ static enum bisect_error handle_bad_merge_base(void)
{
if (is_expected_rev(current_bad_oid)) {
char *bad_hex = oid_to_hex(current_bad_oid);
- char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+ char *good_hex = join_oid_array_hex(&good_revs, ' ');
if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
fprintf(stderr, _("The merge base %s is bad.\n"
"This means the bug has been fixed "
@@ -796,7 +796,7 @@ static void handle_skipped_merge_base(const struct object_id *mb)
{
char *mb_hex = oid_to_hex(mb);
char *bad_hex = oid_to_hex(current_bad_oid);
- char *good_hex = join_sha1_array_hex(&good_revs, ' ');
+ char *good_hex = join_oid_array_hex(&good_revs, ' ');
warning(_("the merge base between %s and [%s] "
"must be skipped.\n"
diff --git a/blame.c b/blame.c
index 29770e5c81..da7e28800e 100644
--- a/blame.c
+++ b/blame.c
@@ -9,6 +9,8 @@
#include "blame.h"
#include "alloc.h"
#include "commit-slab.h"
+#include "bloom.h"
+#include "commit-graph.h"
define_commit_slab(blame_suspects, struct blame_origin *);
static struct blame_suspects blame_suspects;
@@ -1246,13 +1248,74 @@ static int fill_blob_sha1_and_mode(struct repository *r,
return -1;
}
+struct blame_bloom_data {
+ /*
+ * Changed-path Bloom filter keys. These can help prevent
+ * computing diffs against first parents, but we need to
+ * expand the list as code is moved or files are renamed.
+ */
+ struct bloom_filter_settings *settings;
+ struct bloom_key **keys;
+ int nr;
+ int alloc;
+};
+
+static int bloom_count_queries = 0;
+static int bloom_count_no = 0;
+static int maybe_changed_path(struct repository *r,
+ struct blame_origin *origin,
+ struct blame_bloom_data *bd)
+{
+ int i;
+ struct bloom_filter *filter;
+
+ if (!bd)
+ return 1;
+
+ if (origin->commit->generation == GENERATION_NUMBER_INFINITY)
+ return 1;
+
+ filter = get_bloom_filter(r, origin->commit, 0);
+
+ if (!filter)
+ return 1;
+
+ bloom_count_queries++;
+ for (i = 0; i < bd->nr; i++) {
+ if (bloom_filter_contains(filter,
+ bd->keys[i],
+ bd->settings))
+ return 1;
+ }
+
+ bloom_count_no++;
+ return 0;
+}
+
+static void add_bloom_key(struct blame_bloom_data *bd,
+ const char *path)
+{
+ if (!bd)
+ return;
+
+ if (bd->nr >= bd->alloc) {
+ bd->alloc *= 2;
+ REALLOC_ARRAY(bd->keys, bd->alloc);
+ }
+
+ bd->keys[bd->nr] = xmalloc(sizeof(struct bloom_key));
+ fill_bloom_key(path, strlen(path), bd->keys[bd->nr], bd->settings);
+ bd->nr++;
+}
+
/*
* We have an origin -- check if the same path exists in the
* parent and return an origin structure to represent it.
*/
static struct blame_origin *find_origin(struct repository *r,
struct commit *parent,
- struct blame_origin *origin)
+ struct blame_origin *origin,
+ struct blame_bloom_data *bd)
{
struct blame_origin *porigin;
struct diff_options diff_opts;
@@ -1286,10 +1349,18 @@ static struct blame_origin *find_origin(struct repository *r,
if (is_null_oid(&origin->commit->object.oid))
do_diff_cache(get_commit_tree_oid(parent), &diff_opts);
- else
- diff_tree_oid(get_commit_tree_oid(parent),
- get_commit_tree_oid(origin->commit),
- "", &diff_opts);
+ else {
+ int compute_diff = 1;
+ if (origin->commit->parents &&
+ !oidcmp(&parent->object.oid,
+ &origin->commit->parents->item->object.oid))
+ compute_diff = maybe_changed_path(r, origin, bd);
+
+ if (compute_diff)
+ diff_tree_oid(get_commit_tree_oid(parent),
+ get_commit_tree_oid(origin->commit),
+ "", &diff_opts);
+ }
diffcore_std(&diff_opts);
if (!diff_queued_diff.nr) {
@@ -1341,7 +1412,8 @@ static struct blame_origin *find_origin(struct repository *r,
*/
static struct blame_origin *find_rename(struct repository *r,
struct commit *parent,
- struct blame_origin *origin)
+ struct blame_origin *origin,
+ struct blame_bloom_data *bd)
{
struct blame_origin *porigin = NULL;
struct diff_options diff_opts;
@@ -1366,6 +1438,7 @@ static struct blame_origin *find_rename(struct repository *r,
struct diff_filepair *p = diff_queued_diff.queue[i];
if ((p->status == 'R' || p->status == 'C') &&
!strcmp(p->two->path, origin->path)) {
+ add_bloom_key(bd, p->one->path);
porigin = get_origin(parent, p->one->path);
oidcpy(&porigin->blob_oid, &p->one->oid);
porigin->mode = p->one->mode;
@@ -2332,6 +2405,11 @@ static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *bl
#define MAXSG 16
+typedef struct blame_origin *(*blame_find_alg)(struct repository *,
+ struct commit *,
+ struct blame_origin *,
+ struct blame_bloom_data *);
+
static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, int opt)
{
struct rev_info *revs = sb->revs;
@@ -2356,8 +2434,7 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) {
- struct blame_origin *(*find)(struct repository *, struct commit *, struct blame_origin *);
- find = pass ? find_rename : find_origin;
+ blame_find_alg find = pass ? find_rename : find_origin;
for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
i < num_sg && sg;
@@ -2369,7 +2446,7 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
continue;
if (parse_commit(p))
continue;
- porigin = find(sb->repo, p, origin);
+ porigin = find(sb->repo, p, origin, sb->bloom_data);
if (!porigin)
continue;
if (oideq(&porigin->blob_oid, &origin->blob_oid)) {
@@ -2809,3 +2886,45 @@ struct blame_entry *blame_entry_prepend(struct blame_entry *head,
blame_origin_incref(o);
return new_head;
}
+
+void setup_blame_bloom_data(struct blame_scoreboard *sb,
+ const char *path)
+{
+ struct blame_bloom_data *bd;
+
+ if (!sb->repo->objects->commit_graph)
+ return;
+
+ if (!sb->repo->objects->commit_graph->bloom_filter_settings)
+ return;
+
+ bd = xmalloc(sizeof(struct blame_bloom_data));
+
+ bd->settings = sb->repo->objects->commit_graph->bloom_filter_settings;
+
+ bd->alloc = 4;
+ bd->nr = 0;
+ ALLOC_ARRAY(bd->keys, bd->alloc);
+
+ add_bloom_key(bd, path);
+
+ sb->bloom_data = bd;
+}
+
+void cleanup_scoreboard(struct blame_scoreboard *sb)
+{
+ if (sb->bloom_data) {
+ int i;
+ for (i = 0; i < sb->bloom_data->nr; i++) {
+ free(sb->bloom_data->keys[i]->hashes);
+ free(sb->bloom_data->keys[i]);
+ }
+ free(sb->bloom_data->keys);
+ FREE_AND_NULL(sb->bloom_data);
+
+ trace2_data_intmax("blame", sb->repo,
+ "bloom/queries", bloom_count_queries);
+ trace2_data_intmax("blame", sb->repo,
+ "bloom/response-no", bloom_count_no);
+ }
+}
diff --git a/blame.h b/blame.h
index 089b181ff2..b6bbee4147 100644
--- a/blame.h
+++ b/blame.h
@@ -100,6 +100,8 @@ struct blame_entry {
int unblamable;
};
+struct blame_bloom_data;
+
/*
* The current state of the blame assignment.
*/
@@ -156,6 +158,7 @@ struct blame_scoreboard {
void(*found_guilty_entry)(struct blame_entry *, void *);
void *found_guilty_entry_data;
+ struct blame_bloom_data *bloom_data;
};
/*
@@ -180,6 +183,9 @@ void init_scoreboard(struct blame_scoreboard *sb);
void setup_scoreboard(struct blame_scoreboard *sb,
const char *path,
struct blame_origin **orig);
+void setup_blame_bloom_data(struct blame_scoreboard *sb,
+ const char *path);
+void cleanup_scoreboard(struct blame_scoreboard *sb);
struct blame_entry *blame_entry_prepend(struct blame_entry *head,
long start, long end,
diff --git a/bloom.c b/bloom.c
new file mode 100644
index 0000000000..dd9bab9bbd
--- /dev/null
+++ b/bloom.c
@@ -0,0 +1,276 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "hashmap.h"
+#include "commit-graph.h"
+#include "commit.h"
+
+define_commit_slab(bloom_filter_slab, struct bloom_filter);
+
+struct bloom_filter_slab bloom_filters;
+
+struct pathmap_hash_entry {
+ struct hashmap_entry entry;
+ const char path[FLEX_ARRAY];
+};
+
+static uint32_t rotate_left(uint32_t value, int32_t count)
+{
+ uint32_t mask = 8 * sizeof(uint32_t) - 1;
+ count &= mask;
+ return ((value << count) | (value >> ((-count) & mask)));
+}
+
+static inline unsigned char get_bitmask(uint32_t pos)
+{
+ return ((unsigned char)1) << (pos & (BITS_PER_WORD - 1));
+}
+
+static int load_bloom_filter_from_graph(struct commit_graph *g,
+ struct bloom_filter *filter,
+ struct commit *c)
+{
+ uint32_t lex_pos, start_index, end_index;
+
+ while (c->graph_pos < g->num_commits_in_base)
+ g = g->base_graph;
+
+ /* The commit graph commit 'c' lives in doesn't carry bloom filters. */
+ if (!g->chunk_bloom_indexes)
+ return 0;
+
+ lex_pos = c->graph_pos - g->num_commits_in_base;
+
+ end_index = get_be32(g->chunk_bloom_indexes + 4 * lex_pos);
+
+ if (lex_pos > 0)
+ start_index = get_be32(g->chunk_bloom_indexes + 4 * (lex_pos - 1));
+ else
+ start_index = 0;
+
+ filter->len = end_index - start_index;
+ filter->data = (unsigned char *)(g->chunk_bloom_data +
+ sizeof(unsigned char) * start_index +
+ BLOOMDATA_CHUNK_HEADER_SIZE);
+
+ return 1;
+}
+
+/*
+ * Calculate the murmur3 32-bit hash value for the given data
+ * using the given seed.
+ * Produces a uniformly distributed hash value.
+ * Not considered to be cryptographically secure.
+ * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm
+ */
+uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len)
+{
+ const uint32_t c1 = 0xcc9e2d51;
+ const uint32_t c2 = 0x1b873593;
+ const uint32_t r1 = 15;
+ const uint32_t r2 = 13;
+ const uint32_t m = 5;
+ const uint32_t n = 0xe6546b64;
+ int i;
+ uint32_t k1 = 0;
+ const char *tail;
+
+ int len4 = len / sizeof(uint32_t);
+
+ uint32_t k;
+ for (i = 0; i < len4; i++) {
+ uint32_t byte1 = (uint32_t)data[4*i];
+ uint32_t byte2 = ((uint32_t)data[4*i + 1]) << 8;
+ uint32_t byte3 = ((uint32_t)data[4*i + 2]) << 16;
+ uint32_t byte4 = ((uint32_t)data[4*i + 3]) << 24;
+ k = byte1 | byte2 | byte3 | byte4;
+ k *= c1;
+ k = rotate_left(k, r1);
+ k *= c2;
+
+ seed ^= k;
+ seed = rotate_left(seed, r2) * m + n;
+ }
+
+ tail = (data + len4 * sizeof(uint32_t));
+
+ switch (len & (sizeof(uint32_t) - 1)) {
+ case 3:
+ k1 ^= ((uint32_t)tail[2]) << 16;
+ /*-fallthrough*/
+ case 2:
+ k1 ^= ((uint32_t)tail[1]) << 8;
+ /*-fallthrough*/
+ case 1:
+ k1 ^= ((uint32_t)tail[0]) << 0;
+ k1 *= c1;
+ k1 = rotate_left(k1, r1);
+ k1 *= c2;
+ seed ^= k1;
+ break;
+ }
+
+ seed ^= (uint32_t)len;
+ seed ^= (seed >> 16);
+ seed *= 0x85ebca6b;
+ seed ^= (seed >> 13);
+ seed *= 0xc2b2ae35;
+ seed ^= (seed >> 16);
+
+ return seed;
+}
+
+void fill_bloom_key(const char *data,
+ size_t len,
+ struct bloom_key *key,
+ const struct bloom_filter_settings *settings)
+{
+ int i;
+ const uint32_t seed0 = 0x293ae76f;
+ const uint32_t seed1 = 0x7e646e2c;
+ const uint32_t hash0 = murmur3_seeded(seed0, data, len);
+ const uint32_t hash1 = murmur3_seeded(seed1, data, len);
+
+ key->hashes = (uint32_t *)xcalloc(settings->num_hashes, sizeof(uint32_t));
+ for (i = 0; i < settings->num_hashes; i++)
+ key->hashes[i] = hash0 + i * hash1;
+}
+
+void add_key_to_filter(const struct bloom_key *key,
+ struct bloom_filter *filter,
+ const struct bloom_filter_settings *settings)
+{
+ int i;
+ uint64_t mod = filter->len * BITS_PER_WORD;
+
+ for (i = 0; i < settings->num_hashes; i++) {
+ uint64_t hash_mod = key->hashes[i] % mod;
+ uint64_t block_pos = hash_mod / BITS_PER_WORD;
+
+ filter->data[block_pos] |= get_bitmask(hash_mod);
+ }
+}
+
+void init_bloom_filters(void)
+{
+ init_bloom_filter_slab(&bloom_filters);
+}
+
+struct bloom_filter *get_bloom_filter(struct repository *r,
+ struct commit *c,
+ int compute_if_not_present)
+{
+ struct bloom_filter *filter;
+ struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+ int i;
+ struct diff_options diffopt;
+ int max_changes = 512;
+
+ if (bloom_filters.slab_size == 0)
+ return NULL;
+
+ filter = bloom_filter_slab_at(&bloom_filters, c);
+
+ if (!filter->data) {
+ load_commit_graph_info(r, c);
+ if (c->graph_pos != COMMIT_NOT_FROM_GRAPH &&
+ r->objects->commit_graph->chunk_bloom_indexes) {
+ if (load_bloom_filter_from_graph(r->objects->commit_graph, filter, c))
+ return filter;
+ else
+ return NULL;
+ }
+ }
+
+ if (filter->data || !compute_if_not_present)
+ return filter;
+
+ repo_diff_setup(r, &diffopt);
+ diffopt.flags.recursive = 1;
+ diffopt.detect_rename = 0;
+ diffopt.max_changes = max_changes;
+ diff_setup_done(&diffopt);
+
+ if (c->parents)
+ diff_tree_oid(&c->parents->item->object.oid, &c->object.oid, "", &diffopt);
+ else
+ diff_tree_oid(NULL, &c->object.oid, "", &diffopt);
+ diffcore_std(&diffopt);
+
+ if (diff_queued_diff.nr <= max_changes) {
+ struct hashmap pathmap;
+ struct pathmap_hash_entry *e;
+ struct hashmap_iter iter;
+ hashmap_init(&pathmap, NULL, NULL, 0);
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ const char *path = diff_queued_diff.queue[i]->two->path;
+
+ /*
+ * Add each leading directory of the changed file, i.e. for
+ * 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so
+ * the Bloom filter could be used to speed up commands like
+ * 'git log dir/subdir', too.
+ *
+ * Note that directories are added without the trailing '/'.
+ */
+ do {
+ char *last_slash = strrchr(path, '/');
+
+ FLEX_ALLOC_STR(e, path, path);
+ hashmap_entry_init(&e->entry, strhash(path));
+ hashmap_add(&pathmap, &e->entry);
+
+ if (!last_slash)
+ last_slash = (char*)path;
+ *last_slash = '\0';
+
+ } while (*path);
+
+ diff_free_filepair(diff_queued_diff.queue[i]);
+ }
+
+ filter->len = (hashmap_get_size(&pathmap) * settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ filter->data = xcalloc(filter->len, sizeof(unsigned char));
+
+ hashmap_for_each_entry(&pathmap, &iter, e, entry) {
+ struct bloom_key key;
+ fill_bloom_key(e->path, strlen(e->path), &key, &settings);
+ add_key_to_filter(&key, filter, &settings);
+ }
+
+ hashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);
+ } else {
+ for (i = 0; i < diff_queued_diff.nr; i++)
+ diff_free_filepair(diff_queued_diff.queue[i]);
+ filter->data = NULL;
+ filter->len = 0;
+ }
+
+ free(diff_queued_diff.queue);
+ DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+ return filter;
+}
+
+int bloom_filter_contains(const struct bloom_filter *filter,
+ const struct bloom_key *key,
+ const struct bloom_filter_settings *settings)
+{
+ int i;
+ uint64_t mod = filter->len * BITS_PER_WORD;
+
+ if (!mod)
+ return -1;
+
+ for (i = 0; i < settings->num_hashes; i++) {
+ uint64_t hash_mod = key->hashes[i] % mod;
+ uint64_t block_pos = hash_mod / BITS_PER_WORD;
+ if (!(filter->data[block_pos] & get_bitmask(hash_mod)))
+ return 0;
+ }
+
+ return 1;
+} \ No newline at end of file
diff --git a/bloom.h b/bloom.h
new file mode 100644
index 0000000000..b935186425
--- /dev/null
+++ b/bloom.h
@@ -0,0 +1,90 @@
+#ifndef BLOOM_H
+#define BLOOM_H
+
+struct commit;
+struct repository;
+
+struct bloom_filter_settings {
+ /*
+ * The version of the hashing technique being used.
+ * We currently only support version = 1 which is
+ * the seeded murmur3 hashing technique implemented
+ * in bloom.c.
+ */
+ uint32_t hash_version;
+
+ /*
+ * The number of times a path is hashed, i.e. the
+ * number of bit positions tht cumulatively
+ * determine whether a path is present in the
+ * Bloom filter.
+ */
+ uint32_t num_hashes;
+
+ /*
+ * The minimum number of bits per entry in the Bloom
+ * filter. If the filter contains 'n' entries, then
+ * filter size is the minimum number of 8-bit words
+ * that contain n*b bits.
+ */
+ uint32_t bits_per_entry;
+};
+
+#define DEFAULT_BLOOM_FILTER_SETTINGS { 1, 7, 10 }
+#define BITS_PER_WORD 8
+#define BLOOMDATA_CHUNK_HEADER_SIZE 3 * sizeof(uint32_t)
+
+/*
+ * A bloom_filter struct represents a data segment to
+ * use when testing hash values. The 'len' member
+ * dictates how many entries are stored in
+ * 'data'.
+ */
+struct bloom_filter {
+ unsigned char *data;
+ size_t len;
+};
+
+/*
+ * A bloom_key represents the k hash values for a
+ * given string. These can be precomputed and
+ * stored in a bloom_key for re-use when testing
+ * against a bloom_filter. The number of hashes is
+ * given by the Bloom filter settings and is the same
+ * for all Bloom filters and keys interacting with
+ * the loaded version of the commit graph file and
+ * the Bloom data chunks.
+ */
+struct bloom_key {
+ uint32_t *hashes;
+};
+
+/*
+ * Calculate the murmur3 32-bit hash value for the given data
+ * using the given seed.
+ * Produces a uniformly distributed hash value.
+ * Not considered to be cryptographically secure.
+ * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm
+ */
+uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len);
+
+void fill_bloom_key(const char *data,
+ size_t len,
+ struct bloom_key *key,
+ const struct bloom_filter_settings *settings);
+
+void add_key_to_filter(const struct bloom_key *key,
+ struct bloom_filter *filter,
+ const struct bloom_filter_settings *settings);
+
+void init_bloom_filters(void);
+
+struct bloom_filter *get_bloom_filter(struct repository *r,
+ struct commit *c,
+ int compute_if_not_present);
+
+int bloom_filter_contains(const struct bloom_filter *filter,
+ const struct bloom_key *key,
+ const struct bloom_filter_settings *settings);
+
+#endif \ No newline at end of file
diff --git a/branch.c b/branch.c
index 579494738a..2d9e7675a6 100644
--- a/branch.c
+++ b/branch.c
@@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r)
unlink(git_path_merge_rr(r));
unlink(git_path_merge_msg(r));
unlink(git_path_merge_mode(r));
+ save_autostash(git_path_merge_autostash(r));
}
void remove_branch_state(struct repository *r, int verbose)
diff --git a/bugreport.c b/bugreport.c
new file mode 100644
index 0000000000..acacca8fef
--- /dev/null
+++ b/bugreport.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "help.h"
+#include "compat/compiler.h"
+
+static void get_system_info(struct strbuf *sys_info)
+{
+ struct utsname uname_info;
+
+ /* get git version from native cmd */
+ strbuf_addstr(sys_info, _("git version:\n"));
+ get_version_info(sys_info, 1);
+
+ /* system call for other version info */
+ strbuf_addstr(sys_info, "uname: ");
+ if (uname(&uname_info))
+ strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
+ strerror(errno),
+ errno);
+ else
+ strbuf_addf(sys_info, "%s %s %s %s\n",
+ uname_info.sysname,
+ uname_info.release,
+ uname_info.version,
+ uname_info.machine);
+
+ strbuf_addstr(sys_info, _("compiler info: "));
+ get_compiler_info(sys_info);
+ strbuf_addstr(sys_info, _("libc info: "));
+ get_libc_info(sys_info);
+}
+
+static const char * const bugreport_usage[] = {
+ N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"),
+ NULL
+};
+
+static int get_bug_template(struct strbuf *template)
+{
+ const char template_text[] = N_(
+"Thank you for filling out a Git bug report!\n"
+"Please answer the following questions to help us understand your issue.\n"
+"\n"
+"What did you do before the bug happened? (Steps to reproduce your issue)\n"
+"\n"
+"What did you expect to happen? (Expected behavior)\n"
+"\n"
+"What happened instead? (Actual behavior)\n"
+"\n"
+"What's different between what you expected and what actually happened?\n"
+"\n"
+"Anything else you want to add:\n"
+"\n"
+"Please review the rest of the bug report below.\n"
+"You can delete any lines you don't wish to share.\n");
+
+ strbuf_addstr(template, _(template_text));
+ return 0;
+}
+
+static void get_header(struct strbuf *buf, const char *title)
+{
+ strbuf_addf(buf, "\n\n[%s]\n", title);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct strbuf report_path = STRBUF_INIT;
+ int report = -1;
+ time_t now = time(NULL);
+ char *option_output = NULL;
+ char *option_suffix = "%Y-%m-%d-%H%M";
+ int nongit_ok = 0;
+ const char *prefix = NULL;
+ const char *user_relative_path = NULL;
+
+ const struct option bugreport_options[] = {
+ OPT_STRING('o', "output-directory", &option_output, N_("path"),
+ N_("specify a destination for the bugreport file")),
+ OPT_STRING('s', "suffix", &option_suffix, N_("format"),
+ N_("specify a strftime format suffix for the filename")),
+ OPT_END()
+ };
+
+ prefix = setup_git_directory_gently(&nongit_ok);
+
+ argc = parse_options(argc, argv, prefix, bugreport_options,
+ bugreport_usage, 0);
+
+ /* Prepare the path to put the result */
+ strbuf_addstr(&report_path,
+ prefix_filename(prefix,
+ option_output ? option_output : ""));
+ strbuf_complete(&report_path, '/');
+
+ strbuf_addstr(&report_path, "git-bugreport-");
+ strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0);
+ strbuf_addstr(&report_path, ".txt");
+
+ switch (safe_create_leading_directories(report_path.buf)) {
+ case SCLD_OK:
+ case SCLD_EXISTS:
+ break;
+ default:
+ die(_("could not create leading directories for '%s'"),
+ report_path.buf);
+ }
+
+ /* Prepare the report contents */
+ get_bug_template(&buffer);
+
+ get_header(&buffer, _("System Info"));
+ get_system_info(&buffer);
+
+ /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
+ report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
+
+ if (report < 0) {
+ UNLEAK(report_path);
+ die(_("couldn't create a new file at '%s'"), report_path.buf);
+ }
+
+ strbuf_write_fd(&buffer, report);
+ close(report);
+
+ /*
+ * We want to print the path relative to the user, but we still need the
+ * path relative to us to give to the editor.
+ */
+ if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
+ user_relative_path = report_path.buf;
+ fprintf(stderr, _("Created new report at '%s'.\n"),
+ user_relative_path);
+
+ UNLEAK(buffer);
+ UNLEAK(report_path);
+ return !!launch_editor(report_path.buf, NULL, NULL);
+}
diff --git a/builtin.h b/builtin.h
index 2b25a80cde..a5ae15bfe5 100644
--- a/builtin.h
+++ b/builtin.h
@@ -94,25 +94,9 @@
* command.
*/
-#define DEFAULT_MERGE_LOG_LEN 20
-
extern const char git_usage_string[];
extern const char git_more_info_string[];
-#define PRUNE_PACKED_DRY_RUN 01
-#define PRUNE_PACKED_VERBOSE 02
-
-void prune_packed_objects(int);
-
-struct fmt_merge_msg_opts {
- unsigned add_title:1,
- credit_people:1;
- int shortlog_len;
-};
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- struct fmt_merge_msg_opts *);
-
/**
* If a built-in has DELAY_PAGER_CONFIG set, the built-in should call this early
* when it wishes to respect the `pager.foo`-config. The `cmd` is the name of
diff --git a/builtin/blame.c b/builtin/blame.c
index bf1cecdf3f..3c13634f27 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1061,6 +1061,14 @@ parse_done:
string_list_clear(&ignore_revs_file_list, 0);
string_list_clear(&ignore_rev_list, 0);
setup_scoreboard(&sb, path, &o);
+
+ /*
+ * Changed-path Bloom filters are disabled when looking
+ * for copies.
+ */
+ if (!(opt & PICKAXE_BLAME_COPY))
+ setup_blame_bloom_data(&sb, path);
+
lno = sb.num_lines;
if (lno && !range_list.nr)
@@ -1164,5 +1172,7 @@ parse_done:
printf("num get patch: %d\n", sb.num_get_patch);
printf("num commits: %d\n", sb.num_commits);
}
+
+ cleanup_scoreboard(&sb);
return 0;
}
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 6ecc8ee6dc..0d03fdac6e 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -12,7 +12,7 @@
#include "userdiff.h"
#include "streaming.h"
#include "tree-walk.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "packfile.h"
#include "object-store.h"
#include "promisor-remote.h"
diff --git a/builtin/clean.c b/builtin/clean.c
index 5abf087e7c..f14c21b863 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -18,6 +18,7 @@
#include "color.h"
#include "pathspec.h"
#include "help.h"
+#include "prompt.h"
static int force = -1; /* unset */
static int interactive;
@@ -420,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
return found;
}
-
/*
* Parse user input, and return choice(s) for menu (menu_stuff).
*
@@ -580,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
clean_get_color(CLEAN_COLOR_RESET));
}
- if (strbuf_getline_lf(&choice, stdin) != EOF) {
- strbuf_trim(&choice);
- } else {
+ if (git_read_line_interactively(&choice) == EOF) {
eof = 1;
break;
}
@@ -662,9 +660,7 @@ static int filter_by_patterns_cmd(void)
clean_print_color(CLEAN_COLOR_PROMPT);
printf(_("Input ignore patterns>> "));
clean_print_color(CLEAN_COLOR_RESET);
- if (strbuf_getline_lf(&confirm, stdin) != EOF)
- strbuf_trim(&confirm);
- else
+ if (git_read_line_interactively(&confirm) == EOF)
putchar('\n');
/* quit filter_by_pattern mode if press ENTER or Ctrl-D */
@@ -760,9 +756,7 @@ static int ask_each_cmd(void)
qname = quote_path_relative(item->string, NULL, &buf);
/* TRANSLATORS: Make sure to keep [y/N] as is */
printf(_("Remove %s [y/N]? "), qname);
- if (strbuf_getline_lf(&confirm, stdin) != EOF) {
- strbuf_trim(&confirm);
- } else {
+ if (git_read_line_interactively(&confirm) == EOF) {
putchar('\n');
eof = 1;
}
@@ -989,12 +983,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (!cache_name_is_other(ent->name, ent->len))
continue;
- if (pathspec.nr)
- matches = dir_path_match(&the_index, ent, &pathspec, 0, NULL);
-
- if (pathspec.nr && !matches)
- continue;
-
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
diff --git a/builtin/clone.c b/builtin/clone.c
index d8b1f413aa..cb48a291ca 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -102,10 +102,10 @@ static struct option builtin_clone_options[] = {
N_("don't use local hardlinks, always copy")),
OPT_BOOL('s', "shared", &option_shared,
N_("setup as shared repository")),
- OPT_ALIAS(0, "recursive", "recurse-submodules"),
{ OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
N_("pathspec"), N_("initialize submodules in the clone"),
PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
+ OPT_ALIAS(0, "recursive", "recurse-submodules"),
OPT_INTEGER('j', "jobs", &max_jobs,
N_("number of submodules cloned in parallel")),
OPT_STRING(0, "template", &option_template, N_("template-directory"),
@@ -643,7 +643,9 @@ static void write_followtags(const struct ref *refs, const char *msg)
continue;
if (ends_with(ref->name, "^{}"))
continue;
- if (!has_object_file(&ref->old_oid))
+ if (!has_object_file_with_flags(&ref->old_oid,
+ OBJECT_INFO_QUICK |
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
continue;
update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
@@ -676,8 +678,7 @@ static void update_remote_refs(const struct ref *refs,
const char *branch_top,
const char *msg,
struct transport *transport,
- int check_connectivity,
- int check_refs_are_promisor_objects_only)
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
@@ -686,8 +687,6 @@ static void update_remote_refs(const struct ref *refs,
opt.transport = transport;
opt.progress = transport->progress;
- opt.check_refs_are_promisor_objects_only =
- !!check_refs_are_promisor_objects_only;
if (check_connected(iterate_ref_map, &rm, &opt))
die(_("remote did not send all necessary objects"));
@@ -1282,7 +1281,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
update_remote_refs(refs, mapped_refs, remote_head_points_at,
branch_top.buf, reflog_msg.buf, transport,
- !is_local, filter_options.choice);
+ !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index d1ab6625f6..15fe60317c 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -9,7 +9,9 @@
static char const * const builtin_commit_graph_usage[] = {
N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
- N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"),
+ N_("git commit-graph write [--object-dir <objdir>] [--append] "
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
+ "[--changed-paths] [--[no-]progress] <split options>"),
NULL
};
@@ -19,7 +21,9 @@ static const char * const builtin_commit_graph_verify_usage[] = {
};
static const char * const builtin_commit_graph_write_usage[] = {
- N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"),
+ N_("git commit-graph write [--object-dir <objdir>] [--append] "
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
+ "[--changed-paths] [--[no-]progress] <split options>"),
NULL
};
@@ -32,6 +36,7 @@ static struct opts_commit_graph {
int split;
int shallow;
int progress;
+ int enable_changed_paths;
} opts;
static struct object_directory *find_odb(struct repository *r,
@@ -114,10 +119,29 @@ static int graph_verify(int argc, const char **argv)
extern int read_replace_refs;
static struct split_commit_graph_opts split_opts;
+static int write_option_parse_split(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum commit_graph_split_flags *flags = opt->value;
+
+ opts.split = 1;
+ if (!arg)
+ return 0;
+
+ if (!strcmp(arg, "no-merge"))
+ *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
+ else if (!strcmp(arg, "replace"))
+ *flags = COMMIT_GRAPH_SPLIT_REPLACE;
+ else
+ die(_("unrecognized --split argument, %s"), arg);
+
+ return 0;
+}
+
static int graph_write(int argc, const char **argv)
{
struct string_list *pack_indexes = NULL;
- struct string_list *commit_hex = NULL;
+ struct oidset commits = OIDSET_INIT;
struct object_directory *odb = NULL;
struct string_list lines;
int result = 0;
@@ -135,15 +159,19 @@ static int graph_write(int argc, const char **argv)
N_("start walk at commits listed by stdin")),
OPT_BOOL(0, "append", &opts.append,
N_("include all commits already in the commit-graph file")),
+ OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
+ N_("enable computation for changed paths")),
OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
- OPT_BOOL(0, "split", &opts.split,
- N_("allow writing an incremental commit-graph file")),
+ OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL,
+ N_("allow writing an incremental commit-graph file"),
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+ write_option_parse_split),
OPT_INTEGER(0, "max-commits", &split_opts.max_commits,
N_("maximum number of commits in a non-base split commit-graph")),
OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
N_("maximum ratio between two levels of a split commit-graph")),
OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time,
- N_("maximum number of commits in a non-base split commit-graph")),
+ N_("only expire files older than a given date-time")),
OPT_END(),
};
@@ -168,6 +196,9 @@ static int graph_write(int argc, const char **argv)
flags |= COMMIT_GRAPH_WRITE_SPLIT;
if (opts.progress)
flags |= COMMIT_GRAPH_WRITE_PROGRESS;
+ if (opts.enable_changed_paths ||
+ git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
+ flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
read_replace_refs = 0;
odb = find_odb(the_repository, opts.obj_dir);
@@ -188,7 +219,20 @@ static int graph_write(int argc, const char **argv)
if (opts.stdin_packs)
pack_indexes = &lines;
if (opts.stdin_commits) {
- commit_hex = &lines;
+ struct string_list_item *item;
+ oidset_init(&commits, lines.nr);
+ for_each_string_list_item(item, &lines) {
+ struct object_id oid;
+ const char *end;
+
+ if (parse_oid_hex(item->string, &oid, &end)) {
+ error(_("unexpected non-hex object ID: "
+ "%s"), item->string);
+ return 1;
+ }
+
+ oidset_insert(&commits, &oid);
+ }
flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS;
}
@@ -197,7 +241,7 @@ static int graph_write(int argc, const char **argv)
if (write_commit_graph(odb,
pack_indexes,
- commit_hex,
+ opts.stdin_commits ? &commits : NULL,
flags,
&split_opts))
result = 1;
diff --git a/builtin/commit.c b/builtin/commit.c
index d3e7781e65..4743ea5a4c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1700,9 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
"new_index file. Check that disk is not full and quota is\n"
"not exceeded, and then \"git restore --staged :/\" to recover."));
- if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
- write_commit_graph_reachable(the_repository->objects->odb, 0, NULL))
- return 1;
+ git_test_write_commit_graph_or_die();
repo_rerere(the_repository, 0);
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
@@ -1721,6 +1719,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
&oid, flags);
}
+ apply_autostash(git_path_merge_autostash(the_repository));
+
UNLEAK(err);
UNLEAK(sb);
return 0;
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index cb9ea79367..802363d0a2 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -109,6 +109,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
struct object *tree1, *tree2;
static struct rev_info *opt = &log_tree_opt;
struct setup_revision_opt s_r_opt;
+ struct userformat_want w;
int read_stdin = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
@@ -127,6 +128,14 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, opt, &s_r_opt);
+ memset(&w, 0, sizeof(w));
+ userformat_find_requirements(NULL, &w);
+
+ if (!opt->show_notes_given && w.notes)
+ opt->show_notes = 1;
+ if (opt->show_notes)
+ load_display_notes(&opt->notes_opt);
+
while (--argc > 0) {
const char *arg = *++argv;
diff --git a/builtin/diff.c b/builtin/diff.c
index 42ac803091..8537b17bd5 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -17,7 +17,7 @@
#include "log-tree.h"
#include "builtin.h"
#include "submodule.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#define DIFF_NO_INDEX_EXPLICIT 1
#define DIFF_NO_INDEX_IMPLICIT 2
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index dc1485c8aa..4771100072 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -3,7 +3,7 @@
#include "fetch-pack.h"
#include "remote.h"
#include "connect.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "protocol.h"
static const char fetch_pack_usage[] =
diff --git a/builtin/fetch.c b/builtin/fetch.c
index bf6bab80fa..1097e1e512 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -908,13 +908,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
if (!connectivity_checked) {
struct check_connected_options opt = CHECK_CONNECTED_INIT;
- if (filter_options.choice)
- /*
- * Since a filter is specified, objects indirectly
- * referenced by refs are allowed to be absent.
- */
- opt.check_refs_are_promisor_objects_only = 1;
-
rm = ref_map;
if (check_connected(iterate_ref_map, &rm, &opt)) {
rc = error(_("%s did not send all necessary objects\n"), url);
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 172dfbd852..48a8699de7 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -1,669 +1,13 @@
#include "builtin.h"
-#include "cache.h"
#include "config.h"
-#include "refs.h"
-#include "object-store.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "tag.h"
-#include "string-list.h"
-#include "branch.h"
#include "fmt-merge-msg.h"
-#include "gpg-interface.h"
-#include "repository.h"
-#include "commit-reach.h"
+#include "parse-options.h"
static const char * const fmt_merge_msg_usage[] = {
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
NULL
};
-static int use_branch_desc;
-
-int fmt_merge_msg_config(const char *key, const char *value, void *cb)
-{
- if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
- int is_bool;
- merge_log_config = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool && merge_log_config < 0)
- return error("%s: negative length %s", key, value);
- if (is_bool && merge_log_config)
- merge_log_config = DEFAULT_MERGE_LOG_LEN;
- } else if (!strcmp(key, "merge.branchdesc")) {
- use_branch_desc = git_config_bool(key, value);
- } else {
- return git_default_config(key, value, cb);
- }
- return 0;
-}
-
-/* merge data per repository where the merged tips came from */
-struct src_data {
- struct string_list branch, tag, r_branch, generic;
- int head_status;
-};
-
-struct origin_data {
- struct object_id oid;
- unsigned is_local_branch:1;
-};
-
-static void init_src_data(struct src_data *data)
-{
- data->branch.strdup_strings = 1;
- data->tag.strdup_strings = 1;
- data->r_branch.strdup_strings = 1;
- data->generic.strdup_strings = 1;
-}
-
-static struct string_list srcs = STRING_LIST_INIT_DUP;
-static struct string_list origins = STRING_LIST_INIT_DUP;
-
-struct merge_parents {
- int alloc, nr;
- struct merge_parent {
- struct object_id given;
- struct object_id commit;
- unsigned char used;
- } *item;
-};
-
-/*
- * I know, I know, this is inefficient, but you won't be pulling and merging
- * hundreds of heads at a time anyway.
- */
-static struct merge_parent *find_merge_parent(struct merge_parents *table,
- struct object_id *given,
- struct object_id *commit)
-{
- int i;
- for (i = 0; i < table->nr; i++) {
- if (given && !oideq(&table->item[i].given, given))
- continue;
- if (commit && !oideq(&table->item[i].commit, commit))
- continue;
- return &table->item[i];
- }
- return NULL;
-}
-
-static void add_merge_parent(struct merge_parents *table,
- struct object_id *given,
- struct object_id *commit)
-{
- if (table->nr && find_merge_parent(table, given, commit))
- return;
- ALLOC_GROW(table->item, table->nr + 1, table->alloc);
- oidcpy(&table->item[table->nr].given, given);
- oidcpy(&table->item[table->nr].commit, commit);
- table->item[table->nr].used = 0;
- table->nr++;
-}
-
-static int handle_line(char *line, struct merge_parents *merge_parents)
-{
- int i, len = strlen(line);
- struct origin_data *origin_data;
- char *src;
- const char *origin, *tag_name;
- struct src_data *src_data;
- struct string_list_item *item;
- int pulling_head = 0;
- struct object_id oid;
- const unsigned hexsz = the_hash_algo->hexsz;
-
- if (len < hexsz + 3 || line[hexsz] != '\t')
- return 1;
-
- if (starts_with(line + hexsz + 1, "not-for-merge"))
- return 0;
-
- if (line[hexsz + 1] != '\t')
- return 2;
-
- i = get_oid_hex(line, &oid);
- if (i)
- return 3;
-
- if (!find_merge_parent(merge_parents, &oid, NULL))
- return 0; /* subsumed by other parents */
-
- origin_data = xcalloc(1, sizeof(struct origin_data));
- oidcpy(&origin_data->oid, &oid);
-
- if (line[len - 1] == '\n')
- line[len - 1] = 0;
- line += hexsz + 2;
-
- /*
- * At this point, line points at the beginning of comment e.g.
- * "branch 'frotz' of git://that/repository.git".
- * Find the repository name and point it with src.
- */
- src = strstr(line, " of ");
- if (src) {
- *src = 0;
- src += 4;
- pulling_head = 0;
- } else {
- src = line;
- pulling_head = 1;
- }
-
- item = unsorted_string_list_lookup(&srcs, src);
- if (!item) {
- item = string_list_append(&srcs, src);
- item->util = xcalloc(1, sizeof(struct src_data));
- init_src_data(item->util);
- }
- src_data = item->util;
-
- if (pulling_head) {
- origin = src;
- src_data->head_status |= 1;
- } else if (skip_prefix(line, "branch ", &origin)) {
- origin_data->is_local_branch = 1;
- string_list_append(&src_data->branch, origin);
- src_data->head_status |= 2;
- } else if (skip_prefix(line, "tag ", &tag_name)) {
- origin = line;
- string_list_append(&src_data->tag, tag_name);
- src_data->head_status |= 2;
- } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
- string_list_append(&src_data->r_branch, origin);
- src_data->head_status |= 2;
- } else {
- origin = src;
- string_list_append(&src_data->generic, line);
- src_data->head_status |= 2;
- }
-
- if (!strcmp(".", src) || !strcmp(src, origin)) {
- int len = strlen(origin);
- if (origin[0] == '\'' && origin[len - 1] == '\'')
- origin = xmemdupz(origin + 1, len - 2);
- } else
- origin = xstrfmt("%s of %s", origin, src);
- if (strcmp(".", src))
- origin_data->is_local_branch = 0;
- string_list_append(&origins, origin)->util = origin_data;
- return 0;
-}
-
-static void print_joined(const char *singular, const char *plural,
- struct string_list *list, struct strbuf *out)
-{
- if (list->nr == 0)
- return;
- if (list->nr == 1) {
- strbuf_addf(out, "%s%s", singular, list->items[0].string);
- } else {
- int i;
- strbuf_addstr(out, plural);
- for (i = 0; i < list->nr - 1; i++)
- strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
- list->items[i].string);
- strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
- }
-}
-
-static void add_branch_desc(struct strbuf *out, const char *name)
-{
- struct strbuf desc = STRBUF_INIT;
-
- if (!read_branch_desc(&desc, name)) {
- const char *bp = desc.buf;
- while (*bp) {
- const char *ep = strchrnul(bp, '\n');
- if (*ep)
- ep++;
- strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
- bp = ep;
- }
- strbuf_complete_line(out);
- }
- strbuf_release(&desc);
-}
-
-#define util_as_integral(elem) ((intptr_t)((elem)->util))
-
-static void record_person_from_buf(int which, struct string_list *people,
- const char *buffer)
-{
- char *name_buf, *name, *name_end;
- struct string_list_item *elem;
- const char *field;
-
- field = (which == 'a') ? "\nauthor " : "\ncommitter ";
- name = strstr(buffer, field);
- if (!name)
- return;
- name += strlen(field);
- name_end = strchrnul(name, '<');
- if (*name_end)
- name_end--;
- while (isspace(*name_end) && name <= name_end)
- name_end--;
- if (name_end < name)
- return;
- name_buf = xmemdupz(name, name_end - name + 1);
-
- elem = string_list_lookup(people, name_buf);
- if (!elem) {
- elem = string_list_insert(people, name_buf);
- elem->util = (void *)0;
- }
- elem->util = (void*)(util_as_integral(elem) + 1);
- free(name_buf);
-}
-
-
-static void record_person(int which, struct string_list *people,
- struct commit *commit)
-{
- const char *buffer = get_commit_buffer(commit, NULL);
- record_person_from_buf(which, people, buffer);
- unuse_commit_buffer(commit, buffer);
-}
-
-static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
-{
- const struct string_list_item *a = a_, *b = b_;
- return util_as_integral(b) - util_as_integral(a);
-}
-
-static void add_people_count(struct strbuf *out, struct string_list *people)
-{
- if (people->nr == 1)
- strbuf_addstr(out, people->items[0].string);
- else if (people->nr == 2)
- strbuf_addf(out, "%s (%d) and %s (%d)",
- people->items[0].string,
- (int)util_as_integral(&people->items[0]),
- people->items[1].string,
- (int)util_as_integral(&people->items[1]));
- else if (people->nr)
- strbuf_addf(out, "%s (%d) and others",
- people->items[0].string,
- (int)util_as_integral(&people->items[0]));
-}
-
-static void credit_people(struct strbuf *out,
- struct string_list *them,
- int kind)
-{
- const char *label;
- const char *me;
-
- if (kind == 'a') {
- label = "By";
- me = git_author_info(IDENT_NO_DATE);
- } else {
- label = "Via";
- me = git_committer_info(IDENT_NO_DATE);
- }
-
- if (!them->nr ||
- (them->nr == 1 &&
- me &&
- skip_prefix(me, them->items->string, &me) &&
- starts_with(me, " <")))
- return;
- strbuf_addf(out, "\n%c %s ", comment_line_char, label);
- add_people_count(out, them);
-}
-
-static void add_people_info(struct strbuf *out,
- struct string_list *authors,
- struct string_list *committers)
-{
- QSORT(authors->items, authors->nr,
- cmp_string_list_util_as_integral);
- QSORT(committers->items, committers->nr,
- cmp_string_list_util_as_integral);
-
- credit_people(out, authors, 'a');
- credit_people(out, committers, 'c');
-}
-
-static void shortlog(const char *name,
- struct origin_data *origin_data,
- struct commit *head,
- struct rev_info *rev,
- struct fmt_merge_msg_opts *opts,
- struct strbuf *out)
-{
- int i, count = 0;
- struct commit *commit;
- struct object *branch;
- struct string_list subjects = STRING_LIST_INIT_DUP;
- struct string_list authors = STRING_LIST_INIT_DUP;
- struct string_list committers = STRING_LIST_INIT_DUP;
- int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
- struct strbuf sb = STRBUF_INIT;
- const struct object_id *oid = &origin_data->oid;
- int limit = opts->shortlog_len;
-
- branch = deref_tag(the_repository, parse_object(the_repository, oid),
- oid_to_hex(oid),
- the_hash_algo->hexsz);
- if (!branch || branch->type != OBJ_COMMIT)
- return;
-
- setup_revisions(0, NULL, rev, NULL);
- add_pending_object(rev, branch, name);
- add_pending_object(rev, &head->object, "^HEAD");
- head->object.flags |= UNINTERESTING;
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
- while ((commit = get_revision(rev)) != NULL) {
- struct pretty_print_context ctx = {0};
-
- if (commit->parents && commit->parents->next) {
- /* do not list a merge but count committer */
- if (opts->credit_people)
- record_person('c', &committers, commit);
- continue;
- }
- if (!count && opts->credit_people)
- /* the 'tip' committer */
- record_person('c', &committers, commit);
- if (opts->credit_people)
- record_person('a', &authors, commit);
- count++;
- if (subjects.nr > limit)
- continue;
-
- format_commit_message(commit, "%s", &sb, &ctx);
- strbuf_ltrim(&sb);
-
- if (!sb.len)
- string_list_append(&subjects,
- oid_to_hex(&commit->object.oid));
- else
- string_list_append_nodup(&subjects,
- strbuf_detach(&sb, NULL));
- }
-
- if (opts->credit_people)
- add_people_info(out, &authors, &committers);
- if (count > limit)
- strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
- else
- strbuf_addf(out, "\n* %s:\n", name);
-
- if (origin_data->is_local_branch && use_branch_desc)
- add_branch_desc(out, name);
-
- for (i = 0; i < subjects.nr; i++)
- if (i >= limit)
- strbuf_addstr(out, " ...\n");
- else
- strbuf_addf(out, " %s\n", subjects.items[i].string);
-
- clear_commit_marks((struct commit *)branch, flags);
- clear_commit_marks(head, flags);
- free_commit_list(rev->commits);
- rev->commits = NULL;
- rev->pending.nr = 0;
-
- string_list_clear(&authors, 0);
- string_list_clear(&committers, 0);
- string_list_clear(&subjects, 0);
-}
-
-static void fmt_merge_msg_title(struct strbuf *out,
- const char *current_branch)
-{
- int i = 0;
- char *sep = "";
-
- strbuf_addstr(out, "Merge ");
- for (i = 0; i < srcs.nr; i++) {
- struct src_data *src_data = srcs.items[i].util;
- const char *subsep = "";
-
- strbuf_addstr(out, sep);
- sep = "; ";
-
- if (src_data->head_status == 1) {
- strbuf_addstr(out, srcs.items[i].string);
- continue;
- }
- if (src_data->head_status == 3) {
- subsep = ", ";
- strbuf_addstr(out, "HEAD");
- }
- if (src_data->branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("branch ", "branches ", &src_data->branch,
- out);
- }
- if (src_data->r_branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("remote-tracking branch ", "remote-tracking branches ",
- &src_data->r_branch, out);
- }
- if (src_data->tag.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("tag ", "tags ", &src_data->tag, out);
- }
- if (src_data->generic.nr) {
- strbuf_addstr(out, subsep);
- print_joined("commit ", "commits ", &src_data->generic,
- out);
- }
- if (strcmp(".", srcs.items[i].string))
- strbuf_addf(out, " of %s", srcs.items[i].string);
- }
-
- if (!strcmp("master", current_branch))
- strbuf_addch(out, '\n');
- else
- strbuf_addf(out, " into %s\n", current_branch);
-}
-
-static void fmt_tag_signature(struct strbuf *tagbuf,
- struct strbuf *sig,
- const char *buf,
- unsigned long len)
-{
- const char *tag_body = strstr(buf, "\n\n");
- if (tag_body) {
- tag_body += 2;
- strbuf_add(tagbuf, tag_body, buf + len - tag_body);
- }
- strbuf_complete_line(tagbuf);
- if (sig->len) {
- strbuf_addch(tagbuf, '\n');
- strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
- }
-}
-
-static void fmt_merge_msg_sigs(struct strbuf *out)
-{
- int i, tag_number = 0, first_tag = 0;
- struct strbuf tagbuf = STRBUF_INIT;
-
- for (i = 0; i < origins.nr; i++) {
- struct object_id *oid = origins.items[i].util;
- enum object_type type;
- unsigned long size, len;
- char *buf = read_object_file(oid, &type, &size);
- struct signature_check sigc = { 0 };
- struct strbuf sig = STRBUF_INIT;
-
- if (!buf || type != OBJ_TAG)
- goto next;
- len = parse_signature(buf, size);
-
- if (size == len)
- ; /* merely annotated */
- else if (check_signature(buf, len, buf + len, size - len, &sigc) &&
- !sigc.gpg_output)
- strbuf_addstr(&sig, "gpg verification failed.\n");
- else
- strbuf_addstr(&sig, sigc.gpg_output);
- signature_check_clear(&sigc);
-
- if (!tag_number++) {
- fmt_tag_signature(&tagbuf, &sig, buf, len);
- first_tag = i;
- } else {
- if (tag_number == 2) {
- struct strbuf tagline = STRBUF_INIT;
- strbuf_addch(&tagline, '\n');
- strbuf_add_commented_lines(&tagline,
- origins.items[first_tag].string,
- strlen(origins.items[first_tag].string));
- strbuf_insert(&tagbuf, 0, tagline.buf,
- tagline.len);
- strbuf_release(&tagline);
- }
- strbuf_addch(&tagbuf, '\n');
- strbuf_add_commented_lines(&tagbuf,
- origins.items[i].string,
- strlen(origins.items[i].string));
- fmt_tag_signature(&tagbuf, &sig, buf, len);
- }
- strbuf_release(&sig);
- next:
- free(buf);
- }
- if (tagbuf.len) {
- strbuf_addch(out, '\n');
- strbuf_addbuf(out, &tagbuf);
- }
- strbuf_release(&tagbuf);
-}
-
-static void find_merge_parents(struct merge_parents *result,
- struct strbuf *in, struct object_id *head)
-{
- struct commit_list *parents;
- struct commit *head_commit;
- int pos = 0, i, j;
-
- parents = NULL;
- while (pos < in->len) {
- int len;
- char *p = in->buf + pos;
- char *newline = strchr(p, '\n');
- const char *q;
- struct object_id oid;
- struct commit *parent;
- struct object *obj;
-
- len = newline ? newline - p : strlen(p);
- pos += len + !!newline;
-
- if (parse_oid_hex(p, &oid, &q) ||
- q[0] != '\t' ||
- q[1] != '\t')
- continue; /* skip not-for-merge */
- /*
- * Do not use get_merge_parent() here; we do not have
- * "name" here and we do not want to contaminate its
- * util field yet.
- */
- obj = parse_object(the_repository, &oid);
- parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
- if (!parent)
- continue;
- commit_list_insert(parent, &parents);
- add_merge_parent(result, &obj->oid, &parent->object.oid);
- }
- head_commit = lookup_commit(the_repository, head);
- if (head_commit)
- commit_list_insert(head_commit, &parents);
- reduce_heads_replace(&parents);
-
- while (parents) {
- struct commit *cmit = pop_commit(&parents);
- for (i = 0; i < result->nr; i++)
- if (oideq(&result->item[i].commit, &cmit->object.oid))
- result->item[i].used = 1;
- }
-
- for (i = j = 0; i < result->nr; i++) {
- if (result->item[i].used) {
- if (i != j)
- result->item[j] = result->item[i];
- j++;
- }
- }
- result->nr = j;
-}
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- struct fmt_merge_msg_opts *opts)
-{
- int i = 0, pos = 0;
- struct object_id head_oid;
- const char *current_branch;
- void *current_branch_to_free;
- struct merge_parents merge_parents;
-
- memset(&merge_parents, 0, sizeof(merge_parents));
-
- /* get current branch */
- current_branch = current_branch_to_free =
- resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
- if (!current_branch)
- die("No current branch");
- if (starts_with(current_branch, "refs/heads/"))
- current_branch += 11;
-
- find_merge_parents(&merge_parents, in, &head_oid);
-
- /* get a line */
- while (pos < in->len) {
- int len;
- char *newline, *p = in->buf + pos;
-
- newline = strchr(p, '\n');
- len = newline ? newline - p : strlen(p);
- pos += len + !!newline;
- i++;
- p[len] = 0;
- if (handle_line(p, &merge_parents))
- die("error in line %d: %.*s", i, len, p);
- }
-
- if (opts->add_title && srcs.nr)
- fmt_merge_msg_title(out, current_branch);
-
- if (origins.nr)
- fmt_merge_msg_sigs(out);
-
- if (opts->shortlog_len) {
- struct commit *head;
- struct rev_info rev;
-
- head = lookup_commit_or_die(&head_oid, "HEAD");
- repo_init_revisions(the_repository, &rev, NULL);
- rev.commit_format = CMIT_FMT_ONELINE;
- rev.ignore_merges = 1;
- rev.limited = 1;
-
- strbuf_complete_line(out);
-
- for (i = 0; i < origins.nr; i++)
- shortlog(origins.items[i].string,
- origins.items[i].util,
- head, &rev, opts, out);
- }
-
- strbuf_complete_line(out);
- free(current_branch_to_free);
- free(merge_parents.item);
- return 0;
-}
-
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
diff --git a/builtin/grep.c b/builtin/grep.c
index 99e2685090..5e150f5825 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -295,6 +295,38 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
return st;
}
+static void grep_source_name(struct grep_opt *opt, const char *filename,
+ int tree_name_len, struct strbuf *out)
+{
+ strbuf_reset(out);
+
+ if (opt->null_following_name) {
+ if (opt->relative && opt->prefix_length) {
+ struct strbuf rel_buf = STRBUF_INIT;
+ const char *rel_name =
+ relative_path(filename + tree_name_len,
+ opt->prefix, &rel_buf);
+
+ if (tree_name_len)
+ strbuf_add(out, filename, tree_name_len);
+
+ strbuf_addstr(out, rel_name);
+ strbuf_release(&rel_buf);
+ } else {
+ strbuf_addstr(out, filename);
+ }
+ return;
+ }
+
+ if (opt->relative && opt->prefix_length)
+ quote_path_relative(filename + tree_name_len, opt->prefix, out);
+ else
+ quote_c_style(filename + tree_name_len, out, NULL, 0);
+
+ if (tree_name_len)
+ strbuf_insert(out, 0, filename, tree_name_len);
+}
+
static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
const char *filename, int tree_name_len,
const char *path)
@@ -302,13 +334,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
struct strbuf pathbuf = STRBUF_INIT;
struct grep_source gs;
- if (opt->relative && opt->prefix_length) {
- quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
- strbuf_insert(&pathbuf, 0, filename, tree_name_len);
- } else {
- strbuf_addstr(&pathbuf, filename);
- }
-
+ grep_source_name(opt, filename, tree_name_len, &pathbuf);
grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid);
strbuf_release(&pathbuf);
@@ -334,11 +360,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct strbuf buf = STRBUF_INIT;
struct grep_source gs;
- if (opt->relative && opt->prefix_length)
- quote_path_relative(filename, opt->prefix, &buf);
- else
- strbuf_addstr(&buf, filename);
-
+ grep_source_name(opt, filename, 0, &buf);
grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
strbuf_release(&buf);
@@ -679,8 +701,6 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
fill_directory(&dir, opt->repo->index, pathspec);
for (i = 0; i < dir.nr; i++) {
- if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL))
- continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
break;
diff --git a/builtin/help.c b/builtin/help.c
index e5590d7787..299206eb57 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -8,6 +8,7 @@
#include "parse-options.h"
#include "run-command.h"
#include "column.h"
+#include "config-list.h"
#include "help.h"
#include "alias.h"
@@ -62,6 +63,91 @@ static const char * const builtin_help_usage[] = {
NULL
};
+struct slot_expansion {
+ const char *prefix;
+ const char *placeholder;
+ void (*fn)(struct string_list *list, const char *prefix);
+ int found;
+};
+
+static void list_config_help(int for_human)
+{
+ struct slot_expansion slot_expansions[] = {
+ { "advice", "*", list_config_advices },
+ { "color.branch", "<slot>", list_config_color_branch_slots },
+ { "color.decorate", "<slot>", list_config_color_decorate_slots },
+ { "color.diff", "<slot>", list_config_color_diff_slots },
+ { "color.grep", "<slot>", list_config_color_grep_slots },
+ { "color.interactive", "<slot>", list_config_color_interactive_slots },
+ { "color.remote", "<slot>", list_config_color_sideband_slots },
+ { "color.status", "<slot>", list_config_color_status_slots },
+ { "fsck", "<msg-id>", list_config_fsck_msg_ids },
+ { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids },
+ { NULL, NULL, NULL }
+ };
+ const char **p;
+ struct slot_expansion *e;
+ struct string_list keys = STRING_LIST_INIT_DUP;
+ int i;
+
+ for (p = config_name_list; *p; p++) {
+ const char *var = *p;
+ struct strbuf sb = STRBUF_INIT;
+
+ for (e = slot_expansions; e->prefix; e++) {
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder);
+ if (!strcasecmp(var, sb.buf)) {
+ e->fn(&keys, e->prefix);
+ e->found++;
+ break;
+ }
+ }
+ strbuf_release(&sb);
+ if (!e->prefix)
+ string_list_append(&keys, var);
+ }
+
+ for (e = slot_expansions; e->prefix; e++)
+ if (!e->found)
+ BUG("slot_expansion %s.%s is not used",
+ e->prefix, e->placeholder);
+
+ string_list_sort(&keys);
+ for (i = 0; i < keys.nr; i++) {
+ const char *var = keys.items[i].string;
+ const char *wildcard, *tag, *cut;
+
+ if (for_human) {
+ puts(var);
+ continue;
+ }
+
+ wildcard = strchr(var, '*');
+ tag = strchr(var, '<');
+
+ if (!wildcard && !tag) {
+ puts(var);
+ continue;
+ }
+
+ 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);
+ }
+ string_list_clear(&keys, 0);
+}
+
static enum help_format parse_help_format(const char *format)
{
if (!strcmp(format, "man"))
@@ -242,7 +328,7 @@ static int add_man_viewer_cmd(const char *name,
static int add_man_viewer_info(const char *var, const char *value)
{
const char *name, *subkey;
- int namelen;
+ size_t namelen;
if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
return 0;
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index d967d188a3..f176dd28c8 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1368,9 +1368,8 @@ static void fix_unresolved_deltas(struct hashfile *f)
continue;
oid_array_append(&to_fetch, &d->oid);
}
- if (to_fetch.nr)
- promisor_remote_get_direct(the_repository,
- to_fetch.oid, to_fetch.nr);
+ promisor_remote_get_direct(the_repository,
+ to_fetch.oid, to_fetch.nr);
oid_array_clear(&to_fetch);
}
diff --git a/builtin/log.c b/builtin/log.c
index 83a4a6188e..bef7403d5e 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -46,6 +46,7 @@ static int default_abbrev_commit;
static int default_show_root = 1;
static int default_follow;
static int default_show_signature;
+static int default_encode_email_headers = 1;
static int decoration_style;
static int decoration_given;
static int use_mailmap_config = 1;
@@ -151,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
rev->show_signature = default_show_signature;
+ rev->encode_email_headers = default_encode_email_headers;
rev->diffopt.flags.allow_textconv = 1;
if (default_date_mode)
@@ -164,15 +166,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
int quiet = 0, source = 0, mailmap;
static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+ static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;
static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
struct decoration_filter decoration_filter = {&decorate_refs_include,
- &decorate_refs_exclude};
+ &decorate_refs_exclude,
+ &decorate_refs_exclude_config};
static struct revision_sources revision_sources;
const struct option builtin_log_options[] = {
OPT__QUIET(&quiet, N_("suppress diff output")),
OPT_BOOL(0, "source", &source, N_("show source")),
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+ OPT_ALIAS(0, "mailmap", "use-mailmap"),
OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
N_("pattern"), N_("only decorate refs that match <pattern>")),
OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
@@ -236,7 +241,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
}
if (decoration_style) {
+ const struct string_list *config_exclude =
+ repo_config_get_value_multi(the_repository,
+ "log.excludeDecoration");
+
+ if (config_exclude) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, config_exclude)
+ string_list_append(&decorate_refs_exclude_config,
+ item->string);
+ }
+
rev->show_decorations = 1;
+
load_ref_decorations(&decoration_filter, decoration_style);
}
@@ -438,6 +455,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
return git_config_string(&fmt_pretty, var, value);
if (!strcmp(var, "format.subjectprefix"))
return git_config_string(&fmt_patch_subject_prefix, var, value);
+ if (!strcmp(var, "format.encodeemailheaders")) {
+ default_encode_email_headers = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "log.abbrevcommit")) {
default_abbrev_commit = git_config_bool(var, value);
return 0;
@@ -1719,6 +1740,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.show_notes = show_notes;
memcpy(&rev.notes_opt, &notes_opt, sizeof(notes_opt));
rev.commit_format = CMIT_FMT_EMAIL;
+ rev.encode_email_headers = default_encode_email_headers;
rev.expand_tabs_in_log_default = 0;
rev.verbose_header = 1;
rev.diff = 1;
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index f069a028ce..b87c22ac24 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -128,8 +128,9 @@ static void show_dir_entry(const struct index_state *istate,
if (len > ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
- if (!dir_path_match(istate, ent, &pathspec, len, ps_matched))
- return;
+ /* If ps_matches is non-NULL, figure out which pathspec(s) match. */
+ if (ps_matched)
+ dir_path_match(istate, ent, &pathspec, len, ps_matched);
fputs(tag, stdout);
write_eolinfo(istate, NULL, ent->name);
diff --git a/builtin/merge.c b/builtin/merge.c
index d127d2225f..97066a5632 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -40,6 +40,7 @@
#include "branch.h"
#include "commit-reach.h"
#include "wt-status.h"
+#include "commit-graph.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -82,6 +83,7 @@ static int show_progress = -1;
static int default_to_upstream = 1;
static int signoff;
static const char *sign_commit;
+static int autostash;
static int no_verify;
static struct strategy all_strategy[] = {
@@ -286,6 +288,7 @@ static struct option builtin_merge_options[] = {
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_AUTOSTASH(&autostash),
OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")),
OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
@@ -475,6 +478,7 @@ static void finish(struct commit *head_commit,
/* Run a post-merge hook */
run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+ apply_autostash(git_path_merge_autostash(the_repository));
strbuf_release(&reflog_message);
}
@@ -597,10 +601,12 @@ static void parse_branch_merge_options(char *bmo)
static int git_merge_config(const char *k, const char *v, void *cb)
{
int status;
+ const char *str;
- if (branch && starts_with(k, "branch.") &&
- starts_with(k + 7, branch) &&
- !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ if (branch &&
+ skip_prefix(k, "branch.", &str) &&
+ skip_prefix(str, branch, &str) &&
+ !strcmp(str, ".mergeoptions")) {
free(branch_mergeoptions);
branch_mergeoptions = xstrdup(v);
return 0;
@@ -634,6 +640,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return 0;
} else if (!strcmp(k, "gpg.mintrustlevel")) {
check_trust_level = 0;
+ } else if (!strcmp(k, "merge.autostash")) {
+ autostash = git_config_bool(k, v);
+ return 0;
}
status = fmt_merge_msg_config(k, v, cb);
@@ -1281,6 +1290,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (abort_current_merge) {
int nargc = 2;
const char *nargv[] = {"reset", "--merge", NULL};
+ struct strbuf stash_oid = STRBUF_INIT;
if (orig_argc != 2)
usage_msg_opt(_("--abort expects no arguments"),
@@ -1289,8 +1299,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!file_exists(git_path_merge_head(the_repository)))
die(_("There is no merge to abort (MERGE_HEAD missing)."));
+ if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
+ READ_ONELINER_SKIP_IF_EMPTY))
+ unlink(git_path_merge_autostash(the_repository));
+
/* Invoke 'git reset --merge' */
ret = cmd_reset(nargc, nargv, prefix);
+
+ if (stash_oid.len)
+ apply_autostash_oid(stash_oid.buf);
+
+ strbuf_release(&stash_oid);
goto done;
}
@@ -1513,6 +1532,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
goto done;
}
+ if (autostash)
+ create_autostash(the_repository,
+ git_path_merge_autostash(the_repository),
+ "merge");
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
@@ -1579,6 +1602,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
+ if (autostash)
+ create_autostash(the_repository,
+ git_path_merge_autostash(the_repository),
+ "merge");
+
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
@@ -1673,9 +1701,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
head_commit);
}
- if (squash)
+ if (squash) {
finish(head_commit, remoteheads, NULL, NULL);
- else
+
+ git_test_write_commit_graph_or_die();
+ } else
write_merge_state(remoteheads);
if (merge_was_ok)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index dc7c58ce3f..fdd18c7ccb 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -26,7 +26,7 @@
#include "pack-bitmap.h"
#include "delta-islands.h"
#include "reachable.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "argv-array.h"
#include "list.h"
#include "packfile.h"
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index 48c5e78e33..b7b9281a8c 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -1,54 +1,12 @@
#include "builtin.h"
-#include "cache.h"
-#include "progress.h"
#include "parse-options.h"
-#include "packfile.h"
-#include "object-store.h"
+#include "prune-packed.h"
static const char * const prune_packed_usage[] = {
N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
NULL
};
-static struct progress *progress;
-
-static int prune_subdir(unsigned int nr, const char *path, void *data)
-{
- int *opts = data;
- display_progress(progress, nr + 1);
- if (!(*opts & PRUNE_PACKED_DRY_RUN))
- rmdir(path);
- return 0;
-}
-
-static int prune_object(const struct object_id *oid, const char *path,
- void *data)
-{
- int *opts = data;
-
- if (!has_object_pack(oid))
- return 0;
-
- if (*opts & PRUNE_PACKED_DRY_RUN)
- printf("rm -f %s\n", path);
- else
- unlink_or_warn(path);
- return 0;
-}
-
-void prune_packed_objects(int opts)
-{
- if (opts & PRUNE_PACKED_VERBOSE)
- progress = start_delayed_progress(_("Removing duplicate objects"), 256);
-
- for_each_loose_file_in_objdir(get_object_directory(),
- prune_object, NULL, prune_subdir, &opts);
-
- /* Ensure we show 100% before finishing progress */
- display_progress(progress, 256);
- stop_progress(&progress);
-}
-
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
diff --git a/builtin/prune.c b/builtin/prune.c
index 2b76872ad2..fd9acc7222 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -6,6 +6,7 @@
#include "reachable.h"
#include "parse-options.h"
#include "progress.h"
+#include "prune-packed.h"
#include "object-store.h"
static const char * const prune_usage[] = {
diff --git a/builtin/pull.c b/builtin/pull.c
index e42665b681..f1fa6db74e 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -12,7 +12,7 @@
#include "parse-options.h"
#include "exec-cmd.h"
#include "run-command.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "remote.h"
#include "dir.h"
#include "rebase.h"
@@ -110,6 +110,7 @@ static char *opt_ipv4;
static char *opt_ipv6;
static int opt_show_forced_updates = -1;
static char *set_upstream;
+static struct argv_array opt_fetch = ARGV_ARRAY_INIT;
static struct option pull_options[] = {
/* Shared options */
@@ -163,7 +164,7 @@ static struct option pull_options[] = {
N_("verify that the named commit has a valid GPG signature"),
PARSE_OPT_NOARG),
OPT_BOOL(0, "autostash", &opt_autostash,
- N_("automatically stash/stash pop before and after rebase")),
+ N_("automatically stash/stash pop before and after")),
OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
N_("merge strategy to use"),
0),
@@ -207,6 +208,15 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
N_("deepen history of shallow clone"),
0),
+ OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
+ N_("deepen history of shallow repository based on time"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"),
+ N_("deepen history of shallow clone, excluding rev"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
+ N_("deepen history of shallow clone"),
+ 0),
OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
N_("convert to a complete repository"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG),
@@ -216,12 +226,19 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
N_("specify fetch refmap"),
PARSE_OPT_NONEG),
+ OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
+ N_("server-specific"),
+ N_("option to transmit"),
+ 0),
OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL,
N_("use IPv4 addresses only"),
PARSE_OPT_NOARG),
OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL,
N_("use IPv6 addresses only"),
PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
+ N_("report that we have only objects reachable from this object"),
+ 0),
OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
N_("check for forced-updates on all updated branches")),
OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
@@ -567,6 +584,7 @@ static int run_fetch(const char *repo, const char **refspecs)
argv_array_push(&args, "--no-show-forced-updates");
if (set_upstream)
argv_array_push(&args, set_upstream);
+ argv_array_pushv(&args, opt_fetch.argv);
if (repo) {
argv_array_push(&args, repo);
@@ -677,6 +695,10 @@ static int run_merge(void)
argv_array_pushv(&args, opt_strategy_opts.argv);
if (opt_gpg_sign)
argv_array_push(&args, opt_gpg_sign);
+ if (opt_autostash == 0)
+ argv_array_push(&args, "--no-autostash");
+ else if (opt_autostash == 1)
+ argv_array_push(&args, "--autostash");
if (opt_allow_unrelated_histories > 0)
argv_array_push(&args, "--allow-unrelated-histories");
@@ -924,9 +946,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (get_oid("HEAD", &orig_head))
oidclr(&orig_head);
- if (!opt_rebase && opt_autostash != -1)
- die(_("--[no-]autostash option is only valid with --rebase."));
-
autostash = config_autostash;
if (opt_rebase) {
if (opt_autostash != -1)
@@ -992,6 +1011,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (opt_rebase) {
int ret = 0;
+ int ran_ff = 0;
if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head))
@@ -1008,10 +1028,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (is_descendant_of(merge_head, list)) {
/* we can fast-forward this without invoking rebase */
opt_ff = "--ff-only";
+ ran_ff = 1;
ret = run_merge();
}
}
- ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
+ if (!ran_ff)
+ ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
diff --git a/builtin/push.c b/builtin/push.c
index 6dbf0f0bb7..59c8acb556 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -340,6 +340,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
{
int err;
unsigned int reject_reasons;
+ char *anon_url = transport_anonymize_url(transport->url);
transport_set_verbosity(transport, verbosity, progress);
transport->family = family;
@@ -357,18 +358,19 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
}
if (verbosity > 0)
- fprintf(stderr, _("Pushing to %s\n"), transport->url);
+ fprintf(stderr, _("Pushing to %s\n"), anon_url);
trace2_region_enter("push", "transport_push", the_repository);
err = transport_push(the_repository, transport,
rs, flags, &reject_reasons);
trace2_region_leave("push", "transport_push", the_repository);
if (err != 0) {
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
- error(_("failed to push some refs to '%s'"), transport->url);
+ error(_("failed to push some refs to '%s'"), anon_url);
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET));
}
err |= transport_disconnect(transport);
+ free(anon_url);
if (!err)
return 0;
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 27a07d4e78..8cbfc5106e 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -27,6 +27,9 @@
#include "branch.h"
#include "sequencer.h"
#include "rebase-interactive.h"
+#include "reset.h"
+
+#define DEFAULT_REFLOG_ACTION "rebase"
static char const * const builtin_rebase_usage[] = {
N_("git rebase [-i] [options] [--exec <cmd>] "
@@ -85,6 +88,7 @@ struct rebase_options {
const char *action;
int signoff;
int allow_rerere_autoupdate;
+ int keep_empty;
int autosquash;
char *gpg_sign_opt;
int autostash;
@@ -95,11 +99,13 @@ struct rebase_options {
struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
int use_legacy_rebase;
+ int reapply_cherry_picks;
};
#define REBASE_OPTIONS_INIT { \
.type = REBASE_UNSPECIFIED, \
.empty = EMPTY_UNSPECIFIED, \
+ .keep_empty = 1, \
.default_backend = "merge", \
.flags = REBASE_NO_QUIET, \
.git_am_opts = ARGV_ARRAY_INIT, \
@@ -379,11 +385,13 @@ static int run_sequencer_rebase(struct rebase_options *opts,
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
+ flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
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;
switch (command) {
case ACTION_NONE: {
@@ -442,6 +450,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
return ret;
}
+static void imply_merge(struct rebase_options *opts, const char *option);
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
int unset)
{
@@ -449,10 +458,8 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
BUG_ON_OPT_ARG(arg);
- /*
- * If we ever want to remap --keep-empty to --empty=keep, insert:
- * opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
- */
+ imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
+ opts->keep_empty = !unset;
opts->type = REBASE_MERGE;
return 0;
}
@@ -471,7 +478,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
REBASE_FORCE),
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
- N_("(DEPRECATED) keep empty commits"),
+ 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,
@@ -559,7 +566,7 @@ static void imply_merge(struct rebase_options *opts, const char *option)
{
switch (opts->type) {
case REBASE_APPLY:
- die(_("%s requires an interactive rebase"), option);
+ die(_("%s requires the merge backend"), option);
break;
case REBASE_MERGE:
case REBASE_PRESERVE_MERGES:
@@ -586,15 +593,6 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o
return path.buf;
}
-/* Read one file, then strip line endings */
-static int read_one(const char *path, struct strbuf *buf)
-{
- if (strbuf_read_file(buf, path, 0) < 0)
- return error_errno(_("could not read '%s'"), path);
- strbuf_trim_trailing_newline(buf);
- return 0;
-}
-
/* Initialize the rebase options from the state directory. */
static int read_basic_state(struct rebase_options *opts)
{
@@ -602,8 +600,10 @@ static int read_basic_state(struct rebase_options *opts)
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
- if (read_one(state_dir_path("head-name", opts), &head_name) ||
- read_one(state_dir_path("onto", opts), &buf))
+ if (!read_oneliner(&head_name, state_dir_path("head-name", opts),
+ READ_ONELINER_WARN_MISSING) ||
+ !read_oneliner(&buf, state_dir_path("onto", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
opts->head_name = starts_with(head_name.buf, "refs/") ?
xstrdup(head_name.buf) : NULL;
@@ -619,9 +619,11 @@ static int read_basic_state(struct rebase_options *opts)
*/
strbuf_reset(&buf);
if (file_exists(state_dir_path("orig-head", opts))) {
- if (read_one(state_dir_path("orig-head", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("orig-head", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
- } else if (read_one(state_dir_path("head", opts), &buf))
+ } else if (!read_oneliner(&buf, state_dir_path("head", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
if (get_oid(buf.buf, &opts->orig_head))
return error(_("invalid orig-head: '%s'"), buf.buf);
@@ -641,8 +643,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("allow_rerere_autoupdate", opts),
- &buf))
+ if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
if (!strcmp(buf.buf, "--rerere-autoupdate"))
opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE;
@@ -655,8 +657,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("gpg_sign_opt", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("gpg_sign_opt", opts),
- &buf))
+ if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->gpg_sign_opt);
opts->gpg_sign_opt = xstrdup(buf.buf);
@@ -664,7 +666,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("strategy", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("strategy", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("strategy", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->strategy);
opts->strategy = xstrdup(buf.buf);
@@ -672,7 +675,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("strategy_opts", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("strategy_opts", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->strategy_opts);
opts->strategy_opts = xstrdup(buf.buf);
@@ -715,51 +719,6 @@ static int rebase_write_basic_state(struct rebase_options *opts)
return 0;
}
-static int apply_autostash(struct rebase_options *opts)
-{
- const char *path = state_dir_path("autostash", opts);
- struct strbuf autostash = STRBUF_INIT;
- struct child_process stash_apply = CHILD_PROCESS_INIT;
-
- if (!file_exists(path))
- return 0;
-
- if (read_one(path, &autostash))
- return error(_("Could not read '%s'"), path);
- /* Ensure that the hash is not mistaken for a number */
- strbuf_addstr(&autostash, "^0");
- argv_array_pushl(&stash_apply.args,
- "stash", "apply", autostash.buf, NULL);
- stash_apply.git_cmd = 1;
- stash_apply.no_stderr = stash_apply.no_stdout =
- stash_apply.no_stdin = 1;
- if (!run_command(&stash_apply))
- printf(_("Applied autostash.\n"));
- else {
- struct argv_array args = ARGV_ARRAY_INIT;
- int res = 0;
-
- argv_array_pushl(&args,
- "stash", "store", "-m", "autostash", "-q",
- autostash.buf, NULL);
- if (run_command_v_opt(args.argv, RUN_GIT_CMD))
- res = error(_("Cannot store %s"), autostash.buf);
- argv_array_clear(&args);
- strbuf_release(&autostash);
- if (res)
- return res;
-
- fprintf(stderr,
- _("Applying autostash resulted in conflicts.\n"
- "Your changes are safe in the stash.\n"
- "You can run \"git stash pop\" or \"git stash drop\" "
- "at any time.\n"));
- }
-
- strbuf_release(&autostash);
- return 0;
-}
-
static int finish_rebase(struct rebase_options *opts)
{
struct strbuf dir = STRBUF_INIT;
@@ -767,7 +726,7 @@ static int finish_rebase(struct rebase_options *opts)
int ret = 0;
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
- apply_autostash(opts);
+ apply_autostash(state_dir_path("autostash", opts));
close_object_store(the_repository->objects);
/*
* We ignore errors in 'gc --auto', since the
@@ -812,144 +771,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)
}
}
-#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
-
-#define RESET_HEAD_DETACH (1<<0)
-#define RESET_HEAD_HARD (1<<1)
-#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
-#define RESET_HEAD_REFS_ONLY (1<<3)
-#define RESET_ORIG_HEAD (1<<4)
-
-static int reset_head(struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head)
-{
- unsigned detach_head = flags & RESET_HEAD_DETACH;
- unsigned reset_hard = flags & RESET_HEAD_HARD;
- unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
- unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
- unsigned update_orig_head = flags & RESET_ORIG_HEAD;
- struct object_id head_oid;
- struct tree_desc desc[2] = { { NULL }, { NULL } };
- struct lock_file lock = LOCK_INIT;
- struct unpack_trees_options unpack_tree_opts;
- struct tree *tree;
- const char *reflog_action;
- struct strbuf msg = STRBUF_INIT;
- size_t prefix_len;
- struct object_id *orig = NULL, oid_orig,
- *old_orig = NULL, oid_old_orig;
- int ret = 0, nr = 0;
-
- if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
- BUG("Not a fully qualified branch: '%s'", switch_to_branch);
-
- if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
- ret = -1;
- goto leave_reset_head;
- }
-
- if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
- ret = error(_("could not determine HEAD revision"));
- goto leave_reset_head;
- }
-
- if (!oid)
- oid = &head_oid;
-
- if (refs_only)
- goto reset_head_refs;
-
- memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
- setup_unpack_trees_porcelain(&unpack_tree_opts, action);
- unpack_tree_opts.head_idx = 1;
- unpack_tree_opts.src_index = the_repository->index;
- unpack_tree_opts.dst_index = the_repository->index;
- unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
- unpack_tree_opts.update = 1;
- unpack_tree_opts.merge = 1;
- init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
- if (!detach_head)
- unpack_tree_opts.reset = 1;
-
- if (repo_read_index_unmerged(the_repository) < 0) {
- ret = error(_("could not read index"));
- goto leave_reset_head;
- }
-
- if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) {
- ret = error(_("failed to find tree of %s"),
- oid_to_hex(&head_oid));
- goto leave_reset_head;
- }
-
- if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) {
- ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
- goto leave_reset_head;
- }
-
- if (unpack_trees(nr, desc, &unpack_tree_opts)) {
- ret = -1;
- goto leave_reset_head;
- }
-
- tree = parse_tree_indirect(oid);
- prime_cache_tree(the_repository, the_repository->index, tree);
-
- if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
- ret = error(_("could not write index"));
- goto leave_reset_head;
- }
-
-reset_head_refs:
- reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
- strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
- prefix_len = msg.len;
-
- if (update_orig_head) {
- if (!get_oid("ORIG_HEAD", &oid_old_orig))
- old_orig = &oid_old_orig;
- if (!get_oid("HEAD", &oid_orig)) {
- orig = &oid_orig;
- if (!reflog_orig_head) {
- strbuf_addstr(&msg, "updating ORIG_HEAD");
- reflog_orig_head = msg.buf;
- }
- update_ref(reflog_orig_head, "ORIG_HEAD", orig,
- old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
- } else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
- }
-
- if (!reflog_head) {
- strbuf_setlen(&msg, prefix_len);
- strbuf_addstr(&msg, "updating HEAD");
- reflog_head = msg.buf;
- }
- if (!switch_to_branch)
- ret = update_ref(reflog_head, "HEAD", oid, orig,
- detach_head ? REF_NO_DEREF : 0,
- UPDATE_REFS_MSG_ON_ERR);
- else {
- ret = update_ref(reflog_head, switch_to_branch, oid,
- NULL, 0, UPDATE_REFS_MSG_ON_ERR);
- if (!ret)
- ret = create_symref("HEAD", switch_to_branch,
- reflog_head);
- }
- if (run_hook)
- run_hook_le(NULL, "post-checkout",
- oid_to_hex(orig ? orig : &null_oid),
- oid_to_hex(oid), "1", NULL);
-
-leave_reset_head:
- strbuf_release(&msg);
- rollback_lock_file(&lock);
- while (nr)
- free((void *)desc[--nr].buffer);
- return ret;
-}
-
static int move_to_original_branch(struct rebase_options *opts)
{
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -965,8 +786,10 @@ static int move_to_original_branch(struct rebase_options *opts)
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
- ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
- orig_head_reflog.buf, head_reflog.buf);
+ ret = reset_head(the_repository, NULL, "", opts->head_name,
+ RESET_HEAD_REFS_ONLY,
+ orig_head_reflog.buf, head_reflog.buf,
+ DEFAULT_REFLOG_ACTION);
strbuf_release(&orig_head_reflog);
strbuf_release(&head_reflog);
@@ -1054,8 +877,9 @@ static int run_am(struct rebase_options *opts)
free(rebased_patches);
argv_array_clear(&am.args);
- reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
- "HEAD", NULL);
+ reset_head(the_repository, &opts->orig_head, "checkout",
+ opts->head_name, 0,
+ "HEAD", NULL, DEFAULT_REFLOG_ACTION);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
@@ -1163,6 +987,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
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);
@@ -1213,7 +1038,7 @@ finished_rebase:
} else if (status == 2) {
struct strbuf dir = STRBUF_INIT;
- apply_autostash(opts);
+ apply_autostash(state_dir_path("autostash", opts));
strbuf_addstr(&dir, opts->state_dir);
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
@@ -1454,7 +1279,6 @@ static int check_exec_cmd(const char *cmd)
return 0;
}
-
int cmd_rebase(int argc, const char **argv, const char *prefix)
{
struct rebase_options options = REBASE_OPTIONS_INIT;
@@ -1548,7 +1372,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
N_("how to handle commits that become empty"),
PARSE_OPT_NONEG, parse_opt_empty),
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
- N_("(DEPRECATED) keep empty commits"),
+ N_("keep commits which start empty"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
parse_opt_keep_empty },
OPT_BOOL(0, "autosquash", &options.autosquash,
@@ -1557,8 +1381,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
N_("GPG-sign commits"),
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
- OPT_BOOL(0, "autostash", &options.autostash,
- N_("automatically stash/stash pop before and after")),
+ OPT_AUTOSTASH(&options.autostash),
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
N_("add exec lines after each commit of the "
"editable list")),
@@ -1583,6 +1406,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "reschedule-failed-exec",
&reschedule_failed_exec,
N_("automatically re-schedule any `exec` that fails")),
+ OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
+ N_("apply all changes, even those already present upstream")),
OPT_END(),
};
int i;
@@ -1593,6 +1418,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.allow_empty_message = 1;
git_config(rebase_config, &options);
+ /* options.gpg_sign_opt will be either "-S" or NULL */
+ gpg_sign = options.gpg_sign_opt ? "" : NULL;
+ FREE_AND_NULL(options.gpg_sign_opt);
if (options.use_legacy_rebase ||
!git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
@@ -1653,6 +1481,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("cannot combine '--keep-base' with '--root'"));
}
+ if (options.root && fork_point > 0)
+ die(_("cannot combine '--root' with '--fork-point'"));
+
if (action != ACTION_NONE && !in_progress)
die(_("No rebase in progress?"));
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1710,8 +1541,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
- if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
- NULL, NULL) < 0)
+ if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
+ NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
die(_("could not discard worktree changes"));
remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
@@ -1728,9 +1559,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (read_basic_state(&options))
exit(1);
- if (reset_head(&options.orig_head, "reset",
+ if (reset_head(the_repository, &options.orig_head, "reset",
options.head_name, RESET_HEAD_HARD,
- NULL, NULL) < 0)
+ NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
@@ -1738,6 +1569,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
goto cleanup;
}
case ACTION_QUIT: {
+ save_autostash(state_dir_path("autostash", &options));
if (options.type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
@@ -1823,10 +1655,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.empty != EMPTY_UNSPECIFIED)
imply_merge(&options, "--empty");
- if (gpg_sign) {
- free(options.gpg_sign_opt);
+ if (options.reapply_cherry_picks)
+ imply_merge(&options, "--reapply-cherry-picks");
+
+ if (gpg_sign)
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
- }
if (exec.nr) {
int i;
@@ -2087,49 +1920,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("could not read index"));
if (options.autostash) {
- struct lock_file lock_file = LOCK_INIT;
- int fd;
-
- fd = hold_locked_index(&lock_file, 0);
- refresh_cache(REFRESH_QUIET);
- if (0 <= fd)
- repo_update_index_if_able(the_repository, &lock_file);
- rollback_lock_file(&lock_file);
-
- if (has_unstaged_changes(the_repository, 1) ||
- has_uncommitted_changes(the_repository, 1)) {
- const char *autostash =
- state_dir_path("autostash", &options);
- struct child_process stash = CHILD_PROCESS_INIT;
- struct object_id oid;
-
- argv_array_pushl(&stash.args,
- "stash", "create", "autostash", NULL);
- stash.git_cmd = 1;
- stash.no_stdin = 1;
- strbuf_reset(&buf);
- if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
- die(_("Cannot autostash"));
- strbuf_trim_trailing_newline(&buf);
- if (get_oid(buf.buf, &oid))
- die(_("Unexpected stash response: '%s'"),
- buf.buf);
- strbuf_reset(&buf);
- strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
-
- if (safe_create_leading_directories_const(autostash))
- die(_("Could not create directory for '%s'"),
- options.state_dir);
- write_file(autostash, "%s", oid_to_hex(&oid));
- printf(_("Created autostash: %s\n"), buf.buf);
- if (reset_head(NULL, "reset --hard",
- NULL, RESET_HEAD_HARD, NULL, NULL) < 0)
- die(_("could not reset --hard"));
-
- if (discard_index(the_repository->index) < 0 ||
- repo_read_index(the_repository) < 0)
- die(_("could not read index"));
- }
+ create_autostash(the_repository, state_dir_path("autostash", &options),
+ DEFAULT_REFLOG_ACTION);
}
if (require_clean_work_tree(the_repository, "rebase",
@@ -2163,10 +1955,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&buf, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options.switch_to);
- if (reset_head(&options.orig_head, "checkout",
+ if (reset_head(the_repository,
+ &options.orig_head, "checkout",
options.head_name,
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, buf.buf) < 0) {
+ NULL, buf.buf,
+ DEFAULT_REFLOG_ACTION) < 0) {
ret = !!error(_("could not switch to "
"%s"),
options.switch_to);
@@ -2238,10 +2032,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
- if (reset_head(&options.onto->object.oid, "checkout", NULL,
+ if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, msg.buf))
+ NULL, msg.buf, DEFAULT_REFLOG_ACTION))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
@@ -2256,8 +2050,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "rebase finished: %s onto %s",
options.head_name ? options.head_name : "detached HEAD",
oid_to_hex(&options.onto->object.oid));
- reset_head(NULL, "Fast-forwarded", options.head_name,
- RESET_HEAD_REFS_ONLY, "HEAD", msg.buf);
+ reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
+ RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
+ DEFAULT_REFLOG_ACTION);
strbuf_release(&msg);
ret = !!finish_rebase(&options);
goto cleanup;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 2cc18bbffd..a00f91c1a0 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -13,7 +13,7 @@
#include "remote.h"
#include "connect.h"
#include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "connected.h"
#include "argv-array.h"
#include "version.h"
@@ -499,12 +499,27 @@ static char *find_header(const char *msg, size_t len, const char *key,
return NULL;
}
+/*
+ * Return zero if a and b are equal up to n bytes and nonzero if they are not.
+ * This operation is guaranteed to run in constant time to avoid leaking data.
+ */
+static int constant_memequal(const char *a, const char *b, size_t n)
+{
+ int res = 0;
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ res |= a[i] ^ b[i];
+ return res;
+}
+
static const char *check_nonce(const char *buf, size_t len)
{
char *nonce = find_header(buf, len, "nonce", NULL);
timestamp_t stamp, ostamp;
char *bohmac, *expect = NULL;
const char *retval = NONCE_BAD;
+ size_t noncelen;
if (!nonce) {
retval = NONCE_MISSING;
@@ -546,8 +561,14 @@ static const char *check_nonce(const char *buf, size_t len)
goto leave;
}
+ noncelen = strlen(nonce);
expect = prepare_push_cert_nonce(service_dir, stamp);
- if (strcmp(expect, nonce)) {
+ if (noncelen != strlen(expect)) {
+ /* This is not even the right size. */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+ if (constant_memequal(expect, nonce, noncelen)) {
/* Not what we would have signed earlier */
retval = NONCE_BAD;
goto leave;
@@ -872,12 +893,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
opt.env = tmp_objdir_env(tmp_objdir);
setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
if (check_connected(command_singleton_iterator, cmd, &opt)) {
- rollback_lock_file(&shallow_lock);
+ rollback_shallow_file(the_repository, &shallow_lock);
oid_array_clear(&extra);
return -1;
}
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
/*
* Make sure setup_alternate_shallow() for the next ref does
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 81dfd563c0..52ecf6d43c 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
static int reflog_expire_config(const char *var, const char *value, void *cb)
{
const char *pattern, *key;
- int pattern_len;
+ size_t pattern_len;
timestamp_t expire;
int slot;
struct reflog_expire_cfg *ent;
diff --git a/builtin/repack.c b/builtin/repack.c
index 0781763b06..1b686ee9ce 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -10,6 +10,7 @@
#include "argv-array.h"
#include "midx.h"
#include "packfile.h"
+#include "prune-packed.h"
#include "object-store.h"
#include "promisor-remote.h"
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 098ebf22d0..f2c5a34402 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -11,7 +11,7 @@
#include "quote.h"
#include "transport.h"
#include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "gpg-interface.h"
#include "gettext.h"
#include "protocol.h"
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 740da4b6d5..95d0882417 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -18,7 +18,7 @@
static const char *empty_base = "";
static char const * const builtin_sparse_checkout_usage[] = {
- N_("git sparse-checkout (init|list|set|add|disable) <options>"),
+ N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"),
NULL
};
@@ -94,50 +94,37 @@ static int sparse_checkout_list(int argc, const char **argv)
static int update_working_directory(struct pattern_list *pl)
{
- int result = 0;
+ enum update_sparsity_result result;
struct unpack_trees_options o;
struct lock_file lock_file = LOCK_INIT;
- struct object_id oid;
- struct tree *tree;
- struct tree_desc t;
struct repository *r = the_repository;
- if (repo_read_index_unmerged(r))
- die(_("you need to resolve your current index first"));
-
- if (get_oid("HEAD", &oid))
- return 0;
-
- tree = parse_tree_indirect(&oid);
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
-
memset(&o, 0, sizeof(o));
o.verbose_update = isatty(2);
- o.merge = 1;
o.update = 1;
- o.fn = oneway_merge;
o.head_idx = -1;
o.src_index = r->index;
o.dst_index = r->index;
o.skip_sparse_checkout = 0;
o.pl = pl;
- o.keep_pattern_list = !!pl;
- resolve_undo_clear_index(r->index);
setup_work_tree();
- cache_tree_free(&r->index->cache_tree);
-
repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR);
- core_apply_sparse_checkout = 1;
- result = unpack_trees(1, &t, &o);
-
- if (!result) {
- prime_cache_tree(r, r->index, tree);
+ setup_unpack_trees_porcelain(&o, "sparse-checkout");
+ result = update_sparsity(&o);
+ clear_unpack_trees_porcelain(&o);
+
+ if (result == UPDATE_SPARSITY_WARNINGS)
+ /*
+ * We don't do any special handling of warnings from untracked
+ * files in the way or dirty entries that can't be removed.
+ */
+ result = UPDATE_SPARSITY_SUCCESS;
+ if (result == UPDATE_SPARSITY_SUCCESS)
write_locked_index(r->index, &lock_file, COMMIT_LOCK);
- } else
+ else
rollback_lock_file(&lock_file);
return result;
@@ -304,8 +291,6 @@ static int sparse_checkout_init(int argc, const char **argv)
};
repo_read_index(the_repository);
- require_clean_work_tree(the_repository,
- N_("initialize sparse-checkout"), NULL, 1, 0);
argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_init_options,
@@ -560,8 +545,6 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
};
repo_read_index(the_repository);
- require_clean_work_tree(the_repository,
- N_("set sparse-checkout patterns"), NULL, 1, 0);
argc = parse_options(argc, argv, prefix,
builtin_sparse_checkout_set_options,
@@ -571,14 +554,18 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
return modify_pattern_list(argc, argv, m);
}
+static int sparse_checkout_reapply(int argc, const char **argv)
+{
+ repo_read_index(the_repository);
+ return update_working_directory(NULL);
+}
+
static int sparse_checkout_disable(int argc, const char **argv)
{
struct pattern_list pl;
struct strbuf match_all = STRBUF_INIT;
repo_read_index(the_repository);
- require_clean_work_tree(the_repository,
- N_("disable sparse-checkout"), NULL, 1, 0);
memset(&pl, 0, sizeof(pl));
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
@@ -622,6 +609,8 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
return sparse_checkout_set(argc, argv, prefix, REPLACE);
if (!strcmp(argv[0], "add"))
return sparse_checkout_set(argc, argv, prefix, ADD);
+ if (!strcmp(argv[0], "reapply"))
+ return sparse_checkout_reapply(argc, argv);
if (!strcmp(argv[0], "disable"))
return sparse_checkout_disable(argc, argv);
}
diff --git a/builtin/stash.c b/builtin/stash.c
index 6d586ef06d..0c52a3b849 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -861,30 +861,23 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
struct strbuf *untracked_files)
{
int i;
- int max_len;
int found = 0;
- char *seen;
struct dir_struct dir;
memset(&dir, 0, sizeof(dir));
if (include_untracked != INCLUDE_ALL_FILES)
setup_standard_excludes(&dir);
- seen = xcalloc(ps->nr, 1);
-
- max_len = fill_directory(&dir, the_repository->index, ps);
+ fill_directory(&dir, the_repository->index, ps);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
- found++;
- strbuf_addstr(untracked_files, ent->name);
- /* NUL-terminate: will be fed to update-index -z */
- strbuf_addch(untracked_files, '\0');
- }
+ found++;
+ strbuf_addstr(untracked_files, ent->name);
+ /* NUL-terminate: will be fed to update-index -z */
+ strbuf_addch(untracked_files, '\0');
free(ent);
}
- free(seen);
free(dir.entries);
free(dir.ignored);
clear_directory(&dir);
@@ -1041,7 +1034,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
}
cp_diff_tree.git_cmd = 1;
- argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
+ argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
oid_to_hex(&info->w_tree), "--", NULL);
if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
ret = -1;
diff --git a/builtin/tag.c b/builtin/tag.c
index cc30d346f5..dd160b49c7 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -17,7 +17,7 @@
#include "diff.h"
#include "revision.h"
#include "gpg-interface.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "column.h"
#include "ref-filter.h"
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d8f7f0578..b74dd9a69d 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -50,7 +50,7 @@ static const char *parse_arg(const char *next, struct strbuf *arg)
* the argument. Die if C-quoting is malformed or the reference name
* is invalid.
*/
-static char *parse_refname(struct strbuf *input, const char **next)
+static char *parse_refname(const char **next)
{
struct strbuf ref = STRBUF_INIT;
@@ -95,7 +95,7 @@ static char *parse_refname(struct strbuf *input, const char **next)
* provided but cannot be converted to a SHA-1, die. flags can
* include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
*/
-static int parse_next_oid(struct strbuf *input, const char **next,
+static int parse_next_oid(const char **next, const char *end,
struct object_id *oid,
const char *command, const char *refname,
int flags)
@@ -103,7 +103,7 @@ static int parse_next_oid(struct strbuf *input, const char **next,
struct strbuf arg = STRBUF_INIT;
int ret = 0;
- if (*next == input->buf + input->len)
+ if (*next == end)
goto eof;
if (line_termination) {
@@ -128,7 +128,7 @@ static int parse_next_oid(struct strbuf *input, const char **next,
die("%s %s: expected NUL but got: %s",
command, refname, *next);
(*next)++;
- if (*next == input->buf + input->len)
+ if (*next == end)
goto eof;
strbuf_addstr(&arg, *next);
*next += arg.len;
@@ -178,23 +178,23 @@ static int parse_next_oid(struct strbuf *input, const char **next,
* depending on how line_termination is set.
*/
-static const char *parse_cmd_update(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_update(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
int have_old;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("update: missing <ref>");
- if (parse_next_oid(input, &next, &new_oid, "update", refname,
+ if (parse_next_oid(&next, end, &new_oid, "update", refname,
PARSE_SHA1_ALLOW_EMPTY))
die("update %s: missing <newvalue>", refname);
- have_old = !parse_next_oid(input, &next, &old_oid, "update", refname,
+ have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
PARSE_SHA1_OLD);
if (*next != line_termination)
@@ -209,22 +209,20 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_create(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_create(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("create: missing <ref>");
- if (parse_next_oid(input, &next, &new_oid, "create", refname, 0))
+ if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
die("create %s: missing <newvalue>", refname);
if (is_null_oid(&new_oid))
@@ -241,23 +239,21 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_delete(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_delete(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id old_oid;
int have_old;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("delete: missing <ref>");
- if (parse_next_oid(input, &next, &old_oid, "delete", refname,
+ if (parse_next_oid(&next, end, &old_oid, "delete", refname,
PARSE_SHA1_OLD)) {
have_old = 0;
} else {
@@ -277,22 +273,20 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_verify(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_verify(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id old_oid;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("verify: missing <ref>");
- if (parse_next_oid(input, &next, &old_oid, "verify", refname,
+ if (parse_next_oid(&next, end, &old_oid, "verify", refname,
PARSE_SHA1_OLD))
oidclr(&old_oid);
@@ -306,50 +300,179 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_option(struct strbuf *input, const char *next)
+static void parse_cmd_option(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
update_flags |= REF_NO_DEREF;
else
die("option unknown: %s", next);
- return rest;
}
-static void update_refs_stdin(struct ref_transaction *transaction)
+static void parse_cmd_start(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ if (*next != line_termination)
+ die("start: extra input: %s", next);
+ puts("start: ok");
+}
+
+static void parse_cmd_prepare(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("prepare: extra input: %s", next);
+ if (ref_transaction_prepare(transaction, &error))
+ die("prepare: %s", error.buf);
+ puts("prepare: ok");
+}
+
+static void parse_cmd_abort(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("abort: extra input: %s", next);
+ if (ref_transaction_abort(transaction, &error))
+ die("abort: %s", error.buf);
+ puts("abort: ok");
+}
+
+static void parse_cmd_commit(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("commit: extra input: %s", next);
+ if (ref_transaction_commit(transaction, &error))
+ die("commit: %s", error.buf);
+ puts("commit: ok");
+ ref_transaction_free(transaction);
+}
+
+enum update_refs_state {
+ /* Non-transactional state open for updates. */
+ UPDATE_REFS_OPEN,
+ /* A transaction has been started. */
+ UPDATE_REFS_STARTED,
+ /* References are locked and ready for commit */
+ UPDATE_REFS_PREPARED,
+ /* Transaction has been committed or closed. */
+ UPDATE_REFS_CLOSED,
+};
+
+static const struct parse_cmd {
+ const char *prefix;
+ void (*fn)(struct ref_transaction *, const char *, const char *);
+ unsigned args;
+ enum update_refs_state state;
+} command[] = {
+ { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
+ { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
+ { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
+ { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
+ { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
+ { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
+ { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
+ { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
+};
+
+static void update_refs_stdin(void)
{
- struct strbuf input = STRBUF_INIT;
- const char *next;
+ struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
+ enum update_refs_state state = UPDATE_REFS_OPEN;
+ struct ref_transaction *transaction;
+ int i, j;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction)
+ die("%s", err.buf);
- if (strbuf_read(&input, 0, 1000) < 0)
- die_errno("could not read from stdin");
- next = input.buf;
/* Read each line dispatch its command */
- while (next < input.buf + input.len) {
- if (*next == line_termination)
+ while (!strbuf_getwholeline(&input, stdin, line_termination)) {
+ const struct parse_cmd *cmd = NULL;
+
+ if (*input.buf == line_termination)
die("empty command in input");
- else if (isspace(*next))
- die("whitespace before command: %s", next);
- else if (skip_prefix(next, "update ", &next))
- next = parse_cmd_update(transaction, &input, next);
- else if (skip_prefix(next, "create ", &next))
- next = parse_cmd_create(transaction, &input, next);
- else if (skip_prefix(next, "delete ", &next))
- next = parse_cmd_delete(transaction, &input, next);
- else if (skip_prefix(next, "verify ", &next))
- next = parse_cmd_verify(transaction, &input, next);
- else if (skip_prefix(next, "option ", &next))
- next = parse_cmd_option(&input, next);
- else
- die("unknown command: %s", next);
-
- next++;
+ else if (isspace(*input.buf))
+ die("whitespace before command: %s", input.buf);
+
+ for (i = 0; i < ARRAY_SIZE(command); i++) {
+ const char *prefix = command[i].prefix;
+ char c;
+
+ if (!starts_with(input.buf, prefix))
+ continue;
+
+ /*
+ * If the command has arguments, verify that it's
+ * followed by a space. Otherwise, it shall be followed
+ * by a line terminator.
+ */
+ c = command[i].args ? ' ' : line_termination;
+ if (input.buf[strlen(prefix)] != c)
+ continue;
+
+ cmd = &command[i];
+ break;
+ }
+ if (!cmd)
+ die("unknown command: %s", input.buf);
+
+ /*
+ * Read additional arguments if NUL-terminated. Do not raise an
+ * error in case there is an early EOF to let the command
+ * handle missing arguments with a proper error message.
+ */
+ for (j = 1; line_termination == '\0' && j < cmd->args; j++)
+ if (strbuf_appendwholeline(&input, stdin, line_termination))
+ break;
+
+ switch (state) {
+ case UPDATE_REFS_OPEN:
+ case UPDATE_REFS_STARTED:
+ /* Do not downgrade a transaction to a non-transaction. */
+ if (cmd->state >= state)
+ state = cmd->state;
+ break;
+ case UPDATE_REFS_PREPARED:
+ if (cmd->state != UPDATE_REFS_CLOSED)
+ die("prepared transactions can only be closed");
+ state = cmd->state;
+ break;
+ case UPDATE_REFS_CLOSED:
+ die("transaction is closed");
+ break;
+ }
+
+ cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
+ input.buf + input.len);
+ }
+
+ switch (state) {
+ case UPDATE_REFS_OPEN:
+ /* Commit by default if no transaction was requested. */
+ if (ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ break;
+ case UPDATE_REFS_STARTED:
+ case UPDATE_REFS_PREPARED:
+ /* If using a transaction, we want to abort it. */
+ if (ref_transaction_abort(transaction, &err))
+ die("%s", err.buf);
+ break;
+ case UPDATE_REFS_CLOSED:
+ /* Otherwise no need to do anything, the transaction was closed already. */
+ break;
}
+ strbuf_release(&err);
strbuf_release(&input);
}
@@ -384,21 +507,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
}
if (read_stdin) {
- struct strbuf err = STRBUF_INIT;
- struct ref_transaction *transaction;
-
- transaction = ref_transaction_begin(&err);
- if (!transaction)
- die("%s", err.buf);
if (delete || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
- update_refs_stdin(transaction);
- if (ref_transaction_commit(transaction, &err))
- die("%s", err.buf);
- ref_transaction_free(transaction);
- strbuf_release(&err);
+ update_refs_stdin();
return 0;
}
diff --git a/cache.h b/cache.h
index c77b95870a..0f0485ecfe 100644
--- a/cache.h
+++ b/cache.h
@@ -14,7 +14,7 @@
#include "pack-revindex.h"
#include "hash.h"
#include "path.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "repository.h"
#include "mem-pool.h"
diff --git a/ci/git-problem-matcher.json b/ci/git-problem-matcher.json
new file mode 100644
index 0000000000..506dfbd97f
--- /dev/null
+++ b/ci/git-problem-matcher.json
@@ -0,0 +1,16 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "git-test-suite",
+ "pattern": [
+ {
+ "regexp": "^([^ :]+\\.sh):(\\d+): (error|warning|info):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "severity": 3,
+ "message": 4
+ }
+ ]
+ }
+ ]
+}
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index 497fd32ca8..0229a77f7d 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -7,12 +7,16 @@
P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION
LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION
+UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev
+ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl
+ libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl"
case "$jobname" in
linux-clang|linux-gcc)
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
+ sudo apt-get -q -y install language-pack-is libsvn-perl apache2 \
+ $UBUNTU_COMMON_PKGS
case "$jobname" in
linux-gcc)
sudo apt-get -q -y install gcc-8
@@ -59,14 +63,18 @@ osx-clang|osx-gcc)
StaticAnalysis)
sudo apt-get -q update
sudo apt-get -q -y install coccinelle libcurl4-openssl-dev libssl-dev \
- libexpat-dev gettext
+ libexpat-dev gettext make
;;
Documentation)
sudo apt-get -q update
- sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns
+ sudo apt-get -q -y install asciidoc xmlto docbook-xsl-ns make
test -n "$ALREADY_HAVE_ASCIIDOCTOR" ||
- gem install --version 1.5.8 asciidoctor
+ sudo gem install --version 1.5.8 asciidoctor
+ ;;
+linux-gcc-4.8|GETTEXT_POISON)
+ sudo apt-get -q update
+ sudo apt-get -q -y install $UBUNTU_COMMON_PKGS
;;
esac
diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh
new file mode 100755
index 0000000000..26a6689766
--- /dev/null
+++ b/ci/install-docker-dependencies.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Install dependencies required to build and test Git inside container
+#
+
+case "$jobname" in
+Linux32)
+ linux32 --32bit i386 sh -c '
+ apt update >/dev/null &&
+ apt install -y build-essential libcurl4-openssl-dev \
+ libssl-dev libexpat-dev gettext python >/dev/null
+ '
+ ;;
+linux-musl)
+ apk add --update build-base curl-dev openssl-dev expat-dev gettext \
+ pcre2-dev python3 musl-libintl perl-utils ncurses >/dev/null
+ ;;
+esac
diff --git a/ci/lib.sh b/ci/lib.sh
index c3a8cd2104..dac36886e3 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -34,7 +34,7 @@ save_good_tree () {
# successfully before (e.g. because the branch got rebased, changing only
# the commit messages).
skip_good_tree () {
- if test "$TRAVIS_DEBUG_MODE" = true
+ if test "$TRAVIS_DEBUG_MODE" = true || test true = "$GITHUB_ACTIONS"
then
return
fi
@@ -79,6 +79,9 @@ check_unignored_build_artifacts ()
}
}
+# GitHub Action doesn't set TERM, which is required by tput
+export TERM=${TERM:-dumb}
+
# Clear MAKEFLAGS that may come from the outside world.
export MAKEFLAGS=
@@ -136,8 +139,32 @@ then
MAKEFLAGS="$MAKEFLAGS --jobs=10"
test windows_nt != "$CI_OS_NAME" ||
GIT_TEST_OPTS="--no-chain-lint --no-bin-wrappers $GIT_TEST_OPTS"
+elif test true = "$GITHUB_ACTIONS"
+then
+ CI_TYPE=github-actions
+ CI_BRANCH="$GITHUB_REF"
+ CI_COMMIT="$GITHUB_SHA"
+ CI_OS_NAME="$(echo "$RUNNER_OS" | tr A-Z a-z)"
+ test macos != "$CI_OS_NAME" || CI_OS_NAME=osx
+ CI_REPO_SLUG="$GITHUB_REPOSITORY"
+ CI_JOB_ID="$GITHUB_RUN_ID"
+ CC="${CC:-gcc}"
+
+ cache_dir="$HOME/none"
+
+ export GIT_PROVE_OPTS="--timer --jobs 10"
+ export GIT_TEST_OPTS="--verbose-log -x"
+ MAKEFLAGS="$MAKEFLAGS --jobs=10"
+ test windows != "$CI_OS_NAME" ||
+ GIT_TEST_OPTS="--no-chain-lint --no-bin-wrappers $GIT_TEST_OPTS"
+
+ # https://github.com/actions/toolkit/blob/master/docs/commands.md#problem-matchers
+ echo "::add-matcher::ci/git-problem-matcher.json"
+ test linux-musl = "$jobname" ||
+ MAKEFLAGS="$MAKEFLAGS TEST_SHELL_PATH=/bin/sh"
else
echo "Could not identify CI type" >&2
+ env >&2
exit 1
fi
@@ -195,9 +222,17 @@ osx-clang|osx-gcc)
# Travis CI OS X
export GIT_SKIP_TESTS="t9810 t9816"
;;
-GIT_TEST_GETTEXT_POISON)
+GETTEXT_POISON)
export GIT_TEST_GETTEXT_POISON=true
;;
+Linux32)
+ CC=gcc
+ ;;
+linux-musl)
+ CC=gcc
+ MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/python3 USE_LIBPCRE2=Yes"
+ MAKEFLAGS="$MAKEFLAGS NO_REGEX=Yes ICONV_OMITS_BOM=Yes"
+ ;;
esac
MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}"
diff --git a/ci/print-test-failures.sh b/ci/print-test-failures.sh
index e688a26f0d..92a983a265 100755
--- a/ci/print-test-failures.sh
+++ b/ci/print-test-failures.sh
@@ -46,6 +46,13 @@ do
mv "$trash_dir" failed-test-artifacts
continue
;;
+ github-actions)
+ mkdir -p failed-test-artifacts
+ echo "::set-env name=FAILED_TEST_ARTIFACTS::t/failed-test-artifacts"
+ cp "${TEST_EXIT%.exit}.out" failed-test-artifacts/
+ tar czf failed-test-artifacts/"$test_name".trash.tar.gz "$trash_dir"
+ continue
+ ;;
*)
echo "Unhandled CI type: $CI_TYPE" >&2
exit 1
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 4df54c4efe..17e25aade9 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -19,6 +19,7 @@ linux-gcc)
export GIT_TEST_OE_SIZE=10
export GIT_TEST_OE_DELTA_SIZE=5
export GIT_TEST_COMMIT_GRAPH=1
+ export GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1
export GIT_TEST_MULTI_PACK_INDEX=1
export GIT_TEST_ADD_I_USE_BUILTIN=1
make test
diff --git a/ci/run-linux32-build.sh b/ci/run-docker-build.sh
index e3a193adbc..8d47a5fda3 100755
--- a/ci/run-linux32-build.sh
+++ b/ci/run-docker-build.sh
@@ -1,25 +1,33 @@
#!/bin/sh
#
-# Build and test Git in a 32-bit environment
+# Build and test Git inside container
#
# Usage:
-# run-linux32-build.sh <host-user-id>
+# run-docker-build.sh <host-user-id>
#
set -ex
if test $# -ne 1 || test -z "$1"
then
- echo >&2 "usage: run-linux32-build.sh <host-user-id>"
+ echo >&2 "usage: run-docker-build.sh <host-user-id>"
exit 1
fi
-# Update packages to the latest available versions
-linux32 --32bit i386 sh -c '
- apt update >/dev/null &&
- apt install -y build-essential libcurl4-openssl-dev libssl-dev \
- libexpat-dev gettext python >/dev/null
-'
+case "$jobname" in
+Linux32)
+ switch_cmd="linux32 --32bit i386"
+ ;;
+linux-musl)
+ switch_cmd=
+ useradd () { adduser -D "$@"; }
+ ;;
+*)
+ exit 1
+ ;;
+esac
+
+"${0%/*}/install-docker-dependencies.sh"
# If this script runs inside a docker container, then all commands are
# usually executed as root. Consequently, the host user might not be
@@ -51,10 +59,17 @@ else
fi
# Build and test
-linux32 --32bit i386 su -m -l $CI_USER -c '
+command $switch_cmd su -m -l $CI_USER -c "
set -ex
+ export DEVELOPER='$DEVELOPER'
+ export DEFAULT_TEST_TARGET='$DEFAULT_TEST_TARGET'
+ export GIT_PROVE_OPTS='$GIT_PROVE_OPTS'
+ export GIT_TEST_OPTS='$GIT_TEST_OPTS'
+ export GIT_TEST_CLONE_2GB='$GIT_TEST_CLONE_2GB'
+ export MAKEFLAGS='$MAKEFLAGS'
+ export cache_dir='$cache_dir'
cd /usr/src/git
- test -n "$cache_dir" && ln -s "$cache_dir/.prove" t/.prove
+ test -n '$cache_dir' && ln -s '$cache_dir/.prove' t/.prove
make
make test
-'
+"
diff --git a/ci/run-docker.sh b/ci/run-docker.sh
new file mode 100755
index 0000000000..37fa372052
--- /dev/null
+++ b/ci/run-docker.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Download and run Docker image to build and test Git
+#
+
+. ${0%/*}/lib.sh
+
+case "$jobname" in
+Linux32)
+ CI_CONTAINER="daald/ubuntu32:xenial"
+ ;;
+linux-musl)
+ CI_CONTAINER=alpine
+ ;;
+*)
+ exit 1
+ ;;
+esac
+
+docker pull "$CI_CONTAINER"
+
+# Use the following command to debug the docker build locally:
+# <host-user-id> must be 0 if podman is used as drop-in replacement for docker
+# $ docker run -itv "${PWD}:/usr/src/git" --entrypoint /bin/sh "$CI_CONTAINER"
+# root@container:/# export jobname=<jobname>
+# root@container:/# /usr/src/git/ci/run-docker-build.sh <host-user-id>
+
+container_cache_dir=/tmp/travis-cache
+
+docker run \
+ --interactive \
+ --env DEVELOPER \
+ --env DEFAULT_TEST_TARGET \
+ --env GIT_PROVE_OPTS \
+ --env GIT_TEST_OPTS \
+ --env GIT_TEST_CLONE_2GB \
+ --env MAKEFLAGS \
+ --env jobname \
+ --env cache_dir="$container_cache_dir" \
+ --volume "${PWD}:/usr/src/git" \
+ --volume "$cache_dir:$container_cache_dir" \
+ "$CI_CONTAINER" \
+ /usr/src/git/ci/run-docker-build.sh $(id -u $USER)
+
+check_unignored_build_artifacts
+
+save_good_tree
diff --git a/ci/run-linux32-docker.sh b/ci/run-linux32-docker.sh
deleted file mode 100755
index 751acfcf8a..0000000000
--- a/ci/run-linux32-docker.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-#
-# Download and run Docker image to build and test 32-bit Git
-#
-
-. ${0%/*}/lib.sh
-
-docker pull daald/ubuntu32:xenial
-
-# Use the following command to debug the docker build locally:
-# $ docker run -itv "${PWD}:/usr/src/git" --entrypoint /bin/bash daald/ubuntu32:xenial
-# root@container:/# /usr/src/git/ci/run-linux32-build.sh <host-user-id>
-
-container_cache_dir=/tmp/travis-cache
-
-docker run \
- --interactive \
- --env DEVELOPER \
- --env DEFAULT_TEST_TARGET \
- --env GIT_PROVE_OPTS \
- --env GIT_TEST_OPTS \
- --env GIT_TEST_CLONE_2GB \
- --env cache_dir="$container_cache_dir" \
- --volume "${PWD}:/usr/src/git" \
- --volume "$cache_dir:$container_cache_dir" \
- daald/ubuntu32:xenial \
- /usr/src/git/ci/run-linux32-build.sh $(id -u $USER)
-
-check_unignored_build_artifacts
-
-save_good_tree
diff --git a/combine-diff.c b/combine-diff.c
index d5c4d839dc..002e0e5438 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -10,7 +10,7 @@
#include "log-tree.h"
#include "refs.h"
#include "userdiff.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "revision.h"
static int compare_paths(const struct combine_diff_path *one,
diff --git a/command-list.txt b/command-list.txt
index 2087894655..185e5e3f05 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -54,6 +54,7 @@ git-archive mainporcelain
git-bisect mainporcelain info
git-blame ancillaryinterrogators complete
git-branch mainporcelain history
+git-bugreport ancillaryinterrogators
git-bundle mainporcelain
git-cat-file plumbinginterrogators
git-check-attr purehelpers
diff --git a/commit-graph.c b/commit-graph.c
index f013a84e29..6dc777e2f3 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -16,13 +16,32 @@
#include "hashmap.h"
#include "replace-object.h"
#include "progress.h"
+#include "bloom.h"
+#include "commit-slab.h"
+
+void git_test_write_commit_graph_or_die(void)
+{
+ int flags = 0;
+ if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
+ return;
+
+ if (git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
+ flags = COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
+
+ if (write_commit_graph_reachable(the_repository->objects->odb,
+ flags, NULL))
+ die("failed to write commit-graph under GIT_TEST_COMMIT_GRAPH");
+}
#define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
#define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
#define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
#define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */
+#define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */
+#define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */
#define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */
+#define MAX_NUM_CHUNKS 7
#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
@@ -44,9 +63,51 @@
/* Remember to update object flag allocation in object.h */
#define REACHABLE (1u<<15)
-char *get_commit_graph_filename(struct object_directory *odb)
+/* Keep track of the order in which commits are added to our list. */
+define_commit_slab(commit_pos, int);
+static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos);
+
+static void set_commit_pos(struct repository *r, const struct object_id *oid)
+{
+ static int32_t max_pos;
+ struct commit *commit = lookup_commit(r, oid);
+
+ if (!commit)
+ return; /* should never happen, but be lenient */
+
+ *commit_pos_at(&commit_pos, commit) = max_pos++;
+}
+
+static int commit_pos_cmp(const void *va, const void *vb)
+{
+ const struct commit *a = *(const struct commit **)va;
+ const struct commit *b = *(const struct commit **)vb;
+ return commit_pos_at(&commit_pos, a) -
+ commit_pos_at(&commit_pos, b);
+}
+
+static int commit_gen_cmp(const void *va, const void *vb)
+{
+ const struct commit *a = *(const struct commit **)va;
+ const struct commit *b = *(const struct commit **)vb;
+
+ /* lower generation commits first */
+ if (a->generation < b->generation)
+ return -1;
+ else if (a->generation > b->generation)
+ return 1;
+
+ /* use date as a heuristic when generations are equal */
+ if (a->date < b->date)
+ return -1;
+ else if (a->date > b->date)
+ return 1;
+ return 0;
+}
+
+char *get_commit_graph_filename(struct object_directory *obj_dir)
{
- return xstrfmt("%s/info/commit-graph", odb->path);
+ return xstrfmt("%s/info/commit-graph", obj_dir->path);
}
static char *get_split_graph_filename(struct object_directory *odb,
@@ -69,7 +130,6 @@ static uint8_t oid_version(void)
static struct commit_graph *alloc_commit_graph(void)
{
struct commit_graph *g = xcalloc(1, sizeof(*g));
- g->graph_fd = -1;
return g;
}
@@ -123,14 +183,13 @@ struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
return NULL;
}
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
- ret = parse_commit_graph(graph_map, fd, graph_size);
+ close(fd);
+ ret = parse_commit_graph(graph_map, graph_size);
if (ret)
ret->odb = odb;
- else {
+ else
munmap(graph_map, graph_size);
- close(fd);
- }
return ret;
}
@@ -165,8 +224,7 @@ static int verify_commit_graph_lite(struct commit_graph *g)
return 0;
}
-struct commit_graph *parse_commit_graph(void *graph_map, int fd,
- size_t graph_size)
+struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
{
const unsigned char *data, *chunk_lookup;
uint32_t i;
@@ -209,7 +267,6 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
graph->hash_len = the_hash_algo->rawsz;
graph->num_chunks = *(unsigned char*)(data + 6);
- graph->graph_fd = fd;
graph->data = graph_map;
graph->data_len = graph_size;
@@ -274,6 +331,32 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
chunk_repeated = 1;
else
graph->chunk_base_graphs = data + chunk_offset;
+ break;
+
+ case GRAPH_CHUNKID_BLOOMINDEXES:
+ if (graph->chunk_bloom_indexes)
+ chunk_repeated = 1;
+ else
+ graph->chunk_bloom_indexes = data + chunk_offset;
+ break;
+
+ case GRAPH_CHUNKID_BLOOMDATA:
+ if (graph->chunk_bloom_data)
+ chunk_repeated = 1;
+ else {
+ uint32_t hash_version;
+ graph->chunk_bloom_data = data + chunk_offset;
+ hash_version = get_be32(data + chunk_offset);
+
+ if (hash_version != 1)
+ break;
+
+ graph->bloom_filter_settings = xmalloc(sizeof(struct bloom_filter_settings));
+ graph->bloom_filter_settings->hash_version = hash_version;
+ graph->bloom_filter_settings->num_hashes = get_be32(data + chunk_offset + 4);
+ graph->bloom_filter_settings->bits_per_entry = get_be32(data + chunk_offset + 8);
+ }
+ break;
}
if (chunk_repeated) {
@@ -292,6 +375,15 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
last_chunk_offset = chunk_offset;
}
+ if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
+ init_bloom_filters();
+ } else {
+ /* We need both the bloom chunks to exist together. Else ignore the data */
+ graph->chunk_bloom_indexes = NULL;
+ graph->chunk_bloom_data = NULL;
+ graph->bloom_filter_settings = NULL;
+ }
+
hashcpy(graph->oid.hash, graph->data + graph->data_len - graph->hash_len);
if (verify_commit_graph_lite(graph)) {
@@ -788,9 +880,12 @@ struct write_commit_graph_context {
unsigned append:1,
report_progress:1,
split:1,
- check_oids:1;
+ check_oids:1,
+ changed_paths:1,
+ order_by_pack:1;
const struct split_commit_graph_opts *split_opts;
+ size_t total_bloom_filter_data_size;
};
static void write_graph_chunk_fanout(struct hashfile *f,
@@ -866,7 +961,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
if (edge_value >= 0)
edge_value += ctx->new_num_commits_in_base;
- else {
+ else if (ctx->new_base_graph) {
uint32_t pos;
if (find_commit_in_graph(parent->item,
ctx->new_base_graph,
@@ -897,7 +992,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
if (edge_value >= 0)
edge_value += ctx->new_num_commits_in_base;
- else {
+ else if (ctx->new_base_graph) {
uint32_t pos;
if (find_commit_in_graph(parent->item,
ctx->new_base_graph,
@@ -964,7 +1059,7 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
if (edge_value >= 0)
edge_value += ctx->new_num_commits_in_base;
- else {
+ else if (ctx->new_base_graph) {
uint32_t pos;
if (find_commit_in_graph(parent->item,
ctx->new_base_graph,
@@ -986,6 +1081,59 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
}
}
+static void write_graph_chunk_bloom_indexes(struct hashfile *f,
+ struct write_commit_graph_context *ctx)
+{
+ struct commit **list = ctx->commits.list;
+ struct commit **last = ctx->commits.list + ctx->commits.nr;
+ uint32_t cur_pos = 0;
+ struct progress *progress = NULL;
+ int i = 0;
+
+ if (ctx->report_progress)
+ progress = start_delayed_progress(
+ _("Writing changed paths Bloom filters index"),
+ ctx->commits.nr);
+
+ while (list < last) {
+ struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+ cur_pos += filter->len;
+ display_progress(progress, ++i);
+ hashwrite_be32(f, cur_pos);
+ list++;
+ }
+
+ stop_progress(&progress);
+}
+
+static void write_graph_chunk_bloom_data(struct hashfile *f,
+ struct write_commit_graph_context *ctx,
+ const struct bloom_filter_settings *settings)
+{
+ struct commit **list = ctx->commits.list;
+ struct commit **last = ctx->commits.list + ctx->commits.nr;
+ struct progress *progress = NULL;
+ int i = 0;
+
+ if (ctx->report_progress)
+ progress = start_delayed_progress(
+ _("Writing changed paths Bloom filters data"),
+ ctx->commits.nr);
+
+ hashwrite_be32(f, settings->hash_version);
+ hashwrite_be32(f, settings->num_hashes);
+ hashwrite_be32(f, settings->bits_per_entry);
+
+ while (list < last) {
+ struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+ display_progress(progress, ++i);
+ hashwrite(f, filter->data, filter->len * sizeof(unsigned char));
+ list++;
+ }
+
+ stop_progress(&progress);
+}
+
static int oid_compare(const void *_a, const void *_b)
{
const struct object_id *a = (const struct object_id *)_a;
@@ -1017,6 +1165,8 @@ static int add_packed_commits(const struct object_id *oid,
oidcpy(&(ctx->oids.list[ctx->oids.nr]), oid);
ctx->oids.nr++;
+ set_commit_pos(ctx->r, oid);
+
return 0;
}
@@ -1037,6 +1187,8 @@ static void close_reachable(struct write_commit_graph_context *ctx)
{
int i;
struct commit *commit;
+ enum commit_graph_split_flags flags = ctx->split_opts ?
+ ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
if (ctx->report_progress)
ctx->progress = start_delayed_progress(
@@ -1066,8 +1218,9 @@ static void close_reachable(struct write_commit_graph_context *ctx)
if (!commit)
continue;
if (ctx->split) {
- if (!parse_commit(commit) &&
- commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
+ if ((!parse_commit(commit) &&
+ commit->graph_pos == COMMIT_NOT_FROM_GRAPH) ||
+ flags == COMMIT_GRAPH_SPLIT_REPLACE)
add_missing_parents(ctx, commit);
} else if (!parse_commit_no_graph(commit))
add_missing_parents(ctx, commit);
@@ -1133,13 +1286,45 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
stop_progress(&ctx->progress);
}
-static int add_ref_to_list(const char *refname,
- const struct object_id *oid,
- int flags, void *cb_data)
+static void compute_bloom_filters(struct write_commit_graph_context *ctx)
{
- struct string_list *list = (struct string_list *)cb_data;
+ int i;
+ struct progress *progress = NULL;
+ struct commit **sorted_commits;
+
+ init_bloom_filters();
- string_list_append(list, oid_to_hex(oid));
+ if (ctx->report_progress)
+ progress = start_delayed_progress(
+ _("Computing commit changed paths Bloom filters"),
+ ctx->commits.nr);
+
+ ALLOC_ARRAY(sorted_commits, ctx->commits.nr);
+ COPY_ARRAY(sorted_commits, ctx->commits.list, ctx->commits.nr);
+
+ if (ctx->order_by_pack)
+ QSORT(sorted_commits, ctx->commits.nr, commit_pos_cmp);
+ else
+ QSORT(sorted_commits, ctx->commits.nr, commit_gen_cmp);
+
+ for (i = 0; i < ctx->commits.nr; i++) {
+ struct commit *c = sorted_commits[i];
+ struct bloom_filter *filter = get_bloom_filter(ctx->r, c, 1);
+ ctx->total_bloom_filter_data_size += sizeof(unsigned char) * filter->len;
+ display_progress(progress, i + 1);
+ }
+
+ free(sorted_commits);
+ stop_progress(&progress);
+}
+
+static int add_ref_to_set(const char *refname,
+ const struct object_id *oid,
+ int flags, void *cb_data)
+{
+ struct oidset *commits = (struct oidset *)cb_data;
+
+ oidset_insert(commits, oid);
return 0;
}
@@ -1147,14 +1332,14 @@ int write_commit_graph_reachable(struct object_directory *odb,
enum commit_graph_write_flags flags,
const struct split_commit_graph_opts *split_opts)
{
- struct string_list list = STRING_LIST_INIT_DUP;
+ struct oidset commits = OIDSET_INIT;
int result;
- for_each_ref(add_ref_to_list, &list);
- result = write_commit_graph(odb, NULL, &list,
+ for_each_ref(add_ref_to_set, &commits);
+ result = write_commit_graph(odb, NULL, &commits,
flags, split_opts);
- string_list_clear(&list, 0);
+ oidset_clear(&commits);
return result;
}
@@ -1203,39 +1388,46 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
return 0;
}
-static int fill_oids_from_commit_hex(struct write_commit_graph_context *ctx,
- struct string_list *commit_hex)
+static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
+ struct oidset *commits)
{
- uint32_t i;
+ uint32_t i = 0;
struct strbuf progress_title = STRBUF_INIT;
+ struct oidset_iter iter;
+ struct object_id *oid;
+
+ if (!oidset_size(commits))
+ return 0;
if (ctx->report_progress) {
strbuf_addf(&progress_title,
Q_("Finding commits for commit graph from %d ref",
"Finding commits for commit graph from %d refs",
- commit_hex->nr),
- commit_hex->nr);
+ oidset_size(commits)),
+ oidset_size(commits));
ctx->progress = start_delayed_progress(
progress_title.buf,
- commit_hex->nr);
+ oidset_size(commits));
}
- for (i = 0; i < commit_hex->nr; i++) {
- const char *end;
- struct object_id oid;
+
+ oidset_iter_init(commits, &iter);
+ while ((oid = oidset_iter_next(&iter))) {
struct commit *result;
- display_progress(ctx->progress, i + 1);
- if (!parse_oid_hex(commit_hex->items[i].string, &oid, &end) &&
- (result = lookup_commit_reference_gently(ctx->r, &oid, 1))) {
+ display_progress(ctx->progress, ++i);
+
+ result = lookup_commit_reference_gently(ctx->r, oid, 1);
+ if (result) {
ALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);
oidcpy(&ctx->oids.list[ctx->oids.nr], &(result->object.oid));
ctx->oids.nr++;
} else if (ctx->check_oids) {
error(_("invalid commit object id: %s"),
- commit_hex->items[i].string);
+ oid_to_hex(oid));
return -1;
}
}
+
stop_progress(&ctx->progress);
strbuf_release(&progress_title);
@@ -1287,6 +1479,8 @@ static uint32_t count_distinct_commits(struct write_commit_graph_context *ctx)
static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
{
uint32_t i;
+ enum commit_graph_split_flags flags = ctx->split_opts ?
+ ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
ctx->num_extra_edges = 0;
if (ctx->report_progress)
@@ -1303,11 +1497,14 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.list[i]);
- if (ctx->split &&
+ if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
ctx->commits.list[ctx->commits.nr]->graph_pos != COMMIT_NOT_FROM_GRAPH)
continue;
- parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
+ if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
+ parse_commit(ctx->commits.list[ctx->commits.nr]);
+ else
+ parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents);
if (num_parents > 2)
@@ -1350,12 +1547,13 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
int fd;
struct hashfile *f;
struct lock_file lk = LOCK_INIT;
- uint32_t chunk_ids[6];
- uint64_t chunk_offsets[6];
+ uint32_t chunk_ids[MAX_NUM_CHUNKS + 1];
+ uint64_t chunk_offsets[MAX_NUM_CHUNKS + 1];
const unsigned hashsz = the_hash_algo->rawsz;
struct strbuf progress_title = STRBUF_INIT;
int num_chunks = 3;
struct object_id file_hash;
+ const struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
if (ctx->split) {
struct strbuf tmp_file = STRBUF_INIT;
@@ -1382,7 +1580,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
fd = git_mkstemp_mode(ctx->graph_name, 0444);
if (fd < 0) {
- error(_("unable to create '%s'"), ctx->graph_name);
+ error(_("unable to create temporary graph layer"));
return -1;
}
@@ -1400,6 +1598,12 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
chunk_ids[num_chunks] = GRAPH_CHUNKID_EXTRAEDGES;
num_chunks++;
}
+ if (ctx->changed_paths) {
+ chunk_ids[num_chunks] = GRAPH_CHUNKID_BLOOMINDEXES;
+ num_chunks++;
+ chunk_ids[num_chunks] = GRAPH_CHUNKID_BLOOMDATA;
+ num_chunks++;
+ }
if (ctx->num_commit_graphs_after > 1) {
chunk_ids[num_chunks] = GRAPH_CHUNKID_BASE;
num_chunks++;
@@ -1418,6 +1622,15 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
4 * ctx->num_extra_edges;
num_chunks++;
}
+ if (ctx->changed_paths) {
+ chunk_offsets[num_chunks + 1] = chunk_offsets[num_chunks] +
+ sizeof(uint32_t) * ctx->commits.nr;
+ num_chunks++;
+
+ chunk_offsets[num_chunks + 1] = chunk_offsets[num_chunks] +
+ sizeof(uint32_t) * 3 + ctx->total_bloom_filter_data_size;
+ num_chunks++;
+ }
if (ctx->num_commit_graphs_after > 1) {
chunk_offsets[num_chunks + 1] = chunk_offsets[num_chunks] +
hashsz * (ctx->num_commit_graphs_after - 1);
@@ -1455,6 +1668,10 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
write_graph_chunk_data(f, hashsz, ctx);
if (ctx->num_extra_edges)
write_graph_chunk_extra_edges(f, ctx);
+ if (ctx->changed_paths) {
+ write_graph_chunk_bloom_indexes(f, ctx);
+ write_graph_chunk_bloom_data(f, ctx, &bloom_settings);
+ }
if (ctx->num_commit_graphs_after > 1 &&
write_graph_chunk_base(f, ctx)) {
return -1;
@@ -1488,8 +1705,12 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
if (ctx->base_graph_name) {
- const char *dest = ctx->commit_graph_filenames_after[
- ctx->num_commit_graphs_after - 2];
+ const char *dest;
+ int idx = ctx->num_commit_graphs_after - 1;
+ if (ctx->num_commit_graphs_after > 1)
+ idx--;
+
+ dest = ctx->commit_graph_filenames_after[idx];
if (strcmp(ctx->base_graph_name, dest)) {
result = rename(ctx->base_graph_name, dest);
@@ -1529,6 +1750,7 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
{
struct commit_graph *g;
uint32_t num_commits;
+ enum commit_graph_split_flags flags = COMMIT_GRAPH_SPLIT_UNSPECIFIED;
uint32_t i;
int max_commits = 0;
@@ -1539,24 +1761,36 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
if (ctx->split_opts->size_multiple)
size_mult = ctx->split_opts->size_multiple;
+
+ flags = ctx->split_opts->flags;
}
g = ctx->r->objects->commit_graph;
num_commits = ctx->commits.nr;
- ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
-
- while (g && (g->num_commits <= size_mult * num_commits ||
- (max_commits && num_commits > max_commits))) {
- if (g->odb != ctx->odb)
- break;
+ if (flags == COMMIT_GRAPH_SPLIT_REPLACE)
+ ctx->num_commit_graphs_after = 1;
+ else
+ ctx->num_commit_graphs_after = ctx->num_commit_graphs_before + 1;
+
+ if (flags != COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED &&
+ flags != COMMIT_GRAPH_SPLIT_REPLACE) {
+ while (g && (g->num_commits <= size_mult * num_commits ||
+ (max_commits && num_commits > max_commits))) {
+ if (g->odb != ctx->odb)
+ break;
- num_commits += g->num_commits;
- g = g->base_graph;
+ num_commits += g->num_commits;
+ g = g->base_graph;
- ctx->num_commit_graphs_after--;
+ ctx->num_commit_graphs_after--;
+ }
}
- ctx->new_base_graph = g;
+ if (flags != COMMIT_GRAPH_SPLIT_REPLACE)
+ ctx->new_base_graph = g;
+ else if (ctx->num_commit_graphs_after != 1)
+ BUG("split_graph_merge_strategy: num_commit_graphs_after "
+ "should be 1 with --split=replace");
if (ctx->num_commit_graphs_after == 2) {
char *old_graph_name = get_commit_graph_filename(g->odb);
@@ -1570,8 +1804,8 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
free(old_graph_name);
}
- ALLOC_ARRAY(ctx->commit_graph_filenames_after, ctx->num_commit_graphs_after);
- ALLOC_ARRAY(ctx->commit_graph_hash_after, ctx->num_commit_graphs_after);
+ CALLOC_ARRAY(ctx->commit_graph_filenames_after, ctx->num_commit_graphs_after);
+ CALLOC_ARRAY(ctx->commit_graph_hash_after, ctx->num_commit_graphs_after);
for (i = 0; i < ctx->num_commit_graphs_after &&
i < ctx->num_commit_graphs_before; i++)
@@ -1707,7 +1941,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
timestamp_t expire_time = time(NULL);
if (ctx->split_opts && ctx->split_opts->expire_time)
- expire_time -= ctx->split_opts->expire_time;
+ expire_time = ctx->split_opts->expire_time;
if (!ctx->split) {
char *chain_file_name = get_chain_filename(ctx->odb);
unlink(chain_file_name);
@@ -1756,13 +1990,14 @@ out:
int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
- struct string_list *commit_hex,
+ struct oidset *commits,
enum commit_graph_write_flags flags,
const struct split_commit_graph_opts *split_opts)
{
struct write_commit_graph_context *ctx;
uint32_t i, count_distinct = 0;
int res = 0;
+ int replace = 0;
if (!commit_graph_compatible(the_repository))
return 0;
@@ -1775,6 +2010,8 @@ int write_commit_graph(struct object_directory *odb,
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
ctx->check_oids = flags & COMMIT_GRAPH_WRITE_CHECK_OIDS ? 1 : 0;
ctx->split_opts = split_opts;
+ ctx->changed_paths = flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS ? 1 : 0;
+ ctx->total_bloom_filter_data_size = 0;
if (ctx->split) {
struct commit_graph *g;
@@ -1797,6 +2034,9 @@ int write_commit_graph(struct object_directory *odb,
g = g->base_graph;
}
}
+
+ if (ctx->split_opts)
+ replace = ctx->split_opts->flags & COMMIT_GRAPH_SPLIT_REPLACE;
}
ctx->approx_nr_objects = approximate_object_count();
@@ -1824,17 +2064,20 @@ int write_commit_graph(struct object_directory *odb,
}
if (pack_indexes) {
+ ctx->order_by_pack = 1;
if ((res = fill_oids_from_packs(ctx, pack_indexes)))
goto cleanup;
}
- if (commit_hex) {
- if ((res = fill_oids_from_commit_hex(ctx, commit_hex)))
+ if (commits) {
+ if ((res = fill_oids_from_commits(ctx, commits)))
goto cleanup;
}
- if (!pack_indexes && !commit_hex)
+ if (!pack_indexes && !commits) {
+ ctx->order_by_pack = 1;
fill_oids_from_all_packs(ctx);
+ }
close_reachable(ctx);
@@ -1857,18 +2100,22 @@ int write_commit_graph(struct object_directory *odb,
goto cleanup;
}
- if (!ctx->commits.nr)
+ if (!ctx->commits.nr && !replace)
goto cleanup;
if (ctx->split) {
split_graph_merge_strategy(ctx);
- merge_commit_graphs(ctx);
+ if (!replace)
+ merge_commit_graphs(ctx);
} else
ctx->num_commit_graphs_after = 1;
compute_generation_numbers(ctx);
+ if (ctx->changed_paths)
+ compute_bloom_filters(ctx);
+
res = write_commit_graph_file(ctx);
if (ctx->split)
@@ -2088,12 +2335,12 @@ void free_commit_graph(struct commit_graph *g)
{
if (!g)
return;
- if (g->graph_fd >= 0) {
+ if (g->data) {
munmap((void *)g->data, g->data_len);
g->data = NULL;
- close(g->graph_fd);
}
free(g->filename);
+ free(g->bloom_filter_settings);
free(g);
}
diff --git a/commit-graph.h b/commit-graph.h
index e87a6f6360..4212766a4f 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -6,11 +6,23 @@
#include "string-list.h"
#include "cache.h"
#include "object-store.h"
+#include "oidset.h"
#define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH"
#define GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD "GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD"
+#define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS"
+
+/*
+ * This method is only used to enhance coverage of the commit-graph
+ * feature in the test suite with the GIT_TEST_COMMIT_GRAPH and
+ * GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS environment variables. Do not
+ * call this method oustide of a builtin, and only if you know what
+ * you are doing!
+ */
+void git_test_write_commit_graph_or_die(void);
struct commit;
+struct bloom_filter_settings;
char *get_commit_graph_filename(struct object_directory *odb);
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
@@ -39,8 +51,6 @@ struct tree *get_commit_tree_in_graph(struct repository *r,
const struct commit *c);
struct commit_graph {
- int graph_fd;
-
const unsigned char *data;
size_t data_len;
@@ -59,14 +69,17 @@ struct commit_graph {
const unsigned char *chunk_commit_data;
const unsigned char *chunk_extra_edges;
const unsigned char *chunk_base_graphs;
+ const unsigned char *chunk_bloom_indexes;
+ const unsigned char *chunk_bloom_data;
+
+ struct bloom_filter_settings *bloom_filter_settings;
};
struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
struct object_directory *odb);
struct commit_graph *read_commit_graph_one(struct repository *r,
struct object_directory *odb);
-struct commit_graph *parse_commit_graph(void *graph_map, int fd,
- size_t graph_size);
+struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);
/*
* Return 1 if and only if the repository has a commit-graph
@@ -79,13 +92,21 @@ enum commit_graph_write_flags {
COMMIT_GRAPH_WRITE_PROGRESS = (1 << 1),
COMMIT_GRAPH_WRITE_SPLIT = (1 << 2),
/* Make sure that each OID in the input is a valid commit OID. */
- COMMIT_GRAPH_WRITE_CHECK_OIDS = (1 << 3)
+ COMMIT_GRAPH_WRITE_CHECK_OIDS = (1 << 3),
+ COMMIT_GRAPH_WRITE_BLOOM_FILTERS = (1 << 4),
+};
+
+enum commit_graph_split_flags {
+ COMMIT_GRAPH_SPLIT_UNSPECIFIED = 0,
+ COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED = 1,
+ COMMIT_GRAPH_SPLIT_REPLACE = 2
};
struct split_commit_graph_opts {
int size_multiple;
int max_commits;
timestamp_t expire_time;
+ enum commit_graph_split_flags flags;
};
/*
@@ -99,7 +120,7 @@ int write_commit_graph_reachable(struct object_directory *odb,
const struct split_commit_graph_opts *split_opts);
int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
- struct string_list *commit_hex,
+ struct oidset *commits,
enum commit_graph_write_flags flags,
const struct split_commit_graph_opts *split_opts);
diff --git a/commit.h b/commit.h
index 008a0fa4a0..ab91d21131 100644
--- a/commit.h
+++ b/commit.h
@@ -249,6 +249,8 @@ struct oid_array;
struct ref;
int register_shallow(struct repository *r, const struct object_id *oid);
int unregister_shallow(const struct object_id *oid);
+int commit_shallow_file(struct repository *r, struct lock_file *lk);
+void rollback_shallow_file(struct repository *r, struct lock_file *lk);
int for_each_commit_graft(each_commit_graft_fn, void *);
int is_repository_shallow(struct repository *r);
struct commit_list *get_shallow_commits(struct object_array *heads,
diff --git a/compat/compiler.h b/compat/compiler.h
new file mode 100644
index 0000000000..10dbb65937
--- /dev/null
+++ b/compat/compiler.h
@@ -0,0 +1,41 @@
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+
+#ifdef __GLIBC__
+#include <gnu/libc-version.h>
+#endif
+
+static inline void get_compiler_info(struct strbuf *info)
+{
+ int len = info->len;
+#ifdef __clang__
+ strbuf_addf(info, "clang: %s\n", __clang_version__);
+#elif defined(__GNUC__)
+ strbuf_addf(info, "gnuc: %d.%d\n", __GNUC__, __GNUC_MINOR__);
+#endif
+
+#ifdef _MSC_VER
+ strbuf_addf(info, "MSVC version: %02d.%02d.%05d\n",
+ _MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 100000);
+#endif
+
+ if (len == info->len)
+ strbuf_addstr(info, _("no compiler information available\n"));
+}
+
+static inline void get_libc_info(struct strbuf *info)
+{
+ int len = info->len;
+
+#ifdef __GLIBC__
+ strbuf_addf(info, "glibc: %s\n", gnu_get_libc_version());
+#endif
+
+ if (len == info->len)
+ strbuf_addstr(info, _("no libc information available\n"));
+}
+
+#endif /* COMPILER_H */
diff --git a/compat/mingw.c b/compat/mingw.c
index d14065d60e..8ee0b6408e 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -460,8 +460,21 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
handle = CreateFileW(wfilename, FILE_APPEND_DATA,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
- if (handle == INVALID_HANDLE_VALUE)
- return errno = err_win_to_posix(GetLastError()), -1;
+ if (handle == INVALID_HANDLE_VALUE) {
+ DWORD err = GetLastError();
+
+ /*
+ * Some network storage solutions (e.g. Isilon) might return
+ * ERROR_INVALID_PARAMETER instead of expected error
+ * ERROR_PATH_NOT_FOUND, which results in an unknown error. If
+ * so, let's turn the error to ERROR_PATH_NOT_FOUND instead.
+ */
+ if (err == ERROR_INVALID_PARAMETER)
+ err = ERROR_PATH_NOT_FOUND;
+
+ errno = err_win_to_posix(err);
+ return -1;
+ }
/*
* No O_APPEND here, because the CRT uses it only to reset the
@@ -964,7 +977,16 @@ revert_attrs:
size_t mingw_strftime(char *s, size_t max,
const char *format, const struct tm *tm)
{
- size_t ret = strftime(s, max, format, tm);
+ /* a pointer to the original strftime in case we can't find the UCRT version */
+ static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime;
+ size_t ret;
+ DECLARE_PROC_ADDR(ucrtbase.dll, size_t, strftime, char *, size_t,
+ const char *, const struct tm *);
+
+ if (INIT_PROC_ADDR(strftime))
+ ret = strftime(s, max, format, tm);
+ else
+ ret = fallback(s, max, format, tm);
if (!ret && errno == EINVAL)
die("invalid strftime format: '%s'", format);
@@ -1479,6 +1501,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
const char *(*quote_arg)(const char *arg) =
is_msys2_sh(cmd ? cmd : *argv) ?
quote_arg_msys2 : quote_arg_msvc;
+ const char *strace_env;
/* Make sure to override previous errors, if any */
errno = 0;
@@ -1562,6 +1585,31 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
free(quoted);
}
+ strace_env = getenv("GIT_STRACE_COMMANDS");
+ if (strace_env) {
+ char *p = path_lookup("strace.exe", 1);
+ if (!p)
+ return error("strace not found!");
+ if (xutftowcs_path(wcmd, p) < 0) {
+ free(p);
+ return -1;
+ }
+ free(p);
+ if (!strcmp("1", strace_env) ||
+ !strcasecmp("yes", strace_env) ||
+ !strcasecmp("true", strace_env))
+ strbuf_insert(&args, 0, "strace ", 7);
+ else {
+ const char *quoted = quote_arg(strace_env);
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "strace -o %s ", quoted);
+ if (quoted != strace_env)
+ free((char *)quoted);
+ strbuf_insert(&args, 0, buf.buf, buf.len);
+ strbuf_release(&buf);
+ }
+ }
+
ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1));
xutftowcs(wargs, args.buf, 2 * args.len + 1);
strbuf_release(&args);
@@ -2581,12 +2629,14 @@ not_a_reserved_name:
continue;
}
break;
- case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
+ case 'c': case 'C':
+ /* COM1 ... COM9, CON, CONIN$, CONOUT$ */
if ((c = path[++i]) != 'o' && c != 'O')
goto not_a_reserved_name;
c = path[++i];
- if (c == 'm' || c == 'M') { /* COM<N> */
- if (!isdigit(path[++i]))
+ if (c == 'm' || c == 'M') { /* COM1 ... COM9 */
+ c = path[++i];
+ if (c < '1' || c > '9')
goto not_a_reserved_name;
} else if (c == 'n' || c == 'N') { /* CON */
c = path[i + 1];
diff --git a/compat/regex/regex.c b/compat/regex/regex.c
index f3e03a9eab..e6f4a5d177 100644
--- a/compat/regex/regex.c
+++ b/compat/regex/regex.c
@@ -60,6 +60,7 @@
#undefs RE_DUP_MAX and sets it to the right value. */
#include <limits.h>
#include <stdint.h>
+#include <stdlib.h>
#ifdef GAWK
#undef alloca
diff --git a/compat/regex/regex_internal.h b/compat/regex/regex_internal.h
index 3ee8aae59d..0bad8b841e 100644
--- a/compat/regex/regex_internal.h
+++ b/compat/regex/regex_internal.h
@@ -23,7 +23,6 @@
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
diff --git a/compat/vcbuild/README b/compat/vcbuild/README
index 1b6dabf5a2..42292e7c09 100644
--- a/compat/vcbuild/README
+++ b/compat/vcbuild/README
@@ -92,8 +92,8 @@ The Steps of Build Git with VS2008
the git operations.
3. Inside Git's directory run the command:
- make command-list.h
- to generate the command-list.h file needed to compile git.
+ make command-list.h config-list.h
+ to generate the header file needed to compile git.
4. Then either build Git with the GNU Make Makefile in the Git projects
root
diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h
index f2e70872cd..bba2b64408 100644
--- a/compat/win32/path-utils.h
+++ b/compat/win32/path-utils.h
@@ -20,6 +20,17 @@ static inline char *win32_find_last_dir_sep(const char *path)
return ret;
}
#define find_last_dir_sep win32_find_last_dir_sep
+static inline int win32_has_dir_sep(const char *path)
+{
+ /*
+ * See how long the non-separator part of the given path is, and
+ * if and only if it covers the whole path (i.e. path[len] is NUL),
+ * there is no separator in the path---otherwise there is a separator.
+ */
+ size_t len = strcspn(path, "/\\");
+ return !!path[len];
+}
+#define has_dir_sep(path) win32_has_dir_sep(path)
int win32_offset_1st_component(const char *path);
#define offset_1st_component win32_offset_1st_component
diff --git a/config.c b/config.c
index d17d2bd9dc..8db9c77098 100644
--- a/config.c
+++ b/config.c
@@ -37,6 +37,7 @@ struct config_source {
enum config_error_action default_error_action;
int linenr;
int eof;
+ size_t total_len;
struct strbuf value;
struct strbuf var;
unsigned subsection_case_sensitive : 1;
@@ -309,7 +310,7 @@ int git_config_include(const char *var, const char *value, void *data)
{
struct config_include_data *inc = data;
const char *cond, *key;
- int cond_len;
+ size_t cond_len;
int ret;
/*
@@ -358,12 +359,13 @@ static inline int iskeychar(int c)
*
* store_key - pointer to char* which will hold a copy of the key with
* lowercase section and variable name
- * baselen - pointer to int which will hold the length of the
+ * 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, int *baselen_, int quiet)
+static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet)
{
- int i, dot, baselen;
+ size_t i, baselen;
+ int dot;
const char *last_dot = strrchr(key, '.');
/*
@@ -425,7 +427,7 @@ out_free_ret_1:
return -CONFIG_INVALID_KEY;
}
-int git_config_parse_key(const char *key, char **store_key, int *baselen)
+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);
}
@@ -523,6 +525,19 @@ static int get_next_char(void)
c = '\r';
}
}
+
+ if (c != EOF && ++cf->total_len > INT_MAX) {
+ /*
+ * This is an absurdly long config file; refuse to parse
+ * further in order to protect downstream code from integer
+ * overflows. Note that we can't return an error specifically,
+ * but we can mark EOF and put trash in the return value,
+ * which will trigger a parse error.
+ */
+ cf->eof = 1;
+ return 0;
+ }
+
if (c == '\n')
cf->linenr++;
if (c == EOF) {
@@ -728,7 +743,7 @@ static int git_parse_source(config_fn_t fn, void *data,
const struct config_options *opts)
{
int comment = 0;
- int baselen = 0;
+ size_t baselen = 0;
struct strbuf *var = &cf->var;
int error_return = 0;
char *error_msg = NULL;
@@ -1539,6 +1554,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
top->prev = cf;
top->linenr = 1;
top->eof = 0;
+ top->total_len = 0;
strbuf_init(&top->value, 1024);
strbuf_init(&top->var, 1024);
cf = top;
@@ -2383,7 +2399,7 @@ void git_die_config(const char *key, const char *err, ...)
*/
struct config_store_data {
- int baselen;
+ size_t baselen;
char *key;
int do_not_match;
regex_t *value_regex;
@@ -2509,7 +2525,7 @@ static struct strbuf store_create_section(const char *key,
const struct config_store_data *store)
{
const char *dot;
- int i;
+ size_t i;
struct strbuf sb = STRBUF_INIT;
dot = memchr(key, '.', store->baselen);
@@ -2522,7 +2538,9 @@ static struct strbuf store_create_section(const char *key,
}
strbuf_addstr(&sb, "\"]\n");
} else {
- strbuf_addf(&sb, "[%.*s]\n", store->baselen, key);
+ strbuf_addch(&sb, '[');
+ strbuf_add(&sb, key, store->baselen);
+ strbuf_addstr(&sb, "]\n");
}
return sb;
@@ -2545,7 +2563,6 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
{
int i;
ssize_t ret;
- int length = strlen(key + store->baselen + 1);
const char *quote = "";
struct strbuf sb = STRBUF_INIT;
@@ -2564,8 +2581,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
if (i && value[i - 1] == ' ')
quote = "\"";
- strbuf_addf(&sb, "\t%.*s = %s",
- length, key + store->baselen + 1, quote);
+ strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote);
for (i = 0; value[i]; i++)
switch (value[i]) {
@@ -3238,7 +3254,7 @@ int config_error_nonbool(const char *var)
int parse_config_key(const char *var,
const char *section,
- const char **subsection, int *subsection_len,
+ const char **subsection, size_t *subsection_len,
const char **key)
{
const char *dot;
diff --git a/config.h b/config.h
index 9b3773f778..060874488f 100644
--- a/config.h
+++ b/config.h
@@ -254,7 +254,7 @@ 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 **, int *);
+int git_config_parse_key(const char *, char **, size_t *);
int git_config_key_is_valid(const char *key);
int git_config_set_multivar_gently(const char *, const char *, const char *, int);
void git_config_set_multivar(const char *, const char *, const char *, int);
@@ -359,7 +359,7 @@ int git_config_include(const char *name, const char *value, void *data);
*/
int parse_config_key(const char *var,
const char *section,
- const char **subsection, int *subsection_len,
+ const char **subsection, size_t *subsection_len,
const char **key);
/**
diff --git a/config.mak.uname b/config.mak.uname
index 0ab8e00938..5ad43c80b1 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -133,8 +133,17 @@ ifeq ($(uname_S),Darwin)
HAVE_BSD_SYSCTL = YesPlease
FREAD_READS_DIRECTORIES = UnfortunatelyYes
HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
- BASIC_CFLAGS += -I/usr/local/include
- BASIC_LDFLAGS += -L/usr/local/lib
+
+ # Workaround for `gettext` being keg-only and not even being linked via
+ # `brew link --force gettext`, should be obsolete as of
+ # https://github.com/Homebrew/homebrew-core/pull/53489
+ ifeq ($(shell test -d /usr/local/opt/gettext/ && echo y),y)
+ BASIC_CFLAGS += -I/usr/local/include -I/usr/local/opt/gettext/include
+ BASIC_LDFLAGS += -L/usr/local/lib -L/usr/local/opt/gettext/lib
+ ifeq ($(shell test -x /usr/local/opt/gettext/bin/msgfmt && echo y),y)
+ MSGFMT = /usr/local/opt/gettext/bin/msgfmt
+ endif
+ endif
endif
ifeq ($(uname_S),SunOS)
NEEDS_SOCKET = YesPlease
@@ -308,6 +317,7 @@ ifeq ($(uname_S),GNU)
NO_STRLCPY = YesPlease
HAVE_PATHS_H = YesPlease
LIBC_CONTAINS_LIBINTL = YesPlease
+ FREAD_READS_DIRECTORIES = UnfortunatelyYes
endif
ifeq ($(uname_S),IRIX)
NO_SETENV = YesPlease
@@ -721,9 +731,9 @@ vcxproj:
echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets
git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets
- # Add command-list.h
- $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 command-list.h
- git add -f command-list.h
+ # 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 scripts
rm -f perl/perl.mak
diff --git a/connect.c b/connect.c
index b6451ab5e8..23013c6344 100644
--- a/connect.c
+++ b/connect.c
@@ -9,7 +9,7 @@
#include "connect.h"
#include "url.h"
#include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "transport.h"
#include "strbuf.h"
#include "version.h"
diff --git a/connected.c b/connected.c
index ac52b07b47..3135b71e19 100644
--- a/connected.c
+++ b/connected.c
@@ -52,7 +52,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
strbuf_release(&idx_file);
}
- if (opt->check_refs_are_promisor_objects_only) {
+ if (has_promisor_remote()) {
/*
* For partial clones, we don't want to have to do a regular
* connectivity check because we have to enumerate and exclude
@@ -75,13 +75,18 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
if (find_pack_entry_one(oid.hash, p))
goto promisor_pack_found;
}
- return 1;
+ /*
+ * Fallback to rev-list with oid and the rest of the
+ * object IDs provided by fn.
+ */
+ goto no_promisor_pack_found;
promisor_pack_found:
;
} while (!fn(cb_data, &oid));
return 0;
}
+no_promisor_pack_found:
if (opt->shallow_file) {
argv_array_push(&rev_list.args, "--shallow-file");
argv_array_push(&rev_list.args, opt->shallow_file);
diff --git a/connected.h b/connected.h
index eba5c261ba..8d5a6b3ad6 100644
--- a/connected.h
+++ b/connected.h
@@ -46,15 +46,6 @@ struct check_connected_options {
* during a fetch.
*/
unsigned is_deepening_fetch : 1;
-
- /*
- * If non-zero, only check that the top-level objects referenced by the
- * wanted refs (passed in as cb_data) are promisor objects. This is
- * useful for partial clones, where enumerating and excluding all
- * promisor objects is very slow and the commit-walk itself becomes a
- * no-op.
- */
- unsigned check_refs_are_promisor_objects_only : 1;
};
#define CHECK_CONNECTED_INIT { 0 }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index c21786f2fd..b1d6e5ebed 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -504,7 +504,7 @@ __git_index_files ()
{
local root="$2" match="$3"
- __git_ls_files_helper "$root" "$1" "$match" |
+ __git_ls_files_helper "$root" "$1" "${match:-?}" |
awk -F / -v pfx="${2//\\/\\\\}" '{
paths[$1] = 1
}
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index eef4eff53d..ce47e86b60 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -150,9 +150,11 @@ __git_zsh_cmd_common ()
push:'update remote refs along with associated objects'
rebase:'forward-port local commits to the updated upstream head'
reset:'reset current HEAD to the specified state'
+ restore:'restore working tree files'
rm:'remove files from the working tree and from the index'
show:'show various types of objects'
status:'show the working tree status'
+ switch:'switch branches'
tag:'create, list, delete or verify a tag object signed with GPG')
_describe -t common-commands 'common commands' list && _ret=0
}
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
index e800d9f5c9..d50ce26d5d 100755
--- a/contrib/fast-import/import-tars.perl
+++ b/contrib/fast-import/import-tars.perl
@@ -139,6 +139,8 @@ foreach my $tar_file (@ARGV)
print FI "\n";
}
+ next if ($typeflag eq 'g'); # ignore global header
+
my $path;
if ($prefix) {
$path = "$prefix/$name";
diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile
index 6906aae441..6fa7496bfd 100644
--- a/contrib/subtree/Makefile
+++ b/contrib/subtree/Makefile
@@ -25,14 +25,16 @@ ASCIIDOC_HTML = xhtml11
ASCIIDOC_DOCBOOK = docbook
ASCIIDOC_EXTRA =
XMLTO = xmlto
+XMLTO_EXTRA =
ifdef USE_ASCIIDOCTOR
ASCIIDOC = asciidoctor
ASCIIDOC_CONF =
ASCIIDOC_HTML = xhtml5
-ASCIIDOC_DOCBOOK = docbook45
+ASCIIDOC_DOCBOOK = docbook
ASCIIDOC_EXTRA += -I../../Documentation -rasciidoctor-extensions
ASCIIDOC_EXTRA += -alitdd='&\#x2d;&\#x2d;'
+XMLTO_EXTRA += --skip-validation
endif
ifndef SHELL_PATH
@@ -78,7 +80,7 @@ install-html: $(GIT_SUBTREE_HTML)
$(INSTALL) -m 644 $^ $(DESTDIR)$(htmldir)
$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
- $(XMLTO) -m $(MANPAGE_XSL) man $^
+ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $^
$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
$(ASCIIDOC) -b $(ASCIIDOC_DOCBOOK) -d manpage $(ASCIIDOC_CONF) \
diff --git a/convert.c b/convert.c
index 5aa87d45e3..572449825c 100644
--- a/convert.c
+++ b/convert.c
@@ -1018,7 +1018,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
static int read_convert_config(const char *var, const char *value, void *cb)
{
const char *key, *name;
- int namelen;
+ size_t namelen;
struct convert_driver *drv;
/*
diff --git a/credential.c b/credential.c
index 21b3ba152f..d72e2ed0d8 100644
--- a/credential.c
+++ b/credential.c
@@ -88,6 +88,11 @@ static void credential_apply_config(struct credential *c)
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
struct strbuf url = STRBUF_INIT;
+ if (!c->host)
+ die(_("refusing to work with credential missing host field"));
+ if (!c->protocol)
+ die(_("refusing to work with credential missing protocol field"));
+
if (c->configured)
return;
@@ -131,14 +136,14 @@ static void credential_format(struct credential *c, struct strbuf *out)
return;
strbuf_addf(out, "%s://", c->protocol);
if (c->username && *c->username) {
- strbuf_add_percentencode(out, c->username);
+ strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH);
strbuf_addch(out, '@');
}
if (c->host)
strbuf_addstr(out, c->host);
if (c->path) {
strbuf_addch(out, '/');
- strbuf_add_percentencode(out, c->path);
+ strbuf_add_percentencode(out, c->path, 0);
}
}
@@ -222,8 +227,11 @@ int credential_read(struct credential *c, FILE *fp)
return 0;
}
-static void credential_write_item(FILE *fp, const char *key, const char *value)
+static void credential_write_item(FILE *fp, const char *key, const char *value,
+ int required)
{
+ if (!value && required)
+ BUG("credential value for %s is missing", key);
if (!value)
return;
if (strchr(value, '\n'))
@@ -233,11 +241,11 @@ static void credential_write_item(FILE *fp, const char *key, const char *value)
void credential_write(const struct credential *c, FILE *fp)
{
- credential_write_item(fp, "protocol", c->protocol);
- credential_write_item(fp, "host", c->host);
- credential_write_item(fp, "path", c->path);
- credential_write_item(fp, "username", c->username);
- credential_write_item(fp, "password", c->password);
+ credential_write_item(fp, "protocol", c->protocol, 1);
+ credential_write_item(fp, "host", c->host, 1);
+ credential_write_item(fp, "path", c->path, 0);
+ credential_write_item(fp, "username", c->username, 0);
+ credential_write_item(fp, "password", c->password, 0);
}
static int run_credential_helper(struct credential *c,
@@ -383,12 +391,22 @@ int credential_from_url_gently(struct credential *c, const char *url,
* (3) proto://<user>:<pass>@<host>/...
*/
proto_end = strstr(url, "://");
- if (!proto_end)
- return 0;
+ if (!proto_end || proto_end == url) {
+ if (!quiet)
+ warning(_("url has no scheme: %s"), url);
+ return -1;
+ }
cp = proto_end + 3;
at = strchr(cp, '@');
colon = strchr(cp, ':');
- slash = strchrnul(cp, '/');
+
+ /*
+ * A query or fragment marker before the slash ends the host portion.
+ * We'll just continue to call this "slash" for simplicity. Notably our
+ * "trim leading slashes" part won't skip over this part of the path,
+ * but that's what we'd want.
+ */
+ slash = cp + strcspn(cp, "/?#");
if (!at || slash <= at) {
/* Case (1) */
@@ -409,10 +427,8 @@ int credential_from_url_gently(struct credential *c, const char *url,
host = at + 1;
}
- if (proto_end - url > 0)
- c->protocol = xmemdupz(url, proto_end - url);
- if (slash - host > 0)
- c->host = url_decode_mem(host, slash - host);
+ c->protocol = xmemdupz(url, proto_end - url);
+ c->host = url_decode_mem(host, slash - host);
/* Trim leading and trailing slashes from path */
while (*slash == '/')
slash++;
@@ -436,8 +452,6 @@ int credential_from_url_gently(struct credential *c, const char *url,
void credential_from_url(struct credential *c, const char *url)
{
- if (credential_from_url_gently(c, url, 0) < 0) {
- warning(_("skipping credential lookup for url: %s"), url);
- credential_clear(c);
- }
+ if (credential_from_url_gently(c, url, 0) < 0)
+ die(_("credential url cannot be parsed: %s"), url);
}
diff --git a/delta-islands.c b/delta-islands.c
index 09dbd3cf72..aa98b2e541 100644
--- a/delta-islands.c
+++ b/delta-islands.c
@@ -17,7 +17,7 @@
#include "pack-bitmap.h"
#include "pack-objects.h"
#include "delta-islands.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "config.h"
KHASH_INIT(str, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
diff --git a/diff.c b/diff.c
index 1010d806f5..d1ad6a3c4a 100644
--- a/diff.c
+++ b/diff.c
@@ -573,7 +573,7 @@ static int fill_mmfile(struct repository *r, mmfile_t *mf,
mf->size = 0;
return 0;
}
- else if (diff_populate_filespec(r, one, 0))
+ else if (diff_populate_filespec(r, one, NULL))
return -1;
mf->ptr = one->data;
@@ -585,9 +585,13 @@ static int fill_mmfile(struct repository *r, mmfile_t *mf,
static unsigned long diff_filespec_size(struct repository *r,
struct diff_filespec *one)
{
+ struct diff_populate_filespec_options dpf_options = {
+ .check_size_only = 1,
+ };
+
if (!DIFF_FILE_VALID(one))
return 0;
- diff_populate_filespec(r, one, CHECK_SIZE_ONLY);
+ diff_populate_filespec(r, one, &dpf_options);
return one->size;
}
@@ -3020,6 +3024,9 @@ static void show_dirstat(struct diff_options *options)
struct diff_filepair *p = q->queue[i];
const char *name;
unsigned long copied, added, damage;
+ struct diff_populate_filespec_options dpf_options = {
+ .check_size_only = 1,
+ };
name = p->two->path ? p->two->path : p->one->path;
@@ -3047,19 +3054,19 @@ static void show_dirstat(struct diff_options *options)
}
if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
- diff_populate_filespec(options->repo, p->one, 0);
- diff_populate_filespec(options->repo, p->two, 0);
+ diff_populate_filespec(options->repo, p->one, NULL);
+ diff_populate_filespec(options->repo, p->two, NULL);
diffcore_count_changes(options->repo,
p->one, p->two, NULL, NULL,
&copied, &added);
diff_free_filespec_data(p->one);
diff_free_filespec_data(p->two);
} else if (DIFF_FILE_VALID(p->one)) {
- diff_populate_filespec(options->repo, p->one, CHECK_SIZE_ONLY);
+ diff_populate_filespec(options->repo, p->one, &dpf_options);
copied = added = 0;
diff_free_filespec_data(p->one);
} else if (DIFF_FILE_VALID(p->two)) {
- diff_populate_filespec(options->repo, p->two, CHECK_SIZE_ONLY);
+ diff_populate_filespec(options->repo, p->two, &dpf_options);
copied = 0;
added = p->two->size;
diff_free_filespec_data(p->two);
@@ -3339,13 +3346,17 @@ static void emit_binary_diff(struct diff_options *o,
int diff_filespec_is_binary(struct repository *r,
struct diff_filespec *one)
{
+ struct diff_populate_filespec_options dpf_options = {
+ .check_binary = 1,
+ };
+
if (one->is_binary == -1) {
diff_filespec_load_driver(one, r->index);
if (one->driver->binary != -1)
one->is_binary = one->driver->binary;
else {
if (!one->data && DIFF_FILE_VALID(one))
- diff_populate_filespec(r, one, CHECK_BINARY);
+ diff_populate_filespec(r, one, &dpf_options);
if (one->is_binary == -1 && one->data)
one->is_binary = buffer_is_binary(one->data,
one->size);
@@ -3677,8 +3688,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
}
else if (complete_rewrite) {
- diff_populate_filespec(o->repo, one, 0);
- diff_populate_filespec(o->repo, two, 0);
+ diff_populate_filespec(o->repo, one, NULL);
+ diff_populate_filespec(o->repo, two, NULL);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
}
@@ -3914,9 +3925,10 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
*/
int diff_populate_filespec(struct repository *r,
struct diff_filespec *s,
- unsigned int flags)
+ const struct diff_populate_filespec_options *options)
{
- int size_only = flags & CHECK_SIZE_ONLY;
+ int size_only = options ? options->check_size_only : 0;
+ int check_binary = options ? options->check_binary : 0;
int err = 0;
int conv_flags = global_conv_flags_eol;
/*
@@ -3986,7 +3998,7 @@ int diff_populate_filespec(struct repository *r,
* opening the file and inspecting the contents, this
* is probably fine.
*/
- if ((flags & CHECK_BINARY) &&
+ if (check_binary &&
s->size > big_file_threshold && s->is_binary == -1) {
s->is_binary = 1;
return 0;
@@ -4011,12 +4023,30 @@ int diff_populate_filespec(struct repository *r,
}
}
else {
- enum object_type type;
- if (size_only || (flags & CHECK_BINARY)) {
- type = oid_object_info(r, &s->oid, &s->size);
- if (type < 0)
- die("unable to read %s",
- oid_to_hex(&s->oid));
+ struct object_info info = {
+ .sizep = &s->size
+ };
+
+ if (!(size_only || check_binary))
+ /*
+ * Set contentp, since there is no chance that merely
+ * the size is sufficient.
+ */
+ info.contentp = &s->data;
+
+ if (options && options->missing_object_cb) {
+ if (!oid_object_info_extended(r, &s->oid, &info,
+ OBJECT_INFO_LOOKUP_REPLACE |
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
+ goto object_read;
+ options->missing_object_cb(options->missing_object_data);
+ }
+ if (oid_object_info_extended(r, &s->oid, &info,
+ OBJECT_INFO_LOOKUP_REPLACE))
+ die("unable to read %s", oid_to_hex(&s->oid));
+
+object_read:
+ if (size_only || check_binary) {
if (size_only)
return 0;
if (s->size > big_file_threshold && s->is_binary == -1) {
@@ -4024,9 +4054,12 @@ int diff_populate_filespec(struct repository *r,
return 0;
}
}
- s->data = repo_read_object_file(r, &s->oid, &type, &s->size);
- if (!s->data)
- die("unable to read %s", oid_to_hex(&s->oid));
+ if (!info.contentp) {
+ info.contentp = &s->data;
+ if (oid_object_info_extended(r, &s->oid, &info,
+ OBJECT_INFO_LOOKUP_REPLACE))
+ die("unable to read %s", oid_to_hex(&s->oid));
+ }
s->should_free = 1;
}
return 0;
@@ -4144,7 +4177,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
return temp;
}
else {
- if (diff_populate_filespec(r, one, 0))
+ if (diff_populate_filespec(r, one, NULL))
die("cannot read data blob for %s", one->path);
prep_temp_blob(r->index, name, temp,
one->data, one->size,
@@ -6410,9 +6443,9 @@ static int diff_filespec_is_identical(struct repository *r,
{
if (S_ISGITLINK(one->mode))
return 0;
- if (diff_populate_filespec(r, one, 0))
+ if (diff_populate_filespec(r, one, NULL))
return 0;
- if (diff_populate_filespec(r, two, 0))
+ if (diff_populate_filespec(r, two, NULL))
return 0;
return !memcmp(one->data, two->data, one->size);
}
@@ -6420,6 +6453,12 @@ static int diff_filespec_is_identical(struct repository *r,
static int diff_filespec_check_stat_unmatch(struct repository *r,
struct diff_filepair *p)
{
+ struct diff_populate_filespec_options dpf_options = {
+ .check_size_only = 1,
+ .missing_object_cb = diff_queued_diff_prefetch,
+ .missing_object_data = r,
+ };
+
if (p->done_skip_stat_unmatch)
return p->skip_stat_unmatch_result;
@@ -6442,8 +6481,8 @@ static int diff_filespec_check_stat_unmatch(struct repository *r,
!DIFF_FILE_VALID(p->two) ||
(p->one->oid_valid && p->two->oid_valid) ||
(p->one->mode != p->two->mode) ||
- diff_populate_filespec(r, p->one, CHECK_SIZE_ONLY) ||
- diff_populate_filespec(r, p->two, CHECK_SIZE_ONLY) ||
+ diff_populate_filespec(r, p->one, &dpf_options) ||
+ diff_populate_filespec(r, p->two, &dpf_options) ||
(p->one->size != p->two->size) ||
!diff_filespec_is_identical(r, p->one, p->two)) /* (2) */
p->skip_stat_unmatch_result = 1;
@@ -6494,9 +6533,9 @@ void diffcore_fix_diff_index(void)
QSORT(q->queue, q->nr, diffnamecmp);
}
-static void add_if_missing(struct repository *r,
- struct oid_array *to_fetch,
- const struct diff_filespec *filespec)
+void diff_add_if_missing(struct repository *r,
+ struct oid_array *to_fetch,
+ const struct diff_filespec *filespec)
{
if (filespec && filespec->oid_valid &&
!S_ISGITLINK(filespec->mode) &&
@@ -6505,30 +6544,48 @@ static void add_if_missing(struct repository *r,
oid_array_append(to_fetch, &filespec->oid);
}
-void diffcore_std(struct diff_options *options)
+void diff_queued_diff_prefetch(void *repository)
{
- if (options->repo == the_repository && has_promisor_remote()) {
- /*
- * Prefetch the diff pairs that are about to be flushed.
- */
- int i;
- struct diff_queue_struct *q = &diff_queued_diff;
- struct oid_array to_fetch = OID_ARRAY_INIT;
+ struct repository *repo = repository;
+ int i;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct oid_array to_fetch = OID_ARRAY_INIT;
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- add_if_missing(options->repo, &to_fetch, p->one);
- add_if_missing(options->repo, &to_fetch, p->two);
- }
- if (to_fetch.nr)
- /*
- * NEEDSWORK: Consider deduplicating the OIDs sent.
- */
- promisor_remote_get_direct(options->repo,
- to_fetch.oid, to_fetch.nr);
- oid_array_clear(&to_fetch);
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ diff_add_if_missing(repo, &to_fetch, p->one);
+ diff_add_if_missing(repo, &to_fetch, p->two);
}
+ /*
+ * NEEDSWORK: Consider deduplicating the OIDs sent.
+ */
+ promisor_remote_get_direct(repo, to_fetch.oid, to_fetch.nr);
+
+ oid_array_clear(&to_fetch);
+}
+
+void diffcore_std(struct diff_options *options)
+{
+ int output_formats_to_prefetch = DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_NUMSTAT |
+ DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_SHORTSTAT |
+ DIFF_FORMAT_DIRSTAT;
+
+ /*
+ * Check if the user requested a blob-data-requiring diff output and/or
+ * break-rewrite detection (which requires blob data). If yes, prefetch
+ * the diff pairs.
+ *
+ * If no prefetching occurs, diffcore_rename() will prefetch if it
+ * decides that it needs inexact rename detection.
+ */
+ if (options->repo == the_repository && has_promisor_remote() &&
+ (options->output_format & output_formats_to_prefetch ||
+ options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
+ diff_queued_diff_prefetch(options->repo);
+
/* NOTE please keep the following in sync with diff_tree_combined() */
if (options->skip_stat_unmatch)
diffcore_skip_stat_unmatch(options);
@@ -6774,7 +6831,7 @@ size_t fill_textconv(struct repository *r,
*outbuf = "";
return 0;
}
- if (diff_populate_filespec(r, df, 0))
+ if (diff_populate_filespec(r, df, NULL))
die("unable to read files to diff");
*outbuf = df->data;
return df->size;
diff --git a/diff.h b/diff.h
index 6febe7e365..9443dc1b00 100644
--- a/diff.h
+++ b/diff.h
@@ -285,6 +285,11 @@ struct diff_options {
/* Number of hexdigits to abbreviate raw format output to. */
int abbrev;
+ /* If non-zero, then stop computing after this many changes. */
+ int max_changes;
+ /* For internal use only. */
+ int num_changes;
+
int ita_invisible_in_index;
/* white-space error highlighting */
#define WSEH_NEW (1<<12)
diff --git a/diffcore-break.c b/diffcore-break.c
index 9d20a6a6fc..0d4a14964d 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
+#include "promisor-remote.h"
static int should_break(struct repository *r,
struct diff_filespec *src,
@@ -49,6 +50,8 @@ static int should_break(struct repository *r,
unsigned long delta_size, max_size;
unsigned long src_copied, literal_added, src_removed;
+ struct diff_populate_filespec_options options = { 0 };
+
*merge_score_p = 0; /* assume no deletion --- "do not break"
* is the default.
*/
@@ -62,8 +65,13 @@ static int should_break(struct repository *r,
oideq(&src->oid, &dst->oid))
return 0; /* they are the same */
- if (diff_populate_filespec(r, src, 0) ||
- diff_populate_filespec(r, dst, 0))
+ if (r == the_repository && has_promisor_remote()) {
+ options.missing_object_cb = diff_queued_diff_prefetch;
+ options.missing_object_data = r;
+ }
+
+ if (diff_populate_filespec(r, src, &options) ||
+ diff_populate_filespec(r, dst, &options))
return 0; /* error but caught downstream */
max_size = ((src->size > dst->size) ? src->size : dst->size);
diff --git a/diffcore-rename.c b/diffcore-rename.c
index e189f407af..99e63e90f8 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -1,4 +1,5 @@
/*
+ *
* Copyright (C) 2005 Junio C Hamano
*/
#include "cache.h"
@@ -7,6 +8,7 @@
#include "object-store.h"
#include "hashmap.h"
#include "progress.h"
+#include "promisor-remote.h"
/* Table of rename/copy destinations */
@@ -128,10 +130,46 @@ struct diff_score {
short name_score;
};
+struct prefetch_options {
+ struct repository *repo;
+ int skip_unmodified;
+};
+static void prefetch(void *prefetch_options)
+{
+ struct prefetch_options *options = prefetch_options;
+ int i;
+ struct oid_array to_fetch = OID_ARRAY_INIT;
+
+ for (i = 0; i < rename_dst_nr; i++) {
+ if (rename_dst[i].pair)
+ /*
+ * The loop in diffcore_rename() will not need these
+ * blobs, so skip prefetching.
+ */
+ continue; /* already found exact match */
+ diff_add_if_missing(options->repo, &to_fetch,
+ rename_dst[i].two);
+ }
+ for (i = 0; i < rename_src_nr; i++) {
+ if (options->skip_unmodified &&
+ diff_unmodified_pair(rename_src[i].p))
+ /*
+ * The loop in diffcore_rename() will not need these
+ * blobs, so skip prefetching.
+ */
+ continue;
+ diff_add_if_missing(options->repo, &to_fetch,
+ rename_src[i].p->one);
+ }
+ promisor_remote_get_direct(options->repo, to_fetch.oid, to_fetch.nr);
+ oid_array_clear(&to_fetch);
+}
+
static int estimate_similarity(struct repository *r,
struct diff_filespec *src,
struct diff_filespec *dst,
- int minimum_score)
+ int minimum_score,
+ int skip_unmodified)
{
/* src points at a file that existed in the original tree (or
* optionally a file in the destination tree) and dst points
@@ -148,6 +186,15 @@ static int estimate_similarity(struct repository *r,
*/
unsigned long max_size, delta_size, base_size, src_copied, literal_added;
int score;
+ struct diff_populate_filespec_options dpf_options = {
+ .check_size_only = 1
+ };
+ struct prefetch_options prefetch_options = {r, skip_unmodified};
+
+ if (r == the_repository && has_promisor_remote()) {
+ dpf_options.missing_object_cb = prefetch;
+ dpf_options.missing_object_data = &prefetch_options;
+ }
/* We deal only with regular files. Symlink renames are handled
* only when they are exact matches --- in other words, no edits
@@ -166,10 +213,10 @@ static int estimate_similarity(struct repository *r,
* say whether the size is valid or not!)
*/
if (!src->cnt_data &&
- diff_populate_filespec(r, src, CHECK_SIZE_ONLY))
+ diff_populate_filespec(r, src, &dpf_options))
return 0;
if (!dst->cnt_data &&
- diff_populate_filespec(r, dst, CHECK_SIZE_ONLY))
+ diff_populate_filespec(r, dst, &dpf_options))
return 0;
max_size = ((src->size > dst->size) ? src->size : dst->size);
@@ -187,9 +234,11 @@ static int estimate_similarity(struct repository *r,
if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
return 0;
- if (!src->cnt_data && diff_populate_filespec(r, src, 0))
+ dpf_options.check_size_only = 0;
+
+ if (!src->cnt_data && diff_populate_filespec(r, src, &dpf_options))
return 0;
- if (!dst->cnt_data && diff_populate_filespec(r, dst, 0))
+ if (!dst->cnt_data && diff_populate_filespec(r, dst, &dpf_options))
return 0;
if (diffcore_count_changes(r, src, dst,
@@ -261,7 +310,7 @@ static unsigned int hash_filespec(struct repository *r,
struct diff_filespec *filespec)
{
if (!filespec->oid_valid) {
- if (diff_populate_filespec(r, filespec, 0))
+ if (diff_populate_filespec(r, filespec, NULL))
return 0;
hash_object_file(r->hash_algo, filespec->data, filespec->size,
"blob", &filespec->oid);
@@ -566,7 +615,8 @@ void diffcore_rename(struct diff_options *options)
this_src.score = estimate_similarity(options->repo,
one, two,
- minimum_score);
+ minimum_score,
+ skip_unmodified);
this_src.name_score = basename_same(one, two);
this_src.dst = i;
this_src.src = j;
diff --git a/diffcore.h b/diffcore.h
index 7c07347e42..d2a63c5c71 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -65,9 +65,25 @@ void free_filespec(struct diff_filespec *);
void fill_filespec(struct diff_filespec *, const struct object_id *,
int, unsigned short);
-#define CHECK_SIZE_ONLY 1
-#define CHECK_BINARY 2
-int diff_populate_filespec(struct repository *, struct diff_filespec *, unsigned int);
+/*
+ * Prefetch the entries in diff_queued_diff. The parameter is a pointer to a
+ * struct repository.
+ */
+void diff_queued_diff_prefetch(void *repository);
+
+struct diff_populate_filespec_options {
+ unsigned check_size_only : 1;
+ unsigned check_binary : 1;
+
+ /*
+ * If an object is missing, diff_populate_filespec() will invoke this
+ * callback before attempting to read that object again.
+ */
+ void (*missing_object_cb)(void *);
+ void *missing_object_data;
+};
+int diff_populate_filespec(struct repository *, struct diff_filespec *,
+ const struct diff_populate_filespec_options *);
void diff_free_filespec_data(struct diff_filespec *);
void diff_free_filespec_blob(struct diff_filespec *);
int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
@@ -182,4 +198,12 @@ int diffcore_count_changes(struct repository *r,
unsigned long *src_copied,
unsigned long *literal_added);
+/*
+ * If filespec contains an OID and if that object is missing from the given
+ * repository, add that OID to to_fetch.
+ */
+void diff_add_if_missing(struct repository *r,
+ struct oid_array *to_fetch,
+ const struct diff_filespec *filespec);
+
#endif
diff --git a/dir.c b/dir.c
index 0ffb1b3302..d97e955848 100644
--- a/dir.c
+++ b/dir.c
@@ -1727,36 +1727,59 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
static enum path_treatment treat_directory(struct dir_struct *dir,
struct index_state *istate,
struct untracked_cache_dir *untracked,
- const char *dirname, int len, int baselen, int exclude,
+ const char *dirname, int len, int baselen, int excluded,
const struct pathspec *pathspec)
{
- int nested_repo = 0;
-
+ /*
+ * WARNING: From this function, you can return path_recurse or you
+ * can call read_directory_recursive() (or neither), but
+ * you CAN'T DO BOTH.
+ */
+ enum path_treatment state;
+ int matches_how = 0;
+ int nested_repo = 0, check_only, stop_early;
+ int old_ignored_nr, old_untracked_nr;
/* The "len-1" is to strip the final '/' */
- switch (directory_exists_in_index(istate, dirname, len-1)) {
- case index_directory:
- return path_recurse;
+ enum exist_status status = directory_exists_in_index(istate, dirname, len-1);
- case index_gitdir:
+ if (status == index_directory)
+ return path_recurse;
+ if (status == index_gitdir)
return path_none;
+ if (status != index_nonexistent)
+ BUG("Unhandled value for directory_exists_in_index: %d\n", status);
- case index_nonexistent:
- if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
- !(dir->flags & DIR_NO_GITLINKS)) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addstr(&sb, dirname);
- nested_repo = is_nonbare_repository_dir(&sb);
- strbuf_release(&sb);
- }
- if (nested_repo)
- return ((dir->flags & DIR_SKIP_NESTED_GIT) ? path_none :
- (exclude ? path_excluded : path_untracked));
+ /*
+ * We don't want to descend into paths that don't match the necessary
+ * patterns. Clearly, if we don't have a pathspec, then we can't check
+ * for matching patterns. Also, if (excluded) then we know we matched
+ * the exclusion patterns so as an optimization we can skip checking
+ * for matching patterns.
+ */
+ if (pathspec && !excluded) {
+ matches_how = do_match_pathspec(istate, pathspec, dirname, len,
+ 0 /* prefix */, NULL /* seen */,
+ DO_MATCH_LEADING_PATHSPEC);
+ if (!matches_how)
+ return path_none;
+ }
- if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
- break;
- if (exclude &&
- (dir->flags & DIR_SHOW_IGNORED_TOO) &&
- (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
+
+ if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
+ !(dir->flags & DIR_NO_GITLINKS)) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addstr(&sb, dirname);
+ nested_repo = is_nonbare_repository_dir(&sb);
+ strbuf_release(&sb);
+ }
+ if (nested_repo)
+ return ((dir->flags & DIR_SKIP_NESTED_GIT) ? path_none :
+ (excluded ? path_excluded : path_untracked));
+
+ if (!(dir->flags & DIR_SHOW_OTHER_DIRECTORIES)) {
+ if (excluded &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
/*
* This is an excluded directory and we are
@@ -1783,18 +1806,134 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
/* This is the "show_other_directories" case */
- if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
- return exclude ? path_excluded : path_untracked;
+ /*
+ * If we have a pathspec which could match something _below_ this
+ * directory (e.g. when checking 'subdir/' having a pathspec like
+ * 'subdir/some/deep/path/file' or 'subdir/widget-*.c'), then we
+ * need to recurse.
+ */
+ if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
+ return path_recurse;
+
+ /*
+ * Other than the path_recurse case immediately above, we only need
+ * to recurse into untracked/ignored directories if either of the
+ * following bits is set:
+ * - DIR_SHOW_IGNORED_TOO (because then we need to determine if
+ * there are ignored directories below)
+ * - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if
+ * the directory is empty)
+ */
+ if (!(dir->flags & (DIR_SHOW_IGNORED_TOO | DIR_HIDE_EMPTY_DIRECTORIES)))
+ return excluded ? path_excluded : path_untracked;
+ /*
+ * ...and even if DIR_SHOW_IGNORED_TOO is set, we can still avoid
+ * recursing into ignored directories if the path is excluded and
+ * DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
+ */
+ if (excluded &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
+ return path_excluded;
+
+ /*
+ * If we have we don't want to know the all the paths under an
+ * untracked or ignored directory, we still need to go into the
+ * directory to determine if it is empty (because an empty directory
+ * should be path_none instead of path_excluded or path_untracked).
+ */
+ check_only = ((dir->flags & DIR_HIDE_EMPTY_DIRECTORIES) &&
+ !(dir->flags & DIR_SHOW_IGNORED_TOO));
+
+ /*
+ * However, there's another optimization possible as a subset of
+ * check_only, based on the cases we have to consider:
+ * A) Directory matches no exclude patterns:
+ * * Directory is empty => path_none
+ * * Directory has an untracked file under it => path_untracked
+ * * Directory has only ignored files under it => path_excluded
+ * B) Directory matches an exclude pattern:
+ * * Directory is empty => path_none
+ * * Directory has an untracked file under it => path_excluded
+ * * Directory has only ignored files under it => path_excluded
+ * In case A, we can exit as soon as we've found an untracked
+ * file but otherwise have to walk all files. In case B, though,
+ * we can stop at the first file we find under the directory.
+ */
+ stop_early = check_only && excluded;
+
+ /*
+ * If /every/ file within an untracked directory is ignored, then
+ * we want to treat the directory as ignored (for e.g. status
+ * --porcelain), without listing the individual ignored files
+ * underneath. To do so, we'll save the current ignored_nr, and
+ * pop all the ones added after it if it turns out the entire
+ * directory is ignored. Also, when DIR_SHOW_IGNORED_TOO and
+ * !DIR_KEEP_UNTRACKED_CONTENTS then we don't want to show
+ * untracked paths so will need to pop all those off the last
+ * after we traverse.
+ */
+ old_ignored_nr = dir->ignored_nr;
+ old_untracked_nr = dir->nr;
+
+ /* Actually recurse into dirname now, we'll fixup the state later. */
untracked = lookup_untracked(dir->untracked, untracked,
dirname + baselen, len - baselen);
+ state = read_directory_recursive(dir, istate, dirname, len, untracked,
+ check_only, stop_early, pathspec);
+
+ /* There are a variety of reasons we may need to fixup the state... */
+ if (state == path_excluded) {
+ /* state == path_excluded implies all paths under
+ * dirname were ignored...
+ *
+ * if running e.g. `git status --porcelain --ignored=matching`,
+ * then we want to see the subpaths that are ignored.
+ *
+ * if running e.g. just `git status --porcelain`, then
+ * we just want the directory itself to be listed as ignored
+ * and not the individual paths underneath.
+ */
+ int want_ignored_subpaths =
+ ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+ (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING));
+
+ if (want_ignored_subpaths) {
+ /*
+ * with --ignored=matching, we want the subpaths
+ * INSTEAD of the directory itself.
+ */
+ state = path_none;
+ } else {
+ int i;
+ for (i = old_ignored_nr + 1; i<dir->ignored_nr; ++i)
+ FREE_AND_NULL(dir->ignored[i]);
+ dir->ignored_nr = old_ignored_nr;
+ }
+ }
/*
- * If this is an excluded directory, then we only need to check if
- * the directory contains any files.
+ * We may need to ignore some of the untracked paths we found while
+ * traversing subdirectories.
*/
- return read_directory_recursive(dir, istate, dirname, len,
- untracked, 1, exclude, pathspec);
+ if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+ !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
+ int i;
+ for (i = old_untracked_nr + 1; i<dir->nr; ++i)
+ FREE_AND_NULL(dir->entries[i]);
+ dir->nr = old_untracked_nr;
+ }
+
+ /*
+ * If there is nothing under the current directory and we are not
+ * hiding empty directories, then we need to report on the
+ * untracked or ignored status of the directory itself.
+ */
+ if (state == path_none && !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+ state = excluded ? path_excluded : path_untracked;
+
+ return state;
}
/*
@@ -1934,85 +2073,6 @@ static int resolve_dtype(int dtype, struct index_state *istate,
return dtype;
}
-static enum path_treatment treat_one_path(struct dir_struct *dir,
- struct untracked_cache_dir *untracked,
- struct index_state *istate,
- struct strbuf *path,
- int baselen,
- const struct pathspec *pathspec,
- int dtype)
-{
- int exclude;
- int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
- enum path_treatment path_treatment;
-
- dtype = resolve_dtype(dtype, istate, path->buf, path->len);
-
- /* Always exclude indexed files */
- if (dtype != DT_DIR && has_path_in_index)
- return path_none;
-
- /*
- * When we are looking at a directory P in the working tree,
- * there are three cases:
- *
- * (1) P exists in the index. Everything inside the directory P in
- * the working tree needs to go when P is checked out from the
- * index.
- *
- * (2) P does not exist in the index, but there is P/Q in the index.
- * We know P will stay a directory when we check out the contents
- * of the index, but we do not know yet if there is a directory
- * P/Q in the working tree to be killed, so we need to recurse.
- *
- * (3) P does not exist in the index, and there is no P/Q in the index
- * to require P to be a directory, either. Only in this case, we
- * know that everything inside P will not be killed without
- * recursing.
- */
- if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
- (dtype == DT_DIR) &&
- !has_path_in_index &&
- (directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent))
- return path_none;
-
- exclude = is_excluded(dir, istate, path->buf, &dtype);
-
- /*
- * Excluded? If we don't explicitly want to show
- * ignored files, ignore it
- */
- if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
- return path_excluded;
-
- switch (dtype) {
- default:
- return path_none;
- case DT_DIR:
- strbuf_addch(path, '/');
- path_treatment = treat_directory(dir, istate, untracked,
- path->buf, path->len,
- baselen, exclude, pathspec);
- /*
- * If 1) we only want to return directories that
- * match an exclude pattern and 2) this directory does
- * not match an exclude pattern but all of its
- * contents are excluded, then indicate that we should
- * recurse into this directory (instead of marking the
- * directory itself as an ignored path).
- */
- if (!exclude &&
- path_treatment == path_excluded &&
- (dir->flags & DIR_SHOW_IGNORED_TOO) &&
- (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
- return path_recurse;
- return path_treatment;
- case DT_REG:
- case DT_LNK:
- return exclude ? path_excluded : path_untracked;
- }
-}
-
static enum path_treatment treat_path_fast(struct dir_struct *dir,
struct untracked_cache_dir *untracked,
struct cached_dir *cdir,
@@ -2021,6 +2081,11 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
int baselen,
const struct pathspec *pathspec)
{
+ /*
+ * WARNING: From this function, you can return path_recurse or you
+ * can call read_directory_recursive() (or neither), but
+ * you CAN'T DO BOTH.
+ */
strbuf_setlen(path, baselen);
if (!cdir->ucd) {
strbuf_addstr(path, cdir->file);
@@ -2054,6 +2119,8 @@ static enum path_treatment treat_path(struct dir_struct *dir,
int baselen,
const struct pathspec *pathspec)
{
+ int has_path_in_index, dtype, excluded;
+
if (!cdir->d_name)
return treat_path_fast(dir, untracked, cdir, istate, path,
baselen, pathspec);
@@ -2064,8 +2131,72 @@ static enum path_treatment treat_path(struct dir_struct *dir,
if (simplify_away(path->buf, path->len, pathspec))
return path_none;
- return treat_one_path(dir, untracked, istate, path, baselen, pathspec,
- cdir->d_type);
+ dtype = resolve_dtype(cdir->d_type, istate, path->buf, path->len);
+
+ /* Always exclude indexed files */
+ has_path_in_index = !!index_file_exists(istate, path->buf, path->len,
+ ignore_case);
+ if (dtype != DT_DIR && has_path_in_index)
+ return path_none;
+
+ /*
+ * When we are looking at a directory P in the working tree,
+ * there are three cases:
+ *
+ * (1) P exists in the index. Everything inside the directory P in
+ * the working tree needs to go when P is checked out from the
+ * index.
+ *
+ * (2) P does not exist in the index, but there is P/Q in the index.
+ * We know P will stay a directory when we check out the contents
+ * of the index, but we do not know yet if there is a directory
+ * P/Q in the working tree to be killed, so we need to recurse.
+ *
+ * (3) P does not exist in the index, and there is no P/Q in the index
+ * to require P to be a directory, either. Only in this case, we
+ * know that everything inside P will not be killed without
+ * recursing.
+ */
+ if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
+ (dtype == DT_DIR) &&
+ !has_path_in_index &&
+ (directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent))
+ return path_none;
+
+ excluded = is_excluded(dir, istate, path->buf, &dtype);
+
+ /*
+ * Excluded? If we don't explicitly want to show
+ * ignored files, ignore it
+ */
+ if (excluded && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
+ return path_excluded;
+
+ switch (dtype) {
+ default:
+ return path_none;
+ case DT_DIR:
+ /*
+ * WARNING: Do not ignore/amend the return value from
+ * treat_directory(), and especially do not change it to return
+ * path_recurse as that can cause exponential slowdown.
+ * Instead, modify treat_directory() to return the right value.
+ */
+ strbuf_addch(path, '/');
+ return treat_directory(dir, istate, untracked,
+ path->buf, path->len,
+ baselen, excluded, pathspec);
+ case DT_REG:
+ case DT_LNK:
+ if (excluded)
+ return path_excluded;
+ if (pathspec &&
+ !do_match_pathspec(istate, pathspec, path->buf, path->len,
+ 0 /* prefix */, NULL /* seen */,
+ 0 /* flags */))
+ return path_none;
+ return path_untracked;
+ }
}
static void add_untracked(struct untracked_cache_dir *dir, const char *name)
@@ -2245,7 +2376,7 @@ static void add_path_to_appropriate_result_list(struct dir_struct *dir,
* If 'stop_at_first_file' is specified, 'path_excluded' is returned
* to signal that a file was found. This is the least significant value that
* indicates that a file was encountered that does not depend on the order of
- * whether an untracked or exluded path was encountered first.
+ * whether an untracked or excluded path was encountered first.
*
* Returns the most significant path_treatment value encountered in the scan.
* If 'stop_at_first_file' is specified, `path_excluded` is the most
@@ -2258,14 +2389,10 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
int stop_at_first_file, const struct pathspec *pathspec)
{
/*
- * WARNING WARNING WARNING:
- *
- * Any updates to the traversal logic here may need corresponding
- * updates in treat_leading_path(). See the commit message for the
- * commit adding this warning as well as the commit preceding it
- * for details.
+ * WARNING: Do NOT recurse unless path_recurse is returned from
+ * treat_path(). Recursing on any other return value
+ * can result in exponential slowdown.
*/
-
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
struct strbuf path = STRBUF_INIT;
@@ -2287,13 +2414,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
dir_state = state;
/* recurse into subdir if instructed by treat_path */
- if ((state == path_recurse) ||
- ((state == path_untracked) &&
- (resolve_dtype(cdir.d_type, istate, path.buf, path.len) == DT_DIR) &&
- ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
- (pathspec &&
- do_match_pathspec(istate, pathspec, path.buf, path.len,
- baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)))) {
+ if (state == path_recurse) {
struct untracked_cache_dir *ud;
ud = lookup_untracked(dir->untracked, untracked,
path.buf + baselen,
@@ -2341,7 +2462,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
add_untracked(untracked, path.buf + baselen);
break;
}
- /* skip the dir_add_* part */
+ /* skip the add_path_to_appropriate_result_list() */
continue;
}
@@ -2377,15 +2498,6 @@ static int treat_leading_path(struct dir_struct *dir,
const char *path, int len,
const struct pathspec *pathspec)
{
- /*
- * WARNING WARNING WARNING:
- *
- * Any updates to the traversal logic here may need corresponding
- * updates in read_directory_recursive(). See 777b420347 (dir:
- * synchronize treat_leading_path() and read_directory_recursive(),
- * 2019-12-19) and its parent commit for details.
- */
-
struct strbuf sb = STRBUF_INIT;
struct strbuf subdir = STRBUF_INIT;
int prevlen, baselen;
@@ -2436,23 +2548,7 @@ static int treat_leading_path(struct dir_struct *dir,
strbuf_reset(&subdir);
strbuf_add(&subdir, path+prevlen, baselen-prevlen);
cdir.d_name = subdir.buf;
- state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen,
- pathspec);
- if (state == path_untracked &&
- resolve_dtype(cdir.d_type, istate, sb.buf, sb.len) == DT_DIR &&
- (dir->flags & DIR_SHOW_IGNORED_TOO ||
- do_match_pathspec(istate, pathspec, sb.buf, sb.len,
- baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) {
- if (!match_pathspec(istate, pathspec, sb.buf, sb.len,
- 0 /* prefix */, NULL,
- 0 /* do NOT special case dirs */))
- state = path_none;
- add_path_to_appropriate_result_list(dir, NULL, &cdir,
- istate,
- &sb, baselen,
- pathspec, state);
- state = path_recurse;
- }
+ state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen, pathspec);
if (state != path_recurse)
break; /* do not recurse into it */
@@ -2652,28 +2748,6 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
QSORT(dir->entries, dir->nr, cmp_dir_entry);
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
- /*
- * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
- * also pick up untracked contents of untracked dirs; by default
- * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
- */
- if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
- !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
- int i, j;
-
- /* remove from dir->entries untracked contents of untracked dirs */
- for (i = j = 0; j < dir->nr; j++) {
- if (i &&
- check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
- FREE_AND_NULL(dir->entries[j]);
- } else {
- dir->entries[i++] = dir->entries[j];
- }
- }
-
- dir->nr = i;
- }
-
trace_performance_leave("read directory %.*s", len, path);
if (dir->untracked) {
static int force_untracked_cache = -1;
diff --git a/fast-import.c b/fast-import.c
index 202dda11a6..c98970274c 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -39,12 +39,28 @@
struct object_entry {
struct pack_idx_entry idx;
- struct object_entry *next;
+ struct hashmap_entry ent;
uint32_t type : TYPE_BITS,
pack_id : PACK_ID_BITS,
depth : DEPTH_BITS;
};
+static int object_entry_hashcmp(const void *map_data,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata)
+{
+ const struct object_id *oid = keydata;
+ const struct object_entry *e1, *e2;
+
+ e1 = container_of(eptr, const struct object_entry, ent);
+ if (oid)
+ return oidcmp(&e1->idx.oid, oid);
+
+ e2 = container_of(entry_or_key, const struct object_entry, ent);
+ return oidcmp(&e1->idx.oid, &e2->idx.oid);
+}
+
struct object_entry_pool {
struct object_entry_pool *next_pool;
struct object_entry *next_free;
@@ -178,7 +194,7 @@ static off_t pack_size;
/* Table of objects we've written. */
static unsigned int object_entry_alloc = 5000;
static struct object_entry_pool *blocks;
-static struct object_entry *object_table[1 << 16];
+static struct hashmap object_table;
static struct mark_set *marks;
static const char *export_marks_file;
static const char *import_marks_file;
@@ -455,44 +471,37 @@ static struct object_entry *new_object(struct object_id *oid)
static struct object_entry *find_object(struct object_id *oid)
{
- unsigned int h = oid->hash[0] << 8 | oid->hash[1];
- struct object_entry *e;
- for (e = object_table[h]; e; e = e->next)
- if (oideq(oid, &e->idx.oid))
- return e;
- return NULL;
+ return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid,
+ struct object_entry, ent);
}
static struct object_entry *insert_object(struct object_id *oid)
{
- unsigned int h = oid->hash[0] << 8 | oid->hash[1];
- struct object_entry *e = object_table[h];
+ struct object_entry *e;
+ unsigned int hash = oidhash(oid);
- while (e) {
- if (oideq(oid, &e->idx.oid))
- return e;
- e = e->next;
+ e = hashmap_get_entry_from_hash(&object_table, hash, oid,
+ struct object_entry, ent);
+ if (!e) {
+ e = new_object(oid);
+ e->idx.offset = 0;
+ hashmap_entry_init(&e->ent, hash);
+ hashmap_add(&object_table, &e->ent);
}
- e = new_object(oid);
- e->next = object_table[h];
- e->idx.offset = 0;
- object_table[h] = e;
return e;
}
static void invalidate_pack_id(unsigned int id)
{
- unsigned int h;
unsigned long lu;
struct tag *t;
+ struct hashmap_iter iter;
+ struct object_entry *e;
- for (h = 0; h < ARRAY_SIZE(object_table); h++) {
- struct object_entry *e;
-
- for (e = object_table[h]; e; e = e->next)
- if (e->pack_id == id)
- e->pack_id = MAX_PACK_ID;
+ hashmap_for_each_entry(&object_table, &iter, e, ent) {
+ if (e->pack_id == id)
+ e->pack_id = MAX_PACK_ID;
}
for (lu = 0; lu < branch_table_sz; lu++) {
@@ -3511,6 +3520,8 @@ int cmd_main(int argc, const char **argv)
avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
+ hashmap_init(&object_table, object_entry_hashcmp, NULL, 0);
+
/*
* We don't parse most options until after we've seen the set of
* "feature" lines at the start of the stream (which allows the command
diff --git a/fetch-pack.c b/fetch-pack.c
index 1734a573b0..f73a2ce6cb 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -15,7 +15,7 @@
#include "connect.h"
#include "transport.h"
#include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "oidset.h"
#include "packfile.h"
#include "object-store.h"
@@ -1143,6 +1143,7 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
}
static int add_haves(struct fetch_negotiator *negotiator,
+ int seen_ack,
struct strbuf *req_buf,
int *haves_to_send, int *in_vain)
{
@@ -1157,7 +1158,7 @@ static int add_haves(struct fetch_negotiator *negotiator,
}
*in_vain += haves_added;
- if (!haves_added || *in_vain >= MAX_IN_VAIN) {
+ if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) {
/* Send Done */
packet_buf_write(req_buf, "done\n");
ret = 1;
@@ -1173,7 +1174,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
struct fetch_pack_args *args,
const struct ref *wants, struct oidset *common,
int *haves_to_send, int *in_vain,
- int sideband_all)
+ int sideband_all, int seen_ack)
{
int ret = 0;
struct strbuf req_buf = STRBUF_INIT;
@@ -1230,7 +1231,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
add_common(&req_buf, common);
/* Add initial haves */
- ret = add_haves(negotiator, &req_buf, haves_to_send, in_vain);
+ ret = add_haves(negotiator, seen_ack, &req_buf,
+ haves_to_send, in_vain);
}
/* Send request */
@@ -1268,9 +1270,29 @@ static int process_section_header(struct packet_reader *reader,
return ret;
}
-static int process_acks(struct fetch_negotiator *negotiator,
- struct packet_reader *reader,
- struct oidset *common)
+enum common_found {
+ /*
+ * No commit was found to be possessed by both the client and the
+ * server, and "ready" was not received.
+ */
+ NO_COMMON_FOUND,
+
+ /*
+ * At least one commit was found to be possessed by both the client and
+ * the server, and "ready" was not received.
+ */
+ COMMON_FOUND,
+
+ /*
+ * "ready" was received, indicating that the server is ready to send
+ * the packfile without any further negotiation.
+ */
+ READY
+};
+
+static enum common_found process_acks(struct fetch_negotiator *negotiator,
+ struct packet_reader *reader,
+ struct oidset *common)
{
/* received */
int received_ready = 0;
@@ -1285,6 +1307,7 @@ static int process_acks(struct fetch_negotiator *negotiator,
if (skip_prefix(reader->line, "ACK ", &arg)) {
struct object_id oid;
+ received_ack = 1;
if (!get_oid_hex(arg, &oid)) {
struct commit *commit;
oidset_insert(common, &oid);
@@ -1319,8 +1342,8 @@ static int process_acks(struct fetch_negotiator *negotiator,
if (!received_ready && reader->status != PACKET_READ_FLUSH)
die(_("expected no other sections to be sent after no 'ready'"));
- /* return 0 if no common, 1 if there are common, or 2 if ready */
- return received_ready ? 2 : (received_ack ? 1 : 0);
+ return received_ready ? READY :
+ (received_ack ? COMMON_FOUND : NO_COMMON_FOUND);
}
static void receive_shallow_info(struct fetch_pack_args *args,
@@ -1444,6 +1467,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
int haves_to_send = INITIAL_FLUSH;
struct fetch_negotiator negotiator_alloc;
struct fetch_negotiator *negotiator;
+ int seen_ack = 0;
if (args->no_dependents) {
negotiator = NULL;
@@ -1500,7 +1524,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
if (send_fetch_request(negotiator, fd[1], args, ref,
&common,
&haves_to_send, &in_vain,
- reader.use_sideband))
+ reader.use_sideband,
+ seen_ack))
state = FETCH_GET_PACK;
else
state = FETCH_PROCESS_ACKS;
@@ -1508,13 +1533,14 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
case FETCH_PROCESS_ACKS:
/* Process ACKs/NAKs */
switch (process_acks(negotiator, &reader, &common)) {
- case 2:
+ case READY:
state = FETCH_GET_PACK;
break;
- case 1:
+ case COMMON_FOUND:
in_vain = 0;
+ seen_ack = 1;
/* fallthrough */
- default:
+ case NO_COMMON_FOUND:
state = FETCH_SEND_REQUEST;
break;
}
@@ -1629,9 +1655,9 @@ static void update_shallow(struct fetch_pack_args *args,
if (args->deepen && alternate_shallow_file) {
if (*alternate_shallow_file == '\0') { /* --unshallow */
unlink_or_warn(git_path_shallow(the_repository));
- rollback_lock_file(&shallow_lock);
+ rollback_shallow_file(the_repository, &shallow_lock);
} else
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
alternate_shallow_file = NULL;
return;
}
@@ -1655,7 +1681,7 @@ static void update_shallow(struct fetch_pack_args *args,
setup_alternate_shallow(&shallow_lock,
&alternate_shallow_file,
&extra);
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
alternate_shallow_file = NULL;
}
oid_array_clear(&extra);
@@ -1693,7 +1719,7 @@ static void update_shallow(struct fetch_pack_args *args,
setup_alternate_shallow(&shallow_lock,
&alternate_shallow_file,
&extra);
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
oid_array_clear(&extra);
oid_array_clear(&ref);
alternate_shallow_file = NULL;
@@ -1785,7 +1811,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
error(_("remote did not send all necessary objects"));
free_refs(ref_cpy);
ref_cpy = NULL;
- rollback_lock_file(&shallow_lock);
+ rollback_shallow_file(the_repository, &shallow_lock);
goto cleanup;
}
args->connectivity_checked = 1;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
new file mode 100644
index 0000000000..72d32bd73b
--- /dev/null
+++ b/fmt-merge-msg.c
@@ -0,0 +1,656 @@
+#include "config.h"
+#include "refs.h"
+#include "object-store.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+#include "string-list.h"
+#include "branch.h"
+#include "fmt-merge-msg.h"
+#include "commit-reach.h"
+
+static int use_branch_desc;
+
+int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+{
+ if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
+ int is_bool;
+ merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool && merge_log_config < 0)
+ return error("%s: negative length %s", key, value);
+ if (is_bool && merge_log_config)
+ merge_log_config = DEFAULT_MERGE_LOG_LEN;
+ } else if (!strcmp(key, "merge.branchdesc")) {
+ use_branch_desc = git_config_bool(key, value);
+ } else {
+ return git_default_config(key, value, cb);
+ }
+ return 0;
+}
+
+/* merge data per repository where the merged tips came from */
+struct src_data {
+ struct string_list branch, tag, r_branch, generic;
+ int head_status;
+};
+
+struct origin_data {
+ struct object_id oid;
+ unsigned is_local_branch:1;
+};
+
+static void init_src_data(struct src_data *data)
+{
+ data->branch.strdup_strings = 1;
+ data->tag.strdup_strings = 1;
+ data->r_branch.strdup_strings = 1;
+ data->generic.strdup_strings = 1;
+}
+
+static struct string_list srcs = STRING_LIST_INIT_DUP;
+static struct string_list origins = STRING_LIST_INIT_DUP;
+
+struct merge_parents {
+ int alloc, nr;
+ struct merge_parent {
+ struct object_id given;
+ struct object_id commit;
+ unsigned char used;
+ } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+ struct object_id *given,
+ struct object_id *commit)
+{
+ int i;
+ for (i = 0; i < table->nr; i++) {
+ if (given && !oideq(&table->item[i].given, given))
+ continue;
+ if (commit && !oideq(&table->item[i].commit, commit))
+ continue;
+ return &table->item[i];
+ }
+ return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+ struct object_id *given,
+ struct object_id *commit)
+{
+ if (table->nr && find_merge_parent(table, given, commit))
+ return;
+ ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+ oidcpy(&table->item[table->nr].given, given);
+ oidcpy(&table->item[table->nr].commit, commit);
+ table->item[table->nr].used = 0;
+ table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
+{
+ int i, len = strlen(line);
+ struct origin_data *origin_data;
+ char *src;
+ const char *origin, *tag_name;
+ struct src_data *src_data;
+ struct string_list_item *item;
+ int pulling_head = 0;
+ struct object_id oid;
+ const unsigned hexsz = the_hash_algo->hexsz;
+
+ if (len < hexsz + 3 || line[hexsz] != '\t')
+ return 1;
+
+ if (starts_with(line + hexsz + 1, "not-for-merge"))
+ return 0;
+
+ if (line[hexsz + 1] != '\t')
+ return 2;
+
+ i = get_oid_hex(line, &oid);
+ if (i)
+ return 3;
+
+ if (!find_merge_parent(merge_parents, &oid, NULL))
+ return 0; /* subsumed by other parents */
+
+ origin_data = xcalloc(1, sizeof(struct origin_data));
+ oidcpy(&origin_data->oid, &oid);
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = 0;
+ line += hexsz + 2;
+
+ /*
+ * At this point, line points at the beginning of comment e.g.
+ * "branch 'frotz' of git://that/repository.git".
+ * Find the repository name and point it with src.
+ */
+ src = strstr(line, " of ");
+ if (src) {
+ *src = 0;
+ src += 4;
+ pulling_head = 0;
+ } else {
+ src = line;
+ pulling_head = 1;
+ }
+
+ item = unsorted_string_list_lookup(&srcs, src);
+ if (!item) {
+ item = string_list_append(&srcs, src);
+ item->util = xcalloc(1, sizeof(struct src_data));
+ init_src_data(item->util);
+ }
+ src_data = item->util;
+
+ if (pulling_head) {
+ origin = src;
+ src_data->head_status |= 1;
+ } else if (skip_prefix(line, "branch ", &origin)) {
+ origin_data->is_local_branch = 1;
+ string_list_append(&src_data->branch, origin);
+ src_data->head_status |= 2;
+ } else if (skip_prefix(line, "tag ", &tag_name)) {
+ origin = line;
+ string_list_append(&src_data->tag, tag_name);
+ src_data->head_status |= 2;
+ } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
+ string_list_append(&src_data->r_branch, origin);
+ src_data->head_status |= 2;
+ } else {
+ origin = src;
+ string_list_append(&src_data->generic, line);
+ src_data->head_status |= 2;
+ }
+
+ if (!strcmp(".", src) || !strcmp(src, origin)) {
+ int len = strlen(origin);
+ if (origin[0] == '\'' && origin[len - 1] == '\'')
+ origin = xmemdupz(origin + 1, len - 2);
+ } else
+ origin = xstrfmt("%s of %s", origin, src);
+ if (strcmp(".", src))
+ origin_data->is_local_branch = 0;
+ string_list_append(&origins, origin)->util = origin_data;
+ return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+ struct string_list *list, struct strbuf *out)
+{
+ if (list->nr == 0)
+ return;
+ if (list->nr == 1) {
+ strbuf_addf(out, "%s%s", singular, list->items[0].string);
+ } else {
+ int i;
+ strbuf_addstr(out, plural);
+ for (i = 0; i < list->nr - 1; i++)
+ strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
+ list->items[i].string);
+ strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
+ }
+}
+
+static void add_branch_desc(struct strbuf *out, const char *name)
+{
+ struct strbuf desc = STRBUF_INIT;
+
+ if (!read_branch_desc(&desc, name)) {
+ const char *bp = desc.buf;
+ while (*bp) {
+ const char *ep = strchrnul(bp, '\n');
+ if (*ep)
+ ep++;
+ strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
+ bp = ep;
+ }
+ strbuf_complete_line(out);
+ }
+ strbuf_release(&desc);
+}
+
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person_from_buf(int which, struct string_list *people,
+ const char *buffer)
+{
+ char *name_buf, *name, *name_end;
+ struct string_list_item *elem;
+ const char *field;
+
+ field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+ name = strstr(buffer, field);
+ if (!name)
+ return;
+ name += strlen(field);
+ name_end = strchrnul(name, '<');
+ if (*name_end)
+ name_end--;
+ while (isspace(*name_end) && name <= name_end)
+ name_end--;
+ if (name_end < name)
+ return;
+ name_buf = xmemdupz(name, name_end - name + 1);
+
+ elem = string_list_lookup(people, name_buf);
+ if (!elem) {
+ elem = string_list_insert(people, name_buf);
+ elem->util = (void *)0;
+ }
+ elem->util = (void*)(util_as_integral(elem) + 1);
+ free(name_buf);
+}
+
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ const char *buffer = get_commit_buffer(commit, NULL);
+ record_person_from_buf(which, people, buffer);
+ unuse_commit_buffer(commit, buffer);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_, *b = b_;
+ return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+ if (people->nr == 1)
+ strbuf_addstr(out, people->items[0].string);
+ else if (people->nr == 2)
+ strbuf_addf(out, "%s (%d) and %s (%d)",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]),
+ people->items[1].string,
+ (int)util_as_integral(&people->items[1]));
+ else if (people->nr)
+ strbuf_addf(out, "%s (%d) and others",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+ struct string_list *them,
+ int kind)
+{
+ const char *label;
+ const char *me;
+
+ if (kind == 'a') {
+ label = "By";
+ me = git_author_info(IDENT_NO_DATE);
+ } else {
+ label = "Via";
+ me = git_committer_info(IDENT_NO_DATE);
+ }
+
+ if (!them->nr ||
+ (them->nr == 1 &&
+ me &&
+ skip_prefix(me, them->items->string, &me) &&
+ starts_with(me, " <")))
+ return;
+ strbuf_addf(out, "\n%c %s ", comment_line_char, label);
+ add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+ struct string_list *authors,
+ struct string_list *committers)
+{
+ QSORT(authors->items, authors->nr,
+ cmp_string_list_util_as_integral);
+ QSORT(committers->items, committers->nr,
+ cmp_string_list_util_as_integral);
+
+ credit_people(out, authors, 'a');
+ credit_people(out, committers, 'c');
+}
+
+static void shortlog(const char *name,
+ struct origin_data *origin_data,
+ struct commit *head,
+ struct rev_info *rev,
+ struct fmt_merge_msg_opts *opts,
+ struct strbuf *out)
+{
+ int i, count = 0;
+ struct commit *commit;
+ struct object *branch;
+ struct string_list subjects = STRING_LIST_INIT_DUP;
+ struct string_list authors = STRING_LIST_INIT_DUP;
+ struct string_list committers = STRING_LIST_INIT_DUP;
+ int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
+ struct strbuf sb = STRBUF_INIT;
+ const struct object_id *oid = &origin_data->oid;
+ int limit = opts->shortlog_len;
+
+ branch = deref_tag(the_repository, parse_object(the_repository, oid),
+ oid_to_hex(oid),
+ the_hash_algo->hexsz);
+ if (!branch || branch->type != OBJ_COMMIT)
+ return;
+
+ setup_revisions(0, NULL, rev, NULL);
+ add_pending_object(rev, branch, name);
+ add_pending_object(rev, &head->object, "^HEAD");
+ head->object.flags |= UNINTERESTING;
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+ while ((commit = get_revision(rev)) != NULL) {
+ struct pretty_print_context ctx = {0};
+
+ if (commit->parents && commit->parents->next) {
+ /* do not list a merge but count committer */
+ if (opts->credit_people)
+ record_person('c', &committers, commit);
+ continue;
+ }
+ if (!count && opts->credit_people)
+ /* the 'tip' committer */
+ record_person('c', &committers, commit);
+ if (opts->credit_people)
+ record_person('a', &authors, commit);
+ count++;
+ if (subjects.nr > limit)
+ continue;
+
+ format_commit_message(commit, "%s", &sb, &ctx);
+ strbuf_ltrim(&sb);
+
+ if (!sb.len)
+ string_list_append(&subjects,
+ oid_to_hex(&commit->object.oid));
+ else
+ string_list_append_nodup(&subjects,
+ strbuf_detach(&sb, NULL));
+ }
+
+ if (opts->credit_people)
+ add_people_info(out, &authors, &committers);
+ if (count > limit)
+ strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
+ else
+ strbuf_addf(out, "\n* %s:\n", name);
+
+ if (origin_data->is_local_branch && use_branch_desc)
+ add_branch_desc(out, name);
+
+ for (i = 0; i < subjects.nr; i++)
+ if (i >= limit)
+ strbuf_addstr(out, " ...\n");
+ else
+ strbuf_addf(out, " %s\n", subjects.items[i].string);
+
+ clear_commit_marks((struct commit *)branch, flags);
+ clear_commit_marks(head, flags);
+ free_commit_list(rev->commits);
+ rev->commits = NULL;
+ rev->pending.nr = 0;
+
+ string_list_clear(&authors, 0);
+ string_list_clear(&committers, 0);
+ string_list_clear(&subjects, 0);
+}
+
+static void fmt_merge_msg_title(struct strbuf *out,
+ const char *current_branch)
+{
+ int i = 0;
+ char *sep = "";
+
+ strbuf_addstr(out, "Merge ");
+ for (i = 0; i < srcs.nr; i++) {
+ struct src_data *src_data = srcs.items[i].util;
+ const char *subsep = "";
+
+ strbuf_addstr(out, sep);
+ sep = "; ";
+
+ if (src_data->head_status == 1) {
+ strbuf_addstr(out, srcs.items[i].string);
+ continue;
+ }
+ if (src_data->head_status == 3) {
+ subsep = ", ";
+ strbuf_addstr(out, "HEAD");
+ }
+ if (src_data->branch.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("branch ", "branches ", &src_data->branch,
+ out);
+ }
+ if (src_data->r_branch.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("remote-tracking branch ", "remote-tracking branches ",
+ &src_data->r_branch, out);
+ }
+ if (src_data->tag.nr) {
+ strbuf_addstr(out, subsep);
+ subsep = ", ";
+ print_joined("tag ", "tags ", &src_data->tag, out);
+ }
+ if (src_data->generic.nr) {
+ strbuf_addstr(out, subsep);
+ print_joined("commit ", "commits ", &src_data->generic,
+ out);
+ }
+ if (strcmp(".", srcs.items[i].string))
+ strbuf_addf(out, " of %s", srcs.items[i].string);
+ }
+
+ if (!strcmp("master", current_branch))
+ strbuf_addch(out, '\n');
+ else
+ strbuf_addf(out, " into %s\n", current_branch);
+}
+
+static void fmt_tag_signature(struct strbuf *tagbuf,
+ struct strbuf *sig,
+ const char *buf,
+ unsigned long len)
+{
+ const char *tag_body = strstr(buf, "\n\n");
+ if (tag_body) {
+ tag_body += 2;
+ strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+ }
+ strbuf_complete_line(tagbuf);
+ if (sig->len) {
+ strbuf_addch(tagbuf, '\n');
+ strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
+ }
+}
+
+static void fmt_merge_msg_sigs(struct strbuf *out)
+{
+ int i, tag_number = 0, first_tag = 0;
+ struct strbuf tagbuf = STRBUF_INIT;
+
+ for (i = 0; i < origins.nr; i++) {
+ struct object_id *oid = origins.items[i].util;
+ enum object_type type;
+ unsigned long size, len;
+ char *buf = read_object_file(oid, &type, &size);
+ struct signature_check sigc = { NULL };
+ struct strbuf sig = STRBUF_INIT;
+
+ if (!buf || type != OBJ_TAG)
+ goto next;
+ len = parse_signature(buf, size);
+
+ if (size == len)
+ ; /* merely annotated */
+ else if (check_signature(buf, len, buf + len, size - len, &sigc) &&
+ !sigc.gpg_output)
+ strbuf_addstr(&sig, "gpg verification failed.\n");
+ else
+ strbuf_addstr(&sig, sigc.gpg_output);
+ signature_check_clear(&sigc);
+
+ if (!tag_number++) {
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ first_tag = i;
+ } else {
+ if (tag_number == 2) {
+ struct strbuf tagline = STRBUF_INIT;
+ strbuf_addch(&tagline, '\n');
+ strbuf_add_commented_lines(&tagline,
+ origins.items[first_tag].string,
+ strlen(origins.items[first_tag].string));
+ strbuf_insert(&tagbuf, 0, tagline.buf,
+ tagline.len);
+ strbuf_release(&tagline);
+ }
+ strbuf_addch(&tagbuf, '\n');
+ strbuf_add_commented_lines(&tagbuf,
+ origins.items[i].string,
+ strlen(origins.items[i].string));
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ }
+ strbuf_release(&sig);
+ next:
+ free(buf);
+ }
+ if (tagbuf.len) {
+ strbuf_addch(out, '\n');
+ strbuf_addbuf(out, &tagbuf);
+ }
+ strbuf_release(&tagbuf);
+}
+
+static void find_merge_parents(struct merge_parents *result,
+ struct strbuf *in, struct object_id *head)
+{
+ struct commit_list *parents;
+ struct commit *head_commit;
+ int pos = 0, i, j;
+
+ parents = NULL;
+ while (pos < in->len) {
+ int len;
+ char *p = in->buf + pos;
+ char *newline = strchr(p, '\n');
+ const char *q;
+ struct object_id oid;
+ struct commit *parent;
+ struct object *obj;
+
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+
+ if (parse_oid_hex(p, &oid, &q) ||
+ q[0] != '\t' ||
+ q[1] != '\t')
+ continue; /* skip not-for-merge */
+ /*
+ * Do not use get_merge_parent() here; we do not have
+ * "name" here and we do not want to contaminate its
+ * util field yet.
+ */
+ obj = parse_object(the_repository, &oid);
+ parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+ if (!parent)
+ continue;
+ commit_list_insert(parent, &parents);
+ add_merge_parent(result, &obj->oid, &parent->object.oid);
+ }
+ head_commit = lookup_commit(the_repository, head);
+ if (head_commit)
+ commit_list_insert(head_commit, &parents);
+ reduce_heads_replace(&parents);
+
+ while (parents) {
+ struct commit *cmit = pop_commit(&parents);
+ for (i = 0; i < result->nr; i++)
+ if (oideq(&result->item[i].commit, &cmit->object.oid))
+ result->item[i].used = 1;
+ }
+
+ for (i = j = 0; i < result->nr; i++) {
+ if (result->item[i].used) {
+ if (i != j)
+ result->item[j] = result->item[i];
+ j++;
+ }
+ }
+ result->nr = j;
+}
+
+
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ struct fmt_merge_msg_opts *opts)
+{
+ int i = 0, pos = 0;
+ struct object_id head_oid;
+ const char *current_branch;
+ void *current_branch_to_free;
+ struct merge_parents merge_parents;
+
+ memset(&merge_parents, 0, sizeof(merge_parents));
+
+ /* get current branch */
+ current_branch = current_branch_to_free =
+ resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
+ if (!current_branch)
+ die("No current branch");
+ if (starts_with(current_branch, "refs/heads/"))
+ current_branch += 11;
+
+ find_merge_parents(&merge_parents, in, &head_oid);
+
+ /* get a line */
+ while (pos < in->len) {
+ int len;
+ char *newline, *p = in->buf + pos;
+
+ newline = strchr(p, '\n');
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+ i++;
+ p[len] = 0;
+ if (handle_line(p, &merge_parents))
+ die("error in line %d: %.*s", i, len, p);
+ }
+
+ if (opts->add_title && srcs.nr)
+ fmt_merge_msg_title(out, current_branch);
+
+ if (origins.nr)
+ fmt_merge_msg_sigs(out);
+
+ if (opts->shortlog_len) {
+ struct commit *head;
+ struct rev_info rev;
+
+ head = lookup_commit_or_die(&head_oid, "HEAD");
+ repo_init_revisions(the_repository, &rev, NULL);
+ rev.commit_format = CMIT_FMT_ONELINE;
+ rev.ignore_merges = 1;
+ rev.limited = 1;
+
+ strbuf_complete_line(out);
+
+ for (i = 0; i < origins.nr; i++)
+ shortlog(origins.items[i].string,
+ origins.items[i].util,
+ head, &rev, opts, out);
+ }
+
+ strbuf_complete_line(out);
+ free(current_branch_to_free);
+ free(merge_parents.item);
+ return 0;
+}
diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h
index 01e3aa88c5..f2ab0e0085 100644
--- a/fmt-merge-msg.h
+++ b/fmt-merge-msg.h
@@ -1,7 +1,20 @@
#ifndef FMT_MERGE_MSG_H
#define FMT_MERGE_MSG_H
+#include "strbuf.h"
+
+#define DEFAULT_MERGE_LOG_LEN 20
+
+struct fmt_merge_msg_opts {
+ unsigned add_title:1,
+ credit_people:1;
+ int shortlog_len;
+};
+
extern int merge_log_config;
int fmt_merge_msg_config(const char *key, const char *value, void *cb);
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ struct fmt_merge_msg_opts *);
+
#endif /* FMT_MERGE_MSG_H */
diff --git a/fsck.c b/fsck.c
index dc6f6ac3de..087a7f1ffc 100644
--- a/fsck.c
+++ b/fsck.c
@@ -9,6 +9,7 @@
#include "tag.h"
#include "fsck.h"
#include "refs.h"
+#include "url.h"
#include "utf8.h"
#include "decorate.h"
#include "oidset.h"
@@ -911,17 +912,147 @@ done:
return ret;
}
+/*
+ * Like builtin/submodule--helper.c's starts_with_dot_slash, but without
+ * relying on the platform-dependent is_dir_sep helper.
+ *
+ * This is for use in checking whether a submodule URL is interpreted as
+ * relative to the current directory on any platform, since \ is a
+ * directory separator on Windows but not on other platforms.
+ */
+static int starts_with_dot_slash(const char *str)
+{
+ return str[0] == '.' && (str[1] == '/' || str[1] == '\\');
+}
+
+/*
+ * Like starts_with_dot_slash, this is a variant of submodule--helper's
+ * helper of the same name with the twist that it accepts backslash as a
+ * directory separator even on non-Windows platforms.
+ */
+static int starts_with_dot_dot_slash(const char *str)
+{
+ return str[0] == '.' && starts_with_dot_slash(str + 1);
+}
+
+static int submodule_url_is_relative(const char *url)
+{
+ return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
+}
+
+/*
+ * Count directory components that a relative submodule URL should chop
+ * from the remote_url it is to be resolved against.
+ *
+ * In other words, this counts "../" components at the start of a
+ * submodule URL.
+ *
+ * Returns the number of directory components to chop and writes a
+ * pointer to the next character of url after all leading "./" and
+ * "../" components to out.
+ */
+static int count_leading_dotdots(const char *url, const char **out)
+{
+ int result = 0;
+ while (1) {
+ if (starts_with_dot_dot_slash(url)) {
+ result++;
+ url += strlen("../");
+ continue;
+ }
+ if (starts_with_dot_slash(url)) {
+ url += strlen("./");
+ continue;
+ }
+ *out = url;
+ return result;
+ }
+}
+/*
+ * Check whether a transport is implemented by git-remote-curl.
+ *
+ * If it is, returns 1 and writes the URL that would be passed to
+ * git-remote-curl to the "out" parameter.
+ *
+ * Otherwise, returns 0 and leaves "out" untouched.
+ *
+ * Examples:
+ * http::https://example.com/repo.git -> 1, https://example.com/repo.git
+ * https://example.com/repo.git -> 1, https://example.com/repo.git
+ * git://example.com/repo.git -> 0
+ *
+ * This is for use in checking for previously exploitable bugs that
+ * required a submodule URL to be passed to git-remote-curl.
+ */
+static int url_to_curl_url(const char *url, const char **out)
+{
+ /*
+ * We don't need to check for case-aliases, "http.exe", and so
+ * on because in the default configuration, is_transport_allowed
+ * prevents URLs with those schemes from being cloned
+ * automatically.
+ */
+ if (skip_prefix(url, "http::", out) ||
+ skip_prefix(url, "https::", out) ||
+ skip_prefix(url, "ftp::", out) ||
+ skip_prefix(url, "ftps::", out))
+ return 1;
+ if (starts_with(url, "http://") ||
+ starts_with(url, "https://") ||
+ starts_with(url, "ftp://") ||
+ starts_with(url, "ftps://")) {
+ *out = url;
+ return 1;
+ }
+ return 0;
+}
+
static int check_submodule_url(const char *url)
{
- struct credential c = CREDENTIAL_INIT;
- int ret;
+ const char *curl_url;
if (looks_like_command_line_option(url))
return -1;
- ret = credential_from_url_gently(&c, url, 1);
- credential_clear(&c);
- return ret;
+ if (submodule_url_is_relative(url)) {
+ char *decoded;
+ const char *next;
+ int has_nl;
+
+ /*
+ * This could be appended to an http URL and url-decoded;
+ * check for malicious characters.
+ */
+ decoded = url_decode(url);
+ has_nl = !!strchr(decoded, '\n');
+
+ free(decoded);
+ if (has_nl)
+ return -1;
+
+ /*
+ * URLs which escape their root via "../" can overwrite
+ * the host field and previous components, resolving to
+ * URLs like https::example.com/submodule.git and
+ * https:///example.com/submodule.git that were
+ * susceptible to CVE-2020-11008.
+ */
+ if (count_leading_dotdots(url, &next) > 0 &&
+ (*next == ':' || *next == '/'))
+ return -1;
+ }
+
+ else if (url_to_curl_url(url, &curl_url)) {
+ struct credential c = CREDENTIAL_INIT;
+ int ret = 0;
+ if (credential_from_url_gently(&c, curl_url, 1) ||
+ !*c.host)
+ ret = -1;
+ credential_clear(&c);
+ return ret;
+ }
+
+ return 0;
}
struct fsck_gitmodules_data {
@@ -934,7 +1065,7 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
{
struct fsck_gitmodules_data *data = vdata;
const char *subsection, *key;
- int subsection_len;
+ size_t subsection_len;
char *name;
if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 ||
diff --git a/fuzz-commit-graph.c b/fuzz-commit-graph.c
index 0157acbf2e..9fd1c04edd 100644
--- a/fuzz-commit-graph.c
+++ b/fuzz-commit-graph.c
@@ -1,8 +1,7 @@
#include "commit-graph.h"
#include "repository.h"
-struct commit_graph *parse_commit_graph(void *graph_map, int fd,
- size_t graph_size);
+struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
@@ -11,7 +10,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
struct commit_graph *g;
initialize_the_repository();
- g = parse_commit_graph((void *)data, -1, size);
+ g = parse_commit_graph((void *)data, size);
repo_clear(the_repository);
free(g);
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 71158f7d8b..45fecf8bdf 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -76,23 +76,6 @@ print_command_list () {
echo "};"
}
-print_config_list () {
- cat <<EOF
-static const char *config_name_list[] = {
-EOF
- grep -h '^[a-zA-Z].*\..*::$' Documentation/*config.txt Documentation/config/*.txt |
- sed '/deprecated/d; s/::$//; s/, */\n/g' |
- sort |
- while read line
- do
- echo " \"$line\","
- done
- cat <<EOF
- NULL,
-};
-EOF
-}
-
exclude_programs=
while test "--exclude-program" = "$1"
do
@@ -113,5 +96,3 @@ echo
define_category_names "$1"
echo
print_command_list "$1"
-echo
-print_config_list
diff --git a/generate-configlist.sh b/generate-configlist.sh
new file mode 100755
index 0000000000..8692fe5cf4
--- /dev/null
+++ b/generate-configlist.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+echo "/* Automatically generated by generate-configlist.sh */"
+echo
+
+print_config_list () {
+ cat <<EOF
+static const char *config_name_list[] = {
+EOF
+ grep -h '^[a-zA-Z].*\..*::$' Documentation/*config.txt Documentation/config/*.txt |
+ sed '/deprecated/d; s/::$//; s/, */\n/g' |
+ sort |
+ sed 's/^.*$/ "&",/'
+ cat <<EOF
+ NULL,
+};
+EOF
+}
+
+echo
+print_config_list
diff --git a/git-compat-util.h b/git-compat-util.h
index aed0b5d4f9..8ba576e81e 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -389,6 +389,14 @@ static inline char *git_find_last_dir_sep(const char *path)
#define find_last_dir_sep git_find_last_dir_sep
#endif
+#ifndef has_dir_sep
+static inline int git_has_dir_sep(const char *path)
+{
+ return !!strchr(path, '/');
+}
+#define has_dir_sep(path) git_has_dir_sep(path)
+#endif
+
#ifndef query_user_email
#define query_user_email() NULL
#endif
diff --git a/git-p4.py b/git-p4.py
index 1fe03cd339..b8b2a1679e 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -34,6 +34,7 @@ import zipfile
import zlib
import ctypes
import errno
+import glob
# On python2.7 where raw_input() and input() are both availble,
# we want raw_input's semantics, but aliased to input for python3
@@ -165,7 +166,10 @@ def prompt(prompt_text):
"""
choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
while True:
- response = input(prompt_text).strip().lower()
+ sys.stderr.flush()
+ sys.stdout.write(prompt_text)
+ sys.stdout.flush()
+ response=sys.stdin.readline().strip().lower()
if not response:
continue
response = response[0]
@@ -202,6 +206,73 @@ def decode_path(path):
print('Path with non-ASCII characters detected. Used {} to decode: {}'.format(encoding, path))
return path
+def run_git_hook(cmd, param=[]):
+ """Execute a hook if the hook exists."""
+ if verbose:
+ sys.stderr.write("Looking for hook: %s\n" % cmd)
+ sys.stderr.flush()
+
+ hooks_path = gitConfig("core.hooksPath")
+ if len(hooks_path) <= 0:
+ hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
+
+ if not isinstance(param, list):
+ param=[param]
+
+ # resolve hook file name, OS depdenent
+ hook_file = os.path.join(hooks_path, cmd)
+ if platform.system() == 'Windows':
+ if not os.path.isfile(hook_file):
+ # look for the file with an extension
+ files = glob.glob(hook_file + ".*")
+ if not files:
+ return True
+ files.sort()
+ hook_file = files.pop()
+ while hook_file.upper().endswith(".SAMPLE"):
+ # The file is a sample hook. We don't want it
+ if len(files) > 0:
+ hook_file = files.pop()
+ else:
+ return True
+
+ if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
+ return True
+
+ return run_hook_command(hook_file, param) == 0
+
+def run_hook_command(cmd, param):
+ """Executes a git hook command
+ cmd = the command line file to be executed. This can be
+ a file that is run by OS association.
+
+ param = a list of parameters to pass to the cmd command
+
+ On windows, the extension is checked to see if it should
+ be run with the Git for Windows Bash shell. If there
+ is no file extension, the file is deemed a bash shell
+ and will be handed off to sh.exe. Otherwise, Windows
+ will be called with the shell to handle the file assocation.
+
+ For non Windows operating systems, the file is called
+ as an executable.
+ """
+ cli = [cmd] + param
+ use_shell = False
+ if platform.system() == 'Windows':
+ (root,ext) = os.path.splitext(cmd)
+ if ext == "":
+ exe_path = os.environ.get("EXEPATH")
+ if exe_path is None:
+ exe_path = ""
+ else:
+ exe_path = os.path.join(exe_path, "bin")
+ cli = [os.path.join(exe_path, "SH.EXE")] + cli
+ else:
+ use_shell = True
+ return subprocess.call(cli, shell=use_shell)
+
+
def write_pipe(c, stdin):
if verbose:
sys.stderr.write('Writing pipe: %s\n' % str(c))
@@ -1567,13 +1638,39 @@ class P4Submit(Command, P4UserMap):
"work from a local git branch that is not master"),
optparse.make_option("--disable-p4sync", dest="disable_p4sync", action="store_true",
help="Skip Perforce sync of p4/master after submit or shelve"),
+ optparse.make_option("--no-verify", dest="no_verify", action="store_true",
+ help="Bypass p4-pre-submit and p4-changelist hooks"),
]
self.description = """Submit changes from git to the perforce depot.\n
- The `p4-pre-submit` hook is executed if it exists and is executable.
- The hook takes no parameters and nothing from standard input. Exiting with
- non-zero status from this script prevents `git-p4 submit` from launching.
-
- One usage scenario is to run unit tests in the hook."""
+ The `p4-pre-submit` hook is executed if it exists and is executable. It
+ can be bypassed with the `--no-verify` command line option. The hook takes
+ no parameters and nothing from standard input. Exiting with a non-zero status
+ from this script prevents `git-p4 submit` from launching.
+
+ One usage scenario is to run unit tests in the hook.
+
+ The `p4-prepare-changelist` hook is executed right after preparing the default
+ changelist message and before the editor is started. It takes one parameter,
+ the name of the file that contains the changelist text. Exiting with a non-zero
+ status from the script will abort the process.
+
+ The purpose of the hook is to edit the message file in place, and it is not
+ supressed by the `--no-verify` option. This hook is called even if
+ `--prepare-p4-only` is set.
+
+ The `p4-changelist` hook is executed after the changelist message has been
+ edited by the user. It can be bypassed with the `--no-verify` option. It
+ takes a single parameter, the name of the file that holds the proposed
+ changelist text. Exiting with a non-zero status causes the command to abort.
+
+ The hook is allowed to edit the changelist file and can be used to normalize
+ the text into some project standard format. It can also be used to refuse the
+ Submit after inspect the message file.
+
+ The `p4-post-changelist` hook is invoked after the submit has successfully
+ occured in P4. It takes no parameters and is meant primarily for notification
+ and cannot affect the outcome of the git p4 submit action.
+ """
self.usage += " [name of git branch to submit into perforce depot]"
self.origin = ""
@@ -1591,6 +1688,7 @@ class P4Submit(Command, P4UserMap):
self.exportLabels = False
self.p4HasMoveCommand = p4_has_move_command()
self.branch = None
+ self.no_verify = False
if gitConfig('git-p4.largeFileSystem'):
die("Large file system not supported for git-p4 submit command. Please remove it from config.")
@@ -1978,6 +2076,9 @@ class P4Submit(Command, P4UserMap):
applyPatchCmd = patchcmd + "--check --apply -"
patch_succeeded = True
+ if verbose:
+ print("TryPatch: %s" % tryPatchCmd)
+
if os.system(tryPatchCmd) != 0:
fixed_rcs_keywords = False
patch_succeeded = False
@@ -2017,6 +2118,7 @@ class P4Submit(Command, P4UserMap):
print("Retrying the patch with RCS keywords cleaned up")
if os.system(tryPatchCmd) == 0:
patch_succeeded = True
+ print("Patch succeesed this time with RCS keywords cleaned")
if not patch_succeeded:
for f in editedFiles:
@@ -2077,55 +2179,73 @@ class P4Submit(Command, P4UserMap):
tmpFile.write(encode_text_stream(submitTemplate))
tmpFile.close()
- if self.prepare_p4_only:
- #
- # Leave the p4 tree prepared, and the submit template around
- # and let the user decide what to do next
- #
- print()
- print("P4 workspace prepared for submission.")
- print("To submit or revert, go to client workspace")
- print(" " + self.clientPath)
- print()
- print("To submit, use \"p4 submit\" to write a new description,")
- print("or \"p4 submit -i <%s\" to use the one prepared by" \
- " \"git p4\"." % fileName)
- print("You can delete the file \"%s\" when finished." % fileName)
-
- if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
- print("To preserve change ownership by user %s, you must\n" \
- "do \"p4 change -f <change>\" after submitting and\n" \
- "edit the User field.")
- if pureRenameCopy:
- print("After submitting, renamed files must be re-synced.")
- print("Invoke \"p4 sync -f\" on each of these files:")
- for f in pureRenameCopy:
- print(" " + f)
-
- print()
- print("To revert the changes, use \"p4 revert ...\", and delete")
- print("the submit template file \"%s\"" % fileName)
- if filesToAdd:
- print("Since the commit adds new files, they must be deleted:")
- for f in filesToAdd:
- print(" " + f)
- print()
- return True
-
- #
- # Let the user edit the change description, then submit it.
- #
submitted = False
try:
+ # Allow the hook to edit the changelist text before presenting it
+ # to the user.
+ if not run_git_hook("p4-prepare-changelist", [fileName]):
+ return False
+
+ if self.prepare_p4_only:
+ #
+ # Leave the p4 tree prepared, and the submit template around
+ # and let the user decide what to do next
+ #
+ submitted = True
+ print("")
+ print("P4 workspace prepared for submission.")
+ print("To submit or revert, go to client workspace")
+ print(" " + self.clientPath)
+ print("")
+ print("To submit, use \"p4 submit\" to write a new description,")
+ print("or \"p4 submit -i <%s\" to use the one prepared by" \
+ " \"git p4\"." % fileName)
+ print("You can delete the file \"%s\" when finished." % fileName)
+
+ if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
+ print("To preserve change ownership by user %s, you must\n" \
+ "do \"p4 change -f <change>\" after submitting and\n" \
+ "edit the User field.")
+ if pureRenameCopy:
+ print("After submitting, renamed files must be re-synced.")
+ print("Invoke \"p4 sync -f\" on each of these files:")
+ for f in pureRenameCopy:
+ print(" " + f)
+
+ print("")
+ print("To revert the changes, use \"p4 revert ...\", and delete")
+ print("the submit template file \"%s\"" % fileName)
+ if filesToAdd:
+ print("Since the commit adds new files, they must be deleted:")
+ for f in filesToAdd:
+ print(" " + f)
+ print("")
+ sys.stdout.flush()
+ return True
+
if self.edit_template(fileName):
+ if not self.no_verify:
+ if not run_git_hook("p4-changelist", [fileName]):
+ print("The p4-changelist hook failed.")
+ sys.stdout.flush()
+ return False
+
# read the edited message and submit
tmpFile = open(fileName, "rb")
message = decode_text_stream(tmpFile.read())
tmpFile.close()
if self.isWindows:
message = message.replace("\r\n", "\n")
- submitTemplate = message[:message.index(separatorLine)]
+ if message.find(separatorLine) != -1:
+ submitTemplate = message[:message.index(separatorLine)]
+ else:
+ submitTemplate = message
+
+ if len(submitTemplate.strip()) == 0:
+ print("Changelist is empty, aborting this changelist.")
+ sys.stdout.flush()
+ return False
if update_shelve:
p4_write_pipe(['shelve', '-r', '-i'], submitTemplate)
@@ -2148,20 +2268,23 @@ class P4Submit(Command, P4UserMap):
submitted = True
+ run_git_hook("p4-post-changelist")
finally:
- # skip this patch
+ # Revert changes if we skip this patch
if not submitted or self.shelve:
if self.shelve:
print ("Reverting shelved files.")
else:
print ("Submission cancelled, undoing p4 changes.")
+ sys.stdout.flush()
for f in editedFiles | filesToDelete:
p4_revert(f)
for f in filesToAdd:
p4_revert(f)
os.remove(f)
- os.remove(fileName)
+ if not self.prepare_p4_only:
+ os.remove(fileName)
return submitted
# Export git tags as p4 labels. Create a p4 label and then tag
@@ -2385,13 +2508,17 @@ class P4Submit(Command, P4UserMap):
sys.exit("number of commits (%d) must match number of shelved changelist (%d)" %
(len(commits), num_shelves))
- hooks_path = gitConfig("core.hooksPath")
- if len(hooks_path) <= 0:
- hooks_path = os.path.join(os.environ.get("GIT_DIR", ".git"), "hooks")
-
- hook_file = os.path.join(hooks_path, "p4-pre-submit")
- if os.path.isfile(hook_file) and os.access(hook_file, os.X_OK) and subprocess.call([hook_file]) != 0:
- sys.exit(1)
+ if not self.no_verify:
+ try:
+ if not run_git_hook("p4-pre-submit"):
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nYou can skip " \
+ "this pre-submission check by adding\nthe command line option '--no-verify', " \
+ "however,\nthis will also skip the p4-changelist hook as well.")
+ sys.exit(1)
+ except Exception as e:
+ print("\nThe p4-pre-submit hook failed, aborting the submit.\n\nThe hook failed "\
+ "with the error '{0}'".format(e.message) )
+ sys.exit(1)
#
# Apply the commits, one at a time. On failure, ask if should
@@ -4205,7 +4332,6 @@ commands = {
"unshelve" : P4Unshelve,
}
-
def main():
if len(sys.argv[1:]) == 0:
printUsage(commands.keys())
diff --git a/git-submodule.sh b/git-submodule.sh
index 89f915cae9..08e0439df0 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -48,6 +48,8 @@ depth=
progress=
dissociate=
single_branch=
+jobs=
+recommend_shallow=
die_if_unmatched ()
{
diff --git a/git.c b/git.c
index b07198fe03..2e4efb4ff0 100644
--- a/git.c
+++ b/git.c
@@ -351,6 +351,7 @@ static int handle_alias(int *argcp, const char ***argv)
trace2_cmd_alias(alias_command, child.args.argv);
trace2_cmd_list_config();
+ trace2_cmd_list_env_vars();
trace2_cmd_name("_run_shell_alias_");
ret = run_command(&child);
@@ -388,6 +389,7 @@ static int handle_alias(int *argcp, const char ***argv)
trace2_cmd_alias(alias_command, new_argv);
trace2_cmd_list_config();
+ trace2_cmd_list_env_vars();
*argv = new_argv;
*argcp += count - 1;
@@ -439,6 +441,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
trace_argv_printf(argv, "trace: built-in: git");
trace2_cmd_name(p->cmd);
trace2_cmd_list_config();
+ trace2_cmd_list_env_vars();
validate_cache_entries(the_repository->index);
status = p->fn(argc, argv, prefix);
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 65a3a9e62e..0959a782ec 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -1291,9 +1291,23 @@ our $is_last_request = sub { 1 };
our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
our $CGI = 'CGI';
our $cgi;
+our $FCGI_Stream_PRINT_raw = \&FCGI::Stream::PRINT;
sub configure_as_fcgi {
require CGI::Fast;
our $CGI = 'CGI::Fast';
+ # FCGI is not Unicode aware hence the UTF-8 encoding must be done manually.
+ # However no encoding must be done within git_blob_plain() and git_snapshot()
+ # which must still output in raw binary mode.
+ no warnings 'redefine';
+ my $enc = Encode::find_encoding('UTF-8');
+ *FCGI::Stream::PRINT = sub {
+ my @OUTPUT = @_;
+ for (my $i = 1; $i < @_; $i++) {
+ $OUTPUT[$i] = $enc->encode($_[$i], Encode::FB_CROAK|Encode::LEAVE_SRC);
+ }
+ @_ = @OUTPUT;
+ goto $FCGI_Stream_PRINT_raw;
+ };
my $request_number = 0;
# let each child service 100 requests
@@ -4627,7 +4641,7 @@ sub git_print_log {
# print log
my $skip_blank_line = 0;
foreach my $line (@$log) {
- if ($line =~ m/^\s*([A-Z][-A-Za-z]*-[Bb]y|C[Cc]): /) {
+ if ($line =~ m/^\s*([A-Z][-A-Za-z]*-([Bb]y|[Tt]o)|C[Cc]|(Clos|Fix)es): /) {
if (! $opts{'-remove_signoff'}) {
print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
$skip_blank_line = 1;
@@ -7079,6 +7093,7 @@ sub git_blob_plain {
($sandbox ? 'attachment' : 'inline')
. '; filename="' . $save_as . '"');
local $/ = undef;
+ local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw;
binmode STDOUT, ':raw';
print <$fd>;
binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -7417,6 +7432,7 @@ sub git_snapshot {
open my $fd, "-|", $cmd
or die_error(500, "Execute git-archive failed");
+ local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw;
binmode STDOUT, ':raw';
print <$fd>;
binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
diff --git a/graph.c b/graph.c
index 4fb25ad464..4cd9915075 100644
--- a/graph.c
+++ b/graph.c
@@ -1055,7 +1055,7 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
graph_update_state(graph, GRAPH_COLLAPSING);
}
-const char merge_chars[] = {'/', '|', '\\'};
+static const char merge_chars[] = {'/', '|', '\\'};
static void graph_output_post_merge_line(struct git_graph *graph, struct graph_line *line)
{
diff --git a/help.c b/help.c
index cf67624a94..1de9c0d589 100644
--- a/help.c
+++ b/help.c
@@ -407,91 +407,6 @@ void list_common_guides_help(void)
putchar('\n');
}
-struct slot_expansion {
- const char *prefix;
- const char *placeholder;
- void (*fn)(struct string_list *list, const char *prefix);
- int found;
-};
-
-void list_config_help(int for_human)
-{
- struct slot_expansion slot_expansions[] = {
- { "advice", "*", list_config_advices },
- { "color.branch", "<slot>", list_config_color_branch_slots },
- { "color.decorate", "<slot>", list_config_color_decorate_slots },
- { "color.diff", "<slot>", list_config_color_diff_slots },
- { "color.grep", "<slot>", list_config_color_grep_slots },
- { "color.interactive", "<slot>", list_config_color_interactive_slots },
- { "color.remote", "<slot>", list_config_color_sideband_slots },
- { "color.status", "<slot>", list_config_color_status_slots },
- { "fsck", "<msg-id>", list_config_fsck_msg_ids },
- { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids },
- { NULL, NULL, NULL }
- };
- const char **p;
- struct slot_expansion *e;
- struct string_list keys = STRING_LIST_INIT_DUP;
- int i;
-
- for (p = config_name_list; *p; p++) {
- const char *var = *p;
- struct strbuf sb = STRBUF_INIT;
-
- for (e = slot_expansions; e->prefix; e++) {
-
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder);
- if (!strcasecmp(var, sb.buf)) {
- e->fn(&keys, e->prefix);
- e->found++;
- break;
- }
- }
- strbuf_release(&sb);
- if (!e->prefix)
- string_list_append(&keys, var);
- }
-
- for (e = slot_expansions; e->prefix; e++)
- if (!e->found)
- BUG("slot_expansion %s.%s is not used",
- e->prefix, e->placeholder);
-
- string_list_sort(&keys);
- for (i = 0; i < keys.nr; i++) {
- const char *var = keys.items[i].string;
- const char *wildcard, *tag, *cut;
-
- if (for_human) {
- puts(var);
- continue;
- }
-
- wildcard = strchr(var, '*');
- tag = strchr(var, '<');
-
- if (!wildcard && !tag) {
- puts(var);
- continue;
- }
-
- 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);
- }
- string_list_clear(&keys, 0);
-}
-
static int get_alias(const char *var, const char *value, void *data)
{
struct string_list *list = data;
@@ -707,8 +622,32 @@ const char *help_unknown_cmd(const char *cmd)
exit(1);
}
+void get_version_info(struct strbuf *buf, int show_build_options)
+{
+ /*
+ * The format of this string should be kept stable for compatibility
+ * with external projects that rely on the output of "git version".
+ *
+ * Always show the version, even if other options are given.
+ */
+ strbuf_addf(buf, "git version %s\n", git_version_string);
+
+ if (show_build_options) {
+ strbuf_addf(buf, "cpu: %s\n", GIT_HOST_CPU);
+ if (git_built_from_commit_string[0])
+ strbuf_addf(buf, "built from commit: %s\n",
+ git_built_from_commit_string);
+ else
+ strbuf_addstr(buf, "no commit associated with this build\n");
+ strbuf_addf(buf, "sizeof-long: %d\n", (int)sizeof(long));
+ strbuf_addf(buf, "sizeof-size_t: %d\n", (int)sizeof(size_t));
+ /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
+ }
+}
+
int cmd_version(int argc, const char **argv, const char *prefix)
{
+ struct strbuf buf = STRBUF_INIT;
int build_options = 0;
const char * const usage[] = {
N_("git version [<options>]"),
@@ -722,25 +661,11 @@ int cmd_version(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, usage, 0);
- /*
- * The format of this string should be kept stable for compatibility
- * with external projects that rely on the output of "git version".
- *
- * Always show the version, even if other options are given.
- */
- printf("git version %s\n", git_version_string);
+ get_version_info(&buf, build_options);
+ printf("%s", buf.buf);
+
+ strbuf_release(&buf);
- if (build_options) {
- printf("cpu: %s\n", GIT_HOST_CPU);
- if (git_built_from_commit_string[0])
- printf("built from commit: %s\n",
- git_built_from_commit_string);
- else
- printf("no commit associated with this build\n");
- printf("sizeof-long: %d\n", (int)sizeof(long));
- printf("sizeof-size_t: %d\n", (int)sizeof(size_t));
- /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */
- }
return 0;
}
diff --git a/help.h b/help.h
index 7a455beeb7..500521b908 100644
--- a/help.h
+++ b/help.h
@@ -22,7 +22,6 @@ static inline void mput_char(char c, unsigned int num)
void list_common_cmds_help(void);
void list_all_cmds_help(void);
void list_common_guides_help(void);
-void list_config_help(int for_human);
void list_all_main_cmds(struct string_list *list);
void list_all_other_cmds(struct string_list *list);
@@ -38,6 +37,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len);
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 get_version_info(struct strbuf *buf, int show_build_options);
/*
* call this to die(), when it is suspected that the user mistyped a
diff --git a/http.c b/http.c
index 4283be9479..62aa995245 100644
--- a/http.c
+++ b/http.c
@@ -579,6 +579,7 @@ static int has_cert_password(void)
return 0;
if (!cert_auth.password) {
cert_auth.protocol = xstrdup("cert");
+ cert_auth.host = xstrdup("");
cert_auth.username = xstrdup("");
cert_auth.path = xstrdup(ssl_cert);
credential_fill(&cert_auth);
@@ -593,6 +594,7 @@ static int has_proxy_cert_password(void)
return 0;
if (!proxy_cert_auth.password) {
proxy_cert_auth.protocol = xstrdup("cert");
+ proxy_cert_auth.host = xstrdup("");
proxy_cert_auth.username = xstrdup("");
proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert);
credential_fill(&proxy_cert_auth);
diff --git a/line-log.c b/line-log.c
index 9010e00950..40e1738dbb 100644
--- a/line-log.c
+++ b/line-log.c
@@ -519,7 +519,7 @@ static void fill_line_ends(struct repository *r,
unsigned long *ends = NULL;
char *data = NULL;
- if (diff_populate_filespec(r, spec, 0))
+ if (diff_populate_filespec(r, spec, NULL))
die("Cannot read blob %s", oid_to_hex(&spec->oid));
ALLOC_ARRAY(ends, size);
@@ -1045,12 +1045,12 @@ static int process_diff_filepair(struct rev_info *rev,
return 0;
assert(pair->two->oid_valid);
- diff_populate_filespec(rev->diffopt.repo, pair->two, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->two, NULL);
file_target.ptr = pair->two->data;
file_target.size = pair->two->size;
if (pair->one->oid_valid) {
- diff_populate_filespec(rev->diffopt.repo, pair->one, 0);
+ diff_populate_filespec(rev->diffopt.repo, pair->one, NULL);
file_parent.ptr = pair->one->data;
file_parent.size = pair->one->size;
} else {
diff --git a/ll-merge.c b/ll-merge.c
index d65a8971db..1ec0b959e0 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -247,7 +247,7 @@ static int read_merge_config(const char *var, const char *value, void *cb)
{
struct ll_merge_driver *fn;
const char *key, *name;
- int namelen;
+ size_t namelen;
if (!strcmp(var, "merge.default"))
return git_config_string(&default_ll_merge, var, value);
diff --git a/log-tree.c b/log-tree.c
index 897a90233e..55a68d0c61 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -81,6 +81,56 @@ const struct name_decoration *get_name_decoration(const struct object *obj)
return lookup_decoration(&name_decoration, obj);
}
+static int match_ref_pattern(const char *refname,
+ const struct string_list_item *item)
+{
+ int matched = 0;
+ if (item->util == NULL) {
+ if (!wildmatch(item->string, refname, 0))
+ matched = 1;
+ } else {
+ const char *rest;
+ if (skip_prefix(refname, item->string, &rest) &&
+ (!*rest || *rest == '/'))
+ matched = 1;
+ }
+ return matched;
+}
+
+static int ref_filter_match(const char *refname,
+ const struct decoration_filter *filter)
+{
+ struct string_list_item *item;
+ const struct string_list *exclude_patterns = filter->exclude_ref_pattern;
+ const struct string_list *include_patterns = filter->include_ref_pattern;
+ const struct string_list *exclude_patterns_config =
+ filter->exclude_ref_config_pattern;
+
+ if (exclude_patterns && exclude_patterns->nr) {
+ for_each_string_list_item(item, exclude_patterns) {
+ if (match_ref_pattern(refname, item))
+ return 0;
+ }
+ }
+
+ if (include_patterns && include_patterns->nr) {
+ for_each_string_list_item(item, include_patterns) {
+ if (match_ref_pattern(refname, item))
+ return 1;
+ }
+ return 0;
+ }
+
+ if (exclude_patterns_config && exclude_patterns_config->nr) {
+ for_each_string_list_item(item, exclude_patterns_config) {
+ if (match_ref_pattern(refname, item))
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
static int add_ref_decoration(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
@@ -88,9 +138,7 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
enum decoration_type type = DECORATION_NONE;
struct decoration_filter *filter = (struct decoration_filter *)cb_data;
- if (filter && !ref_filter_match(refname,
- filter->include_ref_pattern,
- filter->exclude_ref_pattern))
+ if (filter && !ref_filter_match(refname, filter))
return 0;
if (starts_with(refname, git_replace_ref_base)) {
@@ -155,6 +203,9 @@ void load_ref_decorations(struct decoration_filter *filter, int flags)
for_each_string_list_item(item, filter->include_ref_pattern) {
normalize_glob_ref(item, NULL, item->string);
}
+ for_each_string_list_item(item, filter->exclude_ref_config_pattern) {
+ normalize_glob_ref(item, NULL, item->string);
+ }
}
decoration_loaded = 1;
decoration_flags = flags;
@@ -693,6 +744,7 @@ void show_log(struct rev_info *opt)
ctx.abbrev = opt->diffopt.abbrev;
ctx.after_subject = extra_headers;
ctx.preserve_subject = opt->preserve_subject;
+ ctx.encode_email_headers = opt->encode_email_headers;
ctx.reflog_info = opt->reflog_info;
ctx.fmt = opt->commit_format;
ctx.mailmap = opt->mailmap;
diff --git a/log-tree.h b/log-tree.h
index e668628074..8fa79289ec 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -8,7 +8,9 @@ struct log_info {
};
struct decoration_filter {
- struct string_list *include_ref_pattern, *exclude_ref_pattern;
+ struct string_list *include_ref_pattern;
+ struct string_list *exclude_ref_pattern;
+ struct string_list *exclude_ref_config_pattern;
};
int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
diff --git a/ls-refs.c b/ls-refs.c
index 818aef70a0..50d86866c6 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -93,7 +93,7 @@ int ls_refs(struct repository *r, struct argv_array *keys,
git_config(ls_refs_config, NULL);
- while (packet_reader_read(request) != PACKET_READ_FLUSH) {
+ while (packet_reader_read(request) == PACKET_READ_NORMAL) {
const char *arg = request->line;
const char *out;
@@ -105,6 +105,9 @@ int ls_refs(struct repository *r, struct argv_array *keys,
argv_array_push(&data.prefixes, out);
}
+ if (request->status != PACKET_READ_FLUSH)
+ die(_("expected flush after ls-refs arguments"));
+
head_ref_namespaced(send_ref, &data);
for_each_namespaced_ref(send_ref, &data);
packet_flush(1);
diff --git a/mailinfo.c b/mailinfo.c
index 742fa376ab..5681d9130d 100644
--- a/mailinfo.c
+++ b/mailinfo.c
@@ -447,19 +447,21 @@ static int convert_to_utf8(struct mailinfo *mi,
struct strbuf *line, const char *charset)
{
char *out;
+ size_t out_len;
if (!mi->metainfo_charset || !charset || !*charset)
return 0;
if (same_encoding(mi->metainfo_charset, charset))
return 0;
- out = reencode_string(line->buf, mi->metainfo_charset, charset);
+ out = reencode_string_len(line->buf, line->len,
+ mi->metainfo_charset, charset, &out_len);
if (!out) {
mi->input_error = -1;
return error("cannot convert from %s to %s",
charset, mi->metainfo_charset);
}
- strbuf_attach(line, out, strlen(out), strlen(out));
+ strbuf_attach(line, out, out_len, out_len);
return 0;
}
@@ -1136,6 +1138,11 @@ static void handle_info(struct mailinfo *mi)
else
continue;
+ if (memchr(hdr->buf, '\0', hdr->len)) {
+ error("a NUL byte in '%s' is not allowed.", header[i]);
+ mi->input_error = -1;
+ }
+
if (!strcmp(header[i], "Subject")) {
if (!mi->keep_subject) {
cleanup_subject(mi, hdr);
diff --git a/midx.c b/midx.c
index 1527e464a7..9a61d3b37d 100644
--- a/midx.c
+++ b/midx.c
@@ -72,9 +72,9 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
FREE_AND_NULL(midx_name);
midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
FLEX_ALLOC_STR(m, object_dir, object_dir);
- m->fd = fd;
m->data = midx_map;
m->data_len = midx_size;
m->local = local;
@@ -190,8 +190,6 @@ void close_midx(struct multi_pack_index *m)
return;
munmap((unsigned char *)m->data, m->data_len);
- close(m->fd);
- m->fd = -1;
for (i = 0; i < m->num_packs; i++) {
if (m->packs[i])
@@ -923,6 +921,12 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
cur_chunk = 0;
num_chunks = large_offsets_needed ? 5 : 4;
+ if (packs.nr - dropped_packs == 0) {
+ error(_("no pack files to index."));
+ result = 1;
+ goto cleanup;
+ }
+
written = write_midx_header(f, num_chunks, packs.nr - dropped_packs);
chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
@@ -1124,6 +1128,15 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
i, oid_fanout1, oid_fanout2, i + 1);
}
+ if (m->num_objects == 0) {
+ midx_report(_("the midx contains no oid"));
+ /*
+ * Remaining tests assume that we have objects, so we can
+ * return here.
+ */
+ return verify_midx_error;
+ }
+
if (flags & MIDX_PROGRESS)
progress = start_sparse_progress(_("Verifying OID order in multi-pack-index"),
m->num_objects - 1);
diff --git a/midx.h b/midx.h
index e6fa356b5c..b18cf53bc4 100644
--- a/midx.h
+++ b/midx.h
@@ -12,8 +12,6 @@ struct repository;
struct multi_pack_index {
struct multi_pack_index *next;
- int fd;
-
const unsigned char *data;
size_t data_len;
diff --git a/object-store.h b/object-store.h
index be72fee7d5..d1e490f203 100644
--- a/object-store.h
+++ b/object-store.h
@@ -4,7 +4,7 @@
#include "cache.h"
#include "oidmap.h"
#include "list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "strbuf.h"
#include "thread-utils.h"
diff --git a/object.h b/object.h
index 2dbabfca0a..b22328b838 100644
--- a/object.h
+++ b/object.h
@@ -59,7 +59,7 @@ struct object_array {
/*
* object flag allocation:
- * revision.h: 0---------10 25----28
+ * revision.h: 0---------10 15 25----28
* fetch-pack.c: 01
* negotiator/default.c: 2--5
* walker.c: 0-2
diff --git a/sha1-array.c b/oid-array.c
index 3eeadfede9..8657a5cedf 100644
--- a/sha1-array.c
+++ b/oid-array.c
@@ -1,5 +1,5 @@
#include "cache.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "sha1-lookup.h"
void oid_array_append(struct oid_array *array, const struct object_id *oid)
@@ -46,9 +46,9 @@ int oid_array_for_each(struct oid_array *array,
for_each_oid_fn fn,
void *data)
{
- int i;
+ size_t i;
- /* No oid_array_sort() here! See sha1-array.h */
+ /* No oid_array_sort() here! See oid-array.h */
for (i = 0; i < array->nr; i++) {
int ret = fn(array->oid + i, data);
@@ -62,7 +62,7 @@ int oid_array_for_each_unique(struct oid_array *array,
for_each_oid_fn fn,
void *data)
{
- int i;
+ size_t i;
if (!array->sorted)
oid_array_sort(array);
@@ -82,7 +82,7 @@ void oid_array_filter(struct oid_array *array,
for_each_oid_fn want,
void *cb_data)
{
- unsigned nr = array->nr, src, dst;
+ size_t nr = array->nr, src, dst;
struct object_id *oids = array->oid;
for (src = dst = 0; src < nr; src++) {
diff --git a/sha1-array.h b/oid-array.h
index dc1bca9c9a..f28d322c90 100644
--- a/sha1-array.h
+++ b/oid-array.h
@@ -19,7 +19,7 @@
*
* void some_func(void)
* {
- * struct sha1_array hashes = OID_ARRAY_INIT;
+ * struct oid_array hashes = OID_ARRAY_INIT;
* struct object_id oid;
*
* // Read objects into our set
@@ -49,8 +49,8 @@
*/
struct oid_array {
struct object_id *oid;
- int nr;
- int alloc;
+ size_t nr;
+ size_t alloc;
int sorted;
};
diff --git a/oidset.c b/oidset.c
index f63ce818f6..15d4e18c37 100644
--- a/oidset.c
+++ b/oidset.c
@@ -36,6 +36,11 @@ void oidset_clear(struct oidset *set)
oidset_init(set, 0);
}
+int oidset_size(struct oidset *set)
+{
+ return kh_size(&set->set);
+}
+
void oidset_parse_file(struct oidset *set, const char *path)
{
FILE *fp;
diff --git a/oidset.h b/oidset.h
index d8a106b127..209ae7a173 100644
--- a/oidset.h
+++ b/oidset.h
@@ -4,7 +4,7 @@
#include "khash.h"
/**
- * This API is similar to sha1-array, in that it maintains a set of object ids
+ * This API is similar to oid-array, in that it maintains a set of object ids
* in a memory-efficient way. The major differences are:
*
* 1. It uses a hash, so we can do online duplicate removal, rather than
@@ -55,6 +55,11 @@ int oidset_insert(struct oidset *set, const struct object_id *oid);
int oidset_remove(struct oidset *set, const struct object_id *oid);
/**
+ * Returns the number of oids in the set.
+ */
+int oidset_size(struct oidset *set);
+
+/**
* Remove all entries from the oidset, freeing any resources associated with
* it.
*/
diff --git a/parse-options-cb.c b/parse-options-cb.c
index a28b55be48..86cd393013 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -5,7 +5,7 @@
#include "color.h"
#include "string-list.h"
#include "argv-array.h"
-#include "sha1-array.h"
+#include "oid-array.h"
/*----- some often used options -----*/
diff --git a/parse-options.c b/parse-options.c
index 63d6bab60c..c57618d537 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -648,6 +648,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
int short_name;
const char *long_name;
const char *source;
+ struct strbuf help = STRBUF_INIT;
int j;
if (newopt[i].type != OPTION_ALIAS)
@@ -659,6 +660,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
if (!long_name)
BUG("An alias must have long option name");
+ strbuf_addf(&help, _("alias of --%s"), source);
for (j = 0; j < nr; j++) {
const char *name = options[j].long_name;
@@ -669,15 +671,10 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx,
if (options[j].type == OPTION_ALIAS)
BUG("No please. Nested aliases are not supported.");
- /*
- * NEEDSWORK: this is a bit inconsistent because
- * usage_with_options() on the original options[] will print
- * help string as "alias of %s" but "git cmd -h" will
- * print the original help string.
- */
memcpy(newopt + i, options + j, sizeof(*newopt));
newopt[i].short_name = short_name;
newopt[i].long_name = long_name;
+ newopt[i].help = strbuf_detach(&help, NULL);
break;
}
diff --git a/parse-options.h b/parse-options.h
index fece5ba628..46af942093 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -336,5 +336,6 @@ int parse_opt_passthru_argv(const struct option *, const char *, int);
#define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message"))
#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file"))
#define OPT_PATHSPEC_FILE_NUL(v) OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character"))
+#define OPT_AUTOSTASH(v) OPT_BOOL(0, "autostash", v, N_("automatically stash/stash pop before and after"))
#endif
diff --git a/path.c b/path.c
index 9bd717c307..8b2c753191 100644
--- a/path.c
+++ b/path.c
@@ -1535,5 +1535,6 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
+REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index 14d6dcad16..1f1bf8f87a 100644
--- a/path.h
+++ b/path.h
@@ -177,11 +177,12 @@ struct path_cache {
const char *merge_rr;
const char *merge_mode;
const char *merge_head;
+ const char *merge_autostash;
const char *fetch_head;
const char *shallow;
};
-#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+#define PATH_CACHE_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
const char *git_path_cherry_pick_head(struct repository *r);
const char *git_path_revert_head(struct repository *r);
@@ -190,6 +191,7 @@ const char *git_path_merge_msg(struct repository *r);
const char *git_path_merge_rr(struct repository *r);
const char *git_path_merge_mode(struct repository *r);
const char *git_path_merge_head(struct repository *r);
+const char *git_path_merge_autostash(struct repository *r);
const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);
diff --git a/pretty.c b/pretty.c
index 28afc701b6..2a3d46bf42 100644
--- a/pretty.c
+++ b/pretty.c
@@ -474,7 +474,8 @@ void pp_user_info(struct pretty_print_context *pp,
}
strbuf_addstr(sb, "From: ");
- if (needs_rfc2047_encoding(namebuf, namelen)) {
+ if (pp->encode_email_headers &&
+ needs_rfc2047_encoding(namebuf, namelen)) {
add_rfc2047(sb, namebuf, namelen,
encoding, RFC2047_ADDRESS);
max_length = 76; /* per rfc2047 */
@@ -1767,7 +1768,8 @@ void pp_title_line(struct pretty_print_context *pp,
if (pp->print_email_subject) {
if (pp->rev)
fmt_output_email_subject(sb, pp->rev);
- if (needs_rfc2047_encoding(title.buf, title.len))
+ if (pp->encode_email_headers &&
+ needs_rfc2047_encoding(title.buf, title.len))
add_rfc2047(sb, title.buf, title.len,
encoding, RFC2047_SUBJECT);
else
diff --git a/pretty.h b/pretty.h
index 4ad1fc31ff..071f2fb8e4 100644
--- a/pretty.h
+++ b/pretty.h
@@ -43,6 +43,7 @@ struct pretty_print_context {
struct string_list *mailmap;
int color;
struct ident_split *from_ident;
+ unsigned encode_email_headers:1;
/*
* Fields below here are manipulated internally by pp_* functions and
diff --git a/progress.c b/progress.c
index 19805ac646..75633e9c5e 100644
--- a/progress.c
+++ b/progress.c
@@ -8,6 +8,7 @@
* published by the Free Software Foundation.
*/
+#define GIT_TEST_PROGRESS_ONLY
#include "cache.h"
#include "gettext.h"
#include "progress.h"
@@ -52,7 +53,6 @@ static volatile sig_atomic_t progress_update;
*/
int progress_testing;
uint64_t progress_test_ns = 0;
-void progress_test_force_update(void); /* To silence -Wmissing-prototypes */
void progress_test_force_update(void)
{
progress_update = 1;
diff --git a/progress.h b/progress.h
index 847338911f..f1913acf73 100644
--- a/progress.h
+++ b/progress.h
@@ -3,6 +3,14 @@
struct progress;
+#ifdef GIT_TEST_PROGRESS_ONLY
+
+extern int progress_testing;
+extern uint64_t progress_test_ns;
+void progress_test_force_update(void);
+
+#endif
+
void display_throughput(struct progress *progress, uint64_t total);
void display_progress(struct progress *progress, uint64_t n);
struct progress *start_progress(const char *title, uint64_t total);
diff --git a/promisor-remote.c b/promisor-remote.c
index 9f338c945f..baaea12fd6 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -101,7 +101,7 @@ static void promisor_remote_move_to_tail(struct promisor_remote *r,
static int promisor_remote_config(const char *var, const char *value, void *data)
{
const char *name;
- int namelen;
+ size_t namelen;
const char *subkey;
if (!strcmp(var, "core.partialclonefilter"))
@@ -241,6 +241,9 @@ int promisor_remote_get_direct(struct repository *repo,
int to_free = 0;
int res = -1;
+ if (oid_nr == 0)
+ return 0;
+
promisor_remote_init();
for (r = promisors; r; r = r->next) {
diff --git a/promisor-remote.h b/promisor-remote.h
index 737bac3a33..6343c47d18 100644
--- a/promisor-remote.h
+++ b/promisor-remote.h
@@ -20,6 +20,14 @@ struct promisor_remote {
void promisor_remote_reinit(void);
struct promisor_remote *promisor_remote_find(const char *remote_name);
int has_promisor_remote(void);
+
+/*
+ * Fetches all requested objects from all promisor remotes, trying them one at
+ * a time until all objects are fetched. Returns 0 upon success, and non-zero
+ * otherwise.
+ *
+ * If oid_nr is 0, this function returns 0 (success) immediately.
+ */
int promisor_remote_get_direct(struct repository *repo,
const struct object_id *oids,
int oid_nr);
diff --git a/prompt.c b/prompt.c
index 6d5885d009..5ded21a017 100644
--- a/prompt.c
+++ b/prompt.c
@@ -74,3 +74,15 @@ char *git_prompt(const char *prompt, int flags)
}
return r;
}
+
+int git_read_line_interactively(struct strbuf *line)
+{
+ int ret;
+
+ fflush(stdout);
+ ret = strbuf_getline_lf(line, stdin);
+ if (ret != EOF)
+ strbuf_trim_trailing_newline(line);
+
+ return ret;
+}
diff --git a/prompt.h b/prompt.h
index e04cced030..e294e93541 100644
--- a/prompt.h
+++ b/prompt.h
@@ -6,4 +6,6 @@
char *git_prompt(const char *prompt, int flags);
+int git_read_line_interactively(struct strbuf *line);
+
#endif /* PROMPT_H */
diff --git a/protocol.c b/protocol.c
index 803bef5c87..d390391eba 100644
--- a/protocol.c
+++ b/protocol.c
@@ -39,7 +39,7 @@ enum protocol_version get_protocol_version_config(void)
return env;
}
- return protocol_v2;
+ return protocol_v0;
}
enum protocol_version determine_protocol_version_server(void)
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644
index 0000000000..261520b472
--- /dev/null
+++ b/prune-packed.c
@@ -0,0 +1,43 @@
+#include "object-store.h"
+#include "packfile.h"
+#include "progress.h"
+#include "prune-packed.h"
+
+static struct progress *progress;
+
+static int prune_subdir(unsigned int nr, const char *path, void *data)
+{
+ int *opts = data;
+ display_progress(progress, nr + 1);
+ if (!(*opts & PRUNE_PACKED_DRY_RUN))
+ rmdir(path);
+ return 0;
+}
+
+static int prune_object(const struct object_id *oid, const char *path,
+ void *data)
+{
+ int *opts = data;
+
+ if (!has_object_pack(oid))
+ return 0;
+
+ if (*opts & PRUNE_PACKED_DRY_RUN)
+ printf("rm -f %s\n", path);
+ else
+ unlink_or_warn(path);
+ return 0;
+}
+
+void prune_packed_objects(int opts)
+{
+ if (opts & PRUNE_PACKED_VERBOSE)
+ progress = start_delayed_progress(_("Removing duplicate objects"), 256);
+
+ for_each_loose_file_in_objdir(get_object_directory(),
+ prune_object, NULL, prune_subdir, &opts);
+
+ /* Ensure we show 100% before finishing progress */
+ display_progress(progress, 256);
+ stop_progress(&progress);
+}
diff --git a/prune-packed.h b/prune-packed.h
new file mode 100644
index 0000000000..936fa9df23
--- /dev/null
+++ b/prune-packed.h
@@ -0,0 +1,9 @@
+#ifndef PRUNE_PACKED_H
+#define PRUNE_PACKED_H
+
+#define PRUNE_PACKED_DRY_RUN 01
+#define PRUNE_PACKED_VERBOSE 02
+
+void prune_packed_objects(int);
+
+#endif
diff --git a/range-diff.c b/range-diff.c
index f745567cf6..40af086281 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -63,6 +63,8 @@ static int read_patches(const char *range, struct string_list *list,
"--output-indicator-old=<",
"--output-indicator-context=#",
"--no-abbrev-commit",
+ "--pretty=medium",
+ "--notes",
NULL);
if (other_arg)
argv_array_pushv(&cp.args, other_arg->argv);
@@ -106,20 +108,34 @@ static int read_patches(const char *range, struct string_list *list,
continue;
}
+ if (!util) {
+ error(_("could not parse first line of `log` output: "
+ "did not start with 'commit ': '%s'"),
+ line);
+ string_list_clear(list, 1);
+ strbuf_release(&buf);
+ strbuf_release(&contents);
+ finish_command(&cp);
+ return -1;
+ }
+
if (starts_with(line, "diff --git")) {
struct patch patch = { 0 };
struct strbuf root = STRBUF_INIT;
int linenr = 0;
+ int orig_len;
in_header = 0;
strbuf_addch(&buf, '\n');
if (!util->diff_offset)
util->diff_offset = buf.len;
line[len - 1] = '\n';
+ orig_len = len;
len = parse_git_diff_header(&root, &linenr, 0, line,
len, size, &patch);
if (len < 0)
- die(_("could not parse git header '%.*s'"), (int)len, line);
+ die(_("could not parse git header '%.*s'"),
+ orig_len, line);
strbuf_addstr(&buf, " ## ");
if (patch.is_new > 0)
strbuf_addf(&buf, "%s (new)", patch.new_name);
diff --git a/ref-filter.c b/ref-filter.c
index b1812cb69a..35776838f4 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1976,10 +1976,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
}
/*
- * Given a ref (sha1, refname), check if the ref belongs to the array
- * of sha1s. If the given ref is a tag, check if the given tag points
- * at one of the sha1s in the given sha1 array.
- * the given sha1_array.
+ * Given a ref (oid, refname), check if the ref belongs to the array
+ * of oids. If the given ref is a tag, check if the given tag points
+ * at one of the oids in the given oid array.
* NEEDSWORK:
* 1. Only a single level of inderection is obtained, we might want to
* change this to account for multiple levels (e.g. annotated tags
diff --git a/ref-filter.h b/ref-filter.h
index f1dcff4c6e..64330e9601 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -1,7 +1,7 @@
#ifndef REF_FILTER_H
#define REF_FILTER_H
-#include "sha1-array.h"
+#include "oid-array.h"
#include "refs.h"
#include "commit.h"
#include "parse-options.h"
diff --git a/refs.c b/refs.c
index 1ab0bb54d3..224ff66c7b 100644
--- a/refs.c
+++ b/refs.c
@@ -321,50 +321,6 @@ int ref_exists(const char *refname)
return refs_ref_exists(get_main_ref_store(the_repository), refname);
}
-static int match_ref_pattern(const char *refname,
- const struct string_list_item *item)
-{
- int matched = 0;
- if (item->util == NULL) {
- if (!wildmatch(item->string, refname, 0))
- matched = 1;
- } else {
- const char *rest;
- if (skip_prefix(refname, item->string, &rest) &&
- (!*rest || *rest == '/'))
- matched = 1;
- }
- return matched;
-}
-
-int ref_filter_match(const char *refname,
- const struct string_list *include_patterns,
- const struct string_list *exclude_patterns)
-{
- struct string_list_item *item;
-
- if (exclude_patterns && exclude_patterns->nr) {
- for_each_string_list_item(item, exclude_patterns) {
- if (match_ref_pattern(refname, item))
- return 0;
- }
- }
-
- if (include_patterns && include_patterns->nr) {
- int found = 0;
- for_each_string_list_item(item, include_patterns) {
- if (match_ref_pattern(refname, item)) {
- found = 1;
- break;
- }
- }
-
- if (!found)
- return 0;
- }
- return 1;
-}
-
static int filter_refs(const char *refname, const struct object_id *oid,
int flags, void *data)
{
@@ -1852,14 +1808,14 @@ static struct ref_store *ref_store_init(const char *gitdir,
struct ref_store *get_main_ref_store(struct repository *r)
{
- if (r->refs)
- return r->refs;
+ if (r->refs_private)
+ return r->refs_private;
if (!r->gitdir)
BUG("attempting to get main_ref_store outside of repository");
- r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
- return r->refs;
+ r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+ return r->refs_private;
}
/*
diff --git a/refs.h b/refs.h
index 545029c6d8..a92d2c74c8 100644
--- a/refs.h
+++ b/refs.h
@@ -361,18 +361,6 @@ int for_each_rawref(each_ref_fn fn, void *cb_data);
void normalize_glob_ref(struct string_list_item *item, const char *prefix,
const char *pattern);
-/*
- * Returns 0 if refname matches any of the exclude_patterns, or if it doesn't
- * match any of the include_patterns. Returns 1 otherwise.
- *
- * If pattern list is NULL or empty, matching against that list is skipped.
- * This has the effect of matching everything by default, unless the user
- * specifies rules otherwise.
- */
-int ref_filter_match(const char *refname,
- const struct string_list *include_patterns,
- const struct string_list *exclude_patterns);
-
static inline const char *has_glob_specials(const char *pattern)
{
return strpbrk(pattern, "?*[");
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 561c33ac8a..6516c7bc8c 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2565,16 +2565,18 @@ static void files_transaction_cleanup(struct files_ref_store *refs,
}
}
- if (backend_data->packed_transaction &&
- ref_transaction_abort(backend_data->packed_transaction, &err)) {
- error("error aborting transaction: %s", err.buf);
- strbuf_release(&err);
- }
+ if (backend_data) {
+ if (backend_data->packed_transaction &&
+ ref_transaction_abort(backend_data->packed_transaction, &err)) {
+ error("error aborting transaction: %s", err.buf);
+ strbuf_release(&err);
+ }
- if (backend_data->packed_refs_locked)
- packed_refs_unlock(refs->packed_ref_store);
+ if (backend_data->packed_refs_locked)
+ packed_refs_unlock(refs->packed_ref_store);
- free(backend_data);
+ free(backend_data);
+ }
transaction->state = REF_TRANSACTION_CLOSED;
}
diff --git a/remote-curl.c b/remote-curl.c
index e4cd321844..1c9aa3d0ab 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -12,7 +12,7 @@
#include "sideband.h"
#include "argv-array.h"
#include "credential.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "send-pack.h"
#include "protocol.h"
#include "quote.h"
diff --git a/remote.c b/remote.c
index c43196ec06..534c6426f1 100644
--- a/remote.c
+++ b/remote.c
@@ -174,54 +174,43 @@ static void add_merge(struct branch *branch, const char *name)
branch->merge_name[branch->merge_nr++] = name;
}
-static struct branch *make_branch(const char *name, int len)
+static struct branch *make_branch(const char *name, size_t len)
{
struct branch *ret;
int i;
for (i = 0; i < branches_nr; i++) {
- if (len ? (!strncmp(name, branches[i]->name, len) &&
- !branches[i]->name[len]) :
- !strcmp(name, branches[i]->name))
+ if (!strncmp(name, branches[i]->name, len) &&
+ !branches[i]->name[len])
return branches[i];
}
ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
ret = xcalloc(1, sizeof(struct branch));
branches[branches_nr++] = ret;
- if (len)
- ret->name = xstrndup(name, len);
- else
- ret->name = xstrdup(name);
+ ret->name = xstrndup(name, len);
ret->refname = xstrfmt("refs/heads/%s", ret->name);
return ret;
}
-static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len)
+static struct rewrite *make_rewrite(struct rewrites *r,
+ const char *base, size_t len)
{
struct rewrite *ret;
int i;
for (i = 0; i < r->rewrite_nr; i++) {
- if (len
- ? (len == r->rewrite[i]->baselen &&
- !strncmp(base, r->rewrite[i]->base, len))
- : !strcmp(base, r->rewrite[i]->base))
+ if (len == r->rewrite[i]->baselen &&
+ !strncmp(base, r->rewrite[i]->base, len))
return r->rewrite[i];
}
ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc);
ret = xcalloc(1, sizeof(struct rewrite));
r->rewrite[r->rewrite_nr++] = ret;
- if (len) {
- ret->base = xstrndup(base, len);
- ret->baselen = len;
- }
- else {
- ret->base = xstrdup(base);
- ret->baselen = strlen(base);
- }
+ ret->base = xstrndup(base, len);
+ ret->baselen = len;
return ret;
}
@@ -316,7 +305,7 @@ static void read_branches_file(struct remote *remote)
static int handle_config(const char *key, const char *value, void *cb)
{
const char *name;
- int namelen;
+ size_t namelen;
const char *subkey;
struct remote *remote;
struct branch *branch;
@@ -470,7 +459,7 @@ static void read_config(void)
const char *head_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
- current_branch = make_branch(head_ref, 0);
+ current_branch = make_branch(head_ref, strlen(head_ref));
}
}
git_config(handle_config, NULL);
@@ -1584,7 +1573,7 @@ struct branch *branch_get(const char *name)
if (!name || !*name || !strcmp(name, "HEAD"))
ret = current_branch;
else
- ret = make_branch(name, 0);
+ ret = make_branch(name, strlen(name));
set_merge(ret);
return ret;
}
diff --git a/repository.h b/repository.h
index 040057dea6..6534fbb7b3 100644
--- a/repository.h
+++ b/repository.h
@@ -67,8 +67,12 @@ struct repository {
*/
struct parsed_object_pool *parsed_objects;
- /* The store in which the refs are held. */
- struct ref_store *refs;
+ /*
+ * The store in which the refs are held. This should generally only be
+ * accessed via get_main_ref_store(), as that will lazily initialize
+ * the ref object.
+ */
+ struct ref_store *refs_private;
/*
* Contains path to often used file names.
diff --git a/reset.c b/reset.c
new file mode 100644
index 0000000000..2f4fbd07c5
--- /dev/null
+++ b/reset.c
@@ -0,0 +1,141 @@
+#include "git-compat-util.h"
+#include "cache-tree.h"
+#include "lockfile.h"
+#include "refs.h"
+#include "reset.h"
+#include "run-command.h"
+#include "tree-walk.h"
+#include "tree.h"
+#include "unpack-trees.h"
+
+int reset_head(struct repository *r, struct object_id *oid, const char *action,
+ const char *switch_to_branch, unsigned flags,
+ const char *reflog_orig_head, const char *reflog_head,
+ const char *default_reflog_action)
+{
+ unsigned detach_head = flags & RESET_HEAD_DETACH;
+ unsigned reset_hard = flags & RESET_HEAD_HARD;
+ unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
+ unsigned update_orig_head = flags & RESET_ORIG_HEAD;
+ struct object_id head_oid;
+ struct tree_desc desc[2] = { { NULL }, { NULL } };
+ struct lock_file lock = LOCK_INIT;
+ struct unpack_trees_options unpack_tree_opts;
+ struct tree *tree;
+ const char *reflog_action;
+ struct strbuf msg = STRBUF_INIT;
+ size_t prefix_len;
+ struct object_id *orig = NULL, oid_orig,
+ *old_orig = NULL, oid_old_orig;
+ int ret = 0, nr = 0;
+
+ if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
+ BUG("Not a fully qualified branch: '%s'", switch_to_branch);
+
+ if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
+ ret = -1;
+ goto leave_reset_head;
+ }
+
+ if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
+ ret = error(_("could not determine HEAD revision"));
+ goto leave_reset_head;
+ }
+
+ if (!oid)
+ oid = &head_oid;
+
+ if (refs_only)
+ goto reset_head_refs;
+
+ memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
+ setup_unpack_trees_porcelain(&unpack_tree_opts, action);
+ unpack_tree_opts.head_idx = 1;
+ unpack_tree_opts.src_index = r->index;
+ unpack_tree_opts.dst_index = r->index;
+ unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
+ unpack_tree_opts.update = 1;
+ unpack_tree_opts.merge = 1;
+ init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
+ if (!detach_head)
+ unpack_tree_opts.reset = 1;
+
+ if (repo_read_index_unmerged(r) < 0) {
+ ret = error(_("could not read index"));
+ goto leave_reset_head;
+ }
+
+ if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) {
+ ret = error(_("failed to find tree of %s"),
+ oid_to_hex(&head_oid));
+ goto leave_reset_head;
+ }
+
+ if (!fill_tree_descriptor(r, &desc[nr++], oid)) {
+ ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
+ goto leave_reset_head;
+ }
+
+ if (unpack_trees(nr, desc, &unpack_tree_opts)) {
+ ret = -1;
+ goto leave_reset_head;
+ }
+
+ tree = parse_tree_indirect(oid);
+ prime_cache_tree(r, r->index, tree);
+
+ if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) {
+ ret = error(_("could not write index"));
+ goto leave_reset_head;
+ }
+
+reset_head_refs:
+ reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+ strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action);
+ prefix_len = msg.len;
+
+ if (update_orig_head) {
+ if (!get_oid("ORIG_HEAD", &oid_old_orig))
+ old_orig = &oid_old_orig;
+ if (!get_oid("HEAD", &oid_orig)) {
+ orig = &oid_orig;
+ if (!reflog_orig_head) {
+ strbuf_addstr(&msg, "updating ORIG_HEAD");
+ reflog_orig_head = msg.buf;
+ }
+ update_ref(reflog_orig_head, "ORIG_HEAD", orig,
+ old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
+ } else if (old_orig)
+ delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+ }
+
+ if (!reflog_head) {
+ strbuf_setlen(&msg, prefix_len);
+ strbuf_addstr(&msg, "updating HEAD");
+ reflog_head = msg.buf;
+ }
+ if (!switch_to_branch)
+ ret = update_ref(reflog_head, "HEAD", oid, orig,
+ detach_head ? REF_NO_DEREF : 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ else {
+ ret = update_ref(reflog_head, switch_to_branch, oid,
+ NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ if (!ret)
+ ret = create_symref("HEAD", switch_to_branch,
+ reflog_head);
+ }
+ if (run_hook)
+ run_hook_le(NULL, "post-checkout",
+ oid_to_hex(orig ? orig : &null_oid),
+ oid_to_hex(oid), "1", NULL);
+
+leave_reset_head:
+ strbuf_release(&msg);
+ rollback_lock_file(&lock);
+ while (nr)
+ free((void *)desc[--nr].buffer);
+ return ret;
+
+}
diff --git a/reset.h b/reset.h
new file mode 100644
index 0000000000..12f83c78e2
--- /dev/null
+++ b/reset.h
@@ -0,0 +1,20 @@
+#ifndef RESET_H
+#define RESET_H
+
+#include "hash.h"
+#include "repository.h"
+
+#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
+
+#define RESET_HEAD_DETACH (1<<0)
+#define RESET_HEAD_HARD (1<<1)
+#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
+#define RESET_HEAD_REFS_ONLY (1<<3)
+#define RESET_ORIG_HEAD (1<<4)
+
+int reset_head(struct repository *r, struct object_id *oid, const char *action,
+ const char *switch_to_branch, unsigned flags,
+ const char *reflog_orig_head, const char *reflog_head,
+ const char *default_reflog_action);
+
+#endif
diff --git a/revision.c b/revision.c
index 8136929e23..60cca8c0b9 100644
--- a/revision.c
+++ b/revision.c
@@ -29,6 +29,8 @@
#include "prio-queue.h"
#include "hashmap.h"
#include "utf8.h"
+#include "bloom.h"
+#include "json-writer.h"
volatile show_early_output_fn_t show_early_output;
@@ -624,11 +626,133 @@ static void file_change(struct diff_options *options,
options->flags.has_changes = 1;
}
+static int bloom_filter_atexit_registered;
+static unsigned int count_bloom_filter_maybe;
+static unsigned int count_bloom_filter_definitely_not;
+static unsigned int count_bloom_filter_false_positive;
+static unsigned int count_bloom_filter_not_present;
+static unsigned int count_bloom_filter_length_zero;
+
+static void trace2_bloom_filter_statistics_atexit(void)
+{
+ struct json_writer jw = JSON_WRITER_INIT;
+
+ jw_object_begin(&jw, 0);
+ jw_object_intmax(&jw, "filter_not_present", count_bloom_filter_not_present);
+ jw_object_intmax(&jw, "zero_length_filter", count_bloom_filter_length_zero);
+ jw_object_intmax(&jw, "maybe", count_bloom_filter_maybe);
+ jw_object_intmax(&jw, "definitely_not", count_bloom_filter_definitely_not);
+ jw_object_intmax(&jw, "false_positive", count_bloom_filter_false_positive);
+ jw_end(&jw);
+
+ trace2_data_json("bloom", the_repository, "statistics", &jw);
+
+ jw_release(&jw);
+}
+
+static int forbid_bloom_filters(struct pathspec *spec)
+{
+ if (spec->has_wildcard)
+ return 1;
+ if (spec->nr > 1)
+ return 1;
+ if (spec->magic & ~PATHSPEC_LITERAL)
+ return 1;
+ if (spec->nr && (spec->items[0].magic & ~PATHSPEC_LITERAL))
+ return 1;
+
+ return 0;
+}
+
+static void prepare_to_use_bloom_filter(struct rev_info *revs)
+{
+ struct pathspec_item *pi;
+ char *path_alloc = NULL;
+ const char *path;
+ int last_index;
+ int len;
+
+ if (!revs->commits)
+ return;
+
+ if (forbid_bloom_filters(&revs->prune_data))
+ return;
+
+ repo_parse_commit(revs->repo, revs->commits->item);
+
+ if (!revs->repo->objects->commit_graph)
+ return;
+
+ revs->bloom_filter_settings = revs->repo->objects->commit_graph->bloom_filter_settings;
+ if (!revs->bloom_filter_settings)
+ return;
+
+ pi = &revs->pruning.pathspec.items[0];
+ last_index = pi->len - 1;
+
+ /* remove single trailing slash from path, if needed */
+ if (pi->match[last_index] == '/') {
+ path_alloc = xstrdup(pi->match);
+ path_alloc[last_index] = '\0';
+ path = path_alloc;
+ } else
+ path = pi->match;
+
+ len = strlen(path);
+
+ revs->bloom_key = xmalloc(sizeof(struct bloom_key));
+ fill_bloom_key(path, len, revs->bloom_key, revs->bloom_filter_settings);
+
+ if (trace2_is_enabled() && !bloom_filter_atexit_registered) {
+ atexit(trace2_bloom_filter_statistics_atexit);
+ bloom_filter_atexit_registered = 1;
+ }
+
+ free(path_alloc);
+}
+
+static int check_maybe_different_in_bloom_filter(struct rev_info *revs,
+ struct commit *commit)
+{
+ struct bloom_filter *filter;
+ int result;
+
+ if (!revs->repo->objects->commit_graph)
+ return -1;
+
+ if (commit->generation == GENERATION_NUMBER_INFINITY)
+ return -1;
+
+ filter = get_bloom_filter(revs->repo, commit, 0);
+
+ if (!filter) {
+ count_bloom_filter_not_present++;
+ return -1;
+ }
+
+ if (!filter->len) {
+ count_bloom_filter_length_zero++;
+ return -1;
+ }
+
+ result = bloom_filter_contains(filter,
+ revs->bloom_key,
+ revs->bloom_filter_settings);
+
+ if (result)
+ count_bloom_filter_maybe++;
+ else
+ count_bloom_filter_definitely_not++;
+
+ return result;
+}
+
static int rev_compare_tree(struct rev_info *revs,
- struct commit *parent, struct commit *commit)
+ struct commit *parent, struct commit *commit, int nth_parent)
{
struct tree *t1 = get_commit_tree(parent);
struct tree *t2 = get_commit_tree(commit);
+ int bloom_ret = 1;
if (!t1)
return REV_TREE_NEW;
@@ -653,11 +777,23 @@ static int rev_compare_tree(struct rev_info *revs,
return REV_TREE_SAME;
}
+ if (revs->bloom_key && !nth_parent) {
+ bloom_ret = check_maybe_different_in_bloom_filter(revs, commit);
+
+ if (bloom_ret == 0)
+ return REV_TREE_SAME;
+ }
+
tree_difference = REV_TREE_SAME;
revs->pruning.flags.has_changes = 0;
if (diff_tree_oid(&t1->object.oid, &t2->object.oid, "",
&revs->pruning) < 0)
return REV_TREE_DIFFERENT;
+
+ if (!nth_parent)
+ if (bloom_ret == 1 && tree_difference == REV_TREE_SAME)
+ count_bloom_filter_false_positive++;
+
return tree_difference;
}
@@ -855,7 +991,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
die("cannot simplify commit %s (because of %s)",
oid_to_hex(&commit->object.oid),
oid_to_hex(&p->object.oid));
- switch (rev_compare_tree(revs, p, commit)) {
+ switch (rev_compare_tree(revs, p, commit, nth_parent)) {
case REV_TREE_SAME:
if (!revs->simplify_history || !relevant_commit(p)) {
/* Even if a merge with an uninteresting
@@ -870,7 +1006,19 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
}
parent->next = NULL;
commit->parents = parent;
- commit->object.flags |= TREESAME;
+
+ /*
+ * A merge commit is a "diversion" if it is not
+ * TREESAME to its first parent but is TREESAME
+ * to a later parent. In the simplified history,
+ * we "divert" the history walk to the later
+ * parent. These commits are shown when "show_pulls"
+ * is enabled, so do not mark the object as
+ * TREESAME here.
+ */
+ if (!revs->show_pulls || !nth_parent)
+ commit->object.flags |= TREESAME;
+
return;
case REV_TREE_NEW:
@@ -897,6 +1045,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
relevant_change = 1;
else
irrelevant_change = 1;
+
+ if (!nth_parent)
+ commit->object.flags |= PULL_MERGE;
+
continue;
}
die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid));
@@ -2241,6 +2393,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->graph = graph_init(revs);
+ } else if (!strcmp(arg, "--encode-email-headers")) {
+ revs->encode_email_headers = 1;
+ } else if (!strcmp(arg, "--no-encode-email-headers")) {
+ revs->encode_email_headers = 0;
} else if (!strcmp(arg, "--root")) {
revs->show_root_diff = 1;
} else if (!strcmp(arg, "--no-commit-id")) {
@@ -2265,6 +2421,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--full-diff")) {
revs->diff = 1;
revs->full_diff = 1;
+ } else if (!strcmp(arg, "--show-pulls")) {
+ revs->show_pulls = 1;
} else if (!strcmp(arg, "--full-history")) {
revs->simplify_history = 0;
} else if (!strcmp(arg, "--relative-date")) {
@@ -3019,7 +3177,8 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c
if (!cnt ||
(commit->object.flags & UNINTERESTING) ||
!(commit->object.flags & TREESAME) ||
- (parent = one_relevant_parent(revs, commit->parents)) == NULL)
+ (parent = one_relevant_parent(revs, commit->parents)) == NULL ||
+ (revs->show_pulls && (commit->object.flags & PULL_MERGE)))
st->simplified = commit;
else {
pst = locate_simplify_state(revs, parent);
@@ -3362,6 +3521,8 @@ int prepare_revision_walk(struct rev_info *revs)
FOR_EACH_OBJECT_PROMISOR_ONLY);
}
+ if (revs->pruning.pathspec.nr == 1 && !revs->reflog_info)
+ prepare_to_use_bloom_filter(revs);
if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
commit_list_sort_by_date(&revs->commits);
if (revs->no_walk)
@@ -3379,6 +3540,7 @@ int prepare_revision_walk(struct rev_info *revs)
simplify_merges(revs);
if (revs->children.name)
set_children(revs);
+
return 0;
}
@@ -3602,6 +3764,10 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi
/* drop merges unless we want parenthood */
if (!want_ancestry(revs))
return commit_ignore;
+
+ if (revs->show_pulls && (commit->object.flags & PULL_MERGE))
+ return commit_show;
+
/*
* If we want ancestry, then need to keep any merges
* between relevant commits to tie together topology.
diff --git a/revision.h b/revision.h
index 475f048fb6..93491b79d4 100644
--- a/revision.h
+++ b/revision.h
@@ -34,6 +34,9 @@
#define SYMMETRIC_LEFT (1u<<8)
#define PATCHSAME (1u<<9)
#define BOTTOM (1u<<10)
+
+/* WARNING: This is also used as REACHABLE in commit-graph.c. */
+#define PULL_MERGE (1u<<15)
/*
* Indicates object was reached by traversal. i.e. not given by user on
* command-line or stdin.
@@ -43,7 +46,7 @@
*/
#define NOT_USER_GIVEN (1u<<25)
#define TRACK_LINEAR (1u<<26)
-#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR)
+#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE)
#define TOPO_WALK_EXPLORED (1u<<27)
#define TOPO_WALK_INDEGREE (1u<<28)
@@ -56,6 +59,8 @@ struct repository;
struct rev_info;
struct string_list;
struct saved_parents;
+struct bloom_key;
+struct bloom_filter_settings;
define_shared_commit_slab(revision_sources, char *);
struct rev_cmdline_info {
@@ -129,6 +134,7 @@ struct rev_info {
no_walk:2,
remove_empty_trees:1,
simplify_history:1,
+ show_pulls:1,
topo_order:1,
simplify_merges:1,
simplify_by_decoration:1,
@@ -203,7 +209,8 @@ struct rev_info {
use_terminator:1,
missing_newline:1,
date_mode_explicit:1,
- preserve_subject:1;
+ preserve_subject:1,
+ encode_email_headers:1;
unsigned int disable_stdin:1;
/* --show-linear-break */
unsigned int track_linear:1,
@@ -291,6 +298,15 @@ struct rev_info {
struct revision_sources *sources;
struct topo_walk_info *topo_walk_info;
+
+ /* Commit graph bloom filter fields */
+ /* The bloom filter key for the pathspec */
+ struct bloom_key *bloom_key;
+ /*
+ * The bloom filter settings used to generate the key.
+ * This is loaded from the commit-graph being used.
+ */
+ struct bloom_filter_settings *bloom_filter_settings;
};
int ref_excluded(struct string_list *, const char *path);
diff --git a/run-command.c b/run-command.c
index f5e1149f9b..0f41af3b55 100644
--- a/run-command.c
+++ b/run-command.c
@@ -421,12 +421,12 @@ static int prepare_cmd(struct argv_array *out, const struct child_process *cmd)
}
/*
- * If there are no '/' characters in the command then perform a path
- * lookup and use the resolved path as the command to exec. If there
- * are '/' characters, we have exec attempt to invoke the command
- * directly.
+ * If there are no dir separator characters in the command then perform
+ * a path lookup and use the resolved path as the command to exec. If
+ * there are dir separator characters, we have exec attempt to invoke
+ * the command directly.
*/
- if (!strchr(out->argv[1], '/')) {
+ if (!has_dir_sep(out->argv[1])) {
char *program = locate_in_PATH(out->argv[1]);
if (program) {
free((char *)out->argv[1]);
diff --git a/send-pack.c b/send-pack.c
index 0407841ae8..d1b7edc995 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -12,7 +12,7 @@
#include "quote.h"
#include "transport.h"
#include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "gpg-interface.h"
#include "cache.h"
@@ -190,10 +190,8 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
if (reader->line[0] == 'o' && reader->line[1] == 'k')
hint->status = REF_STATUS_OK;
- else {
+ else
hint->status = REF_STATUS_REMOTE_REJECT;
- ret = -1;
- }
hint->remote_status = xstrdup_or_null(msg);
/* start our next search from the next ref */
hint = hint->next;
@@ -322,29 +320,6 @@ free_return:
return update_seen;
}
-
-static int atomic_push_failure(struct send_pack_args *args,
- struct ref *remote_refs,
- struct ref *failing_ref)
-{
- struct ref *ref;
- /* Mark other refs as failed */
- for (ref = remote_refs; ref; ref = ref->next) {
- if (!ref->peer_ref && !args->send_mirror)
- continue;
-
- switch (ref->status) {
- case REF_STATUS_EXPECTING_REPORT:
- ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
- continue;
- default:
- break; /* do nothing */
- }
- }
- return error("atomic push failed for ref %s. status: %d\n",
- failing_ref->name, failing_ref->status);
-}
-
#define NONCE_LEN_LIMIT 256
static void reject_invalid_nonce(const char *nonce, int len)
@@ -489,7 +464,10 @@ int send_pack(struct send_pack_args *args,
if (use_atomic) {
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
- return atomic_push_failure(args, remote_refs, ref);
+ reject_atomic_push(remote_refs, args->send_mirror);
+ error("atomic push failed for ref %s. status: %d\n",
+ ref->name, ref->status);
+ return args->porcelain ? 0 : -1;
}
/* else fallthrough */
default:
diff --git a/sequencer.c b/sequencer.c
index 6fd2674632..9d1b3e7d4f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -32,6 +32,7 @@
#include "alias.h"
#include "commit-reach.h"
#include "rebase-interactive.h"
+#include "reset.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -419,25 +420,15 @@ static int write_message(const void *buf, size_t len, const char *filename,
return 0;
}
-/*
- * Reads a file that was presumably written by a shell script, i.e. with an
- * end-of-line marker that needs to be stripped.
- *
- * Note that only the last end-of-line marker is stripped, consistent with the
- * behavior of "$(cat path)" in a shell script.
- *
- * Returns 1 if the file was read, 0 if it could not be read or does not exist.
- */
-static int read_oneliner(struct strbuf *buf,
- const char *path, int skip_if_empty)
+int read_oneliner(struct strbuf *buf,
+ const char *path, unsigned flags)
{
int orig_len = buf->len;
- if (!file_exists(path))
- return 0;
-
if (strbuf_read_file(buf, path, 0) < 0) {
- warning_errno(_("could not read '%s'"), path);
+ if ((flags & READ_ONELINER_WARN_MISSING) ||
+ (errno != ENOENT && errno != ENOTDIR))
+ warning_errno(_("could not read '%s'"), path);
return 0;
}
@@ -447,7 +438,7 @@ static int read_oneliner(struct strbuf *buf,
buf->buf[buf->len] = '\0';
}
- if (skip_if_empty && buf->len == orig_len)
+ if ((flags & READ_ONELINER_SKIP_IF_EMPTY) && buf->len == orig_len)
return 0;
return 1;
@@ -946,6 +937,8 @@ static int run_git_commit(struct repository *r,
argv_array_push(&cmd.args, "--amend");
if (opts->gpg_sign)
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+ else
+ argv_array_push(&cmd.args, "--no-gpg-sign");
if (defmsg)
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
else if (!(flags & EDIT_MSG))
@@ -1578,7 +1571,7 @@ static const char *command_to_string(const enum todo_command command)
static char command_to_char(const enum todo_command command)
{
- if (command < TODO_COMMENT && todo_command_info[command].c)
+ if (command < TODO_COMMENT)
return todo_command_info[command].c;
return comment_line_char;
}
@@ -2502,8 +2495,10 @@ static int read_populate_opts(struct replay_opts *opts)
{
if (is_rebase_i(opts)) {
struct strbuf buf = STRBUF_INIT;
+ int ret = 0;
- if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
+ if (read_oneliner(&buf, rebase_path_gpg_sign_opt(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
if (!starts_with(buf.buf, "-S"))
strbuf_reset(&buf);
else {
@@ -2513,7 +2508,8 @@ static int read_populate_opts(struct replay_opts *opts)
strbuf_reset(&buf);
}
- if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
+ if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
if (!strcmp(buf.buf, "--rerere-autoupdate"))
opts->allow_rerere_auto = RERERE_AUTOUPDATE;
else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
@@ -2542,10 +2538,11 @@ static int read_populate_opts(struct replay_opts *opts)
opts->keep_redundant_commits = 1;
read_strategy_opts(opts, &buf);
- strbuf_release(&buf);
+ strbuf_reset(&buf);
if (read_oneliner(&opts->current_fixups,
- rebase_path_current_fixups(), 1)) {
+ rebase_path_current_fixups(),
+ READ_ONELINER_SKIP_IF_EMPTY)) {
const char *p = opts->current_fixups.buf;
opts->current_fixup_count = 1;
while ((p = strchr(p, '\n'))) {
@@ -2555,12 +2552,16 @@ static int read_populate_opts(struct replay_opts *opts)
}
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
- if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
- return error(_("unusable squash-onto"));
+ if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) {
+ ret = error(_("unusable squash-onto"));
+ goto done_rebase_i;
+ }
opts->have_squash_onto = 1;
}
- return 0;
+done_rebase_i:
+ strbuf_release(&buf);
+ return ret;
}
if (!file_exists(git_path_opts_file()))
@@ -3128,7 +3129,7 @@ static int do_exec(struct repository *r, const char *command_line)
const char *child_argv[] = { NULL, NULL };
int dirty, status;
- fprintf(stderr, "Executing: %s\n", command_line);
+ fprintf(stderr, _("Executing: %s\n"), command_line);
child_argv[0] = command_line;
argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
@@ -3675,25 +3676,71 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
-static int apply_autostash(struct replay_opts *opts)
+void create_autostash(struct repository *r, const char *path,
+ const char *default_reflog_action)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct lock_file lock_file = LOCK_INIT;
+ int fd;
+
+ fd = repo_hold_locked_index(r, &lock_file, 0);
+ refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
+ if (0 <= fd)
+ repo_update_index_if_able(r, &lock_file);
+ rollback_lock_file(&lock_file);
+
+ if (has_unstaged_changes(r, 1) ||
+ has_uncommitted_changes(r, 1)) {
+ struct child_process stash = CHILD_PROCESS_INIT;
+ struct object_id oid;
+
+ argv_array_pushl(&stash.args,
+ "stash", "create", "autostash", NULL);
+ stash.git_cmd = 1;
+ stash.no_stdin = 1;
+ strbuf_reset(&buf);
+ if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
+ die(_("Cannot autostash"));
+ strbuf_trim_trailing_newline(&buf);
+ if (get_oid(buf.buf, &oid))
+ die(_("Unexpected stash response: '%s'"),
+ buf.buf);
+ strbuf_reset(&buf);
+ strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
+
+ if (safe_create_leading_directories_const(path))
+ die(_("Could not create directory for '%s'"),
+ path);
+ write_file(path, "%s", oid_to_hex(&oid));
+ printf(_("Created autostash: %s\n"), buf.buf);
+ if (reset_head(r, NULL, "reset --hard",
+ NULL, RESET_HEAD_HARD, NULL, NULL,
+ default_reflog_action) < 0)
+ die(_("could not reset --hard"));
+
+ if (discard_index(r->index) < 0 ||
+ repo_read_index(r) < 0)
+ die(_("could not read index"));
+ }
+ strbuf_release(&buf);
+}
+
+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
{
- struct strbuf stash_sha1 = STRBUF_INIT;
struct child_process child = CHILD_PROCESS_INIT;
int ret = 0;
- if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
- strbuf_release(&stash_sha1);
- return 0;
+ if (attempt_apply) {
+ child.git_cmd = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ argv_array_push(&child.args, "stash");
+ argv_array_push(&child.args, "apply");
+ argv_array_push(&child.args, stash_oid);
+ ret = run_command(&child);
}
- strbuf_trim(&stash_sha1);
- child.git_cmd = 1;
- child.no_stdout = 1;
- child.no_stderr = 1;
- argv_array_push(&child.args, "stash");
- argv_array_push(&child.args, "apply");
- argv_array_push(&child.args, stash_sha1.buf);
- if (!run_command(&child))
+ if (attempt_apply && !ret)
fprintf(stderr, _("Applied autostash.\n"));
else {
struct child_process store = CHILD_PROCESS_INIT;
@@ -3704,30 +3751,67 @@ static int apply_autostash(struct replay_opts *opts)
argv_array_push(&store.args, "-m");
argv_array_push(&store.args, "autostash");
argv_array_push(&store.args, "-q");
- argv_array_push(&store.args, stash_sha1.buf);
+ argv_array_push(&store.args, stash_oid);
if (run_command(&store))
- ret = error(_("cannot store %s"), stash_sha1.buf);
+ ret = error(_("cannot store %s"), stash_oid);
else
fprintf(stderr,
- _("Applying autostash resulted in conflicts.\n"
+ _("%s\n"
"Your changes are safe in the stash.\n"
"You can run \"git stash pop\" or"
- " \"git stash drop\" at any time.\n"));
+ " \"git stash drop\" at any time.\n"),
+ attempt_apply ?
+ _("Applying autostash resulted in conflicts.") :
+ _("Autostash exists; creating a new stash entry."));
}
- strbuf_release(&stash_sha1);
return ret;
}
+static int apply_save_autostash(const char *path, int attempt_apply)
+{
+ struct strbuf stash_oid = STRBUF_INIT;
+ int ret = 0;
+
+ if (!read_oneliner(&stash_oid, path,
+ READ_ONELINER_SKIP_IF_EMPTY)) {
+ strbuf_release(&stash_oid);
+ return 0;
+ }
+ strbuf_trim(&stash_oid);
+
+ ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
+
+ unlink(path);
+ strbuf_release(&stash_oid);
+ return ret;
+}
+
+int save_autostash(const char *path)
+{
+ return apply_save_autostash(path, 0);
+}
+
+int apply_autostash(const char *path)
+{
+ return apply_save_autostash(path, 1);
+}
+
+int apply_autostash_oid(const char *stash_oid)
+{
+ return apply_save_autostash_oid(stash_oid, 1);
+}
+
static const char *reflog_message(struct replay_opts *opts,
const char *sub_action, const char *fmt, ...)
{
va_list ap;
static struct strbuf buf = STRBUF_INIT;
+ char *reflog_action = getenv(GIT_REFLOG_ACTION);
va_start(ap, fmt);
strbuf_reset(&buf);
- strbuf_addstr(&buf, action_name(opts));
+ strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts));
if (sub_action)
strbuf_addf(&buf, " (%s)", sub_action);
if (fmt) {
@@ -3773,7 +3857,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
return error(_("%s: not a valid OID"), orig_head);
if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("could not detach HEAD"));
}
@@ -3815,8 +3899,11 @@ static int pick_commits(struct repository *r,
struct replay_opts *opts)
{
int res = 0, reschedule = 0;
+ char *prev_reflog_action;
+ /* Note that 0 for 3rd parameter of setenv means set only if not set */
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+ prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
opts->record_origin || opts->edit));
@@ -3841,7 +3928,7 @@ static int pick_commits(struct repository *r,
fclose(f);
}
if (!opts->quiet)
- fprintf(stderr, "Rebasing (%d/%d)%s",
+ fprintf(stderr, _("Rebasing (%d/%d)%s"),
todo_list->done_nr,
todo_list->total_nr,
opts->verbose ? "\n" : "\r");
@@ -3861,12 +3948,14 @@ static int pick_commits(struct repository *r,
}
if (item->command <= TODO_SQUASH) {
if (is_rebase_i(opts))
- setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+ setenv(GIT_REFLOG_ACTION, reflog_message(opts,
command_to_string(item->command), NULL),
1);
res = do_pick_commit(r, item->command, item->commit,
opts, is_final_fixup(todo_list),
&check_todo);
+ if (is_rebase_i(opts))
+ setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
if (is_rebase_i(opts) && res < 0) {
/* Reschedule */
advise(_(rescheduled_advice),
@@ -4087,13 +4176,13 @@ cleanup_head_ref:
run_command(&hook);
}
}
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
if (!opts->quiet) {
if (!opts->verbose)
term_clear_line();
fprintf(stderr,
- "Successfully rebased and updated %s.\n",
+ _("Successfully rebased and updated %s.\n"),
head_ref.buf);
}
@@ -4305,7 +4394,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
- if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
+ if (read_oneliner(&buf, rebase_path_stopped_sha(),
+ READ_ONELINER_SKIP_IF_EMPTY) &&
!get_oid_committish(buf.buf, &oid))
record_in_rewritten(&oid, peek_command(&todo_list, 0));
strbuf_release(&buf);
@@ -4607,6 +4697,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
struct rev_info *revs, struct strbuf *out,
unsigned flags)
{
+ int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
@@ -4661,6 +4752,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
is_empty = is_original_commit_empty(commit);
if (!is_empty && (commit->object.flags & PATCHSAME))
continue;
+ if (is_empty && !keep_empty)
+ continue;
strbuf_reset(&oneline);
pretty_print_commit(pp, commit, &oneline);
@@ -4672,6 +4765,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
strbuf_addf(&buf, "%s %s %s", cmd_pick,
oid_to_hex(&commit->object.oid),
oneline.buf);
+ if (is_empty)
+ strbuf_addf(&buf, " %c empty",
+ comment_line_char);
FLEX_ALLOC_STR(entry, string, buf.buf);
oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -4835,14 +4931,16 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
struct pretty_print_context pp = {0};
struct rev_info revs;
struct commit *commit;
+ int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
+ int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
if (!rebase_merges)
revs.max_parents = 1;
- revs.cherry_mark = 1;
+ revs.cherry_mark = !reapply_cherry_picks;
revs.limited = 1;
revs.reverse = 1;
revs.right_only = 1;
@@ -4874,9 +4972,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
if (!is_empty && (commit->object.flags & PATCHSAME))
continue;
+ if (is_empty && !keep_empty)
+ continue;
strbuf_addf(out, "%s %s ", insn,
oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, out);
+ if (is_empty)
+ strbuf_addf(out, " %c empty", comment_line_char);
strbuf_addch(out, '\n');
}
return 0;
@@ -4963,6 +5065,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
max = num;
for (item = todo_list->items, i = 0; i < max; i++, item++) {
+ char cmd;
+
/* if the item is not a command write it and continue */
if (item->command >= TODO_COMMENT) {
strbuf_addf(buf, "%.*s\n", item->arg_len,
@@ -4971,8 +5075,9 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
}
/* add command to the buffer */
- if (flags & TODO_LIST_ABBREVIATE_CMDS)
- strbuf_addch(buf, command_to_char(item->command));
+ cmd = command_to_char(item->command);
+ if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd)
+ strbuf_addch(buf, cmd);
else
strbuf_addstr(buf, command_to_string(item->command));
@@ -5095,7 +5200,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
todo_list_add_exec_commands(todo_list, commands);
if (count_commands(todo_list) == 0) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("nothing to do"));
@@ -5106,12 +5211,12 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
if (res == -1)
return -1;
else if (res == -2) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return -1;
} else if (res == -3) {
- apply_autostash(opts);
+ apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
todo_list_release(&new_todo);
diff --git a/sequencer.h b/sequencer.h
index 0bee85093e..d31c41f018 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -134,7 +134,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);
int sequencer_skip(struct repository *repo, struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
-/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */
+#define TODO_LIST_KEEP_EMPTY (1U << 0)
#define TODO_LIST_SHORTEN_IDS (1U << 1)
#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)
#define TODO_LIST_REBASE_MERGES (1U << 3)
@@ -150,7 +150,7 @@ int sequencer_remove_state(struct replay_opts *opts);
* `--onto`, we do not want to re-generate the root commits.
*/
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
-
+#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char **argv, unsigned flags);
@@ -191,6 +191,12 @@ void commit_post_rewrite(struct repository *r,
const struct commit *current_head,
const struct object_id *new_head);
+void create_autostash(struct repository *r, const char *path,
+ const char *default_reflog_action);
+int save_autostash(const char *path);
+int apply_autostash(const char *path);
+int apply_autostash_oid(const char *stash_oid);
+
#define SUMMARY_INITIAL_COMMIT (1 << 0)
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
void print_commit_summary(struct repository *repo,
@@ -198,6 +204,20 @@ void print_commit_summary(struct repository *repo,
const struct object_id *oid,
unsigned int flags);
+#define READ_ONELINER_SKIP_IF_EMPTY (1 << 0)
+#define READ_ONELINER_WARN_MISSING (1 << 1)
+
+/*
+ * Reads a file that was presumably written by a shell script, i.e. with an
+ * end-of-line marker that needs to be stripped.
+ *
+ * Note that only the last end-of-line marker is stripped, consistent with the
+ * behavior of "$(cat path)" in a shell script.
+ *
+ * Returns 1 if the file was read, 0 if it could not be read or does not exist.
+ */
+int read_oneliner(struct strbuf *buf,
+ const char *path, unsigned flags);
int read_author_script(const char *path, char **name, char **email, char **date,
int allow_missing);
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);
diff --git a/sha1-file.c b/sha1-file.c
index 6926851724..ccd34dd9e8 100644
--- a/sha1-file.c
+++ b/sha1-file.c
@@ -881,9 +881,7 @@ void prepare_alt_odb(struct repository *r)
/* Returns 1 if we have successfully freshened the file, 0 otherwise. */
static int freshen_file(const char *fn)
{
- struct utimbuf t;
- t.actime = t.modtime = time(NULL);
- return !utime(fn, &t);
+ return !utime(fn, NULL);
}
/*
diff --git a/sha1-name.c b/sha1-name.c
index 5bb006e5a9..0b8cb5247a 100644
--- a/sha1-name.c
+++ b/sha1-name.c
@@ -8,7 +8,7 @@
#include "refs.h"
#include "remote.h"
#include "dir.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "packfile.h"
#include "object-store.h"
#include "repository.h"
@@ -1815,8 +1815,8 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
cb.repo = repo;
cb.list = &list;
- refs_for_each_ref(repo->refs, handle_one_ref, &cb);
- refs_head_ref(repo->refs, handle_one_ref, &cb);
+ refs_for_each_ref(get_main_ref_store(repo), handle_one_ref, &cb);
+ refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb);
commit_list_sort_by_date(&list);
return get_oid_oneline(repo, name + 2, oid, list);
}
diff --git a/shallow.c b/shallow.c
index 7fd04afed1..321a27670f 100644
--- a/shallow.c
+++ b/shallow.c
@@ -8,7 +8,7 @@
#include "pkt-line.h"
#include "remote.h"
#include "refs.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "diff.h"
#include "revision.h"
#include "commit-slab.h"
@@ -40,13 +40,6 @@ int register_shallow(struct repository *r, const struct object_id *oid)
int is_repository_shallow(struct repository *r)
{
- /*
- * NEEDSWORK: This function updates
- * r->parsed_objects->{is_shallow,shallow_stat} as a side effect but
- * there is no corresponding function to clear them when the shallow
- * file is updated.
- */
-
FILE *fp;
char buf[1024];
const char *path = r->parsed_objects->alternate_shallow_file;
@@ -79,6 +72,25 @@ int is_repository_shallow(struct repository *r)
return r->parsed_objects->is_shallow;
}
+static void reset_repository_shallow(struct repository *r)
+{
+ r->parsed_objects->is_shallow = -1;
+ stat_validity_clear(r->parsed_objects->shallow_stat);
+}
+
+int commit_shallow_file(struct repository *r, struct lock_file *lk)
+{
+ int res = commit_lock_file(lk);
+ reset_repository_shallow(r);
+ return res;
+}
+
+void rollback_shallow_file(struct repository *r, struct lock_file *lk)
+{
+ rollback_lock_file(lk);
+ reset_repository_shallow(r);
+}
+
/*
* TODO: use "int" elemtype instead of "int *" when/if commit-slab
* supports a "valid" flag.
@@ -410,10 +422,10 @@ void prune_shallow(unsigned options)
if (write_in_full(fd, sb.buf, sb.len) < 0)
die_errno("failed to write to %s",
get_lock_file_path(&shallow_lock));
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
} else {
unlink(git_path_shallow(the_repository));
- rollback_lock_file(&shallow_lock);
+ rollback_shallow_file(the_repository, &shallow_lock);
}
strbuf_release(&sb);
}
diff --git a/shell.c b/shell.c
index 54cca7439d..cef7ffdc9e 100644
--- a/shell.c
+++ b/shell.c
@@ -4,6 +4,7 @@
#include "strbuf.h"
#include "run-command.h"
#include "alias.h"
+#include "prompt.h"
#define COMMAND_DIR "git-shell-commands"
#define HELP_COMMAND COMMAND_DIR "/help"
@@ -76,12 +77,11 @@ static void run_shell(void)
int count;
fprintf(stderr, "git> ");
- if (strbuf_getline_lf(&line, stdin) == EOF) {
+ if (git_read_line_interactively(&line) == EOF) {
fprintf(stderr, "\n");
strbuf_release(&line);
break;
}
- strbuf_trim(&line);
rawargs = strbuf_detach(&line, NULL);
split_args = xstrdup(rawargs);
count = split_cmdline(split_args, &argv);
diff --git a/strbuf.c b/strbuf.c
index bb0065ccaf..2f1a7d3209 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -479,15 +479,17 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
}
}
-#define URL_UNSAFE_CHARS " <>\"%{}|\\^`:/?#[]@!$&'()*+,;="
+#define URL_UNSAFE_CHARS " <>\"%{}|\\^`:?#[]@!$&'()*+,;="
-void strbuf_add_percentencode(struct strbuf *dst, const char *src)
+void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags)
{
size_t i, len = strlen(src);
for (i = 0; i < len; i++) {
unsigned char ch = src[i];
- if (ch <= 0x1F || ch >= 0x7F || strchr(URL_UNSAFE_CHARS, ch))
+ if (ch <= 0x1F || ch >= 0x7F ||
+ (ch == '/' && (flags & STRBUF_ENCODE_SLASH)) ||
+ strchr(URL_UNSAFE_CHARS, ch))
strbuf_addf(dst, "%%%02X", (unsigned char)ch);
else
strbuf_addch(dst, ch);
@@ -554,6 +556,10 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
}
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd)
+{
+ return sb->len ? write(fd, sb->buf, sb->len) : 0;
+}
#define STRBUF_MAXLINK (2*PATH_MAX)
@@ -690,6 +696,16 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
}
#endif
+int strbuf_appendwholeline(struct strbuf *sb, FILE *fp, int term)
+{
+ struct strbuf line = STRBUF_INIT;
+ if (strbuf_getwholeline(&line, fp, term))
+ return EOF;
+ strbuf_addbuf(sb, &line);
+ strbuf_release(&line);
+ return 0;
+}
+
static int strbuf_getdelim(struct strbuf *sb, FILE *fp, int term)
{
if (strbuf_getwholeline(sb, fp, term))
diff --git a/strbuf.h b/strbuf.h
index ce8e49c0b2..7062eb6410 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -378,11 +378,16 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb,
*/
void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
+#define STRBUF_ENCODE_SLASH 1
+
/**
* Append the contents of a string to a strbuf, percent-encoding any characters
* that are needed to be encoded for a URL.
+ *
+ * If STRBUF_ENCODE_SLASH is set in flags, percent-encode slashes. Otherwise,
+ * slashes are not percent-encoded.
*/
-void strbuf_add_percentencode(struct strbuf *dst, const char *src);
+void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags);
/**
* Append the given byte size as a human-readable string (i.e. 12.23 KiB,
@@ -468,6 +473,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
* NUL bytes.
*/
ssize_t strbuf_write(struct strbuf *sb, FILE *stream);
+ssize_t strbuf_write_fd(struct strbuf *sb, int fd);
/**
* Read a line from a FILE *, overwriting the existing contents of
@@ -503,6 +509,12 @@ int strbuf_getline(struct strbuf *sb, FILE *file);
int strbuf_getwholeline(struct strbuf *sb, FILE *file, int term);
/**
+ * Like `strbuf_getwholeline`, but appends the line instead of
+ * resetting the buffer first.
+ */
+int strbuf_appendwholeline(struct strbuf *sb, FILE *file, int term);
+
+/**
* Like `strbuf_getwholeline`, but operates on a file descriptor.
* It reads one character at a time, so it is very slow. Do not
* use it unless you need the correct position in the file
diff --git a/submodule-config.c b/submodule-config.c
index 4d1c92d582..e175dfbc38 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -225,7 +225,8 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
struct strbuf *item)
{
const char *subsection, *key;
- int subsection_len, parse;
+ size_t subsection_len;
+ int parse;
parse = parse_config_key(var, "submodule", &subsection,
&subsection_len, &key);
if (parse < 0 || !subsection)
diff --git a/submodule.c b/submodule.c
index c3aadf3fff..e2ef5698c8 100644
--- a/submodule.c
+++ b/submodule.c
@@ -12,7 +12,7 @@
#include "diffcore.h"
#include "refs.h"
#include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "argv-array.h"
#include "blob.h"
#include "thread-utils.h"
diff --git a/t/README b/t/README
index da5b24feb0..cf863837ab 100644
--- a/t/README
+++ b/t/README
@@ -69,7 +69,8 @@ You can also run each test individually from command line, like this:
You can pass --verbose (or -v), --debug (or -d), and --immediate
(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
-appropriately before running "make".
+appropriately before running "make". Short options can be bundled, i.e.
+'-d -v' is the same as '-dv'.
-v::
--verbose::
@@ -378,6 +379,11 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to
be written after every 'git commit' command, and overrides the
'core.commitGraph' setting to true.
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces
+commit-graph write to compute and write changed path Bloom filters for
+every 'git commit-graph write', as if the `--changed-paths` option was
+passed in.
+
GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all exercises the fsmonitor
code path for utilizing a file system monitor to speed up detecting
new or changed files.
@@ -546,6 +552,41 @@ Here are the "do's:"
reports "ok" or "not ok" to the end user running the tests. Under
--verbose, they are shown to help debug the tests.
+ - Be careful when you loop
+
+ You may need to verify multiple things in a loop, but the
+ following does not work correctly:
+
+ test_expect_success 'test three things' '
+ for i in one two three
+ do
+ test_something "$i"
+ done &&
+ test_something_else
+ '
+
+ Because the status of the loop itself is the exit status of the
+ test_something in the last round, the loop does not fail when
+ "test_something" for "one" or "two" fails. This is not what you
+ want.
+
+ Instead, you can break out of the loop immediately when you see a
+ failure. Because all test_expect_* snippets are executed inside
+ a function, "return 1" can be used to fail the test immediately
+ upon a failure:
+
+ test_expect_success 'test three things' '
+ for i in one two three
+ do
+ test_something "$i" || return 1
+ done &&
+ test_something_else
+ '
+
+ Note that we still &&-chain the loop to propagate failures from
+ earlier commands.
+
+
And here are the "don'ts:"
- Don't exit() within a <script> part.
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
new file mode 100644
index 0000000000..77eb27adac
--- /dev/null
+++ b/t/helper/test-bloom.c
@@ -0,0 +1,93 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "test-tool.h"
+#include "commit.h"
+
+struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+
+static void add_string_to_filter(const char *data, struct bloom_filter *filter) {
+ struct bloom_key key;
+ int i;
+
+ fill_bloom_key(data, strlen(data), &key, &settings);
+ printf("Hashes:");
+ for (i = 0; i < settings.num_hashes; i++){
+ printf("0x%08x|", key.hashes[i]);
+ }
+ printf("\n");
+ add_key_to_filter(&key, filter, &settings);
+}
+
+static void print_bloom_filter(struct bloom_filter *filter) {
+ int i;
+
+ if (!filter) {
+ printf("No filter.\n");
+ return;
+ }
+ printf("Filter_Length:%d\n", (int)filter->len);
+ printf("Filter_Data:");
+ for (i = 0; i < filter->len; i++) {
+ printf("%02x|", filter->data[i]);
+ }
+ printf("\n");
+}
+
+static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
+{
+ struct commit *c;
+ struct bloom_filter *filter;
+ setup_git_directory();
+ c = lookup_commit(the_repository, commit_oid);
+ filter = get_bloom_filter(the_repository, c, 1);
+ print_bloom_filter(filter);
+}
+
+static const char *bloom_usage = "\n"
+" test-tool bloom get_murmer3 <string>\n"
+" test-tool bloom generate_filter <string> [<string>...]\n"
+" test-tool get_filter_for_commit <commit-hex>\n";
+
+int cmd__bloom(int argc, const char **argv)
+{
+ if (argc < 2)
+ usage(bloom_usage);
+
+ if (!strcmp(argv[1], "get_murmur3")) {
+ uint32_t hashed;
+ if (argc < 3)
+ usage(bloom_usage);
+ hashed = murmur3_seeded(0, argv[2], strlen(argv[2]));
+ printf("Murmur3 Hash with seed=0:0x%08x\n", hashed);
+ }
+
+ if (!strcmp(argv[1], "generate_filter")) {
+ struct bloom_filter filter;
+ int i = 2;
+ filter.len = (settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ filter.data = xcalloc(filter.len, sizeof(unsigned char));
+
+ if (argc - 1 < i)
+ usage(bloom_usage);
+
+ while (argv[i]) {
+ add_string_to_filter(argv[i], &filter);
+ i++;
+ }
+
+ print_bloom_filter(&filter);
+ }
+
+ if (!strcmp(argv[1], "get_filter_for_commit")) {
+ struct object_id oid;
+ const char *end;
+ if (argc < 3)
+ usage(bloom_usage);
+ if (parse_oid_hex(argv[2], &oid, &end))
+ die("cannot parse oid '%s'", argv[2]);
+ init_bloom_filters();
+ get_bloom_filter_for_commit(&oid);
+ }
+
+ return 0;
+}
diff --git a/t/helper/test-sha1-array.c b/t/helper/test-oid-array.c
index ad5e69f9d3..ce9fd5f091 100644
--- a/t/helper/test-sha1-array.c
+++ b/t/helper/test-oid-array.c
@@ -1,6 +1,6 @@
#include "test-tool.h"
#include "cache.h"
-#include "sha1-array.h"
+#include "oid-array.h"
static int print_oid(const struct object_id *oid, void *data)
{
@@ -8,7 +8,7 @@ static int print_oid(const struct object_id *oid, void *data)
return 0;
}
-int cmd__sha1_array(int argc, const char **argv)
+int cmd__oid_array(int argc, const char **argv)
{
struct oid_array array = OID_ARRAY_INIT;
struct strbuf line = STRBUF_INIT;
@@ -19,11 +19,11 @@ int cmd__sha1_array(int argc, const char **argv)
if (skip_prefix(line.buf, "append ", &arg)) {
if (get_oid_hex(arg, &oid))
- die("not a hexadecimal SHA1: %s", arg);
+ die("not a hexadecimal oid: %s", arg);
oid_array_append(&array, &oid);
} else if (skip_prefix(line.buf, "lookup ", &arg)) {
if (get_oid_hex(arg, &oid))
- die("not a hexadecimal SHA1: %s", arg);
+ die("not a hexadecimal oid: %s", arg);
printf("%d\n", oid_array_lookup(&array, &oid));
} else if (!strcmp(line.buf, "clear"))
oid_array_clear(&array);
diff --git a/t/helper/test-parse-pathspec-file.c b/t/helper/test-parse-pathspec-file.c
index 02f4ccfd2a..b3e08cef4b 100644
--- a/t/helper/test-parse-pathspec-file.c
+++ b/t/helper/test-parse-pathspec-file.c
@@ -6,7 +6,7 @@
int cmd__parse_pathspec_file(int argc, const char **argv)
{
struct pathspec pathspec;
- const char *pathspec_from_file = 0;
+ const char *pathspec_from_file = NULL;
int pathspec_file_nul = 0, i;
static const char *const usage[] = {
@@ -20,9 +20,9 @@ int cmd__parse_pathspec_file(int argc, const char **argv)
OPT_END()
};
- parse_options(argc, argv, 0, options, usage, 0);
+ parse_options(argc, argv, NULL, options, usage, 0);
- parse_pathspec_file(&pathspec, 0, 0, 0, pathspec_from_file,
+ parse_pathspec_file(&pathspec, 0, 0, NULL, pathspec_from_file,
pathspec_file_nul);
for (i = 0; i < pathspec.nr; i++)
diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c
index 282d536384..12ca698e17 100644
--- a/t/helper/test-pkt-line.c
+++ b/t/helper/test-pkt-line.c
@@ -67,7 +67,7 @@ static void unpack_sideband(void)
case PACKET_READ_NORMAL:
band = reader.line[0] & 0xff;
if (band < 1 || band > 2)
- die("unexpected side band %d", band);
+ continue; /* skip non-sideband packets */
fd = band;
write_or_die(fd, reader.line + 1, reader.pktlen - 1);
diff --git a/t/helper/test-progress.c b/t/helper/test-progress.c
index 42b96cb103..5d05cbe789 100644
--- a/t/helper/test-progress.c
+++ b/t/helper/test-progress.c
@@ -13,20 +13,13 @@
*
* See 't0500-progress-display.sh' for examples.
*/
+#define GIT_TEST_PROGRESS_ONLY
#include "test-tool.h"
#include "gettext.h"
#include "parse-options.h"
#include "progress.h"
#include "strbuf.h"
-/*
- * These are defined in 'progress.c', but are not exposed in 'progress.h',
- * because they are exclusively for testing.
- */
-extern int progress_testing;
-extern uint64_t progress_test_ns;
-void progress_test_force_update(void);
-
int cmd__progress(int argc, const char **argv)
{
int total = 0;
diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c
index f8a461767c..6d0c962438 100644
--- a/t/helper/test-read-graph.c
+++ b/t/helper/test-read-graph.c
@@ -7,26 +7,15 @@
int cmd__read_graph(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
- char *graph_name;
- int open_ok;
- int fd;
- struct stat st;
struct object_directory *odb;
setup_git_directory();
odb = the_repository->objects->odb;
- graph_name = get_commit_graph_filename(odb);
-
- open_ok = open_commit_graph(graph_name, &fd, &st);
- if (!open_ok)
- die_errno(_("Could not open commit-graph '%s'"), graph_name);
-
- graph = load_commit_graph_one_fd_st(fd, &st, odb);
+ graph = read_commit_graph_one(the_repository, odb);
if (!graph)
return 1;
- FREE_AND_NULL(graph_name);
printf("header: %08x %d %d %d %d\n",
ntohl(*(uint32_t*)graph->data),
@@ -45,6 +34,10 @@ int cmd__read_graph(int argc, const char **argv)
printf(" commit_metadata");
if (graph->chunk_extra_edges)
printf(" extra_edges");
+ if (graph->chunk_bloom_indexes)
+ printf(" bloom_indexes");
+ if (graph->chunk_bloom_data)
+ printf(" bloom_data");
printf("\n");
UNLEAK(graph);
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 31eedcd241..590b2efca7 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -15,6 +15,7 @@ struct test_cmd {
static struct test_cmd cmds[] = {
{ "advise", cmd__advise_if_enabled },
+ { "bloom", cmd__bloom },
{ "chmtime", cmd__chmtime },
{ "config", cmd__config },
{ "ctype", cmd__ctype },
@@ -37,6 +38,7 @@ static struct test_cmd cmds[] = {
{ "match-trees", cmd__match_trees },
{ "mergesort", cmd__mergesort },
{ "mktemp", cmd__mktemp },
+ { "oid-array", cmd__oid_array },
{ "oidmap", cmd__oidmap },
{ "online-cpus", cmd__online_cpus },
{ "parse-options", cmd__parse_options },
@@ -57,7 +59,6 @@ static struct test_cmd cmds[] = {
{ "scrap-cache-tree", cmd__scrap_cache_tree },
{ "serve-v2", cmd__serve_v2 },
{ "sha1", cmd__sha1 },
- { "sha1-array", cmd__sha1_array },
{ "sha256", cmd__sha256 },
{ "sigchain", cmd__sigchain },
{ "strcmp-offset", cmd__strcmp_offset },
@@ -112,6 +113,7 @@ int cmd_main(int argc, const char **argv)
argc--;
trace2_cmd_name(cmds[i].name);
trace2_cmd_list_config();
+ trace2_cmd_list_env_vars();
return cmds[i].fn(argc, argv);
}
}
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 4eb5e6609e..ddc8e990e9 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -5,6 +5,7 @@
#include "git-compat-util.h"
int cmd__advise_if_enabled(int argc, const char **argv);
+int cmd__bloom(int argc, const char **argv);
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
int cmd__ctype(int argc, const char **argv);
@@ -47,7 +48,7 @@ int cmd__run_command(int argc, const char **argv);
int cmd__scrap_cache_tree(int argc, const char **argv);
int cmd__serve_v2(int argc, const char **argv);
int cmd__sha1(int argc, const char **argv);
-int cmd__sha1_array(int argc, const char **argv);
+int cmd__oid_array(int argc, const char **argv);
int cmd__sha256(int argc, const char **argv);
int cmd__sigchain(int argc, const char **argv);
int cmd__strcmp_offset(int argc, const char **argv);
diff --git a/t/lib-credential.sh b/t/lib-credential.sh
index bb88cc0108..dea2cbef51 100755..100644
--- a/t/lib-credential.sh
+++ b/t/lib-credential.sh
@@ -1,4 +1,5 @@
-#!/bin/sh
+# Shell library for testing credential handling including helpers. See t0302
+# for an example of testing a specific helper.
# Try a set of credential helpers; the expected stdin,
# stdout and stderr should be provided on stdin,
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
index 8d28652b72..9fc5241228 100755..100644
--- a/t/lib-gpg.sh
+++ b/t/lib-gpg.sh
@@ -1,14 +1,25 @@
-#!/bin/sh
+# We always set GNUPGHOME, even if no usable GPG was found, as
+#
+# - It does not hurt, and
+#
+# - we cannot set global environment variables in lazy prereqs because they are
+# executed in an eval'ed subshell that changes the working directory to a
+# temporary one.
+
+GNUPGHOME="$PWD/gpghome"
+export GNUPGHOME
+
+test_lazy_prereq GPG '
+ gpg_version=$(gpg --version 2>&1)
+ test $? != 127 || exit 1
-gpg_version=$(gpg --version 2>&1)
-if test $? != 127
-then
# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
- # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+ # the gpg version 1.0.6 did not parse trust packets correctly, so for
# that version, creation of signed tags using the generated key fails.
case "$gpg_version" in
- 'gpg (GnuPG) 1.0.6'*)
+ "gpg (GnuPG) 1.0.6"*)
say "Your version of gpg (1.0.6) is too buggy for testing"
+ exit 1
;;
*)
# Available key info:
@@ -27,55 +38,54 @@ then
# To export ownertrust:
# gpg --homedir /tmp/gpghome --export-ownertrust \
# > lib-gpg/ownertrust
- mkdir ./gpghome &&
- chmod 0700 ./gpghome &&
- GNUPGHOME="$(pwd)/gpghome" &&
- export GNUPGHOME &&
- (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
- gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \
+ mkdir "$GNUPGHOME" &&
+ chmod 0700 "$GNUPGHOME" &&
+ (gpgconf --kill gpg-agent || : ) &&
+ gpg --homedir "${GNUPGHOME}" --import \
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
- gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \
+ gpg --homedir "${GNUPGHOME}" --import-ownertrust \
"$TEST_DIRECTORY"/lib-gpg/ownertrust &&
- gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \
- --sign -u committer@example.com &&
- test_set_prereq GPG &&
- # Available key info:
- # * see t/lib-gpg/gpgsm-gen-key.in
- # To generate new certificate:
- # * no passphrase
- # gpgsm --homedir /tmp/gpghome/ \
- # -o /tmp/gpgsm.crt.user \
- # --generate-key \
- # --batch t/lib-gpg/gpgsm-gen-key.in
- # To import certificate:
- # gpgsm --homedir /tmp/gpghome/ \
- # --import /tmp/gpgsm.crt.user
- # To export into a .p12 we can later import:
- # gpgsm --homedir /tmp/gpghome/ \
- # -o t/lib-gpg/gpgsm_cert.p12 \
- # --export-secret-key-p12 "committer@example.com"
- echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \
- --passphrase-fd 0 --pinentry-mode loopback \
- --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
-
- gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K |
- grep fingerprint: |
- cut -d" " -f4 |
- tr -d '\n' >"${GNUPGHOME}/trustlist.txt" &&
-
- echo " S relax" >>"${GNUPGHOME}/trustlist.txt" &&
- echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
- -u committer@example.com -o /dev/null --sign - 2>&1 &&
- test_set_prereq GPGSM
+ gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null \
+ --sign -u committer@example.com
;;
esac
-fi
+'
+
+test_lazy_prereq GPGSM '
+ test_have_prereq GPG &&
+ # Available key info:
+ # * see t/lib-gpg/gpgsm-gen-key.in
+ # To generate new certificate:
+ # * no passphrase
+ # gpgsm --homedir /tmp/gpghome/ \
+ # -o /tmp/gpgsm.crt.user \
+ # --generate-key \
+ # --batch t/lib-gpg/gpgsm-gen-key.in
+ # To import certificate:
+ # gpgsm --homedir /tmp/gpghome/ \
+ # --import /tmp/gpgsm.crt.user
+ # To export into a .p12 we can later import:
+ # gpgsm --homedir /tmp/gpghome/ \
+ # -o t/lib-gpg/gpgsm_cert.p12 \
+ # --export-secret-key-p12 "committer@example.com"
+ echo | gpgsm --homedir "${GNUPGHOME}" \
+ --passphrase-fd 0 --pinentry-mode loopback \
+ --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
+
+ gpgsm --homedir "${GNUPGHOME}" -K |
+ grep fingerprint: |
+ cut -d" " -f4 |
+ tr -d "\\n" >"${GNUPGHOME}/trustlist.txt" &&
+
+ echo " S relax" >>"${GNUPGHOME}/trustlist.txt" &&
+ echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
+ -u committer@example.com -o /dev/null --sign -
+'
-if test_have_prereq GPG &&
- echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null 2>&1
-then
- test_set_prereq RFC1991
-fi
+test_lazy_prereq RFC1991 '
+ test_have_prereq GPG &&
+ echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
+'
sanitize_pgp() {
perl -ne '
diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh
index 1184cceef2..1184cceef2 100755..100644
--- a/t/lib-log-graph.sh
+++ b/t/lib-log-graph.sh
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 64fc6487dd..64fc6487dd 100755..100644
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh
index 7743f4f4c9..80c53edca7 100755
--- a/t/perf/p5310-pack-bitmaps.sh
+++ b/t/perf/p5310-pack-bitmaps.sh
@@ -31,10 +31,6 @@ test_perf 'simulated fetch' '
} | git pack-objects --revs --stdout >/dev/null
'
-test_perf 'pack to file' '
- git pack-objects --all pack1 </dev/null >/dev/null
-'
-
test_perf 'pack to file (bitmap)' '
git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
'
diff --git a/t/perf/p9300-fast-import-export.sh b/t/perf/p9300-fast-import-export.sh
new file mode 100755
index 0000000000..586161e9ad
--- /dev/null
+++ b/t/perf/p9300-fast-import-export.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='test fast-import and fast-export performance'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# Use --no-data here to produce a vastly smaller export file.
+# This is much cheaper to work with but should still exercise
+# fast-import pretty well (we'll still process all commits and
+# trees, which account for 60% or more of objects in most repos).
+#
+# Use --reencode to avoid the default of aborting on non-utf8 commits,
+# which lets this test run against a wider variety of sample repos.
+test_perf 'export (no-blobs)' '
+ git fast-export --reencode=yes --no-data HEAD >export
+'
+
+test_perf 'import (no-blobs)' '
+ git fast-import --force <export
+'
+
+test_done
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 3e440c078d..b859721620 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -833,6 +833,19 @@ then
exit 1
fi
+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_lazy_prereq LAZY true
+
+ test_expect_success lazy 'test_have_prereq LAZY && echo trace'
+
+ test_done
+ EOF
+
+ grep 'echo trace' lazy-prereq-and-tracing/err
+"
+
test_expect_success 'tests clean up even on failures' "
run_sub_test_lib_test_err \
failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF &&
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 26f8206326..1edd5aeb8f 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -392,13 +392,6 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
test_path_is_dir realgitdir/refs
'
-# Tests for the hidden file attribute on windows
-is_hidden () {
- # Use the output of `attrib`, ignore the absolute path
- case "$(attrib "$1")" in *H*?:*) return 0;; esac
- return 1
-}
-
test_expect_success MINGW '.git hidden' '
rm -rf newdir &&
(
@@ -406,7 +399,7 @@ test_expect_success MINGW '.git hidden' '
mkdir newdir &&
cd newdir &&
git init &&
- is_hidden .git
+ test_path_is_hidden .git
) &&
check_config newdir/.git false unset
'
diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh
index 1f600e2cae..88b9ae8158 100755
--- a/t/t0007-git-var.sh
+++ b/t/t0007-git-var.sh
@@ -17,7 +17,7 @@ test_expect_success 'get GIT_COMMITTER_IDENT' '
test_cmp expect actual
'
-test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identites are strict' '
+test_expect_success !FAIL_PREREQS,!AUTOIDENT 'requested identities are strict' '
(
sane_unset GIT_COMMITTER_NAME &&
sane_unset GIT_COMMITTER_EMAIL &&
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 3483b72db4..f8178ee4e3 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -54,7 +54,7 @@ Alias
-A, --alias-source <string>
get a string
-Z, --alias-target <string>
- get a string
+ alias of --alias-source
EOF
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 2ea2d00c39..56db5c8aba 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -476,6 +476,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
C:\\git \
comm \
conout.c \
+ com0.c \
lptN \
\
--not \
@@ -488,6 +489,7 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
"AUX.c" \
"abc/conOut\$ .xyz/test" \
lpt8 \
+ com9.c \
"lpt*" \
Nul \
"PRN./abc"
diff --git a/t/t0064-sha1-array.sh b/t/t0064-sha1-array.sh
index 5dda570b9a..45685af2fd 100755
--- a/t/t0064-sha1-array.sh
+++ b/t/t0064-sha1-array.sh
@@ -18,7 +18,7 @@ test_expect_success 'ordered enumeration' '
{
echoid append 88 44 aa 55 &&
echo for_each_unique
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
test_cmp expect actual
'
@@ -28,7 +28,7 @@ test_expect_success 'ordered enumeration with duplicate suppression' '
echoid append 88 44 aa 55 &&
echoid append 88 44 aa 55 &&
echo for_each_unique
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
test_cmp expect actual
'
@@ -36,7 +36,7 @@ test_expect_success 'lookup' '
{
echoid append 88 44 aa 55 &&
echoid lookup 55
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -eq 1
'
@@ -45,7 +45,7 @@ test_expect_success 'lookup non-existing entry' '
{
echoid append 88 44 aa 55 &&
echoid lookup 33
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -lt 0
'
@@ -55,7 +55,7 @@ test_expect_success 'lookup with duplicates' '
echoid append 88 44 aa 55 &&
echoid append 88 44 aa 55 &&
echoid lookup 55
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -ge 2 &&
test "$n" -le 3
@@ -66,7 +66,7 @@ test_expect_success 'lookup non-existing entry with duplicates' '
echoid append 88 44 aa 55 &&
echoid append 88 44 aa 55 &&
echoid lookup 66
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -lt 0
'
@@ -81,7 +81,7 @@ test_expect_success 'lookup with almost duplicate values' '
echo "append $id1" &&
echo "append $id2" &&
echoid lookup 55
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -eq 0
'
@@ -90,7 +90,7 @@ test_expect_success 'lookup with single duplicate value' '
{
echoid append 55 55 &&
echoid lookup 55
- } | test-tool sha1-array >actual &&
+ } | test-tool oid-array >actual &&
n=$(cat actual) &&
test "$n" -ge 0 &&
test "$n" -le 1
diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh
new file mode 100755
index 0000000000..2e73658a5c
--- /dev/null
+++ b/t/t0091-bugreport.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='git bugreport'
+
+. ./test-lib.sh
+
+# Headers "[System Info]" will be followed by a non-empty line if we put some
+# information there; we can make sure all our headers were followed by some
+# information to check if the command was successful.
+HEADER_PATTERN="^\[.*\]$"
+
+check_all_headers_populated () {
+ while read -r line
+ do
+ if test "$(grep "$HEADER_PATTERN" "$line")"
+ then
+ echo "$line"
+ read -r nextline
+ if test -z "$nextline"; then
+ return 1;
+ fi
+ fi
+ done
+}
+
+test_expect_success 'creates a report with content in the right places' '
+ test_when_finished rm git-bugreport-check-headers.txt &&
+ git bugreport -s check-headers &&
+ check_all_headers_populated <git-bugreport-check-headers.txt
+'
+
+test_expect_success 'dies if file with same name as report already exists' '
+ test_when_finished rm git-bugreport-duplicate.txt &&
+ >>git-bugreport-duplicate.txt &&
+ test_must_fail git bugreport --suffix duplicate
+'
+
+test_expect_success '--output-directory puts the report in the provided dir' '
+ test_when_finished rm -fr foo/ &&
+ git bugreport -o foo/ &&
+ test_path_is_file foo/git-bugreport-*
+'
+
+test_expect_success 'incorrect arguments abort with usage' '
+ test_must_fail git bugreport --false 2>output &&
+ test_i18ngrep usage output &&
+ test_path_is_missing git-bugreport-*
+'
+
+test_expect_success 'runs outside of a git dir' '
+ test_when_finished rm non-repo/git-bugreport-* &&
+ nongit git bugreport
+'
+
+test_expect_success 'can create leading directories outside of a git dir' '
+ test_when_finished rm -fr foo/bar/baz &&
+ nongit git bugreport -o foo/bar/baz
+'
+
+
+test_done
diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh
new file mode 100755
index 0000000000..8f9eef116d
--- /dev/null
+++ b/t/t0095-bloom.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+test_description='Testing the various Bloom filter computations in bloom.c'
+. ./test-lib.sh
+
+test_expect_success 'compute unseeded murmur3 hash for empty string' '
+ cat >expect <<-\EOF &&
+ Murmur3 Hash with seed=0:0x00000000
+ EOF
+ test-tool bloom get_murmur3 "" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute unseeded murmur3 hash for test string 1' '
+ cat >expect <<-\EOF &&
+ Murmur3 Hash with seed=0:0x627b0c2c
+ EOF
+ test-tool bloom get_murmur3 "Hello world!" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute unseeded murmur3 hash for test string 2' '
+ cat >expect <<-\EOF &&
+ Murmur3 Hash with seed=0:0x2e4ff723
+ EOF
+ test-tool bloom get_murmur3 "The quick brown fox jumps over the lazy dog" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for empty string' '
+ cat >expect <<-\EOF &&
+ Hashes:0x5615800c|0x5b966560|0x61174ab4|0x66983008|0x6c19155c|0x7199fab0|0x771ae004|
+ Filter_Length:2
+ Filter_Data:11|11|
+ EOF
+ test-tool bloom generate_filter "" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for whitespace' '
+ cat >expect <<-\EOF &&
+ Hashes:0xf178874c|0x5f3d6eb6|0xcd025620|0x3ac73d8a|0xa88c24f4|0x16510c5e|0x8415f3c8|
+ Filter_Length:2
+ Filter_Data:51|55|
+ EOF
+ test-tool bloom generate_filter " " >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for test string 1' '
+ cat >expect <<-\EOF &&
+ Hashes:0xb270de9b|0x1bb6f26e|0x84fd0641|0xee431a14|0x57892de7|0xc0cf41ba|0x2a15558d|
+ Filter_Length:2
+ Filter_Data:92|6c|
+ EOF
+ test-tool bloom generate_filter "Hello world!" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'compute bloom key for test string 2' '
+ cat >expect <<-\EOF &&
+ Hashes:0x20ab385b|0xf5237fe2|0xc99bc769|0x9e140ef0|0x728c5677|0x47049dfe|0x1b7ce585|
+ Filter_Length:2
+ Filter_Data:a5|4a|
+ EOF
+ test-tool bloom generate_filter "file.txt" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get bloom filters for commit with no changes' '
+ git init &&
+ git commit --allow-empty -m "c0" &&
+ cat >expect <<-\EOF &&
+ Filter_Length:0
+ Filter_Data:
+ EOF
+ test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get bloom filter for commit with 10 changes' '
+ rm actual &&
+ rm expect &&
+ mkdir smallDir &&
+ for i in $(test_seq 0 9)
+ do
+ echo $i >smallDir/$i
+ done &&
+ git add smallDir &&
+ git commit -m "commit with 10 changes" &&
+ cat >expect <<-\EOF &&
+ Filter_Length:25
+ Filter_Data:82|a0|65|47|0c|92|90|c0|a1|40|02|a0|e2|40|e0|04|0a|9a|66|cf|80|19|85|42|23|
+ EOF
+ test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success EXPENSIVE 'get bloom filter for commit with 513 changes' '
+ rm actual &&
+ rm expect &&
+ mkdir bigDir &&
+ for i in $(test_seq 0 512)
+ do
+ echo $i >bigDir/$i
+ done &&
+ git add bigDir &&
+ git commit -m "commit with 513 changes" &&
+ cat >expect <<-\EOF &&
+ Filter_Length:0
+ Filter_Data:
+ EOF
+ test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
+ test_cmp expect actual
+'
+
+test_done \ No newline at end of file
diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh
index 7065a1b937..1529155cf0 100755
--- a/t/t0212-trace2-event.sh
+++ b/t/t0212-trace2-event.sh
@@ -199,6 +199,43 @@ test_expect_success JSON_PP 'event stream, list config' '
test_cmp expect actual
'
+# Test listing of all "interesting" environment variables.
+
+test_expect_success JSON_PP 'event stream, list env vars' '
+ test_when_finished "rm trace.event actual expect" &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ GIT_TRACE2_ENV_VARS="A_VAR,OTHER_VAR,MISSING" \
+ A_VAR=1 OTHER_VAR="hello world" test-tool trace2 001return 0 &&
+ perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
+ sed -e "s/^|//" >expect <<-EOF &&
+ |VAR1 = {
+ | "_SID0_":{
+ | "argv":[
+ | "_EXE_",
+ | "trace2",
+ | "001return",
+ | "0"
+ | ],
+ | "exit_code":0,
+ | "hierarchy":"trace2",
+ | "name":"trace2",
+ | "params":[
+ | {
+ | "param":"A_VAR",
+ | "value":"1"
+ | },
+ | {
+ | "param":"OTHER_VAR",
+ | "value":"hello world"
+ | }
+ | ],
+ | "version":"$V"
+ | }
+ |};
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success JSON_PP 'basic trace2_data' '
test_when_finished "rm trace.event actual expect" &&
GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index 5b78ebbc3f..91a007fbfa 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -22,6 +22,11 @@ test_expect_success 'setup helper scripts' '
exit 0
EOF
+ write_script git-credential-quit <<-\EOF &&
+ . ./dump
+ echo quit=1
+ EOF
+
write_script git-credential-verbatim <<-\EOF &&
user=$1; shift
pass=$1; shift
@@ -35,43 +40,71 @@ test_expect_success 'setup helper scripts' '
test_expect_success 'credential_fill invokes helper' '
check fill "verbatim foo bar" <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=foo
password=bar
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
EOF
'
test_expect_success 'credential_fill invokes multiple helpers' '
check fill useless "verbatim foo bar" <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=foo
password=bar
--
useless: get
+ useless: protocol=http
+ useless: host=example.com
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
EOF
'
test_expect_success 'credential_fill stops when we get a full response' '
check fill "verbatim one two" "verbatim three four" <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=one
password=two
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
EOF
'
test_expect_success 'credential_fill continues through partial response' '
check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=two
password=three
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: username=one
EOF
'
@@ -97,14 +130,20 @@ test_expect_success 'credential_fill passes along metadata' '
test_expect_success 'credential_approve calls all helpers' '
check approve useless "verbatim one two" <<-\EOF
+ protocol=http
+ host=example.com
username=foo
password=bar
--
--
useless: store
+ useless: protocol=http
+ useless: host=example.com
useless: username=foo
useless: password=bar
verbatim: store
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: username=foo
verbatim: password=bar
EOF
@@ -112,6 +151,8 @@ test_expect_success 'credential_approve calls all helpers' '
test_expect_success 'do not bother storing password-less credential' '
check approve useless <<-\EOF
+ protocol=http
+ host=example.com
username=foo
--
--
@@ -121,14 +162,20 @@ test_expect_success 'do not bother storing password-less credential' '
test_expect_success 'credential_reject calls all helpers' '
check reject useless "verbatim one two" <<-\EOF
+ protocol=http
+ host=example.com
username=foo
password=bar
--
--
useless: erase
+ useless: protocol=http
+ useless: host=example.com
useless: username=foo
useless: password=bar
verbatim: erase
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: username=foo
verbatim: password=bar
EOF
@@ -136,33 +183,49 @@ test_expect_success 'credential_reject calls all helpers' '
test_expect_success 'usernames can be preserved' '
check fill "verbatim \"\" three" <<-\EOF
+ protocol=http
+ host=example.com
username=one
--
+ protocol=http
+ host=example.com
username=one
password=three
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: username=one
EOF
'
test_expect_success 'usernames can be overridden' '
check fill "verbatim two three" <<-\EOF
+ protocol=http
+ host=example.com
username=one
--
+ protocol=http
+ host=example.com
username=two
password=three
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
verbatim: username=one
EOF
'
test_expect_success 'do not bother completing already-full credential' '
check fill "verbatim three four" <<-\EOF
+ protocol=http
+ host=example.com
username=one
password=two
--
+ protocol=http
+ host=example.com
username=one
password=two
--
@@ -174,23 +237,31 @@ test_expect_success 'do not bother completing already-full credential' '
# askpass helper is run, we know the internal getpass is working.
test_expect_success 'empty helper list falls back to internal getpass' '
check fill <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=askpass-username
password=askpass-password
--
- askpass: Username:
- askpass: Password:
+ askpass: Username for '\''http://example.com'\'':
+ askpass: Password for '\''http://askpass-username@example.com'\'':
EOF
'
test_expect_success 'internal getpass does not ask for known username' '
check fill <<-\EOF
+ protocol=http
+ host=example.com
username=foo
--
+ protocol=http
+ host=example.com
username=foo
password=askpass-password
--
- askpass: Password:
+ askpass: Password for '\''http://foo@example.com'\'':
EOF
'
@@ -202,7 +273,11 @@ HELPER="!f() {
test_expect_success 'respect configured credentials' '
test_config credential.helper "$HELPER" &&
check fill <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=foo
password=bar
--
@@ -291,6 +366,51 @@ test_expect_success 'match percent-encoded values' '
EOF
'
+test_expect_success 'match percent-encoded UTF-8 values in path' '
+ test_config credential.https://example.com.useHttpPath true &&
+ test_config credential.https://example.com/perú.git.helper "$HELPER" &&
+ check fill <<-\EOF
+ url=https://example.com/per%C3%BA.git
+ --
+ protocol=https
+ host=example.com
+ path=perú.git
+ username=foo
+ password=bar
+ --
+ EOF
+'
+
+test_expect_success 'match percent-encoded values in username' '
+ test_config credential.https://user%2fname@example.com/foo/bar.git.helper "$HELPER" &&
+ check fill <<-\EOF
+ url=https://user%2fname@example.com/foo/bar.git
+ --
+ protocol=https
+ host=example.com
+ username=foo
+ password=bar
+ --
+ EOF
+'
+
+test_expect_success 'fetch with multiple path components' '
+ test_unconfig credential.helper &&
+ test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
+ check fill <<-\EOF
+ url=https://example.com/foo/repo.git
+ --
+ protocol=https
+ host=example.com
+ username=foo
+ password=bar
+ --
+ verbatim: get
+ verbatim: protocol=https
+ verbatim: host=example.com
+ EOF
+'
+
test_expect_success 'pull username from config' '
test_config credential.https://example.com.username foo &&
check fill <<-\EOF
@@ -419,35 +539,119 @@ test_expect_success 'context uses urlmatch' '
test_expect_success 'helpers can abort the process' '
test_must_fail git \
- -c credential.helper="!f() { echo quit=1; }; f" \
+ -c credential.helper=quit \
-c credential.helper="verbatim foo bar" \
- credential fill >stdout &&
- test_must_be_empty stdout
+ credential fill >stdout 2>stderr <<-\EOF &&
+ protocol=http
+ host=example.com
+ EOF
+ test_must_be_empty stdout &&
+ cat >expect <<-\EOF &&
+ quit: get
+ quit: protocol=http
+ quit: host=example.com
+ fatal: credential helper '\''quit'\'' told us to quit
+ EOF
+ test_i18ncmp expect stderr
'
test_expect_success 'empty helper spec resets helper list' '
test_config credential.helper "verbatim file file" &&
check fill "" "verbatim cmdline cmdline" <<-\EOF
+ protocol=http
+ host=example.com
--
+ protocol=http
+ host=example.com
username=cmdline
password=cmdline
--
verbatim: get
+ verbatim: protocol=http
+ verbatim: host=example.com
EOF
'
-test_expect_success 'url parser ignores embedded newlines' '
- check fill <<-EOF
+test_expect_success 'url parser rejects embedded newlines' '
+ test_must_fail git credential fill 2>stderr <<-\EOF &&
url=https://one.example.com?%0ahost=two.example.com/
+ EOF
+ cat >expect <<-\EOF &&
+ warning: url contains a newline in its path component: https://one.example.com?%0ahost=two.example.com/
+ fatal: credential url cannot be parsed: https://one.example.com?%0ahost=two.example.com/
+ EOF
+ test_i18ncmp expect stderr
+'
+
+test_expect_success 'host-less URLs are parsed as empty host' '
+ check fill "verbatim foo bar" <<-\EOF
+ url=cert:///path/to/cert.pem
--
- username=askpass-username
- password=askpass-password
+ protocol=cert
+ host=
+ path=path/to/cert.pem
+ username=foo
+ password=bar
+ --
+ verbatim: get
+ verbatim: protocol=cert
+ verbatim: host=
+ verbatim: path=path/to/cert.pem
+ EOF
+'
+
+test_expect_success 'credential system refuses to work with missing host' '
+ test_must_fail git credential fill 2>stderr <<-\EOF &&
+ protocol=http
+ EOF
+ cat >expect <<-\EOF &&
+ fatal: refusing to work with credential missing host field
+ EOF
+ test_i18ncmp expect stderr
+'
+
+test_expect_success 'credential system refuses to work with missing protocol' '
+ test_must_fail git credential fill 2>stderr <<-\EOF &&
+ host=example.com
+ EOF
+ cat >expect <<-\EOF &&
+ fatal: refusing to work with credential missing protocol field
+ EOF
+ test_i18ncmp expect stderr
+'
+
+# usage: check_host_and_path <url> <expected-host> <expected-path>
+check_host_and_path () {
+ # we always parse the path component, but we need this to make sure it
+ # is passed to the helper
+ test_config credential.useHTTPPath true &&
+ check fill "verbatim user pass" <<-EOF
+ url=$1
--
- warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
- warning: skipping credential lookup for url: https://one.example.com?%0ahost=two.example.com/
- askpass: Username:
- askpass: Password:
+ protocol=https
+ host=$2
+ path=$3
+ username=user
+ password=pass
+ --
+ verbatim: get
+ verbatim: protocol=https
+ verbatim: host=$2
+ verbatim: path=$3
EOF
+}
+
+test_expect_success 'url parser handles bare query marker' '
+ check_host_and_path https://example.com?foo.git example.com ?foo.git
+'
+
+test_expect_success 'url parser handles bare fragment marker' '
+ check_host_and_path https://example.com#foo.git example.com "#foo.git"
+'
+
+test_expect_success 'url parser not confused by encoded markers' '
+ check_host_and_path https://example.com%23%3f%2f/foo.git \
+ "example.com#?/" foo.git
'
test_done
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index eb44bafb59..63223e13bd 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -233,18 +233,19 @@ test_expect_success 'read-tree --reset removes outside worktree' '
test_must_be_empty result
'
-test_expect_success 'print errors when failed to update worktree' '
+test_expect_success 'print warnings when some worktree updates disabled' '
echo sub >.git/info/sparse-checkout &&
git checkout -f init &&
mkdir sub &&
touch sub/added sub/addedtoo &&
- test_must_fail git checkout top 2>actual &&
+ # Use -q to suppress "Previous HEAD position" and "Head is now at" msgs
+ git checkout -q top 2>actual &&
cat >expected <<\EOF &&
-error: The following untracked working tree files would be overwritten by checkout:
+warning: The following paths were already present and thus not updated despite sparse patterns:
sub/added
sub/addedtoo
-Please move or remove them before you switch branches.
-Aborting
+
+After fixing the above paths, you may want to run `git sparse-checkout reapply`.
EOF
test_i18ncmp expected actual
'
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index 44a91205d6..dee99eeec3 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -277,15 +277,23 @@ test_expect_success 'cone mode: add parent path' '
check_files repo a deep folder1
'
-test_expect_success 'revert to old sparse-checkout on bad update' '
+test_expect_success 'not-up-to-date does not block rest of sparsification' '
+ test_when_finished git -C repo sparse-checkout disable &&
test_when_finished git -C repo reset --hard &&
git -C repo sparse-checkout set deep &&
+
echo update >repo/deep/deeper2/a &&
cp repo/.git/info/sparse-checkout expect &&
- test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
- test_i18ngrep "cannot set sparse-checkout patterns" err &&
- test_cmp repo/.git/info/sparse-checkout expect &&
- check_files repo/deep a deeper1 deeper2
+ test_write_lines "!/deep/*/" "/deep/deeper1/" >>expect &&
+
+ git -C repo sparse-checkout set deep/deeper1 2>err &&
+
+ test_i18ngrep "The following paths are not up to date" err &&
+ test_cmp expect repo/.git/info/sparse-checkout &&
+ check_files repo/deep a deeper1 deeper2 &&
+ check_files repo/deep/deeper1 a deepest &&
+ check_files repo/deep/deeper1/deepest a &&
+ check_files repo/deep/deeper2 a
'
test_expect_success 'revert to old sparse-checkout on empty update' '
@@ -315,19 +323,96 @@ test_expect_success '.gitignore should not warn about cone mode' '
test_i18ngrep ! "disabling cone patterns" err
'
-test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status' '
+test_expect_success 'sparse-checkout (init|set|disable) warns with dirty status' '
git clone repo dirty &&
echo dirty >dirty/folder1/a &&
- test_must_fail git -C dirty sparse-checkout init &&
- test_must_fail git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
- test_must_fail git -C dirty sparse-checkout disable &&
+
+ git -C dirty sparse-checkout init 2>err &&
+ test_i18ngrep "warning.*The following paths are not up to date" err &&
+
+ git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+ test_i18ngrep "warning.*The following paths are not up to date" err &&
+ test_path_is_file dirty/folder1/a &&
+
+ git -C dirty sparse-checkout disable 2>err &&
+ test_must_be_empty err &&
+
git -C dirty reset --hard &&
git -C dirty sparse-checkout init &&
git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
- git -C dirty sparse-checkout disable
+ test_path_is_missing dirty/folder1/a &&
+ git -C dirty sparse-checkout disable &&
+ test_path_is_file dirty/folder1/a
+'
+
+test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged status' '
+ git clone repo unmerged &&
+
+ cat >input <<-EOF &&
+ 0 0000000000000000000000000000000000000000 folder1/a
+ 100644 $(git -C unmerged rev-parse HEAD:folder1/a) 1 folder1/a
+ EOF
+ git -C unmerged update-index --index-info <input &&
+
+ git -C unmerged sparse-checkout init 2>err &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+ git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+ test_path_is_file dirty/folder1/a &&
+
+ git -C unmerged sparse-checkout disable 2>err &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+ git -C unmerged reset --hard &&
+ git -C unmerged sparse-checkout init &&
+ git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* &&
+ git -C unmerged sparse-checkout disable
+'
+
+test_expect_success 'sparse-checkout reapply' '
+ git clone repo tweak &&
+
+ echo dirty >tweak/deep/deeper2/a &&
+
+ cat >input <<-EOF &&
+ 0 0000000000000000000000000000000000000000 folder1/a
+ 100644 $(git -C tweak rev-parse HEAD:folder1/a) 1 folder1/a
+ EOF
+ git -C tweak update-index --index-info <input &&
+
+ git -C tweak sparse-checkout init --cone 2>err &&
+ test_i18ngrep "warning.*The following paths are not up to date" err &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+ git -C tweak sparse-checkout set folder2 deep/deeper1 2>err &&
+ test_i18ngrep "warning.*The following paths are not up to date" err &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+ git -C tweak sparse-checkout reapply 2>err &&
+ test_i18ngrep "warning.*The following paths are not up to date" err &&
+ test_path_is_file tweak/deep/deeper2/a &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+ test_path_is_file tweak/folder1/a &&
+
+ git -C tweak checkout HEAD deep/deeper2/a &&
+ git -C tweak sparse-checkout reapply 2>err &&
+ test_i18ngrep ! "warning.*The following paths are not up to date" err &&
+ test_path_is_missing tweak/deep/deeper2/a &&
+ test_i18ngrep "warning.*The following paths are unmerged" err &&
+ test_path_is_file tweak/folder1/a &&
+
+ git -C tweak add folder1/a &&
+ git -C tweak sparse-checkout reapply 2>err &&
+ test_must_be_empty err &&
+ test_path_is_missing tweak/deep/deeper2/a &&
+ test_path_is_missing tweak/folder1/a &&
+
+ git -C tweak sparse-checkout disable
'
test_expect_success 'cone mode: set with core.ignoreCase=true' '
+ rm repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout init --cone &&
git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
cat >expect <<-\EOF &&
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index a6224ef65f..e1197ac818 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1354,15 +1354,6 @@ test_expect_success 'fails with duplicate ref update via symref' '
test_cmp expect actual
'
-run_with_limited_open_files () {
- (ulimit -n 32 && "$@")
-}
-
-test_lazy_prereq ULIMIT_FILE_DESCRIPTORS '
- test_have_prereq !MINGW,!CYGWIN &&
- run_with_limited_open_files true
-'
-
test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' '
(
for i in $(test_seq 33)
@@ -1404,4 +1395,135 @@ test_expect_success 'handle per-worktree refs in refs/bisect' '
! test_cmp main-head worktree-head
'
+test_expect_success 'transaction handles empty commit' '
+ cat >stdin <<-EOF &&
+ start
+ prepare
+ commit
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start prepare commit >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction handles empty commit with missing prepare' '
+ cat >stdin <<-EOF &&
+ start
+ commit
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start commit >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction handles sole commit' '
+ cat >stdin <<-EOF &&
+ commit
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" commit >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction handles empty abort' '
+ cat >stdin <<-EOF &&
+ start
+ prepare
+ abort
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start prepare abort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction exits on multiple aborts' '
+ cat >stdin <<-EOF &&
+ abort
+ abort
+ EOF
+ test_must_fail git update-ref --stdin <stdin >actual 2>err &&
+ printf "%s: ok\n" abort >expect &&
+ test_cmp expect actual &&
+ grep "fatal: transaction is closed" err
+'
+
+test_expect_success 'transaction exits on start after prepare' '
+ cat >stdin <<-EOF &&
+ prepare
+ start
+ EOF
+ test_must_fail git update-ref --stdin <stdin 2>err >actual &&
+ printf "%s: ok\n" prepare >expect &&
+ test_cmp expect actual &&
+ grep "fatal: prepared transactions can only be closed" err
+'
+
+test_expect_success 'transaction handles empty abort with missing prepare' '
+ cat >stdin <<-EOF &&
+ start
+ abort
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start abort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction handles sole abort' '
+ cat >stdin <<-EOF &&
+ abort
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" abort >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction can handle commit' '
+ cat >stdin <<-EOF &&
+ start
+ create $a HEAD
+ commit
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start commit >expect &&
+ test_cmp expect actual &&
+ git rev-parse HEAD >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'transaction can handle abort' '
+ cat >stdin <<-EOF &&
+ start
+ create $b HEAD
+ abort
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start abort >expect &&
+ test_cmp expect actual &&
+ test_path_is_missing .git/$b
+'
+
+test_expect_success 'transaction aborts by default' '
+ cat >stdin <<-EOF &&
+ start
+ create $b HEAD
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start >expect &&
+ test_cmp expect actual &&
+ test_path_is_missing .git/$b
+'
+
+test_expect_success 'transaction with prepare aborts by default' '
+ cat >stdin <<-EOF &&
+ start
+ create $b HEAD
+ prepare
+ EOF
+ git update-ref --stdin <stdin >actual &&
+ printf "%s: ok\n" start prepare >expect &&
+ test_cmp expect actual &&
+ test_path_is_missing .git/$b
+'
+
test_done
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index bbca7ef8da..21583154d8 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -238,4 +238,26 @@ test_expect_success 'checkout -b after clone --no-checkout does a checkout of HE
test_path_is_file dest/a.t
'
+test_expect_success 'checkout -b to a new branch preserves mergeable changes despite sparse-checkout' '
+ test_when_finished "
+ git reset --hard &&
+ git checkout branch1-scratch &&
+ test_might_fail git branch -D branch3 &&
+ git config core.sparseCheckout false &&
+ rm .git/info/sparse-checkout" &&
+
+ test_commit file2 &&
+
+ echo stuff >>file1 &&
+ echo file2 >.git/info/sparse-checkout &&
+ git config core.sparseCheckout true &&
+
+ CURHEAD=$(git rev-parse HEAD) &&
+ do_checkout branch3 $CURHEAD &&
+
+ echo file1 >expect &&
+ git diff --name-only >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 69ffe865b4..52585ec2aa 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -152,7 +152,7 @@ test_expect_success 'linked worktrees are sorted' '
'
test_expect_success 'worktree path when called in .git directory' '
- git worktree list >list1&&
+ git worktree list >list1 &&
git -C .git worktree list >list2 &&
test_cmp list1 list2
'
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
index 0aefadacb0..ffdfb16f58 100755
--- a/t/t3000-ls-files-others.sh
+++ b/t/t3000-ls-files-others.sh
@@ -91,4 +91,125 @@ test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
test_cmp expect actual
'
+test_expect_success 'setup nested pathspec search' '
+ test_create_repo nested &&
+ (
+ cd nested &&
+
+ mkdir -p partially_tracked/untracked_dir &&
+ > partially_tracked/content &&
+ > partially_tracked/untracked_dir/file &&
+
+ mkdir -p untracked/deep &&
+ > untracked/deep/path &&
+ > untracked/deep/foo.c &&
+
+ git add partially_tracked/content
+ )
+'
+
+test_expect_success 'ls-files -o --directory with single deep dir pathspec' '
+ (
+ cd nested &&
+
+ git ls-files -o --directory untracked/deep/ >actual &&
+
+ cat <<-EOF >expect &&
+ untracked/deep/
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files -o --directory with multiple dir pathspecs' '
+ (
+ cd nested &&
+
+ git ls-files -o --directory partially_tracked/ untracked/ >actual &&
+
+ cat <<-EOF >expect &&
+ partially_tracked/untracked_dir/
+ untracked/
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files -o --directory with mix dir/file pathspecs' '
+ (
+ cd nested &&
+
+ git ls-files -o --directory partially_tracked/ untracked/deep/path >actual &&
+
+ cat <<-EOF >expect &&
+ partially_tracked/untracked_dir/
+ untracked/deep/path
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files --o --directory with glob filetype match' '
+ (
+ cd nested &&
+
+ # globs kinda defeat --directory, but only for that pathspec
+ git ls-files --others --directory partially_tracked "untracked/*.c" >actual &&
+
+ cat <<-EOF >expect &&
+ partially_tracked/untracked_dir/
+ untracked/deep/foo.c
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files --o --directory with mix of tracked states' '
+ (
+ cd nested &&
+
+ # globs kinda defeat --directory, but only for that pathspec
+ git ls-files --others --directory partially_tracked/ "untracked/?*" >actual &&
+
+ cat <<-EOF >expect &&
+ partially_tracked/untracked_dir/
+ untracked/deep/
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files --o --directory with glob filetype match only' '
+ (
+ cd nested &&
+
+ git ls-files --others --directory "untracked/*.c" >actual &&
+
+ cat <<-EOF >expect &&
+ untracked/deep/foo.c
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'ls-files --o --directory to get immediate paths under one dir only' '
+ (
+ cd nested &&
+
+ git ls-files --others --directory "untracked/?*" >actual &&
+
+ cat <<-EOF >expect &&
+ untracked/deep/
+ EOF
+
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh
index d314599428..e29c284b9b 100755
--- a/t/t3033-merge-toplevel.sh
+++ b/t/t3033-merge-toplevel.sh
@@ -142,6 +142,17 @@ test_expect_success 'refuse two-project merge by default' '
test_must_fail git merge five
'
+test_expect_success 'refuse two-project merge by default, quit before --autostash happens' '
+ t3033_reset &&
+ git reset --hard four &&
+ echo change >>one.t &&
+ git diff >expect &&
+ test_must_fail git merge --autostash five 2>err &&
+ test_i18ngrep ! "stash" err &&
+ git diff >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'two-project merge with --allow-unrelated-histories' '
t3033_reset &&
git reset --hard four &&
@@ -149,4 +160,15 @@ test_expect_success 'two-project merge with --allow-unrelated-histories' '
git diff --exit-code five
'
+test_expect_success 'two-project merge with --allow-unrelated-histories with --autostash' '
+ t3033_reset &&
+ git reset --hard four &&
+ echo change >>one.t &&
+ git diff one.t >expect &&
+ git merge --allow-unrelated-histories --autostash five 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git diff one.t >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index bd808f87ed..e024cff65c 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -513,6 +513,16 @@ test_expect_success 'range-diff overrides diff.noprefix internally' '
git -c diff.noprefix=true range-diff HEAD^...
'
+test_expect_success 'basic with modified format.pretty with suffix' '
+ git -c format.pretty="format:commit %H%d%n" range-diff \
+ master..topic master..unmodified
+'
+
+test_expect_success 'basic with modified format.pretty without "commit "' '
+ git -c format.pretty="format:%H%n" range-diff \
+ master..topic master..unmodified
+'
+
test_expect_success 'range-diff compares notes by default' '
git notes add -m "topic note" topic &&
git notes add -m "unmodified note" unmodified &&
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index a1ec501a87..6e032716a6 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -162,4 +162,81 @@ test_expect_success 'rebase --skip works with two conflicts in a row' '
git rebase --skip
'
+test_expect_success '--reapply-cherry-picks' '
+ git init repo &&
+
+ # O(1-10) -- O(1-11) -- O(0-10) master
+ # \
+ # -- O(1-11) -- O(1-12) otherbranch
+
+ printf "Line %d\n" $(test_seq 1 10) >repo/file.txt &&
+ git -C repo add file.txt &&
+ git -C repo commit -m "base commit" &&
+
+ printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
+ git -C repo commit -a -m "add 11" &&
+
+ printf "Line %d\n" $(test_seq 0 10) >repo/file.txt &&
+ git -C repo commit -a -m "add 0 delete 11" &&
+
+ git -C repo checkout -b otherbranch HEAD^^ &&
+ printf "Line %d\n" $(test_seq 1 11) >repo/file.txt &&
+ git -C repo commit -a -m "add 11 in another branch" &&
+
+ printf "Line %d\n" $(test_seq 1 12) >repo/file.txt &&
+ git -C repo commit -a -m "add 12 in another branch" &&
+
+ # Regular rebase fails, because the 1-11 commit is deduplicated
+ test_must_fail git -C repo rebase --merge master 2> err &&
+ test_i18ngrep "error: could not apply.*add 12 in another branch" err &&
+ git -C repo rebase --abort &&
+
+ # With --reapply-cherry-picks, it works
+ git -C repo rebase --merge --reapply-cherry-picks master
+'
+
+test_expect_success '--reapply-cherry-picks refrains from reading unneeded blobs' '
+ git init server &&
+
+ # O(1-10) -- O(1-11) -- O(1-12) master
+ # \
+ # -- O(0-10) otherbranch
+
+ printf "Line %d\n" $(test_seq 1 10) >server/file.txt &&
+ git -C server add file.txt &&
+ git -C server commit -m "merge base" &&
+
+ printf "Line %d\n" $(test_seq 1 11) >server/file.txt &&
+ git -C server commit -a -m "add 11" &&
+
+ printf "Line %d\n" $(test_seq 1 12) >server/file.txt &&
+ git -C server commit -a -m "add 12" &&
+
+ git -C server checkout -b otherbranch HEAD^^ &&
+ printf "Line %d\n" $(test_seq 0 10) >server/file.txt &&
+ git -C server commit -a -m "add 0" &&
+
+ test_config -C server uploadpack.allowfilter 1 &&
+ test_config -C server uploadpack.allowanysha1inwant 1 &&
+
+ git clone --filter=blob:none "file://$(pwd)/server" client &&
+ git -C client checkout origin/master &&
+ git -C client checkout origin/otherbranch &&
+
+ # Sanity check to ensure that the blobs from the merge base and "add
+ # 11" are missing
+ git -C client rev-list --objects --all --missing=print >missing_list &&
+ MERGE_BASE_BLOB=$(git -C server rev-parse master^^:file.txt) &&
+ ADD_11_BLOB=$(git -C server rev-parse master^:file.txt) &&
+ grep "[?]$MERGE_BASE_BLOB" missing_list &&
+ grep "[?]$ADD_11_BLOB" missing_list &&
+
+ git -C client rebase --merge --reapply-cherry-picks origin/master &&
+
+ # The blob from the merge base had to be fetched, but not "add 11"
+ git -C client rev-list --objects --all --missing=print >missing_list &&
+ ! grep "[?]$MERGE_BASE_BLOB" missing_list &&
+ grep "[?]$ADD_11_BLOB" missing_list
+'
+
test_done
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 61b76f3301..927a4f4a4e 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -89,22 +89,22 @@ test_expect_success 'GIT_REFLOG_ACTION' '
git checkout -b reflog-topic start &&
test_commit reflog-to-rebase &&
- git rebase --apply reflog-onto &&
+ git rebase reflog-onto &&
git log -g --format=%gs -3 >actual &&
cat >expect <<-\EOF &&
- rebase finished: returning to refs/heads/reflog-topic
- rebase: reflog-to-rebase
- rebase: checkout reflog-onto
+ rebase (finish): returning to refs/heads/reflog-topic
+ rebase (pick): reflog-to-rebase
+ rebase (start): checkout reflog-onto
EOF
test_cmp expect actual &&
git checkout -b reflog-prefix reflog-to-rebase &&
- GIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto &&
+ GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto &&
git log -g --format=%gs -3 >actual &&
cat >expect <<-\EOF &&
- rebase finished: returning to refs/heads/reflog-prefix
- change-the-reflog: reflog-to-rebase
- change-the-reflog: checkout reflog-onto
+ change-the-reflog (finish): returning to refs/heads/reflog-prefix
+ change-the-reflog (pick): reflog-to-rebase
+ change-the-reflog (start): checkout reflog-onto
EOF
test_cmp expect actual
'
diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh
index e85cdc7037..946e92f8da 100755
--- a/t/t3417-rebase-whitespace-fix.sh
+++ b/t/t3417-rebase-whitespace-fix.sh
@@ -52,7 +52,7 @@ test_expect_success 'blank line at end of file; extend at end of file' '
git commit --allow-empty -m "Initial empty commit" &&
git add file && git commit -m first &&
mv second file &&
- git add file && git commit -m second &&
+ git add file && git commit -m second &&
git rebase --whitespace=fix HEAD^^ &&
git diff --exit-code HEAD^:file expect-first &&
test_cmp expect-second file
@@ -118,7 +118,7 @@ test_expect_success 'at beginning of file' '
for i in 1 2 3 4 5; do
echo $i
done >> file &&
- git commit -m more file &&
+ git commit -m more file &&
git rebase --whitespace=fix HEAD^^ &&
test_cmp expect-beginning file
'
diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh
index d934583776..1f32faa4a4 100755
--- a/t/t3419-rebase-patch-id.sh
+++ b/t/t3419-rebase-patch-id.sh
@@ -4,15 +4,6 @@ test_description='git rebase - test patch id computation'
. ./test-lib.sh
-count () {
- i=0
- while test $i -lt $1
- do
- echo "$i"
- i=$(($i+1))
- done
-}
-
scramble () {
i=0
while read x
@@ -26,76 +17,55 @@ scramble () {
mv -f "$1.new" "$1"
}
-run () {
- echo \$ "$@"
- /usr/bin/time "$@" >/dev/null
-}
-
test_expect_success 'setup' '
git commit --allow-empty -m initial &&
git tag root
'
-do_tests () {
- nlines=$1 pr=${2-}
-
- test_expect_success $pr "setup: $nlines lines" "
- rm -f .gitattributes &&
- git checkout -q -f master &&
- git reset --hard root &&
- count $nlines >file &&
- git add file &&
- git commit -q -m initial &&
- git branch -f other &&
-
- scramble file &&
- git add file &&
- git commit -q -m 'change big file' &&
-
- git checkout -q other &&
- : >newfile &&
- git add newfile &&
- git commit -q -m 'add small file' &&
-
- git cherry-pick master >/dev/null 2>&1
- "
-
- test_debug "
- run git diff master^\!
- "
-
- test_expect_success $pr 'setup attributes' "
- echo 'file binary' >.gitattributes
- "
-
- test_debug "
- run git format-patch --stdout master &&
- run git format-patch --stdout --ignore-if-in-upstream master
- "
+test_expect_success 'setup: 500 lines' '
+ rm -f .gitattributes &&
+ git checkout -q -f master &&
+ git reset --hard root &&
+ test_seq 500 >file &&
+ git add file &&
+ git commit -q -m initial &&
+ git branch -f other &&
+
+ scramble file &&
+ git add file &&
+ git commit -q -m "change big file" &&
+
+ git checkout -q other &&
+ : >newfile &&
+ git add newfile &&
+ git commit -q -m "add small file" &&
+
+ git cherry-pick master >/dev/null 2>&1
+'
- test_expect_success $pr 'detect upstream patch' '
- git checkout -q master &&
- scramble file &&
- git add file &&
- git commit -q -m "change big file again" &&
- git checkout -q other^{} &&
- git rebase master &&
- git rev-list master...HEAD~ >revs &&
- test_must_be_empty revs
- '
+test_expect_success 'setup attributes' '
+ echo "file binary" >.gitattributes
+'
- test_expect_success $pr 'do not drop patch' '
- git branch -f squashed master &&
- git checkout -q -f squashed &&
- git reset -q --soft HEAD~2 &&
- git commit -q -m squashed &&
- git checkout -q other^{} &&
- test_must_fail git rebase squashed &&
- git rebase --quit
- '
-}
+test_expect_success 'detect upstream patch' '
+ git checkout -q master &&
+ scramble file &&
+ git add file &&
+ git commit -q -m "change big file again" &&
+ git checkout -q other^{} &&
+ git rebase master &&
+ git rev-list master...HEAD~ >revs &&
+ test_must_be_empty revs
+'
-do_tests 500
-do_tests 50000 EXPENSIVE
+test_expect_success 'do not drop patch' '
+ git branch -f squashed master &&
+ git checkout -q -f squashed &&
+ git reset -q --soft HEAD~2 &&
+ git commit -q -m squashed &&
+ git checkout -q other^{} &&
+ test_must_fail git rebase squashed &&
+ git rebase --quit
+'
test_done
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index b97ea62363..ca331733fb 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -184,6 +184,26 @@ testrebase () {
git checkout feature-branch
'
+ test_expect_success "rebase$type: --quit" '
+ test_config rebase.autostash true &&
+ git reset --hard &&
+ git checkout -b rebased-feature-branch feature-branch &&
+ test_when_finished git branch -D rebased-feature-branch &&
+ echo dirty >>file3 &&
+ git diff >expect &&
+ test_must_fail git rebase$type related-onto-branch &&
+ test_path_is_file $dotest/autostash &&
+ test_path_is_missing file3 &&
+ git rebase --quit &&
+ test_when_finished git stash drop &&
+ test_path_is_missing $dotest/autostash &&
+ ! grep dirty file3 &&
+ git stash show -p >actual &&
+ test_cmp expect actual &&
+ git reset --hard &&
+ git checkout feature-branch
+ '
+
test_expect_success "rebase$type: non-conflicting rebase, conflicting stash" '
test_config rebase.autostash true &&
git reset --hard &&
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index cf8dfd6c20..4a9204b4b6 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -220,14 +220,13 @@ test_have_prereq !REBASE_P || test_run_rebase failure -p
test_run_rebase () {
result=$1
shift
- test_expect_$result "rebase $* --keep-empty" "
+ test_expect_$result "rebase $* --no-keep-empty drops begin-empty commits" "
reset_rebase &&
- git rebase $* --keep-empty c l &&
- test_cmp_rev c HEAD~3 &&
- test_linear_range 'd k l' c..
+ git rebase $* --no-keep-empty c l &&
+ test_cmp_rev c HEAD~2 &&
+ test_linear_range 'd l' c..
"
}
-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
@@ -242,7 +241,6 @@ test_run_rebase () {
test_linear_range 'd k l' j..
"
}
-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
diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh
index e1e30517ea..5e1045a0af 100755
--- a/t/t3424-rebase-empty.sh
+++ b/t/t3424-rebase-empty.sh
@@ -123,6 +123,42 @@ test_expect_success 'rebase --interactive uses default of --empty=ask' '
test_cmp expect actual
'
+test_expect_success 'rebase --merge --empty=drop --keep-empty' '
+ git checkout -B testing localmods &&
+ git rebase --merge --empty=drop --keep-empty upstream &&
+
+ test_write_lines D C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=drop --no-keep-empty' '
+ git checkout -B testing localmods &&
+ git rebase --merge --empty=drop --no-keep-empty upstream &&
+
+ test_write_lines C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep --keep-empty' '
+ git checkout -B testing localmods &&
+ git rebase --merge --empty=keep --keep-empty upstream &&
+
+ test_write_lines D C2 C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rebase --merge --empty=keep --no-keep-empty' '
+ git checkout -B testing localmods &&
+ git rebase --merge --empty=keep --no-keep-empty upstream &&
+
+ test_write_lines C2 C B A >expect &&
+ git log --format=%s >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'rebase --merge does not leave state laying around' '
git checkout -B testing localmods~2 &&
git rebase --merge upstream &&
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh
index 6c9d4a1375..6f0452c0ea 100755
--- a/t/t3432-rebase-fast-forward.sh
+++ b/t/t3432-rebase-fast-forward.sh
@@ -28,10 +28,12 @@ test_rebase_same_head () {
shift &&
cmp_f="$1" &&
shift &&
- test_rebase_same_head_ $status_n $what_n $cmp_n " --apply" "$*" &&
- test_rebase_same_head_ $status_f $what_f $cmp_f " --apply --no-ff" "$*"
- test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" &&
- test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*"
+ test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --apply" "$*" &&
+ test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --apply --no-ff" "$*"
+ test_rebase_same_head_ $status_n $what_n $cmp_n 0 " --merge" "$*" &&
+ test_rebase_same_head_ $status_f $what_f $cmp_f 0 " --merge --no-ff" "$*"
+ test_rebase_same_head_ $status_n $what_n $cmp_n 1 " --merge" "$*" &&
+ test_rebase_same_head_ $status_f $what_f $cmp_f 1 " --merge --no-ff" "$*"
}
test_rebase_same_head_ () {
@@ -41,9 +43,21 @@ test_rebase_same_head_ () {
shift &&
cmp="$1" &&
shift &&
+ abbreviate="$1" &&
+ shift &&
flag="$1"
shift &&
- test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" "
+ if test $abbreviate -eq 1
+ then
+ msg="git rebase$flag $* (rebase.abbreviateCommands = true) with $changes is $what with $cmp HEAD"
+ else
+ msg="git rebase$flag $* with $changes is $what with $cmp HEAD"
+ fi &&
+ test_expect_$status "$msg" "
+ if test $abbreviate -eq 1
+ then
+ test_config rebase.abbreviateCommands true
+ fi &&
oldhead=\$(git rev-parse HEAD) &&
test_when_finished 'git reset --hard \$oldhead' &&
cp .git/logs/HEAD expect &&
diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh
new file mode 100755
index 0000000000..b47c59c190
--- /dev/null
+++ b/t/t3435-rebase-gpg-sign.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Doan Tran Cong Danh
+#
+
+test_description='test rebase --[no-]gpg-sign'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-rebase.sh"
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+if ! test_have_prereq GPG
+then
+ skip_all='skip all test rebase --[no-]gpg-sign, gpg not available'
+ test_done
+fi
+
+test_rebase_gpg_sign () {
+ local must_fail= will=will fake_editor=
+ if test "x$1" = "x!"
+ then
+ must_fail=test_must_fail
+ will="won't"
+ shift
+ fi
+ conf=$1
+ shift
+ test_expect_success "rebase $* with commit.gpgsign=$conf $will sign commit" "
+ git reset two &&
+ git config commit.gpgsign $conf &&
+ set_fake_editor &&
+ FAKE_LINES='r 1 p 2' git rebase --force-rebase --root $* &&
+ $must_fail git verify-commit HEAD^ &&
+ $must_fail git verify-commit HEAD
+ "
+}
+
+test_expect_success 'setup' '
+ test_commit one &&
+ test_commit two &&
+ test_must_fail git verify-commit HEAD &&
+ test_must_fail git verify-commit HEAD^
+'
+
+test_expect_success 'setup: merge commit' '
+ test_commit fork-point &&
+ git switch -c side &&
+ test_commit three &&
+ git switch master &&
+ git merge --no-ff side &&
+ git tag merged
+'
+
+test_rebase_gpg_sign ! false
+test_rebase_gpg_sign true
+test_rebase_gpg_sign ! true --no-gpg-sign
+test_rebase_gpg_sign ! true --gpg-sign --no-gpg-sign
+test_rebase_gpg_sign false --no-gpg-sign --gpg-sign
+test_rebase_gpg_sign true -i
+test_rebase_gpg_sign ! true -i --no-gpg-sign
+test_rebase_gpg_sign ! true -i --gpg-sign --no-gpg-sign
+test_rebase_gpg_sign false -i --no-gpg-sign --gpg-sign
+
+test_expect_failure 'rebase -p --no-gpg-sign override commit.gpgsign' '
+ git reset --hard merged &&
+ git config commit.gpgsign true &&
+ git rebase -p --no-gpg-sign --onto=one fork-point master &&
+ test_must_fail git verify-commit HEAD
+'
+
+test_done
diff --git a/t/t3514-cherry-pick-revert-gpg.sh b/t/t3514-cherry-pick-revert-gpg.sh
new file mode 100755
index 0000000000..5b2e250eaa
--- /dev/null
+++ b/t/t3514-cherry-pick-revert-gpg.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Doan Tran Cong Danh
+#
+
+test_description='test {cherry-pick,revert} --[no-]gpg-sign'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+if ! test_have_prereq GPG
+then
+ skip_all='skip all test {cherry-pick,revert} --[no-]gpg-sign, gpg not available'
+ test_done
+fi
+
+test_gpg_sign () {
+ local must_fail= will=will fake_editor=
+ if test "x$1" = "x!"
+ then
+ must_fail=test_must_fail
+ will="won't"
+ shift
+ fi
+ conf=$1
+ cmd=$2
+ cmit=$3
+ shift 3
+ test_expect_success "$cmd $* $cmit with commit.gpgsign=$conf $will sign commit" "
+ git reset --hard tip &&
+ git config commit.gpgsign $conf &&
+ git $cmd $* $cmit &&
+ git rev-list tip.. >rev-list &&
+ $must_fail git verify-commit \$(cat rev-list)
+ "
+}
+
+test_expect_success 'setup' '
+ test_commit one &&
+ git switch -c side &&
+ test_commit side1 &&
+ test_commit side2 &&
+ git switch - &&
+ test_commit two &&
+ test_commit three &&
+ test_commit tip
+'
+
+test_gpg_sign ! false cherry-pick side
+test_gpg_sign ! false cherry-pick ..side
+test_gpg_sign true cherry-pick side
+test_gpg_sign true cherry-pick ..side
+test_gpg_sign ! true cherry-pick side --no-gpg-sign
+test_gpg_sign ! true cherry-pick ..side --no-gpg-sign
+test_gpg_sign ! true cherry-pick side --gpg-sign --no-gpg-sign
+test_gpg_sign ! true cherry-pick ..side --gpg-sign --no-gpg-sign
+test_gpg_sign false cherry-pick side --no-gpg-sign --gpg-sign
+test_gpg_sign false cherry-pick ..side --no-gpg-sign --gpg-sign
+test_gpg_sign true cherry-pick side --edit
+test_gpg_sign true cherry-pick ..side --edit
+test_gpg_sign ! true cherry-pick side --edit --no-gpg-sign
+test_gpg_sign ! true cherry-pick ..side --edit --no-gpg-sign
+test_gpg_sign ! true cherry-pick side --edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true cherry-pick ..side --edit --gpg-sign --no-gpg-sign
+test_gpg_sign false cherry-pick side --edit --no-gpg-sign --gpg-sign
+test_gpg_sign false cherry-pick ..side --edit --no-gpg-sign --gpg-sign
+
+test_gpg_sign ! false revert HEAD --edit
+test_gpg_sign ! false revert two.. --edit
+test_gpg_sign true revert HEAD --edit
+test_gpg_sign true revert two.. --edit
+test_gpg_sign ! true revert HEAD --edit --no-gpg-sign
+test_gpg_sign ! true revert two.. --edit --no-gpg-sign
+test_gpg_sign ! true revert HEAD --edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true revert two.. --edit --gpg-sign --no-gpg-sign
+test_gpg_sign false revert HEAD --edit --no-gpg-sign --gpg-sign
+test_gpg_sign false revert two.. --edit --no-gpg-sign --gpg-sign
+test_gpg_sign true revert HEAD --no-edit
+test_gpg_sign true revert two.. --no-edit
+test_gpg_sign ! true revert HEAD --no-edit --no-gpg-sign
+test_gpg_sign ! true revert two.. --no-edit --no-gpg-sign
+test_gpg_sign ! true revert HEAD --no-edit --gpg-sign --no-gpg-sign
+test_gpg_sign ! true revert two.. --no-edit --gpg-sign --no-gpg-sign
+test_gpg_sign false revert HEAD --no-edit --no-gpg-sign --gpg-sign
+
+test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 88bc799807..b7d4ba608c 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -192,7 +192,7 @@ test_expect_success 'git add --refresh with pathspec' '
test_must_be_empty actual &&
git diff-files --name-only >actual &&
- ! grep bar actual&&
+ ! grep bar actual &&
grep baz actual
'
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 5bae6e50f1..b3d8bb7577 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -780,7 +780,7 @@ test_expect_success 'add -p patch editing works with pathological context lines'
test_expect_success 'checkout -p works with pathological context lines' '
test_write_lines a a a a a a >a &&
git add a &&
- test_write_lines a b a b a b a b a b a > a&&
+ test_write_lines a b a b a b a b a b a >a &&
test_write_lines s n n y q | git checkout -p &&
test_write_lines a b a b a a b a b a >expect &&
test_cmp expect a
diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh
index 9546b6f8a4..accfe3845c 100755
--- a/t/t3904-stash-patch.sh
+++ b/t/t3904-stash-patch.sh
@@ -89,7 +89,7 @@ test_expect_success 'none of this moved HEAD' '
verify_saved_head
'
-test_expect_failure 'stash -p with split hunk' '
+test_expect_success 'stash -p with split hunk' '
git reset --hard &&
cat >test <<-\EOF &&
aaa
@@ -106,8 +106,8 @@ test_expect_failure 'stash -p with split hunk' '
ccc
EOF
printf "%s\n" s n y q |
- test_might_fail git stash -p 2>error &&
- ! test_must_be_empty error &&
+ git stash -p 2>error &&
+ test_must_be_empty error &&
grep "added line 1" test &&
! grep "added line 2" test
'
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index dde3f11fec..3f60f7d96c 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -95,6 +95,15 @@ test_expect_success setup '
git commit -m "update mode" &&
git checkout -f master &&
+ GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+ git checkout -b note initial &&
+ git update-index --chmod=+x file2 &&
+ git commit -m "update mode (file2)" &&
+ git notes add -m "note" &&
+ git checkout -f master &&
+
# Same merge as master, but with parents reversed. Hide it in a
# pseudo-ref to avoid impacting tests with --all.
commit=$(echo reverse |
@@ -398,6 +407,9 @@ diff --no-index --raw --no-abbrev dir2 dir
diff-tree --pretty --root --stat --compact-summary initial
diff-tree --pretty -R --root --stat --compact-summary initial
+diff-tree --pretty note
+diff-tree --pretty --notes note
+diff-tree --format=%N note
diff-tree --stat --compact-summary initial mode
diff-tree -R --stat --compact-summary initial mode
EOF
diff --git a/t/t4013/diff.diff-tree_--format=%N_note b/t/t4013/diff.diff-tree_--format=%N_note
new file mode 100644
index 0000000000..93042ed539
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--format=%N_note
@@ -0,0 +1,6 @@
+$ git diff-tree --format=%N note
+note
+
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--notes_note b/t/t4013/diff.diff-tree_--pretty_--notes_note
new file mode 100644
index 0000000000..4d0bde601c
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--pretty_--notes_note
@@ -0,0 +1,12 @@
+$ git diff-tree --pretty --notes note
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode (file2)
+
+Notes:
+ note
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_note b/t/t4013/diff.diff-tree_--pretty_note
new file mode 100644
index 0000000000..1fa5967083
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--pretty_note
@@ -0,0 +1,9 @@
+$ git diff-tree --pretty note
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode (file2)
+
+:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M file2
+$
diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all
index 2afe91f116..3f9b872ece 100644
--- a/t/t4013/diff.log_--decorate=full_--all
+++ b/t/t4013/diff.log_--decorate=full_--all
@@ -5,12 +5,27 @@ Date: Mon Jun 26 00:06:00 2006 +0000
update mode
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (refs/heads/note)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode (file2)
+
+Notes:
+ note
+
commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange)
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:06:00 2006 +0000
Rearranged lines in dir/sub
+commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ Notes added by 'git notes add'
+
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master)
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
index d0f308ab2b..f5e20e1e14 100644
--- a/t/t4013/diff.log_--decorate_--all
+++ b/t/t4013/diff.log_--decorate_--all
@@ -5,12 +5,27 @@ Date: Mon Jun 26 00:06:00 2006 +0000
update mode
+commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (note)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ update mode (file2)
+
+Notes:
+ note
+
commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange)
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:06:00 2006 +0000
Rearranged lines in dir/sub
+commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits)
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:06:00 2006 +0000
+
+ Notes added by 'git notes add'
+
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master)
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b653dd7d44..db7e733af9 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1161,6 +1161,59 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' '
'
cat >expect <<'EOF'
+From: Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar
+ Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo
+ Bar Foo Bar Foo Bar Foo Bar <author@example.com>
+EOF
+test_expect_success 'format-patch wraps extremely long from-header (non-ASCII without Q-encoding)' '
+ echo content >>file &&
+ git add file &&
+ GIT_AUTHOR_NAME="Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar" \
+ git commit -m author-check &&
+ git format-patch --no-encode-email-headers --stdout -1 >patch &&
+ sed -n "/^From: /p; /^ /p; /^$/q" patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] Foö
+EOF
+test_expect_success 'subject lines are unencoded with --no-encode-email-headers' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "Foö" &&
+ git format-patch --no-encode-email-headers -1 --stdout >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] Foö
+EOF
+test_expect_success 'subject lines are unencoded with format.encodeEmailHeaders=false' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "Foö" &&
+ git config format.encodeEmailHeaders false &&
+ git format-patch -1 --stdout >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Subject: [PATCH] =?UTF-8?q?Fo=C3=B6?=
+EOF
+test_expect_success '--encode-email-headers overrides format.encodeEmailHeaders' '
+ echo content >>file &&
+ git add file &&
+ git commit -m "Foö" &&
+ git config format.encodeEmailHeaders false &&
+ git format-patch --encode-email-headers -1 --stdout >patch &&
+ grep ^Subject: patch >actual &&
+ test_cmp expect actual
+'
+
+cat >expect <<'EOF'
Subject: header with . in it
EOF
test_expect_success 'subject lines do not have 822 atom-quoting' '
diff --git a/t/t4057-diff-combined-paths.sh b/t/t4057-diff-combined-paths.sh
index 4f4b541658..0b78573733 100755
--- a/t/t4057-diff-combined-paths.sh
+++ b/t/t4057-diff-combined-paths.sh
@@ -14,7 +14,7 @@ diffc_verify () {
test_expect_success 'trivial merge - combine-diff empty' '
for i in $(test_seq 1 9)
do
- echo $i >$i.txt &&
+ echo $i >$i.txt &&
git add $i.txt
done &&
git commit -m "init" &&
diff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh
index 2affd7a100..0f7a6d97a8 100755
--- a/t/t4061-diff-indent.sh
+++ b/t/t4061-diff-indent.sh
@@ -17,7 +17,7 @@ compare_diff () {
# Compare blame output using the expectation for a diff as reference.
# Only look for the lines coming from non-boundary commits.
compare_blame () {
- sed -n -e "1,4d" -e "s/^\+//p" <"$1" >.tmp-1
+ sed -n -e "1,4d" -e "s/^+//p" <"$1" >.tmp-1
sed -ne "s/^[^^][^)]*) *//p" <"$2" >.tmp-2
test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
}
diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh
index 4831ad35e6..c1ed1c2fc4 100755
--- a/t/t4067-diff-partial-clone.sh
+++ b/t/t4067-diff-partial-clone.sh
@@ -131,4 +131,52 @@ test_expect_success 'diff with rename detection batches blobs' '
test_line_count = 1 done_lines
'
+test_expect_success 'diff does not fetch anything if inexact rename detection is not needed' '
+ test_when_finished "rm -rf server client trace" &&
+
+ test_create_repo server &&
+ echo a >server/a &&
+ printf "b\nb\nb\nb\nb\n" >server/b &&
+ git -C server add a b &&
+ git -C server commit -m x &&
+ mv server/b server/c &&
+ git -C server add c &&
+ git -C server commit -a -m x &&
+
+ test_config -C server uploadpack.allowfilter 1 &&
+ test_config -C server uploadpack.allowanysha1inwant 1 &&
+ git clone --bare --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+ # Ensure no fetches.
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --raw -M HEAD^ HEAD &&
+ ! test_path_exists trace
+'
+
+test_expect_success 'diff --break-rewrites fetches only if necessary, and batches blobs if it does' '
+ test_when_finished "rm -rf server client trace" &&
+
+ test_create_repo server &&
+ echo a >server/a &&
+ printf "b\nb\nb\nb\nb\n" >server/b &&
+ git -C server add a b &&
+ git -C server commit -m x &&
+ printf "c\nc\nc\nc\nc\n" >server/b &&
+ git -C server commit -a -m x &&
+
+ test_config -C server uploadpack.allowfilter 1 &&
+ test_config -C server uploadpack.allowanysha1inwant 1 &&
+ git clone --bare --filter=blob:limit=0 "file://$(pwd)/server" client &&
+
+ # Ensure no fetches.
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --raw -M HEAD^ HEAD &&
+ ! test_path_exists trace &&
+
+ # But with --break-rewrites, ensure that there is exactly 1 negotiation
+ # by checking that there is only 1 "done" line sent. ("done" marks the
+ # end of negotiation.)
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C client diff --break-rewrites --raw -M HEAD^ HEAD &&
+ grep "git> done" trace >done_lines &&
+ test_line_count = 1 done_lines
+'
+
test_done
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index 971a5a7512..0ca29821ec 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -52,6 +52,13 @@ test_fix () {
# find touched lines
$DIFF file target | sed -n -e "s/^> //p" >fixed
+ # busybox's diff(1) doesn't output normal format
+ if ! test -s fixed
+ then
+ $DIFF -u file target |
+ grep -v '^+++ target' |
+ sed -ne "/^+/s/+//p" >fixed
+ fi
# the changed lines are all expected to change
fixed_cnt=$(wc -l <fixed)
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index cb45271457..bda4586a79 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -166,7 +166,7 @@ test_expect_success setup '
test_tick &&
git commit -m third &&
- git format-patch --stdout first >patch2 &&
+ git format-patch --stdout first >patch2 &&
git checkout -b lorem &&
sed -n -e "11,\$p" msg >file &&
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 5eeb739f3e..f1ea7d97f5 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -742,7 +742,23 @@ test_expect_success 'decorate-refs with glob' '
octopus-a (octopus-a)
reach
EOF
+ cat >expect.no-decorate <<-\EOF &&
+ Merge-tag-reach
+ Merge-tags-octopus-a-and-octopus-b
+ seventh
+ octopus-b
+ octopus-a
+ reach
+ EOF
+ git log -n6 --decorate=short --pretty="tformat:%f%d" \
+ --decorate-refs="heads/octopus*" >actual &&
+ test_cmp expect.decorate actual &&
git log -n6 --decorate=short --pretty="tformat:%f%d" \
+ --decorate-refs-exclude="heads/octopus*" \
+ --decorate-refs="heads/octopus*" >actual &&
+ test_cmp expect.no-decorate actual &&
+ git -c log.excludeDecoration="heads/octopus*" log \
+ -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs="heads/octopus*" >actual &&
test_cmp expect.decorate actual
'
@@ -787,6 +803,9 @@ test_expect_success 'decorate-refs-exclude with glob' '
EOF
git log -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs-exclude="heads/octopus*" >actual &&
+ test_cmp expect.decorate actual &&
+ git -c log.excludeDecoration="heads/octopus*" log \
+ -n6 --decorate=short --pretty="tformat:%f%d" >actual &&
test_cmp expect.decorate actual
'
@@ -801,6 +820,9 @@ test_expect_success 'decorate-refs-exclude without globs' '
EOF
git log -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs-exclude="tags/reach" >actual &&
+ test_cmp expect.decorate actual &&
+ git -c log.excludeDecoration="tags/reach" log \
+ -n6 --decorate=short --pretty="tformat:%f%d" >actual &&
test_cmp expect.decorate actual
'
@@ -816,11 +838,19 @@ test_expect_success 'multiple decorate-refs-exclude' '
git log -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs-exclude="heads/octopus*" \
--decorate-refs-exclude="tags/reach" >actual &&
+ test_cmp expect.decorate actual &&
+ git -c log.excludeDecoration="heads/octopus*" \
+ -c log.excludeDecoration="tags/reach" log \
+ -n6 --decorate=short --pretty="tformat:%f%d" >actual &&
+ test_cmp expect.decorate actual &&
+ git -c log.excludeDecoration="heads/octopus*" log \
+ --decorate-refs-exclude="tags/reach" \
+ -n6 --decorate=short --pretty="tformat:%f%d" >actual &&
test_cmp expect.decorate actual
'
test_expect_success 'decorate-refs and decorate-refs-exclude' '
- cat >expect.decorate <<-\EOF &&
+ cat >expect.no-decorate <<-\EOF &&
Merge-tag-reach (master)
Merge-tags-octopus-a-and-octopus-b
seventh
@@ -831,6 +861,21 @@ test_expect_success 'decorate-refs and decorate-refs-exclude' '
git log -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs="heads/*" \
--decorate-refs-exclude="heads/oc*" >actual &&
+ test_cmp expect.no-decorate actual
+'
+
+test_expect_success 'deocrate-refs and log.excludeDecoration' '
+ cat >expect.decorate <<-\EOF &&
+ Merge-tag-reach (master)
+ Merge-tags-octopus-a-and-octopus-b
+ seventh
+ octopus-b (octopus-b)
+ octopus-a (octopus-a)
+ reach (reach)
+ EOF
+ git -c log.excludeDecoration="heads/oc*" log \
+ --decorate-refs="heads/*" \
+ -n6 --decorate=short --pretty="tformat:%f%d" >actual &&
test_cmp expect.decorate actual
'
@@ -846,6 +891,10 @@ test_expect_success 'decorate-refs-exclude and simplify-by-decoration' '
git log -n6 --decorate=short --pretty="tformat:%f%d" \
--decorate-refs-exclude="*octopus*" \
--simplify-by-decoration >actual &&
+ test_cmp expect.decorate actual &&
+ git -c log.excludeDecoration="*octopus*" log \
+ -n6 --decorate=short --pretty="tformat:%f%d" \
+ --simplify-by-decoration >actual &&
test_cmp expect.decorate actual
'
diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh
index 4c8f3b8e1b..6cdbe4747a 100755
--- a/t/t4208-log-magic-pathspec.sh
+++ b/t/t4208-log-magic-pathspec.sh
@@ -55,6 +55,10 @@ test_expect_success '"git log -- :/a" should not be ambiguous' '
git log -- :/a
'
+test_expect_success '"git log :/any/path/" should not segfault' '
+ test_must_fail git log :/any/path/
+'
+
# This differs from the ":/a" check above in that :/in looks like a pathspec,
# but doesn't match an actual file.
test_expect_success '"git log :/in" should not be ambiguous' '
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
new file mode 100755
index 0000000000..c7011f33e2
--- /dev/null
+++ b/t/t4216-log-bloom.sh
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+test_description='git log for a path with Bloom filters'
+. ./test-lib.sh
+
+GIT_TEST_COMMIT_GRAPH=0
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+
+test_expect_success 'setup test - repo, commits, commit graph, log outputs' '
+ git init &&
+ mkdir A A/B A/B/C &&
+ test_commit c1 A/file1 &&
+ test_commit c2 A/B/file2 &&
+ test_commit c3 A/B/C/file3 &&
+ test_commit c4 A/file1 &&
+ test_commit c5 A/B/file2 &&
+ test_commit c6 A/B/C/file3 &&
+ test_commit c7 A/file1 &&
+ test_commit c8 A/B/file2 &&
+ test_commit c9 A/B/C/file3 &&
+ test_commit c10 file_to_be_deleted &&
+ git checkout -b side HEAD~4 &&
+ test_commit side-1 file4 &&
+ git checkout master &&
+ git merge side &&
+ test_commit c11 file5 &&
+ mv file5 file5_renamed &&
+ git add file5_renamed &&
+ git commit -m "rename" &&
+ rm file_to_be_deleted &&
+ git add . &&
+ git commit -m "file removed" &&
+ git commit-graph write --reachable --changed-paths
+'
+graph_read_expect () {
+ NUM_CHUNKS=5
+ cat >expect <<- EOF
+ header: 43475048 1 1 $NUM_CHUNKS 0
+ num_commits: $1
+ chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data
+ EOF
+ test-tool read-graph >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'commit-graph write wrote out the bloom chunks' '
+ graph_read_expect 15
+'
+
+# Turn off any inherited trace2 settings for this test.
+sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT
+sane_unset GIT_TRACE2_PERF_BRIEF
+sane_unset GIT_TRACE2_CONFIG_PARAMS
+
+setup () {
+ rm "$TRASH_DIRECTORY/trace.perf"
+ git -c core.commitGraph=false log --pretty="format:%s" $1 >log_wo_bloom &&
+ GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.perf" git -c core.commitGraph=true log --pretty="format:%s" $1 >log_w_bloom
+}
+
+test_bloom_filters_used () {
+ log_args=$1
+ bloom_trace_prefix="statistics:{\"filter_not_present\":0,\"zero_length_filter\":0,\"maybe\""
+ setup "$log_args" &&
+ grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
+ test_cmp log_wo_bloom log_w_bloom &&
+ test_path_is_file "$TRASH_DIRECTORY/trace.perf"
+}
+
+test_bloom_filters_not_used () {
+ log_args=$1
+ setup "$log_args" &&
+ !(grep -q "statistics:{\"filter_not_present\":" "$TRASH_DIRECTORY/trace.perf") &&
+ test_cmp log_wo_bloom log_w_bloom
+}
+
+for path in A A/B A/B/C A/file1 A/B/file2 A/B/C/file3 file4 file5 file5_renamed file_to_be_deleted
+do
+ for option in "" \
+ "--all" \
+ "--full-history" \
+ "--full-history --simplify-merges" \
+ "--simplify-merges" \
+ "--simplify-by-decoration" \
+ "--follow" \
+ "--first-parent" \
+ "--topo-order" \
+ "--date-order" \
+ "--author-date-order" \
+ "--ancestry-path side..master"
+ do
+ test_expect_success "git log option: $option for path: $path" '
+ test_bloom_filters_used "$option -- $path"
+ '
+ done
+done
+
+test_expect_success 'git log -- folder works with and without the trailing slash' '
+ test_bloom_filters_used "-- A" &&
+ test_bloom_filters_used "-- A/"
+'
+
+test_expect_success 'git log for path that does not exist. ' '
+ test_bloom_filters_used "-- path_does_not_exist"
+'
+
+test_expect_success 'git log with --walk-reflogs does not use Bloom filters' '
+ test_bloom_filters_not_used "--walk-reflogs -- A"
+'
+
+test_expect_success 'git log -- multiple path specs does not use Bloom filters' '
+ test_bloom_filters_not_used "-- file4 A/file1"
+'
+
+test_expect_success 'git log with wildcard that resolves to a single path uses Bloom filters' '
+ test_bloom_filters_used "-- *4" &&
+ test_bloom_filters_used "-- *renamed"
+'
+
+test_expect_success 'git log with wildcard that resolves to a multiple paths does not uses Bloom filters' '
+ test_bloom_filters_not_used "-- *" &&
+ test_bloom_filters_not_used "-- file*"
+'
+
+test_expect_success 'setup - add commit-graph to the chain without Bloom filters' '
+ test_commit c14 A/anotherFile2 &&
+ test_commit c15 A/B/anotherFile2 &&
+ test_commit c16 A/B/C/anotherFile2 &&
+ GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0 git commit-graph write --reachable --split &&
+ test_line_count = 2 .git/objects/info/commit-graphs/commit-graph-chain
+'
+
+test_expect_success 'Do not use Bloom filters if the latest graph does not have Bloom filters.' '
+ test_bloom_filters_not_used "-- A/B"
+'
+
+test_expect_success 'setup - add commit-graph to the chain with Bloom filters' '
+ test_commit c17 A/anotherFile3 &&
+ git commit-graph write --reachable --changed-paths --split &&
+ test_line_count = 3 .git/objects/info/commit-graphs/commit-graph-chain
+'
+
+test_bloom_filters_used_when_some_filters_are_missing () {
+ log_args=$1
+ bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"zero_length_filter\":0,\"maybe\":8,\"definitely_not\":6"
+ setup "$log_args" &&
+ grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
+ test_cmp log_wo_bloom log_w_bloom
+}
+
+test_expect_success 'Use Bloom filters if they exist in the latest but not all commit graphs in the chain.' '
+ test_bloom_filters_used_when_some_filters_are_missing "-- A/B"
+'
+
+test_done \ No newline at end of file
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
index fd3bdbfe2c..daf01c309d 100755
--- a/t/t4254-am-corrupt.sh
+++ b/t/t4254-am-corrupt.sh
@@ -3,6 +3,37 @@
test_description='git am with corrupt input'
. ./test-lib.sh
+make_mbox_with_nul () {
+ space=' '
+ q_nul_in_subject=
+ q_nul_in_body=
+ while test $# -ne 0
+ do
+ case "$1" in
+ subject) q_nul_in_subject='=00' ;;
+ body) q_nul_in_body='=00' ;;
+ esac &&
+ shift
+ done &&
+ cat <<-EOF
+ From ec7364544f690c560304f5a5de9428ea3b978b26 Mon Sep 17 00:00:00 2001
+ From: A U Thor <author@example.com>
+ Date: Sun, 19 Apr 2020 13:42:07 +0700
+ Subject: [PATCH] =?ISO-8859-1?q?=C4=CB${q_nul_in_subject}=D1=CF=D6?=
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset=ISO-8859-1
+ Content-Transfer-Encoding: quoted-printable
+
+ abc${q_nul_in_body}def
+ ---
+ diff --git a/afile b/afile
+ new file mode 100644
+ index 0000000000..e69de29bb2
+ --$space
+ 2.26.1
+ EOF
+}
+
test_expect_success setup '
# Note the missing "+++" line:
cat >bad-patch.diff <<-\EOF &&
@@ -25,13 +56,27 @@ test_expect_success setup '
# fatal: unable to write file '(null)' mode 100644: Bad address
# Also, it had the unwanted side-effect of deleting f.
test_expect_success 'try to apply corrupted patch' '
- test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual
-'
-
-test_expect_success 'compare diagnostic; ensure file is still here' '
+ test_when_finished "git am --abort" &&
+ test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual &&
echo "error: git diff header lacks filename information (line 4)" >expected &&
test_path_is_file f &&
test_i18ncmp expected actual
'
+test_expect_success "NUL in commit message's body" '
+ test_when_finished "git am --abort" &&
+ make_mbox_with_nul body >body.patch &&
+ test_must_fail git am body.patch 2>err &&
+ grep "a NUL byte in commit log message not allowed" err
+'
+
+test_expect_success "NUL in commit message's header" "
+ test_when_finished 'git am --abort' &&
+ make_mbox_with_nul subject >subject.patch &&
+ test_must_fail git mailinfo msg patch <subject.patch 2>err &&
+ grep \"a NUL byte in 'Subject' is not allowed\" err &&
+ test_must_fail git am subject.patch 2>err &&
+ grep \"a NUL byte in 'Subject' is not allowed\" err
+"
+
test_done
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index 106eddbd85..3b76d2eb65 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -7,12 +7,12 @@ test_description='git archive --format=zip test'
SUBSTFORMAT=%H%n
test_lazy_prereq UNZIP_SYMLINKS '
- (
- mkdir unzip-symlinks &&
- cd unzip-symlinks &&
- "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip &&
- test -h symlink
- )
+ "$GIT_UNZIP" "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip &&
+ test -h symlink
+'
+
+test_lazy_prereq UNZIP_CONVERT '
+ "$GIT_UNZIP" -a "$TEST_DIRECTORY"/t5003/infozip-symlinks.zip
'
check_zip() {
@@ -39,33 +39,33 @@ check_zip() {
extracted=${dir_with_prefix}a
original=a
- test_expect_success UNZIP " extract ZIP archive with EOL conversion" '
+ test_expect_success UNZIP_CONVERT " extract ZIP archive with EOL conversion" '
(mkdir $dir && cd $dir && "$GIT_UNZIP" -a ../$zipfile)
'
- test_expect_success UNZIP " validate that text files are converted" "
+ test_expect_success UNZIP_CONVERT " validate that text files are converted" "
test_cmp_bin $extracted/text.cr $extracted/text.crlf &&
test_cmp_bin $extracted/text.cr $extracted/text.lf
"
- test_expect_success UNZIP " validate that binary files are unchanged" "
+ test_expect_success UNZIP_CONVERT " validate that binary files are unchanged" "
test_cmp_bin $original/binary.cr $extracted/binary.cr &&
test_cmp_bin $original/binary.crlf $extracted/binary.crlf &&
test_cmp_bin $original/binary.lf $extracted/binary.lf
"
- test_expect_success UNZIP " validate that diff files are converted" "
+ test_expect_success UNZIP_CONVERT " validate that diff files are converted" "
test_cmp_bin $extracted/diff.cr $extracted/diff.crlf &&
test_cmp_bin $extracted/diff.cr $extracted/diff.lf
"
- test_expect_success UNZIP " validate that -diff files are unchanged" "
+ test_expect_success UNZIP_CONVERT " validate that -diff files are unchanged" "
test_cmp_bin $original/nodiff.cr $extracted/nodiff.cr &&
test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf &&
test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf
"
- test_expect_success UNZIP " validate that custom diff is unchanged " "
+ test_expect_success UNZIP_CONVERT " validate that custom diff is unchanged " "
test_cmp_bin $original/custom.cr $extracted/custom.cr &&
test_cmp_bin $original/custom.crlf $extracted/custom.crlf &&
test_cmp_bin $original/custom.lf $extracted/custom.lf
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 9bf920ae17..39e2918a32 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -3,6 +3,8 @@
test_description='commit graph'
. ./test-lib.sh
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+
test_expect_success 'setup full repo' '
mkdir full &&
cd "$TRASH_DIRECTORY/full" &&
@@ -43,7 +45,7 @@ test_expect_success 'create commits and repack' '
test_expect_success 'exit with correct error on bad input to --stdin-commits' '
cd "$TRASH_DIRECTORY/full" &&
echo HEAD | test_expect_code 1 git commit-graph write --stdin-commits 2>stderr &&
- test_i18ngrep "invalid commit object id" stderr &&
+ test_i18ngrep "unexpected non-hex object ID: HEAD" stderr &&
# valid tree OID, but not a commit OID
git rev-parse HEAD^{tree} | test_expect_code 1 git commit-graph write --stdin-commits 2>stderr &&
test_i18ngrep "invalid commit object id" stderr
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 43a7a66c9d..030a7222b2 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -42,10 +42,15 @@ test_expect_success 'setup' '
EOF
'
-test_expect_success 'write midx with no packs' '
- test_when_finished rm -f pack/multi-pack-index &&
- git multi-pack-index --object-dir=. write &&
- midx_read_expect 0 0 4 .
+test_expect_success "don't write midx with no packs" '
+ test_must_fail git multi-pack-index --object-dir=. write &&
+ test_path_is_missing pack/multi-pack-index
+'
+
+test_expect_success "Warn if a midx contains no oid" '
+ cp "$TEST_DIRECTORY"/t5319/no-objects.midx $objdir/pack/multi-pack-index &&
+ test_must_fail git multi-pack-index verify &&
+ rm $objdir/pack/multi-pack-index
'
generate_objects () {
@@ -521,10 +526,10 @@ test_expect_success 'repack with minimum size does not alter existing packs' '
cd dup &&
rm -rf .git/objects/pack &&
mv .git/objects/pack-backup .git/objects/pack &&
- touch -m -t 201901010000 .git/objects/pack/pack-D* &&
- touch -m -t 201901010001 .git/objects/pack/pack-C* &&
- touch -m -t 201901010002 .git/objects/pack/pack-B* &&
- touch -m -t 201901010003 .git/objects/pack/pack-A* &&
+ test-tool chmtime =-5 .git/objects/pack/pack-D* &&
+ test-tool chmtime =-4 .git/objects/pack/pack-C* &&
+ test-tool chmtime =-3 .git/objects/pack/pack-B* &&
+ test-tool chmtime =-2 .git/objects/pack/pack-A* &&
ls .git/objects/pack >expect &&
MINSIZE=$(test-tool path-utils file-size .git/objects/pack/*pack | sort -n | head -n 1) &&
git multi-pack-index repack --batch-size=$MINSIZE &&
diff --git a/t/t5319/no-objects.midx b/t/t5319/no-objects.midx
new file mode 100644
index 0000000000..e466b8e086
--- /dev/null
+++ b/t/t5319/no-objects.midx
Binary files differ
diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
index 53b2e6b455..594edb7307 100755
--- a/t/t5324-split-commit-graph.sh
+++ b/t/t5324-split-commit-graph.sh
@@ -4,6 +4,7 @@ test_description='split commit graph'
. ./test-lib.sh
GIT_TEST_COMMIT_GRAPH=0
+GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
test_expect_success 'setup repo' '
git init &&
@@ -210,8 +211,14 @@ test_expect_success 'test merge stragety constants' '
git config core.commitGraph true &&
test_line_count = 2 $graphdir/commit-graph-chain &&
test_commit 15 &&
- git commit-graph write --reachable --split --size-multiple=10 --expire-time=1980-01-01 &&
+ touch $graphdir/to-delete.graph $graphdir/to-keep.graph &&
+ test-tool chmtime =1546362000 $graphdir/to-delete.graph &&
+ test-tool chmtime =1546362001 $graphdir/to-keep.graph &&
+ git commit-graph write --reachable --split --size-multiple=10 \
+ --expire-time="2019-01-01 12:00 -05:00" &&
test_line_count = 1 $graphdir/commit-graph-chain &&
+ test_path_is_missing $graphdir/to-delete.graph &&
+ test_path_is_file $graphdir/to-keep.graph &&
ls $graphdir/graph-*.graph >graph-files &&
test_line_count = 3 graph-files
) &&
@@ -351,4 +358,47 @@ test_expect_success 'split across alternate where alternate is not split' '
test_cmp commit-graph .git/objects/info/commit-graph
'
+test_expect_success '--split=no-merge always writes an incremental' '
+ test_when_finished rm -rf a b &&
+ rm -rf $graphdir $infodir/commit-graph &&
+ git reset --hard commits/2 &&
+ git rev-list HEAD~1 >a &&
+ git rev-list HEAD >b &&
+ git commit-graph write --split --stdin-commits <a &&
+ git commit-graph write --split=no-merge --stdin-commits <b &&
+ test_line_count = 2 $graphdir/commit-graph-chain
+'
+
+test_expect_success '--split=replace replaces the chain' '
+ rm -rf $graphdir $infodir/commit-graph &&
+ git reset --hard commits/3 &&
+ git rev-list -1 HEAD~2 >a &&
+ git rev-list -1 HEAD~1 >b &&
+ git rev-list -1 HEAD >c &&
+ git commit-graph write --split=no-merge --stdin-commits <a &&
+ git commit-graph write --split=no-merge --stdin-commits <b &&
+ git commit-graph write --split=no-merge --stdin-commits <c &&
+ test_line_count = 3 $graphdir/commit-graph-chain &&
+ git commit-graph write --stdin-commits --split=replace <b &&
+ test_path_is_missing $infodir/commit-graph &&
+ test_path_is_file $graphdir/commit-graph-chain &&
+ ls $graphdir/graph-*.graph >graph-files &&
+ test_line_count = 1 graph-files &&
+ verify_chain_files_exist $graphdir &&
+ graph_read_expect 2
+'
+
+test_expect_success ULIMIT_FILE_DESCRIPTORS 'handles file descriptor exhaustion' '
+ git init ulimit &&
+ (
+ cd ulimit &&
+ for i in $(test_seq 64)
+ do
+ test_commit $i &&
+ test_might_fail run_with_limited_open_files git commit-graph write \
+ --split=no-merge --reachable || return 1
+ done
+ )
+'
+
test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index baa1a99f45..52dd1a688c 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -385,6 +385,54 @@ test_expect_success 'clone shallow with packed refs' '
test_cmp count8.expected count8.actual
'
+test_expect_success 'in_vain not triggered before first ACK' '
+ rm -rf myserver myclient trace &&
+ git init myserver &&
+ test_commit -C myserver foo &&
+ git clone "file://$(pwd)/myserver" myclient &&
+
+ # MAX_IN_VAIN is 256. Because of batching, the client will send 496
+ # (16+32+64+128+256) commits, not 256, before giving up. So create 496
+ # irrelevant commits.
+ test_commit_bulk -C myclient 496 &&
+
+ # The new commit that the client wants to fetch.
+ test_commit -C myserver bar &&
+
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C myclient fetch --progress origin &&
+ test_i18ngrep "Total 3 " trace
+'
+
+test_expect_success 'in_vain resetted upon ACK' '
+ rm -rf myserver myclient trace &&
+ git init myserver &&
+
+ # Linked list of commits on master. The first is common; the rest are
+ # not.
+ test_commit -C myserver first_master_commit &&
+ git clone "file://$(pwd)/myserver" myclient &&
+ test_commit_bulk -C myclient 255 &&
+
+ # Another linked list of commits on anotherbranch with no connection to
+ # master. The first is common; the rest are not.
+ git -C myserver checkout --orphan anotherbranch &&
+ test_commit -C myserver first_anotherbranch_commit &&
+ git -C myclient fetch origin anotherbranch:refs/heads/anotherbranch &&
+ git -C myclient checkout anotherbranch &&
+ test_commit_bulk -C myclient 255 &&
+
+ # The new commit that the client wants to fetch.
+ git -C myserver checkout master &&
+ test_commit -C myserver to_fetch &&
+
+ # The client will send (as "have"s) all 256 commits in anotherbranch
+ # first. The 256th commit is common between the client and the server,
+ # and should reset in_vain. This allows negotiation to continue until
+ # the client reports that first_anotherbranch_commit is common.
+ GIT_TRACE_PACKET="$(pwd)/trace" git -C myclient fetch --progress origin master &&
+ test_i18ngrep "Total 3 " trace
+'
+
test_expect_success 'fetch in shallow repo unreachable shallow objects' '
(
git clone --bare --branch B --single-branch "file://$(pwd)/." no-reflog &&
diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh
index 645b4c78d3..a32efe2b6c 100755
--- a/t/t5504-fetch-receive-strict.sh
+++ b/t/t5504-fetch-receive-strict.sh
@@ -65,6 +65,7 @@ test_expect_success 'fetch with transfer.fsckobjects' '
cat >exp <<EOF
To dst
! refs/heads/master:refs/heads/test [remote rejected] (missing necessary objects)
+Done
EOF
test_expect_success 'push without strict' '
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 04b35402c7..e98c3a0174 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -4,6 +4,14 @@ test_description='git ls-remote'
. ./test-lib.sh
+generate_references () {
+ for ref
+ do
+ oid=$(git rev-parse "$ref") &&
+ printf '%s\t%s\n' "$oid" "$ref" || return 1
+ done
+}
+
test_expect_success setup '
>file &&
git add file &&
@@ -13,11 +21,11 @@ test_expect_success setup '
git tag mark1.1 &&
git tag mark1.2 &&
git tag mark1.10 &&
- git show-ref --tags -d | sed -e "s/ / /" >expected.tag &&
- (
- echo "$(git rev-parse HEAD) HEAD" &&
- git show-ref -d | sed -e "s/ / /"
- ) >expected.all &&
+ git show-ref --tags -d >expected.tag.raw &&
+ sed -e "s/ / /" expected.tag.raw >expected.tag &&
+ generate_references HEAD >expected.all &&
+ git show-ref -d >refs &&
+ sed -e "s/ / /" refs >>expected.all &&
git remote add self "$(pwd)/.git"
'
@@ -43,34 +51,31 @@ test_expect_success 'ls-remote self' '
'
test_expect_success 'ls-remote --sort="version:refname" --tags self' '
- cat >expect <<-EOF &&
- $(git rev-parse mark) refs/tags/mark
- $(git rev-parse mark1.1) refs/tags/mark1.1
- $(git rev-parse mark1.2) refs/tags/mark1.2
- $(git rev-parse mark1.10) refs/tags/mark1.10
- EOF
+ generate_references \
+ refs/tags/mark \
+ refs/tags/mark1.1 \
+ refs/tags/mark1.2 \
+ refs/tags/mark1.10 >expect &&
git ls-remote --sort="version:refname" --tags self >actual &&
test_cmp expect actual
'
test_expect_success 'ls-remote --sort="-version:refname" --tags self' '
- cat >expect <<-EOF &&
- $(git rev-parse mark1.10) refs/tags/mark1.10
- $(git rev-parse mark1.2) refs/tags/mark1.2
- $(git rev-parse mark1.1) refs/tags/mark1.1
- $(git rev-parse mark) refs/tags/mark
- EOF
+ generate_references \
+ refs/tags/mark1.10 \
+ refs/tags/mark1.2 \
+ refs/tags/mark1.1 \
+ refs/tags/mark >expect &&
git ls-remote --sort="-version:refname" --tags self >actual &&
test_cmp expect actual
'
test_expect_success 'ls-remote --sort="-refname" --tags self' '
- cat >expect <<-EOF &&
- $(git rev-parse mark1.2) refs/tags/mark1.2
- $(git rev-parse mark1.10) refs/tags/mark1.10
- $(git rev-parse mark1.1) refs/tags/mark1.1
- $(git rev-parse mark) refs/tags/mark
- EOF
+ generate_references \
+ refs/tags/mark1.2 \
+ refs/tags/mark1.10 \
+ refs/tags/mark1.1 \
+ refs/tags/mark >expect &&
git ls-remote --sort="-refname" --tags self >actual &&
test_cmp expect actual
'
@@ -92,7 +97,7 @@ test_expect_success 'use "origin" when no remote specified' '
test_expect_success 'suppress "From <url>" with -q' '
git ls-remote -q 2>actual_err &&
- test_must_fail test_cmp exp_err actual_err
+ ! test_cmp exp_err actual_err
'
test_expect_success 'use branch.<name>.remote if possible' '
@@ -180,8 +185,8 @@ do
test_config $configsection.hiderefs refs/tags &&
git ls-remote . >actual &&
test_unconfig $configsection.hiderefs &&
- git ls-remote . |
- sed -e "/ refs\/tags\//d" >expect &&
+ git ls-remote . >expect.raw &&
+ sed -e "/ refs\/tags\//d" expect.raw >expect &&
test_cmp expect actual
'
@@ -212,17 +217,18 @@ test_expect_success 'protocol v2 supports hiderefs' '
test_expect_success 'ls-remote --symref' '
git fetch origin &&
- cat >expect <<-EOF &&
- ref: refs/heads/master HEAD
- $(git rev-parse HEAD) HEAD
- $(git rev-parse refs/heads/master) refs/heads/master
- $(git rev-parse HEAD) refs/remotes/origin/HEAD
- $(git rev-parse refs/remotes/origin/master) refs/remotes/origin/master
- $(git rev-parse refs/tags/mark) refs/tags/mark
- $(git rev-parse refs/tags/mark1.1) refs/tags/mark1.1
- $(git rev-parse refs/tags/mark1.10) refs/tags/mark1.10
- $(git rev-parse refs/tags/mark1.2) refs/tags/mark1.2
- EOF
+ echo "ref: refs/heads/master HEAD" >expect &&
+ generate_references \
+ HEAD \
+ refs/heads/master >>expect &&
+ oid=$(git rev-parse HEAD) &&
+ echo "$oid refs/remotes/origin/HEAD" >>expect &&
+ generate_references \
+ refs/remotes/origin/master \
+ refs/tags/mark \
+ refs/tags/mark1.1 \
+ refs/tags/mark1.10 \
+ refs/tags/mark1.2 >>expect &&
# Protocol v2 supports sending symrefs for refs other than HEAD, so use
# protocol v0 here.
GIT_TEST_PROTOCOL_VERSION=0 git ls-remote --symref >actual &&
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 9ff041a093..9c6218f568 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1066,6 +1066,7 @@ test_expect_success 'push --porcelain rejected' '
echo >.git/foo "To testrepo" &&
echo >>.git/foo "! refs/heads/master:refs/heads/master [remote rejected] (branch is currently checked out)" &&
+ echo >>.git/foo "Done" &&
test_must_fail git push >.git/bar --porcelain testrepo refs/heads/master:refs/heads/master &&
test_cmp .git/foo .git/bar
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 2f86fca042..37535d63a9 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -10,11 +10,13 @@ modify () {
}
test_pull_autostash () {
+ expect_parent_num="$1" &&
+ shift &&
git reset --hard before-rebase &&
echo dirty >new_file &&
git add new_file &&
git pull "$@" . copy &&
- test_cmp_rev HEAD^ copy &&
+ test_cmp_rev HEAD^"$expect_parent_num" copy &&
echo dirty >expect &&
test_cmp expect new_file &&
echo "modified again" >expect &&
@@ -26,7 +28,7 @@ test_pull_autostash_fail () {
echo dirty >new_file &&
git add new_file &&
test_must_fail git pull "$@" . copy 2>err &&
- test_i18ngrep "uncommitted changes." err
+ test_i18ngrep "\(uncommitted changes.\)\|\(overwritten by merge:\)" err
}
test_expect_success setup '
@@ -369,22 +371,22 @@ test_expect_success '--rebase fails with multiple branches' '
test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' '
test_config rebase.autostash true &&
- test_pull_autostash --rebase
+ test_pull_autostash 1 --rebase
'
test_expect_success 'pull --rebase --autostash & rebase.autostash=true' '
test_config rebase.autostash true &&
- test_pull_autostash --rebase --autostash
+ test_pull_autostash 1 --rebase --autostash
'
test_expect_success 'pull --rebase --autostash & rebase.autostash=false' '
test_config rebase.autostash false &&
- test_pull_autostash --rebase --autostash
+ test_pull_autostash 1 --rebase --autostash
'
test_expect_success 'pull --rebase --autostash & rebase.autostash unset' '
test_unconfig rebase.autostash &&
- test_pull_autostash --rebase --autostash
+ test_pull_autostash 1 --rebase --autostash
'
test_expect_success 'pull --rebase --no-autostash & rebase.autostash=true' '
@@ -402,13 +404,40 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '
test_pull_autostash_fail --rebase --no-autostash
'
-for i in --autostash --no-autostash
-do
- test_expect_success "pull $i (without --rebase) is illegal" '
- test_must_fail git pull $i . copy 2>err &&
- test_i18ngrep "only valid with --rebase" err
- '
-done
+test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
+ test_config merge.autostash true &&
+ test_pull_autostash 2
+'
+
+test_expect_success 'pull --autostash & merge.autostash=true' '
+ test_config merge.autostash true &&
+ test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --autostash & merge.autostash=false' '
+ test_config merge.autostash false &&
+ test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --autostash & merge.autostash unset' '
+ test_unconfig merge.autostash &&
+ test_pull_autostash 2 --autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash=true' '
+ test_config merge.autostash true &&
+ test_pull_autostash_fail --no-autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash=false' '
+ test_config merge.autostash false &&
+ test_pull_autostash_fail --no-autostash
+'
+
+test_expect_success 'pull --no-autostash & merge.autostash unset' '
+ test_unconfig merge.autostash &&
+ test_pull_autostash_fail --no-autostash
+'
test_expect_success 'pull.rebase' '
git reset --hard before-rebase &&
@@ -422,7 +451,7 @@ test_expect_success 'pull.rebase' '
test_expect_success 'pull --autostash & pull.rebase=true' '
test_config pull.rebase true &&
- test_pull_autostash --autostash
+ test_pull_autostash 1 --autostash
'
test_expect_success 'pull --no-autostash & pull.rebase=true' '
diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh
index 4f681dbbe1..d427a2d7f7 100755
--- a/t/t5537-fetch-shallow.sh
+++ b/t/t5537-fetch-shallow.sh
@@ -16,7 +16,7 @@ test_expect_success 'setup' '
commit 3 &&
commit 4 &&
git config --global transfer.fsckObjects true &&
- test_oid_cache <<-EOF
+ test_oid_cache <<-\EOF
perl sha1:s/0034shallow %s/0036unshallow %s/
perl sha256:s/004cshallow %s/004eunshallow %s/
EOF
@@ -25,10 +25,7 @@ test_expect_success 'setup' '
test_expect_success 'setup shallow clone' '
git clone --no-local --depth=2 .git shallow &&
git --git-dir=shallow/.git log --format=%s >actual &&
- cat <<EOF >expect &&
-4
-3
-EOF
+ test_write_lines 4 3 >expect &&
test_cmp expect actual
'
@@ -38,10 +35,7 @@ test_expect_success 'clone from shallow clone' '
cd shallow2 &&
git fsck &&
git log --format=%s >actual &&
- cat <<EOF >expect &&
-4
-3
-EOF
+ test_write_lines 4 3 >expect &&
test_cmp expect actual
)
'
@@ -56,11 +50,7 @@ test_expect_success 'fetch from shallow clone' '
git fetch &&
git fsck &&
git log --format=%s origin/master >actual &&
- cat <<EOF >expect &&
-5
-4
-3
-EOF
+ test_write_lines 5 4 3 >expect &&
test_cmp expect actual
)
'
@@ -75,10 +65,7 @@ test_expect_success 'fetch --depth from shallow clone' '
git fetch --depth=2 &&
git fsck &&
git log --format=%s origin/master >actual &&
- cat <<EOF >expect &&
-6
-5
-EOF
+ test_write_lines 6 5 >expect &&
test_cmp expect actual
)
'
@@ -89,12 +76,7 @@ test_expect_success 'fetch --unshallow from shallow clone' '
git fetch --unshallow &&
git fsck &&
git log --format=%s origin/master >actual &&
- cat <<EOF >expect &&
-6
-5
-4
-3
-EOF
+ test_write_lines 6 5 4 3 >expect &&
test_cmp expect actual
)
'
@@ -111,15 +93,10 @@ test_expect_success 'fetch something upstream has but hidden by clients shallow
git fetch ../.git +refs/heads/master:refs/remotes/top/master &&
git fsck &&
git log --format=%s top/master >actual &&
- cat <<EOF >expect &&
-add-1-back
-4
-3
-EOF
+ test_write_lines add-1-back 4 3 >expect &&
test_cmp expect actual
) &&
git --git-dir=shallow2/.git cat-file blob $(echo 1|git hash-object --stdin) >/dev/null
-
'
test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
@@ -131,16 +108,12 @@ test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
git init notshallow &&
(
cd notshallow &&
- git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/*&&
+ git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
git for-each-ref --format="%(refname)" >actual.refs &&
- cat <<EOF >expect.refs &&
-refs/remotes/shallow/no-shallow
-EOF
+ echo refs/remotes/shallow/no-shallow >expect.refs &&
test_cmp expect.refs actual.refs &&
git log --format=%s shallow/no-shallow >actual &&
- cat <<EOF >expect &&
-no-shallow
-EOF
+ echo no-shallow >expect &&
test_cmp expect actual
)
'
@@ -158,21 +131,44 @@ test_expect_success 'fetch --update-shallow' '
git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
git fsck &&
git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
- cat <<EOF >expect.refs &&
-refs/remotes/shallow/master
-refs/remotes/shallow/no-shallow
-refs/tags/heavy-tag
-refs/tags/light-tag
-EOF
+ cat <<-\EOF >expect.refs &&
+ refs/remotes/shallow/master
+ refs/remotes/shallow/no-shallow
+ refs/tags/heavy-tag
+ refs/tags/light-tag
+ EOF
+ test_cmp expect.refs actual.refs &&
+ git log --format=%s shallow/master >actual &&
+ test_write_lines 7 6 5 4 3 >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'fetch --update-shallow (with fetch.writeCommitGraph)' '
+ (
+ cd shallow &&
+ git checkout master &&
+ commit 8 &&
+ git tag -m foo heavy-tag-for-graph HEAD^ &&
+ git tag light-tag-for-graph HEAD^:tracked
+ ) &&
+ test_config -C notshallow fetch.writeCommitGraph true &&
+ (
+ cd notshallow &&
+ git fetch --update-shallow ../shallow/.git refs/heads/*:refs/remotes/shallow/* &&
+ git fsck &&
+ git for-each-ref --sort=refname --format="%(refname)" >actual.refs &&
+ cat <<-EOF >expect.refs &&
+ refs/remotes/shallow/master
+ refs/remotes/shallow/no-shallow
+ refs/tags/heavy-tag
+ refs/tags/heavy-tag-for-graph
+ refs/tags/light-tag
+ refs/tags/light-tag-for-graph
+ EOF
test_cmp expect.refs actual.refs &&
git log --format=%s shallow/master >actual &&
- cat <<EOF >expect &&
-7
-6
-5
-4
-3
-EOF
+ test_write_lines 8 7 6 5 4 3 >expect &&
test_cmp expect actual
)
'
@@ -183,10 +179,7 @@ test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' '
find read-only.git -print | xargs chmod -w &&
git clone --no-local --depth=2 read-only.git from-read-only &&
git --git-dir=from-read-only/.git log --format=%s >actual &&
- cat >expect <<EOF &&
-add-1-back
-4
-EOF
+ test_write_lines add-1-back 4 >expect &&
test_cmp expect actual
'
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 23be8ce92d..afc680d5e3 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -177,6 +177,9 @@ test_expect_success 'push (chunked)' '
test $HEAD = $(git rev-parse --verify HEAD))
'
+## References of remote: atomic1(1) master(2) collateral(2) other(2)
+## References of local : atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)
+## Atomic push : master(1) collateral(3) atomic(1)
test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
# Setup upstream repo - empty for now
d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
@@ -189,7 +192,8 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
test_commit atomic2 &&
git branch collateral &&
git branch other &&
- git push "$up" master collateral other &&
+ git push "$up" atomic1 master collateral other &&
+ git tag -d atomic1 &&
# collateral is a valid push, but should be failed by atomic push
git checkout collateral &&
@@ -224,7 +228,11 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
# the collateral failure refs should be indicated to the user
grep "^ ! .*rejected.* atomic -> atomic .*atomic push failed" output &&
- grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output
+ grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output &&
+
+ # never report what we do not push
+ ! grep "^ ! .*rejected.* atomic1 " output &&
+ ! grep "^ ! .*rejected.* other " output
'
test_expect_success 'push --atomic fails on server-side errors' '
diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
index 7079bcf9a0..620c30d58f 100755
--- a/t/t5543-atomic-push.sh
+++ b/t/t5543-atomic-push.sh
@@ -27,6 +27,12 @@ test_refs () {
test_cmp expect actual
}
+fmt_status_report () {
+ sed -n \
+ -e "/^To / { s/ */ /g; p; }" \
+ -e "/^ ! / { s/ */ /g; p; }"
+}
+
test_expect_success 'atomic push works for a single branch' '
mk_repo_pair &&
(
@@ -191,4 +197,87 @@ test_expect_success 'atomic push is not advertised if configured' '
test_refs master HEAD@{1}
'
+# References in upstream : master(1) one(1) foo(1)
+# References in workbench: master(2) foo(1) two(2) bar(2)
+# Atomic push : master(2) two(2) bar(2)
+test_expect_success 'atomic push reports (reject by update hook)' '
+ mk_repo_pair &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git branch foo &&
+ git push up master one foo &&
+ git tag -d one
+ ) &&
+ (
+ mkdir -p upstream/.git/hooks &&
+ cat >upstream/.git/hooks/update <<-EOF &&
+ #!/bin/sh
+
+ if test "\$1" = "refs/heads/bar"
+ then
+ echo >&2 "Pusing to branch bar is prohibited"
+ exit 1
+ fi
+ EOF
+ chmod a+x upstream/.git/hooks/update
+ ) &&
+ (
+ cd workbench &&
+ test_commit two &&
+ git branch bar
+ ) &&
+ test_must_fail git -C workbench \
+ push --atomic up master two bar >out 2>&1 &&
+ fmt_status_report <out >actual &&
+ cat >expect <<-EOF &&
+ To ../upstream
+ ! [remote rejected] master -> master (atomic push failure)
+ ! [remote rejected] two -> two (atomic push failure)
+ ! [remote rejected] bar -> bar (hook declined)
+ EOF
+ test_cmp expect actual
+'
+
+# References in upstream : master(1) one(1) foo(1)
+# References in workbench: master(2) foo(1) two(2) bar(2)
+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '
+ (
+ cd workbench &&
+ git remote remove up &&
+ git remote add up ../upstream
+ ) &&
+ test_must_fail git -C workbench \
+ push --atomic --mirror up >out 2>&1 &&
+ fmt_status_report <out >actual &&
+ cat >expect <<-EOF &&
+ To ../upstream
+ ! [remote rejected] master -> master (atomic push failure)
+ ! [remote rejected] one (atomic push failure)
+ ! [remote rejected] bar -> bar (hook declined)
+ ! [remote rejected] two -> two (atomic push failure)
+ EOF
+ test_cmp expect actual
+'
+
+# References in upstream : master(2) one(1) foo(1)
+# References in workbench: master(1) foo(1) two(2) bar(2)
+test_expect_success 'atomic push reports (reject by non-ff)' '
+ rm upstream/.git/hooks/update &&
+ (
+ cd workbench &&
+ git push up master &&
+ git reset --hard HEAD^
+ ) &&
+ test_must_fail git -C workbench \
+ push --atomic up master foo bar >out 2>&1 &&
+ fmt_status_report <out >actual &&
+ cat >expect <<-EOF &&
+ To ../upstream
+ ! [rejected] master -> master (non-fast-forward)
+ ! [rejected] bar -> bar (atomic push failed)
+ EOF
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh
new file mode 100755
index 0000000000..1b19b3ef55
--- /dev/null
+++ b/t/t5548-push-porcelain.sh
@@ -0,0 +1,279 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Jiang Xin
+#
+test_description='Test git push porcelain output'
+
+. ./test-lib.sh
+
+# Create commits in <repo> and assign each commit's oid to shell variables
+# given in the arguments (A, B, and C). E.g.:
+#
+# create_commits_in <repo> A B C
+#
+# NOTE: Never calling this function from a subshell since variable
+# assignments will disappear when subshell exits.
+create_commits_in () {
+ repo="$1" &&
+ if ! parent=$(git -C "$repo" rev-parse HEAD^{} --)
+ then
+ parent=
+ fi &&
+ T=$(git -C "$repo" write-tree) &&
+ shift &&
+ while test $# -gt 0
+ do
+ name=$1 &&
+ test_tick &&
+ if test -z "$parent"
+ then
+ oid=$(echo $name | git -C "$repo" commit-tree $T)
+ else
+ oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T)
+ fi &&
+ eval $name=$oid &&
+ parent=$oid &&
+ shift ||
+ return 1
+ done &&
+ git -C "$repo" update-ref refs/heads/master $oid
+}
+
+# Format the output of git-push, git-show-ref and other commands to make a
+# user-friendly and stable text. We can easily prepare the expect text
+# without having to worry about future changes of the commit ID and spaces
+# of the output.
+make_user_friendly_and_stable_output () {
+ sed \
+ -e "s/ *\$//" \
+ -e "s/ */ /g" \
+ -e "s/ / /g" \
+ -e "s/$A/<COMMIT-A>/g" \
+ -e "s/$B/<COMMIT-B>/g" \
+ -e "s/$ZERO_OID/<ZERO-OID>/g" \
+ -e "s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g" \
+ -e "s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g" \
+ -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#"
+}
+
+setup_upstream_and_workbench () {
+ # Upstream after setup : master(B) foo(A) bar(A) baz(A)
+ # Workbench after setup : master(A)
+ test_expect_success "setup upstream repository and workbench" '
+ rm -rf upstream.git workbench &&
+ git init --bare upstream.git &&
+ git init workbench &&
+ create_commits_in workbench A B &&
+ (
+ cd workbench &&
+ # Try to make a stable fixed width for abbreviated commit ID,
+ # this fixed-width oid will be replaced with "<OID>".
+ git config core.abbrev 7 &&
+ git remote add origin ../upstream.git &&
+ git update-ref refs/heads/master $A &&
+ git push origin \
+ $B:refs/heads/master \
+ $A:refs/heads/foo \
+ $A:refs/heads/bar \
+ $A:refs/heads/baz
+ ) &&
+ git -C "workbench" config advice.pushUpdateRejected false &&
+ upstream=upstream.git
+ '
+}
+
+run_git_push_porcelain_output_test() {
+ case $1 in
+ http)
+ PROTOCOL="HTTP protocol"
+ URL_PREFIX="http://.*"
+ ;;
+ file)
+ PROTOCOL="builtin protocol"
+ URL_PREFIX="\.\."
+ ;;
+ esac
+
+ # Refs of upstream : master(B) foo(A) bar(A) baz(A)
+ # Refs of workbench: master(A) baz(A) next(A)
+ # git-push : master(A) NULL (B) baz(A) next(A)
+ test_expect_success "porcelain output of successful git-push ($PROTOCOL)" '
+ (
+ cd workbench &&
+ git update-ref refs/heads/master $A &&
+ git update-ref refs/heads/baz $A &&
+ git update-ref refs/heads/next $A &&
+ git push --porcelain --force origin \
+ master \
+ :refs/heads/foo \
+ $B:bar \
+ baz \
+ next
+ ) >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ = refs/heads/baz:refs/heads/baz [up to date]
+ <COMMIT-B>:refs/heads/bar <OID-A>..<OID-B>
+ - :refs/heads/foo [deleted]
+ + refs/heads/master:refs/heads/master <OID-B>...<OID-A> (forced update)
+ * refs/heads/next:refs/heads/next [new branch]
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+ '
+
+ # Refs of upstream : master(A) bar(B) baz(A) next(A)
+ # Refs of workbench: master(B) bar(A) baz(A) next(A)
+ # git-push : master(B) bar(A) NULL next(A)
+ test_expect_success "atomic push failed ($PROTOCOL)" '
+ (
+ cd workbench &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/bar $A &&
+ test_must_fail git push --atomic --porcelain origin \
+ master \
+ bar \
+ :baz \
+ next
+ ) >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ = refs/heads/next:refs/heads/next [up to date]
+ ! refs/heads/bar:refs/heads/bar [rejected] (non-fast-forward)
+ ! (delete):refs/heads/baz [rejected] (atomic push failed)
+ ! refs/heads/master:refs/heads/master [rejected] (atomic push failed)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+ '
+ test_expect_success "prepare pre-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/pre-receive" <<-EOF
+ exit 1
+ EOF
+ '
+
+ # Refs of upstream : master(A) bar(B) baz(A) next(A)
+ # Refs of workbench: master(B) bar(A) baz(A) next(A)
+ # git-push : master(B) bar(A) NULL next(A)
+ test_expect_success "pre-receive hook declined ($PROTOCOL)" '
+ (
+ cd workbench &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/bar $A &&
+ test_must_fail git push --porcelain --force origin \
+ master \
+ bar \
+ :baz \
+ next
+ ) >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ = refs/heads/next:refs/heads/next [up to date]
+ ! refs/heads/bar:refs/heads/bar [remote rejected] (pre-receive hook declined)
+ ! :refs/heads/baz [remote rejected] (pre-receive hook declined)
+ ! refs/heads/master:refs/heads/master [remote rejected] (pre-receive hook declined)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+ '
+
+ test_expect_success "remove pre-receive hook ($PROTOCOL)" '
+ rm "$upstream/hooks/pre-receive"
+ '
+
+ # Refs of upstream : master(A) bar(B) baz(A) next(A)
+ # Refs of workbench: master(B) bar(A) baz(A) next(A)
+ # git-push : master(B) bar(A) NULL next(A)
+ test_expect_success "non-fastforward push ($PROTOCOL)" '
+ (
+ cd workbench &&
+ test_must_fail git push --porcelain origin \
+ master \
+ bar \
+ :baz \
+ next
+ ) >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ = refs/heads/next:refs/heads/next [up to date]
+ - :refs/heads/baz [deleted]
+ refs/heads/master:refs/heads/master <OID-A>..<OID-B>
+ ! refs/heads/bar:refs/heads/bar [rejected] (non-fast-forward)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-B> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+ '
+}
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Run git-push porcelain test on builtin protocol
+run_git_push_porcelain_output_test file
+
+ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+start_httpd
+
+# Re-initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+test_expect_success "setup for http" '
+ git -C upstream.git config http.receivepack true &&
+ upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" &&
+ mv upstream.git "$upstream" &&
+
+ git -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git
+'
+
+setup_askpass_helper
+
+# Run git-push porcelain test on HTTP protocol
+run_git_push_porcelain_output_test http
+
+test_done
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index b811d89cfd..50485300eb 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -248,9 +248,7 @@ test_expect_success 'fetch can handle previously-fetched .idx files' '
'
test_expect_success 'did not use upload-pack service' '
- test_might_fail grep '/git-upload-pack' <"$HTTPD_ROOT_PATH"/access.log >act &&
- : >exp &&
- test_cmp exp act
+ ! grep "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log"
'
test_expect_success 'git client shows text/plain errors' '
@@ -321,11 +319,17 @@ test_expect_success 'git client does not send an empty Accept-Language' '
'
test_expect_success 'remote-http complains cleanly about malformed urls' '
- # do not actually issue "list" or other commands, as we do not
- # want to rely on what curl would actually do with such a broken
- # URL. This is just about making sure we do not segfault during
- # initialization.
- test_must_fail git remote-http http::/example.com/repo.git
+ test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
+ test_i18ngrep "url has no scheme" stderr
+'
+
+# NEEDSWORK: Writing commands to git-remote-curl can race against the latter
+# erroring out, producing SIGPIPE. Remove "ok=sigpipe" once transport-helper has
+# learned to handle early remote helper failures more cleanly.
+test_expect_success 'remote-http complains cleanly about empty scheme' '
+ test_must_fail ok=sigpipe git ls-remote \
+ http::${HTTPD_URL#http}/dumb/repo.git 2>stderr &&
+ test_i18ngrep "url has no scheme" stderr
'
test_expect_success 'redirects can be forbidden/allowed' '
diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh
index 4a110b307e..3f4ac71f83 100755
--- a/t/t5562-http-backend-content-length.sh
+++ b/t/t5562-http-backend-content-length.sh
@@ -53,15 +53,20 @@ test_expect_success 'setup' '
test_commit c1 &&
hash_head=$(git rev-parse HEAD) &&
hash_prev=$(git rev-parse HEAD~1) &&
- printf "want %s" "$hash_head" | packetize >fetch_body &&
- printf 0000 >>fetch_body &&
- printf "have %s" "$hash_prev" | packetize >>fetch_body &&
- printf done | packetize >>fetch_body &&
+ {
+ packetize "want $hash_head" &&
+ printf 0000 &&
+ packetize "have $hash_prev" &&
+ packetize "done"
+ } >fetch_body &&
test_copy_bytes 10 <fetch_body >fetch_body.trunc &&
hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) &&
- printf "%s %s refs/heads/newbranch\\0report-status\\n" "$ZERO_OID" "$hash_next" | packetize >push_body &&
- printf 0000 >>push_body &&
- echo "$hash_next" | git pack-objects --stdout >>push_body &&
+ {
+ printf "%s %s refs/heads/newbranch\\0report-status\\n" \
+ "$ZERO_OID" "$hash_next" | packetize &&
+ printf 0000 &&
+ echo "$hash_next" | git pack-objects --stdout
+ } >push_body &&
test_copy_bytes 10 <push_body >push_body.trunc &&
: >empty_body
'
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 0c74b4e21a..2f7be23044 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -175,7 +175,7 @@ test_expect_success 'clone using repo pointed at by gitfile as reference' '
test_expect_success 'clone and dissociate from reference' '
git init P &&
(
- cd P && test_commit one
+ cd P && test_commit one
) &&
git clone P Q &&
(
diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh
index 9108ff6fbd..6d5a977fcb 100755
--- a/t/t5607-clone-bundle.sh
+++ b/t/t5607-clone-bundle.sh
@@ -16,7 +16,7 @@ test_expect_success 'setup' '
test_expect_success '"verify" needs a worktree' '
git bundle create tip.bundle -1 master &&
- test_must_fail nongit git bundle verify ../tip.bundle 2>err &&
+ nongit test_must_fail git bundle verify ../tip.bundle 2>err &&
test_i18ngrep "need a repository" err
'
diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh
index 60c1ba951b..8e0fd39823 100755
--- a/t/t5611-clone-config.sh
+++ b/t/t5611-clone-config.sh
@@ -92,24 +92,17 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
test_cmp expect actual
'
-# Tests for the hidden file attribute on windows
-is_hidden () {
- # Use the output of `attrib`, ignore the absolute path
- case "$(attrib "$1")" in *H*?:*) return 0;; esac
- return 1
-}
-
test_expect_success MINGW 'clone -c core.hideDotFiles' '
test_commit attributes .gitattributes "" &&
rm -rf child &&
git clone -c core.hideDotFiles=false . child &&
- ! is_hidden child/.gitattributes &&
+ ! test_path_is_hidden child/.gitattributes &&
rm -rf child &&
git clone -c core.hideDotFiles=dotGitOnly . child &&
- ! is_hidden child/.gitattributes &&
+ ! test_path_is_hidden child/.gitattributes &&
rm -rf child &&
git clone -c core.hideDotFiles=true . child &&
- is_hidden child/.gitattributes
+ test_path_is_hidden child/.gitattributes
'
test_done
diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh
index e36ac01661..e3b436d8ae 100755
--- a/t/t5612-clone-refspec.sh
+++ b/t/t5612-clone-refspec.sh
@@ -71,9 +71,9 @@ test_expect_success 'by default all branches will be kept updated' '
(
cd dir_all &&
git fetch &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# follow both master and side
git for-each-ref refs/heads >expect &&
@@ -87,7 +87,7 @@ test_expect_success 'by default no tags will be kept updated' '
git for-each-ref refs/tags >../actual
) &&
git for-each-ref refs/tags >expect &&
- test_must_fail test_cmp expect actual &&
+ ! test_cmp expect actual &&
test_line_count = 2 actual
'
@@ -104,9 +104,9 @@ test_expect_success '--single-branch while HEAD pointing at master' '
(
cd dir_master &&
git fetch --force &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# only follow master
git for-each-ref refs/heads/master >expect &&
@@ -126,9 +126,9 @@ test_expect_success '--single-branch while HEAD pointing at master and --no-tags
(
cd dir_master_no_tags &&
git fetch &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# only follow master
git for-each-ref refs/heads/master >expect &&
@@ -156,9 +156,9 @@ test_expect_success '--single-branch while HEAD pointing at side' '
(
cd dir_side &&
git fetch &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# only follow side
git for-each-ref refs/heads/side >expect &&
@@ -169,9 +169,9 @@ test_expect_success '--single-branch with explicit --branch side' '
(
cd dir_side2 &&
git fetch &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# only follow side
git for-each-ref refs/heads/side >expect &&
@@ -223,9 +223,9 @@ test_expect_success '--single-branch with detached' '
(
cd dir_detached &&
git fetch &&
- git for-each-ref refs/remotes/origin |
+ git for-each-ref refs/remotes/origin >refs &&
sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
+ -e "s|/remotes/origin/|/heads/|" refs >../actual
) &&
# nothing
test_must_be_empty actual
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 77bb91e976..88002b24af 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -49,7 +49,7 @@ test_expect_success 'do partial clone 1' '
test_expect_success 'verify that .promisor file contains refs fetched' '
ls pc1/.git/objects/pack/pack-*.promisor >promisorlist &&
test_line_count = 1 promisorlist &&
- git -C srv.bare rev-list HEAD >headhash &&
+ git -C srv.bare rev-parse --verify HEAD >headhash &&
grep "$(cat headhash) HEAD" $(cat promisorlist) &&
grep "$(cat headhash) refs/heads/master" $(cat promisorlist)
'
@@ -415,6 +415,14 @@ test_expect_success 'verify fetch downloads only one pack when updating refs' '
test_line_count = 3 pack-list
'
+test_expect_success 'single-branch tag following respects partial clone' '
+ git clone --single-branch -b B --filter=blob:none \
+ "file://$(pwd)/srv.bare" single &&
+ git -C single rev-parse --verify refs/tags/B &&
+ git -C single rev-parse --verify refs/tags/A &&
+ test_must_fail git -C single rev-parse --verify refs/tags/C
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index 7fba3063bf..a34460f7d8 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -13,10 +13,7 @@ get_actual_refs () {
}
get_actual_commits () {
- sed -n -e '/packfile/,/0000/{
- /packfile/d
- p
- }' <out | test-tool pkt-line unpack-sideband >o.pack &&
+ test-tool pkt-line unpack-sideband <out >o.pack &&
git index-pack o.pack &&
git verify-pack -v o.idx >objs &&
grep commit objs | cut -d" " -f1 | sort >actual_commits
diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh
new file mode 100755
index 0000000000..950cfb21fe
--- /dev/null
+++ b/t/t5704-protocol-violations.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='Test responses to violations of the network protocol. In most
+of these cases it will generally be acceptable for one side to break off
+communications if the other side says something unexpected. We are mostly
+making sure that we do not segfault or otherwise behave badly.'
+. ./test-lib.sh
+
+test_expect_success 'extra delim packet in v2 ls-refs args' '
+ {
+ packetize command=ls-refs &&
+ printf 0001 &&
+ # protocol expects 0000 flush here
+ printf 0001
+ } >input &&
+ test_must_fail env GIT_PROTOCOL=version=2 \
+ git upload-pack . <input 2>err &&
+ test_i18ngrep "expected flush after ls-refs arguments" err
+'
+
+test_expect_success 'extra delim packet in v2 fetch args' '
+ {
+ packetize command=fetch &&
+ printf 0001 &&
+ # protocol expects 0000 flush here
+ printf 0001
+ } >input &&
+ test_must_fail env GIT_PROTOCOL=version=2 \
+ git upload-pack . <input 2>err &&
+ test_i18ngrep "expected flush after fetch arguments" err
+'
+
+test_done
diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh
index 121e5c6edb..0f04b6cddb 100755
--- a/t/t5801-remote-helpers.sh
+++ b/t/t5801-remote-helpers.sh
@@ -11,9 +11,15 @@ test_description='Test remote-helper import and export commands'
PATH="$TEST_DIRECTORY/t5801:$PATH"
compare_refs() {
+ fail= &&
+ if test "x$1" = 'x!'
+ then
+ fail='!' &&
+ shift
+ fi &&
git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
- test_cmp expect actual
+ eval $fail test_cmp expect actual
}
test_expect_success 'setup repository' '
@@ -189,7 +195,7 @@ test_expect_success GPG 'push signed tag' '
git push origin signed-tag
) &&
compare_refs local signed-tag^{} server signed-tag^{} &&
- test_must_fail compare_refs local signed-tag server signed-tag
+ compare_refs ! local signed-tag server signed-tag
'
test_expect_success GPG 'push signed tag with signed-tags capability' '
diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh
index a10f0df02b..b6fa43ace0 100755
--- a/t/t6012-rev-list-simplify.sh
+++ b/t/t6012-rev-list-simplify.sh
@@ -154,4 +154,124 @@ test_expect_success '--full-diff is not affected by --parents' '
test_cmp expected actual
'
+#
+# Create a new history to demonstrate the value of --show-pulls
+# with respect to the subtleties of simplified history, --full-history,
+# and --simplify-merges.
+#
+# .-A---M-----C--N---O---P
+# / / \ \ \/ / /
+# I B \ R-'`-Z' /
+# \ / \/ /
+# \ / /\ /
+# `---X--' `---Y--'
+#
+# This example is explained in Documentation/rev-list-options.txt
+
+test_expect_success 'rebuild repo' '
+ rm -rf .git * &&
+ git init &&
+ git switch -c main &&
+
+ echo base >file &&
+ git add file &&
+ test_commit I &&
+
+ echo A >file &&
+ git add file &&
+ test_commit A &&
+
+ git switch -c branchB I &&
+ echo B >file &&
+ git add file &&
+ test_commit B &&
+
+ git switch main &&
+ test_must_fail git merge -m "M" B &&
+ echo A >file &&
+ echo B >>file &&
+ git add file &&
+ git merge --continue &&
+ note M &&
+
+ echo C >other &&
+ git add other &&
+ test_commit C &&
+
+ git switch -c branchX I &&
+ echo X >file &&
+ git add file &&
+ test_commit X &&
+
+ git switch -c branchR M &&
+ git merge -m R -Xtheirs X &&
+ note R &&
+
+ git switch main &&
+ git merge -m N R &&
+ note N &&
+
+ git switch -c branchY M &&
+ echo Y >y &&
+ git add y &&
+ test_commit Y &&
+
+ git switch -c branchZ C &&
+ echo Z >z &&
+ git add z &&
+ test_commit Z &&
+
+ git switch main &&
+ git merge -m O Z &&
+ note O &&
+
+ git merge -m P Y &&
+ note P
+'
+
+check_result 'X I' -- file
+check_result 'N R X I' --show-pulls -- file
+
+check_result 'P O N R X M B A I' --full-history --topo-order -- file
+check_result 'N R X M B A I' --simplify-merges --topo-order --show-pulls -- file
+check_result 'R X M B A I' --simplify-merges --topo-order -- file
+check_result 'N M A I' --first-parent -- file
+check_result 'N M A I' --first-parent --show-pulls -- file
+
+# --ancestry-path implies --full-history
+check_result 'P O N R M' --topo-order \
+ --ancestry-path A..HEAD -- file
+check_result 'P O N R M' --topo-order \
+ --show-pulls \
+ --ancestry-path A..HEAD -- file
+check_result 'P O N R M' --topo-order \
+ --full-history \
+ --ancestry-path A..HEAD -- file
+check_result 'R M' --topo-order \
+ --simplify-merges \
+ --ancestry-path A..HEAD -- file
+check_result 'N R M' --topo-order \
+ --simplify-merges --show-pulls \
+ --ancestry-path A..HEAD -- file
+
+test_expect_success 'log --graph --simplify-merges --show-pulls' '
+ cat >expect <<-\EOF &&
+ * N
+ * R
+ |\
+ | * X
+ * | M
+ |\ \
+ | * | B
+ | |/
+ * / A
+ |/
+ * I
+ EOF
+ git log --graph --pretty="%s" \
+ --simplify-merges --show-pulls \
+ -- file >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 821a0c88cf..1313142564 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -148,7 +148,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" created if junk rev' '
test_must_fail git bisect start $HASH4 foo -- &&
git branch > branch.output &&
grep "* other" branch.output > /dev/null &&
- test_must_fail test -e .git/BISECT_START
+ test_path_is_missing .git/BISECT_START
'
test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if junk rev' '
@@ -166,7 +166,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
test_must_fail git bisect start $HASH1 $HASH4 -- &&
git branch > branch.output &&
grep "* other" branch.output > /dev/null &&
- test_must_fail test -e .git/BISECT_START
+ test_path_is_missing .git/BISECT_START
'
test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
@@ -175,7 +175,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
git branch &&
git branch > branch.output &&
grep "* other" branch.output > /dev/null &&
- test_must_fail test -e .git/BISECT_START &&
+ test_path_is_missing .git/BISECT_START &&
test -z "$(git for-each-ref "refs/bisect/*")" &&
git checkout HEAD hello
'
@@ -485,7 +485,7 @@ test_expect_success 'optimized merge base checks' '
git bisect bad &&
git bisect good "$A_HASH" > my_bisect_log4.txt &&
test_i18ngrep "merge base must be tested" my_bisect_log4.txt &&
- test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
+ test_path_is_missing ".git/BISECT_ANCESTORS_OK"
'
# This creates another side branch called "parallel" with some files
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index 190ae149cf..428cff9cf3 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -18,7 +18,7 @@ GIT_FORCE_UNTRACKED_CACHE=true
export GIT_FORCE_UNTRACKED_CACHE
sync_mtime () {
- find . -type d -ls >/dev/null
+ find . -type d -exec ls -ld {} + >/dev/null
}
avoid_racy() {
@@ -30,6 +30,30 @@ status_is_clean() {
test_must_be_empty ../status.actual
}
+# Ignore_Untracked_Cache, abbreviated to 3 letters because then people can
+# compare commands side-by-side, e.g.
+# iuc status --porcelain >expect &&
+# git status --porcelain >actual &&
+# test_cmp expect actual
+iuc () {
+ git ls-files -s >../current-index-entries
+ git ls-files -t | sed -ne s/^S.//p >../current-sparse-entries
+
+ GIT_INDEX_FILE=.git/tmp_index
+ export GIT_INDEX_FILE
+ git update-index --index-info <../current-index-entries
+ git update-index --skip-worktree $(cat ../current-sparse-entries)
+
+ git -c core.untrackedCache=false "$@"
+ ret=$?
+
+ rm ../current-index-entries
+ rm $GIT_INDEX_FILE
+ unset GIT_INDEX_FILE
+
+ return $ret
+}
+
test_lazy_prereq UNTRACKED_CACHE '
{ git update-index --test-untracked-cache; ret=$?; } &&
test $ret -ne 1
@@ -95,6 +119,8 @@ test_expect_success 'status first time (empty cache)' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 3
@@ -115,6 +141,8 @@ test_expect_success 'status second time (fully populated cache)' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -136,6 +164,7 @@ test_expect_success 'modify in root directory, one dir invalidation' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
A done/one
A one
@@ -145,6 +174,7 @@ A two
?? four
?? three
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -183,6 +213,7 @@ test_expect_success 'new .gitignore invalidates recursively' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
A done/one
A one
@@ -192,6 +223,7 @@ A two
?? dtwo/
?? three
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -230,6 +262,7 @@ test_expect_success 'new info/exclude invalidates everything' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
A done/one
A one
@@ -237,6 +270,7 @@ A two
?? .gitignore
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -286,6 +320,7 @@ test_expect_success 'status after the move' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
A done/one
A one
@@ -293,6 +328,7 @@ A one
?? dtwo/
?? two
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -343,6 +379,7 @@ test_expect_success 'status after the move' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
A done/one
A one
@@ -350,6 +387,7 @@ A two
?? .gitignore
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -390,10 +428,12 @@ test_expect_success 'status after commit' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
?? .gitignore
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -447,12 +487,14 @@ test_expect_success 'test sparse status with untracked cache' '
avoid_racy &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
M done/two
?? .gitignore
?? done/five
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -487,12 +529,14 @@ test_expect_success 'test sparse status again with untracked cache' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
M done/two
?? .gitignore
?? done/five
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -514,6 +558,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
M done/two
?? .gitignore
@@ -521,6 +566,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
?? done/sub/
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
cat >../trace.expect <<EOF &&
node creation: 2
@@ -560,6 +606,8 @@ test_expect_success 'test sparse status again with untracked cache and subdir' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual &&
cat >../trace.expect <<EOF &&
node creation: 0
@@ -573,6 +621,7 @@ EOF
test_expect_success 'move entry in subdir from untracked to cached' '
git add dtwo/two &&
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
M done/two
A dtwo/two
@@ -580,12 +629,14 @@ A dtwo/two
?? done/five
?? done/sub/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual
'
test_expect_success 'move entry in subdir from cached to untracked' '
git rm --cached dtwo/two &&
git status --porcelain >../status.actual &&
+ iuc status --porcelain >../status.iuc &&
cat >../status.expect <<EOF &&
M done/two
?? .gitignore
@@ -593,6 +644,7 @@ test_expect_success 'move entry in subdir from cached to untracked' '
?? done/sub/
?? dtwo/
EOF
+ test_cmp ../status.expect ../status.iuc &&
test_cmp ../status.expect ../status.actual
'
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index e3e2aab3b0..956e17abb3 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -938,7 +938,7 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
echo "repo" >expect &&
test_must_fail git config -f .gitmodules submodule.repo.path &&
git config -f .gitmodules submodule.repo_new.path >actual &&
- test_cmp expect actual&&
+ test_cmp expect actual &&
echo "$submodurl/repo" >expect &&
test_must_fail git config -f .gitmodules submodule.repo.url &&
echo "$submodurl/bare.git" >expect &&
@@ -1010,7 +1010,7 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
test -d repo &&
echo "repo" >expect &&
git config -f .gitmodules submodule.repo_new.path >actual &&
- test_cmp expect actual&&
+ test_cmp expect actual &&
echo "$submodurl/repo.git" >expect &&
git config -f .gitmodules submodule.repo_new.url >actual &&
test_cmp expect actual &&
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index 34ac28c056..a3892f494b 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -122,8 +122,8 @@ test_expect_success 'missing submodule alternate fails clone and submodule updat
# update of the submodule succeeds
test_must_fail git submodule update --init &&
# and we have no alternates:
- test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
- test_must_fail test_path_is_file sub/file1
+ test_path_is_missing .git/modules/sub/objects/info/alternates &&
+ test_path_is_missing sub/file1
)
'
@@ -137,7 +137,7 @@ test_expect_success 'ignoring missing submodule alternates passes clone and subm
# update of the submodule succeeds
git submodule update --init &&
# and we have no alternates:
- test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub &&
+ test_path_is_missing .git/modules/sub/objects/info/alternates &&
test_path_is_file sub/file1
)
'
@@ -182,7 +182,7 @@ check_that_two_of_three_alternates_are_used() {
# immediate submodule has alternate:
test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub &&
# but nested submodule has no alternate:
- test_must_fail test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub
+ test_path_is_missing .git/modules/subwithsub/modules/sub/objects/info/alternates
}
diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
index 41431b1ac3..eec96e0ba9 100755
--- a/t/t7416-submodule-dash-url.sh
+++ b/t/t7416-submodule-dash-url.sh
@@ -60,6 +60,116 @@ test_expect_success 'trailing backslash is handled correctly' '
test_i18ngrep ! "unknown option" err
'
+test_expect_success 'fsck rejects missing URL scheme' '
+ git checkout --orphan missing-scheme &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = http::one.example.com/foo.git
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "gitmodules with missing URL scheme" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative URL resolving to missing scheme' '
+ git checkout --orphan relative-missing-scheme &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = "..\\../.\\../:one.example.com/foo.git"
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "gitmodules with relative URL that strips off scheme" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects empty URL scheme' '
+ git checkout --orphan empty-scheme &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = http::://one.example.com/foo.git
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "gitmodules with empty URL scheme" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative URL resolving to empty scheme' '
+ git checkout --orphan relative-empty-scheme &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = ../../../:://one.example.com/foo.git
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "relative gitmodules URL resolving to empty scheme" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects empty hostname' '
+ git checkout --orphan empty-host &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = http:///one.example.com/foo.git
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "gitmodules with extra slashes" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck rejects relative url that produced empty hostname' '
+ git checkout --orphan messy-relative &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = ../../..//one.example.com/foo.git
+ EOF
+ git add .gitmodules &&
+ test_tick &&
+ git commit -m "gitmodules abusing relative_path" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
+test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
+ git checkout --orphan newscheme &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = "data://acjbkd%0akajfdickajkd"
+ EOF
+ git add .gitmodules &&
+ git commit -m "gitmodules with unrecognized scheme" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ git push dst HEAD
+'
+
test_expect_success 'fsck rejects embedded newline in url' '
# create an orphan branch to avoid existing .gitmodules objects
git checkout --orphan newline &&
@@ -76,4 +186,19 @@ test_expect_success 'fsck rejects embedded newline in url' '
grep gitmodulesUrl err
'
+test_expect_success 'fsck rejects embedded newline in relative url' '
+ git checkout --orphan relative-newline &&
+ cat >.gitmodules <<-\EOF &&
+ [submodule "foo"]
+ url = "./%0ahost=two.example.com/foo.git"
+ EOF
+ git add .gitmodules &&
+ git commit -m "relative url with newline" &&
+ test_when_finished "rm -rf dst" &&
+ git init --bare dst &&
+ git -C dst config transfer.fsckObjects true &&
+ test_must_fail git push dst HEAD 2>err &&
+ grep gitmodulesUrl err
+'
+
test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 482ce3510e..8e969f3e36 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1471,7 +1471,7 @@ test_expect_success '"status.branch=true" same as "-b"' '
test_expect_success '"status.branch=true" different from "--no-branch"' '
git status -s --no-branch >expected_nobranch &&
git -c status.branch=true status -s >actual &&
- test_must_fail test_cmp expected_nobranch actual
+ ! test_cmp expected_nobranch actual
'
test_expect_success '"status.branch=true" weaker than "--no-branch"' '
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 132608879a..5883a6adc3 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -29,15 +29,19 @@ Testing basic merge operations/option parsing.
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
-printf '%s\n' 1 2 3 4 5 6 7 8 9 >file
-printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
-printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 Y' >file.9y
-printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
-printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
-printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
-printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z
+test_write_lines 1 2 3 4 5 6 7 8 9 >file
+cp file file.orig
+test_write_lines '1 X' 2 3 4 5 6 7 8 9 >file.1
+test_write_lines 1 2 '3 X' 4 5 6 7 8 9 >file.3
+test_write_lines 1 2 3 4 '5 X' 6 7 8 9 >file.5
+test_write_lines 1 2 3 4 5 6 7 8 '9 X' >file.9
+test_write_lines 1 2 3 4 5 6 7 8 '9 Y' >file.9y
+test_write_lines '1 X' 2 3 4 5 6 7 8 9 >result.1
+test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
+test_write_lines '1 X' 2 3 4 5 6 7 8 '9 X' >result.1-9
+test_write_lines '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+test_write_lines '1 X' 2 '3 X' 4 '5 X' 6 7 8 '9 X' >result.1-3-5-9
+test_write_lines 1 2 3 4 5 6 7 8 '9 Z' >result.9z
create_merge_msgs () {
echo "Merge tag 'c2'" >msg.1-5 &&
@@ -81,7 +85,7 @@ verify_head () {
}
verify_parents () {
- printf '%s\n' "$@" >parents.expected &&
+ test_write_lines "$@" >parents.expected &&
>parents.actual &&
i=1 &&
while test $i -le $#
@@ -95,7 +99,7 @@ verify_parents () {
}
verify_mergeheads () {
- printf '%s\n' "$@" >mergehead.expected &&
+ test_write_lines "$@" >mergehead.expected &&
while read sha1 rest
do
git rev-parse $sha1
@@ -675,6 +679,134 @@ test_expect_success 'refresh the index before merging' '
git merge c3
'
+test_expect_success 'merge with --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git merge --autostash c2 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-5 merge-result &&
+ test_cmp result.1-5-9 file
+'
+
+test_expect_success 'merge with merge.autoStash' '
+ test_config merge.autoStash true &&
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git merge c2 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-5 merge-result &&
+ test_cmp result.1-5-9 file
+'
+
+test_expect_success 'fast-forward merge with --autostash' '
+ git reset --hard c0 &&
+ git merge-file file file.orig file.5 &&
+ git merge --autostash c1 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ test_cmp result.1-5 file
+'
+
+test_expect_success 'octopus merge with --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.3 &&
+ git merge --autostash c2 c3 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-5-9 merge-result &&
+ test_cmp result.1-3-5-9 file
+'
+
+test_expect_success 'conflicted merge with --autostash, --abort restores stash' '
+ git reset --hard c3 &&
+ cp file.1 file &&
+ test_must_fail git merge --autostash c7 &&
+ git merge --abort 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ test_cmp file.1 file
+'
+
+test_expect_success 'completed merge (git commit) with --no-commit and --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git diff >expect &&
+ git merge --no-commit --autostash c2 &&
+ git stash show -p MERGE_AUTOSTASH >actual &&
+ test_cmp expect actual &&
+ git commit 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-5 merge-result &&
+ test_cmp result.1-5-9 file
+'
+
+test_expect_success 'completed merge (git merge --continue) with --no-commit and --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git diff >expect &&
+ git merge --no-commit --autostash c2 &&
+ git stash show -p MERGE_AUTOSTASH >actual &&
+ test_cmp expect actual &&
+ git merge --continue 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-5 merge-result &&
+ test_cmp result.1-5-9 file
+'
+
+test_expect_success 'aborted merge (merge --abort) with --no-commit and --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git diff >expect &&
+ git merge --no-commit --autostash c2 &&
+ git stash show -p MERGE_AUTOSTASH >actual &&
+ test_cmp expect actual &&
+ git merge --abort 2>err &&
+ test_i18ngrep "Applied autostash." err &&
+ git diff >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'aborted merge (reset --hard) with --no-commit and --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git diff >expect &&
+ git merge --no-commit --autostash c2 &&
+ git stash show -p MERGE_AUTOSTASH >actual &&
+ test_cmp expect actual &&
+ git reset --hard 2>err &&
+ test_i18ngrep "Autostash exists; creating a new stash entry." err &&
+ git diff --exit-code
+'
+
+test_expect_success 'quit merge with --no-commit and --autostash' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9 &&
+ git diff >expect &&
+ git merge --no-commit --autostash c2 &&
+ git stash show -p MERGE_AUTOSTASH >actual &&
+ test_cmp expect actual &&
+ git diff HEAD >expect &&
+ git merge --quit 2>err &&
+ test_i18ngrep "Autostash exists; creating a new stash entry." err &&
+ git diff HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'merge with conflicted --autostash changes' '
+ git reset --hard c1 &&
+ git merge-file file file.orig file.9y &&
+ git diff >expect &&
+ test_when_finished "test_might_fail git stash drop" &&
+ git merge --autostash c3 2>err &&
+ test_i18ngrep "Applying autostash resulted in conflicts." err &&
+ git show HEAD:file >merge-result &&
+ test_cmp result.1-9 merge-result &&
+ git stash show -p >actual &&
+ test_cmp expect actual
+'
+
cat >expected.branch <<\EOF
Merge branch 'c5-branch' (early part)
EOF
diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh
index e90413204e..5c8894d94f 100755
--- a/t/t7609-merge-co-error-msgs.sh
+++ b/t/t7609-merge-co-error-msgs.sh
@@ -126,7 +126,7 @@ test_expect_success 'not_uptodate_dir porcelain checkout error' '
git rm rep2 -r &&
>rep &&
>rep2 &&
- git add rep rep2&&
+ git add rep rep2 &&
git commit -m "added test as a file" &&
git checkout master &&
>rep/untracked-file &&
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 7d7b396c23..991d5bd9c0 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -72,6 +72,11 @@ test_expect_success setup '
# Still a no-op.
function dummy() {}
EOF
+ if test_have_prereq FUNNYNAMES
+ then
+ echo unusual >"\"unusual\" pathname" &&
+ echo unusual >"t/nested \"unusual\" pathname"
+ fi &&
git add . &&
test_tick &&
git commit -m initial
@@ -481,6 +486,48 @@ do
git grep --count -h -e b $H -- ab >actual &&
test_cmp expected actual
'
+
+ test_expect_success FUNNYNAMES "grep $L should quote unusual pathnames" '
+ cat >expected <<-EOF &&
+ ${HC}"\"unusual\" pathname":unusual
+ ${HC}"t/nested \"unusual\" pathname":unusual
+ EOF
+ git grep unusual $H >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success FUNNYNAMES "grep $L in subdir should quote unusual relative pathnames" '
+ cat >expected <<-EOF &&
+ ${HC}"nested \"unusual\" pathname":unusual
+ EOF
+ (
+ cd t &&
+ git grep unusual $H
+ ) >actual &&
+ test_cmp expected actual
+ '
+
+ test_expect_success FUNNYNAMES "grep -z $L with unusual pathnames" '
+ cat >expected <<-EOF &&
+ ${HC}"unusual" pathname:unusual
+ ${HC}t/nested "unusual" pathname:unusual
+ EOF
+ git grep -z unusual $H >actual &&
+ tr "\0" ":" <actual >actual-replace-null &&
+ test_cmp expected actual-replace-null
+ '
+
+ test_expect_success FUNNYNAMES "grep -z $L in subdir with unusual relative pathnames" '
+ cat >expected <<-EOF &&
+ ${HC}nested "unusual" pathname:unusual
+ EOF
+ (
+ cd t &&
+ git grep -z unusual $H
+ ) >actual &&
+ tr "\0" ":" <actual >actual-replace-null &&
+ test_cmp expected actual-replace-null
+ '
done
cat >expected <<EOF
diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh
index c90fdc5c89..83f8f5cacb 100755
--- a/t/t9010-svn-fe.sh
+++ b/t/t9010-svn-fe.sh
@@ -486,7 +486,7 @@ test_expect_success 'NUL in property value' '
{
properties \
unimportant "something with a NUL (Q)" \
- svn:log "commit message"&&
+ svn:log "commit message" &&
echo PROPS-END
} |
q_to_nul >props &&
diff --git a/t/t9141-git-svn-multiple-branches.sh b/t/t9141-git-svn-multiple-branches.sh
index 8e7f7d68b7..bf168a3645 100755
--- a/t/t9141-git-svn-multiple-branches.sh
+++ b/t/t9141-git-svn-multiple-branches.sh
@@ -90,10 +90,10 @@ test_expect_success 'Multiple branch or tag paths require -d' '
) &&
( cd svn_project &&
svn_cmd up &&
- test_must_fail test -d b_one/Nope &&
- test_must_fail test -d b_two/Nope &&
- test_must_fail test -d tags_A/Tagless &&
- test_must_fail test -d tags_B/Tagless
+ test_path_is_missing b_one/Nope &&
+ test_path_is_missing b_two/Nope &&
+ test_path_is_missing tags_A/Tagless &&
+ test_path_is_missing tags_B/Tagless
)
'
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh
index 0ede3cfedb..36c6b1a12f 100755
--- a/t/t9160-git-svn-preserve-empty-dirs.sh
+++ b/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -86,8 +86,8 @@ test_expect_success 'remove non-last entry from directory' '
cd "$GIT_REPO" &&
git checkout HEAD~2
) &&
- test_must_fail test -f "$GIT_REPO"/2/.gitignore &&
- test_must_fail test -f "$GIT_REPO"/3/.gitignore
+ test_path_is_missing "$GIT_REPO"/2/.gitignore &&
+ test_path_is_missing "$GIT_REPO"/3/.gitignore
'
# After re-cloning the repository with --placeholder-file specified, there
diff --git a/t/t9164-git-svn-dcommit-concurrent.sh b/t/t9164-git-svn-dcommit-concurrent.sh
index 90346ff4e9..8466269bf5 100755
--- a/t/t9164-git-svn-dcommit-concurrent.sh
+++ b/t/t9164-git-svn-dcommit-concurrent.sh
@@ -92,7 +92,7 @@ test_expect_success 'check if post-commit hook creates a concurrent commit' '
echo 1 >> file &&
svn_cmd commit -m "changing file" &&
svn_cmd up &&
- test_must_fail test_cmp auto_updated_file au_file_saved
+ ! test_cmp auto_updated_file au_file_saved
)
'
@@ -103,7 +103,7 @@ test_expect_success 'check if pre-commit hook fails' '
echo 2 >> file &&
svn_cmd commit -m "changing file once again" &&
echo 3 >> file &&
- test_must_fail svn_cmd commit -m "this commit should fail" &&
+ ! svn_cmd commit -m "this commit should fail" &&
svn_cmd revert file
)
'
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index cc8d463e01..267ddc997d 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -53,7 +53,7 @@ test_expect_success \
test_expect_success \
'Make initial commit' \
- 'echo "Not an empty file." > file &&
+ 'echo "Not an empty file." >file &&
git add file &&
git commit -a -m "Initial commit." &&
git branch b'
@@ -139,7 +139,7 @@ test_expect_success \
test_expect_success \
'commitdiff(0): file added' \
- 'echo "New file" > new_file &&
+ 'echo "New file" >new_file &&
git add new_file &&
git commit -a -m "File added." &&
gitweb_run "p=.git;a=commitdiff"'
@@ -179,7 +179,7 @@ test_expect_success \
test_expect_success \
'commitdiff(0): mode change and modified' \
- 'echo "New line" >> file2 &&
+ 'echo "New line" >>file2 &&
test_chmod +x file2 &&
git commit -a -m "Mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
@@ -197,14 +197,14 @@ propter nomen suum.
EOF
git commit -a -m "File added." &&
git mv file2 file3 &&
- echo "Propter nomen suum." >> file3 &&
+ echo "Propter nomen suum." >>file3 &&
git commit -a -m "File rename and modification." &&
gitweb_run "p=.git;a=commitdiff"'
test_expect_success \
'commitdiff(0): renamed, mode change and modified' \
'git mv file3 file2 &&
- echo "Propter nomen suum." >> file2 &&
+ echo "Propter nomen suum." >>file2 &&
test_chmod +x file2 &&
git commit -a -m "File rename, mode change and modification." &&
gitweb_run "p=.git;a=commitdiff"'
@@ -213,8 +213,8 @@ test_expect_success \
# commitdiff testing (taken from t4114-apply-typechange.sh)
test_expect_success 'setup typechange commits' '
- echo "hello world" > foo &&
- echo "hi planet" > bar &&
+ echo "hello world" >foo &&
+ echo "hi planet" >bar &&
git update-index --add foo bar &&
git commit -m initial &&
git branch initial &&
@@ -223,18 +223,18 @@ test_expect_success 'setup typechange commits' '
git commit -m "foo symlinked to bar" &&
git branch foo-symlinked-to-bar &&
rm -f foo &&
- echo "how far is the sun?" > foo &&
+ echo "how far is the sun?" >foo &&
git update-index foo &&
git commit -m "foo back to file" &&
git branch foo-back-to-file &&
rm -f foo &&
git update-index --remove foo &&
mkdir foo &&
- echo "if only I knew" > foo/baz &&
+ echo "if only I knew" >foo/baz &&
git update-index --add foo/baz &&
git commit -m "foo becomes a directory" &&
git branch "foo-becomes-a-directory" &&
- echo "hello world" > foo/baz &&
+ echo "hello world" >foo/baz &&
git update-index foo/baz &&
git commit -m "foo/baz is the original foo" &&
git branch foo-baz-renamed-from-foo
@@ -324,7 +324,7 @@ test_expect_success 'commitdiff(1): removal of incomplete line' '
test_expect_success \
'Create a merge' \
'git checkout b &&
- echo "Branch" >> b &&
+ echo "Branch" >>b &&
git add b &&
git commit -a -m "On branch" &&
git checkout master &&
@@ -342,26 +342,26 @@ test_expect_success \
test_expect_success \
'Prepare large commit' \
'git checkout b &&
- echo "To be changed" > 01-change &&
- echo "To be renamed" > 02-pure-rename-from &&
- echo "To be deleted" > 03-delete &&
- echo "To be renamed and changed" > 04-rename-from &&
- echo "To have mode changed" > 05-mode-change &&
- echo "File to symlink" > 06-file-or-symlink &&
- echo "To be changed and have mode changed" > 07-change-mode-change &&
+ echo "To be changed" >01-change &&
+ echo "To be renamed" >02-pure-rename-from &&
+ echo "To be deleted" >03-delete &&
+ echo "To be renamed and changed" >04-rename-from &&
+ echo "To have mode changed" >05-mode-change &&
+ echo "File to symlink" >06-file-or-symlink &&
+ echo "To be changed and have mode changed" >07-change-mode-change &&
git add 0* &&
git commit -a -m "Prepare large commit" &&
- echo "Changed" > 01-change &&
+ echo "Changed" >01-change &&
git mv 02-pure-rename-from 02-pure-rename-to &&
git rm 03-delete && rm -f 03-delete &&
- echo "A new file" > 03-new &&
+ echo "A new file" >03-new &&
git add 03-new &&
git mv 04-rename-from 04-rename-to &&
- echo "Changed" >> 04-rename-to &&
+ echo "Changed" >>04-rename-to &&
test_chmod +x 05-mode-change &&
rm -f 06-file-or-symlink &&
test_ln_s_add 01-change 06-file-or-symlink &&
- echo "Changed and have mode changed" > 07-change-mode-change &&
+ echo "Changed and have mode changed" >07-change-mode-change &&
test_chmod +x 07-change-mode-change &&
git commit -a -m "Large commit" &&
git checkout master'
@@ -444,7 +444,7 @@ test_expect_success \
test_expect_success \
'logs: history (implicit HEAD, deleted file)' \
'git checkout master &&
- echo "to be deleted" > deleted_file &&
+ echo "to be deleted" >deleted_file &&
git add deleted_file &&
git commit -m "Add file to be deleted" &&
git rm deleted_file &&
@@ -522,7 +522,7 @@ test_expect_success \
'. "$TEST_DIRECTORY"/t3901/utf8.txt &&
test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
- echo "UTF-8" >> file &&
+ echo "UTF-8" >>file &&
git add file &&
git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt &&
gitweb_run "p=.git;a=commit"'
@@ -532,7 +532,7 @@ test_expect_success \
'. "$TEST_DIRECTORY"/t3901/8859-1.txt &&
test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" &&
test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
- echo "ISO-8859-1" >> file &&
+ echo "ISO-8859-1" >>file &&
git add file &&
test_config i18n.commitencoding ISO-8859-1 &&
git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
@@ -675,8 +675,8 @@ test_expect_success \
test_expect_success \
'README.html with non-ASCII characters (utf-8)' \
- 'echo "<b>UTF-8 example:</b><br />" > .git/README.html &&
- cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >> .git/README.html &&
+ 'echo "<b>UTF-8 example:</b><br />" >.git/README.html &&
+ cat "$TEST_DIRECTORY"/t3900/1-UTF-8.txt >>.git/README.html &&
gitweb_run "p=.git;a=summary"'
# ----------------------------------------------------------------------
@@ -704,7 +704,7 @@ test_expect_success HIGHLIGHT \
test_expect_success HIGHLIGHT \
'syntax highlighting (highlighted, shell script)' \
'git config gitweb.highlight yes &&
- echo "#!/usr/bin/sh" > test.sh &&
+ echo "#!/usr/bin/sh" >test.sh &&
git add test.sh &&
git commit -m "Add test.sh" &&
gitweb_run "p=.git;a=blob;f=test.sh"'
@@ -712,7 +712,7 @@ test_expect_success HIGHLIGHT \
test_expect_success HIGHLIGHT \
'syntax highlighting (highlighter language autodetection)' \
'git config gitweb.highlight yes &&
- echo "#!/usr/bin/perl" > test &&
+ echo "#!/usr/bin/perl" >test &&
git add test &&
git commit -m "Add test" &&
gitweb_run "p=.git;a=blob;f=test"'
@@ -729,11 +729,11 @@ test_expect_success \
'git init --bare foo.git &&
git --git-dir=foo.git --work-tree=. add file &&
git --git-dir=foo.git --work-tree=. commit -m "Initial commit" &&
- echo "foo" > foo.git/description &&
+ echo "foo" >foo.git/description &&
mkdir -p foo &&
(cd foo &&
git clone --shared --bare ../foo.git foo-forked.git &&
- echo "fork of foo" > foo-forked.git/description)'
+ echo "fork of foo" >foo-forked.git/description)'
test_expect_success \
'forks: projects list' \
@@ -754,8 +754,8 @@ EOF
test_expect_success \
'ctags: tag cloud in projects list' \
'mkdir .git/ctags &&
- echo "2" > .git/ctags/foo &&
- echo "1" > .git/ctags/bar &&
+ echo "2" >.git/ctags/foo &&
+ echo "1" >.git/ctags/bar &&
gitweb_run'
test_expect_success \
@@ -769,8 +769,8 @@ test_expect_success \
test_expect_success \
'ctags: malformed tag weights' \
'mkdir -p .git/ctags &&
- echo "not-a-number" > .git/ctags/nan &&
- echo "not-a-number-2" > .git/ctags/nan2 &&
+ echo "not-a-number" >.git/ctags/nan &&
+ echo "not-a-number-2" >.git/ctags/nan2 &&
echo "0.1" >.git/ctags/floating-point &&
gitweb_run'
diff --git a/t/t9819-git-p4-case-folding.sh b/t/t9819-git-p4-case-folding.sh
index 600ce1e0b0..b4d93f0c17 100755
--- a/t/t9819-git-p4-case-folding.sh
+++ b/t/t9819-git-p4-case-folding.sh
@@ -30,7 +30,7 @@ test_expect_success 'Check p4 is in case-folding mode' '
cd "$cli" &&
>lc/FILE.TXT &&
p4 add lc/FILE.TXT &&
- test_must_fail p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
+ ! p4 submit -d "Cannot add file differing only in case" lc/FILE.TXT
)
'
diff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh
index d743ca33ee..ff6c0352e6 100755
--- a/t/t9831-git-p4-triggers.sh
+++ b/t/t9831-git-p4-triggers.sh
@@ -58,7 +58,7 @@ test_expect_success 'import with extra info lines from verbose p4 trigger' '
(
cd "$git" &&
git p4 sync
- )&&
+ ) &&
(
p4 triggers -i <<-EOF
Triggers:
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 5505e5aa24..3c44af6940 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1638,6 +1638,11 @@ test_expect_success 'complete files' '
echo modify > modified &&
test_completion "git add " "modified" &&
+ mkdir -p some/deep &&
+ touch some/deep/path &&
+ test_completion "git add some/" "some/deep" &&
+ git clean -f some &&
+
touch untracked &&
: TODO .gitignore should not be here &&
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 352c213d52..3103be8a32 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -905,7 +905,7 @@ test_expect_code () {
# - not all diff versions understand "-u"
test_cmp() {
- $GIT_TEST_CMP "$@"
+ eval "$GIT_TEST_CMP" '"$@"'
}
# Check that the given config key has the expected value.
@@ -1362,14 +1362,22 @@ nongit () {
)
} 7>&2 2>&4
-# convert stdin to pktline representation; note that empty input becomes an
-# empty packet, not a flush packet (for that you can just print 0000 yourself).
+# convert function arguments or stdin (if not arguments given) to pktline
+# representation. If multiple arguments are given, they are separated by
+# whitespace and put in a single packet. Note that data containing NULs must be
+# given on stdin, and that empty input becomes an empty packet, not a flush
+# packet (for that you can just print 0000 yourself).
packetize() {
- cat >packetize.tmp &&
- len=$(wc -c <packetize.tmp) &&
- printf '%04x%s' "$(($len + 4))" &&
- cat packetize.tmp &&
- rm -f packetize.tmp
+ if test $# -gt 0
+ then
+ packet="$*"
+ printf '%04x%s' "$((4 + ${#packet}))" "$packet"
+ else
+ perl -e '
+ my $packet = do { local $/; <STDIN> };
+ printf "%04x%s", 4 + length($packet), $packet;
+ '
+ fi
}
# Parse the input as a series of pktlines, writing the result to stdout.
@@ -1543,3 +1551,13 @@ test_bitmap_traversal () {
test_cmp "$1.normalized" "$2.normalized" &&
rm -f "$1.normalized" "$2.normalized"
}
+
+# Tests for the hidden file attribute on Windows
+test_path_is_hidden () {
+ test_have_prereq MINGW ||
+ BUG "test_path_is_hidden can only be used on Windows"
+
+ # Use the output of `attrib`, ignore the absolute path
+ case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
+ return 1
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 9fe390bd5a..1b221951a8 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -78,20 +78,23 @@ then
exit 1
fi
-# Parse options while taking care to leave $@ intact, so we will still
-# have all the original command line options when executing the test
-# script again for '--tee' and '--verbose-log' below.
store_arg_to=
-prev_opt=
-for opt
-do
- if test -n "$store_arg_to"
+opt_required_arg=
+# $1: option string
+# $2: name of the var where the arg will be stored
+mark_option_requires_arg () {
+ if test -n "$opt_required_arg"
then
- eval $store_arg_to=\$opt
- store_arg_to=
- prev_opt=
- continue
+ echo "error: options that require args cannot be bundled" \
+ "together: '$opt_required_arg' and '$1'" >&2
+ exit 1
fi
+ opt_required_arg=$1
+ store_arg_to=$2
+}
+
+parse_option () {
+ local opt="$1"
case "$opt" in
-d|--d|--de|--deb|--debu|--debug)
@@ -101,7 +104,7 @@ do
-l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
GIT_TEST_LONG=t; export GIT_TEST_LONG ;;
-r)
- store_arg_to=run_list
+ mark_option_requires_arg "$opt" run_list
;;
--run=*)
run_list=${opt#--*=} ;;
@@ -185,12 +188,42 @@ do
*)
echo "error: unknown test option '$opt'" >&2; exit 1 ;;
esac
+}
+
+# Parse options while taking care to leave $@ intact, so we will still
+# have all the original command line options when executing the test
+# script again for '--tee' and '--verbose-log' later.
+for opt
+do
+ if test -n "$store_arg_to"
+ then
+ eval $store_arg_to=\$opt
+ store_arg_to=
+ opt_required_arg=
+ continue
+ fi
- prev_opt=$opt
+ case "$opt" in
+ --*|-?)
+ parse_option "$opt" ;;
+ -?*)
+ # bundled short options must be fed separately to parse_option
+ opt=${opt#-}
+ while test -n "$opt"
+ do
+ extra=${opt#?}
+ this=${opt%$extra}
+ opt=$extra
+ parse_option "-$this"
+ done
+ ;;
+ *)
+ echo "error: unknown test option '$opt'" >&2; exit 1 ;;
+ esac
done
if test -n "$store_arg_to"
then
- echo "error: $prev_opt requires an argument" >&2
+ echo "error: $opt_required_arg requires an argument" >&2
exit 1
fi
@@ -642,6 +675,18 @@ die () {
fi
}
+file_lineno () {
+ test -z "$GIT_TEST_FRAMEWORK_SELFTEST" && test -n "$BASH" || return 0
+ local i
+ for i in ${!BASH_SOURCE[*]}
+ do
+ case $i,"${BASH_SOURCE[$i]##*/}" in
+ 0,t[0-9]*.sh) echo "t/${BASH_SOURCE[$i]}:$LINENO: ${1+$1: }"; return;;
+ *,t[0-9]*.sh) echo "t/${BASH_SOURCE[$i]}:${BASH_LINENO[$(($i-1))]}: ${1+$1: }"; return;;
+ esac
+ done
+}
+
GIT_EXIT_OK=
trap 'die' EXIT
# Disable '-x' tracing, because with some shells, notably dash, it
@@ -687,7 +732,7 @@ test_failure_ () {
write_junit_xml_testcase "$1" " $junit_insert"
fi
test_failure=$(($test_failure + 1))
- say_color error "not ok $test_count - $1"
+ say_color error "$(file_lineno error)not ok $test_count - $1"
shift
printf '%s\n' "$*" | sed -e 's/^/# /'
test "$immediate" = "" || { finalize_junit_xml; GIT_EXIT_OK=t; exit 1; }
@@ -867,6 +912,7 @@ maybe_setup_valgrind () {
fi
}
+trace_level_=0
want_trace () {
test "$trace" = t && {
test "$verbose" = t || test "$verbose_log" = t
@@ -880,7 +926,7 @@ want_trace () {
test_eval_inner_ () {
# Do not add anything extra (including LF) after '$*'
eval "
- want_trace && set -x
+ want_trace && trace_level_=$(($trace_level_+1)) && set -x
$*"
}
@@ -911,7 +957,8 @@ test_eval_ () {
test_eval_ret_=$?
if want_trace
then
- set +x
+ test 1 = $trace_level_ && set +x
+ trace_level_=$(($trace_level_-1))
fi
} 2>/dev/null 4>&2
@@ -1070,6 +1117,7 @@ finalize_junit_xml () {
junit_time=$(test-tool date getnanos $junit_suite_start)
sed -e "s/\(<testsuite.*\) time=\"[^\"]*\"/\1/" \
-e "s/<testsuite [^>]*/& time=\"$junit_time\"/" \
+ -e '/^ *<\/testsuite/d' \
<"$junit_xml_path" >"$junit_xml_path.new"
mv "$junit_xml_path.new" "$junit_xml_path"
@@ -1618,6 +1666,15 @@ test_lazy_prereq ULIMIT_STACK_SIZE '
run_with_limited_stack true
'
+run_with_limited_open_files () {
+ (ulimit -n 32 && "$@")
+}
+
+test_lazy_prereq ULIMIT_FILE_DESCRIPTORS '
+ test_have_prereq !MINGW,!CYGWIN &&
+ run_with_limited_open_files true
+'
+
build_option () {
git version --build-options |
sed -ne "s/^$1: //p"
diff --git a/trace2.c b/trace2.c
index c7b4f14d29..2c6b570077 100644
--- a/trace2.c
+++ b/trace2.c
@@ -121,6 +121,7 @@ static void tr2main_atexit_handler(void)
tr2_sid_release();
tr2_cmd_name_release();
tr2_cfg_free_patterns();
+ tr2_cfg_free_env_vars();
tr2_sysenv_release();
trace2_enabled = 0;
@@ -311,6 +312,14 @@ void trace2_cmd_list_config_fl(const char *file, int line)
tr2_cfg_list_config_fl(file, line);
}
+void trace2_cmd_list_env_vars_fl(const char *file, int line)
+{
+ if (!trace2_enabled)
+ return;
+
+ tr2_list_env_vars_fl(file, line);
+}
+
void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
const char *value)
{
diff --git a/trace2.h b/trace2.h
index e5e81c0533..b18bc5529c 100644
--- a/trace2.h
+++ b/trace2.h
@@ -183,6 +183,19 @@ void trace2_cmd_list_config_fl(const char *file, int line);
#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)
/*
+ * Emit one or more 'def_param' events for "important" environment variables.
+ *
+ * Use the TR2_SYSENV_ENV_VARS setting to register a comma-separated list of
+ * environment variables considered important. For example:
+ * git config --system trace2.envVars 'GIT_HTTP_USER_AGENT,GIT_CONFIG'
+ * or:
+ * GIT_TRACE2_ENV_VARS="GIT_HTTP_USER_AGENT,GIT_CONFIG"
+ */
+void trace2_cmd_list_env_vars_fl(const char *file, int line);
+
+#define trace2_cmd_list_env_vars() trace2_cmd_list_env_vars_fl(__FILE__, __LINE__)
+
+/*
* Emit a "def_param" event for the given config key/value pair IF
* we consider the key to be "important".
*
diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c
index caa7f06948..ec9ac1a6ef 100644
--- a/trace2/tr2_cfg.c
+++ b/trace2/tr2_cfg.c
@@ -7,6 +7,10 @@ static struct strbuf **tr2_cfg_patterns;
static int tr2_cfg_count_patterns;
static int tr2_cfg_loaded;
+static struct strbuf **tr2_cfg_env_vars;
+static int tr2_cfg_env_vars_count;
+static int tr2_cfg_env_vars_loaded;
+
/*
* Parse a string containing a comma-delimited list of config keys
* or wildcard patterns into a list of strbufs.
@@ -46,6 +50,45 @@ void tr2_cfg_free_patterns(void)
tr2_cfg_loaded = 0;
}
+/*
+ * Parse a string containing a comma-delimited list of environment variable
+ * names into a list of strbufs.
+ */
+static int tr2_load_env_vars(void)
+{
+ struct strbuf **s;
+ const char *varlist;
+
+ if (tr2_cfg_env_vars_loaded)
+ return tr2_cfg_env_vars_count;
+ tr2_cfg_env_vars_loaded = 1;
+
+ varlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS);
+ if (!varlist || !*varlist)
+ return tr2_cfg_env_vars_count;
+
+ tr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1);
+ for (s = tr2_cfg_env_vars; *s; s++) {
+ struct strbuf *buf = *s;
+
+ if (buf->len && buf->buf[buf->len - 1] == ',')
+ strbuf_setlen(buf, buf->len - 1);
+ strbuf_trim_trailing_newline(*s);
+ strbuf_trim(*s);
+ }
+
+ tr2_cfg_env_vars_count = s - tr2_cfg_env_vars;
+ return tr2_cfg_env_vars_count;
+}
+
+void tr2_cfg_free_env_vars(void)
+{
+ if (tr2_cfg_env_vars)
+ strbuf_list_free(tr2_cfg_env_vars);
+ tr2_cfg_env_vars_count = 0;
+ tr2_cfg_env_vars_loaded = 0;
+}
+
struct tr2_cfg_data {
const char *file;
int line;
@@ -79,6 +122,21 @@ void tr2_cfg_list_config_fl(const char *file, int line)
read_early_config(tr2_cfg_cb, &data);
}
+void tr2_list_env_vars_fl(const char *file, int line)
+{
+ struct strbuf **s;
+
+ if (tr2_load_env_vars() <= 0)
+ return;
+
+ for (s = tr2_cfg_env_vars; *s; s++) {
+ struct strbuf *buf = *s;
+ const char *val = getenv(buf->buf);
+ if (val && *val)
+ trace2_def_param_fl(file, line, buf->buf, val);
+ }
+}
+
void tr2_cfg_set_fl(const char *file, int line, const char *key,
const char *value)
{
diff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h
index d9c98f64dd..a11d71feb5 100644
--- a/trace2/tr2_cfg.h
+++ b/trace2/tr2_cfg.h
@@ -8,6 +8,12 @@
void tr2_cfg_list_config_fl(const char *file, int line);
/*
+ * Iterate over all "interesting" environment variables and emit 'def_param'
+ * events for them to TRACE2.
+ */
+void tr2_list_env_vars_fl(const char *file, int line);
+
+/*
* Emit a "def_param" event for the given key/value pair IF we consider
* the key to be "interesting".
*/
@@ -16,4 +22,6 @@ void tr2_cfg_set_fl(const char *file, int line, const char *key,
void tr2_cfg_free_patterns(void);
+void tr2_cfg_free_env_vars(void);
+
#endif /* TR2_CFG_H */
diff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c
index 3c3792eca2..a380dcf910 100644
--- a/trace2/tr2_sysenv.c
+++ b/trace2/tr2_sysenv.c
@@ -29,6 +29,8 @@ struct tr2_sysenv_entry {
static struct tr2_sysenv_entry tr2_sysenv_settings[] = {
[TR2_SYSENV_CFG_PARAM] = { "GIT_TRACE2_CONFIG_PARAMS",
"trace2.configparams" },
+ [TR2_SYSENV_ENV_VARS] = { "GIT_TRACE2_ENV_VARS",
+ "trace2.envvars" },
[TR2_SYSENV_DST_DEBUG] = { "GIT_TRACE2_DST_DEBUG",
"trace2.destinationdebug" },
diff --git a/trace2/tr2_sysenv.h b/trace2/tr2_sysenv.h
index d4364a7b85..3292ee15bc 100644
--- a/trace2/tr2_sysenv.h
+++ b/trace2/tr2_sysenv.h
@@ -11,6 +11,7 @@
*/
enum tr2_sysenv_variable {
TR2_SYSENV_CFG_PARAM = 0,
+ TR2_SYSENV_ENV_VARS,
TR2_SYSENV_DST_DEBUG,
diff --git a/transport-helper.c b/transport-helper.c
index 20a7185ec4..a46afcb69d 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -894,6 +894,7 @@ static int push_refs_with_push(struct transport *transport,
case REF_STATUS_REJECT_STALE:
case REF_STATUS_REJECT_ALREADY_EXISTS:
if (atomic) {
+ reject_atomic_push(remote_refs, mirror);
string_list_clear(&cas_options, 0);
return 0;
} else
@@ -1488,3 +1489,25 @@ int bidirectional_transfer_loop(int input, int output)
return tloop_spawnwait_tasks(&state);
}
+
+void reject_atomic_push(struct ref *remote_refs, int mirror_mode)
+{
+ struct ref *ref;
+
+ /* Mark other refs as failed */
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!ref->peer_ref && !mirror_mode)
+ continue;
+
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_OK:
+ case REF_STATUS_EXPECTING_REPORT:
+ ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+ continue;
+ default:
+ break; /* do nothing */
+ }
+ }
+ return;
+}
diff --git a/transport.c b/transport.c
index 1fdc7dac1a..15f5ba4e8f 100644
--- a/transport.c
+++ b/transport.c
@@ -16,7 +16,7 @@
#include "url.h"
#include "submodule.h"
#include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "sigchain.h"
#include "transport-internal.h"
#include "protocol.h"
@@ -715,7 +715,15 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
close(data->fd[1]);
close(data->fd[0]);
- ret |= finish_connect(data->conn);
+ /*
+ * Atomic push may abort the connection early and close the pipe,
+ * which may cause an error for `finish_connect()`. Ignore this error
+ * for atomic git-push.
+ */
+ if (ret || args.atomic)
+ finish_connect(data->conn);
+ else
+ ret = finish_connect(data->conn);
data->conn = NULL;
data->got_remote_heads = 0;
@@ -1240,20 +1248,6 @@ int transport_push(struct repository *r,
err = push_had_errors(remote_refs);
ret = push_ret | err;
- if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
- struct ref *it;
- for (it = remote_refs; it; it = it->next)
- switch (it->status) {
- case REF_STATUS_NONE:
- case REF_STATUS_UPTODATE:
- case REF_STATUS_OK:
- it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
- break;
- default:
- break;
- }
- }
-
if (!quiet || err)
transport_print_push_status(transport->url, remote_refs,
verbose | porcelain, porcelain,
diff --git a/transport.h b/transport.h
index e0131daab9..4298c855be 100644
--- a/transport.h
+++ b/transport.h
@@ -265,4 +265,7 @@ int transport_refs_pushed(struct ref *ref);
void transport_print_push_status(const char *dest, struct ref *refs,
int verbose, int porcelain, unsigned int *reject_reasons);
+/* common method used by transport-helper.c and send-pack.c */
+void reject_atomic_push(struct ref *refs, int mirror_mode);
+
#endif
diff --git a/tree-diff.c b/tree-diff.c
index 33ded7f8b3..f3d303c6e5 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -434,6 +434,9 @@ static struct combine_diff_path *ll_diff_tree_paths(
if (diff_can_quit_early(opt))
break;
+ if (opt->max_changes && opt->num_changes > opt->max_changes)
+ break;
+
if (opt->pathspec.nr) {
skip_uninteresting(&t, base, opt);
for (i = 0; i < nparent; i++)
@@ -518,6 +521,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
/* t↓ */
update_tree_entry(&t);
+ opt->num_changes++;
}
/* t > p[imin] */
@@ -535,6 +539,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
skip_emit_tp:
/* ∀ pi=p[imin] pi↓ */
update_tp_entries(tp, nparent);
+ opt->num_changes++;
}
}
@@ -552,6 +557,7 @@ struct combine_diff_path *diff_tree_paths(
const struct object_id **parents_oid, int nparent,
struct strbuf *base, struct diff_options *opt)
{
+ opt->num_changes = 0;
p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt);
/*
diff --git a/unpack-trees.c b/unpack-trees.c
index f618a644ef..6bbf58d28e 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -24,7 +24,7 @@
* situation better. See how "git checkout" and "git merge" replaces
* them using setup_unpack_trees_porcelain(), for example.
*/
-static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
+static const char *unpack_plumbing_errors[NB_UNPACK_TREES_WARNING_TYPES] = {
/* ERROR_WOULD_OVERWRITE */
"Entry '%s' would be overwritten by merge. Cannot merge.",
@@ -43,17 +43,20 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
/* ERROR_BIND_OVERLAP */
"Entry '%s' overlaps with '%s'. Cannot bind.",
- /* ERROR_SPARSE_NOT_UPTODATE_FILE */
- "Entry '%s' not uptodate. Cannot update sparse checkout.",
+ /* ERROR_WOULD_LOSE_SUBMODULE */
+ "Submodule '%s' cannot checkout new HEAD.",
- /* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */
- "Working tree file '%s' would be overwritten by sparse checkout update.",
+ /* NB_UNPACK_TREES_ERROR_TYPES; just a meta value */
+ "",
- /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
- "Working tree file '%s' would be removed by sparse checkout update.",
+ /* WARNING_SPARSE_NOT_UPTODATE_FILE */
+ "Path '%s' not uptodate; will not remove from working tree.",
- /* ERROR_WOULD_LOSE_SUBMODULE */
- "Submodule '%s' cannot checkout new HEAD.",
+ /* WARNING_SPARSE_UNMERGED_FILE */
+ "Path '%s' unmerged; will not remove from working tree.",
+
+ /* WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN */
+ "Path '%s' already present; will not overwrite with sparse update.",
};
#define ERRORMSG(o,type) \
@@ -168,15 +171,16 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
*/
msgs[ERROR_BIND_OVERLAP] = _("Entry '%s' overlaps with '%s'. Cannot bind.");
- msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
- _("Cannot update sparse checkout: the following entries are not up to date:\n%s");
- msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
- _("The following working tree files would be overwritten by sparse checkout update:\n%s");
- msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
- _("The following working tree files would be removed by sparse checkout update:\n%s");
msgs[ERROR_WOULD_LOSE_SUBMODULE] =
_("Cannot update submodule:\n%s");
+ msgs[WARNING_SPARSE_NOT_UPTODATE_FILE] =
+ _("The following paths are not up to date and were left despite sparse patterns:\n%s");
+ msgs[WARNING_SPARSE_UNMERGED_FILE] =
+ _("The following paths are unmerged and were left despite sparse patterns:\n%s");
+ msgs[WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN] =
+ _("The following paths were already present and thus not updated despite sparse patterns:\n%s");
+
opts->show_all_errors = 1;
/* rejected paths may not have a static buffer */
for (i = 0; i < ARRAY_SIZE(opts->unpack_rejects); i++)
@@ -226,7 +230,7 @@ static int add_rejected_path(struct unpack_trees_options *o,
/*
* Otherwise, insert in a list for future display by
- * display_error_msgs()
+ * display_(error|warning)_msgs()
*/
string_list_append(&o->unpack_rejects[e], path);
return -1;
@@ -237,13 +241,16 @@ static int add_rejected_path(struct unpack_trees_options *o,
*/
static void display_error_msgs(struct unpack_trees_options *o)
{
- int e, i;
- int something_displayed = 0;
+ int e;
+ unsigned error_displayed = 0;
for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
struct string_list *rejects = &o->unpack_rejects[e];
+
if (rejects->nr > 0) {
+ int i;
struct strbuf path = STRBUF_INIT;
- something_displayed = 1;
+
+ error_displayed = 1;
for (i = 0; i < rejects->nr; i++)
strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
error(ERRORMSG(o, e), super_prefixed(path.buf));
@@ -251,10 +258,36 @@ static void display_error_msgs(struct unpack_trees_options *o)
}
string_list_clear(rejects, 0);
}
- if (something_displayed)
+ if (error_displayed)
fprintf(stderr, _("Aborting\n"));
}
+/*
+ * display all the warning messages stored in a nice way
+ */
+static void display_warning_msgs(struct unpack_trees_options *o)
+{
+ int e;
+ unsigned warning_displayed = 0;
+ for (e = NB_UNPACK_TREES_ERROR_TYPES + 1;
+ e < NB_UNPACK_TREES_WARNING_TYPES; e++) {
+ struct string_list *rejects = &o->unpack_rejects[e];
+
+ if (rejects->nr > 0) {
+ int i;
+ struct strbuf path = STRBUF_INIT;
+
+ warning_displayed = 1;
+ for (i = 0; i < rejects->nr; i++)
+ strbuf_addf(&path, "\t%s\n", rejects->items[i].string);
+ warning(ERRORMSG(o, e), super_prefixed(path.buf));
+ strbuf_release(&path);
+ }
+ string_list_clear(rejects, 0);
+ }
+ if (warning_displayed)
+ fprintf(stderr, _("After fixing the above paths, you may want to run `git sparse-checkout reapply`.\n"));
+}
static int check_submodule_move_head(const struct cache_entry *ce,
const char *old_id,
const char *new_id,
@@ -357,12 +390,12 @@ static void report_collided_checkout(struct index_state *index)
string_list_clear(&list, 0);
}
-static int check_updates(struct unpack_trees_options *o)
+static int check_updates(struct unpack_trees_options *o,
+ struct index_state *index)
{
unsigned cnt = 0;
int errs = 0;
struct progress *progress;
- struct index_state *index = &o->result;
struct checkout state = CHECKOUT_INIT;
int i;
@@ -423,9 +456,8 @@ static int check_updates(struct unpack_trees_options *o)
continue;
oid_array_append(&to_fetch, &ce->oid);
}
- if (to_fetch.nr)
- promisor_remote_get_direct(the_repository,
- to_fetch.oid, to_fetch.nr);
+ promisor_remote_get_direct(the_repository,
+ to_fetch.oid, to_fetch.nr);
oid_array_clear(&to_fetch);
}
for (i = 0; i < index->cache_nr; i++) {
@@ -505,19 +537,39 @@ static int apply_sparse_checkout(struct index_state *istate,
* also stat info may have lost after merged_entry() so calling
* verify_uptodate() again may fail
*/
- if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
+ if (!(ce->ce_flags & CE_UPDATE) &&
+ verify_uptodate_sparse(ce, o)) {
+ ce->ce_flags &= ~CE_SKIP_WORKTREE;
return -1;
+ }
ce->ce_flags |= CE_WT_REMOVE;
ce->ce_flags &= ~CE_UPDATE;
}
if (was_skip_worktree && !ce_skip_worktree(ce)) {
- if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+ if (verify_absent_sparse(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
return -1;
ce->ce_flags |= CE_UPDATE;
}
return 0;
}
+static int warn_conflicted_path(struct index_state *istate,
+ int i,
+ struct unpack_trees_options *o)
+{
+ char *conflicting_path = istate->cache[i]->name;
+ int count = 0;
+
+ add_rejected_path(o, WARNING_SPARSE_UNMERGED_FILE, conflicting_path);
+
+ /* Find out how many higher stage entries at same path */
+ while (++count < istate->cache_nr &&
+ !strcmp(conflicting_path,
+ istate->cache[i+count]->name))
+ /* do nothing */;
+ return count;
+}
+
static inline int call_unpack_fn(const struct cache_entry * const *src,
struct unpack_trees_options *o)
{
@@ -1494,6 +1546,20 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress);
}
+static void populate_from_existing_patterns(struct unpack_trees_options *o,
+ struct pattern_list *pl)
+{
+ char *sparse = git_pathdup("info/sparse-checkout");
+
+ pl->use_cone_patterns = core_sparse_checkout_cone;
+ if (add_patterns_from_file_to_list(sparse, "", 0, pl, NULL) < 0)
+ o->skip_sparse_checkout = 1;
+ else
+ o->pl = pl;
+ free(sparse);
+}
+
+
static int verify_absent(const struct cache_entry *,
enum unpack_trees_error_types,
struct unpack_trees_options *);
@@ -1508,22 +1574,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
int i, ret;
static struct cache_entry *dfc;
struct pattern_list pl;
+ int free_pattern_list = 0;
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
trace_performance_enter();
- memset(&pl, 0, sizeof(pl));
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout && !o->pl) {
- char *sparse = git_pathdup("info/sparse-checkout");
- pl.use_cone_patterns = core_sparse_checkout_cone;
- if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0)
- o->skip_sparse_checkout = 1;
- else
- o->pl = &pl;
- free(sparse);
+ memset(&pl, 0, sizeof(pl));
+ free_pattern_list = 1;
+ populate_from_existing_patterns(o, &pl);
}
memset(&o->result, 0, sizeof(o->result));
@@ -1619,7 +1681,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
/*
* Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
- * If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
+ * If they will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
* so apply_sparse_checkout() won't attempt to remove it from worktree
*/
mark_new_skip_worktree(o->pl, &o->result,
@@ -1639,23 +1701,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
* correct CE_NEW_SKIP_WORKTREE
*/
if (ce->ce_flags & CE_ADDED &&
- verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
- if (!o->show_all_errors)
- goto return_failed;
- ret = -1;
- }
+ verify_absent(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
+ ret = 1;
+
+ if (apply_sparse_checkout(&o->result, ce, o))
+ ret = 1;
- if (apply_sparse_checkout(&o->result, ce, o)) {
- if (!o->show_all_errors)
- goto return_failed;
- ret = -1;
- }
if (!ce_skip_worktree(ce))
empty_worktree = 0;
-
}
- if (ret < 0)
- goto return_failed;
/*
* Sparse checkout is meant to narrow down checkout area
* but it does not make sense to narrow down to empty working
@@ -1666,9 +1720,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
goto done;
}
+ if (ret == 1) {
+ /*
+ * Inability to sparsify or de-sparsify individual
+ * paths is not an error, but just a warning.
+ */
+ if (o->show_all_errors)
+ display_warning_msgs(o);
+ ret = 0;
+ }
}
- ret = check_updates(o) ? (-2) : 0;
+ ret = check_updates(o, &o->result) ? (-2) : 0;
if (o->dst_index) {
move_index_extensions(&o->result, o->src_index);
if (!ret) {
@@ -1691,9 +1754,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->src_index = NULL;
done:
- trace_performance_leave("unpack_trees");
- if (!o->keep_pattern_list)
+ if (free_pattern_list)
clear_pattern_list(&pl);
+ trace_performance_leave("unpack_trees");
return ret;
return_failed:
@@ -1706,6 +1769,91 @@ return_failed:
goto done;
}
+/*
+ * Update SKIP_WORKTREE bits according to sparsity patterns, and update
+ * working directory to match.
+ *
+ * CE_NEW_SKIP_WORKTREE is used internally.
+ */
+enum update_sparsity_result update_sparsity(struct unpack_trees_options *o)
+{
+ enum update_sparsity_result ret = UPDATE_SPARSITY_SUCCESS;
+ struct pattern_list pl;
+ int i, empty_worktree;
+ unsigned old_show_all_errors;
+ int free_pattern_list = 0;
+
+ old_show_all_errors = o->show_all_errors;
+ o->show_all_errors = 1;
+
+ /* Sanity checks */
+ if (!o->update || o->index_only || o->skip_sparse_checkout)
+ BUG("update_sparsity() is for reflecting sparsity patterns in working directory");
+ if (o->src_index != o->dst_index || o->fn)
+ BUG("update_sparsity() called wrong");
+
+ trace_performance_enter();
+
+ /* If we weren't given patterns, use the recorded ones */
+ if (!o->pl) {
+ memset(&pl, 0, sizeof(pl));
+ free_pattern_list = 1;
+ populate_from_existing_patterns(o, &pl);
+ if (o->skip_sparse_checkout)
+ goto skip_sparse_checkout;
+ }
+
+ /* Set NEW_SKIP_WORKTREE on existing entries. */
+ mark_all_ce_unused(o->src_index);
+ mark_new_skip_worktree(o->pl, o->src_index, 0,
+ CE_NEW_SKIP_WORKTREE, o->verbose_update);
+
+ /* Then loop over entries and update/remove as needed */
+ ret = UPDATE_SPARSITY_SUCCESS;
+ empty_worktree = 1;
+ for (i = 0; i < o->src_index->cache_nr; i++) {
+ struct cache_entry *ce = o->src_index->cache[i];
+
+
+ if (ce_stage(ce)) {
+ /* -1 because for loop will increment by 1 */
+ i += warn_conflicted_path(o->src_index, i, o) - 1;
+ ret = UPDATE_SPARSITY_WARNINGS;
+ continue;
+ }
+
+ if (apply_sparse_checkout(o->src_index, ce, o))
+ ret = UPDATE_SPARSITY_WARNINGS;
+
+ if (!ce_skip_worktree(ce))
+ empty_worktree = 0;
+ }
+
+ /*
+ * Sparse checkout is meant to narrow down checkout area
+ * but it does not make sense to narrow down to empty working
+ * tree. This is usually a mistake in sparse checkout rules.
+ * Do not allow users to do that.
+ */
+ if (o->src_index->cache_nr && empty_worktree) {
+ unpack_failed(o, "Sparse checkout leaves no entry on working directory");
+ ret = UPDATE_SPARSITY_INDEX_UPDATE_FAILURES;
+ goto done;
+ }
+
+skip_sparse_checkout:
+ if (check_updates(o, o->src_index))
+ ret = UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES;
+
+done:
+ display_warning_msgs(o);
+ o->show_all_errors = old_show_all_errors;
+ if (free_pattern_list)
+ clear_pattern_list(&pl);
+ trace_performance_leave("update_sparsity");
+ return ret;
+}
+
/* Here come the merge functions */
static int reject_merge(const struct cache_entry *ce,
@@ -1790,7 +1938,7 @@ int verify_uptodate(const struct cache_entry *ce,
static int verify_uptodate_sparse(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
- return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
+ return verify_uptodate_1(ce, o, WARNING_SPARSE_NOT_UPTODATE_FILE);
}
/*
@@ -2028,11 +2176,7 @@ static int verify_absent_sparse(const struct cache_entry *ce,
enum unpack_trees_error_types error_type,
struct unpack_trees_options *o)
{
- enum unpack_trees_error_types orphaned_error = error_type;
- if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
- orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
-
- return verify_absent_1(ce, orphaned_error, o);
+ return verify_absent_1(ce, error_type, o);
}
static int merged_entry(const struct cache_entry *ce,
diff --git a/unpack-trees.h b/unpack-trees.h
index ad41b45a71..9c2f08277e 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -22,11 +22,15 @@ enum unpack_trees_error_types {
ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,
ERROR_WOULD_LOSE_UNTRACKED_REMOVED,
ERROR_BIND_OVERLAP,
- ERROR_SPARSE_NOT_UPTODATE_FILE,
- ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN,
- ERROR_WOULD_LOSE_ORPHANED_REMOVED,
ERROR_WOULD_LOSE_SUBMODULE,
- NB_UNPACK_TREES_ERROR_TYPES
+
+ NB_UNPACK_TREES_ERROR_TYPES,
+
+ WARNING_SPARSE_NOT_UPTODATE_FILE,
+ WARNING_SPARSE_UNMERGED_FILE,
+ WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN,
+
+ NB_UNPACK_TREES_WARNING_TYPES,
};
/*
@@ -59,20 +63,19 @@ struct unpack_trees_options {
quiet,
exiting_early,
show_all_errors,
- dry_run,
- keep_pattern_list;
+ dry_run;
const char *prefix;
int cache_bottom;
struct dir_struct *dir;
struct pathspec *pathspec;
merge_fn_t fn;
- const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
+ const char *msgs[NB_UNPACK_TREES_WARNING_TYPES];
struct argv_array msgs_to_free;
/*
* Store error messages in an array, each case
* corresponding to a error message type
*/
- struct string_list unpack_rejects[NB_UNPACK_TREES_ERROR_TYPES];
+ struct string_list unpack_rejects[NB_UNPACK_TREES_WARNING_TYPES];
int head_idx;
int merge_size;
@@ -91,6 +94,15 @@ struct unpack_trees_options {
int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
+enum update_sparsity_result {
+ UPDATE_SPARSITY_SUCCESS = 0,
+ UPDATE_SPARSITY_WARNINGS = 1,
+ UPDATE_SPARSITY_INDEX_UPDATE_FAILURES = -1,
+ UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES = -2
+};
+
+enum update_sparsity_result update_sparsity(struct unpack_trees_options *options);
+
int verify_uptodate(const struct cache_entry *ce,
struct unpack_trees_options *o);
diff --git a/upload-pack.c b/upload-pack.c
index c53249cac1..902d0ad5e1 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1252,7 +1252,7 @@ static void process_args(struct packet_reader *request,
struct upload_pack_data *data,
struct object_array *want_obj)
{
- while (packet_reader_read(request) != PACKET_READ_FLUSH) {
+ while (packet_reader_read(request) == PACKET_READ_NORMAL) {
const char *arg = request->line;
const char *p;
@@ -1321,6 +1321,9 @@ static void process_args(struct packet_reader *request,
/* ignore unknown lines maybe? */
die("unexpected line: '%s'", arg);
}
+
+ if (request->status != PACKET_READ_FLUSH)
+ die(_("expected flush after fetch arguments"));
}
static int process_haves(struct oid_array *haves, struct oid_array *common,
diff --git a/userdiff.c b/userdiff.c
index efbe05e5a5..30ab42df8e 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -222,7 +222,7 @@ static struct userdiff_driver driver_false = {
{ NULL, 0 }
};
-static struct userdiff_driver *userdiff_find_by_namelen(const char *k, int len)
+static struct userdiff_driver *userdiff_find_by_namelen(const char *k, size_t len)
{
int i;
for (i = 0; i < ndrivers; i++) {
@@ -266,7 +266,7 @@ int userdiff_config(const char *k, const char *v)
{
struct userdiff_driver *drv;
const char *name, *type;
- int namelen;
+ size_t namelen;
if (parse_config_key(k, "diff", &name, &namelen, &type) || !name)
return 0;
diff --git a/wrapper.c b/wrapper.c
index e1eaef2e16..3a1c0e0526 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -218,7 +218,7 @@ ssize_t xread(int fd, void *buf, size_t len)
{
ssize_t nr;
if (len > MAX_IO_SIZE)
- len = MAX_IO_SIZE;
+ len = MAX_IO_SIZE;
while (1) {
nr = read(fd, buf, len);
if (nr < 0) {
@@ -240,7 +240,7 @@ ssize_t xwrite(int fd, const void *buf, size_t len)
{
ssize_t nr;
if (len > MAX_IO_SIZE)
- len = MAX_IO_SIZE;
+ len = MAX_IO_SIZE;
while (1) {
nr = write(fd, buf, len);
if (nr < 0) {
diff --git a/wt-status.c b/wt-status.c
index cc6f94504d..98dfa6f73f 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -722,16 +722,14 @@ static void wt_status_collect_untracked(struct wt_status *s)
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (index_name_is_other(istate, ent->name, ent->len) &&
- dir_path_match(istate, ent, &s->pathspec, 0, NULL))
+ if (index_name_is_other(istate, ent->name, ent->len))
string_list_insert(&s->untracked, ent->name);
free(ent);
}
for (i = 0; i < dir.ignored_nr; i++) {
struct dir_entry *ent = dir.ignored[i];
- if (index_name_is_other(istate, ent->name, ent->len) &&
- dir_path_match(istate, ent, &s->pathspec, 0, NULL))
+ if (index_name_is_other(istate, ent->name, ent->len))
string_list_insert(&s->ignored, ent->name);
free(ent);
}