]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/codingstyle-compare-with-null'
authorJunio C Hamano <gitster@pobox.com>
Thu, 14 May 2020 21:39:42 +0000 (14:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 14 May 2020 21:39:42 +0000 (14:39 -0700)
Doc update.

* jc/codingstyle-compare-with-null:
  CodingGuidelines: do not ==/!= compare with 0 or '\0' or NULL

453 files changed:
.github/workflows/main.yml [new file with mode: 0644]
.gitignore
.travis.yml
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/MyFirstObjectWalk.txt
Documentation/RelNotes/2.27.0.txt [new file with mode: 0644]
Documentation/asciidoc.conf
Documentation/config.txt
Documentation/config/credential.txt
Documentation/config/feature.txt
Documentation/config/fetch.txt
Documentation/config/format.txt
Documentation/config/http.txt
Documentation/config/log.txt
Documentation/config/merge.txt
Documentation/config/pack.txt
Documentation/config/protocol.txt
Documentation/config/push.txt
Documentation/config/stash.txt
Documentation/config/submodule.txt
Documentation/config/tag.txt
Documentation/config/tar.txt [new file with mode: 0644]
Documentation/config/trace2.txt
Documentation/date-formats.txt
Documentation/fetch-options.txt
Documentation/git-am.txt
Documentation/git-bugreport.txt [new file with mode: 0644]
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone.txt
Documentation/git-commit-graph.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-credential-store.txt
Documentation/git-credential.txt
Documentation/git-fast-import.txt
Documentation/git-format-patch.txt
Documentation/git-grep.txt
Documentation/git-init.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-merge.txt
Documentation/git-p4.txt
Documentation/git-pack-objects.txt
Documentation/git-pull.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-reset.txt
Documentation/git-restore.txt
Documentation/git-revert.txt
Documentation/git-sparse-checkout.txt
Documentation/git-switch.txt
Documentation/git-update-ref.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcredentials.txt
Documentation/gitfaq.txt [new file with mode: 0644]
Documentation/githooks.txt
Documentation/gitsubmodules.txt
Documentation/howto/maintain-git.txt
Documentation/manpage-1.72.xsl [deleted file]
Documentation/manpage-base.xsl [deleted file]
Documentation/manpage-bold-literal.xsl
Documentation/manpage-normal.xsl
Documentation/manpage-suppress-sp.xsl [deleted file]
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/revisions.txt
Documentation/technical/api-trace2.txt
Documentation/technical/commit-graph-format.txt
Documentation/user-manual.conf
GIT-VERSION-GEN
INSTALL
Makefile
README.md
RelNotes
abspath.c
add-interactive.c
add-patch.c
advice.c
advice.h
apply.c
archive-tar.c
archive.c
archive.h
azure-pipelines.yml [deleted file]
bisect.c
blame.c
blame.h
bloom.c [new file with mode: 0644]
bloom.h [new file with mode: 0644]
branch.c
bugreport.c [new file with mode: 0644]
builtin.h
builtin/add.c
builtin/am.c
builtin/blame.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout-index.c
builtin/checkout.c
builtin/clean.c
builtin/clone.c
builtin/commit-graph.c
builtin/commit-tree.c
builtin/commit.c
builtin/describe.c
builtin/diff-tree.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/grep.c
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/interpret-trailers.c
builtin/log.c
builtin/ls-files.c
builtin/merge-base.c
builtin/merge.c
builtin/notes.c
builtin/pack-objects.c
builtin/prune-packed.c
builtin/prune.c
builtin/pull.c
builtin/push.c
builtin/read-tree.c
builtin/rebase.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/repack.c
builtin/reset.c
builtin/rev-parse.c
builtin/send-pack.c
builtin/shortlog.c
builtin/show-branch.c
builtin/show-ref.c
builtin/sparse-checkout.c
builtin/stash.c
builtin/submodule--helper.c
builtin/tag.c
builtin/update-index.c
builtin/update-ref.c
builtin/worktree.c
cache.h
ci/config/allow-refs.sample [new file with mode: 0755]
ci/git-problem-matcher.json [new file with mode: 0644]
ci/install-dependencies.sh
ci/install-docker-dependencies.sh [new file with mode: 0755]
ci/lib.sh
ci/print-test-failures.sh
ci/run-build-and-tests.sh
ci/run-docker-build.sh [moved from ci/run-linux32-build.sh with 63% similarity]
ci/run-docker.sh [new file with mode: 0755]
ci/run-linux32-docker.sh [deleted file]
combine-diff.c
command-list.txt
commit-graph.c
commit-graph.h
commit-slab.h
commit.c
commit.h
compat/compiler.h [new file with mode: 0644]
compat/mingw.c
compat/regex/regex.c
compat/regex/regex_internal.h
compat/vcbuild/README
compat/win32/path-utils.h
config.c
config.h
config.mak.dev
config.mak.uname
connect.c
connected.c
connected.h
contrib/completion/git-completion.bash
contrib/completion/git-completion.zsh
contrib/fast-import/import-tars.perl
contrib/subtree/Makefile
convert.c
convert.h
credential-store.c
credential.c
credential.h
csum-file.c
date.c
delta-islands.c
diff.c
diff.h
diffcore-break.c
diffcore-rename.c
diffcore.h
dir.c
editor.c
entry.c
environment.c
fast-import.c
fetch-pack.c
fmt-merge-msg.c [new file with mode: 0644]
fmt-merge-msg.h
fsck.c
fuzz-commit-graph.c
generate-cmdlist.sh
generate-configlist.sh [new file with mode: 0755]
git-bisect.sh
git-compat-util.h
git-legacy-stash.sh [deleted file]
git-p4.py
git-submodule.sh
git.c
gitweb/gitweb.perl
gpg-interface.c
gpg-interface.h
graph.c
hash.h
help.c
help.h
hex.c
http.c
line-log.c
list-objects-filter-options.h
list-objects-filter.c
ll-merge.c
lockfile.c
lockfile.h
log-tree.c
log-tree.h
ls-refs.c
mailinfo.c
merge-recursive.c
merge.c
midx.c
midx.h
object-store.h
object.h
oid-array.c [moved from sha1-array.c with 93% similarity]
oid-array.h [moved from sha1-array.h with 97% similarity]
oidset.c
oidset.h
pack-bitmap.c
parse-options-cb.c
parse-options.c
parse-options.h
path.c
path.h
pretty.c
pretty.h
progress.c
progress.h
promisor-remote.c
promisor-remote.h
prompt.c
prompt.h
protocol.c
prune-packed.c [new file with mode: 0644]
prune-packed.h [new file with mode: 0644]
range-diff.c
ref-filter.c
ref-filter.h
refs.c
refs.h
refs/files-backend.c
remote-curl.c
remote.c
repo-settings.c
repository.c
repository.h
reset.c [new file with mode: 0644]
reset.h [new file with mode: 0644]
revision.c
revision.h
run-command.c
run-command.h
send-pack.c
sequencer.c
sequencer.h
setup.c
sha1-file.c
sha1-name.c
sha256/gcrypt.h
shallow.c
shallow.h [new file with mode: 0644]
shell.c
strbuf.c
strbuf.h
submodule-config.c
submodule.c
submodule.h
t/README
t/helper/test-advise.c [new file with mode: 0644]
t/helper/test-bloom.c [new file with mode: 0644]
t/helper/test-dump-split-index.c
t/helper/test-oid-array.c [moved from t/helper/test-sha1-array.c with 83% similarity]
t/helper/test-parse-pathspec-file.c
t/helper/test-path-utils.c
t/helper/test-pkt-line.c
t/helper/test-progress.c
t/helper/test-read-graph.c
t/helper/test-repository.c
t/helper/test-tool.c
t/helper/test-tool.h
t/lib-credential.sh [changed mode: 0755->0644]
t/lib-gpg.sh [changed mode: 0755->0644]
t/lib-log-graph.sh [changed mode: 0755->0644]
t/lib-submodule-update.sh [changed mode: 0755->0644]
t/perf/p5310-pack-bitmaps.sh
t/perf/p9300-fast-import-export.sh [new file with mode: 0755]
t/t0000-basic.sh
t/t0001-init.sh
t/t0006-date.sh
t/t0007-git-var.sh
t/t0018-advice.sh [new file with mode: 0755]
t/t0021-conversion.sh
t/t0021/rot13-filter.pl
t/t0040-parse-options.sh
t/t0060-path-utils.sh
t/t0064-sha1-array.sh
t/t0091-bugreport.sh [new file with mode: 0755]
t/t0095-bloom.sh [new file with mode: 0755]
t/t0212-trace2-event.sh
t/t0300-credentials.sh
t/t0302-credential-store.sh
t/t1011-read-tree-sparse-checkout.sh
t/t1091-sparse-checkout-builtin.sh
t/t1400-update-ref.sh
t/t1450-fsck.sh
t/t2018-checkout-branch.sh
t/t2070-restore.sh
t/t2402-worktree-list.sh
t/t3000-ls-files-others.sh
t/t3033-merge-toplevel.sh
t/t3206-range-diff.sh
t/t3402-rebase-merge.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3406-rebase-message.sh
t/t3417-rebase-whitespace-fix.sh
t/t3419-rebase-patch-id.sh
t/t3420-rebase-autostash.sh
t/t3421-rebase-topology-linear.sh
t/t3424-rebase-empty.sh
t/t3431-rebase-fork-point.sh
t/t3432-rebase-fast-forward.sh
t/t3435-rebase-gpg-sign.sh [new file with mode: 0755]
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t3514-cherry-pick-revert-gpg.sh [new file with mode: 0755]
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t3903-stash.sh
t/t3904-stash-patch.sh
t/t4013-diff-various.sh
t/t4013/diff.diff-tree_--format=%N_note [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--notes_note [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_note [new file with mode: 0644]
t/t4013/diff.log_--decorate=full_--all
t/t4013/diff.log_--decorate_--all
t/t4014-format-patch.sh
t/t4018-diff-funcname.sh
t/t4018/markdown-heading-indented [new file with mode: 0644]
t/t4018/markdown-heading-non-headings [new file with mode: 0644]
t/t4057-diff-combined-paths.sh
t/t4061-diff-indent.sh
t/t4067-diff-partial-clone.sh
t/t4124-apply-ws-rule.sh
t/t4150-am.sh
t/t4202-log.sh
t/t4208-log-magic-pathspec.sh
t/t4216-log-bloom.sh [new file with mode: 0755]
t/t4254-am-corrupt.sh
t/t5003-archive-zip.sh
t/t5318-commit-graph.sh
t/t5319-multi-pack-index.sh
t/t5319/no-objects.midx [new file with mode: 0644]
t/t5322-pack-objects-sparse.sh
t/t5324-split-commit-graph.sh
t/t5500-fetch-pack.sh
t/t5504-fetch-receive-strict.sh
t/t5512-ls-remote.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5521-pull-options.sh
t/t5537-fetch-shallow.sh
t/t5541-http-push-smart.sh
t/t5543-atomic-push.sh
t/t5548-push-porcelain.sh [new file with mode: 0755]
t/t5550-http-fetch-dumb.sh
t/t5562-http-backend-content-length.sh
t/t5604-clone-reference.sh
t/t5607-clone-bundle.sh
t/t5611-clone-config.sh
t/t5612-clone-refspec.sh
t/t5616-partial-clone.sh
t/t5703-upload-pack-ref-in-want.sh
t/t5704-protocol-violations.sh [new file with mode: 0755]
t/t5801-remote-helpers.sh
t/t6012-rev-list-simplify.sh
t/t6030-bisect-porcelain.sh
t/t6113-rev-list-bitmap-filters.sh
t/t6120-describe.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t6600-test-reach.sh
t/t7004-tag.sh
t/t7063-status-untracked-cache.sh
t/t7112-reset-submodule.sh
t/t7400-submodule-basic.sh
t/t7408-submodule-reference.sh
t/t7508-status.sh
t/t7510-signed-commit.sh
t/t7600-merge.sh
t/t7601-merge-pull-config.sh
t/t7609-merge-co-error-msgs.sh
t/t7810-grep.sh
t/t9010-svn-fe.sh
t/t9141-git-svn-multiple-branches.sh
t/t9160-git-svn-preserve-empty-dirs.sh
t/t9164-git-svn-dcommit-concurrent.sh
t/t9300-fast-import.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9819-git-p4-case-folding.sh
t/t9831-git-p4-triggers.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
tempfile.c
tempfile.h
trace2.c
trace2.h
trace2/tr2_cfg.c
trace2/tr2_cfg.h
trace2/tr2_sysenv.c
trace2/tr2_sysenv.h
transport-helper.c
transport.c
transport.h
tree-diff.c
unpack-trees.c
unpack-trees.h
upload-pack.c
urlmatch.c
urlmatch.h
userdiff.c
worktree.c
wrapper.c
wt-status.c
wt-status.h

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644 (file)
index 0000000..802a4bf
--- /dev/null
@@ -0,0 +1,272 @@
+name: CI/PR
+
+on: [push, pull_request]
+
+env:
+  DEVELOPER: 1
+
+jobs:
+  ci-config:
+      runs-on: ubuntu-latest
+      outputs:
+        enabled: ${{ steps.check-ref.outputs.enabled }}
+      steps:
+        - name: try to clone ci-config branch
+          continue-on-error: true
+          run: |
+            git -c protocol.version=2 clone \
+              --no-tags \
+              --single-branch \
+              -b ci-config \
+              --depth 1 \
+              --no-checkout \
+              --filter=blob:none \
+              https://github.com/${{ github.repository }} \
+              config-repo &&
+              cd config-repo &&
+              git checkout HEAD -- ci/config
+        - id: check-ref
+          name: check whether CI is enabled for ref
+          run: |
+            enabled=yes
+            if test -x config-repo/ci/config/allow-ref &&
+               ! config-repo/ci/config/allow-ref '${{ github.ref }}'
+            then
+              enabled=no
+            fi
+            echo "::set-output name=enabled::$enabled"
+
+  windows-build:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    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:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    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:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    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:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    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:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    env:
+      jobname: StaticAnalysis
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-dependencies.sh
+    - run: ci/run-static-analysis.sh
+  documentation:
+    needs: ci-config
+    if: needs.ci-config.outputs.enabled == 'yes'
+    env:
+      jobname: Documentation
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-dependencies.sh
+    - run: ci/test-documentation.sh
index aebe7c0908f16889ea7dac2b634729c013b6fc99..ee509a2ad263989fcebe3c3543aa32efed1cacda 100644 (file)
@@ -25,6 +25,7 @@
 /git-bisect--helper
 /git-blame
 /git-branch
+/git-bugreport
 /git-bundle
 /git-cat-file
 /git-check-attr
@@ -83,7 +84,6 @@
 /git-init-db
 /git-interpret-trailers
 /git-instaweb
-/git-legacy-stash
 /git-log
 /git-ls-files
 /git-ls-remote
 /gitweb/gitweb.cgi
 /gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
+/config-list.h
 /command-list.h
 *.tar.gz
 *.dsc
index fc5730b085f117a16fb665a060b61e73ec18ba6c..05f3e3f8d79117c1d32bf5e433d0fd49de93125c 100644 (file)
@@ -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:
index 099968d619e3609fb8e3e46e81dd5ca1ee2396ff..227f46ae403ea85a1bea17892aa4b038820b5f9b 100644 (file)
@@ -91,16 +91,10 @@ For shell scripts specifically (not exhaustive):
 
    - No shell arrays.
 
-   - No strlen ${#parameter}.
-
    - No pattern replacement ${parameter/pattern/string}.
 
  - We use Arithmetic Expansion $(( ... )).
 
- - Inside Arithmetic Expansion, spell shell variables with $ in front
-   of them, as some shells do not grok $((x)) while accepting $(($x))
-   just fine (e.g. dash older than 0.5.4).
-
  - We do not use Process Substitution <(list) or >(list).
 
  - Do not write control structures on a single line with semicolon.
index 8fe829cc1b83945f25ae59b427d36d11e024091f..15d9d04f3164b939ec91695f4ec1c3b2fd6c00b3 100644 (file)
@@ -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
index aa828dfdc44a856c8bdd8b0826defeb398113bcc..c3f2d1a831e3b1d9c3e7cd5d555248e01bffbd73 100644 (file)
@@ -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.27.0.txt b/Documentation/RelNotes/2.27.0.txt
new file mode 100644 (file)
index 0000000..b398961
--- /dev/null
@@ -0,0 +1,474 @@
+Git 2.27 Release Notes
+======================
+
+Updates since v2.26
+-------------------
+
+Backward compatibility notes
+
+ * When "git describe C" finds that commit C is pointed by a signed or
+   annotated tag, which records T as its tagname in the object, the
+   command gives T as its answer.  Even if the user renames or moves
+   such a tag from its natural location in the "refs/tags/" hierarchy,
+   "git describe C" would still give T as the answer, but in such a
+   case "git show T^0" would no longer work as expected.  There may be
+   nothing at "refs/tags/T" or even worse there may be a different tag
+   instead.
+
+   Starting from this version, "git describe" will always use the
+   "long" version, as if the "--long" option were given, when giving
+   its output based on such a misplaced tag to work around the problem.
+
+ * "git pull" issues a warning message until the pull.rebase
+   configuration variable is explicitly given, which some existing
+   users may find annoying---those who prefer not to rebase need to
+   set the variable to false to squelch the warning.
+
+
+UI, Workflows & Features
+
+ * A handful of options to configure SSL when talking to proxies have
+   been added.
+
+ * Smudge/clean conversion filters are now given more information
+   (e.g. the object of the tree-ish in which the blob being converted
+   appears, in addition to its path, which has already been given).
+
+ * When "git describe C" finds an annotated tag with tagname A to be
+   the best name to explain commit C, and the tag is stored in a
+   "wrong" place in the refs/tags hierarchy, e.g. refs/tags/B, the
+   command gave a warning message but used A (not B) to describe C.
+   If C is exactly at the tag, the describe output would be "A", but
+   "git rev-parse A^0" would not be equal as "git rev-parse C^0".  The
+   behavior of the command has been changed to use the "long" form
+   i.e. A-0-gOBJECTNAME, which is correctly interpreted by rev-parse.
+
+ * "git pull" learned to warn when no pull.rebase configuration
+   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.
+
+ * The approxidate parser learns to parse seconds with fraction and
+   ignore fractional part.
+
+ * The userdiff patterns for Markdown documents have been added.
+
+ * The sparse-checkout patterns have been forbidden from excluding all
+   paths, leaving an empty working tree, for a long time.  This
+   limitation has been lifted.
+
+ * "git restore --staged --worktree" now defaults to take the contents
+   out of "HEAD", instead of erring out.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The advise API has been revamped to allow more systematic enumeration of
+   advice knobs in the future.
+
+ * SHA-256 transition continues.
+
+ * The code to interface with GnuPG has been refactored.
+
+ * "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.
+
+ * The object walk with object filter "--filter=tree:0" can now take
+   advantage of the pack bitmap when available.
+
+ * Instead of always building all branches at GitHub via Actions,
+   users can specify which branches to build.
+
+
+Fixes since v2.26
+-----------------
+
+ * The real_path() convenience function can easily be misused; with a
+   bit of code refactoring in the callers' side, its use has been
+   eliminated.
+   (merge 49d3c4b481 am/real-path-fix later to maint).
+
+ * Update "git p4" to work with Python 3.
+   (merge 6bb40ed20a yz/p4-py3 later to maint).
+
+ * The mechanism to prevent "git commit" from making an empty commit
+   or amending during an interrupted cherry-pick was broken during the
+   rewrite of "git rebase" in C, which has been corrected.
+   (merge 430b75f720 pw/advise-rebase-skip later to maint).
+
+ * Fix "git checkout --recurse-submodules" of a nested submodule
+   hierarchy.
+   (merge 846f34d351 pb/recurse-submodules-fix later to maint).
+
+ * The "--fork-point" mode of "git rebase" regressed when the command
+   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).
+
+ * Update the parser used for credential.<URL>.<variable>
+   configuration, to handle <URL>s with '/' in them correctly.
+   (merge b44d0118ac bc/wildcard-credential later to maint).
+
+ * Recent updates broke parsing of "credential.<url>.<key>" where
+   <url> is not a full URL (e.g. [credential "https://"] helper = ...)
+   stopped working, which has been corrected.
+   (merge 9a121b0d22 js/partial-urlmatch-2.17 later to maint).
+   (merge cd93e6c029 js/partial-urlmatch later to maint).
+
+ * Some of the files commit-graph subsystem keeps on disk did not
+   correctly honor the core.sharedRepository settings and some were
+   left read-write.
+
+ * In error messages that "git switch" mentions its option to create a
+   new branch, "-b/-B" options were shown, where "-c/-C" options
+   should be, which has been corrected.
+   (merge 7c16ef7577 dl/switch-c-option-in-error-message later to maint).
+
+ * With the recent tightening of the code that is used to parse
+   various parts of a URL for use in the credential subsystem, a
+   hand-edited credential-store file causes the credential helper to
+   die, which is a bit too harsh to the users.  Demote the error
+   behaviour to just ignore and keep using well-formed lines instead.
+   (merge c03859a665 cb/credential-store-ignore-bogus-lines later to maint).
+
+ * The samples in the credential documentation has been updated to
+   make it clear that we depict what would appear in the .git/config
+   file, by adding appropriate quotes as needed..
+   (merge 177681a07e jk/credential-sample-update later to maint).
+
+ * "git branch" and other "for-each-ref" variants accepted multiple
+   --sort=<key> options in the increasing order of precedence, but it
+   had a few breakages around "--ignore-case" handling, and tie-breaking
+   with the refname, which have been fixed.
+   (merge 7c5045fc18 jk/for-each-ref-multi-key-sort-fix later to maint).
+
+ * The coding guideline for shell scripts instructed to refer to a
+   variable with dollar-sign inside arithmetic expansion to work
+   around a bug in old versions of dash, which is a thing of the past.
+   Now we are not forbidden from writing $((var+1)).
+   (merge 32b5fe7f0e jk/arith-expansion-coding-guidelines later to maint).
+
+ * The <stdlib.h> header on NetBSD brings in its own definition of
+   hmac() function (eek), which conflicts with our own and unrelated
+   function with the same name.  Our function has been renamed to work
+   around the issue.
+   (merge 3013118eb8 cb/avoid-colliding-with-netbsd-hmac later to maint).
+
+ * The basic test did not honor $TEST_SHELL_PATH setting, which has
+   been corrected.
+   (merge 0555e4af58 cb/t0000-use-the-configured-shell later to maint).
+
+ * Minor in-code comments and documentation updates around credential
+   API.
+   (merge 1aed817f99 cb/credential-doc-fixes later to maint).
+
+ * Teach "am", "commit", "merge" and "rebase", when they are run with
+   the "--quiet" option, to pass "--quiet" down to "gc --auto".
+   (merge 7c3e9e8cfb jc/auto-gc-quiet later to maint).
+
+ * The code to skip unmerged paths in the index when sparse checkout
+   is in use would have made out-of-bound access of the in-core index
+   when the last path was unmerged, which has been corrected.
+
+ * Serving a "git fetch" client over "git://" and "ssh://" protocols
+   using the on-wire protocol version 2 was buggy on the server end
+   when the client needs to make a follow-up request to
+   e.g. auto-follow tags.
+   (merge 08450ef791 cc/upload-pack-v2-fetch-fix 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).
+   (merge 9c688735f6 rs/doc-passthru-fetch-options later to maint).
+   (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).
+   (merge ce9baf234f dl/push-recurse-submodules-fix later to maint).
+   (merge 4153274052 bc/doc-credential-helper-value later to maint).
index 8fc4b67081a649a6bd54a27f8cee77d41be79327..3e4c13971b4a7c02a769e87685331822c844e238 100644 (file)
@@ -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[]
index 08b13ba72be53bf36e05f7095964c5206d795945..ef0768b91a02cafdcb6044dba0169a5092bbd913 100644 (file)
@@ -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
 ~~~~~~
@@ -447,6 +448,8 @@ include::config/submodule.txt[]
 
 include::config/tag.txt[]
 
+include::config/tar.txt[]
+
 include::config/trace2.txt[]
 
 include::config/transfer.txt[]
index 60fb3189e16144a277cab1c80222c556ce939ed5..9d01641c282aa9cf472dc782e13cd9c430b17e3a 100644 (file)
@@ -1,9 +1,13 @@
 credential.helper::
        Specify an external helper to be called when a username or
        password credential is needed; the helper may consult external
-       storage to avoid prompting the user for the credentials. Note
-       that multiple helpers may be defined. See linkgit:gitcredentials[7]
-       for details.
+       storage to avoid prompting the user for the credentials. This is
+       normally the name of a credential helper with possible
+       arguments, but may also be an absolute path with arguments or, if
+       preceded by `!`, shell commands.
++
+Note that multiple helpers may be defined. See linkgit:gitcredentials[7]
+for details and examples.
 
 credential.useHttpPath::
        When acquiring credentials, consider the "path" component of an http
index 875f8c8a66f36464314d47decf433a82121a395b..4e3a5c0cebc90d1222de23d89db10d6eebfcc4d4 100644 (file)
@@ -12,9 +12,6 @@ feature.experimental::
        setting if you are interested in providing feedback on experimental
        features. The new default values are:
 +
-* `pack.useSparse=true` uses a new algorithm when constructing a pack-file
-which can improve `git push` performance in repos with many files.
-+
 * `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
 skipping more commits at a time, reducing the number of round trips.
 +
index f11940280fe303d53794b1f6de50c63e3a2829be..b1a9b1461d30f4e79bd194d968cebbb8617b3181 100644 (file)
@@ -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
index 45c7bd5a8ff8723ec3478a33fbca4216a5b465fa..564e8091ba5ce6788ad97c2109b14afd6d235c83 100644 (file)
@@ -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],
index e806033aab86ae7efd52a26a1efd2067178c28dc..3968fbb697aea209affaf7f22594c598bb698ae1 100644 (file)
@@ -29,6 +29,27 @@ http.proxyAuthMethod::
 * `ntlm` - NTLM authentication (compare the --ntlm option of `curl(1)`)
 --
 
+http.proxySSLCert::
+       The pathname of a file that stores a client certificate to use to authenticate
+       with an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_CERT` environment
+       variable.
+
+http.proxySSLKey::
+       The pathname of a file that stores a private key to use to authenticate with
+       an HTTPS proxy. Can be overridden by the `GIT_PROXY_SSL_KEY` environment
+       variable.
+
+http.proxySSLCertPasswordProtected::
+       Enable Git's password prompt for the proxy SSL certificate.  Otherwise OpenSSL
+       will prompt the user, possibly many times, if the certificate or private key
+       is encrypted. Can be overriden by the `GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED`
+       environment variable.
+
+http.proxySSLCAInfo::
+       Pathname to the file containing the certificate bundle that should be used to
+       verify the proxy with when using an HTTPS proxy. Can be overriden by the
+       `GIT_PROXY_SSL_CAINFO` environment variable.
+
 http.emptyAuth::
        Attempt authentication without seeking a username or password.  This
        can be used to attempt GSS-Negotiate authentication without specifying
index e9e1e397f3fd154511931f8c88e66c728b3cc07a..208d5fdcaa6894f6cc10177d8379afac6ff12e37 100644 (file)
@@ -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`,
index 6a313937f8c024d18188d94abcb797aea6bc3bc6..cb2ed589075baaeb4001fa84df9bcce524da4a15 100644 (file)
@@ -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.
index 0dac5805816ff6e9c133097be5cd9ae14e67da41..837f1b16792fe2200afe011cc1dc0868b6590fe6 100644 (file)
@@ -119,8 +119,8 @@ pack.useSparse::
        objects. This can have significant performance benefits when
        computing a pack to send a small change. However, it is possible
        that extra objects are added to the pack-file if the included
-       commits contain certain types of direct renames. Default is `false`
-       unless `feature.experimental` is enabled.
+       commits contain certain types of direct renames. Default is
+       `true`.
 
 pack.writeBitmaps (deprecated)::
        This is a deprecated synonym for `repack.writeBitmaps`.
index 756591d77b080ccfe5be5a26c899b65273cf4866..0b40141613e3d3dbc50f727c534229d5ee1da3cc 100644 (file)
@@ -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:
 +
 --
index 0a7aa322a9be695cd9863a519bf3fae5b789a7df..f5e5b38c6889e92d5366e7be1140db57e8f708c4 100644 (file)
@@ -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').
index abc7ef4a3a5c64f634dbe0a95bbef01d65151321..00eb35434e883d917d895296e4b1bfd726bac29b 100644 (file)
@@ -1,17 +1,9 @@
 stash.useBuiltin::
-       Set to `false` to use the legacy shell script implementation of
-       linkgit:git-stash[1]. Is `true` by default, which means use
-       the built-in rewrite of it in C.
-+
-The C rewrite is first included with Git version 2.22 (and Git for Windows
-version 2.19). This option serves as an escape hatch to re-enable the
-legacy version in case any bugs are found in the rewrite. This option and
-the shell script version of linkgit:git-stash[1] will be removed in some
-future release.
-+
-If you find some reason to set this option to `false`, other than
-one-off testing, you should report the behavior difference as a bug in
-Git (see https://git-scm.com/community for details).
+       Unused configuration variable.  Used in Git versions 2.22 to
+       2.26 as an escape hatch to enable the legacy shellscript
+       implementation of stash.  Now the built-in rewrite of it in C
+       is always used. Setting this will emit a warning, to alert any
+       remaining users that setting this now does nothing.
 
 stash.showPatch::
        If this is set to true, the `git stash show` command without an
index b33177151c5fe745652fc64a6fe4ec9858fc18c2..d7a63c8c12bbc24c637dfe5c4c767fdcfb1876ad 100644 (file)
@@ -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.
index 6d9110d84ce74f4de5782e9fcd5531719ed4627a..5062a057ffc6f59ed59fd87cdd3bd2ee98560b16 100644 (file)
@@ -15,10 +15,3 @@ tag.gpgSign::
        convenient to use an agent to avoid typing your gpg passphrase
        several times. Note that this option doesn't affect tag signing
        behavior enabled by "-u <keyid>" or "--local-user=<keyid>" options.
-
-tar.umask::
-       This variable can be used to restrict the permission bits of
-       tar archive entries.  The default is 0002, which turns off the
-       world write bit.  The special value "user" indicates that the
-       archiving user's umask will be used instead.  See umask(2) and
-       linkgit:git-archive[1].
diff --git a/Documentation/config/tar.txt b/Documentation/config/tar.txt
new file mode 100644 (file)
index 0000000..de8ff48
--- /dev/null
@@ -0,0 +1,6 @@
+tar.umask::
+       This variable can be used to restrict the permission bits of
+       tar archive entries.  The default is 0002, which turns off the
+       world write bit.  The special value "user" indicates that the
+       archiving user's umask will be used instead.  See umask(2) and
+       linkgit:git-archive[1].
index 4ce0b9a6d17fdd47d6b3d6cc36ea3ee4ea74cc0f..01d3afd8a8b38d3e6a722018be2fc4544907bf16 100644 (file)
@@ -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.
index 6926e0a4c86a0c8396d9cca4942a81bbb8e7dc85..7e7eaba643a3ce26843ce4b8bed5d310c406fd70 100644 (file)
@@ -20,7 +20,10 @@ RFC 2822::
 ISO 8601::
        Time and date specified by the ISO 8601 standard, for example
        `2005-04-07T22:13:13`. The parser accepts a space instead of the
-       `T` character as well.
+       `T` character as well. Fractional parts of a second will be ignored,
+       for example `2005-04-07T22:13:13.019` will be treated as
+       `2005-04-07T22:13:13`
+
 +
 NOTE: In addition, the date part is accepted in the following formats:
 `YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`.
index a115a1ae0e973dc0375e82cbc1de5eff182aa9a1..6e2a160a47cb8c07a72600d2fc7ad404bf2bc1c1 100644 (file)
@@ -61,10 +61,8 @@ this option multiple times, one for each matching ref name.
 See also the `fetch.negotiationAlgorithm` configuration variable
 documented in linkgit:git-config[1].
 
-ifndef::git-pull[]
 --dry-run::
        Show what would be done, without making any changes.
-endif::git-pull[]
 
 -f::
 --force::
@@ -95,6 +93,7 @@ ifndef::git-pull[]
 --[no-]write-commit-graph::
        Write a commit-graph after fetching. This overrides the config
        setting `fetch.writeCommitGraph`.
+endif::git-pull[]
 
 -p::
 --prune::
@@ -107,6 +106,7 @@ ifndef::git-pull[]
        was cloned with the --mirror option), then they are also
        subject to pruning. Supplying `--prune-tags` is a shorthand for
        providing the tag refspec.
+ifndef::git-pull[]
 +
 See the PRUNING section below for more details.
 
@@ -133,7 +133,6 @@ endif::git-pull[]
        behavior for a remote may be specified with the remote.<name>.tagOpt
        setting. See linkgit:git-config[1].
 
-ifndef::git-pull[]
 --refmap=<refspec>::
        When fetching refs listed on the command line, use the
        specified refspec (can be given more than once) to map the
@@ -154,6 +153,7 @@ ifndef::git-pull[]
        is used (though tags may be pruned anyway if they are also the
        destination of an explicit refspec; see `--prune`).
 
+ifndef::git-pull[]
 --recurse-submodules[=yes|on-demand|no]::
        This option controls if and under what conditions new commits of
        populated submodules should be fetched too. It can be used as a
@@ -163,7 +163,9 @@ 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::
 --jobs=<n>::
@@ -177,9 +179,11 @@ parallel. To control them independently, use the config settings
 Typically, parallel recursive and multi-remote fetches will be faster. By
 default fetches are performed sequentially, not in parallel.
 
+ifndef::git-pull[]
 --no-recurse-submodules::
        Disable recursive fetching of submodules (this has the same effect as
        using the `--recurse-submodules=no` option).
+endif::git-pull[]
 
 --set-upstream::
        If the remote is fetched successfully, pull and add upstream
@@ -188,6 +192,7 @@ default fetches are performed sequentially, not in parallel.
        see `branch.<name>.merge` and `branch.<name>.remote` in
        linkgit:git-config[1].
 
+ifndef::git-pull[]
 --submodule-prefix=<path>::
        Prepend <path> to paths printed in informative messages
        such as "Fetching submodule foo".  This option is used
@@ -200,7 +205,6 @@ default fetches are performed sequentially, not in parallel.
        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::
@@ -210,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
index ab5754e05d16d70b79cf90437bc999628aa99151..38c0852139c17077aacb6bc43fcf8102cc6570f4 100644 (file)
@@ -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 (file)
index 0000000..7fe9aef
--- /dev/null
@@ -0,0 +1,53 @@
+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
+ - A list of enabled hooks
+
+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
index c8fb995fa74ee9262ddd71514aeb250376d30a1b..5b697eee1b7632a046b48d488b3c3af75ad7fa45 100644 (file)
@@ -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.
 
index 83ce51aedfea54fd5150ef142aca24f8c1df95c9..75feeef08a0e64ac3f6bfc35290e92984c1a1580 100644 (file)
@@ -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
index bf24f1813adc3dd9173dfaa9fd845ba80a189eb9..08d6045c4a835dd5032c594dfc112b4adf4c6d5e 100644 (file)
@@ -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
index 28d1fee505306ba8d4b3aa1c9c6ac18522d4bd3b..53a650225a8b4d523cb0278b40d274990805e234 100644 (file)
@@ -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
index ec15ee8d6fad83d5dfe7d019cbb5984303552d9e..2e2c5810983a89a22e3280a276b5e8fd1509c670 100644 (file)
@@ -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
 ------------------
index 13f653989f32f1231030d8e89a70cb6a96c9955f..a3baea32aedddfa00077adc5f2a2b36e38a5ea26 100644 (file)
@@ -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.
index 693dd9d9d760fe2a113d4e15ab7816f34b2cd47d..76b0798856336fe739e8325ed1b5716abb099bfe 100644 (file)
@@ -94,6 +94,10 @@ stored on its own line as a URL like:
 https://user:pass@example.com
 ------------------------------
 
+No other kinds of lines (e.g. empty lines or comment lines) are
+allowed in the file, even though some may be silently ignored. Do
+not view or edit the file with editors.
+
 When Git needs authentication for a particular URL context,
 credential-store will consider that context a pattern to match against
 each entry in the credentials file.  If the protocol, hostname, and
index 6f0c7ca80f7331b103249922d54ebe692698bbe7..8d990e92fd9c03a05f099908584c22068a868904 100644 (file)
@@ -103,17 +103,20 @@ INPUT/OUTPUT FORMAT
 `git credential` reads and/or writes (depending on the action used)
 credential information in its standard input/output. This information
 can correspond either to keys for which `git credential` will obtain
-the login/password information (e.g. host, protocol, path), or to the
-actual credential data to be obtained (login/password).
+the login information (e.g. host, protocol, path), or to the actual
+credential data to be obtained (username/password).
 
 The credential is split into a set of named attributes, with one
-attribute per line. Each attribute is
-specified by a key-value pair, separated by an `=` (equals) sign,
-followed by a newline. The key may contain any bytes except `=`,
-newline, or NUL. The value may contain any bytes except newline or NUL.
+attribute per line. Each attribute is specified by a key-value pair,
+separated by an `=` (equals) sign, followed by a newline.
+
+The key may contain any bytes except `=`, newline, or NUL. The value may
+contain any bytes except newline or NUL.
+
 In both cases, all bytes are treated as-is (i.e., there is no quoting,
 and one cannot transmit a value with newline or NUL in it). The list of
 attributes is terminated by a blank line or end-of-file.
+
 Git understands the following attributes:
 
 `protocol`::
@@ -123,7 +126,8 @@ Git understands the following attributes:
 
 `host`::
 
-       The remote hostname for a network credential.
+       The remote hostname for a network credential.  This includes
+       the port number if one was specified (e.g., "example.com:8088").
 
 `path`::
 
@@ -134,7 +138,7 @@ Git understands the following attributes:
 `username`::
 
        The credential's username, if we already have one (e.g., from a
-       URL, from the user, or from a previously run helper).
+       URL, the configuration, the user, or from a previously run helper).
 
 `password`::
 
@@ -146,8 +150,12 @@ Git understands the following attributes:
        value is parsed as a URL and treated as if its constituent parts
        were read (e.g., `url=https://example.com` would behave as if
        `protocol=https` and `host=example.com` had been provided). This
-       can help callers avoid parsing URLs themselves.  Note that any
-       components which are missing from the URL (e.g., there is no
-       username in the example above) will be set to empty; if you want
-       to provide a URL and override some attributes, provide the URL
-       attribute first, followed by any overrides.
+       can help callers avoid parsing URLs themselves.
+
+       Note that specifying a protocol is mandatory and if the URL
+       doesn't specify a hostname (e.g., "cert:///path/to/file") the
+       credential will contain a hostname attribute whose value is an
+       empty string.
+
+       Components which are missing from the URL (e.g., there is no
+       username in the example above) will be left unset.
index 7889f95940544a016c5ddf696f0995177f5b36e6..77c6b3d0019de2d37be64f03b6f49fa93c3ce4ad 100644 (file)
@@ -122,6 +122,26 @@ Locations of Marks Files
 Relative and non-relative marks may be combined by interweaving
 --(no-)-relative-marks with the --(import|export)-marks= options.
 
+Submodule Rewriting
+~~~~~~~~~~~~~~~~~~~
+
+--rewrite-submodules-from=<name>:<file>::
+--rewrite-submodules-to=<name>:<file>::
+  Rewrite the object IDs for the submodule specified by <name> from the values
+       used in the from <file> to those used in the to <file>. The from marks should
+       have been created by `git fast-export`, and the to marks should have been
+       created by `git fast-import` when importing that same submodule.
++
+<name> may be any arbitrary string not containing a colon character, but the
+same value must be used with both options when specifying corresponding marks.
+Multiple submodules may be specified with different values for <name>. It is an
+error not to use these options in corresponding pairs.
++
+These options are primarily useful when converting a repository from one hash
+algorithm to another; without them, fast-import will fail if it encounters a
+submodule because it has no way of writing the object ID into the new hash
+algorithm.
+
 Performance and Compression Tuning
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index 0d4f8951bbb1d1df0ebba15d2619d5a39568469d..0f81d0437bb65bd0ee68ef63edf7bb48d9ea87a6 100644 (file)
@@ -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
index ddb6acc0257ef25c415de9e63e4fb9bacd1728ea..a7f9bc99eaf18d08fcbcc163da49c50d4c1e44c5 100644 (file)
@@ -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::
index 32880aafb0c55ce29f35490fd734587c38095066..adc6adfd380beb88cfc9e95ad9be39d874b4890e 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git init' [-q | --quiet] [--bare] [--template=<template_directory>]
-         [--separate-git-dir <git dir>]
+         [--separate-git-dir <git dir>] [--object-format=<format]
          [--shared[=<permissions>]] [directory]
 
 
@@ -48,6 +48,11 @@ Only print error and warning messages; all other output will be suppressed.
 Create a bare repository. If `GIT_DIR` environment is not set, it is set to the
 current working directory.
 
+--object-format=<format>::
+
+Specify the given object format (hash algorithm) for the repository.  The valid
+values are 'sha1' and (if enabled) 'sha256'.  'sha1' is the default.
+
 --template=<template_directory>::
 
 Specify the directory from which templates will be used.  (See the "TEMPLATE
index bed09bb09e52c6ddc0923e6173f6f5b5dceceb4a..20e6d21a74abcd354dbad38ab55e82dd6a3fd869 100644 (file)
@@ -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
index 8461c0e83e9d535b25755be1d5045cdc30145cb4..3cb2ebb4380e28e8e4b4e1bb5e3f4ae511960b7a 100644 (file)
@@ -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>]::
index 092529c619e29cfa1c0959358d4671ff1cba1144..3819fadac1f1e4daa3bc44beed5602c413830851 100644 (file)
@@ -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
+list.
 
 --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 list.
 
 --continue::
        After a 'git merge' stops due to conflicts you can conclude the
index 3494a1db3ebf4991ed05011a8f4035807db7a9f6..dab9609013fd5c4fb1947bc5f8cd9cd4e2b5063c 100644 (file)
@@ -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.
index fecdf2600cc9e2c5fe7535231deaab79fc0a7278..eaa2f2a4041f2eed78aea08131b1120fb195aaf7 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
        [--local] [--incremental] [--window=<n>] [--depth=<n>]
        [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]
        [--stdout [--filter=<filter-spec>] | base-name]
-       [--shallow] [--keep-true-parents] [--sparse] < object-list
+       [--shallow] [--keep-true-parents] [--[no-]sparse] < object-list
 
 
 DESCRIPTION
@@ -196,14 +196,16 @@ depth is 4095.
        Add --no-reuse-object if you want to force a uniform compression
        level on all data no matter the source.
 
---sparse::
-       Use the "sparse" algorithm to determine which objects to include in
+--[no-]sparse::
+       Toggle the "sparse" algorithm to determine which objects to include in
        the pack, when combined with the "--revs" option. This algorithm
        only walks trees that appear in paths that introduce new objects.
        This can have significant performance benefits when computing
        a pack to send a small change. However, it is possible that extra
        objects are added to the pack-file if the included commits contain
-       certain types of direct renames.
+       certain types of direct renames. If this option is not included,
+       it defaults to the value of `pack.useSparse`, which is true unless
+       otherwise specified.
 
 --thin::
        Create a "thin" pack by omitting the common objects between a
index dfb901f8b8358ea309e6403079e3c87fb82fee6f..5c3fb67c01483957359df5cbadd0e839809c3c85 100644 (file)
@@ -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
index da33f84f33d2c5869ce730169ff0f0f23edd42d1..5fa8bab64c2d0bb80f88ace023b5eafc898b05f1 100644 (file)
@@ -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::
index f7a6033607fa558dfd5a0437d7b4327ff1c44c78..4624cfd28838054f814d07366810110732e5a3bb 100644 (file)
@@ -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 list.
 
 --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
index 932080c55d2c232236c630a26c9968874882b1f3..252e2d4e47d10404226cce6abfc0132a4d2e9b74 100644 (file)
@@ -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
index 5bf60d49434109918fedbc6dd9051a2f65d24316..84c6c400109851ac34185a4f9004aada07aa79aa 100644 (file)
@@ -22,9 +22,8 @@ The command can also be used to restore the content in the index with
 `--staged`, or restore both the working tree and the index with
 `--staged --worktree`.
 
-By default, the restore sources for working tree and the index are the
-index and `HEAD` respectively. `--source` could be used to specify a
-commit as the restore source.
+By default, if `--staged` is given, the contents are restored from `HEAD`,
+otherwise from the index. Use `--source` to restore from a different commit.
 
 See "Reset, restore and revert" in linkgit:git[1] for the differences
 between the three commands.
@@ -39,10 +38,8 @@ OPTIONS
        tree. It is common to specify the source tree by naming a
        commit, branch or tag associated with it.
 +
-If not specified, the default restore source for the working tree is
-the index, and the default restore source for the index is
-`HEAD`. When both `--staged` and `--worktree` are specified,
-`--source` must also be specified.
+If not specified, the contents are restored from `HEAD` if `--staged` is
+given, otherwise from the index.
 
 -p::
 --patch::
@@ -107,6 +104,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
index 9d22270757c9b5d402f680a3f5933989678cb6f0..044276e9da62af44af9a8d897df0dd536556610b 100644 (file)
@@ -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::
index c0342e53938a4f92398bdd8ceafa57623c2a85ae..1a3ace60820fac2fa71e3d555a4a17df8e4ec524 100644 (file)
@@ -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
index 197900363b0b7daa273fc2b39f7bed6961f08e09..3759c3a265b5b9761a9df4d8e6b9d733d29aee04 100644 (file)
@@ -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.
 
index 96714231179cac8242f1bd0f781503d607c4add3..3e737c236074849377e3766c55f4533a270c436f 100644 (file)
@@ -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
index b0672bd8065fe0cb61ca9e4563b8932a501fb3d9..9d6769e95ab1d356e6c8b07671ac11446a2acc96 100644 (file)
@@ -493,6 +493,12 @@ double-quotes and respecting backslash escapes. E.g., the value
        details. This variable has lower precedence than other path
        variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
 
+`GIT_DEFAULT_HASH_ALGORITHM`::
+       If this variable is set, the default hash algorithm for new
+       repositories will be set to this value. This value is currently
+       ignored when cloning; the setting of the remote repository
+       is used instead. The default is "sha1".
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
index 508fe713c4cfb1e2126bc96fc201ca2cd49c6222..2d0a03715be65edfc4c8be6ca8f2a959ddde7540 100644 (file)
@@ -824,6 +824,8 @@ patterns are available:
 
 - `java` suitable for source code in the Java language.
 
+- `markdown` suitable for Markdown documents.
+
 - `matlab` suitable for source code in the MATLAB and Octave languages.
 
 - `objc` suitable for source code in the Objective-C language.
index 1814d2d23c189c9597205eb2c0ca7f3d0c2306c3..9e481aec858d6eb36a98f3b7432e9847c50dc58b 100644 (file)
@@ -216,20 +216,26 @@ Here are some example specifications:
 
 ----------------------------------------------------
 # run "git credential-foo"
-foo
+[credential]
+       helper = foo
 
 # same as above, but pass an argument to the helper
-foo --bar=baz
+[credential]
+       helper = "foo --bar=baz"
 
 # the arguments are parsed by the shell, so use shell
 # quoting if necessary
-foo --bar="whitespace arg"
+[credential]
+       helper = "foo --bar='whitespace arg'"
 
 # you can also use an absolute path, which will not use the git wrapper
-/path/to/my/helper --with-arguments
+[credential]
+       helper = "/path/to/my/helper --with-arguments"
 
 # or you can specify your own shell snippet
-!f() { echo "password=`cat $HOME/.secret`"; }; f
+[credential "https://example.com"]
+       username = your_user
+       helper = "!f() { test \"$1\" = get && echo \"password=$(cat $HOME/.secret)\"; }; f"
 ----------------------------------------------------
 
 Generally speaking, rule (3) above is the simplest for users to specify.
@@ -262,16 +268,26 @@ For a `get` operation, the helper should produce a list of attributes on
 stdout in the same format (see linkgit:git-credential[1] for common
 attributes). A helper is free to produce a subset, or even no values at
 all if it has nothing useful to provide. Any provided attributes will
-overwrite those already known about by Git.  If a helper outputs a
-`quit` attribute with a value of `true` or `1`, no further helpers will
-be consulted, nor will the user be prompted (if no credential has been
-provided, the operation will then fail).
+overwrite those already known about by Git's credential subsystem.
+
+While it is possible to override all attributes, well behaving helpers
+should refrain from doing so for any attribute other than username and
+password.
+
+If a helper outputs a `quit` attribute with a value of `true` or `1`,
+no further helpers will be consulted, nor will the user be prompted
+(if no credential has been provided, the operation will then fail).
+
+Similarly, no more helpers will be consulted once both username and
+password had been provided.
 
 For a `store` or `erase` operation, the helper's output is ignored.
-If it fails to perform the requested operation, it may complain to
-stderr to inform the user. If it does not support the requested
-operation (e.g., a read-only store), it should silently ignore the
-request.
+
+If a helper fails to perform the requested operation or needs to notify
+the user of a potential issue, it may write to stderr.
+
+If it does not support the requested operation (e.g., a read-only store),
+it should silently ignore the request.
 
 If a helper receives any other operation, it should silently ignore the
 request. This leaves room for future operations to be added (older
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
new file mode 100644 (file)
index 0000000..370d62d
--- /dev/null
@@ -0,0 +1,355 @@
+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.
+
+[[files-in-.gitignore-are-tracked]]
+I asked Git to ignore various files, yet they are still tracked::
+       A `gitignore` file ensures that certain file(s) which are not
+       tracked by Git remain untracked.  However, sometimes particular
+       file(s) may have been tracked before adding them into the
+       `.gitignore`, hence they still remain tracked.  To untrack and
+       ignore files/patterns, use `git rm --cached <file/pattern>`
+       and add a pattern to `.gitignore` that matches the <file>.
+       See linkgit:gitignore[5] for details.
+
+[[fetching-and-pulling]]
+How do I know if I want to do a fetch or a pull?::
+       A fetch stores a copy of the latest changes from the remote
+       repository, without modifying the working tree or current branch.
+       You can then at your leisure inspect, merge, rebase on top of, or
+       ignore the upstream changes.  A pull consists of a fetch followed
+       immediately by either a merge or rebase.  See linkgit:git-pull[1].
+
+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
index 3dccab53758717d6e7822d5cdfefd002864e9276..81f2a87e88ba5fee51419b615a60352a8dd3c999 100644 (file)
@@ -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
 ~~~~~~~~~~~~~~~~~
index c476f891b5cefdd74076f29acea25f270f3d2243..f9f4e65c9e7a3f2444411e98cbd42c61b689d47a 100644 (file)
@@ -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
index ca4378740c6a5cd396c9105362078d16836e1c25..73be8b49f8495a04dbbba0813638abefe4c6b2b0 100644 (file)
@@ -154,15 +154,17 @@ by doing the following:
    - Anything unobvious that is applicable to 'master' (in other
      words, does not depend on anything that is still in 'next'
      and not in 'master') is applied to a new topic branch that
-     is forked from the tip of 'master'.  This includes both
+     is forked from the tip of 'master' (or the last feature release,
+     which is a bit older than 'master').  This includes both
      enhancements and unobvious fixes to 'master'.  A topic
      branch is named as ai/topic where "ai" is two-letter string
      named after author's initial and "topic" is a descriptive name
      of the topic (in other words, "what's the series is about").
 
    - An unobvious fix meant for 'maint' is applied to a new
-     topic branch that is forked from the tip of 'maint'.  The
-     topic is named as ai/maint-topic.
+     topic branch that is forked from the tip of 'maint' (or the
+     oldest and still relevant maintenance branch).  The
+     topic may be named as ai/maint-topic.
 
    - Changes that pertain to an existing topic are applied to
      the branch, but:
@@ -174,24 +176,40 @@ by doing the following:
    - Replacement patches to an existing topic are accepted only
      for commits not in 'next'.
 
-   The above except the "replacement" are all done with:
+   The initial round is done with:
 
      $ git checkout ai/topic ;# or "git checkout -b ai/topic master"
      $ git am -sc3 mailbox
 
-   while patch replacement is often done by:
+   and replacing an existing topic with subsequent round is done with:
 
-     $ git format-patch ai/topic~$n..ai/topic ;# export existing
+     $ git checkout master...ai/topic ;# try to reapply to the same base
+     $ git am -sc3 mailbox
+
+   to prepare the new round on a detached HEAD, and then
+
+     $ git range-diff @{-1}...
+     $ git diff @{-1}
 
-   then replace some parts with the new patch, and reapplying:
+   to double check what changed since the last round, and finally
 
-     $ git checkout ai/topic
-     $ git reset --hard ai/topic~$n
-     $ git am -sc3 -s 000*.txt
+     $ git checkout -B @{-1}
+
+   to conclude (the last step is why a topic already in 'next' is
+   not replaced but updated incrementally).
+
+   Whether it is the initial round or a subsequent round, the topic
+   may not build even in isolation, or may break the build when
+   merged to integration branches due to bugs.  There may already
+   be obvious and trivial improvements suggested on the list.  The
+   maintainer often adds an extra commit, with "SQUASH???" in its
+   title, to fix things up, before publishing the integration
+   branches to make it usable by other developers for testing.
+   These changes are what the maintainer is not 100% committed to
+   (trivial typofixes etc. are often squashed directly into the
+   patches that need fixing, without being applied as a separate
+   "SQUASH???" commit), so that they can be removed easily as needed.
 
-   The full test suite is always run for 'maint' and 'master'
-   after patch application; for topic branches the tests are run
-   as time permits.
 
  - Merge maint to master as needed:
 
@@ -371,6 +389,14 @@ Some observations to be made.
    be included in the next feature release.  Being in the
    'master' branch typically is.
 
+ * Due to the nature of "SQUASH???" fix-ups, if the original author
+   agrees with the suggested changes, it is OK to squash them to
+   appropriate patches in the next round (when the suggested change
+   is small enough, the author should not even bother with
+   "Helped-by").  It is also OK to drop them from the next round
+   when the original author does not agree with the suggestion, but
+   the author is expected to say why somewhere in the discussion.
+
 
 Appendix
 --------
diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl
deleted file mode 100644 (file)
index b4d315c..0000000
+++ /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 (file)
index a264fa6..0000000
+++ /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>
index 94d6c1b54565f88c56c489d604150ed18b8a49b3..e13db856932567d62cf1cb87ab36a7278581fa0d 100644 (file)
@@ -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>
index a48f5b11f3dcc9227131d3a5caf1de4f857f0b28..a9c7ec69f46d8c631e86449aafdf327d8e51d435 100644 (file)
@@ -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 (file)
index a63c763..0000000
+++ /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>
index 40dc4f5e8c6e2d5a59cfbbeea1df7254068a6ed2..80d4831662c5e5fee9cdcc37e14929ce94f9b81a 100644 (file)
@@ -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
index a4b6f49186319ae094d3a7182ab9f7e0a8925ef9..547a55246302f91d6673db79228e08c6de95a286 100644 (file)
@@ -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
index 7d3a60f5b9361cd46adc4f005694a52ed06fd1c1..95ea84990298bf2c0e0dc4b03ee53e3563018a6c 100644 (file)
@@ -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
index bfd02ade99161b64ba89978bfbf9693d7340a962..04ad7dd36ebd311cbb7acb33a94dcc631f393f0a 100644 (file)
@@ -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
index 97f995e5a9a6012ddd41169f06f7b9ddd3aa1c1a..1ad95065c1f64577e2b65e6c730c4550b7c2992e 100644 (file)
@@ -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
index 4f07ceadcb3fb5f8cc3479d0cd9fbeb0adec18b2..6b6085585d5659ea6427247c18c96c84fdef5ade 100644 (file)
@@ -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.
 +
 ------------
 {
index a4f17441aed30f14c036a4bed6a911c86cf31ce5..de56f9f1efd1e1d4f5f29832b54c8e04449bd0eb 100644 (file)
@@ -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
index d87294de2f54c354c6215fb6029a182c966d6b2e..0148f126dcdf6aca15a5560fb5b122b85b022461 100644 (file)
@@ -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[]
index dafe6d036aae4228726240f1d0a390e3ad03b517..21f929e7ee5453fc1e8b282a71f0d865db7a7b0b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.26.2
+DEF_VER=v2.26.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 22c364f34f573c615d968a85374ba2c429b7fabd..9ba33e6a141a3906eb707dd11d1af4b0f8191a55 100644 (file)
--- 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:
index 9804a0758b2458f0ba3d7130c83d7d22d9570879..3d3a39fc192d5544a411d4cedb3785473b6f1148 100644 (file)
--- 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.
@@ -609,7 +603,6 @@ SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
 SCRIPT_SH += git-quiltimport.sh
-SCRIPT_SH += git-legacy-stash.sh
 SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
@@ -617,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
@@ -681,20 +674,23 @@ 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 =
 
 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::
index 9d4564c8aa19cc3d22b86d9f03c12a102bacc4f4..eb8115e6b04814f0c37146bbe3dbc35f3e8992e0 100644 (file)
--- 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
 =========================================================
index 3427be6818967d2d01521a64ffc1f88354290b6f..f3d8527c2c0ceea3adfdbf077cb158e1f1d06a78 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.26.2.txt
\ No newline at end of file
+Documentation/RelNotes/2.27.0.txt
\ No newline at end of file
index 98579853299427ff906434fe56288a73b9d19b21..6f15a418bb64e202811a05e14819f6ef6a578b8d 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -202,22 +202,6 @@ error_out:
        return retval;
 }
 
-/*
- * Resolve `path` into an absolute, cleaned-up path. The return value
- * comes from a shared buffer.
- */
-const char *real_path(const char *path)
-{
-       static struct strbuf realpath = STRBUF_INIT;
-       return strbuf_realpath(&realpath, path, 1);
-}
-
-const char *real_path_if_valid(const char *path)
-{
-       static struct strbuf realpath = STRBUF_INIT;
-       return strbuf_realpath(&realpath, path, 0);
-}
-
 char *real_pathdup(const char *path, int die_on_error)
 {
        struct strbuf realpath = STRBUF_INIT;
@@ -233,7 +217,7 @@ char *real_pathdup(const char *path, int die_on_error)
 
 /*
  * Use this to get an absolute path from a relative one. If you want
- * to resolve links, you should use real_path.
+ * to resolve links, you should use strbuf_realpath.
  */
 const char *absolute_path(const char *path)
 {
index 4a9bf85cac033b0cf22665918fccee561678eb3d..29cd2fe02014b32c21b69059e5bbf93018a77223 100644 (file)
@@ -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;
index d8dafa8168dc8389468d8c2c3cd6221220826605..d8bfe379be4d50893b8b977efff9c79de54b38af 100644 (file)
@@ -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;
 }
 
index 97f3f981b4b484a9edfed7cedadc3c01e51ff47e..f0a3d32d20687d1caadc1534aa15cf50543c27c0 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;
 int advice_waiting_for_editor = 1;
 int advice_graft_file_deprecated = 1;
 int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_nested_tag = 1;
 int advice_submodule_alternate_error_strategy_die = 1;
 int advice_add_ignored_file = 1;
 int advice_add_empty_pathspec = 1;
@@ -82,7 +81,7 @@ static struct {
        { "sequencerInUse", &advice_sequencer_in_use },
        { "implicitIdentity", &advice_implicit_identity },
        { "detachedHead", &advice_detached_head },
-       { "setupStreamFailure", &advice_set_upstream_failure },
+       { "setUpstreamFailure", &advice_set_upstream_failure },
        { "objectNameWarning", &advice_object_name_warning },
        { "amWorkDir", &advice_amworkdir },
        { "rmHints", &advice_rm_hints },
@@ -91,7 +90,6 @@ static struct {
        { "waitingForEditor", &advice_waiting_for_editor },
        { "graftFileDeprecated", &advice_graft_file_deprecated },
        { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
-       { "nestedTag", &advice_nested_tag },
        { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
        { "addIgnoredFile", &advice_add_ignored_file },
        { "addEmptyPathspec", &advice_add_empty_pathspec },
@@ -100,15 +98,58 @@ static struct {
        { "pushNonFastForward", &advice_push_update_rejected }
 };
 
-void advise(const char *advice, ...)
+static struct {
+       const char *key;
+       int enabled;
+} advice_setting[] = {
+       [ADVICE_ADD_EMBEDDED_REPO]                      = { "addEmbeddedRepo", 1 },
+       [ADVICE_AM_WORK_DIR]                            = { "amWorkDir", 1 },
+       [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = { "checkoutAmbiguousRemoteBranchName", 1 },
+       [ADVICE_COMMIT_BEFORE_MERGE]                    = { "commitBeforeMerge", 1 },
+       [ADVICE_DETACHED_HEAD]                          = { "detachedHead", 1 },
+       [ADVICE_FETCH_SHOW_FORCED_UPDATES]              = { "fetchShowForcedUpdates", 1 },
+       [ADVICE_GRAFT_FILE_DEPRECATED]                  = { "graftFileDeprecated", 1 },
+       [ADVICE_IGNORED_HOOK]                           = { "ignoredHook", 1 },
+       [ADVICE_IMPLICIT_IDENTITY]                      = { "implicitIdentity", 1 },
+       [ADVICE_NESTED_TAG]                             = { "nestedTag", 1 },
+       [ADVICE_OBJECT_NAME_WARNING]                    = { "objectNameWarning", 1 },
+       [ADVICE_PUSH_ALREADY_EXISTS]                    = { "pushAlreadyExists", 1 },
+       [ADVICE_PUSH_FETCH_FIRST]                       = { "pushFetchFirst", 1 },
+       [ADVICE_PUSH_NEEDS_FORCE]                       = { "pushNeedsForce", 1 },
+
+       /* make this an alias for backward compatibility */
+       [ADVICE_PUSH_UPDATE_REJECTED_ALIAS]             = { "pushNonFastForward", 1 },
+
+       [ADVICE_PUSH_NON_FF_CURRENT]                    = { "pushNonFFCurrent", 1 },
+       [ADVICE_PUSH_NON_FF_MATCHING]                   = { "pushNonFFMatching", 1 },
+       [ADVICE_PUSH_UNQUALIFIED_REF_NAME]              = { "pushUnqualifiedRefName", 1 },
+       [ADVICE_PUSH_UPDATE_REJECTED]                   = { "pushUpdateRejected", 1 },
+       [ADVICE_RESET_QUIET_WARNING]                    = { "resetQuiet", 1 },
+       [ADVICE_RESOLVE_CONFLICT]                       = { "resolveConflict", 1 },
+       [ADVICE_RM_HINTS]                               = { "rmHints", 1 },
+       [ADVICE_SEQUENCER_IN_USE]                       = { "sequencerInUse", 1 },
+       [ADVICE_SET_UPSTREAM_FAILURE]                   = { "setUpstreamFailure", 1 },
+       [ADVICE_STATUS_AHEAD_BEHIND_WARNING]            = { "statusAheadBehindWarning", 1 },
+       [ADVICE_STATUS_HINTS]                           = { "statusHints", 1 },
+       [ADVICE_STATUS_U_OPTION]                        = { "statusUoption", 1 },
+       [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+       [ADVICE_WAITING_FOR_EDITOR]                     = { "waitingForEditor", 1 },
+};
+
+static const char turn_off_instructions[] =
+N_("\n"
+   "Disable this message with \"git config advice.%s false\"");
+
+static void vadvise(const char *advice, int display_instructions,
+                   const char *key, va_list params)
 {
        struct strbuf buf = STRBUF_INIT;
-       va_list params;
        const char *cp, *np;
 
-       va_start(params, advice);
        strbuf_vaddf(&buf, advice, params);
-       va_end(params);
+
+       if (display_instructions)
+               strbuf_addf(&buf, turn_off_instructions, key);
 
        for (cp = buf.buf; *cp; cp = np) {
                np = strchrnul(cp, '\n');
@@ -122,6 +163,37 @@ void advise(const char *advice, ...)
        strbuf_release(&buf);
 }
 
+void advise(const char *advice, ...)
+{
+       va_list params;
+       va_start(params, advice);
+       vadvise(advice, 0, "", params);
+       va_end(params);
+}
+
+int advice_enabled(enum advice_type type)
+{
+       switch(type) {
+       case ADVICE_PUSH_UPDATE_REJECTED:
+               return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&
+                      advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;
+       default:
+               return advice_setting[type].enabled;
+       }
+}
+
+void advise_if_enabled(enum advice_type type, const char *advice, ...)
+{
+       va_list params;
+
+       if (!advice_enabled(type))
+               return;
+
+       va_start(params, advice);
+       vadvise(advice, 1, advice_setting[type].key, params);
+       va_end(params);
+}
+
 int git_default_advice_config(const char *var, const char *value)
 {
        const char *k, *slot_name;
@@ -148,6 +220,13 @@ int git_default_advice_config(const char *var, const char *value)
                if (strcasecmp(k, advice_config[i].name))
                        continue;
                *advice_config[i].preference = git_config_bool(var, value);
+               break;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
+               if (strcasecmp(k, advice_setting[i].key))
+                       continue;
+               advice_setting[i].enabled = git_config_bool(var, value);
                return 0;
        }
 
@@ -158,8 +237,8 @@ void list_config_advices(struct string_list *list, const char *prefix)
 {
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(advice_config); i++)
-               list_config_item(list, prefix, advice_config[i].name);
+       for (i = 0; i < ARRAY_SIZE(advice_setting); i++)
+               list_config_item(list, prefix, advice_setting[i].key);
 }
 
 int error_resolve_conflict(const char *me)
index 0e6e58d9f8f79df4b4f6ae242cf1fb0a7d179d1b..16f2c11642a7e63c2b61e8cdba221f024d1069e5 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -29,14 +29,64 @@ extern int advice_ignored_hook;
 extern int advice_waiting_for_editor;
 extern int advice_graft_file_deprecated;
 extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_nested_tag;
 extern int advice_submodule_alternate_error_strategy_die;
 extern int advice_add_ignored_file;
 extern int advice_add_empty_pathspec;
 
+/*
+ * To add a new advice, you need to:
+ * Define a new advice_type.
+ * Add a new entry to advice_setting array.
+ * Add the new config variable to Documentation/config/advice.txt.
+ * Call advise_if_enabled to print your advice.
+ */
+ enum advice_type {
+       ADVICE_ADD_EMBEDDED_REPO,
+       ADVICE_AM_WORK_DIR,
+       ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
+       ADVICE_COMMIT_BEFORE_MERGE,
+       ADVICE_DETACHED_HEAD,
+       ADVICE_FETCH_SHOW_FORCED_UPDATES,
+       ADVICE_GRAFT_FILE_DEPRECATED,
+       ADVICE_IGNORED_HOOK,
+       ADVICE_IMPLICIT_IDENTITY,
+       ADVICE_NESTED_TAG,
+       ADVICE_OBJECT_NAME_WARNING,
+       ADVICE_PUSH_ALREADY_EXISTS,
+       ADVICE_PUSH_FETCH_FIRST,
+       ADVICE_PUSH_NEEDS_FORCE,
+       ADVICE_PUSH_NON_FF_CURRENT,
+       ADVICE_PUSH_NON_FF_MATCHING,
+       ADVICE_PUSH_UNQUALIFIED_REF_NAME,
+       ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
+       ADVICE_PUSH_UPDATE_REJECTED,
+       ADVICE_RESET_QUIET_WARNING,
+       ADVICE_RESOLVE_CONFLICT,
+       ADVICE_RM_HINTS,
+       ADVICE_SEQUENCER_IN_USE,
+       ADVICE_SET_UPSTREAM_FAILURE,
+       ADVICE_STATUS_AHEAD_BEHIND_WARNING,
+       ADVICE_STATUS_HINTS,
+       ADVICE_STATUS_U_OPTION,
+       ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+       ADVICE_WAITING_FOR_EDITOR,
+};
+
 int git_default_advice_config(const char *var, const char *value);
 __attribute__((format (printf, 1, 2)))
 void advise(const char *advice, ...);
+
+/**
+ * Checks if advice type is enabled (can be printed to the user).
+ * Should be called before advise().
+ */
+int advice_enabled(enum advice_type type);
+
+/**
+ * Checks the visibility of the advice before printing.
+ */
+void advise_if_enabled(enum advice_type type, const char *advice, ...);
+
 int error_resolve_conflict(const char *me);
 void NORETURN die_resolve_conflict(const char *me);
 void NORETURN die_conclude_merge(void);
diff --git a/apply.c b/apply.c
index bdc008fae2ad77197a859d64196bf91161cf8d67..8bff604dbe203402d93bd13fe5b03d635a7a50ee 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -4349,7 +4349,7 @@ static int try_create_file(struct apply_state *state, const char *path,
        if (fd < 0)
                return 1;
 
-       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) {
+       if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) {
                size = nbuf.len;
                buf  = nbuf.buf;
        }
@@ -4964,15 +4964,15 @@ int apply_parse_options(int argc, const char **argv,
                        const char * const *apply_usage)
 {
        struct option builtin_apply_options[] = {
-               { OPTION_CALLBACK, 0, "exclude", state, N_("path"),
+               OPT_CALLBACK_F(0, "exclude", state, N_("path"),
                        N_("don't apply changes matching the given path"),
-                       PARSE_OPT_NONEG, apply_option_parse_exclude },
-               { OPTION_CALLBACK, 0, "include", state, N_("path"),
+                       PARSE_OPT_NONEG, apply_option_parse_exclude),
+               OPT_CALLBACK_F(0, "include", state, N_("path"),
                        N_("apply changes matching the given path"),
-                       PARSE_OPT_NONEG, apply_option_parse_include },
-               { OPTION_CALLBACK, 'p', NULL, state, N_("num"),
+                       PARSE_OPT_NONEG, apply_option_parse_include),
+               OPT_CALLBACK('p', NULL, state, N_("num"),
                        N_("remove <num> leading slashes from traditional diff paths"),
-                       0, apply_option_parse_p },
+                       apply_option_parse_p),
                OPT_BOOL(0, "no-add", &state->no_add,
                        N_("ignore additions made by the patch")),
                OPT_BOOL(0, "stat", &state->diffstat,
@@ -5005,15 +5005,15 @@ int apply_parse_options(int argc, const char **argv,
                        N_("paths are separated with NUL character"), '\0'),
                OPT_INTEGER('C', NULL, &state->p_context,
                                N_("ensure at least <n> lines of context match")),
-               { OPTION_CALLBACK, 0, "whitespace", state, N_("action"),
+               OPT_CALLBACK(0, "whitespace", state, N_("action"),
                        N_("detect new or modified lines that have whitespace errors"),
-                       0, apply_option_parse_whitespace },
-               { OPTION_CALLBACK, 0, "ignore-space-change", state, NULL,
+                       apply_option_parse_whitespace),
+               OPT_CALLBACK_F(0, "ignore-space-change", state, NULL,
                        N_("ignore changes in whitespace when finding context"),
-                       PARSE_OPT_NOARG, apply_option_parse_space_change },
-               { OPTION_CALLBACK, 0, "ignore-whitespace", state, NULL,
+                       PARSE_OPT_NOARG, apply_option_parse_space_change),
+               OPT_CALLBACK_F(0, "ignore-whitespace", state, NULL,
                        N_("ignore changes in whitespace when finding context"),
-                       PARSE_OPT_NOARG, apply_option_parse_space_change },
+                       PARSE_OPT_NOARG, apply_option_parse_space_change),
                OPT_BOOL('R', "reverse", &state->apply_in_reverse,
                        N_("apply the patch in reverse")),
                OPT_BOOL(0, "unidiff-zero", &state->unidiff_zero,
@@ -5029,9 +5029,9 @@ int apply_parse_options(int argc, const char **argv,
                OPT_BIT(0, "recount", options,
                        N_("do not trust the line counts in the hunk headers"),
                        APPLY_OPT_RECOUNT),
-               { OPTION_CALLBACK, 0, "directory", state, N_("root"),
+               OPT_CALLBACK(0, "directory", state, N_("root"),
                        N_("prepend <root> to all filenames"),
-                       0, apply_option_parse_directory },
+                       apply_option_parse_directory),
                OPT_END()
        };
 
index 5a77701a15c2068ce91dcffb835335203a5860aa..5ceec3684be910c44216d38e11f240fa4cc22ed3 100644 (file)
@@ -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;
index a8da0fcc4f0cc47e585b3b378109fbd84496ed15..fb39706120cdd6b668fbc10cefad608692587d09 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -77,6 +77,11 @@ void *object_file_to_archive(const struct archiver_args *args,
 {
        void *buffer;
        const struct commit *commit = args->convert ? args->commit : NULL;
+       struct checkout_metadata meta;
+
+       init_checkout_metadata(&meta, args->refname,
+                              args->commit_oid ? args->commit_oid :
+                              (args->tree ? &args->tree->object.oid : NULL), oid);
 
        path += args->baselen;
        buffer = read_object_file(oid, type, sizep);
@@ -85,7 +90,7 @@ void *object_file_to_archive(const struct archiver_args *args,
                size_t size = 0;
 
                strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
-               convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf);
+               convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
                if (commit)
                        format_subst(commit, buf.buf, buf.len, &buf);
                buffer = strbuf_detach(&buf, &size);
@@ -385,16 +390,17 @@ static void parse_treeish_arg(const char **argv,
        struct tree *tree;
        const struct commit *commit;
        struct object_id oid;
+       char *ref = NULL;
 
        /* Remotes are only allowed to fetch actual refs */
        if (remote && !remote_allow_unreachable) {
-               char *ref = NULL;
                const char *colon = strchrnul(name, ':');
                int refnamelen = colon - name;
 
                if (!dwim_ref(name, refnamelen, &oid, &ref))
                        die(_("no such ref: %.*s"), refnamelen, name);
-               free(ref);
+       } else {
+               dwim_ref(name, strlen(name), &oid, &ref);
        }
 
        if (get_oid(name, &oid))
@@ -427,6 +433,7 @@ static void parse_treeish_arg(const char **argv,
 
                tree = parse_tree_indirect(&tree_oid);
        }
+       ar_args->refname = ref;
        ar_args->tree = tree;
        ar_args->commit_oid = commit_oid;
        ar_args->commit = commit;
index e60e3dd31c79f1c04b7afcae8eb5623238e1234f..3bd96bf6bba7ecaacb6b50e1f02080f95d11f17c 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct repository;
 
 struct archiver_args {
        struct repository *repo;
+       const char *refname;
        const char *base;
        size_t baselen;
        struct tree *tree;
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644 (file)
index 675c3a4..0000000
+++ /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)
index 9154f810f76af3cdbf82b8bb509f47aa392b0e01..d5e830410f5949d1e8c24e990cf0a70b980656ea 100644 (file)
--- 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 29770e5c81c0ad36cc18f8f17475ba7b2f509fb2..da7e28800e7eb8c13b1200ed6207d2fe077ae66f 100644 (file)
--- 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 089b181ff27b8c90315ef264e038227cb24c7f86..b6bbee4147288271094b3a6dad5a04d1fe597c16 100644 (file)
--- 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 (file)
index 0000000..ee025e0
--- /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);
+
+static 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;
+}
diff --git a/bloom.h b/bloom.h
new file mode 100644 (file)
index 0000000..e0e59e0
--- /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
index 579494738a7f804974d2b396a1c795acd4a44789..2d9e7675a6ed24bd70165a78386ad5a6589a3b50 100644 (file)
--- 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 (file)
index 0000000..aa8a489
--- /dev/null
@@ -0,0 +1,192 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "help.h"
+#include "compat/compiler.h"
+#include "run-command.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 void get_populated_hooks(struct strbuf *hook_info, int nongit)
+{
+       /*
+        * NEEDSWORK: Doesn't look like there is a list of all possible hooks;
+        * so below is a transcription of `git help hooks`. Later, this should
+        * be replaced with some programmatically generated list (generated from
+        * doc or else taken from some library which tells us about all the
+        * hooks)
+        */
+       static const char *hook[] = {
+               "applypatch-msg",
+               "pre-applypatch",
+               "post-applypatch",
+               "pre-commit",
+               "pre-merge-commit",
+               "prepare-commit-msg",
+               "commit-msg",
+               "post-commit",
+               "pre-rebase",
+               "post-checkout",
+               "post-merge",
+               "pre-push",
+               "pre-receive",
+               "update",
+               "post-receive",
+               "post-update",
+               "push-to-checkout",
+               "pre-auto-gc",
+               "post-rewrite",
+               "sendemail-validate",
+               "fsmonitor-watchman",
+               "p4-pre-submit",
+               "post-index-change",
+       };
+       int i;
+
+       if (nongit) {
+               strbuf_addstr(hook_info,
+                       _("not run from a git repository - no hooks to show\n"));
+               return;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(hook); i++)
+               if (find_hook(hook[i]))
+                       strbuf_addf(hook_info, "%s\n", hook[i]);
+}
+
+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);
+
+       get_header(&buffer, _("Enabled Hooks"));
+       get_populated_hooks(&buffer, nongit_ok);
+
+       /* 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);
+}
index 2b25a80cde37b401cdb8e7b5d2487c8989b95dd7..a5ae15bfe54b652465b0164872923c8386d03ae9 100644 (file)
--- a/builtin.h
+++ b/builtin.h
  * 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
index 18a0881ecf951cf79be33eee3470edca39dcf395..298e0114f93166b7e3c1d3c2bc376a34afb7a671 100644 (file)
@@ -330,10 +330,10 @@ static struct option builtin_add_options[] = {
        OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
        OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
        OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
-       { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
+       OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
          NULL /* takes no arguments */,
          N_("ignore paths removed in the working tree (same as --no-all)"),
-         PARSE_OPT_NOARG, ignore_removal_cb },
+         PARSE_OPT_NOARG, ignore_removal_cb),
        OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
        OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
        OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
index e3dfd93c258f6930ac36fa42c6fd9b8c80045576..69e50de018ba01cb2243221f1c7b5d7617afc72d 100644 (file)
@@ -1691,7 +1691,6 @@ static int do_interactive(struct am_state *state)
  */
 static void am_run(struct am_state *state, int resume)
 {
-       const char *argv_gc_auto[] = {"gc", "--auto", NULL};
        struct strbuf sb = STRBUF_INIT;
 
        unlink(am_path(state, "dirtyindex"));
@@ -1796,7 +1795,7 @@ next:
        if (!state->rebasing) {
                am_destroy(state);
                close_object_store(the_repository->objects);
-               run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+               run_auto_gc(state->quiet);
        }
 }
 
index bf1cecdf3f9ebbbd9a42381566c7ca2d86355e0d..94ef57c1cc304963452772e24125e5939dff0481 100644 (file)
@@ -864,8 +864,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
                OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
                OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
-               { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
-               { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
+               OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback),
+               OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback),
                OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
                OPT__ABBREV(&abbrev),
                OPT_END()
@@ -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;
 }
index d8297f80ffc730442d9a590650fe4644b2351d04..accb61b1aae3837a0771839c7b167c85243808f8 100644 (file)
@@ -653,10 +653,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
                OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
                OPT_REF_SORT(sorting_tail),
-               {
-                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
-                       N_("print only branches of the object"), 0, parse_opt_object_name
-               },
+               OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only branches of the object"), parse_opt_object_name),
                OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
                OPT_STRING(  0 , "format", &format.format, N_("format"), N_("format to use for the output")),
                OPT_END(),
@@ -739,7 +737,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                 */
                if (!sorting)
                        sorting = ref_default_sorting();
-               sorting->ignore_case = icase;
+               ref_sorting_icase_all(sorting, icase);
                print_ref_list(&filter, sorting, &format);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
index 272f9fc6d7cb540c89c3dca13969398afd3c1ee1..ae18e20a7c96fd1b20660ba9aa1079bcebe8d2af 100644 (file)
@@ -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"
@@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode,
                             oid_to_hex(oid), path);
        if ((type == OBJ_BLOB) && S_ISREG(mode)) {
                struct strbuf strbuf = STRBUF_INIT;
-               if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
+               struct checkout_metadata meta;
+
+               init_checkout_metadata(&meta, NULL, NULL, oid);
+               if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) {
                        free(*buf);
                        *size = strbuf.len;
                        *buf = strbuf_detach(&strbuf, NULL);
@@ -647,14 +650,14 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "allow-unknown-type", &unknown_type,
                          N_("allow -s and -t to work with broken/corrupt objects")),
                OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
-               { OPTION_CALLBACK, 0, "batch", &batch, "format",
+               OPT_CALLBACK_F(0, "batch", &batch, "format",
                        N_("show info and content of objects fed from the standard input"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
-                       batch_option_callback },
-               { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
+                       batch_option_callback),
+               OPT_CALLBACK_F(0, "batch-check", &batch, "format",
                        N_("show info about objects fed from the standard input"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
-                       batch_option_callback },
+                       batch_option_callback),
                OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
                         N_("follow in-tree symlinks (used with --batch or --batch-check)")),
                OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
index 1ac1cc290ed7db86d3ae42b7df2246dad1c507e3..a854fd16e779123f7d39f685fa3b7eb7c1ba930e 100644 (file)
@@ -177,9 +177,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
                        N_("write the content to temporary files")),
                OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
                        N_("when creating files, prepend <string>")),
-               { OPTION_CALLBACK, 0, "stage", NULL, "(1|2|3|all)",
+               OPT_CALLBACK_F(0, "stage", NULL, "(1|2|3|all)",
                        N_("copy out the files from named stage"),
-                       PARSE_OPT_NONEG, option_parse_stage },
+                       PARSE_OPT_NONEG, option_parse_stage),
                OPT_END()
        };
 
index d6773818b80315773078954c9879e7478224e9eb..e9d111bb8360d19c4c81fa6d8caa54b8049576fe 100644 (file)
@@ -88,6 +88,19 @@ struct checkout_opts {
        struct tree *source_tree;
 };
 
+struct branch_info {
+       const char *name; /* The short name used */
+       const char *path; /* The full name of a real branch */
+       struct commit *commit; /* The named commit */
+       char *refname; /* The full name of the ref being checked out. */
+       struct object_id oid; /* The object ID of the commit being checked out. */
+       /*
+        * if not null the branch is detached because it's already
+        * checked out in this checkout
+        */
+       char *checkout;
+};
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
@@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
        }
 }
 
-static int checkout_worktree(const struct checkout_opts *opts)
+static int checkout_worktree(const struct checkout_opts *opts,
+                            const struct branch_info *info)
 {
        struct checkout state = CHECKOUT_INIT;
        int nr_checkouts = 0, nr_unmerged = 0;
@@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts)
        state.refresh_cache = 1;
        state.istate = &the_index;
 
+       init_checkout_metadata(&state.meta, info->refname,
+                              info->commit ? &info->commit->object.oid : &info->oid,
+                              NULL);
+
        enable_delayed_checkout(&state);
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts)
 }
 
 static int checkout_paths(const struct checkout_opts *opts,
-                         const char *revision)
+                         const struct branch_info *new_branch_info)
 {
        int pos;
        static char *ps_matched;
@@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts,
                else
                        BUG("either flag must have been set, worktree=%d, index=%d",
                            opts->checkout_worktree, opts->checkout_index);
-               return run_add_interactive(revision, patch_mode, &opts->pathspec);
+               return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec);
        }
 
        repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
@@ -523,7 +541,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
        /* Now we are committed to check them out */
        if (opts->checkout_worktree)
-               errs |= checkout_worktree(opts);
+               errs |= checkout_worktree(opts, new_branch_info);
        else
                remove_marked_cache_entries(&the_index, 1);
 
@@ -586,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
 }
 
 static int reset_tree(struct tree *tree, const struct checkout_opts *o,
-                     int worktree, int *writeout_error)
+                     int worktree, int *writeout_error,
+                     struct branch_info *info)
 {
        struct unpack_trees_options opts;
        struct tree_desc tree_desc;
@@ -601,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.verbose_update = o->show_progress;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       init_checkout_metadata(&opts.meta, info->refname,
+                              info->commit ? &info->commit->object.oid :
+                              is_null_oid(&info->oid) ? &tree->object.oid :
+                              &info->oid,
+                              NULL);
        parse_tree(tree);
        init_tree_desc(&tree_desc, tree->buffer, tree->size);
        switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -620,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        }
 }
 
-struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
-       struct commit *commit; /* The named commit */
-       /*
-        * if not null the branch is detached because it's already
-        * checked out in this checkout
-        */
-       char *checkout;
-};
-
 static void setup_branch_path(struct branch_info *branch)
 {
        struct strbuf buf = STRBUF_INIT;
 
+       /*
+        * If this is a ref, resolve it; otherwise, look up the OID for our
+        * expression.  Failure here is okay.
+        */
+       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+               repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
        if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
@@ -663,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
        } else
                new_tree = get_commit_tree(new_branch_info->commit);
        if (opts->discard_changes) {
-               ret = reset_tree(new_tree, opts, 1, writeout_error);
+               ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
                if (ret)
                        return ret;
        } else {
@@ -692,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
                topts.quiet = opts->merge && old_branch_info->commit;
                topts.verbose_update = opts->show_progress;
                topts.fn = twoway_merge;
+               init_checkout_metadata(&topts.meta, new_branch_info->refname,
+                                      new_branch_info->commit ?
+                                      &new_branch_info->commit->object.oid :
+                                      &new_branch_info->oid, NULL);
                if (opts->overwrite_ignore) {
                        topts.dir = xcalloc(1, sizeof(*topts.dir));
                        topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -762,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
                        ret = reset_tree(new_tree,
                                         opts, 1,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
@@ -782,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                exit(128);
                        ret = reset_tree(new_tree,
                                         opts, 0,
-                                        writeout_error);
+                                        writeout_error, new_branch_info);
                        strbuf_release(&o.obuf);
                        strbuf_release(&old_commit_shortname);
                        if (ret)
@@ -1462,9 +1486,9 @@ static struct option *add_common_options(struct checkout_opts *opts,
 {
        struct option options[] = {
                OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+               OPT_CALLBACK_F(0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
                OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
                OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
                OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
@@ -1520,6 +1544,9 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts,
        return newopts;
 }
 
+/* create-branch option (either b or c) */
+static char cb_option = 'b';
+
 static int checkout_main(int argc, const char **argv, const char *prefix,
                         struct checkout_opts *opts, struct option *options,
                         const char * const usagestr[])
@@ -1562,7 +1589,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        }
 
        if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
-               die(_("-b, -B and --orphan are mutually exclusive"));
+               die(_("-%c, -%c and --orphan are mutually exclusive"),
+                               cb_option, toupper(cb_option));
 
        if (opts->overlay_mode == 1 && opts->patch_mode)
                die(_("-p and --overlay are mutually exclusive"));
@@ -1581,16 +1609,16 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        if (opts->checkout_index < 0 || opts->checkout_worktree < 0)
                BUG("these flags should be non-negative by now");
        /*
-        * convenient shortcut: "git restore --staged" equals
-        * "git restore --staged --source HEAD"
+        * convenient shortcut: "git restore --staged [--worktree]" equals
+        * "git restore --staged [--worktree] --source HEAD"
         */
-       if (!opts->from_treeish && opts->checkout_index && !opts->checkout_worktree)
+       if (!opts->from_treeish && opts->checkout_index)
                opts->from_treeish = "HEAD";
 
        /*
         * From here on, new_branch will contain the branch to be checked out,
         * and new_branch_force and new_orphan_branch will tell us which one of
-        * -b/-B/--orphan is being used.
+        * -b/-B/-c/-C/--orphan is being used.
         */
        if (opts->new_branch_force)
                opts->new_branch = opts->new_branch_force;
@@ -1598,7 +1626,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        if (opts->new_orphan_branch)
                opts->new_branch = opts->new_orphan_branch;
 
-       /* --track without -b/-B/--orphan should DWIM */
+       /* --track without -c/-C/-b/-B/--orphan should DWIM */
        if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
                const char *argv0 = argv[0];
                if (!argc || !strcmp(argv0, "--"))
@@ -1607,7 +1635,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                skip_prefix(argv0, "remotes/", &argv0);
                argv0 = strchr(argv0, '/');
                if (!argv0 || !argv0[1])
-                       die(_("missing branch name; try -b"));
+                       die(_("missing branch name; try -%c"), cb_option);
                opts->new_branch = argv0 + 1;
        }
 
@@ -1710,7 +1738,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 
        UNLEAK(opts);
        if (opts->patch_mode || opts->pathspec.nr)
-               return checkout_paths(opts, new_branch_info.name);
+               return checkout_paths(opts, &new_branch_info);
        else
                return checkout_branch(opts, &new_branch_info);
 }
@@ -1798,6 +1826,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
        options = add_common_options(&opts, options);
        options = add_common_switch_branch_options(&opts, options);
 
+       cb_option = 'c';
+
        ret = checkout_main(argc, argv, prefix, &opts,
                            options, switch_branch_usage);
        FREE_AND_NULL(options);
index 5abf087e7c495153b36b927b5b615f80a68d414c..4ca12bc0c0ba2c3bc3084a320835cf83600173ce 100644 (file)
@@ -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;
                        }
@@ -912,8 +906,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
                OPT_BOOL('d', NULL, &remove_directories,
                                N_("remove whole directories")),
-               { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
-                 N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
+               OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"),
+                 N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb),
                OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
                OPT_BOOL('X', NULL, &ignored_only,
                                N_("remove only ignored files")),
@@ -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);
 
index 1ad26f4d8c81b3e26bca8c3701162ab0e8120e87..cb48a291caf9a364f21208e762f6b6e07a6ca7f6 100644 (file)
@@ -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"),
@@ -420,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
        struct dir_iterator *iter;
        int iter_status;
        unsigned int flags;
+       struct strbuf realpath = STRBUF_INIT;
 
        mkdir_if_missing(dest->buf, 0777);
 
@@ -454,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
                if (unlink(dest->buf) && errno != ENOENT)
                        die_errno(_("failed to unlink '%s'"), dest->buf);
                if (!option_no_hardlinks) {
-                       if (!link(real_path(src->buf), dest->buf))
+                       strbuf_realpath(&realpath, src->buf, 1);
+                       if (!link(realpath.buf, dest->buf))
                                continue;
                        if (option_local > 0)
                                die_errno(_("failed to create link '%s'"), dest->buf);
@@ -468,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
                strbuf_setlen(src, src_len);
                die(_("failed to iterate over '%s'"), src->buf);
        }
+
+       strbuf_release(&realpath);
 }
 
 static void clone_local(const char *src_repo, const char *dest_repo)
@@ -639,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);
@@ -672,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;
 
@@ -682,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"));
@@ -780,11 +783,11 @@ static int checkout(int submodule_progress)
        if (!strcmp(head, "HEAD")) {
                if (advice_detached_head)
                        detach_advice(oid_to_hex(&oid));
+               FREE_AND_NULL(head);
        } else {
                if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        }
-       free(head);
 
        /* We need to be in the new work tree for the checkout */
        setup_work_tree();
@@ -799,6 +802,7 @@ static int checkout(int submodule_progress)
        opts.verbose_update = (option_verbosity >= 0);
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
+       init_checkout_metadata(&opts.meta, head, &oid, NULL);
 
        tree = parse_tree_indirect(&oid);
        parse_tree(tree);
@@ -806,6 +810,8 @@ static int checkout(int submodule_progress)
        if (unpack_trees(1, &t, &opts) < 0)
                die(_("unable to checkout working tree"));
 
+       free(head);
+
        if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
@@ -1102,7 +1108,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                }
        }
 
-       init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
+       init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET);
 
        if (real_git_dir)
                git_dir = real_git_dir;
@@ -1275,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);
 
index 4a70b33fb5f1359397742fcf8601c4c1d49109fe..15fe60317c7846df93d9975dc991ba3a67bd0758 100644 (file)
@@ -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,
@@ -39,14 +44,17 @@ static struct object_directory *find_odb(struct repository *r,
 {
        struct object_directory *odb;
        char *obj_dir_real = real_pathdup(obj_dir, 1);
+       struct strbuf odb_path_real = STRBUF_INIT;
 
        prepare_alt_odb(r);
        for (odb = r->objects->odb; odb; odb = odb->next) {
-               if (!strcmp(obj_dir_real, real_path(odb->path)))
+               strbuf_realpath(&odb_path_real, odb->path, 1);
+               if (!strcmp(obj_dir_real, odb_path_real.buf))
                        break;
        }
 
        free(obj_dir_real);
+       strbuf_release(&odb_path_real);
 
        if (!odb)
                die(_("could not find object directory matching %s"), obj_dir);
@@ -111,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;
@@ -132,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(),
        };
 
@@ -165,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);
@@ -185,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;
                }
 
@@ -194,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;
index b866d8395104e61b37ba6eedc1c538d9ba1faabf..1031b9a491c5cec1411ff822402e1fd39b9fca4f 100644 (file)
@@ -108,15 +108,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        struct object_id commit_oid;
 
        struct option options[] = {
-               { OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"),
+               OPT_CALLBACK_F('p', NULL, &parents, N_("parent"),
                        N_("id of a parent commit object"), PARSE_OPT_NONEG,
-                       parse_parent_arg_callback },
-               { OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"),
+                       parse_parent_arg_callback),
+               OPT_CALLBACK_F('m', NULL, &buffer, N_("message"),
                        N_("commit message"), PARSE_OPT_NONEG,
-                       parse_message_arg_callback },
-               { OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"),
+                       parse_message_arg_callback),
+               OPT_CALLBACK_F('F', NULL, &buffer, N_("file"),
                        N_("read commit log message from file"), PARSE_OPT_NONEG,
-                       parse_file_arg_callback },
+                       parse_file_arg_callback),
                { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
                        N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                OPT_END()
index 7ba33a3bec48de4b8a7b6433df1205bde9e3ebfa..d1b7396052a2449f3ebda88df62cf7b89499090f 100644 (file)
@@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
 "    git commit --allow-empty\n"
 "\n");
 
+static const char empty_rebase_pick_advice[] =
+N_("Otherwise, please use 'git rebase --skip'\n");
+
 static const char empty_cherry_pick_advice_single[] =
 N_("Otherwise, please use 'git cherry-pick --skip'\n");
 
@@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode;
 static const char *cleanup_arg;
 
 static enum commit_whence whence;
-static int sequencer_in_use;
 static int use_editor = 1, include_status = 1;
 static int have_option_m;
 static struct strbuf message = STRBUF_INIT;
@@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s)
 {
        if (file_exists(git_path_merge_head(the_repository)))
                whence = FROM_MERGE;
-       else if (file_exists(git_path_cherry_pick_head(the_repository))) {
-               whence = FROM_CHERRY_PICK;
-               if (file_exists(git_path_seq_dir()))
-                       sequencer_in_use = 1;
-       }
-       else
+       else if (!sequencer_determine_whence(the_repository, &whence))
                whence = FROM_COMMIT;
        if (s)
                s->whence = whence;
@@ -477,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
        if (whence != FROM_COMMIT) {
                if (whence == FROM_MERGE)
                        die(_("cannot do a partial commit during a merge."));
-               else if (whence == FROM_CHERRY_PICK)
+               else if (is_from_cherry_pick(whence))
                        die(_("cannot do a partial commit during a cherry-pick."));
+               else if (is_from_rebase(whence))
+                       die(_("cannot do a partial commit during a rebase."));
        }
 
        if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
@@ -795,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        else if (whence == FROM_MERGE)
                hook_arg1 = "merge";
-       else if (whence == FROM_CHERRY_PICK) {
+       else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) {
                hook_arg1 = "commit";
                hook_arg2 = "CHERRY_PICK_HEAD";
        }
@@ -973,12 +972,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
-               else if (whence == FROM_CHERRY_PICK) {
+               else if (is_from_cherry_pick(whence) ||
+                        whence == FROM_REBASE_PICK) {
                        fputs(_(empty_cherry_pick_advice), stderr);
-                       if (!sequencer_in_use)
+                       if (whence == FROM_CHERRY_PICK_SINGLE)
                                fputs(_(empty_cherry_pick_advice_single), stderr);
-                       else
+                       else if (whence == FROM_CHERRY_PICK_MULTI)
                                fputs(_(empty_cherry_pick_advice_multi), stderr);
+                       else
+                               fputs(_(empty_rebase_pick_advice), stderr);
                }
                return 0;
        }
@@ -1181,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (amend && whence != FROM_COMMIT) {
                if (whence == FROM_MERGE)
                        die(_("You are in the middle of a merge -- cannot amend."));
-               else if (whence == FROM_CHERRY_PICK)
+               else if (is_from_cherry_pick(whence))
                        die(_("You are in the middle of a cherry-pick -- cannot amend."));
+               else if (whence == FROM_REBASE_PICK)
+                       die(_("You are in the middle of a rebase -- cannot amend."));
        }
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
@@ -1204,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
-       if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
+       if (!use_message && !is_from_cherry_pick(whence) &&
+           !is_from_rebase(whence) && renew_authorship)
                die(_("--reset-author can be used only with -C, -c or --amend."));
        if (use_message) {
                use_message_buffer = read_commit_message(use_message);
@@ -1213,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                        author_message_buffer = use_message_buffer;
                }
        }
-       if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+       if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) &&
+           !renew_authorship) {
                author_message = "CHERRY_PICK_HEAD";
                author_message_buffer = read_commit_message(author_message);
        }
@@ -1366,9 +1372,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                         N_("show stash information")),
                OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
                         N_("compute full ahead/behind values")),
-               { OPTION_CALLBACK, 0, "porcelain", &status_format,
+               OPT_CALLBACK_F(0, "porcelain", &status_format,
                  N_("version"), N_("machine-readable output"),
-                 PARSE_OPT_OPTARG, opt_parse_porcelain },
+                 PARSE_OPT_OPTARG, opt_parse_porcelain),
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
@@ -1387,9 +1393,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
                OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
                OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
-               { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
+               OPT_CALLBACK_F('M', "find-renames", &rename_score_arg,
                  N_("n"), N_("detect renames, optionally set similarity index"),
-                 PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score },
+                 PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score),
                OPT_END(),
        };
 
@@ -1488,7 +1494,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
-       const char *argv_gc_auto[] = {"gc", "--auto", NULL};
        static struct wt_status s;
        static struct option builtin_commit_options[] = {
                OPT__QUIET(&quiet, N_("suppress summary after successful commit")),
@@ -1631,8 +1636,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        reduce_heads_replace(&parents);
        } else {
                if (!reflog_msg)
-                       reflog_msg = (whence == FROM_CHERRY_PICK)
+                       reflog_msg = is_from_cherry_pick(whence)
                                        ? "commit (cherry-pick)"
+                                       : is_from_rebase(whence)
+                                       ? "commit (rebase)"
                                        : "commit";
                commit_list_insert(current_head, &parents);
        }
@@ -1659,7 +1666,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        if (amend) {
-               const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+               const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
                extra = read_commit_extra_headers(current_head, exclude_gpgsig);
        } else {
                struct commit_extra_header **tail = &extra;
@@ -1692,12 +1699,10 @@ 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);
+       run_auto_gc(quiet);
        run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
        if (amend && !no_post_rewrite) {
                commit_post_rewrite(the_repository, current_head, &oid);
@@ -1713,6 +1718,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;
index 420f4c6401b6204bb422325ed0c08d713b2dfcda..21d2cb9e57f4bf5c33f47989128710411c431d5d 100644 (file)
@@ -54,6 +54,7 @@ struct commit_name {
        struct tag *tag;
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
+       unsigned misnamed:1;
        struct object_id oid;
        char *path;
 };
@@ -132,6 +133,7 @@ static void add_to_known_names(const char *path,
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
+               e->misnamed = 0;
                oidcpy(&e->oid, oid);
                free(e->path);
                e->path = xstrdup(path);
@@ -275,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst)
                        die(_("annotated tag %s not available"), n->path);
        }
        if (n->tag && !n->name_checked) {
-               if (!n->tag->tag)
-                       die(_("annotated tag %s has no embedded name"), n->path);
-               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
-                       warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
+               if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) {
+                       warning(_("tag '%s' is externally known as '%s'"),
+                               n->path, n->tag->tag);
+                       n->misnamed = 1;
+               }
                n->name_checked = 1;
        }
 
@@ -314,7 +317,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
                 * Exact match to an existing ref.
                 */
                append_name(n, dst);
-               if (longformat)
+               if (n->misnamed || longformat)
                        append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
                if (suffix)
                        strbuf_addstr(dst, suffix);
@@ -463,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
        }
 
        append_name(all_matches[0].name, dst);
-       if (abbrev)
+       if (all_matches[0].name->misnamed || abbrev)
                append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
        if (suffix)
                strbuf_addstr(dst, suffix);
index cb9ea793675c850fb4bf9ab87a8e1b62f1695b02..802363d0a2295f855828121a5b99184d6475b74f 100644 (file)
@@ -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;
 
index 42ac803091e6a0c7e22203b097d1f0001c9f5302..8537b17bd5e42aeb52a676f39a2d9a5d48e65aef 100644 (file)
@@ -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
index dc1485c8aa1bf2b623aaa18ccd710644a34e0153..47711000725b4fb7dc504c62cc2d4ac384f7d46b 100644 (file)
@@ -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[] =
index bf6bab80fab915242f412de7f0ced4a92d95f930..b5788c16bf43da12c81525498cf056a9b828962e 100644 (file)
@@ -27,6 +27,7 @@
 #include "branch.h"
 #include "promisor-remote.h"
 #include "commit-graph.h"
+#include "shallow.h"
 
 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 
@@ -156,9 +157,9 @@ static struct option builtin_fetch_options[] = {
                 N_("prune remote-tracking branches no longer on remote")),
        OPT_BOOL('P', "prune-tags", &prune_tags,
                 N_("prune local tags no longer on remote and clobber changed tags")),
-       { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
+       OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
                    N_("control recursive fetching of submodules"),
-                   PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
+                   PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
        OPT_BOOL(0, "dry-run", &dry_run,
                 N_("dry run")),
        OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
@@ -178,15 +179,15 @@ static struct option builtin_fetch_options[] = {
                      1, PARSE_OPT_NONEG),
        { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
                   N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
-       { OPTION_CALLBACK, 0, "recurse-submodules-default",
+       OPT_CALLBACK_F(0, "recurse-submodules-default",
                   &recurse_submodules_default, N_("on-demand"),
                   N_("default for recursive fetching of submodules "
                      "(lower priority than config files)"),
-                  PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules },
+                  PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules),
        OPT_BOOL(0, "update-shallow", &update_shallow,
                 N_("accept refs that update .git/shallow")),
-       { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
-         N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
+       OPT_CALLBACK_F(0, "refmap", NULL, N_("refmap"),
+                      N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg),
        OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
        OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
                        TRANSPORT_FAMILY_IPV4),
@@ -908,13 +909,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);
@@ -1759,7 +1753,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        struct remote *remote = NULL;
        int result = 0;
        int prune_tags_ok = 1;
-       struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
 
        packet_trace_identity("fetch");
 
@@ -1886,13 +1879,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
        close_object_store(the_repository->objects);
 
-       if (enable_auto_gc) {
-               argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
-               if (verbosity < 0)
-                       argv_array_push(&argv_gc_auto, "--quiet");
-               run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
-               argv_array_clear(&argv_gc_auto);
-       }
+       if (enable_auto_gc)
+               run_auto_gc(verbosity < 0);
 
        return result;
 }
index 736f666f644c7cf78b816888ba226693464fc331..48a8699de728a950053b182fae23e73b14fba6a6 100644 (file)
 #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 strbuf sig = STRBUF_INIT;
-
-               if (!buf || type != OBJ_TAG)
-                       goto next;
-               len = parse_signature(buf, size);
-
-               if (size == len)
-                       ; /* merely annotated */
-               else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
-                       if (!sig.len)
-                               strbuf_addstr(&sig, "gpg verification failed.\n");
-               }
-
-               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;
index 465153e85337fdf6b857a94b8c245fe33832b0dc..57489e4eab1c16d94806e82471371b9be574d9ef 100644 (file)
@@ -70,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 
        if (!sorting)
                sorting = ref_default_sorting();
-       sorting->ignore_case = icase;
+       ref_sorting_icase_all(sorting, icase);
        filter.ignore_case = icase;
 
        filter.name_patterns = argv;
index 99e26850907b74374000675aa19bb42cf39c32b2..a5056f395aae16e21b032592558f4389b6953294 100644 (file)
@@ -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;
@@ -886,20 +906,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, N_("file"),
                        N_("read patterns from file"), file_callback),
-               { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"),
-                       N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback },
-               { OPTION_CALLBACK, 0, "and", &opt, NULL,
-                 N_("combine patterns specified with -e"),
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+               OPT_CALLBACK_F('e', NULL, &opt, N_("pattern"),
+                       N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback),
+               OPT_CALLBACK_F(0, "and", &opt, NULL,
+                       N_("combine patterns specified with -e"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback),
                OPT_BOOL(0, "or", &dummy, ""),
-               { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
-               { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
-                 open_callback },
-               { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
-                 PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
-                 close_callback },
+               OPT_CALLBACK_F(0, "not", &opt, NULL, "",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback),
+               OPT_CALLBACK_F('(', NULL, &opt, NULL, "",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+                       open_callback),
+               OPT_CALLBACK_F(')', NULL, &opt, NULL, "",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+                       close_callback),
                OPT__QUIET(&opt.status_only,
                           N_("indicate hit with exit status without output")),
                OPT_BOOL(0, "all-match", &opt.all_match,
index e5590d7787c50af121db0ba7e6221e89c6785df1..299206eb573985370330fd10e619c2343209de9c 100644 (file)
@@ -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;
index d967d188a307fe8763b5fdff08b570999e2a853d..f176dd28c870d5e417b373f2bb0d39b4fa1a0c29 100644 (file)
@@ -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);
        }
 
index 944ec77fe1032775ff595f0b1eb99a1fb22d059f..0b7222e7188858a7c820e870dce4c2dd0370f443 100644 (file)
@@ -20,6 +20,8 @@
 #define TEST_FILEMODE 1
 #endif
 
+#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH"
+
 static int init_is_bare_repository = 0;
 static int init_shared_repository = -1;
 static const char *init_db_template_dir;
@@ -176,13 +178,36 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
        return 1;
 }
 
+void initialize_repository_version(int hash_algo)
+{
+       char repo_version_string[10];
+       int repo_version = GIT_REPO_VERSION;
+
+#ifndef ENABLE_SHA256
+       if (hash_algo != GIT_HASH_SHA1)
+               die(_("The hash algorithm %s is not supported in this build."), hash_algos[hash_algo].name);
+#endif
+
+       if (hash_algo != GIT_HASH_SHA1)
+               repo_version = GIT_REPO_VERSION_READ;
+
+       /* This forces creation of new config file */
+       xsnprintf(repo_version_string, sizeof(repo_version_string),
+                 "%d", repo_version);
+       git_config_set("core.repositoryformatversion", repo_version_string);
+
+       if (hash_algo != GIT_HASH_SHA1)
+               git_config_set("extensions.objectformat",
+                              hash_algos[hash_algo].name);
+}
+
 static int create_default_files(const char *template_path,
-                               const char *original_git_dir)
+                               const char *original_git_dir,
+                               const struct repository_format *fmt)
 {
        struct stat st1;
        struct strbuf buf = STRBUF_INIT;
        char *path;
-       char repo_version_string[10];
        char junk[2];
        int reinit;
        int filemode;
@@ -244,10 +269,7 @@ static int create_default_files(const char *template_path,
                        exit(1);
        }
 
-       /* This forces creation of new config file */
-       xsnprintf(repo_version_string, sizeof(repo_version_string),
-                 "%d", GIT_REPO_VERSION);
-       git_config_set("core.repositoryformatversion", repo_version_string);
+       initialize_repository_version(fmt->hash_algo);
 
        /* Check filemode trustability */
        path = git_path_buf(&buf, "config");
@@ -340,12 +362,33 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
        write_file(git_link, "gitdir: %s", git_dir);
 }
 
+static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash)
+{
+       const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
+       /*
+        * If we already have an initialized repo, don't allow the user to
+        * specify a different algorithm, as that could cause corruption.
+        * Otherwise, if the user has specified one on the command line, use it.
+        */
+       if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo)
+               die(_("attempt to reinitialize repository with different hash"));
+       else if (hash != GIT_HASH_UNKNOWN)
+               repo_fmt->hash_algo = hash;
+       else if (env) {
+               int env_algo = hash_algo_by_name(env);
+               if (env_algo == GIT_HASH_UNKNOWN)
+                       die(_("unknown hash algorithm '%s'"), env);
+               repo_fmt->hash_algo = env_algo;
+       }
+}
+
 int init_db(const char *git_dir, const char *real_git_dir,
-           const char *template_dir, unsigned int flags)
+           const char *template_dir, int hash, unsigned int flags)
 {
        int reinit;
        int exist_ok = flags & INIT_DB_EXIST_OK;
        char *original_git_dir = real_pathdup(git_dir, 1);
+       struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 
        if (real_git_dir) {
                struct stat st;
@@ -356,12 +399,12 @@ int init_db(const char *git_dir, const char *real_git_dir,
                if (!exist_ok && !stat(real_git_dir, &st))
                        die(_("%s already exists"), real_git_dir);
 
-               set_git_dir(real_path(real_git_dir));
+               set_git_dir(real_git_dir, 1);
                git_dir = get_git_dir();
                separate_git_dir(git_dir, original_git_dir);
        }
        else {
-               set_git_dir(real_path(git_dir));
+               set_git_dir(git_dir, 1);
                git_dir = get_git_dir();
        }
        startup_info->have_repository = 1;
@@ -378,9 +421,11 @@ int init_db(const char *git_dir, const char *real_git_dir,
         * config file, so this will not fail.  What we are catching
         * is an attempt to reinitialize new repository with an old tool.
         */
-       check_repository_format();
+       check_repository_format(&repo_fmt);
 
-       reinit = create_default_files(template_dir, original_git_dir);
+       validate_hash_algorithm(&repo_fmt, hash);
+
+       reinit = create_default_files(template_dir, original_git_dir, &repo_fmt);
 
        create_object_directory();
 
@@ -482,6 +527,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *work_tree;
        const char *template_dir = NULL;
        unsigned int flags = 0;
+       const char *object_format = NULL;
+       int hash_algo = GIT_HASH_UNKNOWN;
        const struct option init_db_options[] = {
                OPT_STRING(0, "template", &template_dir, N_("template-directory"),
                                N_("directory from which templates will be used")),
@@ -494,6 +541,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
                OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
                           N_("separate git dir from working tree")),
+               OPT_STRING(0, "object-format", &object_format, N_("hash"),
+                          N_("specify the hash algorithm to use")),
                OPT_END()
        };
 
@@ -546,6 +595,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                free(cwd);
        }
 
+       if (object_format) {
+               hash_algo = hash_algo_by_name(object_format);
+               if (hash_algo == GIT_HASH_UNKNOWN)
+                       die(_("unknown hash algorithm '%s'"), object_format);
+       }
+
        if (init_shared_repository != -1)
                set_shared_repository(init_shared_repository);
 
@@ -597,5 +652,5 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        UNLEAK(work_tree);
 
        flags |= INIT_DB_EXIST_OK;
-       return init_db(git_dir, real_git_dir, template_dir, flags);
+       return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags);
 }
index f101d092b883e6554cce193c66626b5707252c3f..84748eafc01bf148dfe161161f918212cd577cee 100644 (file)
@@ -105,8 +105,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")),
                OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")),
                OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")),
-               { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"),
-                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse },
+               OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("set parsing options"),
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),
                OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")),
                OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"),
                                N_("trailer(s) to add"), option_parse_trailer),
index 83a4a6188e221caefc5028e59cb7c95c2f1d1e0c..d104d5c6889ba2d2ffaaeafa08fbfcd759d8bc92 100644 (file)
@@ -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,21 +166,24 @@ 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,
                                N_("pattern"), N_("do not decorate refs that match <pattern>")),
-               { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
-                 PARSE_OPT_OPTARG, decorate_callback},
+               OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"),
+                              PARSE_OPT_OPTARG, decorate_callback),
                OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
                             N_("Process line range n,m in file, counting from 1"),
                             log_line_range_callback),
@@ -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;
@@ -1625,12 +1646,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int creation_factor = -1;
 
        const struct option builtin_format_patch_options[] = {
-               { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
+               OPT_CALLBACK_F('n', "numbered", &numbered, NULL,
                            N_("use [PATCH n/m] even with a single patch"),
-                           PARSE_OPT_NOARG, numbered_callback },
-               { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
+                           PARSE_OPT_NOARG, numbered_callback),
+               OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL,
                            N_("use [PATCH] even with multiple patches"),
-                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback },
+                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback),
                OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
                OPT_BOOL(0, "stdout", &use_stdout,
                            N_("print patches to standard out")),
@@ -1644,21 +1665,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                            N_("start numbering patches at <n> instead of 1")),
                OPT_INTEGER('v', "reroll-count", &reroll_count,
                            N_("mark the series as Nth re-roll")),
-               { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
+               OPT_CALLBACK_F(0, "rfc", &rev, NULL,
                            N_("Use [RFC PATCH] instead of [PATCH]"),
-                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback),
                OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
                            N_("cover-from-description-mode"),
                            N_("generate parts of a cover letter based on a branch's description")),
-               { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
+               OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"),
                            N_("Use [<prefix>] instead of [PATCH]"),
-                           PARSE_OPT_NONEG, subject_prefix_callback },
-               { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
+                           PARSE_OPT_NONEG, subject_prefix_callback),
+               OPT_CALLBACK_F('o', "output-directory", &output_directory,
                            N_("dir"), N_("store resulting files in <dir>"),
-                           PARSE_OPT_NONEG, output_directory_callback },
-               { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
+                           PARSE_OPT_NONEG, output_directory_callback),
+               OPT_CALLBACK_F('k', "keep-subject", &rev, NULL,
                            N_("don't strip/add [PATCH]"),
-                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
+                           PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback),
                OPT_BOOL(0, "no-binary", &no_binary_diff,
                         N_("don't output binary diffs")),
                OPT_BOOL(0, "zero-commit", &zero_commit,
@@ -1669,27 +1690,25 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                              N_("show patch format instead of default (patch + stat)"),
                              1, PARSE_OPT_NONEG),
                OPT_GROUP(N_("Messaging")),
-               { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
-                           N_("add email header"), 0, header_callback },
-               { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"),
-                           0, to_callback },
-               { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
-                           0, cc_callback },
-               { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+               OPT_CALLBACK(0, "add-header", NULL, N_("header"),
+                           N_("add email header"), header_callback),
+               OPT_CALLBACK(0, "to", NULL, N_("email"), N_("add To: header"), to_callback),
+               OPT_CALLBACK(0, "cc", NULL, N_("email"), N_("add Cc: header"), cc_callback),
+               OPT_CALLBACK_F(0, "from", &from, N_("ident"),
                            N_("set From address to <ident> (or committer ident if absent)"),
-                           PARSE_OPT_OPTARG, from_callback },
+                           PARSE_OPT_OPTARG, from_callback),
                OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
                            N_("make first mail a reply to <message-id>")),
-               { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
+               OPT_CALLBACK_F(0, "attach", &rev, N_("boundary"),
                            N_("attach the patch"), PARSE_OPT_OPTARG,
-                           attach_callback },
-               { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"),
+                           attach_callback),
+               OPT_CALLBACK_F(0, "inline", &rev, N_("boundary"),
                            N_("inline the patch"),
                            PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
-                           inline_callback },
-               { OPTION_CALLBACK, 0, "thread", &thread, N_("style"),
+                           inline_callback),
+               OPT_CALLBACK_F(0, "thread", &thread, N_("style"),
                            N_("enable message threading, styles: shallow, deep"),
-                           PARSE_OPT_OPTARG, thread_callback },
+                           PARSE_OPT_OPTARG, thread_callback),
                OPT_STRING(0, "signature", &signature, N_("signature"),
                            N_("add a signature")),
                OPT_STRING(0, "base", &base_commit, N_("base-commit"),
@@ -1719,6 +1738,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;
index f069a028cea1afa09edda8d039437fe39d9585ac..30a4c10334982e9ea34729a979a83027fa83c940 100644 (file)
@@ -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);
@@ -554,18 +555,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                        N_("show unmerged files in the output")),
                OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
                            N_("show resolve-undo information")),
-               { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
+               OPT_CALLBACK_F('x', "exclude", &exclude_list, N_("pattern"),
                        N_("skip files matching pattern"),
-                       PARSE_OPT_NONEG, option_parse_exclude },
-               { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
+                       PARSE_OPT_NONEG, option_parse_exclude),
+               OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"),
                        N_("exclude patterns are read from <file>"),
-                       PARSE_OPT_NONEG, option_parse_exclude_from },
+                       PARSE_OPT_NONEG, option_parse_exclude_from),
                OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
                        N_("read additional per-directory exclude patterns in <file>")),
-               { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+               OPT_CALLBACK_F(0, "exclude-standard", &dir, NULL,
                        N_("add the standard git exclusions"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       option_parse_exclude_standard },
+                       option_parse_exclude_standard),
                OPT_SET_INT_F(0, "full-name", &prefix_len,
                              N_("make the output relative to the project top directory"),
                              0, PARSE_OPT_NONEG),
index e3f8da13b69b8dbe6898bf71ee2b5df206179bd1..6719ac198dc2092ae49829d5946ca00664e34b0e 100644 (file)
@@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv)
 static int handle_fork_point(int argc, const char **argv)
 {
        struct object_id oid;
-       char *refname;
        struct commit *derived, *fork_point;
        const char *commitname;
 
-       switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
-       case 0:
-               die("No such ref: '%s'", argv[0]);
-       case 1:
-               break; /* good */
-       default:
-               die("Ambiguous refname: '%s'", argv[0]);
-       }
-
        commitname = (argc == 2) ? argv[1] : "HEAD";
        if (get_oid(commitname, &oid))
                die("Not a valid object name: '%s'", commitname);
 
        derived = lookup_commit_reference(the_repository, &oid);
 
-       fork_point = get_fork_point(refname, derived);
+       fork_point = get_fork_point(argv[0], derived);
 
        if (!fork_point)
                return 1;
index d127d2225f897f111124fb55b12cd1aae7db7a8e..ca6a5dc4bf782ef1ac88c817365483cc22c18f58 100644 (file)
@@ -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[] = {
@@ -241,9 +243,9 @@ static int option_parse_n(const struct option *opt,
 }
 
 static struct option builtin_merge_options[] = {
-       { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+       OPT_CALLBACK_F('n', NULL, NULL, NULL,
                N_("do not show a diffstat at the end of the merge"),
-               PARSE_OPT_NOARG, option_parse_n },
+               PARSE_OPT_NOARG, option_parse_n),
        OPT_BOOL(0, "stat", &show_diffstat,
                N_("show a diffstat at the end of the merge")),
        OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
@@ -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")),
@@ -447,7 +450,6 @@ static void finish(struct commit *head_commit,
                if (verbosity >= 0 && !merge_msg.len)
                        printf(_("No merge message -- not updating HEAD\n"));
                else {
-                       const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD", new_head, head,
                                   0, UPDATE_REFS_DIE_ON_ERR);
                        /*
@@ -455,7 +457,7 @@ static void finish(struct commit *head_commit,
                         * user should see them.
                         */
                        close_object_store(the_repository->objects);
-                       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+                       run_auto_gc(verbosity < 0);
                }
        }
        if (new_head && show_diffstat) {
@@ -475,6 +477,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 +600,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 +639,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 +1289,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 +1298,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 +1531,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 +1601,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 +1700,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)
index 35e468ea2d2fb24e9985e0311f9beea31b5eb7a2..2987c08a2e920d294f5a443d24381b6021c15fea 100644 (file)
@@ -406,18 +406,18 @@ static int add(int argc, const char **argv, const char *prefix)
        const struct object_id *note;
        struct note_data d = { 0, 0, NULL, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+               OPT_CALLBACK_F('m', "message", &d, N_("message"),
                        N_("note contents as a string"), PARSE_OPT_NONEG,
-                       parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+                       parse_msg_arg),
+               OPT_CALLBACK_F('F', "file", &d, N_("file"),
                        N_("note contents in a file"), PARSE_OPT_NONEG,
-                       parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+                       parse_file_arg),
+               OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
                        N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
-                       parse_reedit_arg},
-               { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+                       parse_reedit_arg),
+               OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"),
                        N_("reuse specified note object"), PARSE_OPT_NONEG,
-                       parse_reuse_arg},
+                       parse_reuse_arg),
                OPT_BOOL(0, "allow-empty", &allow_empty,
                        N_("allow storing empty note")),
                OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
@@ -572,18 +572,18 @@ static int append_edit(int argc, const char **argv, const char *prefix)
        const char * const *usage;
        struct note_data d = { 0, 0, NULL, STRBUF_INIT };
        struct option options[] = {
-               { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+               OPT_CALLBACK_F('m', "message", &d, N_("message"),
                        N_("note contents as a string"), PARSE_OPT_NONEG,
-                       parse_msg_arg},
-               { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+                       parse_msg_arg),
+               OPT_CALLBACK_F('F', "file", &d, N_("file"),
                        N_("note contents in a file"), PARSE_OPT_NONEG,
-                       parse_file_arg},
-               { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+                       parse_file_arg),
+               OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
                        N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
-                       parse_reedit_arg},
-               { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+                       parse_reedit_arg),
+               OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"),
                        N_("reuse specified note object"), PARSE_OPT_NONEG,
-                       parse_reuse_arg},
+                       parse_reuse_arg),
                OPT_BOOL(0, "allow-empty", &allow_empty,
                        N_("allow storing empty note")),
                OPT_END()
index 02aa6ee4808a96f264a861bc49789341179056be..c5b433a23fdbf078c3e8894bdb0490ae736e02cd 100644 (file)
@@ -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"
@@ -34,6 +34,7 @@
 #include "dir.h"
 #include "midx.h"
 #include "trace2.h"
+#include "shallow.h"
 
 #define IN_PACK(obj) oe_in_pack(&to_pack, obj)
 #define SIZE(obj) oe_size(&to_pack, obj)
@@ -880,7 +881,7 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out,
                        len = encode_in_pack_object_header(header, sizeof(header),
                                                           OBJ_REF_DELTA, size);
                        hashwrite(out, header, len);
-                       hashwrite(out, base_oid.hash, 20);
+                       hashwrite(out, base_oid.hash, the_hash_algo->rawsz);
                        copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
                        return;
                }
@@ -3380,9 +3381,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "all-progress-implied",
                         &all_progress_implied,
                         N_("similar to --all-progress when progress meter is shown")),
-               { OPTION_CALLBACK, 0, "index-version", NULL, N_("<version>[,<offset>]"),
+               OPT_CALLBACK_F(0, "index-version", NULL, N_("<version>[,<offset>]"),
                  N_("write the pack index file in the specified idx format version"),
-                 PARSE_OPT_NONEG, option_parse_index_version },
+                 PARSE_OPT_NONEG, option_parse_index_version),
                OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit,
                              N_("maximum size of each output pack file")),
                OPT_BOOL(0, "local", &local,
@@ -3427,9 +3428,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         N_("keep unreachable objects")),
                OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable,
                         N_("pack loose unreachable objects")),
-               { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"),
+               OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"),
                  N_("unpack unreachable objects newer than <time>"),
-                 PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
+                 PARSE_OPT_OPTARG, option_parse_unpack_unreachable),
                OPT_BOOL(0, "sparse", &sparse,
                         N_("use the sparse reachability algorithm")),
                OPT_BOOL(0, "thin", &thin,
@@ -3454,9 +3455,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                              N_("write a bitmap index if possible"),
                              WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN),
                OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
-               { OPTION_CALLBACK, 0, "missing", NULL, N_("action"),
+               OPT_CALLBACK_F(0, "missing", NULL, N_("action"),
                  N_("handling for missing objects"), PARSE_OPT_NONEG,
-                 option_parse_missing_action },
+                 option_parse_missing_action),
                OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects,
                         N_("do not pack objects in promisor packfiles")),
                OPT_BOOL(0, "delta-islands", &use_delta_islands,
@@ -3469,9 +3470,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 
        read_replace_refs = 0;
 
-       sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
+       sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
        prepare_repo_settings(the_repository);
-       if (!sparse && the_repository->settings.pack_use_sparse != -1)
+       if (sparse < 0)
                sparse = the_repository->settings.pack_use_sparse;
 
        reset_pack_idx_option(&pack_idx_opts);
index 48c5e78e339dbd32db255382f729ca2e65b514c5..b7b9281a8cea2c009ef9a4487094613f032f6aba 100644 (file)
@@ -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;
index 2b76872ad2207857077f4ecf285780e94388d00d..02c6ab7cbaafbac6492fd458bedc0f162e81d683 100644 (file)
@@ -6,7 +6,9 @@
 #include "reachable.h"
 #include "parse-options.h"
 #include "progress.h"
+#include "prune-packed.h"
 #include "object-store.h"
+#include "shallow.h"
 
 static const char * const prune_usage[] = {
        N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
index 3e624d1e008588ed063a7a960026287d34b84965..00e5857a8d18da3394d6e5a3316e8f56bb98fdb5 100644 (file)
@@ -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 */
@@ -117,17 +118,17 @@ static struct option pull_options[] = {
        OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
                N_("force progress reporting"),
                PARSE_OPT_NOARG),
-       { OPTION_CALLBACK, 0, "recurse-submodules",
+       OPT_CALLBACK_F(0, "recurse-submodules",
                   &recurse_submodules, N_("on-demand"),
                   N_("control for recursive fetching of submodules"),
-                  PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
+                  PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
 
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
-       { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
+       OPT_CALLBACK_F('r', "rebase", &opt_rebase,
          "(false|true|merges|preserve|interactive)",
          N_("incorporate changes by rebasing rather than merging"),
-         PARSE_OPT_OPTARG, parse_opt_rebase },
+         PARSE_OPT_OPTARG, parse_opt_rebase),
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
                N_("do not show a diffstat at the end of the merge"),
                PARSE_OPT_NOARG | PARSE_OPT_NONEG),
@@ -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,
@@ -327,6 +344,22 @@ static enum rebase_type config_get_rebase(void)
        if (!git_config_get_value("pull.rebase", &value))
                return parse_config_rebase("pull.rebase", value, 1);
 
+       if (opt_verbosity >= 0 &&
+           (!opt_ff || strcmp(opt_ff, "--ff-only"))) {
+               warning(_("Pulling without specifying how to reconcile divergent branches is\n"
+                       "discouraged. You can squelch this message by running one of the following\n"
+                       "commands sometime before your next pull:\n"
+                       "\n"
+                       "  git config pull.rebase false  # merge (the default strategy)\n"
+                       "  git config pull.rebase true   # rebase\n"
+                       "  git config pull.ff only       # fast-forward only\n"
+                       "\n"
+                       "You can replace \"git config\" with \"git config --global\" to set a default\n"
+                       "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+                       "or --ff-only on the command line to override the configured default per\n"
+                       "invocation.\n"));
+       }
+
        return REBASE_FALSE;
 }
 
@@ -551,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);
@@ -661,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");
 
@@ -908,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)
@@ -976,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))
@@ -992,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))
index 6dbf0f0bb713f167947e8d3d3aa0b8ecae0dc44e..bc94078e72f8e57aaed7d6a4d2ec579b2499a1d4 100644 (file)
@@ -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;
 
@@ -434,10 +436,8 @@ static int option_parse_recurse_submodules(const struct option *opt,
 
        if (unset)
                *recurse_submodules = RECURSE_SUBMODULES_OFF;
-       else if (arg)
-               *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
        else
-               die("%s missing parameter", opt->long_name);
+               *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
 
        return 0;
 }
@@ -548,13 +548,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
                OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
-               { OPTION_CALLBACK,
-                 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
-                 N_("require old value of ref to be at this value"),
-                 PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option },
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
-                       N_("control recursive pushing of submodules"),
-                       PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+               OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
+                              N_("require old value of ref to be at this value"),
+                              PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),
+               OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
+                            N_("control recursive pushing of submodules"), option_parse_recurse_submodules),
                OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE),
                OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")),
@@ -566,9 +564,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
                OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
                        TRANSPORT_PUSH_FOLLOW_TAGS),
-               { OPTION_CALLBACK,
-                 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
-                 PARSE_OPT_OPTARG, option_parse_push_signed },
+               OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
+                               PARSE_OPT_OPTARG, option_parse_push_signed),
                OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
                OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")),
                OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
index af7424b94c8e932a6f8ca2660988ec2867981b7c..485e7b0479488cd0c1de685fc7837738ecf88f0d 100644 (file)
@@ -120,9 +120,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
        int prefix_set = 0;
        struct lock_file lock_file = LOCK_INIT;
        const struct option read_tree_options[] = {
-               { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
+               OPT_CALLBACK_F(0, "index-output", NULL, N_("file"),
                  N_("write resulting index to <file>"),
-                 PARSE_OPT_NONEG, index_output_cb },
+                 PARSE_OPT_NONEG, index_output_cb),
                OPT_BOOL(0, "empty", &read_empty,
                            N_("only empty the index")),
                OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
@@ -140,10 +140,10 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
                  PARSE_OPT_NONEG },
                OPT_BOOL('u', NULL, &opts.update,
                         N_("update working tree with merge result")),
-               { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
+               OPT_CALLBACK_F(0, "exclude-per-directory", &opts,
                  N_("gitignore"),
                  N_("allow explicitly ignored files to be overwritten"),
-                 PARSE_OPT_NONEG, exclude_per_directory_cb },
+                 PARSE_OPT_NONEG, exclude_per_directory_cb),
                OPT_BOOL('i', NULL, &opts.index_only,
                         N_("don't check the working tree after merging")),
                OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
@@ -151,9 +151,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
                         N_("skip applying sparse checkout filter")),
                OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
                         N_("debug unpack-trees")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+               OPT_CALLBACK_F(0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
                OPT__QUIET(&opts.quiet, N_("suppress feedback messages")),
                OPT_END()
        };
index bff53d5d167e8501d7f501046ddb35daf0f2cd17..37ba76ac3d26f86ef3c16b65d2293b873efd8ee5 100644 (file)
@@ -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;
 }
@@ -470,10 +477,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
                           REBASE_FORCE),
-               { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
-                       N_("(DEPRECATED) keep empty commits"),
+               OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
+                       N_("keep commits which start empty"),
                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
-                       parse_opt_keep_empty },
+                       parse_opt_keep_empty),
                OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
                           N_("allow commits with empty messages"),
                           PARSE_OPT_HIDDEN),
@@ -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,65 +719,19 @@ 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;
-       const char *argv_gc_auto[] = { "gc", "--auto", NULL };
        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
         * user should see them.
         */
-       run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+       run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
        if (opts->type == REBASE_MERGE) {
                struct replay_opts replay = REPLAY_OPTS_INIT;
 
@@ -812,143 +770,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;
-       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;
@@ -964,8 +785,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);
@@ -1053,8 +876,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"
@@ -1162,6 +986,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);
@@ -1212,7 +1037,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);
@@ -1453,7 +1278,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;
@@ -1526,18 +1350,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE(0, "show-current-patch", &action,
                            N_("show the patch file being applied or merged"),
                            ACTION_SHOW_CURRENT_PATCH),
-               { OPTION_CALLBACK, 0, "apply", &options, NULL,
+               OPT_CALLBACK_F(0, "apply", &options, NULL,
                        N_("use apply strategies to rebase"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       parse_opt_am },
-               { OPTION_CALLBACK, 'm', "merge", &options, NULL,
+                       parse_opt_am),
+               OPT_CALLBACK_F('m', "merge", &options, NULL,
                        N_("use merging strategies to rebase"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       parse_opt_merge },
-               { OPTION_CALLBACK, 'i', "interactive", &options, NULL,
+                       parse_opt_merge),
+               OPT_CALLBACK_F('i', "interactive", &options, NULL,
                        N_("let the user edit the list of commits to rebase"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       parse_opt_interactive },
+                       parse_opt_interactive),
                OPT_SET_INT_F('p', "preserve-merges", &options.type,
                              N_("(DEPRECATED) try to recreate merges instead of "
                                 "ignoring them"),
@@ -1546,18 +1370,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
                               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"),
+               OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
+                       N_("keep commits which start empty"),
                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
-                       parse_opt_keep_empty },
+                       parse_opt_keep_empty),
                OPT_BOOL(0, "autosquash", &options.autosquash,
                         N_("move commits that begin with "
                            "squash!/fixup! under -i")),
                { 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")),
@@ -1582,6 +1405,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;
@@ -1592,6 +1417,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))
@@ -1652,6 +1480,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);
@@ -1709,8 +1540,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))
@@ -1727,9 +1558,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);
@@ -1737,6 +1568,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;
 
@@ -1822,10 +1654,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;
@@ -2086,49 +1919,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",
@@ -2162,10 +1954,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);
@@ -2237,10 +2031,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);
 
@@ -2255,8 +2049,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;
index 2cc18bbffdcf41cf2510becf6fcdd74c5946494a..ea3d0f01af3b7cfd90e1e235ee4e8694dabf8791 100644 (file)
@@ -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"
@@ -28,6 +28,7 @@
 #include "protocol.h"
 #include "commit-reach.h"
 #include "worktree.h"
+#include "shallow.h"
 
 static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@ -418,7 +419,7 @@ static int copy_to_sideband(int in, int out, void *arg)
        return 0;
 }
 
-static void hmac(unsigned char *out,
+static void hmac_hash(unsigned char *out,
                      const char *key_in, size_t key_len,
                      const char *text, size_t text_len)
 {
@@ -463,10 +464,10 @@ static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp)
        unsigned char hash[GIT_MAX_RAWSZ];
 
        strbuf_addf(&buf, "%s:%"PRItime, path, stamp);
-       hmac(hash, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));
+       hmac_hash(hash, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));
        strbuf_release(&buf);
 
-       /* RFC 2104 5. HMAC-SHA1-80 */
+       /* RFC 2104 5. HMAC-SHA1 or HMAC-SHA256 */
        strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, (int)the_hash_algo->hexsz, hash_to_hex(hash));
        return strbuf_detach(&buf, NULL);
 }
@@ -499,12 +500,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 +562,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;
@@ -855,7 +877,7 @@ static void refuse_unconfigured_deny_delete_current(void)
 static int command_singleton_iterator(void *cb_data, struct object_id *oid);
 static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
 {
-       struct lock_file shallow_lock = LOCK_INIT;
+       struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
        struct oid_array extra = OID_ARRAY_INIT;
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        uint32_t mask = 1 << (cmd->index % 32);
@@ -872,12 +894,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
index 81dfd563c0d937eb8943f84493d2d48e8af15f1b..52ecf6d43c1010e431c2f408a4acb79a31a1543e 100644 (file)
@@ -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;
index 555d4c896c5fba060f3ef65ba591bbdb7d16a355..e8377994e57a2286f9cea329578c8f104a392b88 100644 (file)
@@ -170,9 +170,9 @@ static int add(int argc, const char **argv)
                OPT_STRING_LIST('t', "track", &track, N_("branch"),
                                N_("branch(es) to track")),
                OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")),
-               { OPTION_CALLBACK, 0, "mirror", &mirror, "(push|fetch)",
+               OPT_CALLBACK_F(0, "mirror", &mirror, "(push|fetch)",
                        N_("set up remote as a mirror to push to or fetch from"),
-                       PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt },
+                       PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt),
                OPT_END()
        };
 
index 0781763b06e80d0188acfe3c902061729b762809..df287739d9081cbccb203ca3f8cc744490e949ca 100644 (file)
 #include "argv-array.h"
 #include "midx.h"
 #include "packfile.h"
+#include "prune-packed.h"
 #include "object-store.h"
 #include "promisor-remote.h"
+#include "shallow.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
index 18228c312ea009e79b1ae179f4a40ad3b4a35574..8ae69d6f2b9e5e1760dd40d32631b0ecf48a31e6 100644 (file)
@@ -46,7 +46,7 @@ static inline int is_merge(void)
        return !access(git_path_merge_head(the_repository), F_OK);
 }
 
-static int reset_index(const struct object_id *oid, int reset_type, int quiet)
+static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet)
 {
        int i, nr = 0;
        struct tree_desc desc[2];
@@ -60,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
        opts.dst_index = &the_index;
        opts.fn = oneway_merge;
        opts.merge = 1;
+       init_checkout_metadata(&opts.meta, ref, oid, NULL);
        if (!quiet)
                opts.verbose_update = 1;
        switch (reset_type) {
@@ -301,9 +302,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                N_("reset HEAD, index and working tree"), MERGE),
                OPT_SET_INT(0, "keep", &reset_type,
                                N_("reset HEAD but keep local changes"), KEEP),
-               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+               OPT_CALLBACK_F(0, "recurse-submodules", NULL,
                            "reset", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
                OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
                OPT_BOOL('N', "intent-to-add", &intent_to_add,
                                N_("record only the fact that removed paths will be added later")),
@@ -418,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                }
                        }
                } else {
-                       int err = reset_index(&oid, reset_type, quiet);
+                       struct object_id dummy;
+                       char *ref = NULL;
+                       int err;
+
+                       dwim_ref(rev, strlen(rev), &dummy, &ref);
+                       if (ref && !starts_with(ref, "refs/"))
+                               ref = NULL;
+
+                       err = reset_index(ref, &oid, reset_type, quiet);
                        if (reset_type == KEEP && !err)
-                               err = reset_index(&oid, MIXED, quiet);
+                               err = reset_index(ref, &oid, MIXED, quiet);
                        if (err)
                                die(_("Could not reset index file to revision '%s'."), rev);
+                       free(ref);
                }
 
                if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
index 7a00da820355b61c548449c55381577a2232a0e8..669dd2fd6f087628c3d7dc9ba165771825418799 100644 (file)
@@ -16,6 +16,7 @@
 #include "split-index.h"
 #include "submodule.h"
 #include "commit-reach.h"
+#include "shallow.h"
 
 #define DO_REVS                1
 #define DO_NOREV       2
@@ -808,9 +809,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--show-superproject-working-tree")) {
-                               const char *superproject = get_superproject_working_tree();
-                               if (superproject)
-                                       puts(superproject);
+                               struct strbuf superproject = STRBUF_INIT;
+                               if (get_superproject_working_tree(&superproject))
+                                       puts(superproject.buf);
+                               strbuf_release(&superproject);
                                continue;
                        }
                        if (!strcmp(arg, "--show-prefix")) {
@@ -857,7 +859,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                        if (!gitdir && !prefix)
                                                gitdir = ".git";
                                        if (gitdir) {
-                                               puts(real_path(gitdir));
+                                               struct strbuf realpath = STRBUF_INIT;
+                                               strbuf_realpath(&realpath, gitdir, 1);
+                                               puts(realpath.buf);
+                                               strbuf_release(&realpath);
                                                continue;
                                        }
                                }
index 098ebf22d0d65a6f98606c7ea567467641976f6b..2b9610f12176a7d23df3dfea9765e595f0405c41 100644 (file)
@@ -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"
@@ -165,9 +165,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")),
                OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")),
                OPT_BOOL('f', "force", &force_update, N_("force updates")),
-               { OPTION_CALLBACK,
-                 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
-                 PARSE_OPT_OPTARG, option_parse_push_signed },
+               OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
+                 PARSE_OPT_OPTARG, option_parse_push_signed),
                OPT_STRING_LIST(0, "push-option", &push_options,
                                N_("server-specific"),
                                N_("option to transmit")),
@@ -177,10 +176,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")),
                OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")),
                OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")),
-               { OPTION_CALLBACK,
-                 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
+               OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
                  N_("require old value of ref to be at this value"),
-                 PARSE_OPT_OPTARG, parseopt_push_cas_option },
+                 PARSE_OPT_OPTARG, parseopt_push_cas_option),
                OPT_END()
        };
 
index 65cd41392c1c5e256313b10812a39bf4b848ce1b..c856c58bb5a6cb4c867a2b61bc5355bc96ef915f 100644 (file)
@@ -268,9 +268,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                         N_("Suppress commit descriptions, only provides commit count")),
                OPT_BOOL('e', "email", &log.email,
                         N_("Show the email address of each author")),
-               { OPTION_CALLBACK, 'w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
+               OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
                        N_("Linewrap output"), PARSE_OPT_OPTARG,
-                       &parse_wrap_args },
+                       &parse_wrap_args),
                OPT_END(),
        };
 
index 8c90cbb18f072726f03f40f12f6b9731b29da1b6..7e52ee91264a63713da5178add167f8d62c36877 100644 (file)
@@ -671,11 +671,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                            N_("topologically sort, maintaining date order "
                               "where possible"),
                            REV_SORT_BY_COMMIT_DATE),
-               { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
+               OPT_CALLBACK_F('g', "reflog", &reflog_base, N_("<n>[,<base>]"),
                            N_("show <n> most recent ref-log entries starting at "
                               "base"),
                            PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
-                           parse_reflog_param },
+                           parse_reflog_param),
                OPT_END()
        };
 
index 6456da70cc2c4d6a86c2466c483dd815b15bbb45..ae60b4acf2f444631d477104b7c5bc15e5311658 100644 (file)
@@ -169,15 +169,15 @@ static const struct option show_ref_options[] = {
          N_("show the HEAD reference, even if it would be filtered out")),
        OPT_BOOL('d', "dereference", &deref_tags,
                    N_("dereference tags into object IDs")),
-       { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"),
-         N_("only show SHA1 hash using <n> digits"),
-         PARSE_OPT_OPTARG, &hash_callback },
+       OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+                      N_("only show SHA1 hash using <n> digits"),
+                      PARSE_OPT_OPTARG, &hash_callback),
        OPT__ABBREV(&abbrev),
        OPT__QUIET(&quiet,
                   N_("do not print results to stdout (useful with --verify)")),
-       { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
-         N_("pattern"), N_("show refs from stdin that aren't in local repository"),
-         PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
+       OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
+                      N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+                      PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
        OPT_END()
 };
 
index 740da4b6d54aa1864f8357ca84798a620af9f422..95d0882417211e4172bc028de7e54c1948030f08 100644 (file)
@@ -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);
        }
index 78af6ce56431a5b1a8eac311d8a33d2c702ee8ba..0c52a3b849c4c6f811995f3be89284052c2dd313 100644 (file)
@@ -702,6 +702,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
 
 static int show_stat = 1;
 static int show_patch;
+static int use_legacy_stash;
 
 static int git_stash_config(const char *var, const char *value, void *cb)
 {
@@ -713,7 +714,11 @@ static int git_stash_config(const char *var, const char *value, void *cb)
                show_patch = git_config_bool(var, value);
                return 0;
        }
-       return git_default_config(var, value, cb);
+       if (!strcmp(var, "stash.usebuiltin")) {
+               use_legacy_stash = !git_config_bool(var, value);
+               return 0;
+       }
+       return git_diff_basic_config(var, value, cb);
 }
 
 static int show_stash(int argc, const char **argv, const char *prefix)
@@ -750,7 +755,6 @@ static int show_stash(int argc, const char **argv, const char *prefix)
         * any options.
         */
        if (revision_args.argc == 1) {
-               git_config(git_stash_config, NULL);
                if (show_stat)
                        rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
 
@@ -857,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);
@@ -1037,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;
@@ -1559,29 +1556,6 @@ static int save_stash(int argc, const char **argv, const char *prefix)
        return ret;
 }
 
-static int use_builtin_stash(void)
-{
-       struct child_process cp = CHILD_PROCESS_INIT;
-       struct strbuf out = STRBUF_INIT;
-       int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
-
-       if (env != -1)
-               return env;
-
-       argv_array_pushl(&cp.args,
-                        "config", "--bool", "stash.usebuiltin", NULL);
-       cp.git_cmd = 1;
-       if (capture_command(&cp, &out, 6)) {
-               strbuf_release(&out);
-               return 1;
-       }
-
-       strbuf_trim(&out);
-       ret = !strcmp("true", out.buf);
-       strbuf_release(&out);
-       return ret;
-}
-
 int cmd_stash(int argc, const char **argv, const char *prefix)
 {
        pid_t pid = getpid();
@@ -1592,21 +1566,12 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       if (!use_builtin_stash()) {
-               const char *path = mkpath("%s/git-legacy-stash",
-                                         git_exec_path());
-
-               if (sane_execvp(path, (char **)argv) < 0)
-                       die_errno(_("could not exec %s"), path);
-               else
-                       BUG("sane_execvp() returned???");
-       }
-
-       prefix = setup_git_directory();
-       trace_repo_setup(prefix);
-       setup_work_tree();
+       git_config(git_stash_config, NULL);
 
-       git_config(git_diff_basic_config, NULL);
+       if (use_legacy_stash ||
+           !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
+               warning(_("the stash.useBuiltin support has been removed!\n"
+                         "See its entry in 'git help config' for details."));
 
        argc = parse_options(argc, argv, prefix, options, git_stash_usage,
                             PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
index 86a608eec1144649c87444dac9c3628b1d332977..46c03d2a12671e15296b34d5e194fa837a12cdfb 100644 (file)
@@ -444,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list,
                fn(list->entries[i], cb_data);
 }
 
-struct cb_foreach {
+struct foreach_cb {
        int argc;
        const char **argv;
        const char *prefix;
        int quiet;
        int recursive;
 };
-#define CB_FOREACH_INIT { 0 }
+#define FOREACH_CB_INIT { 0 }
 
 static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
                                       void *cb_data)
 {
-       struct cb_foreach *info = cb_data;
+       struct foreach_cb *info = cb_data;
        const char *path = list_item->name;
        const struct object_id *ce_oid = &list_item->oid;
 
@@ -557,7 +557,7 @@ cleanup:
 
 static int module_foreach(int argc, const char **argv, const char *prefix)
 {
-       struct cb_foreach info = CB_FOREACH_INIT;
+       struct foreach_cb info = FOREACH_CB_INIT;
        struct pathspec pathspec;
        struct module_list list = MODULE_LIST_INIT;
 
@@ -2246,6 +2246,37 @@ static int module_config(int argc, const char **argv, const char *prefix)
        usage_with_options(git_submodule_helper_usage, module_config_options);
 }
 
+static int module_set_url(int argc, const char **argv, const char *prefix)
+{
+       int quiet = 0;
+       const char *newurl;
+       const char *path;
+       char *config_name;
+
+       struct option options[] = {
+               OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")),
+               OPT_END()
+       };
+       const char *const usage[] = {
+               N_("git submodule--helper set-url [--quiet] <path> <newurl>"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+       if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1]))
+               usage_with_options(usage, options);
+
+       config_name = xstrfmt("submodule.%s.url", path);
+
+       config_set_in_gitmodules_file_gently(config_name, newurl);
+       sync_submodule(path, prefix, quiet ? OPT_QUIET : 0);
+
+       free(config_name);
+
+       return 0;
+}
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -2276,6 +2307,7 @@ static struct cmd_struct commands[] = {
        {"is-active", is_active, 0},
        {"check-name", check_name, 0},
        {"config", module_config, 0},
+       {"set-url", module_set_url, 0},
 };
 
 int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
index e0a4c25382846f801c43cd1093ffb0ffa63f975f..5cbd80dc3e93f478eb0ff47465a1e4b287b897d2 100644 (file)
@@ -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"
 
@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,
        if (type <= OBJ_NONE)
                die(_("bad object type."));
 
-       if (type == OBJ_TAG && advice_nested_tag)
-               advise(_(message_advice_nested_tag), tag, object_ref);
+       if (type == OBJ_TAG)
+               advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),
+                                 tag, object_ref);
 
        strbuf_addf(&header,
                    "object %s\n"
@@ -409,8 +410,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_GROUP(N_("Tag creation options")),
                OPT_BOOL('a', "annotate", &annotate,
                                        N_("annotated tag, needs a message")),
-               { OPTION_CALLBACK, 'm', "message", &msg, N_("message"),
-                 N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg },
+               OPT_CALLBACK_F('m', "message", &msg, N_("message"),
+                              N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
                OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
                OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
@@ -484,7 +485,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        }
        if (!sorting)
                sorting = ref_default_sorting();
-       sorting->ignore_case = icase;
+       ref_sorting_icase_all(sorting, icase);
        filter.ignore_case = icase;
        if (cmdmode == 'l') {
                int ret;
index d527b8f1066859d79d746805c42bb73fab95e78c..79087bccea4b8ba78a9d0bd22553b39f84b0e2e1 100644 (file)
@@ -985,14 +985,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                OPT_BIT(0, "unmerged", &refresh_args.flags,
                        N_("refresh even if index contains unmerged entries"),
                        REFRESH_UNMERGED),
-               {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
+               OPT_CALLBACK_F(0, "refresh", &refresh_args, NULL,
                        N_("refresh stat information"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       refresh_callback},
-               {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
+                       refresh_callback),
+               OPT_CALLBACK_F(0, "really-refresh", &refresh_args, NULL,
                        N_("like --refresh, but ignore assume-unchanged setting"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       really_refresh_callback},
+                       really_refresh_callback),
                {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
                        N_("<mode>,<object>,<path>"),
                        N_("add the specified entry to the index"),
@@ -1000,10 +1000,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
                        NULL, 0,
                        cacheinfo_callback},
-               {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x",
+               OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x",
                        N_("override the executable bit of the listed files"),
                        PARSE_OPT_NONEG,
-                       chmod_callback},
+                       chmod_callback),
                {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
                        N_("mark files as \"not changing\""),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
@@ -1045,10 +1045,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                        REFRESH_IGNORE_MISSING),
                OPT_SET_INT(0, "verbose", &verbose,
                        N_("report actions to standard output"), 1),
-               {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
+               OPT_CALLBACK_F(0, "clear-resolve-undo", NULL, NULL,
                        N_("(for porcelains) forget saved unresolved conflicts"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
-                       resolve_undo_clear_callback},
+                       resolve_undo_clear_callback),
                OPT_INTEGER(0, "index-version", &preferred_index_format,
                        N_("write index in this format")),
                OPT_BOOL(0, "split-index", &split_index,
index 2d8f7f05785dca5a083ee6d003d16b4d008dc4d2..b74dd9a69d992bcbac678f5b463a5e00b5299140 100644 (file)
@@ -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;
        }
 
index 24f22800f38c759d123d7e307de488f0c8ee852f..d99db356684fab9c1f2c53790a95bb2e2723c541 100644 (file)
@@ -258,7 +258,7 @@ static int add_worktree(const char *path, const char *refname,
                        const struct add_opts *opts)
 {
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
        const char *name;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct argv_array child_env = ARGV_ARRAY_INIT;
@@ -330,9 +330,11 @@ static int add_worktree(const char *path, const char *refname,
 
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-       write_file(sb.buf, "%s", real_path(sb_git.buf));
+       strbuf_realpath(&realpath, sb_git.buf, 1);
+       write_file(sb.buf, "%s", realpath.buf);
+       strbuf_realpath(&realpath, get_git_common_dir(), 1);
        write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
-                  real_path(get_git_common_dir()), name);
+                  realpath.buf, name);
        /*
         * This is to keep resolve_ref() happy. We need a valid HEAD
         * or is_git_directory() will reject the directory. Any value which
@@ -418,6 +420,7 @@ done:
        strbuf_release(&sb_repo);
        strbuf_release(&sb_git);
        strbuf_release(&sb_name);
+       strbuf_release(&realpath);
        return ret;
 }
 
diff --git a/cache.h b/cache.h
index 37c899b53f7c3d36f1145d33f19d5c8302b911f5..0f0485ecfe2cd9a2270cffcec3552e901658539a 100644 (file)
--- 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"
 
@@ -543,7 +543,7 @@ const char *get_git_common_dir(void);
 char *get_object_directory(void);
 char *get_index_file(void);
 char *get_graft_file(struct repository *r);
-void set_git_dir(const char *path);
+void set_git_dir(const char *path, int make_realpath);
 int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
 int get_common_dir(struct strbuf *sb, const char *gitdir);
 const char *get_git_namespace(void);
@@ -627,7 +627,9 @@ int path_inside_repo(const char *prefix, const char *path);
 #define INIT_DB_EXIST_OK 0x0002
 
 int init_db(const char *git_dir, const char *real_git_dir,
-           const char *template_dir, unsigned int flags);
+           const char *template_dir, int hash_algo,
+           unsigned int flags);
+void initialize_repository_version(int hash_algo);
 
 void sanitize_stdfds(void);
 int daemonize(void);
@@ -1086,8 +1088,10 @@ int verify_repository_format(const struct repository_format *format,
  * and die if it is a version we don't understand. Generally one would
  * set_git_dir() before calling this, and use it only for "are we in a valid
  * repo?".
+ *
+ * If successful and fmt is not NULL, fill fmt with data.
  */
-void check_repository_format(void);
+void check_repository_format(struct repository_format *fmt);
 
 #define MTIME_CHANGED  0x0001
 #define CTIME_CHANGED  0x0002
@@ -1314,8 +1318,6 @@ static inline int is_absolute_path(const char *path)
 int is_directory(const char *);
 char *strbuf_realpath(struct strbuf *resolved, const char *path,
                      int die_on_error);
-const char *real_path(const char *path);
-const char *real_path_if_valid(const char *path);
 char *real_pathdup(const char *path, int die_on_error);
 const char *absolute_path(const char *path);
 char *absolute_pathdup(const char *path);
@@ -1481,6 +1483,9 @@ int set_disambiguate_hint_config(const char *var, const char *value);
 int get_sha1_hex(const char *hex, unsigned char *sha1);
 int get_oid_hex(const char *hex, struct object_id *sha1);
 
+/* Like get_oid_hex, but for an arbitrary hash algorithm. */
+int get_oid_hex_algop(const char *hex, struct object_id *oid, const struct git_hash_algo *algop);
+
 /*
  * Read `len` pairs of hexadecimal digits from `hex` and write the
  * values to `binary` as `len` bytes. Return 0 on success, or -1 if
@@ -1516,6 +1521,20 @@ char *oid_to_hex(const struct object_id *oid);                                           /* same static buffer */
  */
 int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
 
+/* Like parse_oid_hex, but for an arbitrary hash algorithm. */
+int parse_oid_hex_algop(const char *hex, struct object_id *oid, const char **end,
+                       const struct git_hash_algo *algo);
+
+
+/*
+ * These functions work like get_oid_hex and parse_oid_hex, but they will parse
+ * a hex value for any algorithm. The algorithm is detected based on the length
+ * and the algorithm in use is returned. If this is not a hex object ID in any
+ * algorithm, returns GIT_HASH_UNKNOWN.
+ */
+int get_oid_hex_any(const char *hex, struct object_id *oid);
+int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end);
+
 /*
  * This reads short-hand syntax that not only evaluates to a commit
  * object name, but also can act as if the end user spelled the name
@@ -1679,6 +1698,7 @@ struct checkout {
        const char *base_dir;
        int base_dir_len;
        struct delayed_checkout *delayed_checkout;
+       struct checkout_metadata meta;
        unsigned force:1,
                 quiet:1,
                 not_new:1,
diff --git a/ci/config/allow-refs.sample b/ci/config/allow-refs.sample
new file mode 100755 (executable)
index 0000000..f157f19
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Sample script for enabling/disabling GitHub Actions CI runs on
+# particular refs. By default, CI is run for all branches pushed to
+# GitHub. You can override this by dropping the ".sample" from the script,
+# editing it, committing, and pushing the result to the "ci-config" branch of
+# your repository:
+#
+#   git checkout -b ci-config
+#   cp allow-refs.sample allow-refs
+#   $EDITOR allow-refs
+#   git commit -am "implement my ci preferences"
+#   git push
+#
+# This script will then be run when any refs are pushed to that repository. It
+# gets the fully qualified refname as the first argument, and should exit with
+# success only for refs for which you want to run CI.
+
+case "$1" in
+# allow one-off tests by pushing to "for-ci" or "for-ci/mybranch"
+refs/heads/for-ci*) true ;;
+# always build your integration branch
+refs/heads/my-integration-branch) true ;;
+# don't build any other branches or tags
+*) false ;;
+esac
diff --git a/ci/git-problem-matcher.json b/ci/git-problem-matcher.json
new file mode 100644 (file)
index 0000000..506dfbd
--- /dev/null
@@ -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
+                }
+            ]
+        }
+    ]
+}
index 497fd32ca837a9f5b823c22540e4345ea3d5366e..0229a77f7d281fa9717e359f34c634f118035a62 100755 (executable)
@@ -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 (executable)
index 0000000..26a6689
--- /dev/null
@@ -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
index a90d0dc0fd2ae30ba59aa7f2262f884f43dc5ee5..dac36886e3744fba251506be53d48034adcecd17 100755 (executable)
--- 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
 
@@ -162,6 +189,9 @@ linux-clang|linux-gcc)
        if [ "$jobname" = linux-gcc ]
        then
                export CC=gcc-8
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+       else
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
        fi
 
        export GIT_TEST_HTTPD=true
@@ -182,6 +212,9 @@ osx-clang|osx-gcc)
        if [ "$jobname" = osx-gcc ]
        then
                export CC=gcc-9
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)"
+       else
+               MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python2)"
        fi
 
        # t9810 occasionally fails on Travis CI OS X
@@ -189,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}"
index e688a26f0d61464c473fd7f0c7fce1a805269b2c..92a983a265c246da0865fbd9f82ae5c15bb99322 100755 (executable)
@@ -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
index 4df54c4efea8930e34f76205afa92b67e4275662..17e25aade96f46ecfcdf1b5658d788f23b99f587 100755 (executable)
@@ -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
similarity index 63%
rename from ci/run-linux32-build.sh
rename to ci/run-docker-build.sh
index e3a193adbce39cb62b3be20b269444188a900613..8d47a5fda3b1c929a68662cfca3625e63e122194 100755 (executable)
@@ -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 (executable)
index 0000000..37fa372
--- /dev/null
@@ -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 (executable)
index 751acfc..0000000
+++ /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
index d5c4d839dcf23c0204f93d486a53d41860d8631b..002e0e5438bc27472d032ac562a2f3a29ec20a68 100644 (file)
@@ -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,
index 20878946558847e35261a7f99a22405e4d9707d0..185e5e3f05fbc492b7d3849bbe0015c2eff9a195 100644 (file)
@@ -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
index f013a84e294b13b552b62257bbf2fa7e1c353a82..e3420ddcbff5e829aa821d6c42c740e6eb819d37 100644 (file)
 #include "hashmap.h"
 #include "replace-object.h"
 #include "progress.h"
+#include "bloom.h"
+#include "commit-slab.h"
+#include "shallow.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)
 
 /* 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)
 {
-       return xstrfmt("%s/info/commit-graph", odb->path);
+       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", obj_dir->path);
 }
 
 static char *get_split_graph_filename(struct object_directory *odb,
@@ -69,7 +131,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 +184,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 +225,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 +268,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;
 
@@ -224,8 +282,7 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                if (data + graph_size - chunk_lookup <
                    GRAPH_CHUNKLOOKUP_WIDTH) {
                        error(_("commit-graph chunk lookup table entry missing; file may be incomplete"));
-                       free(graph);
-                       return NULL;
+                       goto free_and_return;
                }
 
                chunk_id = get_be32(chunk_lookup + 0);
@@ -236,8 +293,7 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                if (chunk_offset > graph_size - the_hash_algo->rawsz) {
                        error(_("commit-graph improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32),
                              (uint32_t)chunk_offset);
-                       free(graph);
-                       return NULL;
+                       goto free_and_return;
                }
 
                switch (chunk_id) {
@@ -274,12 +330,37 @@ 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) {
                        error(_("commit-graph chunk id %08x appears multiple times"), chunk_id);
-                       free(graph);
-                       return NULL;
+                       goto free_and_return;
                }
 
                if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP)
@@ -292,14 +373,26 @@ 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;
+               FREE_AND_NULL(graph->bloom_filter_settings);
+       }
+
        hashcpy(graph->oid.hash, graph->data + graph->data_len - graph->hash_len);
 
-       if (verify_commit_graph_lite(graph)) {
-               free(graph);
-               return NULL;
-       }
+       if (verify_commit_graph_lite(graph))
+               goto free_and_return;
 
        return graph;
+
+free_and_return:
+       free(graph->bloom_filter_settings);
+       free(graph);
+       return NULL;
 }
 
 static struct commit_graph *load_commit_graph_one(const char *graph_file,
@@ -788,9 +881,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 +962,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 +993,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 +1060,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 +1082,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 +1166,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 +1188,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 +1219,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 +1287,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;
 
-       string_list_append(list, oid_to_hex(oid));
+       init_bloom_filters();
+
+       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 +1333,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 +1389,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 +1480,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 +1498,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 +1548,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;
@@ -1378,17 +1577,25 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
        if (ctx->split) {
                char *lock_name = get_chain_filename(ctx->odb);
 
-               hold_lock_file_for_update(&lk, lock_name, LOCK_DIE_ON_ERROR);
+               hold_lock_file_for_update_mode(&lk, lock_name,
+                                              LOCK_DIE_ON_ERROR, 0444);
 
                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;
+               }
+
+               if (adjust_shared_perm(ctx->graph_name)) {
+                       error(_("unable to adjust shared permissions for '%s'"),
+                             ctx->graph_name);
                        return -1;
                }
 
                f = hashfd(fd, ctx->graph_name);
        } else {
-               hold_lock_file_for_update(&lk, ctx->graph_name, LOCK_DIE_ON_ERROR);
+               hold_lock_file_for_update_mode(&lk, ctx->graph_name,
+                                              LOCK_DIE_ON_ERROR, 0444);
                fd = lk.tempfile->fd;
                f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
        }
@@ -1400,6 +1607,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 +1631,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 +1677,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 +1714,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 +1759,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 +1770,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 +1813,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 +1950,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 +1999,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 +2019,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 +2043,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 +2073,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 +2109,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 +2344,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);
 }
 
index e87a6f636000d68137fe3ec437b02e439a8171a5..4212766a4f0507f52fb5d4f42edbf1b977fc5cae 100644 (file)
@@ -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);
 
index 69bf0c807c64b79216118f3485cfd5dac3b6a273..05b3f2804e74d5bb6bbcc0c735d7471d32566d88 100644 (file)
  * - int *indegree_peek(struct indegree *, struct commit *);
  *
  *   This function is similar to indegree_at(), but it will return NULL
- *   until a call to indegree_at() was made for the commit.
+ *   if the location to store the data associated with the given commit
+ *   has not been allocated yet.
+ *   Note that the location to store the data might have already been
+ *   allocated even if no indegree_at() call has been made for that commit
+ *   yet; in this case this function returns a pointer to a
+ *   zero-initialized location.
  *
  * - void init_indegree(struct indegree *);
  *   void init_indegree_with_stride(struct indegree *, int);
index a6cfa41a4e315225c08b8e51c5dd982d9f6273d2..87686a7055bc5c502de88032d1c474e32539e6a0 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -20,6 +20,7 @@
 #include "refs.h"
 #include "commit-reach.h"
 #include "run-command.h"
+#include "shallow.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -110,7 +111,7 @@ static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
        return commit_graft_table[index]->oid.hash;
 }
 
-static int commit_graft_pos(struct repository *r, const unsigned char *sha1)
+int commit_graft_pos(struct repository *r, const unsigned char *sha1)
 {
        return sha1_pos(sha1, r->parsed_objects->grafts,
                        r->parsed_objects->grafts_nr,
@@ -245,19 +246,6 @@ int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
        return ret;
 }
 
-int unregister_shallow(const struct object_id *oid)
-{
-       int pos = commit_graft_pos(the_repository, oid->hash);
-       if (pos < 0)
-               return -1;
-       if (pos + 1 < the_repository->parsed_objects->grafts_nr)
-               MOVE_ARRAY(the_repository->parsed_objects->grafts + pos,
-                          the_repository->parsed_objects->grafts + pos + 1,
-                          the_repository->parsed_objects->grafts_nr - pos - 1);
-       the_repository->parsed_objects->grafts_nr--;
-       return 0;
-}
-
 struct commit_buffer {
        void *buffer;
        unsigned long size;
@@ -927,12 +915,22 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
        struct commit_list *bases;
        int i;
        struct commit *ret = NULL;
+       char *full_refname;
+
+       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+       case 0:
+               die("No such ref: '%s'", refname);
+       case 1:
+               break; /* good */
+       default:
+               die("Ambiguous refname: '%s'", refname);
+       }
 
        memset(&revs, 0, sizeof(revs));
        revs.initial = 1;
-       for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
+       for_each_reflog_ent(full_refname, collect_one_reflog_ent, &revs);
 
-       if (!revs.nr && !get_oid(refname, &oid))
+       if (!revs.nr)
                add_one_commit(&oid, &revs);
 
        for (i = 0; i < revs.nr; i++)
@@ -958,17 +956,26 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
 
 cleanup_return:
        free_commit_list(bases);
+       free(full_refname);
        return ret;
 }
 
-static const char gpg_sig_header[] = "gpgsig";
-static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+/*
+ * Indexed by hash algorithm identifier.
+ */
+static const char *gpg_sig_headers[] = {
+       NULL,
+       "gpgsig",
+       "gpgsig-sha256",
+};
 
 static int do_sign_commit(struct strbuf *buf, const char *keyid)
 {
        struct strbuf sig = STRBUF_INIT;
        int inspos, copypos;
        const char *eoh;
+       const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+       int gpg_sig_header_len = strlen(gpg_sig_header);
 
        /* find the end of the header */
        eoh = strstr(buf->buf, "\n\n");
@@ -1010,6 +1017,8 @@ int parse_signed_commit(const struct commit *commit,
        const char *buffer = get_commit_buffer(commit, &size);
        int in_signature, saw_signature = -1;
        const char *line, *tail;
+       const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+       int gpg_sig_header_len = strlen(gpg_sig_header);
 
        line = buffer;
        tail = buffer + size;
@@ -1056,11 +1065,17 @@ int remove_signature(struct strbuf *buf)
 
                if (in_signature && line[0] == ' ')
                        sig_end = next;
-               else if (starts_with(line, gpg_sig_header) &&
-                        line[gpg_sig_header_len] == ' ') {
-                       sig_start = line;
-                       sig_end = next;
-                       in_signature = 1;
+               else if (starts_with(line, "gpgsig")) {
+                       int i;
+                       for (i = 1; i < GIT_HASH_NALGOS; i++) {
+                               const char *p;
+                               if (skip_prefix(line, gpg_sig_headers[i], &p) &&
+                                   *p == ' ') {
+                                       sig_start = line;
+                                       sig_end = next;
+                                       in_signature = 1;
+                               }
+                       }
                } else {
                        if (*line == '\n')
                                /* dump the whole remainder of the buffer */
index 008a0fa4a01d06c0e191e5988823ff630931d30d..1b2dea5d85ebe52c8e5f32f1e5e26ff33e0c566c 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -236,6 +236,8 @@ struct commit_graft {
 typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(struct strbuf *line);
+/* commit_graft_pos returns an index into r->parsed_objects->grafts. */
+int commit_graft_pos(struct repository *r, const unsigned char *sha1);
 int register_commit_graft(struct repository *r, struct commit_graft *, int);
 void prepare_commit_graft(struct repository *r);
 struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
@@ -247,53 +249,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit);
 
 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 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,
-                                       int depth, int shallow_flag, int not_shallow_flag);
-struct commit_list *get_shallow_commits_by_rev_list(
-               int ac, const char **av, int shallow_flag, int not_shallow_flag);
-void set_alternate_shallow_file(struct repository *r, const char *path, int override);
-int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
-                         const struct oid_array *extra);
-void setup_alternate_shallow(struct lock_file *shallow_lock,
-                            const char **alternate_shallow_file,
-                            const struct oid_array *extra);
-const char *setup_temporary_shallow(const struct oid_array *extra);
-void advertise_shallow_grafts(int);
-
-/*
- * Initialize with prepare_shallow_info() or zero-initialize (equivalent to
- * prepare_shallow_info with a NULL oid_array).
- */
-struct shallow_info {
-       struct oid_array *shallow;
-       int *ours, nr_ours;
-       int *theirs, nr_theirs;
-       struct oid_array *ref;
-
-       /* for receive-pack */
-       uint32_t **used_shallow;
-       int *need_reachability_test;
-       int *reachable;
-       int *shallow_ref;
-       struct commit **commits;
-       int nr_commits;
-};
-
-void prepare_shallow_info(struct shallow_info *, struct oid_array *);
-void clear_shallow_info(struct shallow_info *);
-void remove_nonexistent_theirs_shallow(struct shallow_info *);
-void assign_shallow_commits_to_refs(struct shallow_info *info,
-                                   uint32_t **used,
-                                   int *ref_status);
-int delayed_reachability_test(struct shallow_info *si, int c);
-#define PRUNE_SHOW_ONLY 1
-#define PRUNE_QUICK 2
-void prune_shallow(unsigned options);
-extern struct trace_key trace_shallow;
 
 int interactive_add(int argc, const char **argv, const char *prefix, int patch);
 int run_add_interactive(const char *revision, const char *patch_mode,
diff --git a/compat/compiler.h b/compat/compiler.h
new file mode 100644 (file)
index 0000000..10dbb65
--- /dev/null
@@ -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 */
index d14065d60ec497131a23549727439d624ff4c710..8ee0b6408e945b369b30d9052c91aa7e1f31553a 100644 (file)
@@ -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];
index f3e03a9eabeeef48abb401ec14b824261b590fa0..e6f4a5d177bb7d44345fcc25fabca1e62b0eebc4 100644 (file)
@@ -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
index 3ee8aae59d7978449b35b6147dce8c3013953c3b..0bad8b841eda898326203118441d10a3e516b183 100644 (file)
@@ -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
index 1b6dabf5a2310ffe25ebd1646f9a9fd3602ed839..42292e7c098e23e61f5f7e602b46ddae335da9e9 100644 (file)
@@ -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
index f2e70872cd20675dd2f84f72e7608799f16941ad..bba2b644080f064cb5160ce41da16841afadeeed 100644 (file)
@@ -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
 
index d17d2bd9dcdef8e4f16316090e1265145834926b..8db9c77098f01bec5178751c8b4e64880d473619 100644 (file)
--- 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;
index 9b3773f77826513226608a592e3295d7a6ea5e3f..060874488f413cd6453e1633cd69c971d6e5d613 100644 (file)
--- 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);
 
 /**
index 89b218d11a5313f327492795964793bec8670160..cd4a82a9eb4a2b0a4a1d45f0186cd7f858a0ff57 100644 (file)
@@ -16,6 +16,8 @@ DEVELOPER_CFLAGS += -Wstrict-prototypes
 DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
 
+DEVELOPER_CFLAGS += -DENABLE_SHA256
+
 ifndef COMPILER_FEATURES
 COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
 endif
index 0ab8e00938397612bc28ea48a218e460a80b1c6a..5ad43c80b1aa8f3e13216af557f26405d1778206 100644 (file)
@@ -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
index b6451ab5e8909017edc678b3021384f3920fbe75..23013c634436adeeac65bd5e6a0d895a06fe6fe6 100644 (file)
--- 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"
index 7e9bd1bc622eefa3dad76c8e786767a0ffa3077f..3135b71e1961ae421855ad4b05887c795c437490 100644 (file)
@@ -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
@@ -61,7 +61,11 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                 * object is a promisor object. Instead, just make sure we
                 * received, in a promisor packfile, the objects pointed to by
                 * each wanted ref.
+                *
+                * Before checking for promisor packs, be sure we have the
+                * latest pack-files loaded into memory.
                 */
+               reprepare_packed_git(the_repository);
                do {
                        struct packed_git *p;
 
@@ -71,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);
index eba5c261bac1887289ac70425cee8c97e9006ece..8d5a6b3ad6fe4bb0f9ca0930f8eea82543968306 100644 (file)
@@ -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 }
index c21786f2fd00263e9b068f1d4750fa16b2411225..b1d6e5ebed7c1839770f03f39157141b4edbefd8 100644 (file)
@@ -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
        }
index eef4eff53dff12f86d8982e6818ec92086aa4d80..ce47e86b60c04c7a398c20ded985f4187e5d3533 100644 (file)
@@ -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
 }
index e800d9f5c9cf25e1aae3a0b87ad35f46b2a973c3..d50ce26d5d9f3d0ceb3351339893c9472f6e5195 100755 (executable)
@@ -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";
index 6906aae44147810ec2cf7560eb2cae3ef56bb454..6fa7496bfdb3fdbcf42a03e88cbcd773f6a302cc 100644 (file)
@@ -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) \
index 5ead3ce678bb746fa82cef9e3b8e8ac407aa0741..572449825c5a4c8fd1ff65bb0c947d0c567d002b 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -797,6 +797,7 @@ static void handle_filter_error(const struct strbuf *filter_status,
 static int apply_multi_file_filter(const char *path, const char *src, size_t len,
                                   int fd, struct strbuf *dst, const char *cmd,
                                   const unsigned int wanted_capability,
+                                  const struct checkout_metadata *meta,
                                   struct delayed_checkout *dco)
 {
        int err;
@@ -855,6 +856,24 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
        if (err)
                goto done;
 
+       if (meta && meta->refname) {
+               err = packet_write_fmt_gently(process->in, "ref=%s\n", meta->refname);
+               if (err)
+                       goto done;
+       }
+
+       if (meta && !is_null_oid(&meta->treeish)) {
+               err = packet_write_fmt_gently(process->in, "treeish=%s\n", oid_to_hex(&meta->treeish));
+               if (err)
+                       goto done;
+       }
+
+       if (meta && !is_null_oid(&meta->blob)) {
+               err = packet_write_fmt_gently(process->in, "blob=%s\n", oid_to_hex(&meta->blob));
+               if (err)
+                       goto done;
+       }
+
        if ((entry->supported_capabilities & CAP_DELAY) &&
            dco && dco->state == CE_CAN_DELAY) {
                can_delay = 1;
@@ -971,6 +990,7 @@ static struct convert_driver {
 static int apply_filter(const char *path, const char *src, size_t len,
                        int fd, struct strbuf *dst, struct convert_driver *drv,
                        const unsigned int wanted_capability,
+                       const struct checkout_metadata *meta,
                        struct delayed_checkout *dco)
 {
        const char *cmd = NULL;
@@ -990,7 +1010,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
                return apply_single_file_filter(path, src, len, fd, dst, cmd);
        else if (drv->process && *drv->process)
                return apply_multi_file_filter(path, src, len, fd, dst,
-                       drv->process, wanted_capability, dco);
+                       drv->process, wanted_capability, meta, dco);
 
        return 0;
 }
@@ -998,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;
 
        /*
@@ -1368,7 +1388,7 @@ int would_convert_to_git_filter_fd(const struct index_state *istate, const char
        if (!ca.drv->required)
                return 0;
 
-       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL);
+       return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN, NULL, NULL);
 }
 
 const char *get_convert_attr_ascii(const struct index_state *istate, const char *path)
@@ -1406,7 +1426,7 @@ int convert_to_git(const struct index_state *istate,
 
        convert_attrs(istate, &ca, path);
 
-       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL);
+       ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN, NULL, NULL);
        if (!ret && ca.drv && ca.drv->required)
                die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
@@ -1441,7 +1461,7 @@ void convert_to_git_filter_fd(const struct index_state *istate,
        assert(ca.drv);
        assert(ca.drv->clean || ca.drv->process);
 
-       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL))
+       if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL))
                die(_("%s: clean filter '%s' failed"), path, ca.drv->name);
 
        encode_to_git(path, dst->buf, dst->len, dst, ca.working_tree_encoding, conv_flags);
@@ -1452,7 +1472,9 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 static int convert_to_working_tree_internal(const struct index_state *istate,
                                            const char *path, const char *src,
                                            size_t len, struct strbuf *dst,
-                                           int normalizing, struct delayed_checkout *dco)
+                                           int normalizing,
+                                           const struct checkout_metadata *meta,
+                                           struct delayed_checkout *dco)
 {
        int ret = 0, ret_filter = 0;
        struct conv_attrs ca;
@@ -1484,7 +1506,7 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
        }
 
        ret_filter = apply_filter(
-               path, src, len, -1, dst, ca.drv, CAP_SMUDGE, dco);
+               path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco);
        if (!ret_filter && ca.drv && ca.drv->required)
                die(_("%s: smudge filter %s failed"), path, ca.drv->name);
 
@@ -1494,22 +1516,24 @@ static int convert_to_working_tree_internal(const struct index_state *istate,
 int async_convert_to_working_tree(const struct index_state *istate,
                                  const char *path, const char *src,
                                  size_t len, struct strbuf *dst,
+                                 const struct checkout_metadata *meta,
                                  void *dco)
 {
-       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, dco);
+       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco);
 }
 
 int convert_to_working_tree(const struct index_state *istate,
                            const char *path, const char *src,
-                           size_t len, struct strbuf *dst)
+                           size_t len, struct strbuf *dst,
+                           const struct checkout_metadata *meta)
 {
-       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, NULL);
+       return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL);
 }
 
 int renormalize_buffer(const struct index_state *istate, const char *path,
                       const char *src, size_t len, struct strbuf *dst)
 {
-       int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL);
+       int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL);
        if (ret) {
                src = dst->buf;
                len = dst->len;
@@ -1982,3 +2006,25 @@ int stream_filter(struct stream_filter *filter,
 {
        return filter->vtbl->filter(filter, input, isize_p, output, osize_p);
 }
+
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                           const struct object_id *treeish,
+                           const struct object_id *blob)
+{
+       memset(meta, 0, sizeof(*meta));
+       if (refname)
+               meta->refname = refname;
+       if (treeish)
+               oidcpy(&meta->treeish, treeish);
+       if (blob)
+               oidcpy(&meta->blob, blob);
+}
+
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                            const struct checkout_metadata *src,
+                            const struct object_id *blob)
+{
+       memcpy(dst, src, sizeof(*dst));
+       if (blob)
+               oidcpy(&dst->blob, blob);
+}
index 3710969d43d6a16c6eab2e8302f962aa466ccd9f..e29d1026a6866b63b047b6838727bcb71ea6eccb 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -4,10 +4,10 @@
 #ifndef CONVERT_H
 #define CONVERT_H
 
+#include "hash.h"
 #include "string-list.h"
 
 struct index_state;
-struct object_id;
 struct strbuf;
 
 #define CONV_EOL_RNDTRP_DIE   (1<<0) /* Die if CRLF to LF to CRLF is different */
@@ -57,6 +57,12 @@ struct delayed_checkout {
        struct string_list paths;
 };
 
+struct checkout_metadata {
+       const char *refname;
+       struct object_id treeish;
+       struct object_id blob;
+};
+
 extern enum eol core_eol;
 extern char *check_roundtrip_encoding;
 const char *get_cached_convert_stats_ascii(const struct index_state *istate,
@@ -71,10 +77,12 @@ int convert_to_git(const struct index_state *istate,
                   struct strbuf *dst, int conv_flags);
 int convert_to_working_tree(const struct index_state *istate,
                            const char *path, const char *src,
-                           size_t len, struct strbuf *dst);
+                           size_t len, struct strbuf *dst,
+                           const struct checkout_metadata *meta);
 int async_convert_to_working_tree(const struct index_state *istate,
                                  const char *path, const char *src,
                                  size_t len, struct strbuf *dst,
+                                 const struct checkout_metadata *meta,
                                  void *dco);
 int async_query_available_blobs(const char *cmd,
                                struct string_list *available_paths);
@@ -94,6 +102,23 @@ void convert_to_git_filter_fd(const struct index_state *istate,
 int would_convert_to_git_filter_fd(const struct index_state *istate,
                                   const char *path);
 
+/*
+ * Initialize the checkout metadata with the given values.  Any argument may be
+ * NULL if it is not applicable.  The treeish should be a commit if that is
+ * available, and a tree otherwise.
+ *
+ * The refname is not copied and must be valid for the lifetime of the struct.
+ * THe object IDs are copied.
+ */
+void init_checkout_metadata(struct checkout_metadata *meta, const char *refname,
+                           const struct object_id *treeish,
+                           const struct object_id *blob);
+
+/* Copy the metadata from src to dst, updating the blob. */
+void clone_checkout_metadata(struct checkout_metadata *dst,
+                            const struct checkout_metadata *src,
+                            const struct object_id *blob);
+
 /*
  * Reset the internal list of attributes used by convert_to_git and
  * convert_to_working_tree.
index c010497cb21db3c2bc921caf1b34be3e60ff5570..294e77168156225efcb3b6840bc1dd7883c6dcac 100644 (file)
@@ -24,8 +24,8 @@ static int parse_credential_file(const char *fn,
        }
 
        while (strbuf_getline_lf(&line, fh) != EOF) {
-               credential_from_url(&entry, line.buf);
-               if (entry.username && entry.password &&
+               if (!credential_from_url_gently(&entry, line.buf, 1) &&
+                   entry.username && entry.password &&
                    credential_match(c, &entry)) {
                        found_credential = 1;
                        if (match_cb) {
index 108d9e183a5ddac0ccafca25d184cb94710ee9f5..d8d226b97e34805735cc14bdc3e549d012b33f8d 100644 (file)
@@ -37,6 +37,10 @@ int credential_match(const struct credential *want,
 #undef CHECK
 }
 
+
+static int credential_from_potentially_partial_url(struct credential *c,
+                                                  const char *url);
+
 static int credential_config_callback(const char *var, const char *value,
                                      void *data)
 {
@@ -82,6 +86,22 @@ static int select_all(const struct urlmatch_item *a,
        return 0;
 }
 
+static int match_partial_url(const char *url, void *cb)
+{
+       struct credential *c = cb;
+       struct credential want = CREDENTIAL_INIT;
+       int matches = 0;
+
+       if (credential_from_potentially_partial_url(&want, url) < 0)
+               warning(_("skipping credential lookup for key: credential.%s"),
+                       url);
+       else
+               matches = credential_match(&want, c);
+       credential_clear(&want);
+
+       return matches;
+}
+
 static void credential_apply_config(struct credential *c)
 {
        char *normalized_url;
@@ -101,6 +121,7 @@ static void credential_apply_config(struct credential *c)
        config.collect_fn = credential_config_callback;
        config.cascade_fn = NULL;
        config.select_fn = select_all;
+       config.fallback_match_fn = match_partial_url;
        config.cb = c;
 
        credential_format(c, &url);
@@ -136,14 +157,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);
        }
 }
 
@@ -377,8 +398,31 @@ static int check_url_component(const char *url, int quiet,
        return -1;
 }
 
-int credential_from_url_gently(struct credential *c, const char *url,
-                              int quiet)
+/*
+ * Potentially-partial URLs can, but do not have to, contain
+ *
+ * - a protocol (or scheme) of the form "<protocol>://"
+ *
+ * - a host name (the part after the protocol and before the first slash after
+ *   that, if any)
+ *
+ * - a user name and potentially a password (as "<user>[:<password>]@" part of
+ *   the host name)
+ *
+ * - a path (the part after the host name, if any, starting with the slash)
+ *
+ * Missing parts will be left unset in `struct credential`. Thus, `https://`
+ * will have only the `protocol` set, `example.com` only the host name, and
+ * `/git` only the path.
+ *
+ * Note that an empty host name in an otherwise fully-qualified URL (e.g.
+ * `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to
+ * be potentially partial, and only then (otherwise, the empty string is used).
+ *
+ * The credential_from_url() function does not allow partial URLs.
+ */
+static int credential_from_url_1(struct credential *c, const char *url,
+                                int allow_partial_url, int quiet)
 {
        const char *at, *colon, *cp, *slash, *host, *proto_end;
 
@@ -391,15 +435,22 @@ int credential_from_url_gently(struct credential *c, const char *url,
         *   (3) proto://<user>:<pass>@<host>/...
         */
        proto_end = strstr(url, "://");
-       if (!proto_end || proto_end == url) {
+       if (!allow_partial_url && (!proto_end || proto_end == url)) {
                if (!quiet)
                        warning(_("url has no scheme: %s"), url);
                return -1;
        }
-       cp = proto_end + 3;
+       cp = proto_end ? proto_end + 3 : url;
        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) */
@@ -420,8 +471,10 @@ int credential_from_url_gently(struct credential *c, const char *url,
                host = at + 1;
        }
 
-       c->protocol = xmemdupz(url, proto_end - url);
-       c->host = url_decode_mem(host, slash - host);
+       if (proto_end && proto_end - url > 0)
+               c->protocol = xmemdupz(url, proto_end - url);
+       if (!allow_partial_url || slash - host > 0)
+               c->host = url_decode_mem(host, slash - host);
        /* Trim leading and trailing slashes from path */
        while (*slash == '/')
                slash++;
@@ -443,6 +496,17 @@ int credential_from_url_gently(struct credential *c, const char *url,
        return 0;
 }
 
+static int credential_from_potentially_partial_url(struct credential *c,
+                                                  const char *url)
+{
+       return credential_from_url_1(c, url, 1, 0);
+}
+
+int credential_from_url_gently(struct credential *c, const char *url, int quiet)
+{
+       return credential_from_url_1(c, url, 0, quiet);
+}
+
 void credential_from_url(struct credential *c, const char *url)
 {
        if (credential_from_url_gently(c, url, 0) < 0)
index d99ec42b2a8c96a298e75b809bf86e47d6d38211..c0e17e3554fce30586abe8a32aad503e4ec92b03 100644 (file)
@@ -177,8 +177,8 @@ void credential_write(const struct credential *, FILE *);
  * Parse a url into a credential struct, replacing any existing contents.
  *
  * If the url can't be parsed (e.g., a missing "proto://" component), the
- * resulting credential will be empty but we'll still return success from the
- * "gently" form.
+ * resulting credential will be empty and the function will return an
+ * error (even in the "gently" form).
  *
  * If we encounter a component which cannot be represented as a credential
  * value (e.g., because it contains a newline), the "gently" form will return
@@ -189,7 +189,7 @@ void credential_write(const struct credential *, FILE *);
 void credential_from_url(struct credential *, const char *url);
 int credential_from_url_gently(struct credential *, const char *url, int quiet);
 
-int credential_match(const struct credential *have,
-                    const struct credential *want);
+int credential_match(const struct credential *want,
+                    const struct credential *have);
 
 #endif /* CREDENTIAL_H */
index 53ce37f7ca42996dbfb4cf80e2127ea43496734d..0f35fa5ee47c5280b73c945a0043dea439917eb5 100644 (file)
@@ -157,7 +157,7 @@ void hashfile_checkpoint(struct hashfile *f, struct hashfile_checkpoint *checkpo
 {
        hashflush(f);
        checkpoint->offset = f->total;
-       checkpoint->ctx = f->ctx;
+       the_hash_algo->clone_fn(&checkpoint->ctx, &f->ctx);
 }
 
 int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint)
diff --git a/date.c b/date.c
index b0d9a8421db6a41fdc510841455f4dbbfd1190c7..f9ea807b3a9f9ee64a4173fa923f31b00455e840 100644 (file)
--- a/date.c
+++ b/date.c
@@ -497,7 +497,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
        return skip_alpha(date);
 }
 
-static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+static int set_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
 {
        if (month > 0 && month < 13 && day > 0 && day < 32) {
                struct tm check = *tm;
@@ -518,9 +518,9 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now,
                else if (year < 38)
                        r->tm_year = year + 100;
                else
-                       return 0;
+                       return -1;
                if (!now_tm)
-                       return 1;
+                       return 0;
 
                specified = tm_to_time_t(r);
 
@@ -529,14 +529,33 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now,
                 * sure it is not later than ten days from now...
                 */
                if ((specified != -1) && (now + 10*24*3600 < specified))
-                       return 0;
+                       return -1;
                tm->tm_mon = r->tm_mon;
                tm->tm_mday = r->tm_mday;
                if (year != -1)
                        tm->tm_year = r->tm_year;
-               return 1;
+               return 0;
        }
-       return 0;
+       return -1;
+}
+
+static int set_time(long hour, long minute, long second, struct tm *tm)
+{
+       /* We accept 61st second because of leap second */
+       if (0 <= hour && hour <= 24 &&
+           0 <= minute && minute < 60 &&
+           0 <= second && second <= 60) {
+               tm->tm_hour = hour;
+               tm->tm_min = minute;
+               tm->tm_sec = second;
+               return 0;
+       }
+       return -1;
+}
+
+static int is_date_known(struct tm *tm)
+{
+       return tm->tm_year != -1 && tm->tm_mon != -1 && tm->tm_mday != -1;
 }
 
 static int match_multi_number(timestamp_t num, char c, const char *date,
@@ -556,10 +575,14 @@ static int match_multi_number(timestamp_t num, char c, const char *date,
        case ':':
                if (num3 < 0)
                        num3 = 0;
-               if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
-                       tm->tm_hour = num;
-                       tm->tm_min = num2;
-                       tm->tm_sec = num3;
+               if (set_time(num, num2, num3, tm) == 0) {
+                       /*
+                        * If %H:%M:%S was just parsed followed by: .<num4>
+                        * Consider (& discard) it as fractional second
+                        * if %Y%m%d is parsed before.
+                        */
+                       if (*end == '.' && isdigit(end[1]) && is_date_known(tm))
+                               strtol(end + 1, &end, 10);
                        break;
                }
                return 0;
@@ -575,10 +598,10 @@ static int match_multi_number(timestamp_t num, char c, const char *date,
 
                if (num > 70) {
                        /* yyyy-mm-dd? */
-                       if (is_date(num, num2, num3, NULL, now, tm))
+                       if (set_date(num, num2, num3, NULL, now, tm) == 0)
                                break;
                        /* yyyy-dd-mm? */
-                       if (is_date(num, num3, num2, NULL, now, tm))
+                       if (set_date(num, num3, num2, NULL, now, tm) == 0)
                                break;
                }
                /* Our eastern European friends say dd.mm.yy[yy]
@@ -586,14 +609,14 @@ static int match_multi_number(timestamp_t num, char c, const char *date,
                 * mm/dd/yy[yy] form only when separator is not '.'
                 */
                if (c != '.' &&
-                   is_date(num3, num, num2, refuse_future, now, tm))
+                   set_date(num3, num, num2, refuse_future, now, tm) == 0)
                        break;
                /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
-               if (is_date(num3, num2, num, refuse_future, now, tm))
+               if (set_date(num3, num2, num, refuse_future, now, tm) == 0)
                        break;
                /* Funny European mm.dd.yy */
                if (c == '.' &&
-                   is_date(num3, num, num2, refuse_future, now, tm))
+                   set_date(num3, num, num2, refuse_future, now, tm) == 0)
                        break;
                return 0;
        }
@@ -664,6 +687,20 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
                n++;
        } while (isdigit(date[n]));
 
+       /* 8 digits, compact style of ISO-8601's date: YYYYmmDD */
+       /* 6 digits, compact style of ISO-8601's time: HHMMSS */
+       if (n == 8 || n == 6) {
+               unsigned int num1 = num / 10000;
+               unsigned int num2 = (num % 10000) / 100;
+               unsigned int num3 = num % 100;
+               if (n == 8)
+                       set_date(num1, num2, num3, NULL, time(NULL), tm);
+               else if (n == 6 && set_time(num1, num2, num3, tm) == 0 &&
+                        *end == '.' && isdigit(end[1]))
+                       strtoul(end + 1, &end, 10);
+               return end - date;
+       }
+
        /* Four-digit year or a timezone? */
        if (n == 4) {
                if (num <= 1400 && *offset == -1) {
index 09dbd3cf72ba99d0f8ab793298b6b80215d450d3..aa98b2e54146f7de1f349f16a9a01e754a3a0b73 100644 (file)
@@ -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 f2cfbf2214a29fc9ddf0e356230df7625d3d1139..d1ad6a3c4ad0bc5049d1af26ca0ea33fc9f68a1e 100644 (file)
--- 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;
@@ -4062,6 +4095,9 @@ static void prep_temp_blob(struct index_state *istate,
        struct strbuf tempfile = STRBUF_INIT;
        char *path_dup = xstrdup(path);
        const char *base = basename(path_dup);
+       struct checkout_metadata meta;
+
+       init_checkout_metadata(&meta, NULL, NULL, oid);
 
        /* Generate "XXXXXX_basename.ext" */
        strbuf_addstr(&tempfile, "XXXXXX_");
@@ -4071,7 +4107,7 @@ static void prep_temp_blob(struct index_state *istate,
        if (!temp->tempfile)
                die_errno("unable to create temp-file");
        if (convert_to_working_tree(istate, path,
-                       (const char *)blob, (size_t)size, &buf)) {
+                       (const char *)blob, (size_t)size, &buf, &meta)) {
                blob = buf.buf;
                size = buf.len;
        }
@@ -4141,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,
@@ -6407,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);
 }
@@ -6417,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;
 
@@ -6439,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;
@@ -6491,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) &&
@@ -6502,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);
@@ -6771,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 6febe7e3656ae4da3923d9485bb9dee10c1d5acd..9443dc1b0039026ba6c32efd3e445fa61a0860f5 100644 (file)
--- 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)
index 9d20a6a6fc1d8abe1cb4a75afea6c38ea01c6d3c..0d4a14964d00b3a77016fb0ed677b1847d1ab72c 100644 (file)
@@ -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);
index e189f407af3a8cc0155cd7a1146c9daacfe304a8..99e63e90f89afaf55ef16bb3c11c1546c4d76c2a 100644 (file)
@@ -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;
index 7c07347e42b52c0d010b0c13011536afc0c4d581..d2a63c5c71f4e3667643b1fba5c195e049e313c9 100644 (file)
@@ -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 0ffb1b3302452c2cce0bdaa55e5259d9be168b63..d97e9558489d3bf622673b1f5b90053154ea9033 100644 (file)
--- 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;
index f079abbf1102686fdb8e64258f456877f976dd03..91989ee8a116aa44502a2c60dfbd02ba4528a34f 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -54,7 +54,8 @@ static int launch_specified_editor(const char *editor, const char *path,
                return error("Terminal is dumb, but EDITOR unset");
 
        if (strcmp(editor, ":")) {
-               const char *args[] = { editor, real_path(path), NULL };
+               struct strbuf realpath = STRBUF_INIT;
+               const char *args[] = { editor, NULL, NULL };
                struct child_process p = CHILD_PROCESS_INIT;
                int ret, sig;
                int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
@@ -75,16 +76,22 @@ static int launch_specified_editor(const char *editor, const char *path,
                        fflush(stderr);
                }
 
+               strbuf_realpath(&realpath, path, 1);
+               args[1] = realpath.buf;
+
                p.argv = args;
                p.env = env;
                p.use_shell = 1;
                p.trace2_child_class = "editor";
-               if (start_command(&p) < 0)
+               if (start_command(&p) < 0) {
+                       strbuf_release(&realpath);
                        return error("unable to start editor '%s'", editor);
+               }
 
                sigchain_push(SIGINT, SIG_IGN);
                sigchain_push(SIGQUIT, SIG_IGN);
                ret = finish_command(&p);
+               strbuf_release(&realpath);
                sig = ret - 128;
                sigchain_pop(SIGINT);
                sigchain_pop(SIGQUIT);
diff --git a/entry.c b/entry.c
index 53380bb614c19e82edfb45049f1703b4a3b9c8a3..00b49033668160574c35555293c0c755cbafaeaa 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -264,6 +264,9 @@ static int write_entry(struct cache_entry *ce,
        size_t newsize = 0;
        struct stat st;
        const struct submodule *sub;
+       struct checkout_metadata meta;
+
+       clone_checkout_metadata(&meta, &state->meta, &ce->oid);
 
        if (ce_mode_s_ifmt == S_IFREG) {
                struct stream_filter *filter = get_stream_filter(state->istate, ce->name,
@@ -315,13 +318,13 @@ static int write_entry(struct cache_entry *ce,
                 */
                if (dco && dco->state != CE_NO_DELAY) {
                        ret = async_convert_to_working_tree(state->istate, ce->name, new_blob,
-                                                           size, &buf, dco);
+                                                           size, &buf, &meta, dco);
                        if (ret && string_list_has_string(&dco->paths, ce->name)) {
                                free(new_blob);
                                goto delayed;
                        }
                } else
-                       ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf);
+                       ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta);
 
                if (ret) {
                        free(new_blob);
index e72a02d0d577dab4da0f559ed988899baff91fbc..aaca0e91ac8f4a3043dfbe963b33a376e72020fd 100644 (file)
@@ -17,6 +17,7 @@
 #include "argv-array.h"
 #include "object-store.h"
 #include "chdir-notify.h"
+#include "shallow.h"
 
 int trust_executable_bit = 1;
 int trust_ctime = 1;
@@ -254,8 +255,11 @@ static int git_work_tree_initialized;
  */
 void set_git_work_tree(const char *new_work_tree)
 {
+       struct strbuf realpath = STRBUF_INIT;
+
        if (git_work_tree_initialized) {
-               new_work_tree = real_path(new_work_tree);
+               strbuf_realpath(&realpath, new_work_tree, 1);
+               new_work_tree = realpath.buf;
                if (strcmp(new_work_tree, the_repository->worktree))
                        die("internal error: work tree has already been set\n"
                            "Current worktree: %s\nNew worktree: %s",
@@ -264,6 +268,8 @@ void set_git_work_tree(const char *new_work_tree)
        }
        git_work_tree_initialized = 1;
        repo_set_worktree(the_repository, new_work_tree);
+
+       strbuf_release(&realpath);
 }
 
 const char *get_git_work_tree(void)
@@ -345,11 +351,20 @@ static void update_relative_gitdir(const char *name,
        free(path);
 }
 
-void set_git_dir(const char *path)
+void set_git_dir(const char *path, int make_realpath)
 {
+       struct strbuf realpath = STRBUF_INIT;
+
+       if (make_realpath) {
+               strbuf_realpath(&realpath, path, 1);
+               path = realpath.buf;
+       }
+
        set_git_dir_1(path);
        if (!is_absolute_path(path))
                chdir_notify_register(NULL, update_relative_gitdir, NULL);
+
+       strbuf_release(&realpath);
 }
 
 const char *get_log_output_encoding(void)
index b8b65a801cc1f902f7131590816459d2d1e0faa0..c98970274c4056178b0e9a37e3ba38b323707b50 100644 (file)
@@ -18,6 +18,7 @@
 #include "object-store.h"
 #include "mem-pool.h"
 #include "commit-reach.h"
+#include "khash.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
 
 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;
@@ -53,6 +70,7 @@ struct object_entry_pool {
 
 struct mark_set {
        union {
+               struct object_id *oids[1024];
                struct object_entry *marked[1024];
                struct mark_set *sets[1024];
        } data;
@@ -131,6 +149,9 @@ struct recent_command {
        char *buf;
 };
 
+typedef void (*mark_set_inserter_t)(struct mark_set *s, struct object_id *oid, uintmax_t mark);
+typedef void (*each_mark_fn_t)(uintmax_t mark, void *obj, void *cbp);
+
 /* Configured limits on output */
 static unsigned long max_depth = 50;
 static off_t max_packsize;
@@ -173,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;
@@ -222,6 +243,11 @@ static int allow_unsafe_features;
 /* Signal handling */
 static volatile sig_atomic_t checkpoint_requested;
 
+/* Submodule marks */
+static struct string_list sub_marks_from = STRING_LIST_INIT_DUP;
+static struct string_list sub_marks_to = STRING_LIST_INIT_DUP;
+static kh_oid_map_t *sub_oid_map;
+
 /* Where to write output of cat-blob commands */
 static int cat_blob_fd = STDOUT_FILENO;
 
@@ -230,6 +256,29 @@ static void parse_get_mark(const char *p);
 static void parse_cat_blob(const char *p);
 static void parse_ls(const char *p, struct branch *b);
 
+static void for_each_mark(struct mark_set *m, uintmax_t base, each_mark_fn_t callback, void *p)
+{
+       uintmax_t k;
+       if (m->shift) {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.sets[k])
+                               for_each_mark(m->data.sets[k], base + (k << m->shift), callback, p);
+               }
+       } else {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.marked[k])
+                               callback(base + k, m->data.marked[k], p);
+               }
+       }
+}
+
+static void dump_marks_fn(uintmax_t mark, void *object, void *cbp) {
+       struct object_entry *e = object;
+       FILE *f = cbp;
+
+       fprintf(f, ":%" PRIuMAX " %s\n", mark, oid_to_hex(&e->idx.oid));
+}
+
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
        fprintf(rpt, "%s:\n", b->name);
@@ -258,8 +307,6 @@ static void write_branch_report(FILE *rpt, struct branch *b)
        fputc('\n', rpt);
 }
 
-static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
-
 static void write_crash_report(const char *err)
 {
        char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
@@ -338,7 +385,7 @@ static void write_crash_report(const char *err)
        if (export_marks_file)
                fprintf(rpt, "  exported to %s\n", export_marks_file);
        else
-               dump_marks_helper(rpt, 0, marks);
+               for_each_mark(marks, 0, dump_marks_fn, rpt);
 
        fputc('\n', rpt);
        fputs("-------------------\n", rpt);
@@ -424,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++) {
@@ -493,9 +533,8 @@ static char *pool_strdup(const char *s)
        return r;
 }
 
-static void insert_mark(uintmax_t idnum, struct object_entry *oe)
+static void insert_mark(struct mark_set *s, uintmax_t idnum, struct object_entry *oe)
 {
-       struct mark_set *s = marks;
        while ((idnum >> s->shift) >= 1024) {
                s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
                s->shift = marks->shift + 10;
@@ -516,10 +555,9 @@ static void insert_mark(uintmax_t idnum, struct object_entry *oe)
        s->data.marked[idnum] = oe;
 }
 
-static struct object_entry *find_mark(uintmax_t idnum)
+static void *find_mark(struct mark_set *s, uintmax_t idnum)
 {
        uintmax_t orig_idnum = idnum;
-       struct mark_set *s = marks;
        struct object_entry *oe = NULL;
        if ((idnum >> s->shift) < 1024) {
                while (s && s->shift) {
@@ -919,7 +957,7 @@ static int store_object(
 
        e = insert_object(&oid);
        if (mark)
-               insert_mark(mark, e);
+               insert_mark(marks, mark, e);
        if (e->idx.offset) {
                duplicate_count_by_type[type]++;
                return 1;
@@ -1117,7 +1155,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
        e = insert_object(&oid);
 
        if (mark)
-               insert_mark(mark, e);
+               insert_mark(marks, mark, e);
 
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
@@ -1655,26 +1693,6 @@ static void dump_tags(void)
        strbuf_release(&err);
 }
 
-static void dump_marks_helper(FILE *f,
-       uintmax_t base,
-       struct mark_set *m)
-{
-       uintmax_t k;
-       if (m->shift) {
-               for (k = 0; k < 1024; k++) {
-                       if (m->data.sets[k])
-                               dump_marks_helper(f, base + (k << m->shift),
-                                       m->data.sets[k]);
-               }
-       } else {
-               for (k = 0; k < 1024; k++) {
-                       if (m->data.marked[k])
-                               fprintf(f, ":%" PRIuMAX " %s\n", base + k,
-                                       oid_to_hex(&m->data.marked[k]->idx.oid));
-               }
-       }
-}
-
 static void dump_marks(void)
 {
        struct lock_file mark_lock = LOCK_INIT;
@@ -1704,7 +1722,7 @@ static void dump_marks(void)
                return;
        }
 
-       dump_marks_helper(f, 0, marks);
+       for_each_mark(marks, 0, dump_marks_fn, f);
        if (commit_lock_file(&mark_lock)) {
                failure |= error_errno("Unable to write file %s",
                                       export_marks_file);
@@ -1712,21 +1730,38 @@ static void dump_marks(void)
        }
 }
 
-static void read_marks(void)
+static void insert_object_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark)
+{
+       struct object_entry *e;
+       e = find_object(oid);
+       if (!e) {
+               enum object_type type = oid_object_info(the_repository,
+                                                       oid, NULL);
+               if (type < 0)
+                       die("object not found: %s", oid_to_hex(oid));
+               e = insert_object(oid);
+               e->type = type;
+               e->pack_id = MAX_PACK_ID;
+               e->idx.offset = 1; /* just not zero! */
+       }
+       insert_mark(s, mark, e);
+}
+
+static void insert_oid_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark)
+{
+       insert_mark(s, mark, xmemdupz(oid, sizeof(*oid)));
+}
+
+static void read_mark_file(struct mark_set *s, FILE *f, mark_set_inserter_t inserter)
 {
        char line[512];
-       FILE *f = fopen(import_marks_file, "r");
-       if (f)
-               ;
-       else if (import_marks_file_ignore_missing && errno == ENOENT)
-               goto done; /* Marks file does not exist */
-       else
-               die_errno("cannot read '%s'", import_marks_file);
        while (fgets(line, sizeof(line), f)) {
                uintmax_t mark;
                char *end;
                struct object_id oid;
-               struct object_entry *e;
+
+               /* Ensure SHA-1 objects are padded with zeros. */
+               memset(oid.hash, 0, sizeof(oid.hash));
 
                end = strchr(line, '\n');
                if (line[0] != ':' || !end)
@@ -1734,21 +1769,23 @@ static void read_marks(void)
                *end = 0;
                mark = strtoumax(line + 1, &end, 10);
                if (!mark || end == line + 1
-                       || *end != ' ' || get_oid_hex(end + 1, &oid))
+                       || *end != ' '
+                       || get_oid_hex_any(end + 1, &oid) == GIT_HASH_UNKNOWN)
                        die("corrupt mark line: %s", line);
-               e = find_object(&oid);
-               if (!e) {
-                       enum object_type type = oid_object_info(the_repository,
-                                                               &oid, NULL);
-                       if (type < 0)
-                               die("object not found: %s", oid_to_hex(&oid));
-                       e = insert_object(&oid);
-                       e->type = type;
-                       e->pack_id = MAX_PACK_ID;
-                       e->idx.offset = 1; /* just not zero! */
-               }
-               insert_mark(mark, e);
+               inserter(s, &oid, mark);
        }
+}
+
+static void read_marks(void)
+{
+       FILE *f = fopen(import_marks_file, "r");
+       if (f)
+               ;
+       else if (import_marks_file_ignore_missing && errno == ENOENT)
+               goto done; /* Marks file does not exist */
+       else
+               die_errno("cannot read '%s'", import_marks_file);
+       read_mark_file(marks, f, insert_object_entry);
        fclose(f);
 done:
        import_marks_file_done = 1;
@@ -2134,6 +2171,30 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
        return do_change_note_fanout(root, root, hex_oid, 0, path, 0, fanout);
 }
 
+static int parse_mapped_oid_hex(const char *hex, struct object_id *oid, const char **end)
+{
+       int algo;
+       khiter_t it;
+
+       /* Make SHA-1 object IDs have all-zero padding. */
+       memset(oid->hash, 0, sizeof(oid->hash));
+
+       algo = parse_oid_hex_any(hex, oid, end);
+       if (algo == GIT_HASH_UNKNOWN)
+               return -1;
+
+       it = kh_get_oid_map(sub_oid_map, *oid);
+       /* No such object? */
+       if (it == kh_end(sub_oid_map)) {
+               /* If we're using the same algorithm, pass it through. */
+               if (hash_algos[algo].format_id == the_hash_algo->format_id)
+                       return 0;
+               return -1;
+       }
+       oidcpy(oid, kh_value(sub_oid_map, it));
+       return 0;
+}
+
 /*
  * Given a pointer into a string, parse a mark reference:
  *
@@ -2214,13 +2275,13 @@ static void file_change_m(const char *p, struct branch *b)
        }
 
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_space(&p));
+               oe = find_mark(marks, parse_mark_ref_space(&p));
                oidcpy(&oid, &oe->idx.oid);
        } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(&oid);
                if (*p++ != ' ')
@@ -2388,13 +2449,13 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
        /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_space(&p));
+               oe = find_mark(marks, parse_mark_ref_space(&p));
                oidcpy(&oid, &oe->idx.oid);
        } else if (skip_prefix(p, "inline ", &p)) {
                inline_data = 1;
                oe = NULL; /* not used with inline_data, but makes gcc happy */
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(&oid);
                if (*p++ != ' ')
@@ -2409,7 +2470,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa
                oidcpy(&commit_oid, &s->oid);
        } else if (*p == ':') {
                uintmax_t commit_mark = parse_mark_ref_eol(p);
-               struct object_entry *commit_oe = find_mark(commit_mark);
+               struct object_entry *commit_oe = find_mark(marks, commit_mark);
                if (commit_oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", commit_mark);
                oidcpy(&commit_oid, &commit_oe->idx.oid);
@@ -2513,7 +2574,7 @@ static int parse_objectish(struct branch *b, const char *objectish)
                oidcpy(&b->branch_tree.versions[1].oid, t);
        } else if (*objectish == ':') {
                uintmax_t idnum = parse_mark_ref_eol(objectish);
-               struct object_entry *oe = find_mark(idnum);
+               struct object_entry *oe = find_mark(marks, idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
                if (!oideq(&b->oid, &oe->idx.oid)) {
@@ -2577,7 +2638,7 @@ static struct hash_list *parse_merge(unsigned int *count)
                        oidcpy(&n->oid, &s->oid);
                else if (*from == ':') {
                        uintmax_t idnum = parse_mark_ref_eol(from);
-                       struct object_entry *oe = find_mark(idnum);
+                       struct object_entry *oe = find_mark(marks, idnum);
                        if (oe->type != OBJ_COMMIT)
                                die("Mark :%" PRIuMAX " not a commit", idnum);
                        oidcpy(&n->oid, &oe->idx.oid);
@@ -2751,7 +2812,7 @@ static void parse_new_tag(const char *arg)
        } else if (*from == ':') {
                struct object_entry *oe;
                from_mark = parse_mark_ref_eol(from);
-               oe = find_mark(from_mark);
+               oe = find_mark(marks, from_mark);
                type = oe->type;
                oidcpy(&oid, &oe->idx.oid);
        } else if (!get_oid(from, &oid)) {
@@ -2909,7 +2970,7 @@ static void parse_get_mark(const char *p)
        if (*p != ':')
                die("Not a mark: %s", p);
 
-       oe = find_mark(parse_mark_ref_eol(p));
+       oe = find_mark(marks, parse_mark_ref_eol(p));
        if (!oe)
                die("Unknown mark: %s", command_buf.buf);
 
@@ -2924,12 +2985,12 @@ static void parse_cat_blob(const char *p)
 
        /* cat-blob SP <object> LF */
        if (*p == ':') {
-               oe = find_mark(parse_mark_ref_eol(p));
+               oe = find_mark(marks, parse_mark_ref_eol(p));
                if (!oe)
                        die("Unknown mark: %s", command_buf.buf);
                oidcpy(&oid, &oe->idx.oid);
        } else {
-               if (parse_oid_hex(p, &oid, &p))
+               if (parse_mapped_oid_hex(p, &oid, &p))
                        die("Invalid dataref: %s", command_buf.buf);
                if (*p)
                        die("Garbage after SHA1: %s", command_buf.buf);
@@ -2993,18 +3054,54 @@ static struct object_entry *dereference(struct object_entry *oe,
        return find_object(oid);
 }
 
+static void insert_mapped_mark(uintmax_t mark, void *object, void *cbp)
+{
+       struct object_id *fromoid = object;
+       struct object_id *tooid = find_mark(cbp, mark);
+       int ret;
+       khiter_t it;
+
+       it = kh_put_oid_map(sub_oid_map, *fromoid, &ret);
+       /* We've already seen this object. */
+       if (ret == 0)
+               return;
+       kh_value(sub_oid_map, it) = tooid;
+}
+
+static void build_mark_map_one(struct mark_set *from, struct mark_set *to)
+{
+       for_each_mark(from, 0, insert_mapped_mark, to);
+}
+
+static void build_mark_map(struct string_list *from, struct string_list *to)
+{
+       struct string_list_item *fromp, *top;
+
+       sub_oid_map = kh_init_oid_map();
+
+       for_each_string_list_item(fromp, from) {
+               top = string_list_lookup(to, fromp->string);
+               if (!fromp->util) {
+                       die(_("Missing from marks for submodule '%s'"), fromp->string);
+               } else if (!top || !top->util) {
+                       die(_("Missing to marks for submodule '%s'"), fromp->string);
+               }
+               build_mark_map_one(fromp->util, top->util);
+       }
+}
+
 static struct object_entry *parse_treeish_dataref(const char **p)
 {
        struct object_id oid;
        struct object_entry *e;
 
        if (**p == ':') {       /* <mark> */
-               e = find_mark(parse_mark_ref_space(p));
+               e = find_mark(marks, parse_mark_ref_space(p));
                if (!e)
                        die("Unknown mark: %s", command_buf.buf);
                oidcpy(&oid, &e->idx.oid);
        } else {        /* <sha1> */
-               if (parse_oid_hex(*p, &oid, p))
+               if (parse_mapped_oid_hex(*p, &oid, p))
                        die("Invalid dataref: %s", command_buf.buf);
                e = find_object(&oid);
                if (*(*p)++ != ' ')
@@ -3130,7 +3227,7 @@ static void parse_alias(void)
                die(_("Expected 'to' command, got %s"), command_buf.buf);
        e = find_object(&b.oid);
        assert(e);
-       insert_mark(next_mark, e);
+       insert_mark(marks, next_mark, e);
 }
 
 static char* make_fast_import_path(const char *path)
@@ -3210,6 +3307,26 @@ static void option_export_pack_edges(const char *edges)
        pack_edges = xfopen(edges, "a");
 }
 
+static void option_rewrite_submodules(const char *arg, struct string_list *list)
+{
+       struct mark_set *ms;
+       FILE *fp;
+       char *s = xstrdup(arg);
+       char *f = strchr(s, ':');
+       if (!f)
+               die(_("Expected format name:filename for submodule rewrite option"));
+       *f = '\0';
+       f++;
+       ms = xcalloc(1, sizeof(*ms));
+       string_list_insert(list, s)->util = ms;
+
+       fp = fopen(f, "r");
+       if (!fp)
+               die_errno("cannot read '%s'", f);
+       read_mark_file(ms, fp, insert_oid_entry);
+       fclose(fp);
+}
+
 static int parse_one_option(const char *option)
 {
        if (skip_prefix(option, "max-pack-size=", &option)) {
@@ -3272,6 +3389,11 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_export_marks(arg);
        } else if (!strcmp(feature, "alias")) {
                ; /* Don't die - this feature is supported */
+       } else if (skip_prefix(feature, "rewrite-submodules-to=", &arg)) {
+               option_rewrite_submodules(arg, &sub_marks_to);
+       } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
+               option_rewrite_submodules(arg, &sub_marks_from);
+       } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
        } else if (!strcmp(feature, "get-mark")) {
                ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "cat-blob")) {
@@ -3377,6 +3499,7 @@ static void parse_argv(void)
        seen_data_command = 1;
        if (import_marks_file)
                read_marks();
+       build_mark_map(&sub_marks_from, &sub_marks_to);
 }
 
 int cmd_main(int argc, const char **argv)
@@ -3397,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
index 1734a573b010dd2f87541154cc70e5bb71b00463..7eaa19d7c17abeb4d2975d112a79762507a81009 100644 (file)
 #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"
 #include "connected.h"
 #include "fetch-negotiator.h"
 #include "fsck.h"
+#include "shallow.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -34,7 +35,7 @@ static int fetch_fsck_objects = -1;
 static int transfer_fsck_objects = -1;
 static int agent_supported;
 static int server_supports_filtering;
-static struct lock_file shallow_lock;
+static struct shallow_lock shallow_lock;
 static const char *alternate_shallow_file;
 static struct strbuf fsck_msg_types = STRBUF_INIT;
 
@@ -1143,6 +1144,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 +1159,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 +1175,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 +1232,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 +1271,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 +1308,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 +1343,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 +1468,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 +1525,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 +1534,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 +1656,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 +1682,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 +1720,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 +1812,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 (file)
index 0000000..72d32bd
--- /dev/null
@@ -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;
+}
index 01e3aa88c50c1994065a99515bd13f484558b553..f2ab0e0085ada6509c02427503b1ea04b18951e0 100644 (file)
@@ -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 73f30773f28acc06f4b9c04dda1e1a54b46c6930..087a7f1ffc7fa78356c8492a1bf910bf9e65d82d 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -1065,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 ||
index 0157acbf2e0554aaa7b3b6750a4d2bdc1ff89abd..9fd1c04edd3108917ebae6e83840e1a2383606a9 100644 (file)
@@ -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);
 
index 71158f7d8ba9c774ca6790ea8594e96accb17fce..45fecf8bdfb21abea3c8935ba926ab819773509e 100755 (executable)
@@ -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 (executable)
index 0000000..8692fe5
--- /dev/null
@@ -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
index efee12b8b1e6d53d3cd5275683ebb0a4198b4b95..71b367a9447a6fb448e20fc606918103fc4f1bfc 100755 (executable)
@@ -209,6 +209,7 @@ bisect_replay () {
        test "$#" -eq 1 || die "$(gettext "No logfile given")"
        test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")"
        git bisect--helper --bisect-reset || exit
+       oIFS="$IFS" IFS="$IFS$(printf '\015')"
        while read git bisect command rev
        do
                test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue
@@ -232,6 +233,7 @@ bisect_replay () {
                        die "$(gettext "?? what are you talking about?")" ;;
                esac
        done <"$file"
+       IFS="$oIFS"
        bisect_auto_next
 }
 
index aed0b5d4f9028a5ae38e8676ec2d1c3a63dfdc97..8ba576e81e3e52843cc84350978a26df8e3344e7 100644 (file)
@@ -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-legacy-stash.sh b/git-legacy-stash.sh
deleted file mode 100755 (executable)
index 4d4ebb4..0000000
+++ /dev/null
@@ -1,798 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2007, Nanako Shiraishi
-
-dashless=$(basename "$0" | sed -e 's/-/ /')
-USAGE="list [<options>]
-   or: $dashless show [<stash>]
-   or: $dashless drop [-q|--quiet] [<stash>]
-   or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
-   or: $dashless branch <branchname> [<stash>]
-   or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-                     [-u|--include-untracked] [-a|--all] [<message>]
-   or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
-                      [-u|--include-untracked] [-a|--all] [-m <message>]
-                      [-- <pathspec>...]]
-   or: $dashless clear"
-
-SUBDIRECTORY_OK=Yes
-OPTIONS_SPEC=
-START_DIR=$(pwd)
-. git-sh-setup
-require_work_tree
-prefix=$(git rev-parse --show-prefix) || exit 1
-cd_to_toplevel
-
-TMP="$GIT_DIR/.git-stash.$$"
-TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$
-trap 'rm -f "$TMP-"* "$TMPindex"' 0
-
-ref_stash=refs/stash
-
-if git config --get-colorbool color.interactive; then
-       help_color="$(git config --get-color color.interactive.help 'red bold')"
-       reset_color="$(git config --get-color '' reset)"
-else
-       help_color=
-       reset_color=
-fi
-
-no_changes () {
-       git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
-       git diff-files --quiet --ignore-submodules -- "$@" &&
-       (test -z "$untracked" || test -z "$(untracked_files "$@")")
-}
-
-untracked_files () {
-       if test "$1" = "-z"
-       then
-               shift
-               z=-z
-       else
-               z=
-       fi
-       excl_opt=--exclude-standard
-       test "$untracked" = "all" && excl_opt=
-       git ls-files -o $z $excl_opt -- "$@"
-}
-
-prepare_fallback_ident () {
-       if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1
-       then
-               GIT_AUTHOR_NAME="git stash"
-               GIT_AUTHOR_EMAIL=git@stash
-               GIT_COMMITTER_NAME="git stash"
-               GIT_COMMITTER_EMAIL=git@stash
-               export GIT_AUTHOR_NAME
-               export GIT_AUTHOR_EMAIL
-               export GIT_COMMITTER_NAME
-               export GIT_COMMITTER_EMAIL
-       fi
-}
-
-clear_stash () {
-       if test $# != 0
-       then
-               die "$(gettext "git stash clear with parameters is unimplemented")"
-       fi
-       if current=$(git rev-parse --verify --quiet $ref_stash)
-       then
-               git update-ref -d $ref_stash $current
-       fi
-}
-
-maybe_quiet () {
-       case "$1" in
-       --keep-stdout)
-               shift
-               if test -n "$GIT_QUIET"
-               then
-                       "$@" 2>/dev/null
-               else
-                       "$@"
-               fi
-               ;;
-       *)
-               if test -n "$GIT_QUIET"
-               then
-                       "$@" >/dev/null 2>&1
-               else
-                       "$@"
-               fi
-               ;;
-       esac
-}
-
-create_stash () {
-
-       prepare_fallback_ident
-
-       stash_msg=
-       untracked=
-       while test $# != 0
-       do
-               case "$1" in
-               -m|--message)
-                       shift
-                       stash_msg=${1?"BUG: create_stash () -m requires an argument"}
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               -u|--include-untracked)
-                       shift
-                       untracked=${1?"BUG: create_stash () -u requires an argument"}
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       git update-index -q --refresh
-       if maybe_quiet no_changes "$@"
-       then
-               exit 0
-       fi
-
-       # state of the base commit
-       if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD)
-       then
-               head=$(git rev-list --oneline -n 1 HEAD --)
-       elif test -n "$GIT_QUIET"
-       then
-               exit 1
-       else
-               die "$(gettext "You do not have the initial commit yet")"
-       fi
-
-       if branch=$(git symbolic-ref -q HEAD)
-       then
-               branch=${branch#refs/heads/}
-       else
-               branch='(no branch)'
-       fi
-       msg=$(printf '%s: %s' "$branch" "$head")
-
-       # state of the index
-       i_tree=$(git write-tree) &&
-       i_commit=$(printf 'index on %s\n' "$msg" |
-               git commit-tree $i_tree -p $b_commit) ||
-               die "$(gettext "Cannot save the current index state")"
-
-       if test -n "$untracked"
-       then
-               # Untracked files are stored by themselves in a parentless commit, for
-               # ease of unpacking later.
-               u_commit=$(
-                       untracked_files -z "$@" | (
-                               GIT_INDEX_FILE="$TMPindex" &&
-                               export GIT_INDEX_FILE &&
-                               rm -f "$TMPindex" &&
-                               git update-index -z --add --remove --stdin &&
-                               u_tree=$(git write-tree) &&
-                               printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree  &&
-                               rm -f "$TMPindex"
-               ) ) || die "$(gettext "Cannot save the untracked files")"
-
-               untracked_commit_option="-p $u_commit";
-       else
-               untracked_commit_option=
-       fi
-
-       if test -z "$patch_mode"
-       then
-
-               # state of the working tree
-               w_tree=$( (
-                       git read-tree --index-output="$TMPindex" -m $i_tree &&
-                       GIT_INDEX_FILE="$TMPindex" &&
-                       export GIT_INDEX_FILE &&
-                       git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
-                       git update-index --ignore-skip-worktree-entries \
-                               -z --add --remove --stdin <"$TMP-stagenames" &&
-                       git write-tree &&
-                       rm -f "$TMPindex"
-               ) ) ||
-                       die "$(gettext "Cannot save the current worktree state")"
-
-       else
-
-               rm -f "$TMP-index" &&
-               GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
-
-               # find out what the user wants
-               GIT_INDEX_FILE="$TMP-index" \
-                       git add --legacy-stash-p -- "$@" &&
-
-               # state of the working tree
-               w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
-               die "$(gettext "Cannot save the current worktree state")"
-
-               git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
-               test -s "$TMP-patch" ||
-               die "$(gettext "No changes selected")"
-
-               rm -f "$TMP-index" ||
-               die "$(gettext "Cannot remove temporary index (can't happen)")"
-
-       fi
-
-       # create the stash
-       if test -z "$stash_msg"
-       then
-               stash_msg=$(printf 'WIP on %s' "$msg")
-       else
-               stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
-       fi
-       w_commit=$(printf '%s\n' "$stash_msg" |
-       git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
-       die "$(gettext "Cannot record working tree state")"
-}
-
-store_stash () {
-       while test $# != 0
-       do
-               case "$1" in
-               -m|--message)
-                       shift
-                       stash_msg="$1"
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               -q|--quiet)
-                       quiet=t
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-       test $# = 1 ||
-       die "$(eval_gettext "\"$dashless store\" requires one <commit> argument")"
-
-       w_commit="$1"
-       if test -z "$stash_msg"
-       then
-               stash_msg="Created via \"git stash store\"."
-       fi
-
-       git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
-       ret=$?
-       test $ret != 0 && test -z "$quiet" &&
-       die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
-       return $ret
-}
-
-push_stash () {
-       keep_index=
-       patch_mode=
-       untracked=
-       stash_msg=
-       while test $# != 0
-       do
-               case "$1" in
-               -k|--keep-index)
-                       keep_index=t
-                       ;;
-               --no-keep-index)
-                       keep_index=n
-                       ;;
-               -p|--patch)
-                       patch_mode=t
-                       # only default to keep if we don't already have an override
-                       test -z "$keep_index" && keep_index=t
-                       ;;
-               -q|--quiet)
-                       GIT_QUIET=t
-                       ;;
-               -u|--include-untracked)
-                       untracked=untracked
-                       ;;
-               -a|--all)
-                       untracked=all
-                       ;;
-               -m|--message)
-                       shift
-                       test -z ${1+x} && usage
-                       stash_msg=$1
-                       ;;
-               -m*)
-                       stash_msg=${1#-m}
-                       ;;
-               --message=*)
-                       stash_msg=${1#--message=}
-                       ;;
-               --help)
-                       show_help
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               -*)
-                       option="$1"
-                       eval_gettextln "error: unknown option for 'stash push': \$option"
-                       usage
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
-
-       if test -n "$patch_mode" && test -n "$untracked"
-       then
-               die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
-       fi
-
-       test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
-
-       git update-index -q --refresh
-       if maybe_quiet no_changes "$@"
-       then
-               say "$(gettext "No local changes to save")"
-               exit 0
-       fi
-
-       git reflog exists $ref_stash ||
-               clear_stash || die "$(gettext "Cannot initialize stash")"
-
-       create_stash -m "$stash_msg" -u "$untracked" -- "$@"
-       store_stash -m "$stash_msg" -q $w_commit ||
-       die "$(gettext "Cannot save the current status")"
-       say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
-
-       if test -z "$patch_mode"
-       then
-               test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
-               if test -n "$untracked" && test $# = 0
-               then
-                       git clean --force --quiet -d $CLEAN_X_OPTION
-               fi
-
-               if test $# != 0
-               then
-                       test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION=
-                       test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION=
-                       git add $UPDATE_OPTION $FORCE_OPTION -- "$@"
-                       git diff-index -p --cached --binary HEAD -- "$@" |
-                       git apply --index -R
-               else
-                       git reset --hard -q --no-recurse-submodules
-               fi
-
-               if test "$keep_index" = "t" && test -n "$i_tree"
-               then
-                       git read-tree --reset $i_tree
-                       git ls-files -z --modified -- "$@" |
-                       git checkout-index -z --force --stdin
-               fi
-       else
-               git apply -R < "$TMP-patch" ||
-               die "$(gettext "Cannot remove worktree changes")"
-
-               if test "$keep_index" != "t"
-               then
-                       git reset -q -- "$@"
-               fi
-       fi
-}
-
-save_stash () {
-       push_options=
-       while test $# != 0
-       do
-               case "$1" in
-               -q|--quiet)
-                       GIT_QUIET=t
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               -*)
-                       # pass all options through to push_stash
-                       push_options="$push_options $1"
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-               shift
-       done
-
-       stash_msg="$*"
-
-       if test -z "$stash_msg"
-       then
-               push_stash $push_options
-       else
-               push_stash $push_options -m "$stash_msg"
-       fi
-}
-
-have_stash () {
-       git rev-parse --verify --quiet $ref_stash >/dev/null
-}
-
-list_stash () {
-       have_stash || return 0
-       git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash --
-}
-
-show_stash () {
-       ALLOW_UNKNOWN_FLAGS=t
-       assert_stash_like "$@"
-
-       if test -z "$FLAGS"
-       then
-               if test "$(git config --bool stash.showStat || echo true)" = "true"
-               then
-                       FLAGS=--stat
-               fi
-
-               if test "$(git config --bool stash.showPatch || echo false)" = "true"
-               then
-                       FLAGS=${FLAGS}${FLAGS:+ }-p
-               fi
-
-               if test -z "$FLAGS"
-               then
-                       return 0
-               fi
-       fi
-
-       git diff ${FLAGS} $b_commit $w_commit
-}
-
-show_help () {
-       exec git help stash
-       exit 1
-}
-
-#
-# Parses the remaining options looking for flags and
-# at most one revision defaulting to ${ref_stash}@{0}
-# if none found.
-#
-# Derives related tree and commit objects from the
-# revision, if one is found.
-#
-# stash records the work tree, and is a merge between the
-# base commit (first parent) and the index tree (second parent).
-#
-#   REV is set to the symbolic version of the specified stash-like commit
-#   IS_STASH_LIKE is non-blank if ${REV} looks like a stash
-#   IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
-#   s is set to the SHA1 of the stash commit
-#   w_commit is set to the commit containing the working tree
-#   b_commit is set to the base commit
-#   i_commit is set to the commit containing the index tree
-#   u_commit is set to the commit containing the untracked files tree
-#   w_tree is set to the working tree
-#   b_tree is set to the base tree
-#   i_tree is set to the index tree
-#   u_tree is set to the untracked files tree
-#
-#   GIT_QUIET is set to t if -q is specified
-#   INDEX_OPTION is set to --index if --index is specified.
-#   FLAGS is set to the remaining flags (if allowed)
-#
-# dies if:
-#   * too many revisions specified
-#   * no revision is specified and there is no stash stack
-#   * a revision is specified which cannot be resolve to a SHA1
-#   * a non-existent stash reference is specified
-#   * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t"
-#
-
-parse_flags_and_rev()
-{
-       test "$PARSE_CACHE" = "$*" && return 0 # optimisation
-       PARSE_CACHE="$*"
-
-       IS_STASH_LIKE=
-       IS_STASH_REF=
-       INDEX_OPTION=
-       s=
-       w_commit=
-       b_commit=
-       i_commit=
-       u_commit=
-       w_tree=
-       b_tree=
-       i_tree=
-       u_tree=
-
-       FLAGS=
-       REV=
-       for opt
-       do
-               case "$opt" in
-                       -q|--quiet)
-                               GIT_QUIET=-t
-                       ;;
-                       --index)
-                               INDEX_OPTION=--index
-                       ;;
-                       --help)
-                               show_help
-                       ;;
-                       -*)
-                               test "$ALLOW_UNKNOWN_FLAGS" = t ||
-                                       die "$(eval_gettext "unknown option: \$opt")"
-                               FLAGS="${FLAGS}${FLAGS:+ }$opt"
-                       ;;
-                       *)
-                               REV="${REV}${REV:+ }'$opt'"
-                       ;;
-               esac
-       done
-
-       eval set -- $REV
-
-       case $# in
-               0)
-                       have_stash || die "$(gettext "No stash entries found.")"
-                       set -- ${ref_stash}@{0}
-               ;;
-               1)
-                       :
-               ;;
-               *)
-                       die "$(eval_gettext "Too many revisions specified: \$REV")"
-               ;;
-       esac
-
-       case "$1" in
-               *[!0-9]*)
-                       :
-               ;;
-               *)
-                       set -- "${ref_stash}@{$1}"
-               ;;
-       esac
-
-       REV=$(git rev-parse --symbolic --verify --quiet "$1") || {
-               reference="$1"
-               die "$(eval_gettext "\$reference is not a valid reference")"
-       }
-
-       i_commit=$(git rev-parse --verify --quiet "$REV^2") &&
-       set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) &&
-       s=$1 &&
-       w_commit=$1 &&
-       b_commit=$2 &&
-       w_tree=$3 &&
-       b_tree=$4 &&
-       i_tree=$5 &&
-       IS_STASH_LIKE=t &&
-       test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
-       IS_STASH_REF=t
-
-       u_commit=$(git rev-parse --verify --quiet "$REV^3") &&
-       u_tree=$(git rev-parse "$REV^3:" 2>/dev/null)
-}
-
-is_stash_like()
-{
-       parse_flags_and_rev "$@"
-       test -n "$IS_STASH_LIKE"
-}
-
-assert_stash_like() {
-       is_stash_like "$@" || {
-               args="$*"
-               die "$(eval_gettext "'\$args' is not a stash-like commit")"
-       }
-}
-
-is_stash_ref() {
-       is_stash_like "$@" && test -n "$IS_STASH_REF"
-}
-
-assert_stash_ref() {
-       is_stash_ref "$@" || {
-               args="$*"
-               die "$(eval_gettext "'\$args' is not a stash reference")"
-       }
-}
-
-apply_stash () {
-
-       assert_stash_like "$@"
-
-       git update-index -q --refresh || die "$(gettext "unable to refresh index")"
-
-       # current index state
-       c_tree=$(git write-tree) ||
-               die "$(gettext "Cannot apply a stash in the middle of a merge")"
-
-       unstashed_index_tree=
-       if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
-                       test "$c_tree" != "$i_tree"
-       then
-               git diff-tree --binary $s^2^..$s^2 | git apply --cached
-               test $? -ne 0 &&
-                       die "$(gettext "Conflicts in index. Try without --index.")"
-               unstashed_index_tree=$(git write-tree) ||
-                       die "$(gettext "Could not save index tree")"
-               git reset
-       fi
-
-       if test -n "$u_tree"
-       then
-               GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" &&
-               GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
-               rm -f "$TMPindex" ||
-               die "$(gettext "Could not restore untracked files from stash entry")"
-       fi
-
-       eval "
-               GITHEAD_$w_tree='Stashed changes' &&
-               GITHEAD_$c_tree='Updated upstream' &&
-               GITHEAD_$b_tree='Version stash was based on' &&
-               export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
-       "
-
-       if test -n "$GIT_QUIET"
-       then
-               GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
-       fi
-       if git merge-recursive $b_tree -- $c_tree $w_tree
-       then
-               # No conflict
-               if test -n "$unstashed_index_tree"
-               then
-                       git read-tree "$unstashed_index_tree"
-               else
-                       a="$TMP-added" &&
-                       git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" &&
-                       git read-tree --reset $c_tree &&
-                       git update-index --add --stdin <"$a" ||
-                               die "$(gettext "Cannot unstage modified files")"
-                       rm -f "$a"
-               fi
-               squelch=
-               if test -n "$GIT_QUIET"
-               then
-                       squelch='>/dev/null 2>&1'
-               fi
-               (cd "$START_DIR" && eval "git status $squelch") || :
-       else
-               # Merge conflict; keep the exit status from merge-recursive
-               status=$?
-               git rerere
-               if test -n "$INDEX_OPTION"
-               then
-                       gettextln "Index was not unstashed." >&2
-               fi
-               exit $status
-       fi
-}
-
-pop_stash() {
-       assert_stash_ref "$@"
-
-       if apply_stash "$@"
-       then
-               drop_stash "$@"
-       else
-               status=$?
-               say "$(gettext "The stash entry is kept in case you need it again.")"
-               exit $status
-       fi
-}
-
-drop_stash () {
-       assert_stash_ref "$@"
-
-       git reflog delete --updateref --rewrite "${REV}" &&
-               say "$(eval_gettext "Dropped \${REV} (\$s)")" ||
-               die "$(eval_gettext "\${REV}: Could not drop stash entry")"
-
-       # clear_stash if we just dropped the last stash entry
-       git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null ||
-       clear_stash
-}
-
-apply_to_branch () {
-       test -n "$1" || die "$(gettext "No branch name specified")"
-       branch=$1
-       shift 1
-
-       set -- --index "$@"
-       assert_stash_like "$@"
-
-       git checkout -b $branch $REV^ &&
-       apply_stash "$@" && {
-               test -z "$IS_STASH_REF" || drop_stash "$@"
-       }
-}
-
-test "$1" = "-p" && set "push" "$@"
-
-PARSE_CACHE='--not-parsed'
-# The default command is "push" if nothing but options are given
-seen_non_option=
-for opt
-do
-       case "$opt" in
-       --) break ;;
-       -*) ;;
-       *) seen_non_option=t; break ;;
-       esac
-done
-
-test -n "$seen_non_option" || set "push" "$@"
-
-# Main command set
-case "$1" in
-list)
-       shift
-       list_stash "$@"
-       ;;
-show)
-       shift
-       show_stash "$@"
-       ;;
-save)
-       shift
-       save_stash "$@"
-       ;;
-push)
-       shift
-       push_stash "$@"
-       ;;
-apply)
-       shift
-       apply_stash "$@"
-       ;;
-clear)
-       shift
-       clear_stash "$@"
-       ;;
-create)
-       shift
-       create_stash -m "$*" && echo "$w_commit"
-       ;;
-store)
-       shift
-       store_stash "$@"
-       ;;
-drop)
-       shift
-       drop_stash "$@"
-       ;;
-pop)
-       shift
-       pop_stash "$@"
-       ;;
-branch)
-       shift
-       apply_to_branch "$@"
-       ;;
-*)
-       case $# in
-       0)
-               push_stash &&
-               say "$(gettext "(To restore them type \"git stash apply\")")"
-               ;;
-       *)
-               usage
-       esac
-       ;;
-esac
index 9a71a6690db35d674456c677be1212e44e57a582..b8b2a1679e7336e27d794947e9191cd4b5ec1b57 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
 # pylint: disable=too-many-branches,too-many-nested-blocks
 #
 import sys
-if sys.hexversion < 0x02040000:
-    # The limiter is the subprocess module
-    sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
+if sys.version_info.major < 3 and sys.version_info.minor < 7:
+    sys.stderr.write("git-p4: requires Python 2.7 or later.\n")
     sys.exit(1)
 import os
 import optparse
+import functools
 import marshal
 import subprocess
 import tempfile
@@ -34,37 +34,17 @@ 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
+# compatibility
 # support basestring in python3
 try:
-    unicode = unicode
-except NameError:
-    # 'unicode' is undefined, must be Python 3
-    str = str
-    unicode = str
-    bytes = bytes
-    basestring = (str,bytes)
-else:
-    # 'unicode' exists, must be Python 2
-    str = str
-    unicode = unicode
-    bytes = str
-    basestring = basestring
-
-try:
-    from subprocess import CalledProcessError
-except ImportError:
-    # from python2.7:subprocess.py
-    # Exception classes used by this module.
-    class CalledProcessError(Exception):
-        """This exception is raised when a process run by check_call() returns
-        a non-zero exit status.  The exit status will be stored in the
-        returncode attribute."""
-        def __init__(self, returncode, cmd):
-            self.returncode = returncode
-            self.cmd = cmd
-        def __str__(self):
-            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+    if raw_input and input:
+        input = raw_input
+except:
+    pass
 
 verbose = False
 
@@ -113,7 +93,7 @@ def p4_build_cmd(cmd):
         # Provide a way to not pass this option by setting git-p4.retries to 0
         real_cmd += ["-r", str(retries)]
 
-    if isinstance(cmd,basestring):
+    if not isinstance(cmd, list):
         real_cmd = ' '.join(real_cmd) + ' ' + cmd
     else:
         real_cmd += cmd
@@ -186,18 +166,118 @@ def prompt(prompt_text):
     """
     choices = set(m.group(1) for m in re.finditer(r"\[(.)\]", prompt_text))
     while True:
-        response = raw_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]
         if response in choices:
             return response
 
+# We need different encoding/decoding strategies for text data being passed
+# around in pipes depending on python version
+if bytes is not str:
+    # For python3, always encode and decode as appropriate
+    def decode_text_stream(s):
+        return s.decode() if isinstance(s, bytes) else s
+    def encode_text_stream(s):
+        return s.encode() if isinstance(s, str) else s
+else:
+    # For python2.7, pass read strings as-is, but also allow writing unicode
+    def decode_text_stream(s):
+        return s
+    def encode_text_stream(s):
+        return s.encode('utf_8') if isinstance(s, unicode) else s
+
+def decode_path(path):
+    """Decode a given string (bytes or otherwise) using configured path encoding options
+    """
+    encoding = gitConfig('git-p4.pathEncoding') or 'utf_8'
+    if bytes is not str:
+        return path.decode(encoding, errors='replace') if isinstance(path, bytes) else path
+    else:
+        try:
+            path.decode('ascii')
+        except:
+            path = path.decode(encoding, errors='replace')
+            if verbose:
+                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))
 
-    expand = isinstance(c,basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
     pipe = p.stdin
     val = pipe.write(stdin)
@@ -209,6 +289,8 @@ def write_pipe(c, stdin):
 
 def p4_write_pipe(c, stdin):
     real_cmd = p4_build_cmd(c)
+    if bytes is not str and isinstance(stdin, str):
+        stdin = encode_text_stream(stdin)
     return write_pipe(real_cmd, stdin)
 
 def read_pipe_full(c):
@@ -219,15 +301,17 @@ def read_pipe_full(c):
     if verbose:
         sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    expand = isinstance(c,basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
     (out, err) = p.communicate()
-    return (p.returncode, out, err)
+    return (p.returncode, out, decode_text_stream(err))
 
-def read_pipe(c, ignore_error=False):
+def read_pipe(c, ignore_error=False, raw=False):
     """ Read output from  command. Returns the output text on
         success. On failure, terminates execution, unless
         ignore_error is True, when it returns an empty string.
+
+        If raw is True, do not attempt to decode output text.
     """
     (retcode, out, err) = read_pipe_full(c)
     if retcode != 0:
@@ -235,6 +319,8 @@ def read_pipe(c, ignore_error=False):
             out = ""
         else:
             die('Command failed: %s\nError: %s' % (str(c), err))
+    if not raw:
+        out = decode_text_stream(out)
     return out
 
 def read_pipe_text(c):
@@ -245,23 +331,22 @@ def read_pipe_text(c):
     if retcode != 0:
         return None
     else:
-        return out.rstrip()
+        return decode_text_stream(out).rstrip()
 
-def p4_read_pipe(c, ignore_error=False):
+def p4_read_pipe(c, ignore_error=False, raw=False):
     real_cmd = p4_build_cmd(c)
-    return read_pipe(real_cmd, ignore_error)
+    return read_pipe(real_cmd, ignore_error, raw=raw)
 
 def read_pipe_lines(c):
     if verbose:
         sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    expand = isinstance(c, basestring)
+    expand = not isinstance(c, list)
     p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
     pipe = p.stdout
-    val = pipe.readlines()
+    val = [decode_text_stream(line) for line in pipe.readlines()]
     if pipe.close() or p.wait():
         die('Command failed: %s' % str(c))
-
     return val
 
 def p4_read_pipe_lines(c):
@@ -289,6 +374,7 @@ def p4_has_move_command():
     cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     (out, err) = p.communicate()
+    err = decode_text_stream(err)
     # return code will be 1 in either case
     if err.find("Invalid option") >= 0:
         return False
@@ -298,7 +384,7 @@ def p4_has_move_command():
     return True
 
 def system(cmd, ignore_error=False):
-    expand = isinstance(cmd,basestring)
+    expand = not isinstance(cmd, list)
     if verbose:
         sys.stderr.write("executing %s\n" % str(cmd))
     retcode = subprocess.call(cmd, shell=expand)
@@ -310,7 +396,7 @@ def system(cmd, ignore_error=False):
 def p4_system(cmd):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
-    expand = isinstance(real_cmd, basestring)
+    expand = not isinstance(real_cmd, list)
     retcode = subprocess.call(real_cmd, shell=expand)
     if retcode:
         raise CalledProcessError(retcode, real_cmd)
@@ -548,7 +634,7 @@ def getP4OpenedType(file):
 # Return the set of all p4 labels
 def getP4Labels(depotPaths):
     labels = set()
-    if isinstance(depotPaths,basestring):
+    if not isinstance(depotPaths, list):
         depotPaths = [depotPaths]
 
     for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
@@ -565,12 +651,7 @@ def getGitTags():
         gitTags.add(tag)
     return gitTags
 
-def diffTreePattern():
-    # This is a simple generator for the diff tree regex pattern. This could be
-    # a class variable if this and parseDiffTreeEntry were a part of a class.
-    pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
-    while True:
-        yield pattern
+_diff_tree_pattern = None
 
 def parseDiffTreeEntry(entry):
     """Parses a single diff tree entry into its component elements.
@@ -591,7 +672,11 @@ def parseDiffTreeEntry(entry):
 
     If the pattern is not matched, None is returned."""
 
-    match = diffTreePattern().next().match(entry)
+    global _diff_tree_pattern
+    if not _diff_tree_pattern:
+        _diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+
+    match = _diff_tree_pattern.match(entry)
     if match:
         return {
             'src_mode': match.group(1),
@@ -643,7 +728,7 @@ def isModeExecChanged(src_mode, dst_mode):
 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
         errors_as_exceptions=False):
 
-    if isinstance(cmd,basestring):
+    if not isinstance(cmd, list):
         cmd = "-G " + cmd
         expand = True
     else:
@@ -660,11 +745,12 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
     stdin_file = None
     if stdin is not None:
         stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
-        if isinstance(stdin,basestring):
+        if not isinstance(stdin, list):
             stdin_file.write(stdin)
         else:
             for i in stdin:
-                stdin_file.write(i + '\n')
+                stdin_file.write(encode_text_stream(i))
+                stdin_file.write(b'\n')
         stdin_file.flush()
         stdin_file.seek(0)
 
@@ -677,6 +763,20 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
     try:
         while True:
             entry = marshal.load(p4.stdout)
+            if bytes is not str:
+                # Decode unmarshalled dict to use str keys and values, except for:
+                #   - `data` which may contain arbitrary binary data
+                #   - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text
+                decoded_entry = {}
+                for key, value in entry.items():
+                    key = key.decode()
+                    if isinstance(value, bytes) and not (key in ('data', 'path', 'clientFile') or key.startswith('depotFile')):
+                        value = value.decode()
+                    decoded_entry[key] = value
+                # Parse out data if it's an error response
+                if decoded_entry.get('code') == 'error' and 'data' in decoded_entry:
+                    decoded_entry['data'] = decoded_entry['data'].decode()
+                entry = decoded_entry
             if skip_info:
                 if 'code' in entry and entry['code'] == 'info':
                     continue
@@ -727,7 +827,8 @@ def p4Where(depotPath):
         if "depotFile" in entry:
             # Search for the base client side depot path, as long as it starts with the branch's P4 path.
             # The base path always ends with "/...".
-            if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
+            entry_path = decode_path(entry['depotFile'])
+            if entry_path.find(depotPath) == 0 and entry_path[-4:] == "/...":
                 output = entry
                 break
         elif "data" in entry:
@@ -742,11 +843,11 @@ def p4Where(depotPath):
         return ""
     clientPath = ""
     if "path" in output:
-        clientPath = output.get("path")
+        clientPath = decode_path(output['path'])
     elif "data" in output:
         data = output.get("data")
-        lastSpace = data.rfind(" ")
-        clientPath = data[lastSpace + 1:]
+        lastSpace = data.rfind(b" ")
+        clientPath = decode_path(data[lastSpace + 1:])
 
     if clientPath.endswith("..."):
         clientPath = clientPath[:-3]
@@ -894,6 +995,7 @@ def branch_exists(branch):
     cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, _ = p.communicate()
+    out = decode_text_stream(out)
     if p.returncode:
         return False
     # expect exactly one line of output: the branch name
@@ -1171,7 +1273,7 @@ class LargeFileSystem(object):
         assert False, "Method 'pushFile' required in " + self.__class__.__name__
 
     def hasLargeFileExtension(self, relPath):
-        return reduce(
+        return functools.reduce(
             lambda a, b: a or b,
             [relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
             False
@@ -1278,7 +1380,7 @@ class GitLFS(LargeFileSystem):
             ['git', 'lfs', 'pointer', '--file=' + contentFile],
             stdout=subprocess.PIPE
         )
-        pointerFile = pointerProcess.stdout.read()
+        pointerFile = decode_text_stream(pointerProcess.stdout.read())
         if pointerProcess.wait():
             os.remove(contentFile)
             die('git-lfs pointer command failed. Did you install the extension?')
@@ -1414,14 +1516,14 @@ class P4UserMap:
         for (key, val) in self.users.items():
             s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
 
-        open(self.getUserCacheFilename(), "wb").write(s)
+        open(self.getUserCacheFilename(), 'w').write(s)
         self.userMapFromPerforceServer = True
 
     def loadUserMapFromCache(self):
         self.users = {}
         self.userMapFromPerforceServer = False
         try:
-            cache = open(self.getUserCacheFilename(), "rb")
+            cache = open(self.getUserCacheFilename(), 'r')
             lines = cache.readlines()
             cache.close()
             for line in lines:
@@ -1536,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 = ""
@@ -1560,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.")
@@ -1698,7 +1827,8 @@ class P4Submit(Command, P4UserMap):
         c = changes[0]
         if c['User'] == newUser: return   # nothing to do
         c['User'] = newUser
-        input = marshal.dumps(c)
+        # p4 does not understand format version 3 and above
+        input = marshal.dumps(c, 2)
 
         result = p4CmdList("change -f -i", stdin=input)
         for r in result:
@@ -1762,7 +1892,7 @@ class P4Submit(Command, P4UserMap):
                 break
         if not change_entry:
             die('Failed to decode output of p4 change -o')
-        for key, value in change_entry.iteritems():
+        for key, value in change_entry.items():
             if key.startswith('File'):
                 if 'depot-paths' in settings:
                     if not [p for p in settings['depot-paths']
@@ -1946,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
@@ -1985,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:
@@ -2042,58 +2176,76 @@ class P4Submit(Command, P4UserMap):
         tmpFile = os.fdopen(handle, "w+b")
         if self.isWindows:
             submitTemplate = submitTemplate.replace("\n", "\r\n")
-        tmpFile.write(submitTemplate)
+        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 = tmpFile.read()
+                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)
@@ -2116,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
@@ -2353,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
@@ -2509,7 +2668,7 @@ class View(object):
 
     def convert_client_path(self, clientFile):
         # chop off //client/ part to make it relative
-        if not clientFile.startswith(self.client_prefix):
+        if not decode_path(clientFile).startswith(self.client_prefix):
             die("No prefix '%s' on clientFile '%s'" %
                 (self.client_prefix, clientFile))
         return clientFile[len(self.client_prefix):]
@@ -2518,7 +2677,7 @@ class View(object):
         """ Caching file paths by "p4 where" batch query """
 
         # List depot file paths exclude that already cached
-        fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
+        fileArgs = [f['path'] for f in files if decode_path(f['path']) not in self.client_spec_path_cache]
 
         if len(fileArgs) == 0:
             return  # All files in cache
@@ -2533,16 +2692,18 @@ class View(object):
             if "unmap" in res:
                 # it will list all of them, but only one not unmap-ped
                 continue
+            depot_path = decode_path(res['depotFile'])
             if gitConfigBool("core.ignorecase"):
-                res['depotFile'] = res['depotFile'].lower()
-            self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
+                depot_path = depot_path.lower()
+            self.client_spec_path_cache[depot_path] = self.convert_client_path(res["clientFile"])
 
         # not found files or unmap files set to ""
         for depotFile in fileArgs:
+            depotFile = decode_path(depotFile)
             if gitConfigBool("core.ignorecase"):
                 depotFile = depotFile.lower()
             if depotFile not in self.client_spec_path_cache:
-                self.client_spec_path_cache[depotFile] = ""
+                self.client_spec_path_cache[depotFile] = b''
 
     def map_in_client(self, depot_path):
         """Return the relative location in the client where this
@@ -2647,6 +2808,7 @@ class P4Sync(Command, P4UserMap):
     def checkpoint(self):
         self.gitStream.write("checkpoint\n\n")
         self.gitStream.write("progress checkpoint\n\n")
+        self.gitStream.flush()
         out = self.gitOutput.readline()
         if self.verbose:
             print("checkpoint finished: " + out)
@@ -2660,7 +2822,7 @@ class P4Sync(Command, P4UserMap):
             elif path.lower() == p.lower():
                 return False
         for p in self.depotPaths:
-            if p4PathStartsWith(path, p):
+            if p4PathStartsWith(path, decode_path(p)):
                 return True
         return False
 
@@ -2669,7 +2831,7 @@ class P4Sync(Command, P4UserMap):
         fnum = 0
         while "depotFile%s" % fnum in commit:
             path =  commit["depotFile%s" % fnum]
-            found = self.isPathWanted(path)
+            found = self.isPathWanted(decode_path(path))
             if not found:
                 fnum = fnum + 1
                 continue
@@ -2703,7 +2865,7 @@ class P4Sync(Command, P4UserMap):
         if self.useClientSpec:
             # branch detection moves files up a level (the branch name)
             # from what client spec interpretation gives
-            path = self.clientSpecDirs.map_in_client(path)
+            path = decode_path(self.clientSpecDirs.map_in_client(path))
             if self.detectBranches:
                 for b in self.knownBranches:
                     if p4PathStartsWith(path, b + "/"):
@@ -2737,14 +2899,15 @@ class P4Sync(Command, P4UserMap):
         branches = {}
         fnum = 0
         while "depotFile%s" % fnum in commit:
-            path =  commit["depotFile%s" % fnum]
+            raw_path = commit["depotFile%s" % fnum]
+            path = decode_path(raw_path)
             found = self.isPathWanted(path)
             if not found:
                 fnum = fnum + 1
                 continue
 
             file = {}
-            file["path"] = path
+            file["path"] = raw_path
             file["rev"] = commit["rev%s" % fnum]
             file["action"] = commit["action%s" % fnum]
             file["type"] = commit["type%s" % fnum]
@@ -2753,7 +2916,7 @@ class P4Sync(Command, P4UserMap):
             # start with the full relative path where this file would
             # go in a p4 client
             if self.useClientSpec:
-                relPath = self.clientSpecDirs.map_in_client(path)
+                relPath = decode_path(self.clientSpecDirs.map_in_client(path))
             else:
                 relPath = self.stripRepoPath(path, self.depotPaths)
 
@@ -2769,7 +2932,7 @@ class P4Sync(Command, P4UserMap):
         return branches
 
     def writeToGitStream(self, gitMode, relPath, contents):
-        self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
+        self.gitStream.write(encode_text_stream(u'M {} inline {}\n'.format(gitMode, relPath)))
         self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
         for d in contents:
             self.gitStream.write(d)
@@ -2791,14 +2954,15 @@ class P4Sync(Command, P4UserMap):
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
-        relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
-        relPath = self.encodeWithUTF8(relPath)
+        file_path = file['depotFile']
+        relPath = self.stripRepoPath(decode_path(file_path), self.branchPrefixes)
+
         if verbose:
             if 'fileSize' in self.stream_file:
                 size = int(self.stream_file['fileSize'])
             else:
                 size = 0 # deleted files don't get a fileSize apparently
-            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
+            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file_path, relPath, size/1024/1024))
             sys.stdout.flush()
 
         (type_base, type_mods) = split_p4_type(file["type"])
@@ -2810,13 +2974,13 @@ class P4Sync(Command, P4UserMap):
             git_mode = "120000"
             # p4 print on a symlink sometimes contains "target\n";
             # if it does, remove the newline
-            data = ''.join(contents)
+            data = ''.join(decode_text_stream(c) for c in contents)
             if not data:
                 # Some version of p4 allowed creating a symlink that pointed
                 # to nothing.  This causes p4 errors when checking out such
                 # a change, and errors here too.  Work around it by ignoring
                 # the bad symlink; hopefully a future change fixes it.
-                print("\nIgnoring empty symlink in %s" % file['depotFile'])
+                print("\nIgnoring empty symlink in %s" % file_path)
                 return
             elif data[-1] == '\n':
                 contents = [data[:-1]]
@@ -2835,7 +2999,7 @@ class P4Sync(Command, P4UserMap):
             # just the native "NT" type.
             #
             try:
-                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
+                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (decode_path(file['depotFile']), file['change'])], raw=True)
             except Exception as e:
                 if 'Translation of file content failed' in str(e):
                     type_base = 'binary'
@@ -2843,7 +3007,7 @@ class P4Sync(Command, P4UserMap):
                     raise e
             else:
                 if p4_version_string().find('/NT') >= 0:
-                    text = text.replace('\r\n', '\n')
+                    text = text.replace(b'\r\n', b'\n')
                 contents = [ text ]
 
         if type_base == "apple":
@@ -2864,7 +3028,7 @@ class P4Sync(Command, P4UserMap):
         pattern = p4_keywords_regexp_for_type(type_base, type_mods)
         if pattern:
             regexp = re.compile(pattern, re.VERBOSE)
-            text = ''.join(contents)
+            text = ''.join(decode_text_stream(c) for c in contents)
             text = regexp.sub(r'$\1$', text)
             contents = [ text ]
 
@@ -2874,12 +3038,11 @@ class P4Sync(Command, P4UserMap):
         self.writeToGitStream(git_mode, relPath, contents)
 
     def streamOneP4Deletion(self, file):
-        relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
-        relPath = self.encodeWithUTF8(relPath)
+        relPath = self.stripRepoPath(decode_path(file['path']), self.branchPrefixes)
         if verbose:
             sys.stdout.write("delete %s\n" % relPath)
             sys.stdout.flush()
-        self.gitStream.write("D %s\n" % relPath)
+        self.gitStream.write(encode_text_stream(u'D {}\n'.format(relPath)))
 
         if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
             self.largeFileSystem.removeLargeFile(relPath)
@@ -2979,9 +3142,9 @@ class P4Sync(Command, P4UserMap):
                 if 'shelved_cl' in f:
                     # Handle shelved CLs using the "p4 print file@=N" syntax to print
                     # the contents
-                    fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
+                    fileArg = f['path'] + encode_text_stream('@={}'.format(f['shelved_cl']))
                 else:
-                    fileArg = '%s#%s' % (f['path'], f['rev'])
+                    fileArg = f['path'] + encode_text_stream('#{}'.format(f['rev']))
 
                 fileArgs.append(fileArg)
 
@@ -3062,8 +3225,8 @@ class P4Sync(Command, P4UserMap):
         if self.clientSpecDirs:
             self.clientSpecDirs.update_client_spec_path_cache(files)
 
-        files = [f for f in files
-            if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
+        files = [f for (f, path) in ((f, decode_path(f['path'])) for f in files)
+            if self.inClientSpec(path) and self.hasBranchPrefix(path)]
 
         if gitConfigBool('git-p4.keepEmptyCommits'):
             allow_empty = True
@@ -3635,6 +3798,15 @@ class P4Sync(Command, P4UserMap):
         self.gitStream = self.importProcess.stdin
         self.gitError = self.importProcess.stderr
 
+        if bytes is not str:
+            # Wrap gitStream.write() so that it can be called using `str` arguments
+            def make_encoded_write(write):
+                def encoded_write(s):
+                    return write(s.encode() if isinstance(s, str) else s)
+                return encoded_write
+
+            self.gitStream.write = make_encoded_write(self.gitStream.write)
+
     def closeStreams(self):
         if self.gitStream is None:
             return
@@ -4160,7 +4332,6 @@ commands = {
     "unshelve" : P4Unshelve,
 }
 
-
 def main():
     if len(sys.argv[1:]) == 0:
         printUsage(commands.keys())
index 89f915cae99b12d134aeb7e72ec460dfe1e8e247..39ebdf25b5f7128dee92adda8311c83a3e605b4f 100755 (executable)
@@ -48,6 +48,8 @@ depth=
 progress=
 dissociate=
 single_branch=
+jobs=
+recommend_shallow=
 
 die_if_unmatched ()
 {
@@ -803,27 +805,7 @@ cmd_set_url() {
                shift
        done
 
-       if test $# -ne 2
-       then
-               usage
-       fi
-
-       # we can't use `git submodule--helper name` here because internally, it
-       # hashes the path so a trailing slash could lead to an unintentional no match
-       name="$(git submodule--helper list "$1" | cut -f2)"
-       if test -z "$name"
-       then
-               exit 1
-       fi
-
-       url="$2"
-       if test -z "$url"
-       then
-               exit 1
-       fi
-
-       git submodule--helper config submodule."$name".url "$url"
-       git submodule--helper sync ${GIT_QUIET:+--quiet} "$name"
+       git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@"
 }
 
 #
diff --git a/git.c b/git.c
index 7be7ad34bd053884ec48923706e70c81719a8660..a2d337eed77c13bf43ffde57642af9c1ea68b2db 100644 (file)
--- a/git.c
+++ b/git.c
@@ -4,6 +4,7 @@
 #include "help.h"
 #include "run-command.h"
 #include "alias.h"
+#include "shallow.h"
 
 #define RUN_SETUP              (1<<0)
 #define RUN_SETUP_GENTLY       (1<<1)
@@ -351,6 +352,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 +390,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 +442,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);
@@ -574,12 +578,7 @@ static struct cmd_struct commands[] = {
        { "show-ref", cmd_show_ref, RUN_SETUP },
        { "sparse-checkout", cmd_sparse_checkout, RUN_SETUP | NEED_WORK_TREE },
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
-       /*
-        * NEEDSWORK: Until the builtin stash is thoroughly robust and no
-        * longer needs redirection to the stash shell script this is kept as
-        * is, then should be changed to RUN_SETUP | NEED_WORK_TREE
-        */
-       { "stash", cmd_stash },
+       { "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
        { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT },
index 65a3a9e62e809b1a74722536e7b2adc8389c595e..0959a782eccb60f1358f7abf5db534058c37e69e 100755 (executable)
@@ -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
index 165274d74a84a8f30b21a049ebcbccf8aaecc6fb..2d538bcd6e30d1314f76d89764284f8735cd187f 100644 (file)
@@ -256,6 +256,55 @@ error:
        FREE_AND_NULL(sigc->key);
 }
 
+static int verify_signed_buffer(const char *payload, size_t payload_size,
+                               const char *signature, size_t signature_size,
+                               struct strbuf *gpg_output,
+                               struct strbuf *gpg_status)
+{
+       struct child_process gpg = CHILD_PROCESS_INIT;
+       struct gpg_format *fmt;
+       struct tempfile *temp;
+       int ret;
+       struct strbuf buf = STRBUF_INIT;
+
+       temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
+       if (!temp)
+               return error_errno(_("could not create temporary file"));
+       if (write_in_full(temp->fd, signature, signature_size) < 0 ||
+           close_tempfile_gently(temp) < 0) {
+               error_errno(_("failed writing detached signature to '%s'"),
+                           temp->filename.buf);
+               delete_tempfile(&temp);
+               return -1;
+       }
+
+       fmt = get_format_by_sig(signature);
+       if (!fmt)
+               BUG("bad signature '%s'", signature);
+
+       argv_array_push(&gpg.args, fmt->program);
+       argv_array_pushv(&gpg.args, fmt->verify_args);
+       argv_array_pushl(&gpg.args,
+                        "--status-fd=1",
+                        "--verify", temp->filename.buf, "-",
+                        NULL);
+
+       if (!gpg_status)
+               gpg_status = &buf;
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+       ret = pipe_command(&gpg, payload, payload_size,
+                          gpg_status, 0, gpg_output, 0);
+       sigchain_pop(SIGPIPE);
+
+       delete_tempfile(&temp);
+
+       ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
+       strbuf_release(&buf); /* no matter it was used or not */
+
+       return ret;
+}
+
 int check_signature(const char *payload, size_t plen, const char *signature,
        size_t slen, struct signature_check *sigc)
 {
@@ -418,51 +467,3 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 
        return 0;
 }
-
-int verify_signed_buffer(const char *payload, size_t payload_size,
-                        const char *signature, size_t signature_size,
-                        struct strbuf *gpg_output, struct strbuf *gpg_status)
-{
-       struct child_process gpg = CHILD_PROCESS_INIT;
-       struct gpg_format *fmt;
-       struct tempfile *temp;
-       int ret;
-       struct strbuf buf = STRBUF_INIT;
-
-       temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
-       if (!temp)
-               return error_errno(_("could not create temporary file"));
-       if (write_in_full(temp->fd, signature, signature_size) < 0 ||
-           close_tempfile_gently(temp) < 0) {
-               error_errno(_("failed writing detached signature to '%s'"),
-                           temp->filename.buf);
-               delete_tempfile(&temp);
-               return -1;
-       }
-
-       fmt = get_format_by_sig(signature);
-       if (!fmt)
-               BUG("bad signature '%s'", signature);
-
-       argv_array_push(&gpg.args, fmt->program);
-       argv_array_pushv(&gpg.args, fmt->verify_args);
-       argv_array_pushl(&gpg.args,
-                        "--status-fd=1",
-                        "--verify", temp->filename.buf, "-",
-                        NULL);
-
-       if (!gpg_status)
-               gpg_status = &buf;
-
-       sigchain_push(SIGPIPE, SIG_IGN);
-       ret = pipe_command(&gpg, payload, payload_size,
-                          gpg_status, 0, gpg_output, 0);
-       sigchain_pop(SIGPIPE);
-
-       delete_tempfile(&temp);
-
-       ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
-       strbuf_release(&buf); /* no matter it was used or not */
-
-       return ret;
-}
index 796571e9e90fa50bc6f368bdeefacc8e86a9c520..f4e9b4f3715a0b0d4be3ef526388bf6b87b56fda 100644 (file)
@@ -54,15 +54,6 @@ size_t parse_signature(const char *buf, size_t size);
 int sign_buffer(struct strbuf *buffer, struct strbuf *signature,
                const char *signing_key);
 
-/*
- * Run "gpg" to see if the payload matches the detached signature.
- * gpg_output, when set, receives the diagnostic output from GPG.
- * gpg_status, when set, receives the status output from GPG.
- */
-int verify_signed_buffer(const char *payload, size_t payload_size,
-                        const char *signature, size_t signature_size,
-                        struct strbuf *gpg_output, struct strbuf *gpg_status);
-
 int git_gpg_config(const char *, const char *, void *);
 void set_signing_key(const char *);
 const char *get_signing_key(void);
diff --git a/graph.c b/graph.c
index 4fb25ad464db5778262936cdcc97b5088abc7678..4cd9915075ff23d8a5218609b06239b9d9f08435 100644 (file)
--- 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/hash.h b/hash.h
index 52a4f1a3f43089f02bbdcfb2759da479841c7405..e0f3f16b06b7930e448912bdc699a47125a19b0c 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -16,6 +16,7 @@
 #endif
 
 #if defined(SHA256_GCRYPT)
+#define SHA256_NEEDS_CLONE_HELPER
 #include "sha256/gcrypt.h"
 #elif defined(SHA256_OPENSSL)
 #include <openssl/sha.h>
 #define git_SHA256_Update      platform_SHA256_Update
 #define git_SHA256_Final       platform_SHA256_Final
 
+#ifdef platform_SHA256_Clone
+#define git_SHA256_Clone       platform_SHA256_Clone
+#endif
+
 #ifdef SHA1_MAX_BLOCK_SIZE
 #include "compat/sha1-chunked.h"
 #undef git_SHA1_Update
 #define git_SHA1_Update                git_SHA1_Update_Chunked
 #endif
 
+static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src)
+{
+       memcpy(dst, src, sizeof(*dst));
+}
+
+#ifndef SHA256_NEEDS_CLONE_HELPER
+static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *src)
+{
+       memcpy(dst, src, sizeof(*dst));
+}
+#endif
+
 /*
  * Note that these constants are suitable for indexing the hash_algos array and
  * comparing against each other, but are otherwise arbitrary, so they should not
@@ -85,6 +102,7 @@ union git_hash_ctx {
 typedef union git_hash_ctx git_hash_ctx;
 
 typedef void (*git_hash_init_fn)(git_hash_ctx *ctx);
+typedef void (*git_hash_clone_fn)(git_hash_ctx *dst, const git_hash_ctx *src);
 typedef void (*git_hash_update_fn)(git_hash_ctx *ctx, const void *in, size_t len);
 typedef void (*git_hash_final_fn)(unsigned char *hash, git_hash_ctx *ctx);
 
@@ -110,6 +128,9 @@ struct git_hash_algo {
        /* The hash initialization function. */
        git_hash_init_fn init_fn;
 
+       /* The hash context cloning function. */
+       git_hash_clone_fn clone_fn;
+
        /* The hash update function. */
        git_hash_update_fn update_fn;
 
diff --git a/help.c b/help.c
index cf67624a94bc47ad7d373d64a1d3b20f562c8fc1..1de9c0d589cd9b615b1b20d60e06b9601d08fe8f 100644 (file)
--- 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 7a455beeb725e267946b1ac2c4d91ba9fee2a8c1..500521b9081c3725cdfcc252ef30394af40a6943 100644 (file)
--- 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/hex.c b/hex.c
index fd7f00c43f969a30a76fe8ac8bf2255ac0320676..da51e64929a4f4973406d001a66e5ec72e76e628 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -47,32 +47,73 @@ int hex_to_bytes(unsigned char *binary, const char *hex, size_t len)
        return 0;
 }
 
-int get_sha1_hex(const char *hex, unsigned char *sha1)
+static int get_hash_hex_algop(const char *hex, unsigned char *hash,
+                             const struct git_hash_algo *algop)
 {
        int i;
-       for (i = 0; i < the_hash_algo->rawsz; i++) {
+       for (i = 0; i < algop->rawsz; i++) {
                int val = hex2chr(hex);
                if (val < 0)
                        return -1;
-               *sha1++ = val;
+               *hash++ = val;
                hex += 2;
        }
        return 0;
 }
 
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+       return get_hash_hex_algop(hex, sha1, the_hash_algo);
+}
+
+int get_oid_hex_algop(const char *hex, struct object_id *oid,
+                     const struct git_hash_algo *algop)
+{
+       return get_hash_hex_algop(hex, oid->hash, algop);
+}
+
+/*
+ * NOTE: This function relies on hash algorithms being in order from shortest
+ * length to longest length.
+ */
+int get_oid_hex_any(const char *hex, struct object_id *oid)
+{
+       int i;
+       for (i = GIT_HASH_NALGOS - 1; i > 0; i--) {
+               if (!get_hash_hex_algop(hex, oid->hash, &hash_algos[i]))
+                       return i;
+       }
+       return GIT_HASH_UNKNOWN;
+}
+
 int get_oid_hex(const char *hex, struct object_id *oid)
 {
-       return get_sha1_hex(hex, oid->hash);
+       return get_oid_hex_algop(hex, oid, the_hash_algo);
 }
 
-int parse_oid_hex(const char *hex, struct object_id *oid, const char **end)
+int parse_oid_hex_algop(const char *hex, struct object_id *oid,
+                       const char **end,
+                       const struct git_hash_algo *algop)
 {
-       int ret = get_oid_hex(hex, oid);
+       int ret = get_hash_hex_algop(hex, oid->hash, algop);
        if (!ret)
-               *end = hex + the_hash_algo->hexsz;
+               *end = hex + algop->hexsz;
        return ret;
 }
 
+int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end)
+{
+       int ret = get_oid_hex_any(hex, oid);
+       if (ret)
+               *end = hex + hash_algos[ret].hexsz;
+       return ret;
+}
+
+int parse_oid_hex(const char *hex, struct object_id *oid, const char **end)
+{
+       return parse_oid_hex_algop(hex, oid, end, the_hash_algo);
+}
+
 char *hash_to_hex_algop_r(char *buffer, const unsigned char *hash,
                          const struct git_hash_algo *algop)
 {
diff --git a/http.c b/http.c
index 5f712634825ee7a9a10c344f889ca262f94f6407..62aa995245324dc30fec699ed8c570af7b152b8f 100644 (file)
--- a/http.c
+++ b/http.c
@@ -86,6 +86,13 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static const char *http_proxy_authmethod;
+
+static const char *http_proxy_ssl_cert;
+static const char *http_proxy_ssl_key;
+static const char *http_proxy_ssl_ca_info;
+static struct credential proxy_cert_auth = CREDENTIAL_INIT;
+static int proxy_ssl_cert_password_required;
+
 static struct {
        const char *name;
        long curlauth_param;
@@ -365,6 +372,20 @@ static int http_options(const char *var, const char *value, void *cb)
        if (!strcmp("http.proxyauthmethod", var))
                return git_config_string(&http_proxy_authmethod, var, value);
 
+       if (!strcmp("http.proxysslcert", var))
+               return git_config_string(&http_proxy_ssl_cert, var, value);
+
+       if (!strcmp("http.proxysslkey", var))
+               return git_config_string(&http_proxy_ssl_key, var, value);
+
+       if (!strcmp("http.proxysslcainfo", var))
+               return git_config_string(&http_proxy_ssl_ca_info, var, value);
+
+       if (!strcmp("http.proxysslcertpasswordprotected", var)) {
+               proxy_ssl_cert_password_required = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp("http.cookiefile", var))
                return git_config_pathname(&curl_cookie_file, var, value);
        if (!strcmp("http.savecookies", var)) {
@@ -566,6 +587,22 @@ static int has_cert_password(void)
        return 1;
 }
 
+#if LIBCURL_VERSION_NUM >= 0x073400
+static int has_proxy_cert_password(void)
+{
+       if (http_proxy_ssl_cert == NULL || proxy_ssl_cert_password_required != 1)
+               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);
+       }
+       return 1;
+}
+#endif
+
 #if LIBCURL_VERSION_NUM >= 0x071900
 static void set_curl_keepalive(CURL *c)
 {
@@ -925,8 +962,14 @@ static CURL *get_curl_handle(void)
 #if LIBCURL_VERSION_NUM >= 0x073400
                curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL);
 #endif
-       } else if (ssl_cainfo != NULL)
-               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       } else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) {
+               if (ssl_cainfo != NULL)
+                       curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+#if LIBCURL_VERSION_NUM >= 0x073400
+               if (http_proxy_ssl_ca_info != NULL)
+                       curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
+#endif
+       }
 
        if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
                curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
@@ -1019,9 +1062,18 @@ static CURL *get_curl_handle(void)
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
 #endif
 #if LIBCURL_VERSION_NUM >= 0x073400
-               else if (starts_with(curl_http_proxy, "https"))
-                       curl_easy_setopt(result,
-                               CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
+               else if (starts_with(curl_http_proxy, "https")) {
+                       curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
+
+                       if (http_proxy_ssl_cert)
+                               curl_easy_setopt(result, CURLOPT_PROXY_SSLCERT, http_proxy_ssl_cert);
+
+                       if (http_proxy_ssl_key)
+                               curl_easy_setopt(result, CURLOPT_PROXY_SSLKEY, http_proxy_ssl_key);
+
+                       if (has_proxy_cert_password())
+                               curl_easy_setopt(result, CURLOPT_PROXY_KEYPASSWD, proxy_cert_auth.password);
+               }
 #endif
                if (strstr(curl_http_proxy, "://"))
                        credential_from_url(&proxy_auth, curl_http_proxy);
@@ -1161,6 +1213,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
                max_requests = DEFAULT_MAX_REQUESTS;
 #endif
 
+       set_from_env(&http_proxy_ssl_cert, "GIT_PROXY_SSL_CERT");
+       set_from_env(&http_proxy_ssl_key, "GIT_PROXY_SSL_KEY");
+       set_from_env(&http_proxy_ssl_ca_info, "GIT_PROXY_SSL_CAINFO");
+
+       if (getenv("GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED"))
+               proxy_ssl_cert_password_required = 1;
+
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
@@ -1231,6 +1290,12 @@ void http_cleanup(void)
        }
        ssl_cert_password_required = 0;
 
+       if (proxy_cert_auth.password != NULL) {
+               memset(proxy_cert_auth.password, 0, strlen(proxy_cert_auth.password));
+               FREE_AND_NULL(proxy_cert_auth.password);
+       }
+       proxy_ssl_cert_password_required = 0;
+
        FREE_AND_NULL(cached_accept_language);
 }
 
index 9010e00950b379501a6607d660497e94f8412d95..40e1738dbb3817aaf59df68a7c967c43548f4f2f 100644 (file)
@@ -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 {
index 2ffb39222c49745f68134fe6e360ba8cc00f15cc..73fffa4ad746a31a4a798d05f38452f1916b365d 100644 (file)
@@ -82,9 +82,9 @@ int opt_parse_list_objects_filter(const struct option *opt,
                                  const char *arg, int unset);
 
 #define OPT_PARSE_LIST_OBJECTS_FILTER(fo) \
-       { OPTION_CALLBACK, 0, CL_ARG__FILTER, fo, N_("args"), \
-         N_("object filtering"), 0, \
-         opt_parse_list_objects_filter }
+       OPT_CALLBACK(0, CL_ARG__FILTER, fo, N_("args"), \
+         N_("object filtering"), \
+         opt_parse_list_objects_filter)
 
 /*
  * Translates abbreviated numbers in the filter's filter_spec into their
index 1e8d4e763da07377263086d252d1228004971383..0a3ef3cab3ac31abb679a18f048798e7e4ac54ea 100644 (file)
@@ -663,6 +663,9 @@ struct filter *list_objects_filter__init(
 
        assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT);
 
+       if (!filter_options)
+               return NULL;
+
        if (filter_options->choice >= LOFC__COUNT)
                BUG("invalid list-objects filter choice: %d",
                    filter_options->choice);
index d65a8971db732782822feede96e02f86d336e547..1ec0b959e015b20442aaddc417952f7a7a731d42 100644 (file)
@@ -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);
index 8e8ab4f29f3eaf05f056211042b5fd2f01727363..cc9a4b84283be34cdcd72770d77e2ef1adb998a6 100644 (file)
@@ -70,7 +70,8 @@ static void resolve_symlink(struct strbuf *path)
 }
 
 /* Make sure errno contains a meaningful value on error */
-static int lock_file(struct lock_file *lk, const char *path, int flags)
+static int lock_file(struct lock_file *lk, const char *path, int flags,
+                    int mode)
 {
        struct strbuf filename = STRBUF_INIT;
 
@@ -79,7 +80,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
                resolve_symlink(&filename);
 
        strbuf_addstr(&filename, LOCK_SUFFIX);
-       lk->tempfile = create_tempfile(filename.buf);
+       lk->tempfile = create_tempfile_mode(filename.buf, mode);
        strbuf_release(&filename);
        return lk->tempfile ? lk->tempfile->fd : -1;
 }
@@ -99,7 +100,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
  * exactly once. If timeout_ms is -1, try indefinitely.
  */
 static int lock_file_timeout(struct lock_file *lk, const char *path,
-                            int flags, long timeout_ms)
+                            int flags, long timeout_ms, int mode)
 {
        int n = 1;
        int multiplier = 1;
@@ -107,7 +108,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path,
        static int random_initialized = 0;
 
        if (timeout_ms == 0)
-               return lock_file(lk, path, flags);
+               return lock_file(lk, path, flags, mode);
 
        if (!random_initialized) {
                srand((unsigned int)getpid());
@@ -121,7 +122,7 @@ static int lock_file_timeout(struct lock_file *lk, const char *path,
                long backoff_ms, wait_ms;
                int fd;
 
-               fd = lock_file(lk, path, flags);
+               fd = lock_file(lk, path, flags, mode);
 
                if (fd >= 0)
                        return fd; /* success */
@@ -169,10 +170,11 @@ NORETURN void unable_to_lock_die(const char *path, int err)
 }
 
 /* This should return a meaningful errno on failure */
-int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path,
-                                     int flags, long timeout_ms)
+int hold_lock_file_for_update_timeout_mode(struct lock_file *lk,
+                                          const char *path, int flags,
+                                          long timeout_ms, int mode)
 {
-       int fd = lock_file_timeout(lk, path, flags, timeout_ms);
+       int fd = lock_file_timeout(lk, path, flags, timeout_ms, mode);
        if (fd < 0) {
                if (flags & LOCK_DIE_ON_ERROR)
                        unable_to_lock_die(path, errno);
index 9843053ce8940391834a86b122d1f64d5b345edf..db93e6ba73e68ddcd114da0c75e5217a33b5a395 100644 (file)
  * functions. In particular, the state diagram and the cleanup
  * machinery are all implemented in the tempfile module.
  *
+ * Permission bits
+ * ---------------
+ *
+ * If you call either `hold_lock_file_for_update_mode` or
+ * `hold_lock_file_for_update_timeout_mode`, you can specify a suggested
+ * mode for the underlying temporary file. Note that the file isn't
+ * guaranteed to have this exact mode, since it may be limited by either
+ * the umask, 'core.sharedRepository', or both. See `adjust_shared_perm`
+ * for more.
  *
  * Error handling
  * --------------
@@ -156,12 +165,20 @@ struct lock_file {
  * file descriptor for writing to it, or -1 on error. If the file is
  * currently locked, retry with quadratic backoff for at least
  * timeout_ms milliseconds. If timeout_ms is 0, try exactly once; if
- * timeout_ms is -1, retry indefinitely. The flags argument and error
- * handling are described above.
+ * timeout_ms is -1, retry indefinitely. The flags argument, error
+ * handling, and mode are described above.
  */
-int hold_lock_file_for_update_timeout(
+int hold_lock_file_for_update_timeout_mode(
+               struct lock_file *lk, const char *path,
+               int flags, long timeout_ms, int mode);
+
+static inline int hold_lock_file_for_update_timeout(
                struct lock_file *lk, const char *path,
-               int flags, long timeout_ms);
+               int flags, long timeout_ms)
+{
+       return hold_lock_file_for_update_timeout_mode(lk, path, flags,
+                                                     timeout_ms, 0666);
+}
 
 /*
  * Attempt to create a lockfile for the file at `path` and return a
@@ -175,6 +192,13 @@ static inline int hold_lock_file_for_update(
        return hold_lock_file_for_update_timeout(lk, path, flags, 0);
 }
 
+static inline int hold_lock_file_for_update_mode(
+               struct lock_file *lk, const char *path,
+               int flags, int mode)
+{
+       return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode);
+}
+
 /*
  * Return a nonzero value iff `lk` is currently locked.
  */
index 52127427ffeace62621a3761dc540a174671f4a5..55a68d0c6101a7a287c9544e1963427fd0b77793 100644 (file)
@@ -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;
@@ -449,22 +500,21 @@ static void show_signature(struct rev_info *opt, struct commit *commit)
 {
        struct strbuf payload = STRBUF_INIT;
        struct strbuf signature = STRBUF_INIT;
-       struct strbuf gpg_output = STRBUF_INIT;
+       struct signature_check sigc = { 0 };
        int status;
 
        if (parse_signed_commit(commit, &payload, &signature) <= 0)
                goto out;
 
-       status = verify_signed_buffer(payload.buf, payload.len,
-                                     signature.buf, signature.len,
-                                     &gpg_output, NULL);
-       if (status && !gpg_output.len)
-               strbuf_addstr(&gpg_output, "No signature\n");
-
-       show_sig_lines(opt, status, gpg_output.buf);
+       status = check_signature(payload.buf, payload.len, signature.buf,
+                                signature.len, &sigc);
+       if (status && !sigc.gpg_output)
+               show_sig_lines(opt, status, "No signature\n");
+       else
+               show_sig_lines(opt, status, sigc.gpg_output);
+       signature_check_clear(&sigc);
 
  out:
-       strbuf_release(&gpg_output);
        strbuf_release(&payload);
        strbuf_release(&signature);
 }
@@ -497,8 +547,9 @@ static int show_one_mergetag(struct commit *commit,
        struct object_id oid;
        struct tag *tag;
        struct strbuf verify_message;
+       struct signature_check sigc = { 0 };
        int status, nth;
-       size_t payload_size, gpg_message_offset;
+       size_t payload_size;
 
        hash_object_file(the_hash_algo, extra->value, extra->len,
                         type_name(OBJ_TAG), &oid);
@@ -520,19 +571,19 @@ static int show_one_mergetag(struct commit *commit,
        else
                strbuf_addf(&verify_message,
                            "parent #%d, tagged '%s'\n", nth + 1, tag->tag);
-       gpg_message_offset = verify_message.len;
 
        payload_size = parse_signature(extra->value, extra->len);
        status = -1;
        if (extra->len > payload_size) {
                /* could have a good signature */
-               if (!verify_signed_buffer(extra->value, payload_size,
-                                         extra->value + payload_size,
-                                         extra->len - payload_size,
-                                         &verify_message, NULL))
-                       status = 0; /* good */
-               else if (verify_message.len <= gpg_message_offset)
+               status = check_signature(extra->value, payload_size,
+                                        extra->value + payload_size,
+                                        extra->len - payload_size, &sigc);
+               if (sigc.gpg_output)
+                       strbuf_addstr(&verify_message, sigc.gpg_output);
+               else
                        strbuf_addstr(&verify_message, "No signature\n");
+               signature_check_clear(&sigc);
                /* otherwise we couldn't verify, which is shown as bad */
        }
 
@@ -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;
index e66862807463a12e28573be04cbcfcf2d200e6f3..8fa79289ec6b6cb27e68534845d345caf40137ea 100644 (file)
@@ -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);
index 818aef70a09e7cedc2f241f235302e69969b2e3b..50d86866c6eac5957951e5b685d7912fcf12eed7 100644 (file)
--- 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);
index 742fa376ab01142de8bb7410c37657a5ede4a391..5681d9130db6f54971cf2b966d1743b87a130b86 100644 (file)
@@ -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);
index 7a4e6f20fa8c8b163239d2872795e4d51a3d9b34..d92e2acf1ed2cfb4d4ce1e50fee3a64fb70b91ac 100644 (file)
@@ -958,7 +958,7 @@ static int update_file_flags(struct merge_options *opt,
                if (S_ISREG(contents->mode)) {
                        struct strbuf strbuf = STRBUF_INIT;
                        if (convert_to_working_tree(opt->repo->index,
-                                                   path, buf, size, &strbuf)) {
+                                                   path, buf, size, &strbuf, NULL)) {
                                free(buf);
                                size = strbuf.len;
                                buf = strbuf_detach(&strbuf, NULL);
diff --git a/merge.c b/merge.c
index 7c1d756c3fad6f9570b31c2c36f96e9f90df39f4..aa36de2f64a4e1195a274f5d50c471c6806d4db8 100644 (file)
--- a/merge.c
+++ b/merge.c
@@ -94,6 +94,7 @@ int checkout_fast_forward(struct repository *r,
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
+       init_checkout_metadata(&opts.meta, NULL, remote, NULL);
        setup_unpack_trees_porcelain(&opts, "merge");
 
        if (unpack_trees(nr_trees, t, &opts)) {
diff --git a/midx.c b/midx.c
index 1527e464a7b43e352f352dfc6c6f2b011e35986d..9a61d3b37d9a3607e6a53258760c9bfa7e732075 100644 (file)
--- 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 e6fa356b5caaf67451751c121325d74e79007f31..b18cf53bc4abf9650019aa7b4827696ea32056ac 100644 (file)
--- 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;
 
index be72fee7d557758a9e0efbaa13efeb2b6fb84276..d1e490f2035d2f48583a30eb7a44cc4613a662fd 100644 (file)
@@ -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"
 
index 2dbabfca0ab8185f52cca6d3b7c69658e838ae49..b22328b8383ef026df6ad05decb62c9af60ec6d5 100644 (file)
--- 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
similarity index 93%
rename from sha1-array.c
rename to oid-array.c
index 3eeadfede94b94ed017b9196ebf42041d1e30c74..8657a5cedfa68cbc3711d4b288e514978ff5b6df 100644 (file)
@@ -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++) {
similarity index 97%
rename from sha1-array.h
rename to oid-array.h
index dc1bca9c9aea6cdbf001b8d43d2e43b99c034d81..f28d322c9006553c1335b836cea9252f80bb1e96 100644 (file)
@@ -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;
 };
 
index f63ce818f67766378485ab16075dd11e87c00ca0..15d4e18c37353bf0f186155de78f1af322fd855c 100644 (file)
--- 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;
index 5346563b0bccb602b8de745d1308793e8995a5e5..209ae7a1736e49818acc1e05b7f3cb4aceada0fe 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -1,11 +1,10 @@
 #ifndef OIDSET_H
 #define OIDSET_H
 
-#include "hashmap.h"
 #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 +54,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.
index 49a8d10d0cf99716ea37b78bec578ca96c17de20..4077e731e800c52548243452e9a5cf715a8f43b0 100644 (file)
@@ -506,7 +506,8 @@ static int should_include(struct commit *commit, void *_data)
 static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
                                   struct rev_info *revs,
                                   struct object_list *roots,
-                                  struct bitmap *seen)
+                                  struct bitmap *seen,
+                                  struct list_objects_filter_options *filter)
 {
        struct bitmap *base = NULL;
        int needs_walk = 0;
@@ -599,8 +600,9 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
                show_data.bitmap_git = bitmap_git;
                show_data.base = base;
 
-               traverse_commit_list(revs, show_commit, show_object,
-                                    &show_data);
+               traverse_commit_list_filtered(filter, revs,
+                                             show_commit, show_object,
+                                             &show_data, NULL);
        }
 
        return base;
@@ -715,8 +717,9 @@ static int in_bitmapped_pack(struct bitmap_index *bitmap_git,
        return 0;
 }
 
-static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git,
-                                    struct object_list *tip_objects)
+static struct bitmap *find_tip_objects(struct bitmap_index *bitmap_git,
+                                      struct object_list *tip_objects,
+                                      enum object_type type)
 {
        struct bitmap *result = bitmap_new();
        struct object_list *p;
@@ -724,7 +727,7 @@ static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git,
        for (p = tip_objects; p; p = p->next) {
                int pos;
 
-               if (p->item->type != OBJ_BLOB)
+               if (p->item->type != type)
                        continue;
 
                pos = bitmap_position(bitmap_git, &p->item->oid);
@@ -737,9 +740,10 @@ static struct bitmap *find_tip_blobs(struct bitmap_index *bitmap_git,
        return result;
 }
 
-static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
-                                   struct object_list *tip_objects,
-                                   struct bitmap *to_filter)
+static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git,
+                                      struct object_list *tip_objects,
+                                      struct bitmap *to_filter,
+                                      enum object_type type)
 {
        struct eindex *eindex = &bitmap_git->ext_index;
        struct bitmap *tips;
@@ -747,18 +751,21 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
        eword_t mask;
        uint32_t i;
 
+       if (type != OBJ_BLOB && type != OBJ_TREE)
+               BUG("filter_bitmap_exclude_type: unsupported type '%d'", type);
+
        /*
         * The non-bitmap version of this filter never removes
-        * blobs which the other side specifically asked for,
+        * objects which the other side specifically asked for,
         * so we must match that behavior.
         */
-       tips = find_tip_blobs(bitmap_git, tip_objects);
+       tips = find_tip_objects(bitmap_git, tip_objects, type);
 
        /*
         * We can use the blob type-bitmap to work in whole words
         * for the objects that are actually in the bitmapped packfile.
         */
-       for (i = 0, init_type_iterator(&it, bitmap_git, OBJ_BLOB);
+       for (i = 0, init_type_iterator(&it, bitmap_git, type);
             i < to_filter->word_alloc && ewah_iterator_next(&mask, &it);
             i++) {
                if (i < tips->word_alloc)
@@ -773,7 +780,7 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
         */
        for (i = 0; i < eindex->count; i++) {
                uint32_t pos = i + bitmap_git->pack->num_objects;
-               if (eindex->objects[i]->type == OBJ_BLOB &&
+               if (eindex->objects[i]->type == type &&
                    bitmap_get(to_filter, pos) &&
                    !bitmap_get(tips, pos))
                        bitmap_unset(to_filter, pos);
@@ -782,6 +789,14 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
        bitmap_free(tips);
 }
 
+static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
+                                   struct object_list *tip_objects,
+                                   struct bitmap *to_filter)
+{
+       filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter,
+                                  OBJ_BLOB);
+}
+
 static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,
                                     uint32_t pos)
 {
@@ -820,7 +835,7 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git,
        eword_t mask;
        uint32_t i;
 
-       tips = find_tip_blobs(bitmap_git, tip_objects);
+       tips = find_tip_objects(bitmap_git, tip_objects, OBJ_BLOB);
 
        for (i = 0, init_type_iterator(&it, bitmap_git, OBJ_BLOB);
             i < to_filter->word_alloc && ewah_iterator_next(&mask, &it);
@@ -854,6 +869,20 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git,
        bitmap_free(tips);
 }
 
+static void filter_bitmap_tree_depth(struct bitmap_index *bitmap_git,
+                                    struct object_list *tip_objects,
+                                    struct bitmap *to_filter,
+                                    unsigned long limit)
+{
+       if (limit)
+               BUG("filter_bitmap_tree_depth given non-zero limit");
+
+       filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter,
+                                  OBJ_TREE);
+       filter_bitmap_exclude_type(bitmap_git, tip_objects, to_filter,
+                                  OBJ_BLOB);
+}
+
 static int filter_bitmap(struct bitmap_index *bitmap_git,
                         struct object_list *tip_objects,
                         struct bitmap *to_filter,
@@ -877,6 +906,15 @@ static int filter_bitmap(struct bitmap_index *bitmap_git,
                return 0;
        }
 
+       if (filter->choice == LOFC_TREE_DEPTH &&
+           filter->tree_exclude_depth == 0) {
+               if (bitmap_git)
+                       filter_bitmap_tree_depth(bitmap_git, tip_objects,
+                                                to_filter,
+                                                filter->tree_exclude_depth);
+               return 0;
+       }
+
        /* filter choice not handled */
        return -1;
 }
@@ -963,7 +1001,8 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
 
        if (haves) {
                revs->ignore_missing_links = 1;
-               haves_bitmap = find_objects(bitmap_git, revs, haves, NULL);
+               haves_bitmap = find_objects(bitmap_git, revs, haves, NULL,
+                                           filter);
                reset_revision_walk();
                revs->ignore_missing_links = 0;
 
@@ -971,7 +1010,8 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
                        BUG("failed to perform bitmap walk");
        }
 
-       wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap);
+       wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap,
+                                   filter);
 
        if (!wants_bitmap)
                BUG("failed to perform bitmap walk");
index a28b55be48dc84582f18df446b1eb36f26b17167..86cd3930136e269ca68759b0113875e0a8e80e8a 100644 (file)
@@ -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 -----*/
 
index 63d6bab60c0ef7ea4a836c6c8dc3f70daf8516d4..c57618d53736c754cee32b341f7c97d881c3036c 100644 (file)
@@ -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;
                }
 
index fece5ba628e5f15756a95608c9254d02458123d0..46af94209305529253c0308a69db9caabd8b6624 100644 (file)
@@ -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 88cf59300738a2f10750ff458fa35b1211f00ab8..8b2c7531919eef89b340b3323d1cbd806ba57b0c 100644 (file)
--- a/path.c
+++ b/path.c
@@ -723,7 +723,7 @@ static struct passwd *getpw_str(const char *username, size_t len)
  * then it is a newly allocated string. Returns NULL on getpw failure or
  * if path is NULL.
  *
- * If real_home is true, real_path($HOME) is used in the expansion.
+ * If real_home is true, strbuf_realpath($HOME) is used in the expansion.
  */
 char *expand_user_path(const char *path, int real_home)
 {
@@ -850,8 +850,8 @@ const char *enter_repo(const char *path, int strict)
        }
 
        if (is_git_directory(".")) {
-               set_git_dir(".");
-               check_repository_format();
+               set_git_dir(".", 0);
+               check_repository_format(NULL);
                return path;
        }
 
@@ -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 14d6dcad161e3720629ed60d1ec7e4a4b3e34fdd..1f1bf8f87a86d4444879d3fdc0a30076b2c111f3 100644 (file)
--- 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);
 
index 28afc701b6a46d1aca84d7867549391d79a6ab68..2a3d46bf42fea1d375eb1b29e52ee1caaa0372a0 100644 (file)
--- 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
index 4ad1fc31ff33408881abf2ea5e28ab813d6c1f85..071f2fb8e449cee51c6556998b416c4fa7c8005a 100644 (file)
--- 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
index 19805ac6461ba3b1300e542989189f76738c1407..75633e9c5e91a959e624ea38898933ea20d1daaa 100644 (file)
@@ -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;
index 847338911fbb6cf359f39ea0cb7a406297b61812..f1913acf73f1790366ebd2f3f802f0472221bc7c 100644 (file)
@@ -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);
index 9f338c945faf997a11d2a55065ddbc14d8be6cdc..baaea12fd69b3b2c86f9fe5308a06c65e2eff3eb 100644 (file)
@@ -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) {
index 737bac3a330e22fe11058d5667cdb3f199c0484a..6343c47d1830f947aae456ed48e305faef26b6c0 100644 (file)
@@ -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);
index 6d5885d0096407d7d12f4511100ebee5cb5f752d..5ded21a017f1089c5a5f63c998c3b11631c0a5d6 100644 (file)
--- 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;
+}
index e04cced030ca4df6d729c2132ca4671b959952b4..e294e93541cfa0749cfb60fdceac2ebd3f41751d 100644 (file)
--- 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 */
index 803bef5c87e00617537d3ef1fa04204b7350afb4..d390391ebac80a7b9d506c592bbd32b24f9f250f 100644 (file)
@@ -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 (file)
index 0000000..261520b
--- /dev/null
@@ -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 (file)
index 0000000..936fa9d
--- /dev/null
@@ -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
index f745567cf6719665cdfd23145335380f36e0c54b..40af0862818c15d6c432da1d02fbe1ba7cadf230 100644 (file)
@@ -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);
index b1812cb69a173c983bf434bcab0522bb96d03dd0..bf7b70299b43b1145c22b353f78eed1ec372f023 100644 (file)
@@ -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
@@ -2296,7 +2295,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
                if (va->value < vb->value)
                        cmp = -1;
                else if (va->value == vb->value)
-                       cmp = cmp_fn(a->refname, b->refname);
+                       cmp = 0;
                else
                        cmp = 1;
        }
@@ -2315,7 +2314,16 @@ static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
                if (cmp)
                        return cmp;
        }
-       return 0;
+       s = ref_sorting;
+       return s && s->ignore_case ?
+               strcasecmp(a->refname, b->refname) :
+               strcmp(a->refname, b->refname);
+}
+
+void ref_sorting_icase_all(struct ref_sorting *sorting, int flag)
+{
+       for (; sorting; sorting = sorting->next)
+               sorting->ignore_case = !!flag;
 }
 
 void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
index f1dcff4c6e23e831eff9915b659da062a835e8c8..8ecc33cdfa5006e34251131b0c6c93b335383809 100644 (file)
@@ -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"
@@ -114,6 +114,8 @@ void ref_array_clear(struct ref_array *array);
 int verify_ref_format(struct ref_format *format);
 /*  Sort the given ref_array as per the ref_sorting provided */
 void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
+/*  Set the ignore_case flag for all elements of a sorting list */
+void ref_sorting_icase_all(struct ref_sorting *sorting, int flag);
 /*  Based on the given format and quote_style, fill the strbuf */
 int format_ref_array_item(struct ref_array_item *info,
                          const struct ref_format *format,
diff --git a/refs.c b/refs.c
index 1ab0bb54d3d73bfe17f0eab4cd02d760bb9a7d01..224ff66c7bb0ef587cf2b34763586f53ad73bd84 100644 (file)
--- 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 545029c6d8050a173a1486cdaffb8f33c51ae93e..a92d2c74c8306a25c0b6eab7624a06adae37a1b8 100644 (file)
--- 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, "?*[");
index 561c33ac8a97a8e0a3de9ba27ca9bd51937c6c03..6516c7bc8c8fea09cfd296c5c4185332b8358d06 100644 (file)
@@ -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;
 }
index e4cd3218447ff385d31d621eb70f67f295f3f92a..1c9aa3d0ab978c1f98f1b4ff45143d765fcf6c27 100644 (file)
@@ -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"
index c43196ec06c50beb1b7c93e180a62c031d67c10e..534c6426f1e653e6fbb81bd7256a6b3f9d2dfb19 100644 (file)
--- 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;
 }
index a703e407a3f0a9c55c9a18ad1db6bf345f1165dd..dc6817daa9537c3877dae686368ef58708ac0512 100644 (file)
@@ -45,6 +45,8 @@ void prepare_repo_settings(struct repository *r)
 
        if (!repo_config_get_bool(r, "pack.usesparse", &value))
                r->settings.pack_use_sparse = value;
+       UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
+
        if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) {
                UPDATE_DEFAULT_BOOL(r->settings.index_version, 4);
                UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);
@@ -52,7 +54,6 @@ void prepare_repo_settings(struct repository *r)
        if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value))
                r->settings.fetch_write_commit_graph = value;
        if (!repo_config_get_bool(r, "feature.experimental", &value) && value) {
-               UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
                UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
                UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 1);
        }
index a4174ddb0629cdd690142fb42c5d4182492485c4..6f7f6f002b1a45faa52305ca8c370e2e959b2f5e 100644 (file)
@@ -89,6 +89,10 @@ void repo_set_gitdir(struct repository *repo,
 void repo_set_hash_algo(struct repository *repo, int hash_algo)
 {
        repo->hash_algo = &hash_algos[hash_algo];
+#ifndef ENABLE_SHA256
+       if (hash_algo != GIT_HASH_SHA1)
+               die(_("The hash algorithm %s is not supported in this build."), repo->hash_algo->name);
+#endif
 }
 
 /*
index 040057dea6f4f32bf11d429b3ad10a37d4b10520..6534fbb7b31301aa514c6cfad4f55b03289228b5 100644 (file)
@@ -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 (file)
index 0000000..2f4fbd0
--- /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 (file)
index 0000000..12f83c7
--- /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
index 8136929e23626e3bd9a8e9c6108fd2e32b2bd1cb..60cca8c0b9660e5c3e9927da99dee1fcda1c0069 100644 (file)
@@ -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.
index 475f048fb61185519b0010ee132d42b1b2c768f7..93491b79d475adb32414008558d0d94ec9240d56 100644 (file)
@@ -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);
index f5e1149f9b395e77c3d55a147332c1117746d7b1..9b3a57d1e392c8d311cfbfbad7859e9e627cd5ef 100644 (file)
@@ -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]);
@@ -1864,3 +1864,16 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
 
        return result;
 }
+
+int run_auto_gc(int quiet)
+{
+       struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
+       int status;
+
+       argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
+       if (quiet)
+               argv_array_push(&argv_gc_auto, "--quiet");
+       status = run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
+       argv_array_clear(&argv_gc_auto);
+       return status;
+}
index 0f3cc73ab6727e9b3805d93e5e4ab4dec3c1d5cb..191dfcdafe3dfcf6aff4b1c15e1be0924fbdb2d1 100644 (file)
@@ -218,6 +218,11 @@ LAST_ARG_MUST_BE_NULL
 int run_hook_le(const char *const *env, const char *name, ...);
 int run_hook_ve(const char *const *env, const char *name, va_list args);
 
+/*
+ * Trigger an auto-gc
+ */
+int run_auto_gc(int quiet);
+
 #define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
index 0407841ae87af99254f254b65bb53815790fec9a..0abee22283dae8ceba42eb4b9b5ecb4842c98984 100644 (file)
 #include "quote.h"
 #include "transport.h"
 #include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
 #include "gpg-interface.h"
 #include "cache.h"
+#include "shallow.h"
 
 int option_parse_push_signed(const struct option *opt,
                             const char *arg, int unset)
@@ -190,10 +191,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 +321,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 +465,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:
index e528225e787f4e26de604807efc798e8b967010e..9d1b3e7d4fa3ff44c1b06df9552f2139a433879c 100644 (file)
@@ -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"
 
@@ -40,7 +41,7 @@ static const char cherry_picked_prefix[] = "(cherry picked from commit ";
 
 GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
 
-GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
+static GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
@@ -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))
@@ -1323,7 +1316,7 @@ static int try_to_commit(struct repository *r,
                return -1;
 
        if (flags & AMEND_MSG) {
-               const char *exclude_gpgsig[] = { "gpgsig", NULL };
+               const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
                const char *out_enc = get_commit_output_encoding();
                const char *message = logmsg_reencode(current_head, NULL,
                                                      out_enc);
@@ -1433,9 +1426,19 @@ out:
        return res;
 }
 
+static int write_rebase_head(struct object_id *oid)
+{
+       if (update_ref("rebase", "REBASE_HEAD", oid,
+                      NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+               return error(_("could not update %s"), "REBASE_HEAD");
+
+       return 0;
+}
+
 static int do_commit(struct repository *r,
                     const char *msg_file, const char *author,
-                    struct replay_opts *opts, unsigned int flags)
+                    struct replay_opts *opts, unsigned int flags,
+                    struct object_id *oid)
 {
        int res = 1;
 
@@ -1460,8 +1463,12 @@ static int do_commit(struct repository *r,
                        return res;
                }
        }
-       if (res == 1)
+       if (res == 1) {
+               if (is_rebase_i(opts) && oid)
+                       if (write_rebase_head(oid))
+                           return -1;
                return run_git_commit(r, msg_file, opts, flags);
+       }
 
        return res;
 }
@@ -1564,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;
 }
@@ -1929,7 +1936,9 @@ static int do_pick_commit(struct repository *r,
         * However, if the merge did not even start, then we don't want to
         * write it at all.
         */
-       if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
+       if ((command == TODO_PICK || command == TODO_REWORD ||
+            command == TODO_EDIT) && !opts->no_commit &&
+           (res == 0 || res == 1) &&
            update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
                       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
                res = -1;
@@ -1965,7 +1974,8 @@ static int do_pick_commit(struct repository *r,
        } /* else allow == 0 and there's nothing special to do */
        if (!opts->no_commit && !drop_commit) {
                if (author || command == TODO_REVERT || (flags & AMEND_MSG))
-                       res = do_commit(r, msg_file, author, opts, flags);
+                       res = do_commit(r, msg_file, author, opts, flags,
+                                       commit? &commit->object.oid : NULL);
                else
                        res = error(_("unable to parse commit author"));
                *check_todo = !!(flags & EDIT_MSG);
@@ -2485,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 {
@@ -2496,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"))
@@ -2525,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'))) {
@@ -2538,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()))
@@ -3000,9 +3018,7 @@ static int make_patch(struct repository *r,
        p = short_commit_name(commit);
        if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
                return -1;
-       if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
-                      NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
-               res |= error(_("could not update %s"), "REBASE_HEAD");
+       res |= write_rebase_head(&commit->object.oid);
 
        strbuf_addf(&buf, "%s/patch", get_dir(opts));
        memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@ -3113,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",
@@ -3290,6 +3306,7 @@ static int do_reset(struct repository *r,
        unpack_tree_opts.fn = oneway_merge;
        unpack_tree_opts.merge = 1;
        unpack_tree_opts.update = 1;
+       init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
        if (repo_read_index_unmerged(r)) {
                rollback_lock_file(&lock);
@@ -3659,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;
@@ -3688,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."));
+       }
+
+       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);
 
-       strbuf_release(&stash_sha1);
+       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) {
@@ -3757,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"));
        }
@@ -3799,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));
@@ -3825,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");
@@ -3845,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),
@@ -4071,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);
                }
 
@@ -4289,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);
@@ -4591,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;
@@ -4645,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);
@@ -4656,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);
@@ -4819,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;
@@ -4858,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;
@@ -4947,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,
@@ -4955,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));
 
@@ -5079,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"));
@@ -5090,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);
 
@@ -5315,3 +5436,24 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
 
        return 0;
 }
+
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
+{
+       if (file_exists(git_path_cherry_pick_head(r))) {
+               struct object_id cherry_pick_head, rebase_head;
+
+               if (file_exists(git_path_seq_dir()))
+                       *whence = FROM_CHERRY_PICK_MULTI;
+               if (file_exists(rebase_path()) &&
+                   !get_oid("REBASE_HEAD", &rebase_head) &&
+                   !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) &&
+                   oideq(&rebase_head, &cherry_pick_head))
+                       *whence = FROM_REBASE_PICK;
+               else
+                       *whence = FROM_CHERRY_PICK_SINGLE;
+
+               return 1;
+       }
+
+       return 0;
+}
index 718a07426dae930dc07417f902fa1ad5682eb337..d31c41f018cb398e3da8e412f99925a8b30bde22 100644 (file)
@@ -3,12 +3,12 @@
 
 #include "cache.h"
 #include "strbuf.h"
+#include "wt-status.h"
 
 struct commit;
 struct repository;
 
 const char *git_path_commit_editmsg(void);
-const char *git_path_seq_dir(void);
 const char *rebase_path_todo(void);
 const char *rebase_path_todo_backup(void);
 const char *rebase_path_dropped(void);
@@ -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);
@@ -206,4 +226,5 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
 void sequencer_post_commit_cleanup(struct repository *r, int verbose);
 int sequencer_get_last_command(struct repository* r,
                               enum replay_action *action);
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence);
 #endif /* SEQUENCER_H */
diff --git a/setup.c b/setup.c
index 5ea9285a128c019018e48db1abea577f553f1785..65fe5ecefbe196eba04070978bc668b9af17257d 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -32,6 +32,7 @@ static int abspath_part_inside_repo(char *path)
        char *path0;
        int off;
        const char *work_tree = get_git_work_tree();
+       struct strbuf realpath = STRBUF_INIT;
 
        if (!work_tree)
                return -1;
@@ -60,8 +61,10 @@ static int abspath_part_inside_repo(char *path)
                path++;
                if (*path == '/') {
                        *path = '\0';
-                       if (fspathcmp(real_path(path0), work_tree) == 0) {
+                       strbuf_realpath(&realpath, path0, 1);
+                       if (fspathcmp(realpath.buf, work_tree) == 0) {
                                memmove(path0, path + 1, len - (path - path0));
+                               strbuf_release(&realpath);
                                return 0;
                        }
                        *path = '/';
@@ -69,11 +72,14 @@ static int abspath_part_inside_repo(char *path)
        }
 
        /* check whole path */
-       if (fspathcmp(real_path(path0), work_tree) == 0) {
+       strbuf_realpath(&realpath, path0, 1);
+       if (fspathcmp(realpath.buf, work_tree) == 0) {
                *path0 = '\0';
+               strbuf_release(&realpath);
                return 0;
        }
 
+       strbuf_release(&realpath);
        return -1;
 }
 
@@ -623,6 +629,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
        struct stat st;
        int fd;
        ssize_t len;
+       static struct strbuf realpath = STRBUF_INIT;
 
        if (stat(path, &st)) {
                /* NEEDSWORK: discern between ENOENT vs other errors */
@@ -673,7 +680,9 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
                error_code = READ_GITFILE_ERR_NOT_A_REPO;
                goto cleanup_return;
        }
-       path = real_path(dir);
+
+       strbuf_realpath(&realpath, dir, 1);
+       path = realpath.buf;
 
 cleanup_return:
        if (return_error_code)
@@ -729,7 +738,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
                }
 
                /* #18, #26 */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
@@ -751,7 +760,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        }
        else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
                /* #16d */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
@@ -763,14 +772,14 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 
        /* both get_git_work_tree() and cwd are already normalized */
        if (!strcmp(cwd->buf, worktree)) { /* cwd == worktree */
-               set_git_dir(gitdirenv);
+               set_git_dir(gitdirenv, 0);
                free(gitfile);
                return NULL;
        }
 
        offset = dir_inside_of(cwd->buf, worktree);
        if (offset >= 0) {      /* cwd inside worktree? */
-               set_git_dir(real_path(gitdirenv));
+               set_git_dir(gitdirenv, 1);
                if (chdir(worktree))
                        die_errno(_("cannot chdir to '%s'"), worktree);
                strbuf_addch(cwd, '/');
@@ -779,7 +788,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
        }
 
        /* cwd outside worktree */
-       set_git_dir(gitdirenv);
+       set_git_dir(gitdirenv, 0);
        free(gitfile);
        return NULL;
 }
@@ -808,7 +817,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
 
        /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */
        if (is_bare_repository_cfg > 0) {
-               set_git_dir(offset == cwd->len ? gitdir : real_path(gitdir));
+               set_git_dir(gitdir, (offset != cwd->len));
                if (chdir(cwd->buf))
                        die_errno(_("cannot come back to cwd"));
                return NULL;
@@ -817,7 +826,7 @@ static const char *setup_discovered_git_dir(const char *gitdir,
        /* #0, #1, #5, #8, #9, #12, #13 */
        set_git_work_tree(".");
        if (strcmp(gitdir, DEFAULT_GIT_DIR_ENVIRONMENT))
-               set_git_dir(gitdir);
+               set_git_dir(gitdir, 0);
        inside_git_dir = 0;
        inside_work_tree = 1;
        if (offset >= cwd->len)
@@ -860,10 +869,10 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset,
                        die_errno(_("cannot come back to cwd"));
                root_len = offset_1st_component(cwd->buf);
                strbuf_setlen(cwd, offset > root_len ? offset : root_len);
-               set_git_dir(cwd->buf);
+               set_git_dir(cwd->buf, 0);
        }
        else
-               set_git_dir(".");
+               set_git_dir(".", 0);
        return NULL;
 }
 
@@ -881,7 +890,7 @@ static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_
 
 /*
  * A "string_list_each_func_t" function that canonicalizes an entry
- * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or
+ * from GIT_CEILING_DIRECTORIES using real_pathdup(), or
  * discards it if unusable.  The presence of an empty entry in
  * GIT_CEILING_DIRECTORIES turns off canonicalization for all
  * subsequent entries.
@@ -1257,10 +1266,12 @@ int git_config_perm(const char *var, const char *value)
        return -(i & 0666);
 }
 
-void check_repository_format(void)
+void check_repository_format(struct repository_format *fmt)
 {
        struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
-       check_repository_format_gently(get_git_dir(), &repo_fmt, NULL);
+       if (!fmt)
+               fmt = &repo_fmt;
+       check_repository_format_gently(get_git_dir(), fmt, NULL);
        startup_info->have_repository = 1;
        clear_repository_format(&repo_fmt);
 }
index 616886799e5906988ac4834d71cd259ee1e540a8..ccd34dd9e8ce9c498facd694162c30656bfe3195 100644 (file)
@@ -74,6 +74,11 @@ static void git_hash_sha1_init(git_hash_ctx *ctx)
        git_SHA1_Init(&ctx->sha1);
 }
 
+static void git_hash_sha1_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       git_SHA1_Clone(&dst->sha1, &src->sha1);
+}
+
 static void git_hash_sha1_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        git_SHA1_Update(&ctx->sha1, data, len);
@@ -90,6 +95,11 @@ static void git_hash_sha256_init(git_hash_ctx *ctx)
        git_SHA256_Init(&ctx->sha256);
 }
 
+static void git_hash_sha256_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       git_SHA256_Clone(&dst->sha256, &src->sha256);
+}
+
 static void git_hash_sha256_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        git_SHA256_Update(&ctx->sha256, data, len);
@@ -105,6 +115,11 @@ static void git_hash_unknown_init(git_hash_ctx *ctx)
        BUG("trying to init unknown hash");
 }
 
+static void git_hash_unknown_clone(git_hash_ctx *dst, const git_hash_ctx *src)
+{
+       BUG("trying to clone unknown hash");
+}
+
 static void git_hash_unknown_update(git_hash_ctx *ctx, const void *data, size_t len)
 {
        BUG("trying to update unknown hash");
@@ -123,6 +138,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                0,
                0,
                git_hash_unknown_init,
+               git_hash_unknown_clone,
                git_hash_unknown_update,
                git_hash_unknown_final,
                NULL,
@@ -136,6 +152,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                GIT_SHA1_HEXSZ,
                GIT_SHA1_BLKSZ,
                git_hash_sha1_init,
+               git_hash_sha1_clone,
                git_hash_sha1_update,
                git_hash_sha1_final,
                &empty_tree_oid,
@@ -149,6 +166,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
                GIT_SHA256_HEXSZ,
                GIT_SHA256_BLKSZ,
                git_hash_sha256_init,
+               git_hash_sha256_clone,
                git_hash_sha256_update,
                git_hash_sha256_final,
                &empty_tree_oid_sha256,
@@ -676,20 +694,15 @@ void add_to_alternates_memory(const char *reference)
 char *compute_alternate_path(const char *path, struct strbuf *err)
 {
        char *ref_git = NULL;
-       const char *repo, *ref_git_s;
+       const char *repo;
        int seen_error = 0;
 
-       ref_git_s = real_path_if_valid(path);
-       if (!ref_git_s) {
+       ref_git = real_pathdup(path, 0);
+       if (!ref_git) {
                seen_error = 1;
                strbuf_addf(err, _("path '%s' does not exist"), path);
                goto out;
-       } else
-               /*
-                * Beware: read_gitfile(), real_path() and mkpath()
-                * return static buffer
-                */
-               ref_git = xstrdup(ref_git_s);
+       }
 
        repo = read_gitfile(ref_git);
        if (!repo)
@@ -868,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);
 }
 
 /*
index 5bb006e5a9e0fe6373a3a69c3d2c3cb7d1147738..0b8cb5247abc05bcc498908fcd34b1e24c817976 100644 (file)
@@ -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);
                }
index 09bd8bb20062ae6047a9680162cc761ad7ca8531..501da5ed9197ec61497e244879c7f60ba58617c1 100644 (file)
@@ -22,8 +22,14 @@ inline void gcrypt_SHA256_Final(unsigned char *digest, gcrypt_SHA256_CTX *ctx)
        memcpy(digest, gcry_md_read(*ctx, GCRY_MD_SHA256), SHA256_DIGEST_SIZE);
 }
 
+inline void gcrypt_SHA256_Clone(gcrypt_SHA256_CTX *dst, const gcrypt_SHA256_CTX *src)
+{
+       gcry_md_copy(dst, *src);
+}
+
 #define platform_SHA256_CTX gcrypt_SHA256_CTX
 #define platform_SHA256_Init gcrypt_SHA256_Init
+#define platform_SHA256_Clone gcrypt_SHA256_Clone
 #define platform_SHA256_Update gcrypt_SHA256_Update
 #define platform_SHA256_Final gcrypt_SHA256_Final
 
index 7fd04afed19af7de9dc1438b328567d0b7f2b2b4..b826de9b676c1c0d61fb30638284ed32c0f8403e 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -8,12 +8,13 @@
 #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"
 #include "list-objects.h"
 #include "commit-reach.h"
+#include "shallow.h"
 
 void set_alternate_shallow_file(struct repository *r, const char *path, int override)
 {
@@ -38,15 +39,21 @@ int register_shallow(struct repository *r, const struct object_id *oid)
        return register_commit_graft(r, graft, 0);
 }
 
-int is_repository_shallow(struct repository *r)
+int unregister_shallow(const struct object_id *oid)
 {
-       /*
-        * 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.
-        */
+       int pos = commit_graft_pos(the_repository, oid->hash);
+       if (pos < 0)
+               return -1;
+       if (pos + 1 < the_repository->parsed_objects->grafts_nr)
+               MOVE_ARRAY(the_repository->parsed_objects->grafts + pos,
+                          the_repository->parsed_objects->grafts + pos + 1,
+                          the_repository->parsed_objects->grafts_nr - pos - 1);
+       the_repository->parsed_objects->grafts_nr--;
+       return 0;
+}
 
+int is_repository_shallow(struct repository *r)
+{
        FILE *fp;
        char buf[1024];
        const char *path = r->parsed_objects->alternate_shallow_file;
@@ -79,6 +86,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 shallow_lock *lk)
+{
+       int res = commit_lock_file(&lk->lock);
+       reset_repository_shallow(r);
+       return res;
+}
+
+void rollback_shallow_file(struct repository *r, struct shallow_lock *lk)
+{
+       rollback_lock_file(&lk->lock);
+       reset_repository_shallow(r);
+}
+
 /*
  * TODO: use "int" elemtype instead of "int *" when/if commit-slab
  * supports a "valid" flag.
@@ -340,22 +366,22 @@ const char *setup_temporary_shallow(const struct oid_array *extra)
        return "";
 }
 
-void setup_alternate_shallow(struct lock_file *shallow_lock,
+void setup_alternate_shallow(struct shallow_lock *shallow_lock,
                             const char **alternate_shallow_file,
                             const struct oid_array *extra)
 {
        struct strbuf sb = STRBUF_INIT;
        int fd;
 
-       fd = hold_lock_file_for_update(shallow_lock,
+       fd = hold_lock_file_for_update(&shallow_lock->lock,
                                       git_path_shallow(the_repository),
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update(the_repository);
        if (write_shallow_commits(&sb, 0, extra)) {
                if (write_in_full(fd, sb.buf, sb.len) < 0)
                        die_errno("failed to write to %s",
-                                 get_lock_file_path(shallow_lock));
-               *alternate_shallow_file = get_lock_file_path(shallow_lock);
+                                 get_lock_file_path(&shallow_lock->lock));
+               *alternate_shallow_file = get_lock_file_path(&shallow_lock->lock);
        } else
                /*
                 * is_repository_shallow() sees empty string as "no
@@ -388,7 +414,7 @@ void advertise_shallow_grafts(int fd)
  */
 void prune_shallow(unsigned options)
 {
-       struct lock_file shallow_lock = LOCK_INIT;
+       struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
        struct strbuf sb = STRBUF_INIT;
        unsigned flags = SEEN_ONLY;
        int fd;
@@ -402,18 +428,18 @@ void prune_shallow(unsigned options)
                strbuf_release(&sb);
                return;
        }
-       fd = hold_lock_file_for_update(&shallow_lock,
+       fd = hold_lock_file_for_update(&shallow_lock.lock,
                                       git_path_shallow(the_repository),
                                       LOCK_DIE_ON_ERROR);
        check_shallow_file_for_update(the_repository);
        if (write_shallow_commits_1(&sb, 0, NULL, flags)) {
                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);
+                                 get_lock_file_path(&shallow_lock.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/shallow.h b/shallow.h
new file mode 100644 (file)
index 0000000..5b4a96d
--- /dev/null
+++ b/shallow.h
@@ -0,0 +1,81 @@
+#ifndef SHALLOW_H
+#define SHALLOW_H
+
+#include "lockfile.h"
+#include "object.h"
+#include "repository.h"
+#include "strbuf.h"
+
+void set_alternate_shallow_file(struct repository *r, const char *path, int override);
+int register_shallow(struct repository *r, const struct object_id *oid);
+int unregister_shallow(const struct object_id *oid);
+int is_repository_shallow(struct repository *r);
+
+/*
+ * Lock for updating the $GIT_DIR/shallow file.
+ *
+ * Use `commit_shallow_file()` to commit an update, or
+ * `rollback_shallow_file()` to roll it back. In either case, any
+ * in-memory cached information about which commits are shallow will be
+ * appropriately invalidated so that future operations reflect the new
+ * state.
+ */
+struct shallow_lock {
+       struct lock_file lock;
+};
+#define SHALLOW_LOCK_INIT { LOCK_INIT }
+
+/* commit $GIT_DIR/shallow and reset stat-validity checks */
+int commit_shallow_file(struct repository *r, struct shallow_lock *lk);
+/* rollback $GIT_DIR/shallow and reset stat-validity checks */
+void rollback_shallow_file(struct repository *r, struct shallow_lock *lk);
+
+struct commit_list *get_shallow_commits(struct object_array *heads,
+                                       int depth, int shallow_flag, int not_shallow_flag);
+struct commit_list *get_shallow_commits_by_rev_list(
+               int ac, const char **av, int shallow_flag, int not_shallow_flag);
+int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
+                         const struct oid_array *extra);
+
+void setup_alternate_shallow(struct shallow_lock *shallow_lock,
+                            const char **alternate_shallow_file,
+                            const struct oid_array *extra);
+
+const char *setup_temporary_shallow(const struct oid_array *extra);
+
+void advertise_shallow_grafts(int);
+
+#define PRUNE_SHOW_ONLY 1
+#define PRUNE_QUICK 2
+void prune_shallow(unsigned options);
+
+/*
+ * Initialize with prepare_shallow_info() or zero-initialize (equivalent to
+ * prepare_shallow_info with a NULL oid_array).
+ */
+struct shallow_info {
+       struct oid_array *shallow;
+       int *ours, nr_ours;
+       int *theirs, nr_theirs;
+       struct oid_array *ref;
+
+       /* for receive-pack */
+       uint32_t **used_shallow;
+       int *need_reachability_test;
+       int *reachable;
+       int *shallow_ref;
+       struct commit **commits;
+       int nr_commits;
+};
+
+void prepare_shallow_info(struct shallow_info *, struct oid_array *);
+void clear_shallow_info(struct shallow_info *);
+void remove_nonexistent_theirs_shallow(struct shallow_info *);
+void assign_shallow_commits_to_refs(struct shallow_info *info,
+                                   uint32_t **used,
+                                   int *ref_status);
+int delayed_reachability_test(struct shallow_info *si, int c);
+
+extern struct trace_key trace_shallow;
+
+#endif /* SHALLOW_H */
diff --git a/shell.c b/shell.c
index 54cca7439de636daa37b7f8627c5cdd8bb431703..cef7ffdc9e1d3040930841134577c591cf02f650 100644 (file)
--- 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);
index bb0065ccaf5b764323cd727dbabe3061bbe328e4..2f1a7d32098d40365bb314d394519a908a013927 100644 (file)
--- 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))
index ce8e49c0b2a1427fc9e28c9d0c5c3c6a8e5b66fc..7062eb641024979c1f5b8a9df040b28adf984e5e 100644 (file)
--- 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
@@ -502,6 +508,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
index 4d1c92d5826da800360a98e88d577f6f25d27f96..e175dfbc388ab50a55429bfff287541bb2a48530 100644 (file)
@@ -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)
index 31f391d7d2541c1498387248e669248de3430b5d..e2ef5698c893c3587e500ac88b46aa9ba68609ea 100644 (file)
@@ -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"
@@ -2168,13 +2168,13 @@ void absorb_git_dir_into_superproject(const char *path,
        }
 }
 
-const char *get_superproject_working_tree(void)
+int get_superproject_working_tree(struct strbuf *buf)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf sb = STRBUF_INIT;
-       const char *one_up = real_path_if_valid("../");
+       struct strbuf one_up = STRBUF_INIT;
        const char *cwd = xgetcwd();
-       const char *ret = NULL;
+       int ret = 0;
        const char *subpath;
        int code;
        ssize_t len;
@@ -2185,12 +2185,13 @@ const char *get_superproject_working_tree(void)
                 * We might have a superproject, but it is harder
                 * to determine.
                 */
-               return NULL;
+               return 0;
 
-       if (!one_up)
-               return NULL;
+       if (!strbuf_realpath(&one_up, "../", 0))
+               return 0;
 
-       subpath = relative_path(cwd, one_up, &sb);
+       subpath = relative_path(cwd, one_up.buf, &sb);
+       strbuf_release(&one_up);
 
        prepare_submodule_repo_env(&cp.env_array);
        argv_array_pop(&cp.env_array);
@@ -2231,7 +2232,8 @@ const char *get_superproject_working_tree(void)
                super_wt = xstrdup(cwd);
                super_wt[cwd_len - super_sub_len] = '\0';
 
-               ret = real_path(super_wt);
+               strbuf_realpath(buf, super_wt, 1);
+               ret = 1;
                free(super_wt);
        }
        strbuf_release(&sb);
@@ -2240,10 +2242,10 @@ const char *get_superproject_working_tree(void)
 
        if (code == 128)
                /* '../' is not a git repository */
-               return NULL;
+               return 0;
        if (code == 0 && len == 0)
                /* There is an unrelated git repository at '../' */
-               return NULL;
+               return 0;
        if (code)
                die(_("ls-tree returned unexpected return code %d"), code);
 
index c81ec1a9b6c8ff3061c8119ad68b32f850314d9b..4dad649f94220e3897e629a5737da5f08705a5fb 100644 (file)
@@ -152,8 +152,8 @@ void absorb_git_dir_into_superproject(const char *path,
 /*
  * Return the absolute path of the working tree of the superproject, which this
  * project is a submodule of. If this repository is not a submodule of
- * another repository, return NULL.
+ * another repository, return 0.
  */
-const char *get_superproject_working_tree(void);
+int get_superproject_working_tree(struct strbuf *buf);
 
 #endif
index 9afd61e3ca0d19e569c5d31855fbbc79c83c9d4e..cf863837ab92992afb5ce3b562f9045bfb648c28 100644 (file)
--- 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.
@@ -386,17 +392,13 @@ GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path
 for the index version specified.  Can be set to any valid version
 (currently 2, 3, or 4).
 
-GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects
-builtin to use the sparse object walk. This can still be overridden by
-the --no-sparse command-line argument.
+GIT_TEST_PACK_SPARSE=<boolean> if disabled will default the pack-objects
+builtin to use the non-sparse object walk. This can still be overridden by
+the --sparse command-line argument.
 
 GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
 by overriding the minimum number of cache entries required per thread.
 
-GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the
-built-in version of git-stash. See 'stash.useBuiltin' in
-git-config(1).
-
 GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
 built-in version of git add -i. See 'add.interactive.useBuiltin' in
 git-config(1).
@@ -550,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-advise.c b/t/helper/test-advise.c
new file mode 100644 (file)
index 0000000..38cdc28
--- /dev/null
@@ -0,0 +1,22 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "advice.h"
+#include "config.h"
+
+int cmd__advise_if_enabled(int argc, const char **argv)
+{
+       if (!argv[1])
+       die("usage: %s <advice>", argv[0]);
+
+       setup_git_directory();
+       git_config(git_default_config, NULL);
+
+       /*
+        * Any advice type can be used for testing, but NESTED_TAG was
+        * selected here and in t0018 where this command is being
+        * executed.
+        */
+       advise_if_enabled(ADVICE_NESTED_TAG, argv[1]);
+
+       return 0;
+}
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
new file mode 100644 (file)
index 0000000..456f5ea
--- /dev/null
@@ -0,0 +1,93 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "test-tool.h"
+#include "commit.h"
+
+static 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;
+}
index 63c689d6ee9d018efd32aa228ee3cf784fa5b71d..a209880eb37a2ce4de4b9f916ec3dbf52b987957 100644 (file)
@@ -13,6 +13,8 @@ int cmd__dump_split_index(int ac, const char **av)
        struct split_index *si;
        int i;
 
+       setup_git_directory();
+
        do_read_index(&the_index, av[1], 1);
        printf("own %s\n", oid_to_hex(&the_index.oid));
        si = the_index.split_index;
similarity index 83%
rename from t/helper/test-sha1-array.c
rename to t/helper/test-oid-array.c
index ad5e69f9d3b0e03442f0d23b3b559bbfc163ee7b..ce9fd5f0919f76ee3660cec5464b8a31c65eba07 100644 (file)
@@ -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);
index 02f4ccfd2a2d1d23f25d82e38a050c70ee3fff10..b3e08cef4b3f86ba95509f1d66345f7ca6016ea3 100644 (file)
@@ -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++)
index 409034cf4eef59363653b39cf3aae3fc37c07856..313a153209c48f9001f122fa61f6c42c39ac02f8 100644 (file)
@@ -290,11 +290,14 @@ int cmd__path_utils(int argc, const char **argv)
        }
 
        if (argc >= 2 && !strcmp(argv[1], "real_path")) {
+               struct strbuf realpath = STRBUF_INIT;
                while (argc > 2) {
-                       puts(real_path(argv[2]));
+                       strbuf_realpath(&realpath, argv[2], 1);
+                       puts(realpath.buf);
                        argc--;
                        argv++;
                }
+               strbuf_release(&realpath);
                return 0;
        }
 
index 282d53638446bb2f0c91b81b313852cbadfb3c1f..12ca698e17a1d556bf345355495849b452b79330 100644 (file)
@@ -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);
index 42b96cb103c6acee9ad2a9dfad8afbaa7ef854e9..5d05cbe7894097f6410328b966bda936e678237f 100644 (file)
  *
  * 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;
index f8a461767ca44571193005e3c3d2fb74d775f9de..6d0c962438bac9510fc55857eb44736a0284a1b1 100644 (file)
@@ -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);
index f7f861844560e0578eda989e39c4bd0572dcd5d6..56f0e3c1bef293dd505e28af4b48dfce11490b3c 100644 (file)
@@ -19,12 +19,11 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 
        memset(the_repository, 0, sizeof(*the_repository));
 
-       /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
-       repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
-
        if (repo_init(&r, gitdir, worktree))
                die("Couldn't init repo");
 
+       repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo));
+
        c = lookup_commit(&r, commit_oid);
 
        if (!parse_commit_in_graph(&r, c))
@@ -50,12 +49,11 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 
        memset(the_repository, 0, sizeof(*the_repository));
 
-       /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */
-       repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
-
        if (repo_init(&r, gitdir, worktree))
                die("Couldn't init repo");
 
+       repo_set_hash_algo(the_repository, hash_algo_by_ptr(r.hash_algo));
+
        c = lookup_commit(&r, commit_oid);
 
        /*
@@ -75,6 +73,10 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 
 int cmd__repository(int argc, const char **argv)
 {
+       int nongit_ok = 0;
+
+       setup_git_directory_gently(&nongit_ok);
+
        if (argc < 2)
                die("must have at least 2 arguments");
        if (!strcmp(argv[1], "parse_commit_in_graph")) {
index c9a232d23897b70b051bfd76488ca0e971830b83..590b2efca705c57a6f9df1df42163588429a918a 100644 (file)
@@ -14,6 +14,8 @@ 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 },
@@ -36,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 },
@@ -56,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 },
@@ -111,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);
                }
        }
index c8549fd87f23b6372a41b2ad5d7ba28df3e258c7..ddc8e990e918232093b4958e1c94f44436142053 100644 (file)
@@ -4,6 +4,8 @@
 #define USE_THE_INDEX_COMPATIBILITY_MACROS
 #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);
@@ -46,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);
old mode 100755 (executable)
new mode 100644 (file)
index bb88cc0..dea2cbe
@@ -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,
old mode 100755 (executable)
new mode 100644 (file)
index 8d28652..9fc5241
@@ -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 '
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index 1dd17fc..64fc648
@@ -297,7 +297,7 @@ test_submodule_content () {
 # - Directory containing tracked files replaced by submodule
 # - Submodule replaced by tracked files in directory
 # - Submodule replaced by tracked file with the same name
-# - tracked file replaced by submodule
+# - Tracked file replaced by submodule
 #
 # The default is that submodule contents aren't changed until "git submodule
 # update" is run. And even then that command doesn't delete the work tree of
@@ -621,11 +621,13 @@ test_submodule_forced_switch () {
 # - Directory containing tracked files replaced by submodule
 # - Submodule replaced by tracked files in directory
 # - Submodule replaced by tracked file with the same name
-# - tracked file replaced by submodule
+# - Tracked file replaced by submodule
 #
 # New test cases
 # - Removing a submodule with a git directory absorbs the submodules
 #   git directory first into the superproject.
+# - Switching from no submodule to nested submodules
+# - Switching from nested submodules to no submodule
 
 # Internal function; use test_submodule_switch_recursing_with_args() or
 # test_submodule_forced_switch_recursing_with_args() instead.
@@ -658,22 +660,6 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
-       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
-               prolog &&
-               reset_work_tree_to_interested add_sub1 &&
-               (
-                       cd submodule_update &&
-                       git -C sub1 checkout -b keep_branch &&
-                       git -C sub1 rev-parse HEAD >expect &&
-                       git branch -t modify_sub1 origin/modify_sub1 &&
-                       $command modify_sub1 &&
-                       test_superproject_content origin/modify_sub1 &&
-                       test_submodule_content sub1 origin/modify_sub1 &&
-                       git -C sub1 rev-parse keep_branch >actual &&
-                       test_cmp expect actual &&
-                       test_must_fail git -C sub1 symbolic-ref HEAD
-               )
-       '
 
        # Replacing a tracked file with a submodule produces a checked out submodule
        test_expect_success "$command: replace tracked file with submodule checks out submodule" '
@@ -699,6 +685,19 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/replace_directory_with_sub1
                )
        '
+       # Switching to a commit with nested submodules recursively checks them out
+       test_expect_success "$command: nested submodules are checked out" '
+               prolog &&
+               reset_work_tree_to_interested no_submodule &&
+               (
+                       cd submodule_update &&
+                       git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
+                       $command modify_sub1_recursively &&
+                       test_superproject_content origin/modify_sub1_recursively &&
+                       test_submodule_content sub1 origin/modify_sub1_recursively &&
+                       test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
+               )
+       '
 
        ######################## Disappearing submodule #######################
        # Removing a submodule removes its work tree ...
@@ -762,6 +761,21 @@ test_submodule_recursing_with_args_common() {
                )
        '
 
+       # Switching to a commit without nested submodules removes their worktrees
+       test_expect_success "$command: worktrees of nested submodules are removed" '
+               prolog &&
+               reset_work_tree_to_interested add_nested_sub &&
+               (
+                       cd submodule_update &&
+                       git branch -t no_submodule origin/no_submodule &&
+                       $command no_submodule &&
+                       test_superproject_content origin/no_submodule &&
+                       ! test_path_is_dir sub1 &&
+                       test_must_fail git config -f .git/modules/sub1/config core.worktree &&
+                       test_must_fail git config -f .git/modules/sub1/modules/sub2/config core.worktree
+               )
+       '
+
        ########################## Modified submodule #########################
        # Updating a submodule sha1 updates the submodule's work tree
        test_expect_success "$command: modified submodule updates submodule work tree" '
@@ -789,6 +803,23 @@ test_submodule_recursing_with_args_common() {
                        test_submodule_content sub1 origin/add_sub1
                )
        '
+       # Updating a submodule does not touch the currently checked out branch in the submodule
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
+               prolog &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       $command modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
+               )
+       '
 }
 
 # Declares and invokes several tests that, in various situations, checks that
@@ -908,7 +939,6 @@ test_submodule_switch_recursing_with_args () {
                )
        '
 
-       # recursing deeper than one level doesn't work yet.
        test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
                reset_work_tree_to_interested add_nested_sub &&
index 7743f4f4c9ff728001b4d9d8201654a288c1ef9b..b3e725f0310a7b4d8343089de922c7eb6d148a52 100755 (executable)
@@ -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
 '
@@ -57,6 +53,11 @@ test_perf 'rev-list count with blob:limit=1k' '
                --filter=blob:limit=1k >/dev/null
 '
 
+test_perf 'rev-list count with tree:0' '
+       git rev-list --use-bitmap-index --count --objects --all \
+               --filter=tree:0 >/dev/null
+'
+
 test_perf 'simulated partial clone' '
        git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
 '
@@ -90,4 +91,9 @@ test_perf 'pack to file (partial bitmap)' '
        git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
 '
 
+test_perf 'rev-list with tree filter (partial bitmap)' '
+       git rev-list --use-bitmap-index --count --objects --all \
+               --filter=tree:0 >/dev/null
+'
+
 test_done
diff --git a/t/perf/p9300-fast-import-export.sh b/t/perf/p9300-fast-import-export.sh
new file mode 100755 (executable)
index 0000000..586161e
--- /dev/null
@@ -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
index 3e440c078d5752620a9ce9dc91f61e9441cb3665..2ff176cd5d954cb1c83f062adbdb28a7e14d2668 100755 (executable)
@@ -77,9 +77,7 @@ _run_sub_test_lib_test_common () {
                # the sub-test.
                sane_unset HARNESS_ACTIVE &&
                cd "$name" &&
-               cat >"$name.sh" <<-EOF &&
-               #!$SHELL_PATH
-
+               write_script "$name.sh" "$TEST_SHELL_PATH" <<-EOF &&
                test_description='$descr (run in sub test-lib)
 
                This is run in a sub test-lib so that we do not get incorrect
@@ -94,15 +92,15 @@ _run_sub_test_lib_test_common () {
                . "\$TEST_DIRECTORY"/test-lib.sh
                EOF
                cat >>"$name.sh" &&
-               chmod +x "$name.sh" &&
                export TEST_DIRECTORY &&
                TEST_OUTPUT_DIRECTORY=$(pwd) &&
                export TEST_OUTPUT_DIRECTORY &&
+               sane_unset GIT_TEST_FAIL_PREREQS &&
                if test -z "$neg"
                then
                        ./"$name.sh" "$@" >out 2>err
                else
-                       !  ./"$name.sh" "$@" >out 2>err
+                       ! ./"$name.sh" "$@" >out 2>err
                fi
        )
 }
@@ -833,6 +831,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 &&
index 26f82063267f3043f7f9c36745d913c12dbe0a27..1edd5aeb8f01d490b1cceba2c1d67df181868280 100755 (executable)
@@ -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
 '
index d9fcc829a9e6cb10088e6503841928cf85d1d697..75ee9a96b80bbad5506bc6961b23988358a65c8a 100755 (executable)
@@ -81,6 +81,11 @@ check_parse 2008-02 bad
 check_parse 2008-02-14 bad
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008.02.14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '20080214T203045-04:00' '2008-02-14 20:30:45 -0400'
+check_parse '20080214T203045 -04:00' '2008-02-14 20:30:45 -0400'
+check_parse '20080214T203045.019-04:00' '2008-02-14 20:30:45 -0400'
+check_parse '2008-02-14 20:30:45.019-04:00' '2008-02-14 20:30:45 -0400'
 check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'
 check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'
@@ -103,6 +108,7 @@ check_approxidate 5.seconds.ago '2009-08-30 19:19:55'
 check_approxidate 10.minutes.ago '2009-08-30 19:10:00'
 check_approxidate yesterday '2009-08-29 19:20:00'
 check_approxidate 3.days.ago '2009-08-27 19:20:00'
+check_approxidate '12:34:56.3.days.ago' '2009-08-27 12:34:56'
 check_approxidate 3.weeks.ago '2009-08-09 19:20:00'
 check_approxidate 3.months.ago '2009-05-30 19:20:00'
 check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00'
index 1f600e2cae544b598823a2fd3854493fc8e828c9..88b9ae81588d1cd8581c29faf67ee8fe4a4e9a6d 100755 (executable)
@@ -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/t0018-advice.sh b/t/t0018-advice.sh
new file mode 100755 (executable)
index 0000000..e03554d
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='Test advise_if_enabled functionality'
+
+. ./test-lib.sh
+
+test_expect_success 'advice should be printed when config variable is unset' '
+       cat >expect <<-\EOF &&
+       hint: This is a piece of advice
+       hint: Disable this message with "git config advice.nestedTag false"
+       EOF
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'advice should be printed when config variable is set to true' '
+       cat >expect <<-\EOF &&
+       hint: This is a piece of advice
+       hint: Disable this message with "git config advice.nestedTag false"
+       EOF
+       test_config advice.nestedTag true &&
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'advice should not be printed when config variable is set to false' '
+       test_config advice.nestedTag false &&
+       test-tool advise "This is a piece of advice" 2>actual &&
+       test_must_be_empty actual
+'
+
+test_done
index dc664da551a3e9711df96f7291df4a1c8070d4d7..4bfffa9c314bf8236c9c7d74fca166ed86d5952a 100755 (executable)
@@ -364,6 +364,10 @@ test_expect_success PERL 'required process filter should filter data' '
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               EMPTY=$(git hash-object /dev/null) &&
 
                filter_git add . &&
                cat >expected.log <<-EOF &&
@@ -378,14 +382,16 @@ test_expect_success PERL 'required process filter should filter data' '
                test_cmp_count expected.log debug.log &&
 
                git commit -m "test commit 2" &&
+               MASTER=$(git rev-parse --verify master) &&
+               META="ref=refs/heads/master treeish=$MASTER" &&
                rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" &&
 
                filter_git checkout --quiet --no-progress . &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -406,10 +412,10 @@ test_expect_success PERL 'required process filter should filter data' '
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
-                       IN: smudge test4-empty.r 0 [OK] -- OUT: 0  [OK]
-                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK]
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -420,6 +426,117 @@ test_expect_success PERL 'required process filter should filter data' '
        )
 '
 
+test_expect_success PERL 'required process filter should filter data for various subcommands' '
+       test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" &&
+       test_config_global filter.protocol.required true &&
+       (
+               cd repo &&
+
+               S=$(file_size test.r) &&
+               S2=$(file_size test2.r) &&
+               S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               M3=$(git hash-object "testsubdir/test3 '\''sq'\'',\$x=.r") &&
+               EMPTY=$(git hash-object /dev/null) &&
+
+               MASTER=$(git rev-parse --verify master) &&
+
+               cp "$TEST_ROOT/test.o" test5.r &&
+               git add test5.r &&
+               git commit -m "test commit 3" &&
+               git checkout empty-branch &&
+               filter_git rebase --onto empty-branch master^^ master &&
+               MASTER2=$(git rev-parse --verify master) &&
+               META="ref=refs/heads/master treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git reset --hard empty-branch &&
+               filter_git reset --hard $MASTER &&
+               META="treeish=$MASTER" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git branch old-master $MASTER &&
+               git reset --hard empty-branch &&
+               filter_git reset --hard old-master &&
+               META="ref=refs/heads/old-master treeish=$MASTER" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               git checkout -b merge empty-branch &&
+               git branch -f master $MASTER2 &&
+               filter_git merge master &&
+               META="treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               filter_git archive master >/dev/null &&
+               META="ref=refs/heads/master treeish=$MASTER2" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log &&
+
+               TREE="$(git rev-parse $MASTER2^{tree})" &&
+               filter_git archive $TREE >/dev/null &&
+               META="treeish=$TREE" &&
+               cat >expected.log <<-EOF &&
+                       START
+                       init handshake complete
+                       IN: smudge test.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r $META blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test4-empty.r $META blob=$EMPTY 0 [OK] -- OUT: 0  [OK]
+                       IN: smudge test5.r $META blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $META blob=$M3 $S3 [OK] -- OUT: $S3 . [OK]
+                       STOP
+               EOF
+               test_cmp_exclude_clean expected.log debug.log
+       )
+'
+
 test_expect_success PERL 'required process filter takes precedence' '
        test_config_global filter.protocol.clean false &&
        test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" &&
@@ -519,17 +636,22 @@ test_expect_success PERL 'required process filter should process multiple packet
                EOF
                test_cmp_count expected.log debug.log &&
 
-               rm -f *.file &&
+               M1="blob=$(git hash-object 1pkt_1__.file)" &&
+               M2="blob=$(git hash-object 2pkt_1+1.file)" &&
+               M3="blob=$(git hash-object 2pkt_2-1.file)" &&
+               M4="blob=$(git hash-object 2pkt_2__.file)" &&
+               M5="blob=$(git hash-object 3pkt_2+1.file)" &&
+               rm -f *.file debug.log &&
 
                filter_git checkout --quiet --no-progress -- *.file &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge 1pkt_1__.file $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
-                       IN: smudge 2pkt_1+1.file $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
-                       IN: smudge 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
-                       IN: smudge 2pkt_2__.file $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
-                       IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
+                       IN: smudge 1pkt_1__.file $M1 $(($S    )) [OK] -- OUT: $(($S    )) . [OK]
+                       IN: smudge 2pkt_1+1.file $M2 $(($S  +1)) [OK] -- OUT: $(($S  +1)) .. [OK]
+                       IN: smudge 2pkt_2-1.file $M3 $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK]
+                       IN: smudge 2pkt_2__.file $M4 $(($S*2  )) [OK] -- OUT: $(($S*2  )) .. [OK]
+                       IN: smudge 3pkt_2+1.file $M5 $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -578,6 +700,10 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                SF=$(file_size smudge-write-fail.r) &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               MF=$(git hash-object smudge-write-fail.r) &&
+               rm -f debug.log &&
 
                git add . &&
                rm -f *.r &&
@@ -591,11 +717,11 @@ test_expect_success PERL 'process filter should restart after unexpected write f
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge smudge-write-fail.r $SF [OK] -- [WRITE FAIL]
+                       IN: smudge smudge-write-fail.r blob=$MF $SF [OK] -- [WRITE FAIL]
                        START
                        init handshake complete
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -629,6 +755,10 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                S=$(file_size test.r) &&
                S2=$(file_size test2.r) &&
                SE=$(file_size error.r) &&
+               M=$(git hash-object test.r) &&
+               M2=$(git hash-object test2.r) &&
+               ME=$(git hash-object error.r) &&
+               rm -f debug.log &&
 
                git add . &&
                rm -f *.r &&
@@ -637,9 +767,9 @@ test_expect_success PERL 'process filter should not be restarted if it signals a
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge error.r $SE [OK] -- [ERROR]
-                       IN: smudge test.r $S [OK] -- OUT: $S . [OK]
-                       IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK]
+                       IN: smudge error.r blob=$ME $SE [OK] -- [ERROR]
+                       IN: smudge test.r blob=$M $S [OK] -- OUT: $S . [OK]
+                       IN: smudge test2.r blob=$M2 $S2 [OK] -- OUT: $S2 . [OK]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -665,18 +795,21 @@ test_expect_success PERL 'process filter abort stops processing of all further f
                echo "error this blob and all future blobs" >abort.o &&
                cp abort.o abort.r &&
 
+               M="blob=$(git hash-object abort.r)" &&
+               rm -f debug.log &&
                SA=$(file_size abort.r) &&
 
                git add . &&
                rm -f *.r &&
 
+
                # Note: This test assumes that Git filters files in alphabetical
                # order ("abort.r" before "test.r").
                filter_git checkout --quiet --no-progress . &&
                cat >expected.log <<-EOF &&
                        START
                        init handshake complete
-                       IN: smudge abort.r $SA [OK] -- [ABORT]
+                       IN: smudge abort.r $M $SA [OK] -- [ABORT]
                        STOP
                EOF
                test_cmp_exclude_clean expected.log debug.log &&
@@ -727,27 +860,29 @@ test_expect_success PERL 'delayed checkout in process filter' '
        ) &&
 
        S=$(file_size "$TEST_ROOT/test.o") &&
+       PM="ref=refs/heads/master treeish=$(git -C repo rev-parse --verify master) " &&
+       M="${PM}blob=$(git -C repo rev-parse --verify master:test.a)" &&
        cat >a.exp <<-EOF &&
                START
                init handshake complete
-               IN: smudge test.a $S [OK] -- OUT: $S . [OK]
-               IN: smudge test-delay10.a $S [OK] -- [DELAYED]
-               IN: smudge test-delay11.a $S [OK] -- [DELAYED]
-               IN: smudge test-delay20.a $S [OK] -- [DELAYED]
+               IN: smudge test.a $M $S [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.a $M $S [OK] -- [DELAYED]
+               IN: smudge test-delay11.a $M $S [OK] -- [DELAYED]
+               IN: smudge test-delay20.a $M $S [OK] -- [DELAYED]
                IN: list_available_blobs test-delay10.a test-delay11.a [OK]
-               IN: smudge test-delay10.a 0 [OK] -- OUT: $S . [OK]
-               IN: smudge test-delay11.a 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.a $M 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay11.a $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs test-delay20.a [OK]
-               IN: smudge test-delay20.a 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay20.a $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs [OK]
                STOP
        EOF
        cat >b.exp <<-EOF &&
                START
                init handshake complete
-               IN: smudge test-delay10.b $S [OK] -- [DELAYED]
+               IN: smudge test-delay10.b $M $S [OK] -- [DELAYED]
                IN: list_available_blobs test-delay10.b [OK]
-               IN: smudge test-delay10.b 0 [OK] -- OUT: $S . [OK]
+               IN: smudge test-delay10.b $M 0 [OK] -- OUT: $S . [OK]
                IN: list_available_blobs [OK]
                STOP
        EOF
@@ -767,8 +902,11 @@ test_expect_success PERL 'delayed checkout in process filter' '
 
                rm *.a *.b &&
                filter_git checkout . &&
-               test_cmp_count ../a.exp a.log &&
-               test_cmp_count ../b.exp b.log &&
+               # We are not checking out a ref here, so filter out ref metadata.
+               sed -e "s!$PM!!" ../a.exp >a.exp.filtered &&
+               sed -e "s!$PM!!" ../b.exp >b.exp.filtered &&
+               test_cmp_count a.exp.filtered a.log &&
+               test_cmp_count b.exp.filtered b.log &&
 
                test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a &&
                test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a &&
index 470107248eb161b9314ceb0ab93f21f072cf86cd..cd32a82da5c3bc4ac5b6c1748ef3368ad6f4ca70 100644 (file)
@@ -135,7 +135,13 @@ while (1) {
                                if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) {
                                        $DELAY{$pathname}{"requested"} = 1;
                                }
+                       } elsif ($buffer =~ /^(ref|treeish|blob)=/) {
+                               print $debug " $buffer";
                        } else {
+                               # In general, filters need to be graceful about
+                               # new metadata, since it's documented that we
+                               # can pass any key-value pairs, but for tests,
+                               # let's be a little stricter.
                                die "Unknown message '$buffer'";
                        }
 
index 3483b72db42a61563ffa88d30c0f084178d91ab3..f8178ee4e392fa5cfdbef07b0f42710ea68bc67b 100755 (executable)
@@ -54,7 +54,7 @@ Alias
     -A, --alias-source <string>
                           get a string
     -Z, --alias-target <string>
-                          get a string
+                          alias of --alias-source
 
 EOF
 
index 2ea2d00c39a6a53ae121dd35014a4c539ea1b35b..56db5c8abab62e7ae327bece57768f0f4ae6f0e4 100755 (executable)
@@ -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"
index 5dda570b9a1ef3139165a4b901e16f64a3263209..45685af2fd5095a9901fdb150de1c6fd8703728f 100755 (executable)
@@ -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 (executable)
index 0000000..526304f
--- /dev/null
@@ -0,0 +1,76 @@
+#!/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_expect_success 'indicates populated hooks' '
+       test_when_finished rm git-bugreport-hooks.txt &&
+       test_when_finished rm -fr .git/hooks &&
+       rm -fr .git/hooks &&
+       mkdir .git/hooks &&
+       for hook in applypatch-msg prepare-commit-msg.sample
+       do
+               write_script ".git/hooks/$hook" <<-EOF || return 1
+               echo "hook $hook exists"
+               EOF
+       done &&
+       git bugreport -s hooks &&
+       grep applypatch-msg git-bugreport-hooks.txt &&
+       ! grep prepare-commit-msg git-bugreport-hooks.txt
+'
+
+test_done
diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh
new file mode 100755 (executable)
index 0000000..809ec7b
--- /dev/null
@@ -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
index 7065a1b9373b0a02a9b632b00aba2a1c5d5b1465..1529155cf01629a2c82bc66ebc37c3617086f422 100755 (executable)
@@ -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 &&
index 5555a1524f1df58e640c4f2cc59e6c60a824d7f9..bc2d74098f035cde80de8282736188c6f6db3066 100755 (executable)
@@ -366,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
@@ -532,7 +577,7 @@ test_expect_success 'url parser rejects embedded newlines' '
        url=https://one.example.com?%0ahost=two.example.com/
        EOF
        cat >expect <<-\EOF &&
-       warning: url contains a newline in its host component: https://one.example.com?%0ahost=two.example.com/
+       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
@@ -575,4 +620,76 @@ test_expect_success 'credential system refuses to work with missing protocol' '
        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
+       --
+       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_expect_success 'credential config with partial URLs' '
+       echo "echo password=yep" | write_script git-credential-yep &&
+       test_write_lines url=https://user@example.com/repo.git >stdin &&
+       for partial in \
+               example.com \
+               user@example.com \
+               https:// \
+               https://example.com \
+               https://example.com/ \
+               https://user@example.com \
+               https://user@example.com/ \
+               https://example.com/repo.git \
+               https://user@example.com/repo.git \
+               /repo.git
+       do
+               git -c credential.$partial.helper=yep \
+                       credential fill <stdin >stdout &&
+               grep yep stdout ||
+               return 1
+       done &&
+
+       for partial in \
+               dont.use.this \
+               http:// \
+               /repo
+       do
+               git -c credential.$partial.helper=yep \
+                       credential fill <stdin >stdout &&
+               ! grep yep stdout ||
+               return 1
+       done &&
+
+       git -c credential.$partial.helper=yep \
+               -c credential.with%0anewline.username=uh-oh \
+               credential fill <stdin >stdout 2>stderr &&
+       test_i18ngrep "skipping credential lookup for key" stderr
+'
+
 test_done
index d6b54e8c65a3ec4408fe07ee76a6d8bed957207a..716bf1af9fd04e64b7d5c03570b5ba74a8529d46 100755 (executable)
@@ -107,7 +107,6 @@ test_expect_success 'store: if both xdg and home files exist, only store in home
        test_must_be_empty "$HOME/.config/git/credentials"
 '
 
-
 test_expect_success 'erase: erase matching credentials from both xdg and home files' '
        echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" &&
        mkdir -p "$HOME/.config/git" &&
@@ -120,4 +119,94 @@ test_expect_success 'erase: erase matching credentials from both xdg and home fi
        test_must_be_empty "$HOME/.config/git/credentials"
 '
 
+invalid_credential_test() {
+       test_expect_success "get: ignore credentials without $1 as invalid" '
+               echo "$2" >"$HOME/.git-credentials" &&
+               check fill store <<-\EOF
+               protocol=https
+               host=example.com
+               --
+               protocol=https
+               host=example.com
+               username=askpass-username
+               password=askpass-password
+               --
+               askpass: Username for '\''https://example.com'\'':
+               askpass: Password for '\''https://askpass-username@example.com'\'':
+               --
+               EOF
+       '
+}
+
+invalid_credential_test "scheme" ://user:pass@example.com
+invalid_credential_test "valid host/path" https://user:pass@
+invalid_credential_test "username/password" https://pass@example.com
+
+test_expect_success 'get: credentials with DOS line endings are invalid' '
+       printf "https://user:pass@example.com\r\n" >"$HOME/.git-credentials" &&
+       check fill store <<-\EOF
+       protocol=https
+       host=example.com
+       --
+       protocol=https
+       host=example.com
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username for '\''https://example.com'\'':
+       askpass: Password for '\''https://askpass-username@example.com'\'':
+       --
+       EOF
+'
+
+test_expect_success 'get: credentials with path and DOS line endings are valid' '
+       printf "https://user:pass@example.com/repo.git\r\n" >"$HOME/.git-credentials" &&
+       check fill store <<-\EOF
+       url=https://example.com/repo.git
+       --
+       protocol=https
+       host=example.com
+       username=user
+       password=pass
+       --
+       EOF
+'
+
+test_expect_success 'get: credentials with DOS line endings are invalid if path is relevant' '
+       printf "https://user:pass@example.com/repo.git\r\n" >"$HOME/.git-credentials" &&
+       test_config credential.useHttpPath true &&
+       check fill store <<-\EOF
+       url=https://example.com/repo.git
+       --
+       protocol=https
+       host=example.com
+       path=repo.git
+       username=askpass-username
+       password=askpass-password
+       --
+       askpass: Username for '\''https://example.com/repo.git'\'':
+       askpass: Password for '\''https://askpass-username@example.com/repo.git'\'':
+       --
+       EOF
+'
+
+test_expect_success 'get: store file can contain empty/bogus lines' '
+       echo "" >"$HOME/.git-credentials" &&
+       q_to_tab <<-\CREDENTIAL >>"$HOME/.git-credentials" &&
+       #comment
+       Q
+       https://user:pass@example.com
+       CREDENTIAL
+       check fill store <<-\EOF
+       protocol=https
+       host=example.com
+       --
+       protocol=https
+       host=example.com
+       username=user
+       password=pass
+       --
+       EOF
+'
+
 test_done
index eb44bafb5937000bd3b918d476b519e893f7e94d..140f45997736c0ead2d059183881a5cbf2c96758 100755 (executable)
@@ -74,13 +74,19 @@ test_expect_success 'read-tree --no-sparse-checkout with empty .git/info/sparse-
 test_expect_success 'read-tree with empty .git/info/sparse-checkout' '
        git config core.sparsecheckout true &&
        echo >.git/info/sparse-checkout &&
-       read_tree_u_must_fail -m -u HEAD &&
+       read_tree_u_must_succeed -m -u HEAD &&
        git ls-files --stage >result &&
        test_cmp expected result &&
        git ls-files -t >result &&
+       cat >expected.swt <<-\EOF &&
+       S init.t
+       S sub/added
+       S sub/addedtoo
+       S subsub/added
+       EOF
        test_cmp expected.swt result &&
-       test -f init.t &&
-       test -f sub/added
+       test -f init.t &&
+       test -f sub/added
 '
 
 test_expect_success 'match directories with trailing slash' '
@@ -233,18 +239,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
 '
index 44a91205d600e85c575a0debacdd5e2ff086b5f0..88cdde255cdad7a0b138033b3ff9a997e4a2c0b2 100755 (executable)
@@ -106,10 +106,8 @@ test_expect_success 'set enables config' '
                cd empty-config &&
                test_commit test file &&
                test_path_is_missing .git/config.worktree &&
-               test_must_fail git sparse-checkout set nothing &&
+               git sparse-checkout set nothing &&
                test_path_is_file .git/config.worktree &&
-               test_must_fail git config core.sparseCheckout &&
-               git sparse-checkout set "/*" &&
                test_cmp_config true core.sparseCheckout
        )
 '
@@ -277,15 +275,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' '
@@ -294,8 +300,8 @@ test_expect_success 'revert to old sparse-checkout on empty update' '
                echo >file &&
                git add file &&
                git commit -m "test" &&
-               test_must_fail git sparse-checkout set nothing 2>err &&
-               test_i18ngrep "Sparse checkout leaves no entry on working directory" err &&
+               git sparse-checkout set nothing 2>err &&
+               test_i18ngrep "Sparse checkout leaves no entry on working directory" err &&
                test_i18ngrep ! ".git/index.lock" err &&
                git sparse-checkout set file
        )
@@ -315,19 +321,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 &&
index a6224ef65fe90a32fc7a603db25ef4d923cc299a..e1197ac8189b942a3ab17511b30d97069c5c597e 100755 (executable)
@@ -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
index d09eff503c65aa4f9b6e95f6c1d163f0936e89f9..449ebc5657c57259c0096d1294adb96ba6f585d0 100755 (executable)
@@ -133,6 +133,30 @@ test_expect_success 'other worktree HEAD link pointing at a funny place' '
        test_i18ngrep "worktrees/other/HEAD points to something strange" out
 '
 
+test_expect_success 'commit with multiple signatures is okay' '
+       git cat-file commit HEAD >basis &&
+       cat >sigs <<-EOF &&
+       gpgsig -----BEGIN PGP SIGNATURE-----
+         VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg==
+         -----END PGP SIGNATURE-----
+       gpgsig-sha256 -----BEGIN PGP SIGNATURE-----
+         VGhpcyBpcyBub3QgcmVhbGx5IGEgc2lnbmF0dXJlLg==
+         -----END PGP SIGNATURE-----
+       EOF
+       sed -e "/^committer/q" basis >okay &&
+       cat sigs >>okay &&
+       echo >>okay &&
+       sed -e "1,/^$/d" basis >>okay &&
+       cat okay &&
+       new=$(git hash-object -t commit -w --stdin <okay) &&
+       test_when_finished "remove_object $new" &&
+       git update-ref refs/heads/bogus "$new" &&
+       test_when_finished "git update-ref -d refs/heads/bogus" &&
+       git fsck 2>out &&
+       cat out &&
+       ! grep "commit $new" out
+'
+
 test_expect_success 'email without @ is okay' '
        git cat-file commit HEAD >basis &&
        sed "s/@/AT/" basis >okay &&
index bbca7ef8da6df7903a4bb64ecad43a3eb24526c4..21583154d8e0d029168cfe87624cad89eeb765ef 100755 (executable)
@@ -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
index 076d0df7fc0602e8d47c03ffa6ddb6dff0b985a9..89e5a142c9e2284ca7931560af50074b0d6e6c8e 100755 (executable)
@@ -69,6 +69,17 @@ test_expect_success 'restore --staged uses HEAD as source' '
        test_cmp expected actual
 '
 
+test_expect_success 'restore --worktree --staged uses HEAD as source' '
+       test_when_finished git reset --hard &&
+       git show HEAD:./first.t >expected &&
+       echo dirty >>first.t &&
+       git add first.t &&
+       git restore --worktree --staged first.t &&
+       git show :./first.t >actual &&
+       test_cmp expected actual &&
+       test_cmp expected first.t
+'
+
 test_expect_success 'restore --ignore-unmerged ignores unmerged entries' '
        git init unmerged &&
        (
index 69ffe865b43965bb84bbe60402fc9c03ecc8ba9b..52585ec2aa8ba6d9104060361523c1b307bebfda 100755 (executable)
@@ -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
 '
index 0aefadacb05329d4f9b9ddf11fcbcc66e91158c8..ffdfb16f580571a50e542b8ddb50915db8ad0584 100755 (executable)
@@ -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
index d314599428129d5759e3df5e991e5a1bb90960db..e29c284b9b9b06a333c9e6d8b5240821403275b3 100755 (executable)
@@ -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
index bd808f87ed5bdf0de3f07299cc25d877219aa7ee..e024cff65cb73e3d5db18ea417bd233618689008 100755 (executable)
@@ -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 &&
index a1ec501a872b9ae4087c93a5736fcd297bda7fc8..6e032716a687a3cd64b37683c67d3a476f06287f 100755 (executable)
@@ -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
index ee8a8dba528697569a8a3f469bd28200f555d763..a927774910bc058a9d661e59b1670f4fd043fc5f 100755 (executable)
@@ -29,6 +29,13 @@ test_expect_success setup '
        test_tick &&
        git commit -m reverted-goodbye &&
        git tag reverted-goodbye &&
+       git checkout goodbye &&
+       test_tick &&
+       GIT_AUTHOR_NAME="Another Author" \
+               GIT_AUTHOR_EMAIL="another.author@example.com" \
+               git commit --amend --no-edit -m amended-goodbye &&
+       test_tick &&
+       git tag amended-goodbye &&
 
        git checkout -f skip-reference &&
        echo moo > hello &&
@@ -85,6 +92,78 @@ test_expect_success 'moved back to branch correctly' '
 
 test_debug 'gitk --all & sleep 1'
 
+test_expect_success 'correct advice upon picking empty commit' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i --onto goodbye \
+               amended-goodbye^ amended-goodbye 2>err &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct authorship when committing empty pick' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i --onto goodbye \
+               amended-goodbye^ amended-goodbye &&
+       git commit --allow-empty &&
+       git log --pretty=format:"%an <%ae>%n%ad%B" -1 amended-goodbye >expect &&
+       git log --pretty=format:"%an <%ae>%n%ad%B" -1 HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'correct advice upon rewording empty commit' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="reword 1" git rebase -i \
+                       --onto goodbye amended-goodbye^ amended-goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct advice upon editing empty commit' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="edit 1" git rebase -i \
+                       --onto goodbye amended-goodbye^ amended-goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git rebase --skip" err &&
+       test_must_fail git commit &&
+       test_i18ngrep "git rebase --skip" err
+'
+
+test_expect_success 'correct advice upon cherry-picking an empty commit during a rebase' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_amended-goodbye" \
+                       git rebase -i goodbye^ goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
+       test_must_fail git commit 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err
+'
+
+test_expect_success 'correct advice upon multi cherry-pick picking an empty commit during a rebase' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_goodbye_amended-goodbye" \
+                       git rebase -i goodbye^^ goodbye 2>err
+       ) &&
+       test_i18ngrep "previous cherry-pick is now empty" err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
+       test_must_fail git commit 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err
+'
+
 test_expect_success 'fixup that empties commit fails' '
        test_when_finished "git rebase --abort" &&
        (
index c5ce3ab760eae5b438dddf20fc1389824f21c7e3..4a7d21f898f7361ffcdf988b2458e52089b758ba 100755 (executable)
@@ -187,7 +187,7 @@ test_expect_success 'no changes are a nop' '
        git checkout branch2 &&
        git rebase -i F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-       test $(git rev-parse I) = $(git rev-parse HEAD)
+       test_cmp_rev I HEAD
 '
 
 test_expect_success 'test the [branch] option' '
@@ -196,16 +196,16 @@ test_expect_success 'test the [branch] option' '
        git commit -m "stop here" &&
        git rebase -i F branch2 &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-       test $(git rev-parse I) = $(git rev-parse branch2) &&
-       test $(git rev-parse I) = $(git rev-parse HEAD)
+       test_cmp_rev I branch2 &&
+       test_cmp_rev I HEAD
 '
 
 test_expect_success 'test --onto <branch>' '
        git checkout -b test-onto branch2 &&
        git rebase -i --onto branch1 F &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
-       test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
-       test $(git rev-parse I) = $(git rev-parse branch2)
+       test_cmp_rev HEAD^ branch1 &&
+       test_cmp_rev I branch2
 '
 
 test_expect_success 'rebase on top of a non-conflicting commit' '
@@ -214,12 +214,12 @@ test_expect_success 'rebase on top of a non-conflicting commit' '
        git rebase -i branch2 &&
        test file6 = $(git diff --name-only original-branch1) &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-       test $(git rev-parse I) = $(git rev-parse branch2) &&
-       test $(git rev-parse I) = $(git rev-parse HEAD~2)
+       test_cmp_rev I branch2 &&
+       test_cmp_rev I HEAD~2
 '
 
 test_expect_success 'reflog for the branch shows state before rebase' '
-       test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+       test_cmp_rev branch1@{1} original-branch1
 '
 
 test_expect_success 'reflog for the branch shows correct finish message' '
@@ -279,7 +279,7 @@ test_expect_success 'show conflicted patch' '
 
 test_expect_success 'abort' '
        git rebase --abort &&
-       test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+       test_cmp_rev new-branch1 HEAD &&
        test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
        test_path_is_missing .git/rebase-merge
 '
@@ -322,7 +322,7 @@ test_expect_success 'retain authorship w/ conflicts' '
        echo resolved >conflict &&
        git add conflict &&
        git rebase --continue &&
-       test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) &&
+       test_cmp_rev conflict-a^0 HEAD^ &&
        git show >out &&
        grep AttributeMe out
 '
@@ -339,7 +339,7 @@ test_expect_success 'squash' '
                        git rebase -i --onto master HEAD~2
        ) &&
        test B = $(cat file7) &&
-       test $(git rev-parse HEAD^) = $(git rev-parse master)
+       test_cmp_rev HEAD^ master
 '
 
 test_expect_success 'retain authorship when squashing' '
@@ -398,9 +398,9 @@ test_expect_success REBASE_P 'preserve merges with -p' '
        git update-index --refresh &&
        git diff-files --quiet &&
        git diff-index --quiet --cached HEAD -- &&
-       test $(git rev-parse HEAD~6) = $(git rev-parse branch1) &&
-       test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) &&
-       test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) &&
+       test_cmp_rev HEAD~6 branch1 &&
+       test_cmp_rev HEAD~4^2 to-be-preserved &&
+       test_cmp_rev HEAD^^2^ HEAD^^^ &&
        test $(git show HEAD~5:file1) = B &&
        test $(git show HEAD~3:file1) = C &&
        test $(git show HEAD:file1) = E &&
@@ -432,7 +432,7 @@ test_expect_success '--continue tries to commit' '
                git add file1 &&
                FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue
        ) &&
-       test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+       test_cmp_rev HEAD^ new-branch1 &&
        git show HEAD | grep chouette
 '
 
@@ -739,7 +739,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
                --author="Somebody else <somebody@else.com>" &&
        test $(git rev-parse branch3) != $(git rev-parse branch4) &&
        git rebase -i branch3 &&
-       test $(git rev-parse branch3) = $(git rev-parse branch4)
+       test_cmp_rev branch3 branch4
 
 '
 
@@ -798,7 +798,7 @@ test_expect_success 'rebase -i continue with unstaged submodule' '
        test_must_fail git rebase -i submodule-base &&
        git reset &&
        git rebase --continue &&
-       test $(git rev-parse submodule-base) = $(git rev-parse HEAD)
+       test_cmp_rev submodule-base HEAD
 '
 
 test_expect_success 'avoid unnecessary reset' '
@@ -821,7 +821,7 @@ test_expect_success 'reword' '
                        git rebase -i A &&
                git show HEAD | grep "E changed" &&
                test $(git rev-parse master) != $(git rev-parse HEAD) &&
-               test $(git rev-parse master^) = $(git rev-parse HEAD^) &&
+               test_cmp_rev master^ HEAD^ &&
                FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" \
                        git rebase -i A &&
                git show HEAD^ | grep "D changed" &&
@@ -885,7 +885,7 @@ test_expect_success 'always cherry-pick with --no-ff' '
                git diff HEAD~$p original-no-ff-branch~$p > out &&
                test_must_be_empty out
        done &&
-       test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
+       test_cmp_rev HEAD~3 original-no-ff-branch~3 &&
        git diff HEAD~3 original-no-ff-branch~3 > out &&
        test_must_be_empty out
 '
@@ -1734,6 +1734,32 @@ test_expect_success 'post-commit hook is called' '
        test_cmp expect actual
 '
 
+test_expect_success 'correct error message for partial commit after empty pick' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="2 1 1" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i A D
+       ) &&
+       echo x >file1 &&
+       test_must_fail git commit file1 2>err &&
+       test_i18ngrep "cannot do a partial commit during a rebase." err
+'
+
+test_expect_success 'correct error message for commit --amend after empty pick' '
+       test_when_finished "git rebase --abort" &&
+       (
+               set_fake_editor &&
+               FAKE_LINES="1 1" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i A D
+       ) &&
+       echo x>file1 &&
+       test_must_fail git commit -a --amend 2>err &&
+       test_i18ngrep "middle of a rebase -- cannot amend." err
+'
+
 # This must be the last test in this file
 test_expect_success '$EDITOR and friends are unchanged' '
        test_editor_unchanged
index 61b76f3301982d26b808044bde9ce928f663112e..927a4f4a4e4a2d1d79f3d12f77ae4918c4fd6cee 100755 (executable)
@@ -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
 '
index e85cdc7037bcb6184d1ce6f9aa7ff2cfc4af2096..946e92f8dac84edcad260d4f7354b996e54b61ec 100755 (executable)
@@ -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
 '
index d93458377690cdddddf73621f24d7782de959aec..1f32faa4a4db3d0307365af0c9cbd5bf58ff964f 100755 (executable)
@@ -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
index b97ea623639486000a2ca26da253775add02356a..ca331733fbb58e7d9f4c2e27d186b24170e56b36 100755 (executable)
@@ -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 &&
index cf8dfd6c203b30b0da4339b099cf6cabcbee04a9..4a9204b4b6491e4bb734897268a2154ff407e62c 100755 (executable)
@@ -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 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
index e1e30517ea646c56f6653c70b41968ca8c6222ed..5e1045a0afc9ecfe122904db0a3c589ff804c732 100755 (executable)
@@ -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 &&
index 78851b9a2ae84d6b73b3cffc0026082abce322e1..172562789e474042922db296c0fce842f2d25437 100755 (executable)
@@ -47,11 +47,31 @@ test_rebase 'G F B A' --keep-base
 test_rebase 'G F C E D B A' --no-fork-point
 test_rebase 'G F C D B A' --no-fork-point --onto D
 test_rebase 'G F C B A' --no-fork-point --keep-base
+
 test_rebase 'G F E D B A' --fork-point refs/heads/master
+test_rebase 'G F E D B A' --fork-point master
+
 test_rebase 'G F D B A' --fork-point --onto D refs/heads/master
+test_rebase 'G F D B A' --fork-point --onto D master
+
 test_rebase 'G F B A' --fork-point --keep-base refs/heads/master
+test_rebase 'G F B A' --fork-point --keep-base master
+
 test_rebase 'G F C E D B A' refs/heads/master
+test_rebase 'G F C E D B A' master
+
 test_rebase 'G F C D B A' --onto D refs/heads/master
+test_rebase 'G F C D B A' --onto D master
+
 test_rebase 'G F C B A' --keep-base refs/heads/master
+test_rebase 'G F C B A' --keep-base master
+
+test_expect_success 'git rebase --fork-point with ambigous refname' '
+       git checkout master &&
+       git checkout -b one &&
+       git checkout side &&
+       git tag one &&
+       test_must_fail git rebase --fork-point --onto D one
+'
 
 test_done
index 6c9d4a13758194dfcf4b005e36f8358ed5a27f80..6f0452c0eac516796e27c5689b2e091c2b21b8ce 100755 (executable)
@@ -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 (executable)
index 0000000..b47c59c
--- /dev/null
@@ -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
index 9bd482ce3b8912b2016fe2997c50d59b5a5162c5..752bc43487196a4be33ff7df770384f331a68315 100755 (executable)
@@ -161,6 +161,29 @@ test_expect_success 'successful commit clears CHERRY_PICK_HEAD' '
 
        test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
 '
+
+test_expect_success 'partial commit of cherry-pick fails' '
+       pristine_detach initial &&
+
+       test_must_fail git cherry-pick picked &&
+       echo resolved >foo &&
+       git add foo &&
+       test_must_fail git commit foo 2>err &&
+
+       test_i18ngrep "cannot do a partial commit during a cherry-pick." err
+'
+
+test_expect_success 'commit --amend of cherry-pick fails' '
+       pristine_detach initial &&
+
+       test_must_fail git cherry-pick picked &&
+       echo resolved >foo &&
+       git add foo &&
+       test_must_fail git commit --amend 2>err &&
+
+       test_i18ngrep "in the middle of a cherry-pick -- cannot amend." err
+'
+
 test_expect_success 'successful final commit clears cherry-pick state' '
        pristine_detach initial &&
 
index 793bcc7fe3246e8fc375b8678bb99f7bc875a48c..5b94fdaa6713a2bcf7958d6630925a09fea05ed6 100755 (executable)
@@ -123,7 +123,8 @@ test_expect_success 'revert --skip to skip commit' '
 test_expect_success 'skip "empty" commit' '
        pristine_detach picked &&
        test_commit dummy foo d &&
-       test_must_fail git cherry-pick anotherpick &&
+       test_must_fail git cherry-pick anotherpick 2>err &&
+       test_i18ngrep "git cherry-pick --skip" err &&
        git cherry-pick --skip &&
        test_cmp_rev dummy HEAD
 '
diff --git a/t/t3514-cherry-pick-revert-gpg.sh b/t/t3514-cherry-pick-revert-gpg.sh
new file mode 100755 (executable)
index 0000000..5b2e250
--- /dev/null
@@ -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
index 88bc799807f7dd99ba98cda2b698ef83e027f7b7..b7d4ba608cbc96998fdac714ebb53a0f53c46f9e 100755 (executable)
@@ -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
 '
 
index 5bae6e50f1f3e76c1615bf5e3031e99f92d619b9..b3d8bb7577661c0211bc349900cc321f9971ff05 100755 (executable)
@@ -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 >&&
        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
index 3ad23e2502bea0e861d21822e5ef6b3559e0f8bd..9f7ca9896755ecf38ad09f136cc75d6cbb18542b 100755 (executable)
@@ -1290,4 +1290,18 @@ test_expect_success 'stash handles skip-worktree entries nicely' '
        git rev-parse --verify refs/stash:A.t
 '
 
+test_expect_success 'stash -c stash.useBuiltin=false warning ' '
+       expected="stash.useBuiltin support has been removed" &&
+
+       git -c stash.useBuiltin=false stash 2>err &&
+       test_i18ngrep "$expected" err &&
+       env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err &&
+       test_i18ngrep "$expected" err &&
+
+       git -c stash.useBuiltin=true stash 2>err &&
+       test_must_be_empty err &&
+       env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err &&
+       test_must_be_empty err
+'
+
 test_done
index 9546b6f8a4e2fdf0c25f3b463de45a0feae4695c..accfe3845c418ee90d8c9b1ec3100fa3e964d8d4 100755 (executable)
@@ -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
 '
index dde3f11fec364c22470b56f44508c0cd3284e4b2..3f60f7d96ce1998cd977751158c09cadfd926d15 100755 (executable)
@@ -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 (file)
index 0000000..93042ed
--- /dev/null
@@ -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 (file)
index 0000000..4d0bde6
--- /dev/null
@@ -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 (file)
index 0000000..1fa5967
--- /dev/null
@@ -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
+$
index 2afe91f1167b14329108d438fa9cf9ed7963acad..3f9b872eceb734cb1e7adbd53a4de0580b1cd524 100644 (file)
@@ -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>
index d0f308ab2bb73048cf153e209d7046e47682a524..f5e20e1e14aaef179ac9570b0e16bc47077ff79d 100644 (file)
@@ -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>
index b653dd7d44521270995b456d7b8e2dcfd6957af5..db7e733af9e5be1abe8991e1513f9d31949433f4 100755 (executable)
@@ -1160,6 +1160,59 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' '
        check_author "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"
 '
 
+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
index 02255a08bf1718659f9815e37644c07a196ab850..9d077975791c1c002b4a927dfae71de594c8855f 100755 (executable)
@@ -38,6 +38,7 @@ diffpatterns="
        golang
        html
        java
+       markdown
        matlab
        objc
        pascal
diff --git a/t/t4018/markdown-heading-indented b/t/t4018/markdown-heading-indented
new file mode 100644 (file)
index 0000000..1991c2b
--- /dev/null
@@ -0,0 +1,6 @@
+Indented headings are allowed, as long as the indent is no more than 3 spaces.
+
+   ### RIGHT
+
+- something
+- ChangeMe
diff --git a/t/t4018/markdown-heading-non-headings b/t/t4018/markdown-heading-non-headings
new file mode 100644 (file)
index 0000000..c479c1a
--- /dev/null
@@ -0,0 +1,17 @@
+Headings can be right next to other lines of the file:
+# RIGHT
+Indents of four or more spaces make a code block:
+
+    # code comment, not heading
+
+If there's no space after the final hash, it's not a heading:
+
+#hashtag
+
+Sequences of more than 6 hashes don't make a heading:
+
+####### over-enthusiastic heading
+
+So the detected heading should be right up at the start of this file.
+
+ChangeMe
index 4f4b541658a1b604ee27aff76a4e2047fca8111c..0b78573733b30716d1ed4fb8bd2fdeee0c5b9d83 100755 (executable)
@@ -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" &&
index 2affd7a100996f93839eec682e4d7381e3ac0b2a..0f7a6d97a8b7e23959ef0c0bf7968fd69339283a 100755 (executable)
@@ -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
 }
index 4831ad35e61436fdc400533fb0e7f88e6d27027c..c1ed1c2fc4a7f36178220f2c9ead05bfb826ee33 100755 (executable)
@@ -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
index 971a5a7512ac772f8b7e25a6a75bb4ac2ca43fd3..0ca29821ece3fda711c5c8c82791bed7a829b538 100755 (executable)
@@ -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)
index cb45271457266daf4d809362cb846dc0f879dd48..bda4586a7951a234b50ffbb28cdd4d7df05e4c56 100755 (executable)
@@ -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 &&
index 0f766ba65f598c105bbce6f8f3aa2d4cda1c0de3..f1ea7d97f589c7746cf3a19908cdb23927c09067 100755 (executable)
@@ -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
 '
 
@@ -1627,6 +1676,66 @@ test_expect_success GPG 'log --graph --show-signature for merged tag in shallow
        grep "tag signed_tag_shallow names a non-parent $hash" actual
 '
 
+test_expect_success GPG 'log --graph --show-signature for merged tag with missing key' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-nokey master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-nokey master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_nokey &&
+       git checkout plain-nokey &&
+       git merge --no-ff -m msg signed_tag_nokey &&
+       GNUPGHOME=. git log --graph --show-signature -n1 plain-nokey >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpg: Signature made" actual &&
+       grep "^| | gpg: Can'"'"'t check signature: \(public key not found\|No public key\)" actual
+'
+
+test_expect_success GPG 'log --graph --show-signature for merged tag with bad signature' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-bad master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-bad master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_bad &&
+       git cat-file tag signed_tag_bad >raw &&
+       sed -e "s/signed_tag_msg/forged/" raw >forged &&
+       git hash-object -w -t tag forged >forged.tag &&
+       git checkout plain-bad &&
+       git merge --no-ff -m msg "$(cat forged.tag)" &&
+       git log --graph --show-signature -n1 plain-bad >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpg: Signature made" actual &&
+       grep "^| | gpg: BAD signature from" actual
+'
+
+test_expect_success GPG 'log --show-signature for merged tag with GPG failure' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       git checkout -b plain-fail master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-fail master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_fail &&
+       git checkout plain-fail &&
+       git merge --no-ff -m msg signed_tag_fail &&
+       TMPDIR="$(pwd)/bogus" git log --show-signature -n1 plain-fail >actual &&
+       grep "^merged tag" actual &&
+       grep "^No signature" actual &&
+       ! grep "^gpg: Signature made" actual
+'
+
 test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
        test_when_finished "git reset --hard && git checkout master" &&
        test_config gpg.format x509 &&
@@ -1648,6 +1757,51 @@ test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
        grep "^| | gpgsm: Good signature" actual
 '
 
+test_expect_success GPGSM 'log --graph --show-signature for merged tag x509 missing key' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       test_config gpg.format x509 &&
+       test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+       git checkout -b plain-x509-nokey master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-x509-nokey master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_x509_nokey &&
+       git checkout plain-x509-nokey &&
+       git merge --no-ff -m msg signed_tag_x509_nokey &&
+       GNUPGHOME=. git log --graph --show-signature -n1 plain-x509-nokey >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpgsm: certificate not found" actual
+'
+
+test_expect_success GPGSM 'log --graph --show-signature for merged tag x509 bad signature' '
+       test_when_finished "git reset --hard && git checkout master" &&
+       test_config gpg.format x509 &&
+       test_config user.signingkey $GIT_COMMITTER_EMAIL &&
+       git checkout -b plain-x509-bad master &&
+       echo aaa >bar &&
+       git add bar &&
+       git commit -m bar_commit &&
+       git checkout -b tagged-x509-bad master &&
+       echo bbb >baz &&
+       git add baz &&
+       git commit -m baz_commit &&
+       git tag -s -m signed_tag_msg signed_tag_x509_bad &&
+       git cat-file tag signed_tag_x509_bad >raw &&
+       sed -e "s/signed_tag_msg/forged/" raw >forged &&
+       git hash-object -w -t tag forged >forged.tag &&
+       git checkout plain-x509-bad &&
+       git merge --no-ff -m msg "$(cat forged.tag)" &&
+       git log --graph --show-signature -n1 plain-x509-bad >actual &&
+       grep "^|\\\  merged tag" actual &&
+       grep "^| | gpgsm: Signature made" actual &&
+       grep "^| | gpgsm: invalid signature" actual
+'
+
+
 test_expect_success GPG '--no-show-signature overrides --show-signature' '
        git log -1 --show-signature --no-show-signature signed >actual &&
        ! grep "^gpg:" actual
index 4c8f3b8e1bdd084a6407d76ff26cff0048e90bdf..6cdbe4747aa837376fd01c94420beb1bd1bb6d56 100755 (executable)
@@ -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 (executable)
index 0000000..21b68dd
--- /dev/null
@@ -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
index fd3bdbfe2c0a30a0b712b1a59c2bacf0efde167c..daf01c309d03203026d27f2ff978974053e8eeee 100755 (executable)
@@ -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
index 106eddbd85b04ac8539b722ade63936e28325840..3b76d2eb653f7aed47d4c7391105ec6f24623e97 100755 (executable)
@@ -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
index 9bf920ae1716dd3703d206be05605cfdfde21984..424599959c9c31a1392ef584b924a787895906bb 100755 (executable)
@@ -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" &&
@@ -12,6 +14,10 @@ test_expect_success 'setup full repo' '
        test_oid_init
 '
 
+test_expect_success POSIXPERM 'tweak umask for modebit tests' '
+       umask 022
+'
+
 test_expect_success 'verify graph with no graph file' '
        cd "$TRASH_DIRECTORY/full" &&
        git commit-graph verify
@@ -43,7 +49,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
@@ -96,6 +102,13 @@ test_expect_success 'write graph' '
        graph_read_expect "3"
 '
 
+test_expect_success POSIXPERM 'write graph has correct permissions' '
+       test_path_is_file $objdir/info/commit-graph &&
+       echo "-r--r--r--" >expect &&
+       test_modebits $objdir/info/commit-graph >actual &&
+       test_cmp expect actual
+'
+
 graph_git_behavior 'graph exists' full commits/3 commits/1
 
 test_expect_success 'Add more commits' '
@@ -421,7 +434,8 @@ GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES))
 corrupt_graph_setup() {
        cd "$TRASH_DIRECTORY/full" &&
        test_when_finished mv commit-graph-backup $objdir/info/commit-graph &&
-       cp $objdir/info/commit-graph commit-graph-backup
+       cp $objdir/info/commit-graph commit-graph-backup &&
+       chmod u+w $objdir/info/commit-graph
 }
 
 corrupt_graph_verify() {
@@ -435,6 +449,7 @@ corrupt_graph_verify() {
        fi &&
        git status --short &&
        GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD=true git commit-graph write &&
+       chmod u+w $objdir/info/commit-graph &&
        git commit-graph verify
 }
 
index 43a7a66c9d1b50640775d6b38df7a31b32e8e548..030a7222b2aca5e97652194ea9a0d8cdf4ca9ab0 100755 (executable)
@@ -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 (file)
index 0000000..e466b8e
Binary files /dev/null and b/t/t5319/no-objects.midx differ
index 7124b5581a0e3e31ae9b78b46aec9f059eeb7c1a..a581eaf52936292f28b2ff9542bccd8f2ea5152a 100755 (executable)
@@ -105,14 +105,16 @@ test_expect_success 'non-sparse pack-objects' '
        test_cmp required_objects.txt nonsparse_required_objects.txt
 '
 
+# --sparse is enabled by default by pack.useSparse
 test_expect_success 'sparse pack-objects' '
+       GIT_TEST_PACK_SPARSE=-1 &&
        git rev-parse                   \
                topic1                  \
                topic1^{tree}           \
                topic1:f3               \
                topic1:f3/f4            \
                topic1:f3/f4/data.txt | sort >expect_sparse_objects.txt &&
-       git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack &&
+       git pack-objects --stdout --revs <packinput.txt >sparse.pack &&
        git index-pack -o sparse.idx sparse.pack &&
        git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt &&
        test_cmp expect_sparse_objects.txt sparse_objects.txt
index 53b2e6b4555de7d7cc27c727e2e7a3d7a4ff3f5b..269d0964a3e0b51bceffe0362f45f5c0a8bd229c 100755 (executable)
@@ -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 &&
@@ -36,6 +37,10 @@ graph_read_expect() {
        test_cmp expect output
 }
 
+test_expect_success POSIXPERM 'tweak umask for modebit tests' '
+       umask 022
+'
+
 test_expect_success 'create commits and write commit-graph' '
        for i in $(test_seq 3)
        do
@@ -210,8 +215,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 +362,67 @@ 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
+       )
+'
+
+while read mode modebits
+do
+       test_expect_success POSIXPERM "split commit-graph respects core.sharedrepository $mode" '
+               rm -rf $graphdir $infodir/commit-graph &&
+               git reset --hard commits/1 &&
+               test_config core.sharedrepository "$mode" &&
+               git commit-graph write --split --reachable &&
+               ls $graphdir/graph-*.graph >graph-files &&
+               test_line_count = 1 graph-files &&
+               echo "$modebits" >expect &&
+               test_modebits $graphdir/graph-*.graph >actual &&
+               test_cmp expect actual &&
+               test_modebits $graphdir/commit-graph-chain >actual &&
+               test_cmp expect actual
+       '
+done <<\EOF
+0666 -r--r--r--
+0600 -r--------
+EOF
+
 test_done
index baa1a99f45f8540b1c5c91841e9797e85e902b63..52dd1a688cd052a098586b6e1a89b2f621fdd99c 100755 (executable)
@@ -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 &&
index 645b4c78d356971cdf096456d7e90463f58cd0e3..a32efe2b6cdd84690bdb5193609105204ff1dd39 100755 (executable)
@@ -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' '
index 04b35402c7aab16319713f8aed205e6c9d809893..e98c3a01741d8ed830d6342dd566b24eee25e8b6 100755 (executable)
@@ -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 &&
index 9ff041a093e71aac932f5e563d4922930bc62ff2..9c6218f568ea3bc4c61a1377fc4b2be150b4be1d 100755 (executable)
@@ -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
index 2f86fca0428cde001e2e6b3735e215714037059d..37535d63a9cc0c8d611747bd3235995d0b98a115 100755 (executable)
@@ -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 --rebase
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash=true' '
        test_config rebase.autostash true &&
-       test_pull_autostash --rebase --autostash
+       test_pull_autostash --rebase --autostash
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash=false' '
        test_config rebase.autostash false &&
-       test_pull_autostash --rebase --autostash
+       test_pull_autostash --rebase --autostash
 '
 
 test_expect_success 'pull --rebase --autostash & rebase.autostash unset' '
        test_unconfig rebase.autostash &&
-       test_pull_autostash --rebase --autostash
+       test_pull_autostash --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 --autostash
 '
 
 test_expect_success 'pull --no-autostash & pull.rebase=true' '
index ccde8ba491e8f119dc48accf5c10f533bf74c2fd..159afa7ac81396e1d9915335f030cd0fcc8b7b3f 100755 (executable)
@@ -11,10 +11,10 @@ test_expect_success 'setup' '
         git commit -m one)
 '
 
-test_expect_success 'git pull -q' '
+test_expect_success 'git pull -q --no-rebase' '
        mkdir clonedq &&
        (cd clonedq && git init &&
-       git pull -q "../parent" >out 2>err &&
+       git pull -q --no-rebase "../parent" >out 2>err &&
        test_must_be_empty err &&
        test_must_be_empty out)
 '
@@ -30,10 +30,10 @@ test_expect_success 'git pull -q --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull' '
+test_expect_success 'git pull --no-rebase' '
        mkdir cloned &&
        (cd cloned && git init &&
-       git pull "../parent" >out 2>err &&
+       git pull --no-rebase "../parent" >out 2>err &&
        test -s err &&
        test_must_be_empty out)
 '
@@ -46,10 +46,10 @@ test_expect_success 'git pull --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull -v' '
+test_expect_success 'git pull -v --no-rebase' '
        mkdir clonedv &&
        (cd clonedv && git init &&
-       git pull -v "../parent" >out 2>err &&
+       git pull -v --no-rebase "../parent" >out 2>err &&
        test -s err &&
        test_must_be_empty out)
 '
@@ -62,25 +62,25 @@ test_expect_success 'git pull -v --rebase' '
        test_must_be_empty out)
 '
 
-test_expect_success 'git pull -v -q' '
+test_expect_success 'git pull -v -q --no-rebase' '
        mkdir clonedvq &&
        (cd clonedvq && git init &&
-       git pull -v -q "../parent" >out 2>err &&
+       git pull -v -q --no-rebase "../parent" >out 2>err &&
        test_must_be_empty out &&
        test_must_be_empty err)
 '
 
-test_expect_success 'git pull -q -v' '
+test_expect_success 'git pull -q -v --no-rebase' '
        mkdir clonedqv &&
        (cd clonedqv && git init &&
-       git pull -q -v "../parent" >out 2>err &&
+       git pull -q -v --no-rebase "../parent" >out 2>err &&
        test_must_be_empty out &&
        test -s err)
 '
 test_expect_success 'git pull --cleanup errors early on invalid argument' '
        mkdir clonedcleanup &&
        (cd clonedcleanup && git init &&
-       test_must_fail git pull --cleanup invalid "../parent" >out 2>err &&
+       test_must_fail git pull --no-rebase --cleanup invalid "../parent" >out 2>err &&
        test_must_be_empty out &&
        test -s err)
 '
index 4f681dbbe11c7f37d47d30bfa33a215c0dd9e189..d427a2d7f7a1c40dd77ac3809af0a15187870e32 100755 (executable)
@@ -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
 '
 
index 23be8ce92d67824166156cf0c5ca380022b79efc..afc680d5e3d3940ee63a1564b9ef5aa1ca39c9a4 100755 (executable)
@@ -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' '
index 7079bcf9a0567e926a37031dd56711a0b093d712..620c30d58f00b978bd20753acbf12181d5de2abe 100755 (executable)
@@ -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 (executable)
index 0000000..1b19b3e
--- /dev/null
@@ -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
index ea2688bde59c9fa0bfe9829e7f8913c280164cde..50485300eb19ea757dac4ee36ec395329d33e1f0 100755 (executable)
@@ -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' '
index 4a110b307ee53e623bff98a68307c4947139c39f..3f4ac71f83b571a61c14b5627d843b73cb244f0e 100755 (executable)
@@ -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
 '
index 0c74b4e21a3ef17499401cdd1f4eeaab5c392137..2f7be23044712ae832428fac09ad0a3cfd6a1fff 100755 (executable)
@@ -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 &&
        (
index 9108ff6fbd6f52d9df6b9ec52226a71ced8fdc06..6d5a977fcba60f70d2a7329eba4c70e7ac66e925 100755 (executable)
@@ -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
 '
 
index 60c1ba951b7d4178708574d10986659749925059..8e0fd398236b5fd503565db7c2e266aef744476a 100755 (executable)
@@ -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
index e36ac01661d1b5cd26b0ae4fe615b9ccf44e02c8..e3b436d8ae791c587e92ac8d665b189ed1f82220 100755 (executable)
@@ -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
index 77bb91e97692272b572cd0dd89053e0bb8c7e050..8a27452a51159b51922ff64fffab4b801a20b9db 100755 (executable)
@@ -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)
 '
@@ -384,12 +384,11 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' '
        grep "want $(cat hash)" trace
 '
 
-# The following two tests must be in this order, or else
-# the first will not fail. It is important that the srv.bare
-# repository did not have tags during clone, but has tags
+# The following two tests must be in this order. It is important that
+# the srv.bare repository did not have tags during clone, but has tags
 # in the fetch.
 
-test_expect_failure 'verify fetch succeeds when asking for new tags' '
+test_expect_success 'verify fetch succeeds when asking for new tags' '
        git clone --filter=blob:none "file://$(pwd)/srv.bare" tag-test &&
        for i in I J K
        do
@@ -415,6 +414,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
 
index 7fba3063bf9150687df4bcbd56cdfecf50c8e289..a34460f7d82ea0ce894dab17ad3b6a7f7765b408 100755 (executable)
@@ -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 (executable)
index 0000000..950cfb2
--- /dev/null
@@ -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
index 121e5c6edb0a21eb8a6d72d01c7edd5f45d11b2f..0f04b6cddb723316b0edf1135086405a09e4205b 100755 (executable)
@@ -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' '
index a10f0df02b0ec8acdcaf7c90d02da4a9c20ffb1b..b6fa43ace0184f6504aeffd813964f5ac95a9fef 100755 (executable)
@@ -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
index 821a0c88cf0221c3662693afaadf9280dab0be8d..ac31faefa15acd484f5c6c3042fa080d78a770bd 100755 (executable)
@@ -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
@@ -792,6 +792,13 @@ test_expect_success 'bisect replay with old and new' '
        git bisect reset
 '
 
+test_expect_success 'bisect replay with CRLF log' '
+       append_cr <log_to_replay.txt >log_to_replay_crlf.txt &&
+       git bisect replay log_to_replay_crlf.txt >bisect_result_crlf &&
+       grep "$HASH2 is the first new commit" bisect_result_crlf &&
+       git bisect reset
+'
+
 test_expect_success 'bisect cannot mix old/new and good/bad' '
        git bisect start &&
        git bisect bad $HASH4 &&
index 145603f124574cb209155dd643fdc555685a79fa..2b551e6fd0ce1d6706334eaa2a65cda7f9e56618 100755 (executable)
@@ -53,4 +53,25 @@ test_expect_success 'blob:limit filter with specified blob' '
        test_bitmap_traversal expect actual
 '
 
+test_expect_success 'tree:0 filter' '
+       git rev-list --objects --filter=tree:0 HEAD >expect &&
+       git rev-list --use-bitmap-index \
+                    --objects --filter=tree:0 HEAD >actual &&
+       test_bitmap_traversal expect actual
+'
+
+test_expect_success 'tree:0 filter with specified blob, tree' '
+       git rev-list --objects --filter=tree:0 HEAD HEAD:two.t >expect &&
+       git rev-list --use-bitmap-index \
+                    --objects --filter=tree:0 HEAD HEAD:two.t >actual &&
+       test_bitmap_traversal expect actual
+'
+
+test_expect_success 'tree:1 filter' '
+       git rev-list --objects --filter=tree:1 HEAD >expect &&
+       git rev-list --use-bitmap-index \
+                    --objects --filter=tree:1 HEAD >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 34502e3a505443d02a57ccc241e178feeffd1ce5..f822d5d3285040f8a90fab1e7fa4e327196f60f2 100755 (executable)
@@ -129,12 +129,30 @@ test_expect_success 'rename tag A to Q locally' '
        mv .git/refs/tags/A .git/refs/tags/Q
 '
 cat - >err.expect <<EOF
-warning: tag 'A' is really 'Q' here
+warning: tag 'Q' is externally known as 'A'
 EOF
 check_describe A-* HEAD
 test_expect_success 'warning was displayed for Q' '
        test_i18ncmp err.expect err.actual
 '
+test_expect_success 'misnamed annotated tag forces long output' '
+       description=$(git describe --no-long Q^0) &&
+       expr "$description" : "A-0-g[0-9a-f]*$" &&
+       git rev-parse --verify "$description" >actual &&
+       git rev-parse --verify Q^0 >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'abbrev=0 will not break misplaced tag (1)' '
+       description=$(git describe --abbrev=0 Q^0) &&
+       expr "$description" : "A-0-g[0-9a-f]*$"
+'
+
+test_expect_success 'abbrev=0 will not break misplaced tag (2)' '
+       description=$(git describe --abbrev=0 c^0) &&
+       expr "$description" : "A-1-g[0-9a-f]*$"
+'
+
 test_expect_success 'rename tag Q back to A' '
        mv .git/refs/tags/Q .git/refs/tags/A
 '
index 8a72b4c43a4ff5ba2e2aeb99121ba8ba054dc396..b15582a7a2b50306c440f7203e073abd975706df 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='fmt-merge-msg test'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 test_expect_success setup '
        echo one >one &&
@@ -73,6 +74,10 @@ test_expect_success setup '
        apos="'\''"
 '
 
+test_expect_success GPG 'set up a signed tag' '
+       git tag -s -m signed-tag-msg signed-good-tag left
+'
+
 test_expect_success 'message for merging local branch' '
        echo "Merge branch ${apos}left${apos}" >expected &&
 
@@ -83,6 +88,24 @@ test_expect_success 'message for merging local branch' '
        test_cmp expected actual
 '
 
+test_expect_success GPG 'message for merging local tag signed by good key' '
+       git checkout master &&
+       git fetch . signed-good-tag &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "^Merge tag ${apos}signed-good-tag${apos}" actual &&
+       grep "^# gpg: Signature made" actual &&
+       grep "^# gpg: Good signature from" actual
+'
+
+test_expect_success GPG 'message for merging local tag signed by unknown key' '
+       git checkout master &&
+       git fetch . signed-good-tag &&
+       GNUPGHOME=. git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "^Merge tag ${apos}signed-good-tag${apos}" actual &&
+       grep "^# gpg: Signature made" actual &&
+       grep "^# gpg: Can${apos}t check signature: \(public key not found\|No public key\)" actual
+'
+
 test_expect_success 'message for merging external branch' '
        echo "Merge branch ${apos}left${apos} of $(pwd)" >expected &&
 
index 9c910ce746733cabaafd34adceeb59cf53dca1f7..da59fadc5d8f915cbb60cd426c58becb04e7c42a 100755 (executable)
@@ -20,6 +20,10 @@ setdate_and_increment () {
 }
 
 test_expect_success setup '
+       test_oid_cache <<-EOF &&
+       disklen sha1:138
+       disklen sha256:154
+       EOF
        setdate_and_increment &&
        echo "Using $datestamp" > one &&
        git add one &&
@@ -50,6 +54,9 @@ test_atom() {
        "
 }
 
+hexlen=$(test_oid hexsz)
+disklen=$(test_oid disklen)
+
 test_atom head refname refs/heads/master
 test_atom head refname: refs/heads/master
 test_atom head refname:short master
@@ -82,9 +89,9 @@ test_atom head push:rstrip=-1 refs
 test_atom head push:strip=1 remotes/myfork/master
 test_atom head push:strip=-1 master
 test_atom head objecttype commit
-test_atom head objectsize 171
-test_atom head objectsize:disk 138
-test_atom head deltabase 0000000000000000000000000000000000000000
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $disklen
+test_atom head deltabase $ZERO_OID
 test_atom head objectname $(git rev-parse refs/heads/master)
 test_atom head objectname:short $(git rev-parse --short refs/heads/master)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
@@ -125,11 +132,11 @@ test_atom tag refname:short testtag
 test_atom tag upstream ''
 test_atom tag push ''
 test_atom tag objecttype tag
-test_atom tag objectsize 154
-test_atom tag objectsize:disk 138
-test_atom tag '*objectsize:disk' 138
-test_atom tag deltabase 0000000000000000000000000000000000000000
-test_atom tag '*deltabase' 0000000000000000000000000000000000000000
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $disklen
+test_atom tag '*objectsize:disk' $disklen
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
 test_atom tag objectname $(git rev-parse refs/tags/testtag)
 test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
@@ -139,7 +146,7 @@ test_atom tag parent ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
-test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
 test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
@@ -643,17 +650,59 @@ test_atom refs/tags/signed-long contents "subject line
 body contents
 $sig"
 
-cat >expected <<EOF
-$(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
-$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
-EOF
+test_expect_success 'set up multiple-sort tags' '
+       for when in 100000 200000
+       do
+               for email in user1 user2
+               do
+                       for ref in ref1 ref2
+                       do
+                               GIT_COMMITTER_DATE="@$when +0000" \
+                               GIT_COMMITTER_EMAIL="$email@example.com" \
+                               git tag -m "tag $ref-$when-$email" \
+                               multi-$ref-$when-$email || return 1
+                       done
+               done
+       done
+'
 
 test_expect_success 'Verify sort with multiple keys' '
-       git for-each-ref --format="%(objectname) %(taggeremail) %(refname)" --sort=objectname --sort=taggeremail \
-               refs/tags/bogo refs/tags/master > actual &&
+       cat >expected <<-\EOF &&
+       100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+       100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+       100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+       100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+       200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+       200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+       200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+       200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+       EOF
+       git for-each-ref \
+               --format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+               --sort=-refname \
+               --sort=taggeremail \
+               --sort=taggerdate \
+               "refs/tags/multi-*" >actual &&
        test_cmp expected actual
 '
 
+test_expect_success 'equivalent sorts fall back on refname' '
+       cat >expected <<-\EOF &&
+       100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+       100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+       100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+       100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+       200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+       200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+       200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+       200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+       EOF
+       git for-each-ref \
+               --format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+               --sort=taggerdate \
+               "refs/tags/multi-*" >actual &&
+       test_cmp expected actual
+'
 
 test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
        test_when_finished "git checkout master" &&
@@ -888,4 +937,44 @@ test_expect_success 'for-each-ref --ignore-case ignores case' '
        test_cmp expect actual
 '
 
+test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
+       # name refs numerically to avoid case-insensitive filesystem conflicts
+       nr=0 &&
+       for email in a A b B
+       do
+               for subject in a A b B
+               do
+                       GIT_COMMITTER_EMAIL="$email@example.com" \
+                       git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+                       nr=$((nr+1))||
+                       return 1
+               done
+       done &&
+       git for-each-ref --ignore-case \
+               --format="%(taggeremail) %(subject) %(refname)" \
+               --sort=refname \
+               --sort=subject \
+               --sort=taggeremail \
+               refs/tags/icase-* >actual &&
+       cat >expect <<-\EOF &&
+       <a@example.com> tag a refs/tags/icase-00
+       <a@example.com> tag A refs/tags/icase-01
+       <A@example.com> tag a refs/tags/icase-04
+       <A@example.com> tag A refs/tags/icase-05
+       <a@example.com> tag b refs/tags/icase-02
+       <a@example.com> tag B refs/tags/icase-03
+       <A@example.com> tag b refs/tags/icase-06
+       <A@example.com> tag B refs/tags/icase-07
+       <b@example.com> tag a refs/tags/icase-08
+       <b@example.com> tag A refs/tags/icase-09
+       <B@example.com> tag a refs/tags/icase-12
+       <B@example.com> tag A refs/tags/icase-13
+       <b@example.com> tag b refs/tags/icase-10
+       <b@example.com> tag B refs/tags/icase-11
+       <B@example.com> tag b refs/tags/icase-14
+       <B@example.com> tag B refs/tags/icase-15
+       EOF
+       test_cmp expect actual
+'
+
 test_done
index b24d85003606bf2076902d944fb395bf55761d5a..475564bee70ab705f7e28de82c687da6ea6a82c1 100755 (executable)
@@ -51,8 +51,10 @@ test_expect_success 'setup' '
        done &&
        git commit-graph write --reachable &&
        mv .git/objects/info/commit-graph commit-graph-full &&
+       chmod u+w commit-graph-full &&
        git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
        mv .git/objects/info/commit-graph commit-graph-half &&
+       chmod u+w commit-graph-half &&
        git config core.commitGraph true
 '
 
index 6db92bd3ba62db4bb4e653480faf90f20d2b0bcb..74b637deb259a9c65e9abcac5ded9bcf5bf76b67 100755 (executable)
@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '
        hint: already a tag. If you meant to tag the object that it points to, use:
        hint: |
        hint:   git tag -f nested annotated-v4.0^{}
+       hint: Disable this message with "git config advice.nestedTag false"
        EOF
        git tag -m nested nested annotated-v4.0 2>actual &&
        test_i18ncmp expect actual
index 190ae149cf3cb6daa0a89d50a5a44ccafdd2aaec..428cff9cf3f5bfc6947dcb84df4c53ce3b14037b 100755 (executable)
@@ -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
 '
 
index a1cb9ff858e4a2113bbf7fb732b9e6d0cb308445..67346424a53960c33becefe865ad8881c1f1777e 100755 (executable)
@@ -5,7 +5,6 @@ test_description='reset can handle submodules'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
-KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
 KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
index e3e2aab3b0a106a5a137361c3f1765515c1b1931..956e17abb3d7d5fb480c84c977067837c6baca61 100755 (executable)
@@ -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 &&
index 34ac28c056bbe8c80178082c770f392bff333fa7..a3892f494b68c866b150fdead376942bff47541b 100755 (executable)
@@ -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
 }
 
 
index 482ce3510edd1007118d52fbb36111a8b0d09247..8e969f3e3680d856073460b5b15e524ad9170d7f 100755 (executable)
@@ -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"' '
index 0c06d22a0079a61f04cd83dad29562dc4d017218..6baaa1ad91d4e51364afb12e62be6a1b48f2ea74 100755 (executable)
@@ -6,6 +6,11 @@ GNUPGHOME_NOT_USED=$GNUPGHOME
 . "$TEST_DIRECTORY/lib-gpg.sh"
 
 test_expect_success GPG 'create signed commits' '
+       test_oid_cache <<-\EOF &&
+       header sha1:gpgsig
+       header sha256:gpgsig-sha256
+       EOF
+
        test_when_finished "test_unconfig commit.gpgsign" &&
 
        echo 1 >file && git add file &&
@@ -155,6 +160,11 @@ test_expect_success GPG 'verify signatures with --raw' '
        )
 '
 
+test_expect_success GPG 'proper header is used for hash algorithm' '
+       git cat-file commit fourth-signed >output &&
+       grep "^$(test_oid header) -----BEGIN PGP SIGNATURE-----" output
+'
+
 test_expect_success GPG 'show signed commit with signature' '
        git show -s initial >commit &&
        git show -s --show-signature initial >show &&
@@ -162,7 +172,7 @@ test_expect_success GPG 'show signed commit with signature' '
        git cat-file commit initial >cat &&
        grep -v -e "gpg: " -e "Warning: " show >show.commit &&
        grep -e "gpg: " -e "Warning: " show >show.gpg &&
-       grep -v "^ " cat | grep -v "^gpgsig " >cat.commit &&
+       grep -v "^ " cat | grep -v "^$(test_oid header) " >cat.commit &&
        test_cmp show.commit commit &&
        test_cmp show.gpg verify.2 &&
        test_cmp cat.commit verify.1
@@ -299,10 +309,10 @@ test_expect_success GPG 'check config gpg.format values' '
 test_expect_success GPG 'detect fudged commit with double signature' '
        sed -e "/gpgsig/,/END PGP/d" forged1 >double-base &&
        sed -n -e "/gpgsig/,/END PGP/p" forged1 | \
-               sed -e "s/^gpgsig//;s/^ //" | gpg --dearmor >double-sig1.sig &&
+               sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig &&
        gpg -o double-sig2.sig -u 29472784 --detach-sign double-base &&
        cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc &&
-       sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/gpgsig /;2,\$s/^/ /" \
+       sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \
                double-combined.asc > double-gpgsig &&
        sed -e "/committer/r double-gpgsig" double-base >double-commit &&
        git hash-object -w -t commit double-commit >double-commit.commit &&
index 132608879ad3c93011b8429d7f6f0f8ab1ed45cf..5883a6adc31b5f0580b9fea5a7863174e803dc99 100755 (executable)
@@ -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
index c6c44ec570dac66ca9a800ac687280cd3597e886..0f97828cd0bb8e6c3f1c39332010f5fbcfdb6512 100755 (executable)
@@ -27,6 +27,44 @@ test_expect_success 'setup' '
        git tag c3
 '
 
+test_expect_success 'pull.rebase not set' '
+       git reset --hard c0 &&
+       git pull . c1 2>err &&
+       test_i18ngrep "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=false' '
+       git reset --hard c0 &&
+       test_config pull.ff false &&
+       git pull . c1 2>err &&
+       test_i18ngrep "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=only' '
+       git reset --hard c0 &&
+       test_config pull.ff only &&
+       git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --rebase given' '
+       git reset --hard c0 &&
+       git pull --rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --no-rebase given' '
+       git reset --hard c0 &&
+       git pull --no-rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --ff-only given' '
+       git reset --hard c0 &&
+       git pull --ff-only . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
 test_expect_success 'merge c1 with c2' '
        git reset --hard c1 &&
        test -f c0.c &&
index e90413204ee32cafc3380244a5cf013eadcd72c1..5c8894d94ff107b6ea9ca63226075fadf9501e55 100755 (executable)
@@ -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 &&
index 7d7b396c2370f0f7b9b0f2d48035d7260e6b464b..991d5bd9c03f525d536770908269209d716f39c1 100755 (executable)
@@ -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
index c90fdc5c8940164c6bf0eacdf9f7ac2e3b0e02ec..83f8f5cacb59658bab98fde2da87f6cb5f7a9fca 100755 (executable)
@@ -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 &&
index 8e7f7d68b7349cdbb11ee9337444c066ae590f99..bf168a3645a796b3cc533ac1a9dcae34a75dff8c 100755 (executable)
@@ -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
        )
 '
 
index 0ede3cfedb2a7b5ac66238a61615395001885a94..36c6b1a12ffd95a1d6fe4ee4efd18a47f1581078 100755 (executable)
@@ -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
index 90346ff4e92ac06d6ca9fd8ea7b5b7d4f1f7f374..8466269bf50b66dd8d55cadc891600172659438e 100755 (executable)
@@ -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
        )
 '
index 3e41c58a13689b65bf5a9ed6b2fc6466b303af81..768257b29e0cf0be6df93e01d529b50269d72465 100755 (executable)
@@ -3381,4 +3381,113 @@ test_expect_success 'X: handling encoding' '
        git log -1 --format=%B encoding | grep $(printf "\317\200")
 '
 
+###
+### series Y (submodules and hash algorithms)
+###
+
+cat >Y-sub-input <<\Y_INPUT_END
+blob
+mark :1
+data 4
+foo
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Full Name <user@company.tld> 1000000000 +0100
+committer Full Name <user@company.tld> 1000000000 +0100
+data 24
+Test submodule commit 1
+M 100644 :1 file
+
+blob
+mark :3
+data 8
+foo
+bar
+
+commit refs/heads/master
+mark :4
+author Full Name <user@company.tld> 1000000001 +0100
+committer Full Name <user@company.tld> 1000000001 +0100
+data 24
+Test submodule commit 2
+from :2
+M 100644 :3 file
+Y_INPUT_END
+
+# Note that the submodule object IDs are intentionally not translated.
+cat >Y-main-input <<\Y_INPUT_END
+blob
+mark :1
+data 4
+foo
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Full Name <user@company.tld> 2000000000 +0100
+committer Full Name <user@company.tld> 2000000000 +0100
+data 14
+Test commit 1
+M 100644 :1 file
+
+blob
+mark :3
+data 73
+[submodule "sub1"]
+       path = sub1
+       url = https://void.example.com/main.git
+
+commit refs/heads/master
+mark :4
+author Full Name <user@company.tld> 2000000001 +0100
+committer Full Name <user@company.tld> 2000000001 +0100
+data 14
+Test commit 2
+from :2
+M 100644 :3 .gitmodules
+M 160000 0712c5be7cf681388e355ef47525aaf23aee1a6d sub1
+
+blob
+mark :5
+data 8
+foo
+bar
+
+commit refs/heads/master
+mark :6
+author Full Name <user@company.tld> 2000000002 +0100
+committer Full Name <user@company.tld> 2000000002 +0100
+data 14
+Test commit 3
+from :4
+M 100644 :5 file
+M 160000 ff729f5e62f72c0c3978207d9a80e5f3a65f14d7 sub1
+Y_INPUT_END
+
+cat >Y-marks <<\Y_INPUT_END
+:2 0712c5be7cf681388e355ef47525aaf23aee1a6d
+:4 ff729f5e62f72c0c3978207d9a80e5f3a65f14d7
+Y_INPUT_END
+
+test_expect_success 'Y: setup' '
+       test_oid_cache <<-EOF
+       Ymaster sha1:9afed2f9161ddf416c0a1863b8b0725b00070504
+       Ymaster sha256:c0a1010da1df187b2e287654793df01b464bd6f8e3f17fc1481a7dadf84caee3
+       EOF
+'
+
+test_expect_success 'Y: rewrite submodules' '
+       git init main1 &&
+       (
+               cd main1 &&
+               git init sub2 &&
+               git -C sub2 fast-import --export-marks=../sub2-marks <../Y-sub-input &&
+               git fast-import --rewrite-submodules-from=sub:../Y-marks \
+                       --rewrite-submodules-to=sub:sub2-marks <../Y-main-input &&
+               test "$(git rev-parse master)" = "$(test_oid Ymaster)"
+       )
+'
+
 test_done
index cc8d463e01ac74a78fa08b638318a88b8ea98bff..267ddc997d028926a81562fe6fe8eab9157da819 100755 (executable)
@@ -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'
 
index 600ce1e0b0d7edf193291a6a25450c92a5888475..b4d93f0c17c35722184d09d354556e59fef8a2b4 100755 (executable)
@@ -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
        )
 '
 
index d743ca33ee6ab29f97d328756663803824f105f5..ff6c0352e688246e9b97e063679889c2583c459e 100755 (executable)
@@ -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:
index 5505e5aa249e43b88455b0565f0f9348546d5e4c..3c44af69401594545082c47b16d5a98262c5302f 100755 (executable)
@@ -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 &&
index 352c213d52e2e4a50808128c027eba7f1ac163df..3103be8a32393f736481139dd70793ac4e4d97b7 100644 (file)
@@ -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
+}
index 0ea1e5a05edd86b3923cb82fd503b34ebbe93254..baf94546da10b369ba1f96d5d40b8ae43dfafe57 100644 (file)
@@ -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
+}
 
-       prev_opt=$opt
+# 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
+
+       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
 
@@ -494,21 +527,6 @@ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
        ;;
 esac
 
-# Convenience
-#
-# A regexp to match 5, 35 and 40 hexdigits
-_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
-_x40="$_x35$_x05"
-
-# Zero SHA-1
-_z40=0000000000000000000000000000000000000000
-
-OID_REGEX="$_x40"
-ZERO_OID=$_z40
-EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-
 # Line feed
 LF='
 '
@@ -657,6 +675,20 @@ die () {
        fi
 }
 
+file_lineno () {
+       test -z "$GIT_TEST_FRAMEWORK_SELFTEST" && test -n "$BASH" || return 0
+       eval '
+               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
@@ -702,7 +734,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; }
@@ -882,6 +914,7 @@ maybe_setup_valgrind () {
        fi
 }
 
+trace_level_=0
 want_trace () {
        test "$trace" = t && {
                test "$verbose" = t || test "$verbose_log" = t
@@ -895,7 +928,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
                $*"
 }
 
@@ -926,7 +959,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
 
@@ -1085,6 +1119,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"
 
@@ -1383,6 +1418,20 @@ then
        fi
 fi
 
+# Convenience
+# A regexp to match 5, 35 and 40 hexdigits
+_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
+_x40="$_x35$_x05"
+
+test_oid_init
+
+ZERO_OID=$(test_oid zero)
+OID_REGEX=$(echo $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
+EMPTY_TREE=$(test_oid empty_tree)
+EMPTY_BLOB=$(test_oid empty_blob)
+_z40=$ZERO_OID
+
 # Provide an implementation of the 'yes' utility; the upper bound
 # limit is there to help Windows that cannot stop this loop from
 # wasting cycles when the downstream stops reading, so do not be
@@ -1619,6 +1668,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"
index d43ad8c1912d977183270fabe7ac76c5bbb7a1a3..94aa18f3f7db211482f8392a5ecc2a3da786fc18 100644 (file)
@@ -130,17 +130,17 @@ static void deactivate_tempfile(struct tempfile *tempfile)
 }
 
 /* Make sure errno contains a meaningful value on error */
-struct tempfile *create_tempfile(const char *path)
+struct tempfile *create_tempfile_mode(const char *path, int mode)
 {
        struct tempfile *tempfile = new_tempfile();
 
        strbuf_add_absolute_path(&tempfile->filename, path);
        tempfile->fd = open(tempfile->filename.buf,
-                           O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
+                           O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, mode);
        if (O_CLOEXEC && tempfile->fd < 0 && errno == EINVAL)
                /* Try again w/o O_CLOEXEC: the kernel might not support it */
                tempfile->fd = open(tempfile->filename.buf,
-                                   O_RDWR | O_CREAT | O_EXCL, 0666);
+                                   O_RDWR | O_CREAT | O_EXCL, mode);
        if (tempfile->fd < 0) {
                deactivate_tempfile(tempfile);
                return NULL;
index cddda0a33c3e1ef39c6d294221fc9f7ae5205217..4de3bc77d246ef5ceceabc42e64ae35a9960b26a 100644 (file)
@@ -88,8 +88,16 @@ struct tempfile {
  * Attempt to create a temporary file at the specified `path`. Return
  * a tempfile (whose "fd" member can be used for writing to it), or
  * NULL on error. It is an error if a file already exists at that path.
+ * Note that `mode` will be further modified by the umask, and possibly
+ * `core.sharedRepository`, so it is not guaranteed to have the given
+ * mode.
  */
-struct tempfile *create_tempfile(const char *path);
+struct tempfile *create_tempfile_mode(const char *path, int mode);
+
+static inline struct tempfile *create_tempfile(const char *path)
+{
+       return create_tempfile_mode(path, 0666);
+}
 
 /*
  * Register an existing file as a tempfile, meaning that it will be
index c7b4f14d29a9d0ef38fba7595d15911757b9cbdb..2c6b570077b3980e9ecfeb088d49a125362610f7 100644 (file)
--- 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)
 {
index e5e81c05332f1352fd62935e5fe2d87c519f3567..b18bc5529c6e9a4692f30005ec1ca3d1803821b5 100644 (file)
--- a/trace2.h
+++ b/trace2.h
@@ -182,6 +182,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".
index caa7f06948ab97c09ee9adf82a9588fcc07d6e52..ec9ac1a6efd3f62b4ccae9db8a9d3ac3ceb1c64a 100644 (file)
@@ -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)
 {
index d9c98f64ddf2523e764c857302f88ce7fad31c39..a11d71feb5bfb1c0e044ece4dcba6e47d560be2f 100644 (file)
@@ -7,6 +7,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 */
index 3c3792eca2e254499d30f6e7184e6cc6b12238c4..a380dcf9105e8d7b0022b34508ca384923b2fcd6 100644 (file)
@@ -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" },
index d4364a7b85a51744b302389f6af937e8c492aaef..3292ee15bc9676ec5c1ffe79a0db041b39ee9eac 100644 (file)
@@ -11,6 +11,7 @@
  */
 enum tr2_sysenv_variable {
        TR2_SYSENV_CFG_PARAM = 0,
+       TR2_SYSENV_ENV_VARS,
 
        TR2_SYSENV_DST_DEBUG,
 
index 20a7185ec40e1cf4612c259019f325208632373b..a46afcb69db615d2f8917f852db28c3dca501de0 100644 (file)
@@ -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;
+}
index 1fdc7dac1a6230bdf3492cbebd6642fc825dac5f..15f5ba4e8f22c69959357bc8a7fe073678fe8862 100644 (file)
@@ -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,
index e0131daab987f582801972fe84aa412d235c89f7..4298c855be66bb41c41053a1ca9fced7b077a389 100644 (file)
@@ -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
index 33ded7f8b3e71069f7a75bf7524443f3d81b7532..f3d303c6e541acd4ab715c0a5a329abdc24e7819 100644 (file)
@@ -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);
 
        /*
index 1ecdab330408a1cca7f703b6f2dc8d9b11ae0261..02048dfdac8ab721b432f24a3dbcefc67f564e37 100644 (file)
@@ -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;
 
@@ -371,6 +404,7 @@ static int check_updates(struct unpack_trees_options *o)
        state.quiet = 1;
        state.refresh_cache = 1;
        state.istate = index;
+       clone_checkout_metadata(&state.meta, &o->meta, NULL);
 
        if (!o->update || o->dry_run) {
                remove_marked_cache_entries(index, 0);
@@ -422,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++) {
@@ -504,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 are at same path */
+       while ((++count) + i < istate->cache_nr &&
+              !strcmp(conflicting_path, istate->cache[count + i]->name))
+               ; /* do nothing */
+
+       return count;
+}
+
 static inline int call_unpack_fn(const struct cache_entry * const *src,
                                 struct unpack_trees_options *o)
 {
@@ -1493,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 *);
@@ -1507,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));
@@ -1614,11 +1677,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        }
 
        if (!o->skip_sparse_checkout) {
-               int empty_worktree = 1;
-
                /*
                 * 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,
@@ -1638,36 +1699,24 @@ 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;
-                       }
-
-                       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;
+                           verify_absent(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
+                               ret = 1;
 
+                       if (apply_sparse_checkout(&o->result, ce, o))
+                               ret = 1;
                }
-               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
-                * tree. This is usually a mistake in sparse checkout rules.
-                * Do not allow users to do that.
-                */
-               if (o->result.cache_nr && empty_worktree) {
-                       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) {
@@ -1690,9 +1739,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:
@@ -1705,6 +1754,74 @@ 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;
+       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;
+       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;
+       }
+
+skip_sparse_checkout:
+       if (check_updates(o, o->src_index))
+               ret = UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES;
+
+       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,
@@ -1789,7 +1906,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);
 }
 
 /*
@@ -1815,9 +1932,6 @@ static void invalidate_ce_path(const struct cache_entry *ce,
 /*
  * Check that checking out ce->sha1 in subdir ce->name is not
  * going to overwrite any working files.
- *
- * Currently, git does not checkout subprojects during a superproject
- * checkout, so it is not going to overwrite anything.
  */
 static int verify_clean_submodule(const char *old_sha1,
                                  const struct cache_entry *ce,
@@ -2030,11 +2144,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,
@@ -2067,7 +2177,7 @@ static int merged_entry(const struct cache_entry *ce,
                }
                invalidate_ce_path(merge, o);
 
-               if (submodule_from_ce(ce)) {
+               if (submodule_from_ce(ce) && file_exists(ce->name)) {
                        int ret = check_submodule_move_head(ce, NULL,
                                                            oid_to_hex(&ce->oid),
                                                            o);
@@ -2096,7 +2206,7 @@ static int merged_entry(const struct cache_entry *ce,
                        invalidate_ce_path(old, o);
                }
 
-               if (submodule_from_ce(ce)) {
+               if (submodule_from_ce(ce) && file_exists(ce->name)) {
                        int ret = check_submodule_move_head(ce, oid_to_hex(&old->oid),
                                                            oid_to_hex(&ce->oid),
                                                            o);
index ae1557fb8046c957dfa1feef6349a0117617f389..9c2f08277ee1ac4ba7e16e51c2c2d53c9ff14485 100644 (file)
@@ -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;
@@ -85,11 +88,21 @@ struct unpack_trees_options {
        struct index_state result;
 
        struct pattern_list *pl; /* for internal use */
+       struct checkout_metadata meta;
 };
 
 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);
 
index c53249cac19a33351f4f747782b71f877fc0692f..0478bff3e7fd11fdf89463a1f23cd2371de1b220 100644 (file)
@@ -26,6 +26,7 @@
 #include "serve.h"
 #include "commit-graph.h"
 #include "commit-reach.h"
+#include "shallow.h"
 
 /* Remember to update object flag allocation in object.h */
 #define THEY_HAVE      (1u << 11)
@@ -68,7 +69,6 @@ static const char *pack_objects_hook;
 static int filter_capability_requested;
 static int allow_filter;
 static int allow_ref_in_want;
-static struct list_objects_filter_options filter_options;
 
 static int allow_sideband_all;
 
@@ -103,7 +103,8 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 }
 
 static void create_pack_file(const struct object_array *have_obj,
-                            const struct object_array *want_obj)
+                            const struct object_array *want_obj,
+                            struct list_objects_filter_options *filter_options)
 {
        struct child_process pack_objects = CHILD_PROCESS_INIT;
        char data[8193], progress[128];
@@ -140,9 +141,9 @@ static void create_pack_file(const struct object_array *have_obj,
                argv_array_push(&pack_objects.args, "--delta-base-offset");
        if (use_include_tag)
                argv_array_push(&pack_objects.args, "--include-tag");
-       if (filter_options.choice) {
+       if (filter_options->choice) {
                const char *spec =
-                       expand_list_objects_filter_spec(&filter_options);
+                       expand_list_objects_filter_spec(filter_options);
                if (pack_objects.use_shell) {
                        struct strbuf buf = STRBUF_INIT;
                        sq_quote_buf(&buf, spec);
@@ -848,7 +849,9 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not,
        return 0;
 }
 
-static void receive_needs(struct packet_reader *reader, struct object_array *want_obj)
+static void receive_needs(struct packet_reader *reader,
+                         struct object_array *want_obj,
+                         struct list_objects_filter_options *filter_options)
 {
        struct object_array shallows = OBJECT_ARRAY_INIT;
        struct string_list deepen_not = STRING_LIST_INIT_DUP;
@@ -883,8 +886,8 @@ static void receive_needs(struct packet_reader *reader, struct object_array *wan
                if (skip_prefix(reader->line, "filter ", &arg)) {
                        if (!filter_capability_requested)
                                die("git upload-pack: filtering capability not negotiated");
-                       list_objects_filter_die_if_populated(&filter_options);
-                       parse_list_objects_filter(&filter_options, arg);
+                       list_objects_filter_die_if_populated(filter_options);
+                       parse_list_objects_filter(filter_options, arg);
                        continue;
                }
 
@@ -1087,11 +1090,14 @@ void upload_pack(struct upload_pack_options *options)
        struct string_list symref = STRING_LIST_INIT_DUP;
        struct object_array want_obj = OBJECT_ARRAY_INIT;
        struct packet_reader reader;
+       struct list_objects_filter_options filter_options;
 
        stateless_rpc = options->stateless_rpc;
        timeout = options->timeout;
        daemon_mode = options->daemon_mode;
 
+       memset(&filter_options, 0, sizeof(filter_options));
+
        git_config(upload_pack_config, NULL);
 
        head_ref_namespaced(find_symref, &symref);
@@ -1114,12 +1120,14 @@ void upload_pack(struct upload_pack_options *options)
                           PACKET_READ_CHOMP_NEWLINE |
                           PACKET_READ_DIE_ON_ERR_PACKET);
 
-       receive_needs(&reader, &want_obj);
+       receive_needs(&reader, &want_obj, &filter_options);
        if (want_obj.nr) {
                struct object_array have_obj = OBJECT_ARRAY_INIT;
                get_common_commits(&reader, &have_obj, &want_obj);
-               create_pack_file(&have_obj, &want_obj);
+               create_pack_file(&have_obj, &want_obj, &filter_options);
        }
+
+       list_objects_filter_release(&filter_options);
 }
 
 struct upload_pack_data {
@@ -1134,6 +1142,8 @@ struct upload_pack_data {
        int deepen_rev_list;
        int deepen_relative;
 
+       struct list_objects_filter_options filter_options;
+
        struct packet_writer writer;
 
        unsigned stateless_rpc : 1;
@@ -1169,6 +1179,7 @@ static void upload_pack_data_clear(struct upload_pack_data *data)
        oid_array_clear(&data->haves);
        object_array_clear(&data->shallows);
        string_list_clear(&data->deepen_not, 0);
+       list_objects_filter_release(&data->filter_options);
 }
 
 static int parse_want(struct packet_writer *writer, const char *line,
@@ -1252,7 +1263,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;
 
@@ -1306,8 +1317,8 @@ static void process_args(struct packet_reader *request,
                }
 
                if (allow_filter && skip_prefix(arg, "filter ", &p)) {
-                       list_objects_filter_die_if_populated(&filter_options);
-                       parse_list_objects_filter(&filter_options, p);
+                       list_objects_filter_die_if_populated(&data->filter_options);
+                       parse_list_objects_filter(&data->filter_options, p);
                        continue;
                }
 
@@ -1321,6 +1332,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,
@@ -1511,7 +1525,7 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys,
                        send_shallow_info(&data, &want_obj);
 
                        packet_writer_write(&data.writer, "packfile\n");
-                       create_pack_file(&have_obj, &want_obj);
+                       create_pack_file(&have_obj, &want_obj, &data.filter_options);
                        state = FETCH_DONE;
                        break;
                case FETCH_DONE:
index 29272a5c4f4d4a1785b66cb3c3a213c911d0883b..33a2ccd306b6a76c29b27974da64cb7a95247264 100644 (file)
@@ -572,10 +572,14 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
 
                config_url = xmemdupz(key, dot - key);
                norm_url = url_normalize_1(config_url, &norm_info, 1);
+               if (norm_url)
+                       retval = match_urls(url, &norm_info, &matched);
+               else if (collect->fallback_match_fn)
+                       retval = collect->fallback_match_fn(config_url,
+                                                           collect->cb);
+               else
+                       retval = 0;
                free(config_url);
-               if (!norm_url)
-                       return 0;
-               retval = match_urls(url, &norm_info, &matched);
                free(norm_url);
                if (!retval)
                        return 0;
index 2407520731f9f0c5250d7bdf5833c56320c10160..6ff42f81b0c1e0fdcdd75511f8b7746da0dccbf7 100644 (file)
@@ -59,6 +59,11 @@ struct urlmatch_config {
         * specificity rules) than existing.
         */
        int (*select_fn)(const struct urlmatch_item *found, const struct urlmatch_item *existing);
+       /*
+        * An optional callback to allow e.g. for partial URLs; it shall
+        * return 1 or 0 depending whether `url` matches or not.
+        */
+       int (*fallback_match_fn)(const char *url, void *cb);
 };
 
 int urlmatch_config_entry(const char *var, const char *value, void *cb);
index efbe05e5a5b6f1aa9a6b92365a4f987f6078dc04..1df884ef0bbfbba5a751eafc1922369ad818fce5 100644 (file)
@@ -79,6 +79,9 @@ PATTERNS("java",
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
         "|[-+*/<>%&^|=!]="
         "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
+PATTERNS("markdown",
+        "^ {0,3}#{1,6}[ \t].*",
+        "[^<>= \t]+"),
 PATTERNS("matlab",
         /*
          * Octave pattern is mostly the same as matlab, except that '%%%' and
@@ -222,7 +225,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 +269,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;
index eba4fd3a03812f046dadc1baf9d7644ba8dfcce9..ee82235f260c21748ec987ada46a4422980d9c8b 100644 (file)
@@ -226,17 +226,20 @@ struct worktree *find_worktree(struct worktree **list,
 
 struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
 {
+       struct strbuf wt_path = STRBUF_INIT;
        char *path = real_pathdup(p, 0);
 
        if (!path)
                return NULL;
        for (; *list; list++) {
-               const char *wt_path = real_path_if_valid((*list)->path);
+               if (!strbuf_realpath(&wt_path, (*list)->path, 0))
+                       continue;
 
-               if (wt_path && !fspathcmp(path, wt_path))
+               if (!fspathcmp(path, wt_path.buf))
                        break;
        }
        free(path);
+       strbuf_release(&wt_path);
        return *list;
 }
 
@@ -285,6 +288,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
                      unsigned flags)
 {
        struct strbuf wt_path = STRBUF_INIT;
+       struct strbuf realpath = STRBUF_INIT;
        char *path = NULL;
        int err, ret = -1;
 
@@ -336,7 +340,8 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
                goto done;
        }
 
-       ret = fspathcmp(path, real_path(git_common_path("worktrees/%s", wt->id)));
+       strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
+       ret = fspathcmp(path, realpath.buf);
 
        if (ret)
                strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
@@ -344,6 +349,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 done:
        free(path);
        strbuf_release(&wt_path);
+       strbuf_release(&realpath);
        return ret;
 }
 
@@ -450,7 +456,7 @@ const struct worktree *find_shared_symref(const char *symref,
 int submodule_uses_worktrees(const char *path)
 {
        char *submodule_gitdir;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
        DIR *dir;
        struct dirent *d;
        int ret = 0;
@@ -464,18 +470,16 @@ int submodule_uses_worktrees(const char *path)
        get_common_dir_noenv(&sb, submodule_gitdir);
        free(submodule_gitdir);
 
-       /*
-        * The check below is only known to be good for repository format
-        * version 0 at the time of writing this code.
-        */
        strbuf_addstr(&sb, "/config");
        read_repository_format(&format, sb.buf);
-       if (format.version != 0) {
+       if (verify_repository_format(&format, &err)) {
+               strbuf_release(&err);
                strbuf_release(&sb);
                clear_repository_format(&format);
                return 1;
        }
        clear_repository_format(&format);
+       strbuf_release(&err);
 
        /* Replace config by worktrees. */
        strbuf_setlen(&sb, sb.len - strlen("config"));
index e1eaef2e1641457fabc8ab00b68d3b6b3f0e2354..3a1c0e052677dca8d1bc19121d609e0c226e88ee 100644 (file)
--- 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) {
index cc6f94504d9fa9f7ec80f5131996a10c878c26cb..98dfa6f73f9d7cd41867f97fdae41bd4dc5ec2a1 100644 (file)
@@ -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);
        }
index 71c3f25f43deffd56cb6cc73cb76b3cbfaa66290..73ab5d4da1c0b4766cb63501b9db06c0a7fea934 100644 (file)
@@ -38,9 +38,22 @@ enum show_ignored_type {
 enum commit_whence {
        FROM_COMMIT,     /* normal */
        FROM_MERGE,      /* commit came from merge */
-       FROM_CHERRY_PICK /* commit came from cherry-pick */
+       FROM_CHERRY_PICK_SINGLE, /* commit came from cherry-pick */
+       FROM_CHERRY_PICK_MULTI, /* commit came from a sequence of cherry-picks */
+       FROM_REBASE_PICK /* commit came from a pick/reword/edit */
 };
 
+static inline int is_from_cherry_pick(enum commit_whence whence)
+{
+       return whence == FROM_CHERRY_PICK_SINGLE ||
+               whence == FROM_CHERRY_PICK_MULTI;
+}
+
+static inline int is_from_rebase(enum commit_whence whence)
+{
+       return whence == FROM_REBASE_PICK;
+}
+
 struct wt_status_change_data {
        int worktree_status;
        int index_status;