]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ah/userdiff-markdown'
authorJunio C Hamano <gitster@pobox.com>
Fri, 8 May 2020 21:25:01 +0000 (14:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 8 May 2020 21:25:01 +0000 (14:25 -0700)
The userdiff patterns for Markdown documents have been added.

* ah/userdiff-markdown:
  userdiff: support Markdown

261 files changed:
.github/workflows/main.yml [new file with mode: 0644]
.gitignore
.travis.yml
Documentation/Makefile
Documentation/RelNotes/2.27.0.txt
Documentation/asciidoc.conf
Documentation/config.txt
Documentation/config/fetch.txt
Documentation/config/log.txt
Documentation/config/merge.txt
Documentation/config/protocol.txt
Documentation/config/push.txt
Documentation/config/submodule.txt
Documentation/date-formats.txt
Documentation/fetch-options.txt
Documentation/git-bugreport.txt [new file with mode: 0644]
Documentation/git-checkout.txt
Documentation/git-commit-graph.txt
Documentation/git-credential-store.txt
Documentation/git-grep.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-merge.txt
Documentation/git-pull.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-reset.txt
Documentation/git-restore.txt
Documentation/git-sparse-checkout.txt
Documentation/git-switch.txt
Documentation/git-update-ref.txt
Documentation/gitsubmodules.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/revisions.txt
Documentation/technical/commit-graph-format.txt
Documentation/user-manual.conf
INSTALL
Makefile
README.md
apply.c
archive-tar.c
azure-pipelines.yml [deleted file]
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/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/diff-tree.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/grep.c
builtin/help.c
builtin/index-pack.c
builtin/interpret-trailers.c
builtin/log.c
builtin/ls-files.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/send-pack.c
builtin/shortlog.c
builtin/show-branch.c
builtin/show-ref.c
builtin/sparse-checkout.c
builtin/stash.c
builtin/tag.c
builtin/update-index.c
builtin/update-ref.c
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]
command-list.txt
commit-graph.c
commit-graph.h
commit.h
compat/compiler.h [new file with mode: 0644]
compat/regex/regex.c
compat/regex/regex_internal.h
compat/vcbuild/README
config.c
config.h
config.mak.uname
contrib/completion/git-completion.bash
contrib/completion/git-completion.zsh
contrib/subtree/Makefile
convert.c
credential-store.c
credential.c
date.c
diff.c
diff.h
diffcore-break.c
diffcore-rename.c
diffcore.h
dir.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-submodule.sh
gitweb/gitweb.perl
graph.c
help.c
help.h
line-log.c
list-objects-filter-options.h
ll-merge.c
lockfile.c
lockfile.h
log-tree.c
log-tree.h
mailinfo.c
midx.c
midx.h
oidset.c
oidset.h
parse-options.c
parse-options.h
path.c
path.h
progress.c
progress.h
promisor-remote.c
promisor-remote.h
protocol.c
prune-packed.c [new file with mode: 0644]
prune-packed.h [new file with mode: 0644]
range-diff.c
refs.c
refs.h
refs/files-backend.c
remote.c
reset.c [new file with mode: 0644]
reset.h [new file with mode: 0644]
revision.c
revision.h
send-pack.c
sequencer.c
sequencer.h
sha1-file.c
shallow.c
strbuf.c
strbuf.h
submodule-config.c
t/README
t/helper/test-bloom.c [new file with mode: 0644]
t/helper/test-parse-pathspec-file.c
t/helper/test-pkt-line.c
t/helper/test-progress.c
t/helper/test-read-graph.c
t/helper/test-tool.c
t/helper/test-tool.h
t/perf/p9300-fast-import-export.sh [new file with mode: 0755]
t/t0000-basic.sh
t/t0006-date.sh
t/t0040-parse-options.sh
t/t0091-bugreport.sh [new file with mode: 0755]
t/t0095-bloom.sh [new file with mode: 0755]
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/t2018-checkout-branch.sh
t/t3000-ls-files-others.sh
t/t3033-merge-toplevel.sh
t/t3206-range-diff.sh
t/t3420-rebase-autostash.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/t4061-diff-indent.sh
t/t4067-diff-partial-clone.sh
t/t4124-apply-ws-rule.sh
t/t4202-log.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/t5324-split-commit-graph.sh
t/t5500-fetch-pack.sh
t/t5504-fetch-receive-strict.sh
t/t5516-fetch-push.sh
t/t5520-pull.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/t5616-partial-clone.sh
t/t5703-upload-pack-ref-in-want.sh
t/t6030-bisect-porcelain.sh
t/t6600-test-reach.sh
t/t7063-status-untracked-cache.sh
t/t7408-submodule-reference.sh
t/t7508-status.sh
t/t7600-merge.sh
t/t7810-grep.sh
t/t9141-git-svn-multiple-branches.sh
t/t9160-git-svn-preserve-empty-dirs.sh
t/t9164-git-svn-dcommit-concurrent.sh
t/t9819-git-p4-case-folding.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
tempfile.c
tempfile.h
transport-helper.c
transport.c
transport.h
tree-diff.c
unpack-trees.c
unpack-trees.h
urlmatch.c
urlmatch.h
userdiff.c
wt-status.c

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644 (file)
index 0000000..fd4df93
--- /dev/null
@@ -0,0 +1,230 @@
+name: CI/PR
+
+on: [push, pull_request]
+
+env:
+  DEVELOPER: 1
+
+jobs:
+  windows-build:
+    runs-on: windows-latest
+    steps:
+    - uses: actions/checkout@v1
+    - name: download git-sdk-64-minimal
+      shell: bash
+      run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+    - name: build
+      shell: powershell
+      env:
+        HOME: ${{runner.workspace}}
+        MSYSTEM: MINGW64
+        NO_PERL: 1
+      run: |
+        & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+        printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
+
+          ci/make-test-artifacts.sh artifacts
+        "@
+    - name: upload build artifacts
+      uses: actions/upload-artifact@v1
+      with:
+        name: windows-artifacts
+        path: artifacts
+  windows-test:
+    runs-on: windows-latest
+    needs: [windows-build]
+    strategy:
+      matrix:
+        nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    steps:
+    - uses: actions/checkout@v1
+    - name: download git-sdk-64-minimal
+      shell: bash
+      run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+    - name: download build artifacts
+      uses: actions/download-artifact@v1
+      with:
+        name: windows-artifacts
+        path: ${{github.workspace}}
+    - name: extract build artifacts
+      shell: bash
+      run: tar xf artifacts.tar.gz
+    - name: test
+      shell: powershell
+      run: |
+        & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+          # Let Git ignore the SDK
+          printf '%s\n' /git-sdk-64-minimal/ >>.git/info/exclude
+
+          ci/run-test-slice.sh ${{matrix.nr}} 10
+        "@
+    - name: ci/print-test-failures.sh
+      if: failure()
+      shell: powershell
+      run: |
+        & .\git-sdk-64-minimal\usr\bin\bash.exe -lc ci/print-test-failures.sh
+    - name: Upload failed tests' directories
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+      uses: actions/upload-artifact@v1
+      with:
+        name: failed-tests-windows
+        path: ${{env.FAILED_TEST_ARTIFACTS}}
+  vs-build:
+    env:
+      MSYSTEM: MINGW64
+      NO_PERL: 1
+      GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
+    runs-on: windows-latest
+    steps:
+    - uses: actions/checkout@v1
+    - name: download git-sdk-64-minimal
+      shell: bash
+      run: a=git-sdk-64-minimal && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+    - name: generate Visual Studio solution
+      shell: powershell
+      run: |
+        & .\git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+          make NDEBUG=1 DEVELOPER=1 vcxproj
+        "@
+        if (!$?) { exit(1) }
+    - name: download vcpkg artifacts
+      shell: powershell
+      run: |
+        $urlbase = "https://dev.azure.com/git/git/_apis/build/builds"
+        $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
+        $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl
+        (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip")
+        Expand-Archive compat.zip -DestinationPath . -Force
+        Remove-Item compat.zip
+    - name: add msbuild to PATH
+      uses: microsoft/setup-msbuild@v1.0.0
+    - name: MSBuild
+      run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142
+    - name: bundle artifact tar
+      shell: powershell
+      env:
+        MSVC: 1
+        VCPKG_ROOT: ${{github.workspace}}\compat\vcbuild\vcpkg
+      run: |
+        & compat\vcbuild\vcpkg_copy_dlls.bat release
+        if (!$?) { exit(1) }
+        & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+          mkdir -p artifacts &&
+          eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts 2>&1 | grep ^tar)\"
+        "@
+    - name: upload build artifacts
+      uses: actions/upload-artifact@v1
+      with:
+        name: vs-artifacts
+        path: artifacts
+  vs-test:
+    runs-on: windows-latest
+    needs: [vs-build]
+    strategy:
+      matrix:
+        nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    steps:
+    - uses: actions/checkout@v1
+    - name: download git-64-portable
+      shell: bash
+      run: a=git-64-portable && mkdir -p $a && curl -# https://wingit.blob.core.windows.net/ci-artifacts/$a.tar.xz | tar -C $a -xJf -
+    - name: download build artifacts
+      uses: actions/download-artifact@v1
+      with:
+        name: vs-artifacts
+        path: ${{github.workspace}}
+    - name: extract build artifacts
+      shell: bash
+      run: tar xf artifacts.tar.gz
+    - name: test (parallel)
+      shell: powershell
+      env:
+        MSYSTEM: MINGW64
+        NO_SVN_TESTS: 1
+        GIT_TEST_SKIP_REBASE_P: 1
+      run: |
+        & git-64-portable\git-cmd.exe --command=usr\bin\bash.exe -lc @"
+          # Let Git ignore the SDK and the test-cache
+          printf '%s\n' /git-64-portable/ /test-cache/ >>.git/info/exclude
+
+          cd t &&
+          PATH=\"`$PWD/helper:`$PATH\" &&
+          test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \
+                  `$(test-tool.exe path-utils slice-tests \
+                          ${{matrix.nr}} 10 t[0-9]*.sh)
+        "@
+  regular:
+    strategy:
+      matrix:
+        vector:
+          - jobname: linux-clang
+            cc: clang
+            pool: ubuntu-latest
+          - jobname: linux-gcc
+            cc: gcc
+            pool: ubuntu-latest
+          - jobname: osx-clang
+            cc: clang
+            pool: macos-latest
+          - jobname: osx-gcc
+            cc: gcc
+            pool: macos-latest
+          - jobname: GETTEXT_POISON
+            cc: gcc
+            pool: ubuntu-latest
+    env:
+      CC: ${{matrix.vector.cc}}
+      jobname: ${{matrix.vector.jobname}}
+    runs-on: ${{matrix.vector.pool}}
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-dependencies.sh
+    - run: ci/run-build-and-tests.sh
+    - run: ci/print-test-failures.sh
+      if: failure()
+    - name: Upload failed tests' directories
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+      uses: actions/upload-artifact@v1
+      with:
+        name: failed-tests-${{matrix.vector.jobname}}
+        path: ${{env.FAILED_TEST_ARTIFACTS}}
+  dockerized:
+    strategy:
+      matrix:
+        vector:
+        - jobname: linux-musl
+          image: alpine
+        - jobname: Linux32
+          image: daald/ubuntu32:xenial
+    env:
+      jobname: ${{matrix.vector.jobname}}
+    runs-on: ubuntu-latest
+    container: ${{matrix.vector.image}}
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-docker-dependencies.sh
+    - run: ci/run-build-and-tests.sh
+    - run: ci/print-test-failures.sh
+      if: failure()
+    - name: Upload failed tests' directories
+      if: failure() && env.FAILED_TEST_ARTIFACTS != ''
+      uses: actions/upload-artifact@v1
+      with:
+        name: failed-tests-${{matrix.vector.jobname}}
+        path: ${{env.FAILED_TEST_ARTIFACTS}}
+  static-analysis:
+    env:
+      jobname: StaticAnalysis
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-dependencies.sh
+    - run: ci/run-static-analysis.sh
+  documentation:
+    env:
+      jobname: Documentation
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v1
+    - run: ci/install-dependencies.sh
+    - run: ci/test-documentation.sh
index 188bd1c3de13ac2c71765a475f1117ccf954fe56..ee509a2ad263989fcebe3c3543aa32efed1cacda 100644 (file)
@@ -25,6 +25,7 @@
 /git-bisect--helper
 /git-blame
 /git-branch
+/git-bugreport
 /git-bundle
 /git-cat-file
 /git-check-attr
 /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 59e6ce3a2a648d9e6e1cb3fb6f9e3cac4487c662..15d9d04f3164b939ec91695f4ec1c3b2fd6c00b3 100644 (file)
@@ -150,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 0bd2dc713dd946c65570f017e53b84f3923ba6ed..9c7041eb0821a180d9a39545909a62e86aceeef5 100644 (file)
@@ -76,6 +76,29 @@ UI, Workflows & Features
  * 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.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -102,6 +125,33 @@ Performance, Internal Implementation, Development Support etc.
  * A Windows-specific test element has been made more robust against
    misuse from both user's environment and programmer's errors.
 
+ * Various tests have been updated to work around issues found with
+   shell utilities that come with busybox etc.
+
+ * The config API made mixed uses of int and size_t types to represent
+   length of various pieces of text it parsed, which has been updated
+   to use the correct type (i.e. size_t) throughout.
+
+ * The "--decorate-refs" and "--decorate-refs-exclude" options "git
+   log" takes have learned a companion configuration variable
+   log.excludeDecoration that sits at the lowest priority in the
+   family.
+
+ * A new CI job to build and run test suite on linux with musl libc
+   has been added.
+
+ * Update the CI configuration to use GitHub Actions, retiring the one
+   based on Azure Pipelines.
+
+ * The directory traversal code had redundant recursive calls which
+   made its performance characteristics exponential with respect to
+   the depth of the tree, which was corrected.
+
+ * "git blame" learns to take advantage of the "changed-paths" Bloom
+   filter stored in the commit-graph file.
+
+ * The "bugreport" tool has been added.
+
 
 Fixes since v2.26
 -----------------
@@ -195,6 +245,133 @@ Fixes since v2.26
  * 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.
+
  * 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).
@@ -215,3 +392,10 @@ Fixes since v2.26
    (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).
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 74009d5402cf486005d7ee580c74c96171c012f1..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
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 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 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 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 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 05709f67a105caf8965eed40cdd7f9f186366516..6e2a160a47cb8c07a72600d2fc7ad404bf2bc1c1 100644 (file)
@@ -163,7 +163,8 @@ ifndef::git-pull[]
        value. Use 'on-demand' to only recurse into a populated submodule
        when the superproject retrieves a commit that updates the submodule's
        reference to a commit that isn't already in the local submodule
-       clone.
+       clone. By default, 'on-demand' is used, unless
+       `fetch.recurseSubmodules` is set (see linkgit:git-config[1]).
 endif::git-pull[]
 
 -j::
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
new file mode 100644 (file)
index 0000000..643d1b2
--- /dev/null
@@ -0,0 +1,52 @@
+git-bugreport(1)
+================
+
+NAME
+----
+git-bugreport - Collect information for user to file a bug report
+
+SYNOPSIS
+--------
+[verse]
+'git bugreport' [(-o | --output-directory) <path>] [(-s | --suffix) <format>]
+
+DESCRIPTION
+-----------
+Captures information about the user's machine, Git client, and repository state,
+as well as a form requesting information about the behavior the user observed,
+into a single text file which the user can then share, for example to the Git
+mailing list, in order to report an observed bug.
+
+The following information is requested from the user:
+
+ - Reproduction steps
+ - Expected behavior
+ - Actual behavior
+
+The following information is captured automatically:
+
+ - 'git version --build-options'
+ - uname sysname, release, version, and machine strings
+ - Compiler-specific info string
+
+This tool is invoked via the typical Git setup process, which means that in some
+cases, it might not be able to launch - for example, if a relevant config file
+is unreadable. In this kind of scenario, it may be helpful to manually gather
+the kind of information listed above when manually asking for help.
+
+OPTIONS
+-------
+-o <path>::
+--output-directory <path>::
+       Place the resulting bug report file in `<path>` instead of the root of
+       the Git repository.
+
+-s <format>::
+--suffix <format>::
+       Specify an alternate suffix for the bugreport name, to create a file
+       named 'git-bugreport-<formatted suffix>'. This should take the form of a
+       link:strftime[3] format string; the current local time will be used.
+
+GIT
+---
+Part of the linkgit:git[1] suite
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 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 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 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 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..ec06b2f8c243d6ecaf11a0e36b160a49b5a325d6 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
+reflog.
 
 --quit::
        Forget about the current merge in progress. Leave the index
-       and the working tree as-is.
+       and the working tree as-is. If `MERGE_AUTOSTASH` is present, the
+       stash entry will be saved to the stash reflog.
 
 --continue::
        After a 'git merge' stops due to conflicts you can conclude the
index 21e10905fa28f4cfd77f9b1ecfd2a17e5a8835ab..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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
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 bed500f151ee29048643946a51a2736aab3ceb02..794f2f39f12a23b341d1043696e26c74de67831c 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 reflog.
 
 --apply:
        Use applying strategies to rebase (calling `git-am`
@@ -448,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>::
@@ -635,6 +638,7 @@ 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
 -----------------------
@@ -722,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
 ~~~~~~~~~~~~~~~~
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..8e3b33980281336765ac1749cfe6add3ce9cda4e 100644 (file)
@@ -107,6 +107,17 @@ in linkgit:git-checkout[1] for details.
        patterns and unconditionally restores any files in
        `<pathspec>`.
 
+--recurse-submodules::
+--no-recurse-submodules::
+       If `<pathspec>` names an active submodule and the restore location
+       includes the working tree, the submodule will only be updated if
+       this option is given, in which case its working tree will be
+       restored to the commit recorded in the superproject, and any local
+       modifications overwritten. If nothing (or
+       `--no-recurse-submodules`) is used, submodules working trees will
+       not be updated. Just like linkgit:git-checkout[1], this will detach
+       `HEAD` of the submodule.
+
 --overlay::
 --no-overlay::
        In overlay mode, the command never removes files when
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 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
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 fb3a6e8d429acceab689dc22374e13ca0d7e314c..80d4831662c5e5fee9cdcc37e14929ce94f9b81a 100644 (file)
@@ -160,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 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 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[]
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 dc356ce4ddc9f824016c894a4d18ca801d093f8b..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.
@@ -616,8 +610,8 @@ SCRIPT_SH += git-web--browse.sh
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-rebase--preserve-merges
-SCRIPT_LIB += git-sh-setup
 SCRIPT_LIB += git-sh-i18n
+SCRIPT_LIB += git-sh-setup
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-archimport.perl
@@ -680,14 +674,15 @@ EXTRA_PROGRAMS =
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
 
+PROGRAM_OBJS += bugreport.o
 PROGRAM_OBJS += credential-store.o
 PROGRAM_OBJS += daemon.o
 PROGRAM_OBJS += fast-import.o
 PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += remote-testsvn.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
 PROGRAM_OBJS += shell.o
-PROGRAM_OBJS += remote-testsvn.o
 
 # Binary suffix, set to .exe for Windows builds
 X =
@@ -695,6 +690,7 @@ X =
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
 TEST_BUILTINS_OBJS += test-advise.o
+TEST_BUILTINS_OBJS += test-bloom.o
 TEST_BUILTINS_OBJS += test-chmtime.o
 TEST_BUILTINS_OBJS += test-config.o
 TEST_BUILTINS_OBJS += test-ctype.o
@@ -709,15 +705,16 @@ TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
 TEST_BUILTINS_OBJS += test-example-decorate.o
 TEST_BUILTINS_OBJS += test-genrandom.o
 TEST_BUILTINS_OBJS += test-genzeros.o
+TEST_BUILTINS_OBJS += test-hash-speed.o
 TEST_BUILTINS_OBJS += test-hash.o
 TEST_BUILTINS_OBJS += test-hashmap.o
-TEST_BUILTINS_OBJS += test-hash-speed.o
 TEST_BUILTINS_OBJS += test-index-version.o
 TEST_BUILTINS_OBJS += test-json-writer.o
 TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
 TEST_BUILTINS_OBJS += test-match-trees.o
 TEST_BUILTINS_OBJS += test-mergesort.o
 TEST_BUILTINS_OBJS += test-mktemp.o
+TEST_BUILTINS_OBJS += test-oid-array.o
 TEST_BUILTINS_OBJS += test-oidmap.o
 TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-parse-options.o
@@ -738,7 +735,6 @@ TEST_BUILTINS_OBJS += test-run-command.o
 TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
 TEST_BUILTINS_OBJS += test-serve-v2.o
 TEST_BUILTINS_OBJS += test-sha1.o
-TEST_BUILTINS_OBJS += test-oid-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,32 +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 += oid-array.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
@@ -952,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
@@ -965,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
@@ -979,8 +980,8 @@ LIB_OBJS += sequencer.o
 LIB_OBJS += serve.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.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
 =========================================================
diff --git a/apply.c b/apply.c
index 144c19aaca80d943e237ffd84f215a49415e0d5a..8bff604dbe203402d93bd13fe5b03d635a7a50ee 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -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;
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)
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..dd9bab9
--- /dev/null
+++ b/bloom.c
@@ -0,0 +1,276 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "hashmap.h"
+#include "commit-graph.h"
+#include "commit.h"
+
+define_commit_slab(bloom_filter_slab, struct bloom_filter);
+
+struct bloom_filter_slab bloom_filters;
+
+struct pathmap_hash_entry {
+    struct hashmap_entry entry;
+    const char path[FLEX_ARRAY];
+};
+
+static uint32_t rotate_left(uint32_t value, int32_t count)
+{
+       uint32_t mask = 8 * sizeof(uint32_t) - 1;
+       count &= mask;
+       return ((value << count) | (value >> ((-count) & mask)));
+}
+
+static inline unsigned char get_bitmask(uint32_t pos)
+{
+       return ((unsigned char)1) << (pos & (BITS_PER_WORD - 1));
+}
+
+static int load_bloom_filter_from_graph(struct commit_graph *g,
+                                  struct bloom_filter *filter,
+                                  struct commit *c)
+{
+       uint32_t lex_pos, start_index, end_index;
+
+       while (c->graph_pos < g->num_commits_in_base)
+               g = g->base_graph;
+
+       /* The commit graph commit 'c' lives in doesn't carry bloom filters. */
+       if (!g->chunk_bloom_indexes)
+               return 0;
+
+       lex_pos = c->graph_pos - g->num_commits_in_base;
+
+       end_index = get_be32(g->chunk_bloom_indexes + 4 * lex_pos);
+
+       if (lex_pos > 0)
+               start_index = get_be32(g->chunk_bloom_indexes + 4 * (lex_pos - 1));
+       else
+               start_index = 0;
+
+       filter->len = end_index - start_index;
+       filter->data = (unsigned char *)(g->chunk_bloom_data +
+                                       sizeof(unsigned char) * start_index +
+                                       BLOOMDATA_CHUNK_HEADER_SIZE);
+
+       return 1;
+}
+
+/*
+ * Calculate the murmur3 32-bit hash value for the given data
+ * using the given seed.
+ * Produces a uniformly distributed hash value.
+ * Not considered to be cryptographically secure.
+ * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm
+ */
+uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len)
+{
+       const uint32_t c1 = 0xcc9e2d51;
+       const uint32_t c2 = 0x1b873593;
+       const uint32_t r1 = 15;
+       const uint32_t r2 = 13;
+       const uint32_t m = 5;
+       const uint32_t n = 0xe6546b64;
+       int i;
+       uint32_t k1 = 0;
+       const char *tail;
+
+       int len4 = len / sizeof(uint32_t);
+
+       uint32_t k;
+       for (i = 0; i < len4; i++) {
+               uint32_t byte1 = (uint32_t)data[4*i];
+               uint32_t byte2 = ((uint32_t)data[4*i + 1]) << 8;
+               uint32_t byte3 = ((uint32_t)data[4*i + 2]) << 16;
+               uint32_t byte4 = ((uint32_t)data[4*i + 3]) << 24;
+               k = byte1 | byte2 | byte3 | byte4;
+               k *= c1;
+               k = rotate_left(k, r1);
+               k *= c2;
+
+               seed ^= k;
+               seed = rotate_left(seed, r2) * m + n;
+       }
+
+       tail = (data + len4 * sizeof(uint32_t));
+
+       switch (len & (sizeof(uint32_t) - 1)) {
+       case 3:
+               k1 ^= ((uint32_t)tail[2]) << 16;
+               /*-fallthrough*/
+       case 2:
+               k1 ^= ((uint32_t)tail[1]) << 8;
+               /*-fallthrough*/
+       case 1:
+               k1 ^= ((uint32_t)tail[0]) << 0;
+               k1 *= c1;
+               k1 = rotate_left(k1, r1);
+               k1 *= c2;
+               seed ^= k1;
+               break;
+       }
+
+       seed ^= (uint32_t)len;
+       seed ^= (seed >> 16);
+       seed *= 0x85ebca6b;
+       seed ^= (seed >> 13);
+       seed *= 0xc2b2ae35;
+       seed ^= (seed >> 16);
+
+       return seed;
+}
+
+void fill_bloom_key(const char *data,
+                                       size_t len,
+                                       struct bloom_key *key,
+                                       const struct bloom_filter_settings *settings)
+{
+       int i;
+       const uint32_t seed0 = 0x293ae76f;
+       const uint32_t seed1 = 0x7e646e2c;
+       const uint32_t hash0 = murmur3_seeded(seed0, data, len);
+       const uint32_t hash1 = murmur3_seeded(seed1, data, len);
+
+       key->hashes = (uint32_t *)xcalloc(settings->num_hashes, sizeof(uint32_t));
+       for (i = 0; i < settings->num_hashes; i++)
+               key->hashes[i] = hash0 + i * hash1;
+}
+
+void add_key_to_filter(const struct bloom_key *key,
+                                          struct bloom_filter *filter,
+                                          const struct bloom_filter_settings *settings)
+{
+       int i;
+       uint64_t mod = filter->len * BITS_PER_WORD;
+
+       for (i = 0; i < settings->num_hashes; i++) {
+               uint64_t hash_mod = key->hashes[i] % mod;
+               uint64_t block_pos = hash_mod / BITS_PER_WORD;
+
+               filter->data[block_pos] |= get_bitmask(hash_mod);
+       }
+}
+
+void init_bloom_filters(void)
+{
+       init_bloom_filter_slab(&bloom_filters);
+}
+
+struct bloom_filter *get_bloom_filter(struct repository *r,
+                                     struct commit *c,
+                                         int compute_if_not_present)
+{
+       struct bloom_filter *filter;
+       struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+       int i;
+       struct diff_options diffopt;
+       int max_changes = 512;
+
+       if (bloom_filters.slab_size == 0)
+               return NULL;
+
+       filter = bloom_filter_slab_at(&bloom_filters, c);
+
+       if (!filter->data) {
+               load_commit_graph_info(r, c);
+               if (c->graph_pos != COMMIT_NOT_FROM_GRAPH &&
+                       r->objects->commit_graph->chunk_bloom_indexes) {
+                       if (load_bloom_filter_from_graph(r->objects->commit_graph, filter, c))
+                               return filter;
+                       else
+                               return NULL;
+               }
+       }
+
+       if (filter->data || !compute_if_not_present)
+               return filter;
+
+       repo_diff_setup(r, &diffopt);
+       diffopt.flags.recursive = 1;
+       diffopt.detect_rename = 0;
+       diffopt.max_changes = max_changes;
+       diff_setup_done(&diffopt);
+
+       if (c->parents)
+               diff_tree_oid(&c->parents->item->object.oid, &c->object.oid, "", &diffopt);
+       else
+               diff_tree_oid(NULL, &c->object.oid, "", &diffopt);
+       diffcore_std(&diffopt);
+
+       if (diff_queued_diff.nr <= max_changes) {
+               struct hashmap pathmap;
+               struct pathmap_hash_entry *e;
+               struct hashmap_iter iter;
+               hashmap_init(&pathmap, NULL, NULL, 0);
+
+               for (i = 0; i < diff_queued_diff.nr; i++) {
+                       const char *path = diff_queued_diff.queue[i]->two->path;
+
+                       /*
+                       * Add each leading directory of the changed file, i.e. for
+                       * 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so
+                       * the Bloom filter could be used to speed up commands like
+                       * 'git log dir/subdir', too.
+                       *
+                       * Note that directories are added without the trailing '/'.
+                       */
+                       do {
+                               char *last_slash = strrchr(path, '/');
+
+                               FLEX_ALLOC_STR(e, path, path);
+                               hashmap_entry_init(&e->entry, strhash(path));
+                               hashmap_add(&pathmap, &e->entry);
+
+                               if (!last_slash)
+                                       last_slash = (char*)path;
+                               *last_slash = '\0';
+
+                       } while (*path);
+
+                       diff_free_filepair(diff_queued_diff.queue[i]);
+               }
+
+               filter->len = (hashmap_get_size(&pathmap) * settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+               filter->data = xcalloc(filter->len, sizeof(unsigned char));
+
+               hashmap_for_each_entry(&pathmap, &iter, e, entry) {
+                       struct bloom_key key;
+                       fill_bloom_key(e->path, strlen(e->path), &key, &settings);
+                       add_key_to_filter(&key, filter, &settings);
+               }
+
+               hashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);
+       } else {
+               for (i = 0; i < diff_queued_diff.nr; i++)
+                       diff_free_filepair(diff_queued_diff.queue[i]);
+               filter->data = NULL;
+               filter->len = 0;
+       }
+
+       free(diff_queued_diff.queue);
+       DIFF_QUEUE_CLEAR(&diff_queued_diff);
+
+       return filter;
+}
+
+int bloom_filter_contains(const struct bloom_filter *filter,
+                         const struct bloom_key *key,
+                         const struct bloom_filter_settings *settings)
+{
+       int i;
+       uint64_t mod = filter->len * BITS_PER_WORD;
+
+       if (!mod)
+               return -1;
+
+       for (i = 0; i < settings->num_hashes; i++) {
+               uint64_t hash_mod = key->hashes[i] % mod;
+               uint64_t block_pos = hash_mod / BITS_PER_WORD;
+               if (!(filter->data[block_pos] & get_bitmask(hash_mod)))
+                       return 0;
+       }
+
+       return 1;
+}
\ No newline at end of file
diff --git a/bloom.h b/bloom.h
new file mode 100644 (file)
index 0000000..b935186
--- /dev/null
+++ b/bloom.h
@@ -0,0 +1,90 @@
+#ifndef BLOOM_H
+#define BLOOM_H
+
+struct commit;
+struct repository;
+
+struct bloom_filter_settings {
+       /*
+        * The version of the hashing technique being used.
+        * We currently only support version = 1 which is
+        * the seeded murmur3 hashing technique implemented
+        * in bloom.c.
+        */
+       uint32_t hash_version;
+
+       /*
+        * The number of times a path is hashed, i.e. the
+        * number of bit positions tht cumulatively
+        * determine whether a path is present in the
+        * Bloom filter.
+        */
+       uint32_t num_hashes;
+
+       /*
+        * The minimum number of bits per entry in the Bloom
+        * filter. If the filter contains 'n' entries, then
+        * filter size is the minimum number of 8-bit words
+        * that contain n*b bits.
+        */
+       uint32_t bits_per_entry;
+};
+
+#define DEFAULT_BLOOM_FILTER_SETTINGS { 1, 7, 10 }
+#define BITS_PER_WORD 8
+#define BLOOMDATA_CHUNK_HEADER_SIZE 3 * sizeof(uint32_t)
+
+/*
+ * A bloom_filter struct represents a data segment to
+ * use when testing hash values. The 'len' member
+ * dictates how many entries are stored in
+ * 'data'.
+ */
+struct bloom_filter {
+       unsigned char *data;
+       size_t len;
+};
+
+/*
+ * A bloom_key represents the k hash values for a
+ * given string. These can be precomputed and
+ * stored in a bloom_key for re-use when testing
+ * against a bloom_filter. The number of hashes is
+ * given by the Bloom filter settings and is the same
+ * for all Bloom filters and keys interacting with
+ * the loaded version of the commit graph file and
+ * the Bloom data chunks.
+ */
+struct bloom_key {
+       uint32_t *hashes;
+};
+
+/*
+ * Calculate the murmur3 32-bit hash value for the given data
+ * using the given seed.
+ * Produces a uniformly distributed hash value.
+ * Not considered to be cryptographically secure.
+ * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm
+ */
+uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len);
+
+void fill_bloom_key(const char *data,
+                   size_t len,
+                   struct bloom_key *key,
+                   const struct bloom_filter_settings *settings);
+
+void add_key_to_filter(const struct bloom_key *key,
+                                          struct bloom_filter *filter,
+                                          const struct bloom_filter_settings *settings);
+
+void init_bloom_filters(void);
+
+struct bloom_filter *get_bloom_filter(struct repository *r,
+                                     struct commit *c,
+                                     int compute_if_not_present);
+
+int bloom_filter_contains(const struct bloom_filter *filter,
+                         const struct bloom_key *key,
+                         const struct bloom_filter_settings *settings);
+
+#endif
\ No newline at end of file
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..acacca8
--- /dev/null
@@ -0,0 +1,140 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "help.h"
+#include "compat/compiler.h"
+
+static void get_system_info(struct strbuf *sys_info)
+{
+       struct utsname uname_info;
+
+       /* get git version from native cmd */
+       strbuf_addstr(sys_info, _("git version:\n"));
+       get_version_info(sys_info, 1);
+
+       /* system call for other version info */
+       strbuf_addstr(sys_info, "uname: ");
+       if (uname(&uname_info))
+               strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
+                           strerror(errno),
+                           errno);
+       else
+               strbuf_addf(sys_info, "%s %s %s %s\n",
+                           uname_info.sysname,
+                           uname_info.release,
+                           uname_info.version,
+                           uname_info.machine);
+
+       strbuf_addstr(sys_info, _("compiler info: "));
+       get_compiler_info(sys_info);
+       strbuf_addstr(sys_info, _("libc info: "));
+       get_libc_info(sys_info);
+}
+
+static const char * const bugreport_usage[] = {
+       N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"),
+       NULL
+};
+
+static int get_bug_template(struct strbuf *template)
+{
+       const char template_text[] = N_(
+"Thank you for filling out a Git bug report!\n"
+"Please answer the following questions to help us understand your issue.\n"
+"\n"
+"What did you do before the bug happened? (Steps to reproduce your issue)\n"
+"\n"
+"What did you expect to happen? (Expected behavior)\n"
+"\n"
+"What happened instead? (Actual behavior)\n"
+"\n"
+"What's different between what you expected and what actually happened?\n"
+"\n"
+"Anything else you want to add:\n"
+"\n"
+"Please review the rest of the bug report below.\n"
+"You can delete any lines you don't wish to share.\n");
+
+       strbuf_addstr(template, _(template_text));
+       return 0;
+}
+
+static void get_header(struct strbuf *buf, const char *title)
+{
+       strbuf_addf(buf, "\n\n[%s]\n", title);
+}
+
+int cmd_main(int argc, const char **argv)
+{
+       struct strbuf buffer = STRBUF_INIT;
+       struct strbuf report_path = STRBUF_INIT;
+       int report = -1;
+       time_t now = time(NULL);
+       char *option_output = NULL;
+       char *option_suffix = "%Y-%m-%d-%H%M";
+       int nongit_ok = 0;
+       const char *prefix = NULL;
+       const char *user_relative_path = NULL;
+
+       const struct option bugreport_options[] = {
+               OPT_STRING('o', "output-directory", &option_output, N_("path"),
+                          N_("specify a destination for the bugreport file")),
+               OPT_STRING('s', "suffix", &option_suffix, N_("format"),
+                          N_("specify a strftime format suffix for the filename")),
+               OPT_END()
+       };
+
+       prefix = setup_git_directory_gently(&nongit_ok);
+
+       argc = parse_options(argc, argv, prefix, bugreport_options,
+                            bugreport_usage, 0);
+
+       /* Prepare the path to put the result */
+       strbuf_addstr(&report_path,
+                     prefix_filename(prefix,
+                                     option_output ? option_output : ""));
+       strbuf_complete(&report_path, '/');
+
+       strbuf_addstr(&report_path, "git-bugreport-");
+       strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0);
+       strbuf_addstr(&report_path, ".txt");
+
+       switch (safe_create_leading_directories(report_path.buf)) {
+       case SCLD_OK:
+       case SCLD_EXISTS:
+               break;
+       default:
+               die(_("could not create leading directories for '%s'"),
+                   report_path.buf);
+       }
+
+       /* Prepare the report contents */
+       get_bug_template(&buffer);
+
+       get_header(&buffer, _("System Info"));
+       get_system_info(&buffer);
+
+       /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
+       report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
+
+       if (report < 0) {
+               UNLEAK(report_path);
+               die(_("couldn't create a new file at '%s'"), report_path.buf);
+       }
+
+       strbuf_write_fd(&buffer, report);
+       close(report);
+
+       /*
+        * We want to print the path relative to the user, but we still need the
+        * path relative to us to give to the editor.
+        */
+       if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
+               user_relative_path = report_path.buf;
+       fprintf(stderr, _("Created new report at '%s'.\n"),
+               user_relative_path);
+
+       UNLEAK(buffer);
+       UNLEAK(report_path);
+       return !!launch_editor(report_path.buf, NULL, NULL);
+}
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 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..176e524a94d2ae015c905cb75711edd1548f080e 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(),
index 0d03fdac6e1c5e848d5b1981f3b7d4c8ea6221c1..ae18e20a7c96fd1b20660ba9aa1079bcebe8d2af 100644 (file)
@@ -650,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 8bc94d392b83624d0cd538fbbd702dc4ad824f7c..91d9e88b05dfc6a71d7200be6c95859ddeddd2fe 100644 (file)
@@ -1486,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"),
@@ -1544,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[])
@@ -1586,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"));
@@ -1614,7 +1618,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
        /*
         * 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;
@@ -1622,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, "--"))
@@ -1631,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;
        }
 
@@ -1822,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 c8c011d2ddfa3e2653535c6d4b36675a20a90031..4ca12bc0c0ba2c3bc3084a320835cf83600173ce 100644 (file)
@@ -906,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")),
@@ -983,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 a4f836d1bafda24fd8abbe02f1c00fcc05f58fe8..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"),
index d1ab6625f632031787584e3fa792015a137ba8bd..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,
@@ -114,10 +119,29 @@ static int graph_verify(int argc, const char **argv)
 extern int read_replace_refs;
 static struct split_commit_graph_opts split_opts;
 
+static int write_option_parse_split(const struct option *opt, const char *arg,
+                                   int unset)
+{
+       enum commit_graph_split_flags *flags = opt->value;
+
+       opts.split = 1;
+       if (!arg)
+               return 0;
+
+       if (!strcmp(arg, "no-merge"))
+               *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
+       else if (!strcmp(arg, "replace"))
+               *flags = COMMIT_GRAPH_SPLIT_REPLACE;
+       else
+               die(_("unrecognized --split argument, %s"), arg);
+
+       return 0;
+}
+
 static int graph_write(int argc, const char **argv)
 {
        struct string_list *pack_indexes = NULL;
-       struct string_list *commit_hex = NULL;
+       struct oidset commits = OIDSET_INIT;
        struct object_directory *odb = NULL;
        struct string_list lines;
        int result = 0;
@@ -135,15 +159,19 @@ static int graph_write(int argc, const char **argv)
                        N_("start walk at commits listed by stdin")),
                OPT_BOOL(0, "append", &opts.append,
                        N_("include all commits already in the commit-graph file")),
+               OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
+                       N_("enable computation for changed paths")),
                OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
-               OPT_BOOL(0, "split", &opts.split,
-                       N_("allow writing an incremental commit-graph file")),
+               OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL,
+                       N_("allow writing an incremental commit-graph file"),
+                       PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+                       write_option_parse_split),
                OPT_INTEGER(0, "max-commits", &split_opts.max_commits,
                        N_("maximum number of commits in a non-base split commit-graph")),
                OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
                        N_("maximum ratio between two levels of a split commit-graph")),
                OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time,
-                       N_("maximum number of commits in a non-base split commit-graph")),
+                       N_("only expire files older than a given date-time")),
                OPT_END(),
        };
 
@@ -168,6 +196,9 @@ static int graph_write(int argc, const char **argv)
                flags |= COMMIT_GRAPH_WRITE_SPLIT;
        if (opts.progress)
                flags |= COMMIT_GRAPH_WRITE_PROGRESS;
+       if (opts.enable_changed_paths ||
+           git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
+               flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
 
        read_replace_refs = 0;
        odb = find_odb(the_repository, opts.obj_dir);
@@ -188,7 +219,20 @@ static int graph_write(int argc, const char **argv)
                if (opts.stdin_packs)
                        pack_indexes = &lines;
                if (opts.stdin_commits) {
-                       commit_hex = &lines;
+                       struct string_list_item *item;
+                       oidset_init(&commits, lines.nr);
+                       for_each_string_list_item(item, &lines) {
+                               struct object_id oid;
+                               const char *end;
+
+                               if (parse_oid_hex(item->string, &oid, &end)) {
+                                       error(_("unexpected non-hex object ID: "
+                                               "%s"), item->string);
+                                       return 1;
+                               }
+
+                               oidset_insert(&commits, &oid);
+                       }
                        flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS;
                }
 
@@ -197,7 +241,7 @@ static int graph_write(int argc, const char **argv)
 
        if (write_commit_graph(odb,
                               pack_indexes,
-                              commit_hex,
+                              opts.stdin_commits ? &commits : NULL,
                               flags,
                               &split_opts))
                result = 1;
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 d3e7781e658a67d79df781df44a7d716a5094b30..a73de0a4c529f658c04e6a2a453b93c1c70718c8 100644 (file)
@@ -1372,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),
@@ -1393,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(),
        };
 
@@ -1700,9 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                      "new_index file. Check that disk is not full and quota is\n"
                      "not exceeded, and then \"git restore --staged :/\" to recover."));
 
-       if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
-           write_commit_graph_reachable(the_repository->objects->odb, 0, NULL))
-               return 1;
+       git_test_write_commit_graph_or_die();
 
        repo_rerere(the_repository, 0);
        run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
@@ -1721,6 +1719,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                                     &oid, flags);
        }
 
+       apply_autostash(git_path_merge_autostash(the_repository));
+
        UNLEAK(err);
        UNLEAK(sb);
        return 0;
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 1097e1e512bb3cd0798d07b1ff08df3af25adac7..3ae52c015d30243a40b2647f90e9c50bafa2f160 100644 (file)
@@ -156,9 +156,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 +178,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),
index 172dfbd852a9d50aeb93b42183a77e88979c6597..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 signature_check sigc = { 0 };
-               struct strbuf sig = STRBUF_INIT;
-
-               if (!buf || type != OBJ_TAG)
-                       goto next;
-               len = parse_signature(buf, size);
-
-               if (size == len)
-                       ; /* merely annotated */
-               else if (check_signature(buf, len, buf + len, size - len, &sigc) &&
-                       !sigc.gpg_output)
-                       strbuf_addstr(&sig, "gpg verification failed.\n");
-               else
-                       strbuf_addstr(&sig, sigc.gpg_output);
-               signature_check_clear(&sigc);
-
-               if (!tag_number++) {
-                       fmt_tag_signature(&tagbuf, &sig, buf, len);
-                       first_tag = i;
-               } else {
-                       if (tag_number == 2) {
-                               struct strbuf tagline = STRBUF_INIT;
-                               strbuf_addch(&tagline, '\n');
-                               strbuf_add_commented_lines(&tagline,
-                                               origins.items[first_tag].string,
-                                               strlen(origins.items[first_tag].string));
-                               strbuf_insert(&tagbuf, 0, tagline.buf,
-                                             tagline.len);
-                               strbuf_release(&tagline);
-                       }
-                       strbuf_addch(&tagbuf, '\n');
-                       strbuf_add_commented_lines(&tagbuf,
-                                       origins.items[i].string,
-                                       strlen(origins.items[i].string));
-                       fmt_tag_signature(&tagbuf, &sig, buf, len);
-               }
-               strbuf_release(&sig);
-       next:
-               free(buf);
-       }
-       if (tagbuf.len) {
-               strbuf_addch(out, '\n');
-               strbuf_addbuf(out, &tagbuf);
-       }
-       strbuf_release(&tagbuf);
-}
-
-static void find_merge_parents(struct merge_parents *result,
-                              struct strbuf *in, struct object_id *head)
-{
-       struct commit_list *parents;
-       struct commit *head_commit;
-       int pos = 0, i, j;
-
-       parents = NULL;
-       while (pos < in->len) {
-               int len;
-               char *p = in->buf + pos;
-               char *newline = strchr(p, '\n');
-               const char *q;
-               struct object_id oid;
-               struct commit *parent;
-               struct object *obj;
-
-               len = newline ? newline - p : strlen(p);
-               pos += len + !!newline;
-
-               if (parse_oid_hex(p, &oid, &q) ||
-                   q[0] != '\t' ||
-                   q[1] != '\t')
-                       continue; /* skip not-for-merge */
-               /*
-                * Do not use get_merge_parent() here; we do not have
-                * "name" here and we do not want to contaminate its
-                * util field yet.
-                */
-               obj = parse_object(the_repository, &oid);
-               parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
-               if (!parent)
-                       continue;
-               commit_list_insert(parent, &parents);
-               add_merge_parent(result, &obj->oid, &parent->object.oid);
-       }
-       head_commit = lookup_commit(the_repository, head);
-       if (head_commit)
-               commit_list_insert(head_commit, &parents);
-       reduce_heads_replace(&parents);
-
-       while (parents) {
-               struct commit *cmit = pop_commit(&parents);
-               for (i = 0; i < result->nr; i++)
-                       if (oideq(&result->item[i].commit, &cmit->object.oid))
-                               result->item[i].used = 1;
-       }
-
-       for (i = j = 0; i < result->nr; i++) {
-               if (result->item[i].used) {
-                       if (i != j)
-                               result->item[j] = result->item[i];
-                       j++;
-               }
-       }
-       result->nr = j;
-}
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
-                 struct fmt_merge_msg_opts *opts)
-{
-       int i = 0, pos = 0;
-       struct object_id head_oid;
-       const char *current_branch;
-       void *current_branch_to_free;
-       struct merge_parents merge_parents;
-
-       memset(&merge_parents, 0, sizeof(merge_parents));
-
-       /* get current branch */
-       current_branch = current_branch_to_free =
-               resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
-       if (!current_branch)
-               die("No current branch");
-       if (starts_with(current_branch, "refs/heads/"))
-               current_branch += 11;
-
-       find_merge_parents(&merge_parents, in, &head_oid);
-
-       /* get a line */
-       while (pos < in->len) {
-               int len;
-               char *newline, *p = in->buf + pos;
-
-               newline = strchr(p, '\n');
-               len = newline ? newline - p : strlen(p);
-               pos += len + !!newline;
-               i++;
-               p[len] = 0;
-               if (handle_line(p, &merge_parents))
-                       die("error in line %d: %.*s", i, len, p);
-       }
-
-       if (opts->add_title && srcs.nr)
-               fmt_merge_msg_title(out, current_branch);
-
-       if (origins.nr)
-               fmt_merge_msg_sigs(out);
-
-       if (opts->shortlog_len) {
-               struct commit *head;
-               struct rev_info rev;
-
-               head = lookup_commit_or_die(&head_oid, "HEAD");
-               repo_init_revisions(the_repository, &rev, NULL);
-               rev.commit_format = CMIT_FMT_ONELINE;
-               rev.ignore_merges = 1;
-               rev.limited = 1;
-
-               strbuf_complete_line(out);
-
-               for (i = 0; i < origins.nr; i++)
-                       shortlog(origins.items[i].string,
-                                origins.items[i].util,
-                                head, &rev, opts, out);
-       }
-
-       strbuf_complete_line(out);
-       free(current_branch_to_free);
-       free(merge_parents.item);
-       return 0;
-}
-
 int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 {
        const char *inpath = NULL;
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 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 a5c3ace9a0f68912c63f57dc4152d2f9c4f819e8..d104d5c6889ba2d2ffaaeafa08fbfcd759d8bc92 100644 (file)
@@ -166,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),
@@ -238,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);
        }
 
@@ -1631,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")),
@@ -1650,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,
@@ -1675,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"),
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 df83ba2a807a88fa5904a2acbe64a7caa7188224..923e32acf1a969810077eb9c42d14bce132ecfc7 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")),
@@ -475,6 +478,7 @@ static void finish(struct commit *head_commit,
        /* Run a post-merge hook */
        run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
 
+       apply_autostash(git_path_merge_autostash(the_repository));
        strbuf_release(&reflog_message);
 }
 
@@ -636,6 +640,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
                return 0;
        } else if (!strcmp(k, "gpg.mintrustlevel")) {
                check_trust_level = 0;
+       } else if (!strcmp(k, "merge.autostash")) {
+               autostash = git_config_bool(k, v);
+               return 0;
        }
 
        status = fmt_merge_msg_config(k, v, cb);
@@ -1283,6 +1290,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (abort_current_merge) {
                int nargc = 2;
                const char *nargv[] = {"reset", "--merge", NULL};
+               struct strbuf stash_oid = STRBUF_INIT;
 
                if (orig_argc != 2)
                        usage_msg_opt(_("--abort expects no arguments"),
@@ -1291,8 +1299,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (!file_exists(git_path_merge_head(the_repository)))
                        die(_("There is no merge to abort (MERGE_HEAD missing)."));
 
+               if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
+                   READ_ONELINER_SKIP_IF_EMPTY))
+                       unlink(git_path_merge_autostash(the_repository));
+
                /* Invoke 'git reset --merge' */
                ret = cmd_reset(nargc, nargv, prefix);
+
+               if (stash_oid.len)
+                       apply_autostash_oid(stash_oid.buf);
+
+               strbuf_release(&stash_oid);
                goto done;
        }
 
@@ -1515,6 +1532,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        goto done;
                }
 
+               if (autostash)
+                       create_autostash(the_repository,
+                                        git_path_merge_autostash(the_repository),
+                                        "merge");
                if (checkout_fast_forward(the_repository,
                                          &head_commit->object.oid,
                                          &commit->object.oid,
@@ -1581,6 +1602,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (fast_forward == FF_ONLY)
                die(_("Not possible to fast-forward, aborting."));
 
+       if (autostash)
+               create_autostash(the_repository,
+                                git_path_merge_autostash(the_repository),
+                                "merge");
+
        /* We are going to make a new commit. */
        git_committer_info(IDENT_STRICT);
 
@@ -1675,9 +1701,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                                   head_commit);
        }
 
-       if (squash)
+       if (squash) {
                finish(head_commit, remoteheads, NULL, NULL);
-       else
+
+               git_test_write_commit_graph_or_die();
+       } else
                write_merge_state(remoteheads);
 
        if (merge_was_ok)
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 fdd18c7ccb76de5988554667f50f51c94e474857..03b85f5166d358bda7c8dd3d1edc3097a1227388 100644 (file)
@@ -3380,9 +3380,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 +3427,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 +3454,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,
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..fd9acc722247ece74483f6a2a7674a4c5f712570 100644 (file)
@@ -6,6 +6,7 @@
 #include "reachable.h"
 #include "parse-options.h"
 #include "progress.h"
+#include "prune-packed.h"
 #include "object-store.h"
 
 static const char * const prune_usage[] = {
index b5d51ea74fb02ee7bca8c9cf7b4bbf82e466c1f9..00e5857a8d18da3394d6e5a3316e8f56bb98fdb5 100644 (file)
@@ -118,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),
@@ -164,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),
@@ -695,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");
 
@@ -942,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)
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 c466923869d6fdd211347125986e26c82e1d0153..ca6aa0dc7a8685a7dbdd5e07f5b20be7e3a38756 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>] "
@@ -474,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,
+               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),
@@ -590,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)
 {
@@ -606,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;
@@ -623,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);
@@ -645,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;
@@ -659,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);
@@ -668,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);
@@ -676,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);
@@ -719,51 +719,6 @@ static int rebase_write_basic_state(struct rebase_options *opts)
        return 0;
 }
 
-static int apply_autostash(struct rebase_options *opts)
-{
-       const char *path = state_dir_path("autostash", opts);
-       struct strbuf autostash = STRBUF_INIT;
-       struct child_process stash_apply = CHILD_PROCESS_INIT;
-
-       if (!file_exists(path))
-               return 0;
-
-       if (read_one(path, &autostash))
-               return error(_("Could not read '%s'"), path);
-       /* Ensure that the hash is not mistaken for a number */
-       strbuf_addstr(&autostash, "^0");
-       argv_array_pushl(&stash_apply.args,
-                        "stash", "apply", autostash.buf, NULL);
-       stash_apply.git_cmd = 1;
-       stash_apply.no_stderr = stash_apply.no_stdout =
-               stash_apply.no_stdin = 1;
-       if (!run_command(&stash_apply))
-               printf(_("Applied autostash.\n"));
-       else {
-               struct argv_array args = ARGV_ARRAY_INIT;
-               int res = 0;
-
-               argv_array_pushl(&args,
-                                "stash", "store", "-m", "autostash", "-q",
-                                autostash.buf, NULL);
-               if (run_command_v_opt(args.argv, RUN_GIT_CMD))
-                       res = error(_("Cannot store %s"), autostash.buf);
-               argv_array_clear(&args);
-               strbuf_release(&autostash);
-               if (res)
-                       return res;
-
-               fprintf(stderr,
-                       _("Applying autostash resulted in conflicts.\n"
-                         "Your changes are safe in the stash.\n"
-                         "You can run \"git stash pop\" or \"git stash drop\" "
-                         "at any time.\n"));
-       }
-
-       strbuf_release(&autostash);
-       return 0;
-}
-
 static int finish_rebase(struct rebase_options *opts)
 {
        struct strbuf dir = STRBUF_INIT;
@@ -771,7 +726,7 @@ static int finish_rebase(struct rebase_options *opts)
        int ret = 0;
 
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
-       apply_autostash(opts);
+       apply_autostash(state_dir_path("autostash", opts));
        close_object_store(the_repository->objects);
        /*
         * We ignore errors in 'gc --auto', since the
@@ -816,144 +771,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)
        }
 }
 
-#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
-
-#define RESET_HEAD_DETACH (1<<0)
-#define RESET_HEAD_HARD (1<<1)
-#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
-#define RESET_HEAD_REFS_ONLY (1<<3)
-#define RESET_ORIG_HEAD (1<<4)
-
-static int reset_head(struct object_id *oid, const char *action,
-                     const char *switch_to_branch, unsigned flags,
-                     const char *reflog_orig_head, const char *reflog_head)
-{
-       unsigned detach_head = flags & RESET_HEAD_DETACH;
-       unsigned reset_hard = flags & RESET_HEAD_HARD;
-       unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
-       unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
-       unsigned update_orig_head = flags & RESET_ORIG_HEAD;
-       struct object_id head_oid;
-       struct tree_desc desc[2] = { { NULL }, { NULL } };
-       struct lock_file lock = LOCK_INIT;
-       struct unpack_trees_options unpack_tree_opts;
-       struct tree *tree;
-       const char *reflog_action;
-       struct strbuf msg = STRBUF_INIT;
-       size_t prefix_len;
-       struct object_id *orig = NULL, oid_orig,
-               *old_orig = NULL, oid_old_orig;
-       int ret = 0, nr = 0;
-
-       if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
-               BUG("Not a fully qualified branch: '%s'", switch_to_branch);
-
-       if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
-               ret = -1;
-               goto leave_reset_head;
-       }
-
-       if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
-               ret = error(_("could not determine HEAD revision"));
-               goto leave_reset_head;
-       }
-
-       if (!oid)
-               oid = &head_oid;
-
-       if (refs_only)
-               goto reset_head_refs;
-
-       memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
-       setup_unpack_trees_porcelain(&unpack_tree_opts, action);
-       unpack_tree_opts.head_idx = 1;
-       unpack_tree_opts.src_index = the_repository->index;
-       unpack_tree_opts.dst_index = the_repository->index;
-       unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
-       unpack_tree_opts.update = 1;
-       unpack_tree_opts.merge = 1;
-       init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
-       if (!detach_head)
-               unpack_tree_opts.reset = 1;
-
-       if (repo_read_index_unmerged(the_repository) < 0) {
-               ret = error(_("could not read index"));
-               goto leave_reset_head;
-       }
-
-       if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) {
-               ret = error(_("failed to find tree of %s"),
-                           oid_to_hex(&head_oid));
-               goto leave_reset_head;
-       }
-
-       if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) {
-               ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
-               goto leave_reset_head;
-       }
-
-       if (unpack_trees(nr, desc, &unpack_tree_opts)) {
-               ret = -1;
-               goto leave_reset_head;
-       }
-
-       tree = parse_tree_indirect(oid);
-       prime_cache_tree(the_repository, the_repository->index, tree);
-
-       if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
-               ret = error(_("could not write index"));
-               goto leave_reset_head;
-       }
-
-reset_head_refs:
-       reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
-       strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
-       prefix_len = msg.len;
-
-       if (update_orig_head) {
-               if (!get_oid("ORIG_HEAD", &oid_old_orig))
-                       old_orig = &oid_old_orig;
-               if (!get_oid("HEAD", &oid_orig)) {
-                       orig = &oid_orig;
-                       if (!reflog_orig_head) {
-                               strbuf_addstr(&msg, "updating ORIG_HEAD");
-                               reflog_orig_head = msg.buf;
-                       }
-                       update_ref(reflog_orig_head, "ORIG_HEAD", orig,
-                                  old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
-               } else if (old_orig)
-                       delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
-       }
-
-       if (!reflog_head) {
-               strbuf_setlen(&msg, prefix_len);
-               strbuf_addstr(&msg, "updating HEAD");
-               reflog_head = msg.buf;
-       }
-       if (!switch_to_branch)
-               ret = update_ref(reflog_head, "HEAD", oid, orig,
-                                detach_head ? REF_NO_DEREF : 0,
-                                UPDATE_REFS_MSG_ON_ERR);
-       else {
-               ret = update_ref(reflog_head, switch_to_branch, oid,
-                                NULL, 0, UPDATE_REFS_MSG_ON_ERR);
-               if (!ret)
-                       ret = create_symref("HEAD", switch_to_branch,
-                                           reflog_head);
-       }
-       if (run_hook)
-               run_hook_le(NULL, "post-checkout",
-                           oid_to_hex(orig ? orig : &null_oid),
-                           oid_to_hex(oid), "1", NULL);
-
-leave_reset_head:
-       strbuf_release(&msg);
-       rollback_lock_file(&lock);
-       while (nr)
-               free((void *)desc[--nr].buffer);
-       return ret;
-}
-
 static int move_to_original_branch(struct rebase_options *opts)
 {
        struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -969,8 +786,10 @@ static int move_to_original_branch(struct rebase_options *opts)
                    opts->head_name, oid_to_hex(&opts->onto->object.oid));
        strbuf_addf(&head_reflog, "rebase finished: returning to %s",
                    opts->head_name);
-       ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
-                        orig_head_reflog.buf, head_reflog.buf);
+       ret = reset_head(the_repository, NULL, "", opts->head_name,
+                        RESET_HEAD_REFS_ONLY,
+                        orig_head_reflog.buf, head_reflog.buf,
+                        DEFAULT_REFLOG_ACTION);
 
        strbuf_release(&orig_head_reflog);
        strbuf_release(&head_reflog);
@@ -1058,8 +877,9 @@ static int run_am(struct rebase_options *opts)
                free(rebased_patches);
                argv_array_clear(&am.args);
 
-               reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
-                          "HEAD", NULL);
+               reset_head(the_repository, &opts->orig_head, "checkout",
+                          opts->head_name, 0,
+                          "HEAD", NULL, DEFAULT_REFLOG_ACTION);
                error(_("\ngit encountered an error while preparing the "
                        "patches to replay\n"
                        "these revisions:\n"
@@ -1218,7 +1038,7 @@ finished_rebase:
        } else if (status == 2) {
                struct strbuf dir = STRBUF_INIT;
 
-               apply_autostash(opts);
+               apply_autostash(state_dir_path("autostash", opts));
                strbuf_addstr(&dir, opts->state_dir);
                remove_dir_recursively(&dir, 0);
                strbuf_release(&dir);
@@ -1459,7 +1279,6 @@ static int check_exec_cmd(const char *cmd)
        return 0;
 }
 
-
 int cmd_rebase(int argc, const char **argv, const char *prefix)
 {
        struct rebase_options options = REBASE_OPTIONS_INIT;
@@ -1532,18 +1351,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"),
@@ -1552,18 +1371,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,
+               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")),
@@ -1663,6 +1481,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        die(_("cannot combine '--keep-base' with '--root'"));
        }
 
+       if (options.root && fork_point > 0)
+               die(_("cannot combine '--root' with '--fork-point'"));
+
        if (action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1720,8 +1541,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                rerere_clear(the_repository, &merge_rr);
                string_list_clear(&merge_rr, 1);
 
-               if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
-                              NULL, NULL) < 0)
+               if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
+                              NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
                        die(_("could not discard worktree changes"));
                remove_branch_state(the_repository, 0);
                if (read_basic_state(&options))
@@ -1738,9 +1559,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
                if (read_basic_state(&options))
                        exit(1);
-               if (reset_head(&options.orig_head, "reset",
+               if (reset_head(the_repository, &options.orig_head, "reset",
                               options.head_name, RESET_HEAD_HARD,
-                              NULL, NULL) < 0)
+                              NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
                remove_branch_state(the_repository, 0);
@@ -1748,6 +1569,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                goto cleanup;
        }
        case ACTION_QUIT: {
+               save_autostash(state_dir_path("autostash", &options));
                if (options.type == REBASE_MERGE) {
                        struct replay_opts replay = REPLAY_OPTS_INIT;
 
@@ -2098,49 +1920,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                die(_("could not read index"));
 
        if (options.autostash) {
-               struct lock_file lock_file = LOCK_INIT;
-               int fd;
-
-               fd = hold_locked_index(&lock_file, 0);
-               refresh_cache(REFRESH_QUIET);
-               if (0 <= fd)
-                       repo_update_index_if_able(the_repository, &lock_file);
-               rollback_lock_file(&lock_file);
-
-               if (has_unstaged_changes(the_repository, 1) ||
-                   has_uncommitted_changes(the_repository, 1)) {
-                       const char *autostash =
-                               state_dir_path("autostash", &options);
-                       struct child_process stash = CHILD_PROCESS_INIT;
-                       struct object_id oid;
-
-                       argv_array_pushl(&stash.args,
-                                        "stash", "create", "autostash", NULL);
-                       stash.git_cmd = 1;
-                       stash.no_stdin = 1;
-                       strbuf_reset(&buf);
-                       if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
-                               die(_("Cannot autostash"));
-                       strbuf_trim_trailing_newline(&buf);
-                       if (get_oid(buf.buf, &oid))
-                               die(_("Unexpected stash response: '%s'"),
-                                   buf.buf);
-                       strbuf_reset(&buf);
-                       strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
-
-                       if (safe_create_leading_directories_const(autostash))
-                               die(_("Could not create directory for '%s'"),
-                                   options.state_dir);
-                       write_file(autostash, "%s", oid_to_hex(&oid));
-                       printf(_("Created autostash: %s\n"), buf.buf);
-                       if (reset_head(NULL, "reset --hard",
-                                      NULL, RESET_HEAD_HARD, NULL, NULL) < 0)
-                               die(_("could not reset --hard"));
-
-                       if (discard_index(the_repository->index) < 0 ||
-                               repo_read_index(the_repository) < 0)
-                               die(_("could not read index"));
-               }
+               create_autostash(the_repository, state_dir_path("autostash", &options),
+                                DEFAULT_REFLOG_ACTION);
        }
 
        if (require_clean_work_tree(the_repository, "rebase",
@@ -2174,10 +1955,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                                strbuf_addf(&buf, "%s: checkout %s",
                                            getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
                                            options.switch_to);
-                               if (reset_head(&options.orig_head, "checkout",
+                               if (reset_head(the_repository,
+                                              &options.orig_head, "checkout",
                                               options.head_name,
                                               RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
-                                              NULL, buf.buf) < 0) {
+                                              NULL, buf.buf,
+                                              DEFAULT_REFLOG_ACTION) < 0) {
                                        ret = !!error(_("could not switch to "
                                                        "%s"),
                                                      options.switch_to);
@@ -2249,10 +2032,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        strbuf_addf(&msg, "%s: checkout %s",
                    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
-       if (reset_head(&options.onto->object.oid, "checkout", NULL,
+       if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
                       RESET_HEAD_DETACH | RESET_ORIG_HEAD |
                       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
-                      NULL, msg.buf))
+                      NULL, msg.buf, DEFAULT_REFLOG_ACTION))
                die(_("Could not detach HEAD"));
        strbuf_release(&msg);
 
@@ -2267,8 +2050,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                strbuf_addf(&msg, "rebase finished: %s onto %s",
                        options.head_name ? options.head_name : "detached HEAD",
                        oid_to_hex(&options.onto->object.oid));
-               reset_head(NULL, "Fast-forwarded", options.head_name,
-                          RESET_HEAD_REFS_ONLY, "HEAD", msg.buf);
+               reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
+                          RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
+                          DEFAULT_REFLOG_ACTION);
                strbuf_release(&msg);
                ret = !!finish_rebase(&options);
                goto cleanup;
index d46147f709b5a1a6b21eea62e3ea1c4c2f536dd2..a00f91c1a0a8b5979e0f2cde49637c3552bb256b 100644 (file)
@@ -499,12 +499,27 @@ static char *find_header(const char *msg, size_t len, const char *key,
        return NULL;
 }
 
+/*
+ * Return zero if a and b are equal up to n bytes and nonzero if they are not.
+ * This operation is guaranteed to run in constant time to avoid leaking data.
+ */
+static int constant_memequal(const char *a, const char *b, size_t n)
+{
+       int res = 0;
+       size_t i;
+
+       for (i = 0; i < n; i++)
+               res |= a[i] ^ b[i];
+       return res;
+}
+
 static const char *check_nonce(const char *buf, size_t len)
 {
        char *nonce = find_header(buf, len, "nonce", NULL);
        timestamp_t stamp, ostamp;
        char *bohmac, *expect = NULL;
        const char *retval = NONCE_BAD;
+       size_t noncelen;
 
        if (!nonce) {
                retval = NONCE_MISSING;
@@ -546,8 +561,14 @@ static const char *check_nonce(const char *buf, size_t len)
                goto leave;
        }
 
+       noncelen = strlen(nonce);
        expect = prepare_push_cert_nonce(service_dir, stamp);
-       if (strcmp(expect, nonce)) {
+       if (noncelen != strlen(expect)) {
+               /* This is not even the right size. */
+               retval = NONCE_BAD;
+               goto leave;
+       }
+       if (constant_memequal(expect, nonce, noncelen)) {
                /* Not what we would have signed earlier */
                retval = NONCE_BAD;
                goto leave;
@@ -872,12 +893,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
        opt.env = tmp_objdir_env(tmp_objdir);
        setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
        if (check_connected(command_singleton_iterator, cmd, &opt)) {
-               rollback_lock_file(&shallow_lock);
+               rollback_shallow_file(the_repository, &shallow_lock);
                oid_array_clear(&extra);
                return -1;
        }
 
-       commit_lock_file(&shallow_lock);
+       commit_shallow_file(the_repository, &shallow_lock);
 
        /*
         * Make sure setup_alternate_shallow() for the next ref does
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..1b686ee9cee7c13f7c0052f397e1006d079667a0 100644 (file)
@@ -10,6 +10,7 @@
 #include "argv-array.h"
 #include "midx.h"
 #include "packfile.h"
+#include "prune-packed.h"
 #include "object-store.h"
 #include "promisor-remote.h"
 
index 4c634111bd80f62b948ccb8935e0d1f6d21ab438..8ae69d6f2b9e5e1760dd40d32631b0ecf48a31e6 100644 (file)
@@ -302,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")),
index f2c5a34402962ac2ff263c532462e2fc30a19622..2b9610f12176a7d23df3dfea9765e595f0405c41 100644 (file)
@@ -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 6d586ef06dfd6e243053c3c7cda86b81fd5a3ddb..0c52a3b849c4c6f811995f3be89284052c2dd313 100644 (file)
@@ -861,30 +861,23 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
                               struct strbuf *untracked_files)
 {
        int i;
-       int max_len;
        int found = 0;
-       char *seen;
        struct dir_struct dir;
 
        memset(&dir, 0, sizeof(dir));
        if (include_untracked != INCLUDE_ALL_FILES)
                setup_standard_excludes(&dir);
 
-       seen = xcalloc(ps->nr, 1);
-
-       max_len = fill_directory(&dir, the_repository->index, ps);
+       fill_directory(&dir, the_repository->index, ps);
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
-               if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
-                       found++;
-                       strbuf_addstr(untracked_files, ent->name);
-                       /* NUL-terminate: will be fed to update-index -z */
-                       strbuf_addch(untracked_files, '\0');
-               }
+               found++;
+               strbuf_addstr(untracked_files, ent->name);
+               /* NUL-terminate: will be fed to update-index -z */
+               strbuf_addch(untracked_files, '\0');
                free(ent);
        }
 
-       free(seen);
        free(dir.entries);
        free(dir.ignored);
        clear_directory(&dir);
@@ -1041,7 +1034,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
        }
 
        cp_diff_tree.git_cmd = 1;
-       argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
+       argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
                         oid_to_hex(&info->w_tree), "--", NULL);
        if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
                ret = -1;
index dd160b49c7d407c9929bb91f48aec194dfc05f12..b93b7365f48bcfd4dee8f9587c933a4e22ff8d07 100644 (file)
@@ -410,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")),
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;
        }
 
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 c3a8cd2104641e8134f8af04dd4ac6bc4a841209..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
 
@@ -195,9 +222,17 @@ osx-clang|osx-gcc)
        # Travis CI OS X
        export GIT_SKIP_TESTS="t9810 t9816"
        ;;
-GIT_TEST_GETTEXT_POISON)
+GETTEXT_POISON)
        export GIT_TEST_GETTEXT_POISON=true
        ;;
+Linux32)
+       CC=gcc
+       ;;
+linux-musl)
+       CC=gcc
+       MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/python3 USE_LIBPCRE2=Yes"
+       MAKEFLAGS="$MAKEFLAGS NO_REGEX=Yes ICONV_OMITS_BOM=Yes"
+       ;;
 esac
 
 MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}"
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 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..aa3adb912f06305f342c076cfc5e210ca50a3834 100644 (file)
 #include "hashmap.h"
 #include "replace-object.h"
 #include "progress.h"
+#include "bloom.h"
+#include "commit-slab.h"
+
+void git_test_write_commit_graph_or_die(void)
+{
+       int flags = 0;
+       if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
+               return;
+
+       if (git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
+               flags = COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
+
+       if (write_commit_graph_reachable(the_repository->objects->odb,
+                                        flags, NULL))
+               die("failed to write commit-graph under GIT_TEST_COMMIT_GRAPH");
+}
 
 #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
 #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
 #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
 #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
 #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */
+#define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */
+#define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */
 #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */
+#define MAX_NUM_CHUNKS 7
 
 #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
 
 /* Remember to update object flag allocation in object.h */
 #define REACHABLE       (1u<<15)
 
-char *get_commit_graph_filename(struct object_directory *odb)
+/* Keep track of the order in which commits are added to our list. */
+define_commit_slab(commit_pos, int);
+static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos);
+
+static void set_commit_pos(struct repository *r, const struct object_id *oid)
+{
+       static int32_t max_pos;
+       struct commit *commit = lookup_commit(r, oid);
+
+       if (!commit)
+               return; /* should never happen, but be lenient */
+
+       *commit_pos_at(&commit_pos, commit) = max_pos++;
+}
+
+static int commit_pos_cmp(const void *va, const void *vb)
+{
+       const struct commit *a = *(const struct commit **)va;
+       const struct commit *b = *(const struct commit **)vb;
+       return commit_pos_at(&commit_pos, a) -
+              commit_pos_at(&commit_pos, b);
+}
+
+static int commit_gen_cmp(const void *va, const void *vb)
+{
+       const struct commit *a = *(const struct commit **)va;
+       const struct commit *b = *(const struct commit **)vb;
+
+       /* lower generation commits first */
+       if (a->generation < b->generation)
+               return -1;
+       else if (a->generation > b->generation)
+               return 1;
+
+       /* use date as a heuristic when generations are equal */
+       if (a->date < b->date)
+               return -1;
+       else if (a->date > b->date)
+               return 1;
+       return 0;
+}
+
+char *get_commit_graph_filename(struct object_directory *obj_dir)
 {
-       return xstrfmt("%s/info/commit-graph", odb->path);
+       return xstrfmt("%s/info/commit-graph", obj_dir->path);
 }
 
 static char *get_split_graph_filename(struct object_directory *odb,
@@ -69,7 +130,6 @@ static uint8_t oid_version(void)
 static struct commit_graph *alloc_commit_graph(void)
 {
        struct commit_graph *g = xcalloc(1, sizeof(*g));
-       g->graph_fd = -1;
 
        return g;
 }
@@ -123,14 +183,13 @@ struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
                return NULL;
        }
        graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
-       ret = parse_commit_graph(graph_map, fd, graph_size);
+       close(fd);
+       ret = parse_commit_graph(graph_map, graph_size);
 
        if (ret)
                ret->odb = odb;
-       else {
+       else
                munmap(graph_map, graph_size);
-               close(fd);
-       }
 
        return ret;
 }
@@ -165,8 +224,7 @@ static int verify_commit_graph_lite(struct commit_graph *g)
        return 0;
 }
 
-struct commit_graph *parse_commit_graph(void *graph_map, int fd,
-                                       size_t graph_size)
+struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
 {
        const unsigned char *data, *chunk_lookup;
        uint32_t i;
@@ -209,7 +267,6 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
 
        graph->hash_len = the_hash_algo->rawsz;
        graph->num_chunks = *(unsigned char*)(data + 6);
-       graph->graph_fd = fd;
        graph->data = graph_map;
        graph->data_len = graph_size;
 
@@ -274,6 +331,32 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                                chunk_repeated = 1;
                        else
                                graph->chunk_base_graphs = data + chunk_offset;
+                       break;
+
+               case GRAPH_CHUNKID_BLOOMINDEXES:
+                       if (graph->chunk_bloom_indexes)
+                               chunk_repeated = 1;
+                       else
+                               graph->chunk_bloom_indexes = data + chunk_offset;
+                       break;
+
+               case GRAPH_CHUNKID_BLOOMDATA:
+                       if (graph->chunk_bloom_data)
+                               chunk_repeated = 1;
+                       else {
+                               uint32_t hash_version;
+                               graph->chunk_bloom_data = data + chunk_offset;
+                               hash_version = get_be32(data + chunk_offset);
+
+                               if (hash_version != 1)
+                                       break;
+
+                               graph->bloom_filter_settings = xmalloc(sizeof(struct bloom_filter_settings));
+                               graph->bloom_filter_settings->hash_version = hash_version;
+                               graph->bloom_filter_settings->num_hashes = get_be32(data + chunk_offset + 4);
+                               graph->bloom_filter_settings->bits_per_entry = get_be32(data + chunk_offset + 8);
+                       }
+                       break;
                }
 
                if (chunk_repeated) {
@@ -292,6 +375,15 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,
                last_chunk_offset = chunk_offset;
        }
 
+       if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) {
+               init_bloom_filters();
+       } else {
+               /* We need both the bloom chunks to exist together. Else ignore the data */
+               graph->chunk_bloom_indexes = NULL;
+               graph->chunk_bloom_data = NULL;
+               graph->bloom_filter_settings = NULL;
+       }
+
        hashcpy(graph->oid.hash, graph->data + graph->data_len - graph->hash_len);
 
        if (verify_commit_graph_lite(graph)) {
@@ -788,9 +880,12 @@ struct write_commit_graph_context {
        unsigned append:1,
                 report_progress:1,
                 split:1,
-                check_oids:1;
+                check_oids:1,
+                changed_paths:1,
+                order_by_pack:1;
 
        const struct split_commit_graph_opts *split_opts;
+       size_t total_bloom_filter_data_size;
 };
 
 static void write_graph_chunk_fanout(struct hashfile *f,
@@ -866,7 +961,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
 
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
-                       else {
+                       else if (ctx->new_base_graph) {
                                uint32_t pos;
                                if (find_commit_in_graph(parent->item,
                                                         ctx->new_base_graph,
@@ -897,7 +992,7 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len,
 
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
-                       else {
+                       else if (ctx->new_base_graph) {
                                uint32_t pos;
                                if (find_commit_in_graph(parent->item,
                                                         ctx->new_base_graph,
@@ -964,7 +1059,7 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
 
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
-                       else {
+                       else if (ctx->new_base_graph) {
                                uint32_t pos;
                                if (find_commit_in_graph(parent->item,
                                                         ctx->new_base_graph,
@@ -986,6 +1081,59 @@ static void write_graph_chunk_extra_edges(struct hashfile *f,
        }
 }
 
+static void write_graph_chunk_bloom_indexes(struct hashfile *f,
+                                           struct write_commit_graph_context *ctx)
+{
+       struct commit **list = ctx->commits.list;
+       struct commit **last = ctx->commits.list + ctx->commits.nr;
+       uint32_t cur_pos = 0;
+       struct progress *progress = NULL;
+       int i = 0;
+
+       if (ctx->report_progress)
+               progress = start_delayed_progress(
+                       _("Writing changed paths Bloom filters index"),
+                       ctx->commits.nr);
+
+       while (list < last) {
+               struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+               cur_pos += filter->len;
+               display_progress(progress, ++i);
+               hashwrite_be32(f, cur_pos);
+               list++;
+       }
+
+       stop_progress(&progress);
+}
+
+static void write_graph_chunk_bloom_data(struct hashfile *f,
+                                        struct write_commit_graph_context *ctx,
+                                        const struct bloom_filter_settings *settings)
+{
+       struct commit **list = ctx->commits.list;
+       struct commit **last = ctx->commits.list + ctx->commits.nr;
+       struct progress *progress = NULL;
+       int i = 0;
+
+       if (ctx->report_progress)
+               progress = start_delayed_progress(
+                       _("Writing changed paths Bloom filters data"),
+                       ctx->commits.nr);
+
+       hashwrite_be32(f, settings->hash_version);
+       hashwrite_be32(f, settings->num_hashes);
+       hashwrite_be32(f, settings->bits_per_entry);
+
+       while (list < last) {
+               struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+               display_progress(progress, ++i);
+               hashwrite(f, filter->data, filter->len * sizeof(unsigned char));
+               list++;
+       }
+
+       stop_progress(&progress);
+}
+
 static int oid_compare(const void *_a, const void *_b)
 {
        const struct object_id *a = (const struct object_id *)_a;
@@ -1017,6 +1165,8 @@ static int add_packed_commits(const struct object_id *oid,
        oidcpy(&(ctx->oids.list[ctx->oids.nr]), oid);
        ctx->oids.nr++;
 
+       set_commit_pos(ctx->r, oid);
+
        return 0;
 }
 
@@ -1037,6 +1187,8 @@ static void close_reachable(struct write_commit_graph_context *ctx)
 {
        int i;
        struct commit *commit;
+       enum commit_graph_split_flags flags = ctx->split_opts ?
+               ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
 
        if (ctx->report_progress)
                ctx->progress = start_delayed_progress(
@@ -1066,8 +1218,9 @@ static void close_reachable(struct write_commit_graph_context *ctx)
                if (!commit)
                        continue;
                if (ctx->split) {
-                       if (!parse_commit(commit) &&
-                           commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
+                       if ((!parse_commit(commit) &&
+                            commit->graph_pos == COMMIT_NOT_FROM_GRAPH) ||
+                           flags == COMMIT_GRAPH_SPLIT_REPLACE)
                                add_missing_parents(ctx, commit);
                } else if (!parse_commit_no_graph(commit))
                        add_missing_parents(ctx, commit);
@@ -1133,13 +1286,45 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
        stop_progress(&ctx->progress);
 }
 
-static int add_ref_to_list(const char *refname,
-                          const struct object_id *oid,
-                          int flags, void *cb_data)
+static void compute_bloom_filters(struct write_commit_graph_context *ctx)
+{
+       int i;
+       struct progress *progress = NULL;
+       struct commit **sorted_commits;
+
+       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 string_list *list = (struct string_list *)cb_data;
+       struct oidset *commits = (struct oidset *)cb_data;
 
-       string_list_append(list, oid_to_hex(oid));
+       oidset_insert(commits, oid);
        return 0;
 }
 
@@ -1147,14 +1332,14 @@ int write_commit_graph_reachable(struct object_directory *odb,
                                 enum commit_graph_write_flags flags,
                                 const struct split_commit_graph_opts *split_opts)
 {
-       struct string_list list = STRING_LIST_INIT_DUP;
+       struct oidset commits = OIDSET_INIT;
        int result;
 
-       for_each_ref(add_ref_to_list, &list);
-       result = write_commit_graph(odb, NULL, &list,
+       for_each_ref(add_ref_to_set, &commits);
+       result = write_commit_graph(odb, NULL, &commits,
                                    flags, split_opts);
 
-       string_list_clear(&list, 0);
+       oidset_clear(&commits);
        return result;
 }
 
@@ -1203,39 +1388,46 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx,
        return 0;
 }
 
-static int fill_oids_from_commit_hex(struct write_commit_graph_context *ctx,
-                                    struct string_list *commit_hex)
+static int fill_oids_from_commits(struct write_commit_graph_context *ctx,
+                                 struct oidset *commits)
 {
-       uint32_t i;
+       uint32_t i = 0;
        struct strbuf progress_title = STRBUF_INIT;
+       struct oidset_iter iter;
+       struct object_id *oid;
+
+       if (!oidset_size(commits))
+               return 0;
 
        if (ctx->report_progress) {
                strbuf_addf(&progress_title,
                            Q_("Finding commits for commit graph from %d ref",
                               "Finding commits for commit graph from %d refs",
-                              commit_hex->nr),
-                           commit_hex->nr);
+                              oidset_size(commits)),
+                           oidset_size(commits));
                ctx->progress = start_delayed_progress(
                                        progress_title.buf,
-                                       commit_hex->nr);
+                                       oidset_size(commits));
        }
-       for (i = 0; i < commit_hex->nr; i++) {
-               const char *end;
-               struct object_id oid;
+
+       oidset_iter_init(commits, &iter);
+       while ((oid = oidset_iter_next(&iter))) {
                struct commit *result;
 
-               display_progress(ctx->progress, i + 1);
-               if (!parse_oid_hex(commit_hex->items[i].string, &oid, &end) &&
-                   (result = lookup_commit_reference_gently(ctx->r, &oid, 1))) {
+               display_progress(ctx->progress, ++i);
+
+               result = lookup_commit_reference_gently(ctx->r, oid, 1);
+               if (result) {
                        ALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);
                        oidcpy(&ctx->oids.list[ctx->oids.nr], &(result->object.oid));
                        ctx->oids.nr++;
                } else if (ctx->check_oids) {
                        error(_("invalid commit object id: %s"),
-                           commit_hex->items[i].string);
+                             oid_to_hex(oid));
                        return -1;
                }
        }
+
        stop_progress(&ctx->progress);
        strbuf_release(&progress_title);
 
@@ -1287,6 +1479,8 @@ static uint32_t count_distinct_commits(struct write_commit_graph_context *ctx)
 static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
 {
        uint32_t i;
+       enum commit_graph_split_flags flags = ctx->split_opts ?
+               ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
 
        ctx->num_extra_edges = 0;
        if (ctx->report_progress)
@@ -1303,11 +1497,14 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
                ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
                ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.list[i]);
 
-               if (ctx->split &&
+               if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
                    ctx->commits.list[ctx->commits.nr]->graph_pos != COMMIT_NOT_FROM_GRAPH)
                        continue;
 
-               parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
+               if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
+                       parse_commit(ctx->commits.list[ctx->commits.nr]);
+               else
+                       parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]);
 
                num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents);
                if (num_parents > 2)
@@ -1350,12 +1547,13 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
        int fd;
        struct hashfile *f;
        struct lock_file lk = LOCK_INIT;
-       uint32_t chunk_ids[6];
-       uint64_t chunk_offsets[6];
+       uint32_t chunk_ids[MAX_NUM_CHUNKS + 1];
+       uint64_t chunk_offsets[MAX_NUM_CHUNKS + 1];
        const unsigned hashsz = the_hash_algo->rawsz;
        struct strbuf progress_title = STRBUF_INIT;
        int num_chunks = 3;
        struct object_id file_hash;
+       const struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
 
        if (ctx->split) {
                struct strbuf tmp_file = STRBUF_INIT;
@@ -1378,17 +1576,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 +1606,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 +1630,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 +1676,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 +1713,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 +1758,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 +1769,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 +1812,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 +1949,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 +1998,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 +2018,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 +2042,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 +2072,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 +2108,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 +2343,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 008a0fa4a01d06c0e191e5988823ff630931d30d..ab91d21131c8dfbc5386a3c5c9ac538f64d94601 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -249,6 +249,8 @@ struct oid_array;
 struct ref;
 int register_shallow(struct repository *r, const struct object_id *oid);
 int unregister_shallow(const struct object_id *oid);
+int commit_shallow_file(struct repository *r, struct lock_file *lk);
+void rollback_shallow_file(struct repository *r, struct lock_file *lk);
 int for_each_commit_graft(each_commit_graft_fn, void *);
 int is_repository_shallow(struct repository *r);
 struct commit_list *get_shallow_commits(struct object_array *heads,
diff --git a/compat/compiler.h b/compat/compiler.h
new file mode 100644 (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 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 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 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 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 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 5aa87d45e3e5a03b7530fcc36d8a71581250f446..572449825c5a4c8fd1ff65bb0c947d0c567d002b 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1018,7 +1018,7 @@ static int apply_filter(const char *path, const char *src, size_t len,
 static int read_convert_config(const char *var, const char *value, void *cb)
 {
        const char *key, *name;
-       int namelen;
+       size_t namelen;
        struct convert_driver *drv;
 
        /*
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 064e25e5d5754b68e54664fbb13b79b289d4e006..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,12 +435,12 @@ 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, ':');
 
@@ -427,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++;
@@ -450,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)
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) {
diff --git a/diff.c b/diff.c
index 1010d806f50dac47927f6f70125647e9bf8276ef..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;
@@ -4144,7 +4177,7 @@ static struct diff_tempfile *prepare_temp_file(struct repository *r,
                return temp;
        }
        else {
-               if (diff_populate_filespec(r, one, 0))
+               if (diff_populate_filespec(r, one, NULL))
                        die("cannot read data blob for %s", one->path);
                prep_temp_blob(r->index, name, temp,
                               one->data, one->size,
@@ -6410,9 +6443,9 @@ static int diff_filespec_is_identical(struct repository *r,
 {
        if (S_ISGITLINK(one->mode))
                return 0;
-       if (diff_populate_filespec(r, one, 0))
+       if (diff_populate_filespec(r, one, NULL))
                return 0;
-       if (diff_populate_filespec(r, two, 0))
+       if (diff_populate_filespec(r, two, NULL))
                return 0;
        return !memcmp(one->data, two->data, one->size);
 }
@@ -6420,6 +6453,12 @@ static int diff_filespec_is_identical(struct repository *r,
 static int diff_filespec_check_stat_unmatch(struct repository *r,
                                            struct diff_filepair *p)
 {
+       struct diff_populate_filespec_options dpf_options = {
+               .check_size_only = 1,
+               .missing_object_cb = diff_queued_diff_prefetch,
+               .missing_object_data = r,
+       };
+
        if (p->done_skip_stat_unmatch)
                return p->skip_stat_unmatch_result;
 
@@ -6442,8 +6481,8 @@ static int diff_filespec_check_stat_unmatch(struct repository *r,
            !DIFF_FILE_VALID(p->two) ||
            (p->one->oid_valid && p->two->oid_valid) ||
            (p->one->mode != p->two->mode) ||
-           diff_populate_filespec(r, p->one, CHECK_SIZE_ONLY) ||
-           diff_populate_filespec(r, p->two, CHECK_SIZE_ONLY) ||
+           diff_populate_filespec(r, p->one, &dpf_options) ||
+           diff_populate_filespec(r, p->two, &dpf_options) ||
            (p->one->size != p->two->size) ||
            !diff_filespec_is_identical(r, p->one, p->two)) /* (2) */
                p->skip_stat_unmatch_result = 1;
@@ -6494,9 +6533,9 @@ void diffcore_fix_diff_index(void)
        QSORT(q->queue, q->nr, diffnamecmp);
 }
 
-static void add_if_missing(struct repository *r,
-                          struct oid_array *to_fetch,
-                          const struct diff_filespec *filespec)
+void diff_add_if_missing(struct repository *r,
+                        struct oid_array *to_fetch,
+                        const struct diff_filespec *filespec)
 {
        if (filespec && filespec->oid_valid &&
            !S_ISGITLINK(filespec->mode) &&
@@ -6505,30 +6544,48 @@ static void add_if_missing(struct repository *r,
                oid_array_append(to_fetch, &filespec->oid);
 }
 
-void diffcore_std(struct diff_options *options)
+void diff_queued_diff_prefetch(void *repository)
 {
-       if (options->repo == the_repository && has_promisor_remote()) {
-               /*
-                * Prefetch the diff pairs that are about to be flushed.
-                */
-               int i;
-               struct diff_queue_struct *q = &diff_queued_diff;
-               struct oid_array to_fetch = OID_ARRAY_INIT;
+       struct repository *repo = repository;
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct oid_array to_fetch = OID_ARRAY_INIT;
 
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       add_if_missing(options->repo, &to_fetch, p->one);
-                       add_if_missing(options->repo, &to_fetch, p->two);
-               }
-               if (to_fetch.nr)
-                       /*
-                        * NEEDSWORK: Consider deduplicating the OIDs sent.
-                        */
-                       promisor_remote_get_direct(options->repo,
-                                                  to_fetch.oid, to_fetch.nr);
-               oid_array_clear(&to_fetch);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_add_if_missing(repo, &to_fetch, p->one);
+               diff_add_if_missing(repo, &to_fetch, p->two);
        }
 
+       /*
+        * NEEDSWORK: Consider deduplicating the OIDs sent.
+        */
+       promisor_remote_get_direct(repo, to_fetch.oid, to_fetch.nr);
+
+       oid_array_clear(&to_fetch);
+}
+
+void diffcore_std(struct diff_options *options)
+{
+       int output_formats_to_prefetch = DIFF_FORMAT_DIFFSTAT |
+               DIFF_FORMAT_NUMSTAT |
+               DIFF_FORMAT_PATCH |
+               DIFF_FORMAT_SHORTSTAT |
+               DIFF_FORMAT_DIRSTAT;
+
+       /*
+        * Check if the user requested a blob-data-requiring diff output and/or
+        * break-rewrite detection (which requires blob data). If yes, prefetch
+        * the diff pairs.
+        *
+        * If no prefetching occurs, diffcore_rename() will prefetch if it
+        * decides that it needs inexact rename detection.
+        */
+       if (options->repo == the_repository && has_promisor_remote() &&
+           (options->output_format & output_formats_to_prefetch ||
+            options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK))
+               diff_queued_diff_prefetch(options->repo);
+
        /* NOTE please keep the following in sync with diff_tree_combined() */
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
@@ -6774,7 +6831,7 @@ size_t fill_textconv(struct repository *r,
                        *outbuf = "";
                        return 0;
                }
-               if (diff_populate_filespec(r, df, 0))
+               if (diff_populate_filespec(r, df, NULL))
                        die("unable to read files to diff");
                *outbuf = df->data;
                return df->size;
diff --git a/diff.h b/diff.h
index 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 202dda11a6b206162a7199718c21c43beb698e22..c98970274c4056178b0e9a37e3ba38b323707b50 100644 (file)
 
 struct object_entry {
        struct pack_idx_entry idx;
-       struct object_entry *next;
+       struct hashmap_entry ent;
        uint32_t type : TYPE_BITS,
                pack_id : PACK_ID_BITS,
                depth : DEPTH_BITS;
 };
 
+static int object_entry_hashcmp(const void *map_data,
+                               const struct hashmap_entry *eptr,
+                               const struct hashmap_entry *entry_or_key,
+                               const void *keydata)
+{
+       const struct object_id *oid = keydata;
+       const struct object_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct object_entry, ent);
+       if (oid)
+               return oidcmp(&e1->idx.oid, oid);
+
+       e2 = container_of(entry_or_key, const struct object_entry, ent);
+       return oidcmp(&e1->idx.oid, &e2->idx.oid);
+}
+
 struct object_entry_pool {
        struct object_entry_pool *next_pool;
        struct object_entry *next_free;
@@ -178,7 +194,7 @@ static off_t pack_size;
 /* Table of objects we've written. */
 static unsigned int object_entry_alloc = 5000;
 static struct object_entry_pool *blocks;
-static struct object_entry *object_table[1 << 16];
+static struct hashmap object_table;
 static struct mark_set *marks;
 static const char *export_marks_file;
 static const char *import_marks_file;
@@ -455,44 +471,37 @@ static struct object_entry *new_object(struct object_id *oid)
 
 static struct object_entry *find_object(struct object_id *oid)
 {
-       unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-       struct object_entry *e;
-       for (e = object_table[h]; e; e = e->next)
-               if (oideq(oid, &e->idx.oid))
-                       return e;
-       return NULL;
+       return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid,
+                                          struct object_entry, ent);
 }
 
 static struct object_entry *insert_object(struct object_id *oid)
 {
-       unsigned int h = oid->hash[0] << 8 | oid->hash[1];
-       struct object_entry *e = object_table[h];
+       struct object_entry *e;
+       unsigned int hash = oidhash(oid);
 
-       while (e) {
-               if (oideq(oid, &e->idx.oid))
-                       return e;
-               e = e->next;
+       e = hashmap_get_entry_from_hash(&object_table, hash, oid,
+                                       struct object_entry, ent);
+       if (!e) {
+               e = new_object(oid);
+               e->idx.offset = 0;
+               hashmap_entry_init(&e->ent, hash);
+               hashmap_add(&object_table, &e->ent);
        }
 
-       e = new_object(oid);
-       e->next = object_table[h];
-       e->idx.offset = 0;
-       object_table[h] = e;
        return e;
 }
 
 static void invalidate_pack_id(unsigned int id)
 {
-       unsigned int h;
        unsigned long lu;
        struct tag *t;
+       struct hashmap_iter iter;
+       struct object_entry *e;
 
-       for (h = 0; h < ARRAY_SIZE(object_table); h++) {
-               struct object_entry *e;
-
-               for (e = object_table[h]; e; e = e->next)
-                       if (e->pack_id == id)
-                               e->pack_id = MAX_PACK_ID;
+       hashmap_for_each_entry(&object_table, &iter, e, ent) {
+               if (e->pack_id == id)
+                       e->pack_id = MAX_PACK_ID;
        }
 
        for (lu = 0; lu < branch_table_sz; lu++) {
@@ -3511,6 +3520,8 @@ int cmd_main(int argc, const char **argv)
        avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
        marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
 
+       hashmap_init(&object_table, object_entry_hashcmp, NULL, 0);
+
        /*
         * We don't parse most options until after we've seen the set of
         * "feature" lines at the start of the stream (which allows the command
index 0b07b3ee73b8727ce2af65b90b43f2cb10246ea9..f73a2ce6cba2af20669c9bf98acabb878725a0a7 100644 (file)
@@ -1143,6 +1143,7 @@ static void add_common(struct strbuf *req_buf, struct oidset *common)
 }
 
 static int add_haves(struct fetch_negotiator *negotiator,
+                    int seen_ack,
                     struct strbuf *req_buf,
                     int *haves_to_send, int *in_vain)
 {
@@ -1157,7 +1158,7 @@ static int add_haves(struct fetch_negotiator *negotiator,
        }
 
        *in_vain += haves_added;
-       if (!haves_added || *in_vain >= MAX_IN_VAIN) {
+       if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) {
                /* Send Done */
                packet_buf_write(req_buf, "done\n");
                ret = 1;
@@ -1173,7 +1174,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
                              struct fetch_pack_args *args,
                              const struct ref *wants, struct oidset *common,
                              int *haves_to_send, int *in_vain,
-                             int sideband_all)
+                             int sideband_all, int seen_ack)
 {
        int ret = 0;
        struct strbuf req_buf = STRBUF_INIT;
@@ -1230,7 +1231,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
                add_common(&req_buf, common);
 
                /* Add initial haves */
-               ret = add_haves(negotiator, &req_buf, haves_to_send, in_vain);
+               ret = add_haves(negotiator, seen_ack, &req_buf,
+                               haves_to_send, in_vain);
        }
 
        /* Send request */
@@ -1268,9 +1270,29 @@ static int process_section_header(struct packet_reader *reader,
        return ret;
 }
 
-static int process_acks(struct fetch_negotiator *negotiator,
-                       struct packet_reader *reader,
-                       struct oidset *common)
+enum common_found {
+       /*
+        * No commit was found to be possessed by both the client and the
+        * server, and "ready" was not received.
+        */
+       NO_COMMON_FOUND,
+
+       /*
+        * At least one commit was found to be possessed by both the client and
+        * the server, and "ready" was not received.
+        */
+       COMMON_FOUND,
+
+       /*
+        * "ready" was received, indicating that the server is ready to send
+        * the packfile without any further negotiation.
+        */
+       READY
+};
+
+static enum common_found process_acks(struct fetch_negotiator *negotiator,
+                                     struct packet_reader *reader,
+                                     struct oidset *common)
 {
        /* received */
        int received_ready = 0;
@@ -1285,6 +1307,7 @@ static int process_acks(struct fetch_negotiator *negotiator,
 
                if (skip_prefix(reader->line, "ACK ", &arg)) {
                        struct object_id oid;
+                       received_ack = 1;
                        if (!get_oid_hex(arg, &oid)) {
                                struct commit *commit;
                                oidset_insert(common, &oid);
@@ -1319,8 +1342,8 @@ static int process_acks(struct fetch_negotiator *negotiator,
        if (!received_ready && reader->status != PACKET_READ_FLUSH)
                die(_("expected no other sections to be sent after no 'ready'"));
 
-       /* return 0 if no common, 1 if there are common, or 2 if ready */
-       return received_ready ? 2 : (received_ack ? 1 : 0);
+       return received_ready ? READY :
+               (received_ack ? COMMON_FOUND : NO_COMMON_FOUND);
 }
 
 static void receive_shallow_info(struct fetch_pack_args *args,
@@ -1444,6 +1467,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
        int haves_to_send = INITIAL_FLUSH;
        struct fetch_negotiator negotiator_alloc;
        struct fetch_negotiator *negotiator;
+       int seen_ack = 0;
 
        if (args->no_dependents) {
                negotiator = NULL;
@@ -1500,7 +1524,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        if (send_fetch_request(negotiator, fd[1], args, ref,
                                               &common,
                                               &haves_to_send, &in_vain,
-                                              reader.use_sideband))
+                                              reader.use_sideband,
+                                              seen_ack))
                                state = FETCH_GET_PACK;
                        else
                                state = FETCH_PROCESS_ACKS;
@@ -1508,13 +1533,14 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                case FETCH_PROCESS_ACKS:
                        /* Process ACKs/NAKs */
                        switch (process_acks(negotiator, &reader, &common)) {
-                       case 2:
+                       case READY:
                                state = FETCH_GET_PACK;
                                break;
-                       case 1:
+                       case COMMON_FOUND:
                                in_vain = 0;
+                               seen_ack = 1;
                                /* fallthrough */
-                       default:
+                       case NO_COMMON_FOUND:
                                state = FETCH_SEND_REQUEST;
                                break;
                        }
@@ -1629,9 +1655,9 @@ static void update_shallow(struct fetch_pack_args *args,
        if (args->deepen && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
                        unlink_or_warn(git_path_shallow(the_repository));
-                       rollback_lock_file(&shallow_lock);
+                       rollback_shallow_file(the_repository, &shallow_lock);
                } else
-                       commit_lock_file(&shallow_lock);
+                       commit_shallow_file(the_repository, &shallow_lock);
                alternate_shallow_file = NULL;
                return;
        }
@@ -1655,7 +1681,7 @@ static void update_shallow(struct fetch_pack_args *args,
                        setup_alternate_shallow(&shallow_lock,
                                                &alternate_shallow_file,
                                                &extra);
-                       commit_lock_file(&shallow_lock);
+                       commit_shallow_file(the_repository, &shallow_lock);
                        alternate_shallow_file = NULL;
                }
                oid_array_clear(&extra);
@@ -1693,7 +1719,7 @@ static void update_shallow(struct fetch_pack_args *args,
                setup_alternate_shallow(&shallow_lock,
                                        &alternate_shallow_file,
                                        &extra);
-               commit_lock_file(&shallow_lock);
+               commit_shallow_file(the_repository, &shallow_lock);
                oid_array_clear(&extra);
                oid_array_clear(&ref);
                alternate_shallow_file = NULL;
@@ -1785,7 +1811,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
                        error(_("remote did not send all necessary objects"));
                        free_refs(ref_cpy);
                        ref_cpy = NULL;
-                       rollback_lock_file(&shallow_lock);
+                       rollback_shallow_file(the_repository, &shallow_lock);
                        goto cleanup;
                }
                args->connectivity_checked = 1;
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
new file mode 100644 (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 89f915cae99b12d134aeb7e72ec460dfe1e8e247..08e0439df0a2cccf62274c9636e8eeb609827d2f 100755 (executable)
@@ -48,6 +48,8 @@ depth=
 progress=
 dissociate=
 single_branch=
+jobs=
+recommend_shallow=
 
 die_if_unmatched ()
 {
index 1a02a1242d5be347b9dd70d5366f3bf077732366..0959a782eccb60f1358f7abf5db534058c37e69e 100755 (executable)
@@ -4641,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;
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/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
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 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 0064788b252cadb98b71eec4d3517f92c88bba86..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;
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 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);
diff --git a/midx.c b/midx.c
index a520e263956086a988b77d6e22a7262c9aeba9b8..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])
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 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 3a2d9d1115a45e47a93fa3950ad91a0eb1af4c7c..209ae7a1736e49818acc1e05b7f3cb4aceada0fe 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -54,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 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 9bd717c3073f4f4af8d69a14f18ec94f06079c6e..8b2c7531919eef89b340b3323d1cbd806ba57b0c 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1535,5 +1535,6 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
 REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
 REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
+REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
 REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
 REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index 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 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 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);
diff --git a/refs.c b/refs.c
index b8759116cd0906632249170bdae8623371b05ffc..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)
 {
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 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;
 }
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 5bc96444b66813c817c3260151462ee678efcd45..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
@@ -3385,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)
@@ -3402,6 +3540,7 @@ int prepare_revision_walk(struct rev_info *revs)
                simplify_merges(revs);
        if (revs->children.name)
                set_children(revs);
+
        return 0;
 }
 
index c1af164b30e6ce823635d6b382e7dac47f7e7a41..93491b79d475adb32414008558d0d94ec9240d56 100644 (file)
@@ -59,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 {
@@ -296,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 da4741ce4ae1d447fdefbb81cd99b9d7cfc33eaf..d1b7edc9957874dea1a1d1c8daf62e2ef2d7f8d8 100644 (file)
@@ -190,10 +190,8 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
 
                if (reader->line[0] == 'o' && reader->line[1] == 'k')
                        hint->status = REF_STATUS_OK;
-               else {
+               else
                        hint->status = REF_STATUS_REMOTE_REJECT;
-                       ret = -1;
-               }
                hint->remote_status = xstrdup_or_null(msg);
                /* start our next search from the next ref */
                hint = hint->next;
@@ -322,29 +320,6 @@ free_return:
        return update_seen;
 }
 
-
-static int atomic_push_failure(struct send_pack_args *args,
-                              struct ref *remote_refs,
-                              struct ref *failing_ref)
-{
-       struct ref *ref;
-       /* Mark other refs as failed */
-       for (ref = remote_refs; ref; ref = ref->next) {
-               if (!ref->peer_ref && !args->send_mirror)
-                       continue;
-
-               switch (ref->status) {
-               case REF_STATUS_EXPECTING_REPORT:
-                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
-                       continue;
-               default:
-                       break; /* do nothing */
-               }
-       }
-       return error("atomic push failed for ref %s. status: %d\n",
-                    failing_ref->name, failing_ref->status);
-}
-
 #define NONCE_LEN_LIMIT 256
 
 static void reject_invalid_nonce(const char *nonce, int len)
@@ -489,7 +464,10 @@ int send_pack(struct send_pack_args *args,
                        if (use_atomic) {
                                strbuf_release(&req_buf);
                                strbuf_release(&cap_buf);
-                               return atomic_push_failure(args, remote_refs, ref);
+                               reject_atomic_push(remote_refs, args->send_mirror);
+                               error("atomic push failed for ref %s. status: %d\n",
+                                     ref->name, ref->status);
+                               return args->porcelain ? 0 : -1;
                        }
                        /* else fallthrough */
                default:
index f30bb73c703a2b872dec85de7c68f6aa6c0b075d..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"
 
@@ -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;
@@ -2504,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 {
@@ -2515,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"))
@@ -2544,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'))) {
@@ -2557,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()))
@@ -3677,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;
@@ -3706,21 +3751,57 @@ static int apply_autostash(struct replay_opts *opts)
                argv_array_push(&store.args, "-m");
                argv_array_push(&store.args, "autostash");
                argv_array_push(&store.args, "-q");
-               argv_array_push(&store.args, stash_sha1.buf);
+               argv_array_push(&store.args, stash_oid);
                if (run_command(&store))
-                       ret = error(_("cannot store %s"), stash_sha1.buf);
+                       ret = error(_("cannot store %s"), stash_oid);
                else
                        fprintf(stderr,
-                               _("Applying autostash resulted in conflicts.\n"
+                               _("%s\n"
                                  "Your changes are safe in the stash.\n"
                                  "You can run \"git stash pop\" or"
-                                 " \"git stash drop\" at any time.\n"));
+                                 " \"git stash drop\" at any time.\n"),
+                               attempt_apply ?
+                               _("Applying autostash resulted in conflicts.") :
+                               _("Autostash exists; creating a new stash entry."));
        }
 
-       strbuf_release(&stash_sha1);
        return ret;
 }
 
+static int apply_save_autostash(const char *path, int attempt_apply)
+{
+       struct strbuf stash_oid = STRBUF_INIT;
+       int ret = 0;
+
+       if (!read_oneliner(&stash_oid, path,
+                          READ_ONELINER_SKIP_IF_EMPTY)) {
+               strbuf_release(&stash_oid);
+               return 0;
+       }
+       strbuf_trim(&stash_oid);
+
+       ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
+
+       unlink(path);
+       strbuf_release(&stash_oid);
+       return ret;
+}
+
+int save_autostash(const char *path)
+{
+       return apply_save_autostash(path, 0);
+}
+
+int apply_autostash(const char *path)
+{
+       return apply_save_autostash(path, 1);
+}
+
+int apply_autostash_oid(const char *stash_oid)
+{
+       return apply_save_autostash_oid(stash_oid, 1);
+}
+
 static const char *reflog_message(struct replay_opts *opts,
        const char *sub_action, const char *fmt, ...)
 {
@@ -3776,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"));
        }
@@ -4095,7 +4176,7 @@ cleanup_head_ref:
                                run_command(&hook);
                        }
                }
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
 
                if (!opts->quiet) {
                        if (!opts->verbose)
@@ -4313,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);
@@ -5118,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"));
@@ -5129,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);
 
index 9611605711fd6f41bbf9b561ac05768e00f2f24e..d31c41f018cb398e3da8e412f99925a8b30bde22 100644 (file)
@@ -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);
index 69268517242802283ce28a24fcbc49be2b8bdfd9..ccd34dd9e8ce9c498facd694162c30656bfe3195 100644 (file)
@@ -881,9 +881,7 @@ void prepare_alt_odb(struct repository *r)
 /* Returns 1 if we have successfully freshened the file, 0 otherwise. */
 static int freshen_file(const char *fn)
 {
-       struct utimbuf t;
-       t.actime = t.modtime = time(NULL);
-       return !utime(fn, &t);
+       return !utime(fn, NULL);
 }
 
 /*
index 14f7fa6e27ebd22a15be35edaf867f619a054703..321a27670fcc9197e4254ac2e2df7bbd698c632c 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -40,13 +40,6 @@ int register_shallow(struct repository *r, const struct object_id *oid)
 
 int is_repository_shallow(struct repository *r)
 {
-       /*
-        * NEEDSWORK: This function updates
-        * r->parsed_objects->{is_shallow,shallow_stat} as a side effect but
-        * there is no corresponding function to clear them when the shallow
-        * file is updated.
-        */
-
        FILE *fp;
        char buf[1024];
        const char *path = r->parsed_objects->alternate_shallow_file;
@@ -79,6 +72,25 @@ int is_repository_shallow(struct repository *r)
        return r->parsed_objects->is_shallow;
 }
 
+static void reset_repository_shallow(struct repository *r)
+{
+       r->parsed_objects->is_shallow = -1;
+       stat_validity_clear(r->parsed_objects->shallow_stat);
+}
+
+int commit_shallow_file(struct repository *r, struct lock_file *lk)
+{
+       int res = commit_lock_file(lk);
+       reset_repository_shallow(r);
+       return res;
+}
+
+void rollback_shallow_file(struct repository *r, struct lock_file *lk)
+{
+       rollback_lock_file(lk);
+       reset_repository_shallow(r);
+}
+
 /*
  * TODO: use "int" elemtype instead of "int *" when/if commit-slab
  * supports a "valid" flag.
@@ -410,10 +422,10 @@ void prune_shallow(unsigned options)
                if (write_in_full(fd, sb.buf, sb.len) < 0)
                        die_errno("failed to write to %s",
                                  get_lock_file_path(&shallow_lock));
-               commit_lock_file(&shallow_lock);
+               commit_shallow_file(the_repository, &shallow_lock);
        } else {
                unlink(git_path_shallow(the_repository));
-               rollback_lock_file(&shallow_lock);
+               rollback_shallow_file(the_repository, &shallow_lock);
        }
        strbuf_release(&sb);
 }
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 d12efcd3a42e604e5c3aa09c9df2e92740ac2142..cf863837ab92992afb5ce3b562f9045bfb648c28 100644 (file)
--- a/t/README
+++ b/t/README
@@ -379,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.
@@ -547,6 +552,41 @@ Here are the "do's:"
    reports "ok" or "not ok" to the end user running the tests. Under
    --verbose, they are shown to help debug the tests.
 
+ - Be careful when you loop
+
+   You may need to verify multiple things in a loop, but the
+   following does not work correctly:
+
+       test_expect_success 'test three things' '
+           for i in one two three
+           do
+               test_something "$i"
+           done &&
+           test_something_else
+       '
+
+   Because the status of the loop itself is the exit status of the
+   test_something in the last round, the loop does not fail when
+   "test_something" for "one" or "two" fails.  This is not what you
+   want.
+
+   Instead, you can break out of the loop immediately when you see a
+   failure.  Because all test_expect_* snippets are executed inside
+   a function, "return 1" can be used to fail the test immediately
+   upon a failure:
+
+       test_expect_success 'test three things' '
+           for i in one two three
+           do
+               test_something "$i" || return 1
+           done &&
+           test_something_else
+       '
+
+   Note that we still &&-chain the loop to propagate failures from
+   earlier commands.
+
+
 And here are the "don'ts:"
 
  - Don't exit() within a <script> part.
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
new file mode 100644 (file)
index 0000000..77eb27a
--- /dev/null
@@ -0,0 +1,93 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "test-tool.h"
+#include "commit.h"
+
+struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+
+static void add_string_to_filter(const char *data, struct bloom_filter *filter) {
+               struct bloom_key key;
+               int i;
+
+               fill_bloom_key(data, strlen(data), &key, &settings);
+               printf("Hashes:");
+               for (i = 0; i < settings.num_hashes; i++){
+                       printf("0x%08x|", key.hashes[i]);
+               }
+               printf("\n");
+               add_key_to_filter(&key, filter, &settings);
+}
+
+static void print_bloom_filter(struct bloom_filter *filter) {
+       int i;
+
+       if (!filter) {
+               printf("No filter.\n");
+               return;
+       }
+       printf("Filter_Length:%d\n", (int)filter->len);
+       printf("Filter_Data:");
+       for (i = 0; i < filter->len; i++) {
+               printf("%02x|", filter->data[i]);
+       }
+       printf("\n");
+}
+
+static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
+{
+       struct commit *c;
+       struct bloom_filter *filter;
+       setup_git_directory();
+       c = lookup_commit(the_repository, commit_oid);
+       filter = get_bloom_filter(the_repository, c, 1);
+       print_bloom_filter(filter);
+}
+
+static const char *bloom_usage = "\n"
+"  test-tool bloom get_murmer3 <string>\n"
+"  test-tool bloom generate_filter <string> [<string>...]\n"
+"  test-tool get_filter_for_commit <commit-hex>\n";
+
+int cmd__bloom(int argc, const char **argv)
+{
+       if (argc < 2)
+               usage(bloom_usage);
+
+       if (!strcmp(argv[1], "get_murmur3")) {
+               uint32_t hashed;
+               if (argc < 3)
+                       usage(bloom_usage);
+               hashed = murmur3_seeded(0, argv[2], strlen(argv[2]));
+               printf("Murmur3 Hash with seed=0:0x%08x\n", hashed);
+       }
+
+       if (!strcmp(argv[1], "generate_filter")) {
+               struct bloom_filter filter;
+               int i = 2;
+               filter.len =  (settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+               filter.data = xcalloc(filter.len, sizeof(unsigned char));
+
+               if (argc - 1 < i)
+                       usage(bloom_usage);
+
+               while (argv[i]) {
+                       add_string_to_filter(argv[i], &filter);
+                       i++;
+               }
+
+               print_bloom_filter(&filter);
+       }
+
+       if (!strcmp(argv[1], "get_filter_for_commit")) {
+               struct object_id oid;
+               const char *end;
+               if (argc < 3)
+                       usage(bloom_usage);
+               if (parse_oid_hex(argv[2], &oid, &end))
+                       die("cannot parse oid '%s'", argv[2]);
+               init_bloom_filters();
+               get_bloom_filter_for_commit(&oid);
+       }
+
+       return 0;
+}
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 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 2ece4d1ebfbbd40fb2b868bacc4dad05733c59ff..590b2efca705c57a6f9df1df42163588429a918a 100644 (file)
@@ -15,6 +15,7 @@ struct test_cmd {
 
 static struct test_cmd cmds[] = {
        { "advise", cmd__advise_if_enabled },
+       { "bloom", cmd__bloom },
        { "chmtime", cmd__chmtime },
        { "config", cmd__config },
        { "ctype", cmd__ctype },
index 1cbaec02f305fe04b6f647c589d67dfe35f5afe7..ddc8e990e918232093b4958e1c94f44436142053 100644 (file)
@@ -5,6 +5,7 @@
 #include "git-compat-util.h"
 
 int cmd__advise_if_enabled(int argc, const char **argv);
+int cmd__bloom(int argc, const char **argv);
 int cmd__chmtime(int argc, const char **argv);
 int cmd__config(int argc, const char **argv);
 int cmd__ctype(int argc, const char **argv);
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 b85972162003847481f73c280e074e5bcd2c0e91..f58f3deaa8372480980410e4ee8d3d19f94b9e46 100755 (executable)
@@ -98,6 +98,7 @@ _run_sub_test_lib_test_common () {
                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
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 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
 
diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh
new file mode 100755 (executable)
index 0000000..2e73658
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+test_description='git bugreport'
+
+. ./test-lib.sh
+
+# Headers "[System Info]" will be followed by a non-empty line if we put some
+# information there; we can make sure all our headers were followed by some
+# information to check if the command was successful.
+HEADER_PATTERN="^\[.*\]$"
+
+check_all_headers_populated () {
+       while read -r line
+       do
+               if test "$(grep "$HEADER_PATTERN" "$line")"
+               then
+                       echo "$line"
+                       read -r nextline
+                       if test -z "$nextline"; then
+                               return 1;
+                       fi
+               fi
+       done
+}
+
+test_expect_success 'creates a report with content in the right places' '
+       test_when_finished rm git-bugreport-check-headers.txt &&
+       git bugreport -s check-headers &&
+       check_all_headers_populated <git-bugreport-check-headers.txt
+'
+
+test_expect_success 'dies if file with same name as report already exists' '
+       test_when_finished rm git-bugreport-duplicate.txt &&
+       >>git-bugreport-duplicate.txt &&
+       test_must_fail git bugreport --suffix duplicate
+'
+
+test_expect_success '--output-directory puts the report in the provided dir' '
+       test_when_finished rm -fr foo/ &&
+       git bugreport -o foo/ &&
+       test_path_is_file foo/git-bugreport-*
+'
+
+test_expect_success 'incorrect arguments abort with usage' '
+       test_must_fail git bugreport --false 2>output &&
+       test_i18ngrep usage output &&
+       test_path_is_missing git-bugreport-*
+'
+
+test_expect_success 'runs outside of a git dir' '
+       test_when_finished rm non-repo/git-bugreport-* &&
+       nongit git bugreport
+'
+
+test_expect_success 'can create leading directories outside of a git dir' '
+       test_when_finished rm -fr foo/bar/baz &&
+       nongit git bugreport -o foo/bar/baz
+'
+
+
+test_done
diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh
new file mode 100755 (executable)
index 0000000..8f9eef1
--- /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
\ No newline at end of file
index 48484cbcf6eda7e728515c9f217be4d5515fa8e4..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
@@ -609,4 +654,42 @@ test_expect_success 'url parser not confused by encoded markers' '
                "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..63223e13bd1bd20d2539a3e577a84c47416b5ed5 100755 (executable)
@@ -233,18 +233,19 @@ test_expect_success 'read-tree --reset removes outside worktree' '
        test_must_be_empty result
 '
 
-test_expect_success 'print errors when failed to update worktree' '
+test_expect_success 'print warnings when some worktree updates disabled' '
        echo sub >.git/info/sparse-checkout &&
        git checkout -f init &&
        mkdir sub &&
        touch sub/added sub/addedtoo &&
-       test_must_fail git checkout top 2>actual &&
+       # Use -q to suppress "Previous HEAD position" and "Head is now at" msgs
+       git checkout -q top 2>actual &&
        cat >expected <<\EOF &&
-error: The following untracked working tree files would be overwritten by checkout:
+warning: The following paths were already present and thus not updated despite sparse patterns:
        sub/added
        sub/addedtoo
-Please move or remove them before you switch branches.
-Aborting
+
+After fixing the above paths, you may want to run `git sparse-checkout reapply`.
 EOF
        test_i18ncmp expected actual
 '
index 44a91205d600e85c575a0debacdd5e2ff086b5f0..dee99eeec305ea77b9d6ad9cdd904409e157da85 100755 (executable)
@@ -277,15 +277,23 @@ test_expect_success 'cone mode: add parent path' '
        check_files repo a deep folder1
 '
 
-test_expect_success 'revert to old sparse-checkout on bad update' '
+test_expect_success 'not-up-to-date does not block rest of sparsification' '
+       test_when_finished git -C repo sparse-checkout disable &&
        test_when_finished git -C repo reset --hard &&
        git -C repo sparse-checkout set deep &&
+
        echo update >repo/deep/deeper2/a &&
        cp repo/.git/info/sparse-checkout expect &&
-       test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
-       test_i18ngrep "cannot set sparse-checkout patterns" err &&
-       test_cmp repo/.git/info/sparse-checkout expect &&
-       check_files repo/deep a deeper1 deeper2
+       test_write_lines "!/deep/*/" "/deep/deeper1/" >>expect &&
+
+       git -C repo sparse-checkout set deep/deeper1 2>err &&
+
+       test_i18ngrep "The following paths are not up to date" err &&
+       test_cmp expect repo/.git/info/sparse-checkout &&
+       check_files repo/deep a deeper1 deeper2 &&
+       check_files repo/deep/deeper1 a deepest &&
+       check_files repo/deep/deeper1/deepest a &&
+       check_files repo/deep/deeper2 a
 '
 
 test_expect_success 'revert to old sparse-checkout on empty update' '
@@ -315,19 +323,96 @@ test_expect_success '.gitignore should not warn about cone mode' '
        test_i18ngrep ! "disabling cone patterns" err
 '
 
-test_expect_success 'sparse-checkout (init|set|disable) fails with dirty status' '
+test_expect_success 'sparse-checkout (init|set|disable) warns with dirty status' '
        git clone repo dirty &&
        echo dirty >dirty/folder1/a &&
-       test_must_fail git -C dirty sparse-checkout init &&
-       test_must_fail git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
-       test_must_fail git -C dirty sparse-checkout disable &&
+
+       git -C dirty sparse-checkout init 2>err &&
+       test_i18ngrep "warning.*The following paths are not up to date" err &&
+
+       git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+       test_i18ngrep "warning.*The following paths are not up to date" err &&
+       test_path_is_file dirty/folder1/a &&
+
+       git -C dirty sparse-checkout disable 2>err &&
+       test_must_be_empty err &&
+
        git -C dirty reset --hard &&
        git -C dirty sparse-checkout init &&
        git -C dirty sparse-checkout set /folder2/* /deep/deeper1/* &&
-       git -C dirty sparse-checkout disable
+       test_path_is_missing dirty/folder1/a &&
+       git -C dirty sparse-checkout disable &&
+       test_path_is_file dirty/folder1/a
+'
+
+test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged status' '
+       git clone repo unmerged &&
+
+       cat >input <<-EOF &&
+       0 0000000000000000000000000000000000000000      folder1/a
+       100644 $(git -C unmerged rev-parse HEAD:folder1/a) 1    folder1/a
+       EOF
+       git -C unmerged update-index --index-info <input &&
+
+       git -C unmerged sparse-checkout init 2>err &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+       git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* 2>err &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+       test_path_is_file dirty/folder1/a &&
+
+       git -C unmerged sparse-checkout disable 2>err &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+       git -C unmerged reset --hard &&
+       git -C unmerged sparse-checkout init &&
+       git -C unmerged sparse-checkout set /folder2/* /deep/deeper1/* &&
+       git -C unmerged sparse-checkout disable
+'
+
+test_expect_success 'sparse-checkout reapply' '
+       git clone repo tweak &&
+
+       echo dirty >tweak/deep/deeper2/a &&
+
+       cat >input <<-EOF &&
+       0 0000000000000000000000000000000000000000      folder1/a
+       100644 $(git -C tweak rev-parse HEAD:folder1/a) 1       folder1/a
+       EOF
+       git -C tweak update-index --index-info <input &&
+
+       git -C tweak sparse-checkout init --cone 2>err &&
+       test_i18ngrep "warning.*The following paths are not up to date" err &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+       git -C tweak sparse-checkout set folder2 deep/deeper1 2>err &&
+       test_i18ngrep "warning.*The following paths are not up to date" err &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+
+       git -C tweak sparse-checkout reapply 2>err &&
+       test_i18ngrep "warning.*The following paths are not up to date" err &&
+       test_path_is_file tweak/deep/deeper2/a &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+       test_path_is_file tweak/folder1/a &&
+
+       git -C tweak checkout HEAD deep/deeper2/a &&
+       git -C tweak sparse-checkout reapply 2>err &&
+       test_i18ngrep ! "warning.*The following paths are not up to date" err &&
+       test_path_is_missing tweak/deep/deeper2/a &&
+       test_i18ngrep "warning.*The following paths are unmerged" err &&
+       test_path_is_file tweak/folder1/a &&
+
+       git -C tweak add folder1/a &&
+       git -C tweak sparse-checkout reapply 2>err &&
+       test_must_be_empty err &&
+       test_path_is_missing tweak/deep/deeper2/a &&
+       test_path_is_missing tweak/folder1/a &&
+
+       git -C tweak sparse-checkout disable
 '
 
 test_expect_success 'cone mode: set with core.ignoreCase=true' '
+       rm repo/.git/info/sparse-checkout &&
        git -C repo sparse-checkout init --cone &&
        git -C repo -c core.ignoreCase=true sparse-checkout set folder1 &&
        cat >expect <<-\EOF &&
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 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 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 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 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 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 5eeb739f3ed4bb44a8951fb223445ead15dc61c8..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
 '
 
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
new file mode 100755 (executable)
index 0000000..c7011f3
--- /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
\ No newline at end of file
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 22240fd30b482f201daa69ada8ffcb6248a24638..030a7222b2aca5e97652194ea9a0d8cdf4ca9ab0 100755 (executable)
@@ -526,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 &&
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 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 b57209c84fcad43ce728dd8bfa24674caa64c37c..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' '
@@ -133,14 +110,10 @@ test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
        cd notshallow &&
        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 8f0d81a27e7fd945c52715fefe8541555e4a5e35..88002b24afc65f4fcab2ee059a208bf1b141a4d8 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)
 '
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
index 821a0c88cf0221c3662693afaadf9280dab0be8d..131314256468c75f6bb7585fe26adf75b1c988a6 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
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 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 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 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 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 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 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 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 d9ef356a16a288aef01683be15ba003e780473f5..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.
index 0bb1105ec3791eb6ccf254c34e4e25b8eb0aecda..1b221951a8e78da99dbec41047a6d9274d686f76 100644 (file)
@@ -675,6 +675,18 @@ die () {
        fi
 }
 
+file_lineno () {
+       test -z "$GIT_TEST_FRAMEWORK_SELFTEST" && test -n "$BASH" || return 0
+       local i
+       for i in ${!BASH_SOURCE[*]}
+       do
+               case $i,"${BASH_SOURCE[$i]##*/}" in
+               0,t[0-9]*.sh) echo "t/${BASH_SOURCE[$i]}:$LINENO: ${1+$1: }"; return;;
+               *,t[0-9]*.sh) echo "t/${BASH_SOURCE[$i]}:${BASH_LINENO[$(($i-1))]}: ${1+$1: }"; return;;
+               esac
+       done
+}
+
 GIT_EXIT_OK=
 trap 'die' EXIT
 # Disable '-x' tracing, because with some shells, notably dash, it
@@ -720,7 +732,7 @@ test_failure_ () {
                write_junit_xml_testcase "$1" "      $junit_insert"
        fi
        test_failure=$(($test_failure + 1))
-       say_color error "not ok $test_count - $1"
+       say_color error "$(file_lineno error)not ok $test_count - $1"
        shift
        printf '%s\n' "$*" | sed -e 's/^/#      /'
        test "$immediate" = "" || { finalize_junit_xml; GIT_EXIT_OK=t; exit 1; }
@@ -1654,6 +1666,15 @@ test_lazy_prereq ULIMIT_STACK_SIZE '
        run_with_limited_stack true
 '
 
+run_with_limited_open_files () {
+       (ulimit -n 32 && "$@")
+}
+
+test_lazy_prereq ULIMIT_FILE_DESCRIPTORS '
+       test_have_prereq !MINGW,!CYGWIN &&
+       run_with_limited_open_files true
+'
+
 build_option () {
        git version --build-options |
        sed -ne "s/^$1: //p"
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 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 471c5bd339c07e83701026968d46b79faa39e2cd..15f5ba4e8f22c69959357bc8a7fe073678fe8862 100644 (file)
@@ -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 f618a644efa0f942619d0ed71c8ff4d68f11a4de..6bbf58d28eaefb5428f0198e8da897f08bcec94c 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;
 
@@ -423,9 +456,8 @@ static int check_updates(struct unpack_trees_options *o)
                                continue;
                        oid_array_append(&to_fetch, &ce->oid);
                }
-               if (to_fetch.nr)
-                       promisor_remote_get_direct(the_repository,
-                                                  to_fetch.oid, to_fetch.nr);
+               promisor_remote_get_direct(the_repository,
+                                          to_fetch.oid, to_fetch.nr);
                oid_array_clear(&to_fetch);
        }
        for (i = 0; i < index->cache_nr; i++) {
@@ -505,19 +537,39 @@ static int apply_sparse_checkout(struct index_state *istate,
                 * also stat info may have lost after merged_entry() so calling
                 * verify_uptodate() again may fail
                 */
-               if (!(ce->ce_flags & CE_UPDATE) && verify_uptodate_sparse(ce, o))
+               if (!(ce->ce_flags & CE_UPDATE) &&
+                   verify_uptodate_sparse(ce, o)) {
+                       ce->ce_flags &= ~CE_SKIP_WORKTREE;
                        return -1;
+               }
                ce->ce_flags |= CE_WT_REMOVE;
                ce->ce_flags &= ~CE_UPDATE;
        }
        if (was_skip_worktree && !ce_skip_worktree(ce)) {
-               if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
+               if (verify_absent_sparse(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
                        return -1;
                ce->ce_flags |= CE_UPDATE;
        }
        return 0;
 }
 
+static int warn_conflicted_path(struct index_state *istate,
+                               int i,
+                               struct unpack_trees_options *o)
+{
+       char *conflicting_path = istate->cache[i]->name;
+       int count = 0;
+
+       add_rejected_path(o, WARNING_SPARSE_UNMERGED_FILE, conflicting_path);
+
+       /* Find out how many higher stage entries at same path */
+       while (++count < istate->cache_nr &&
+              !strcmp(conflicting_path,
+                      istate->cache[i+count]->name))
+               /* do nothing */;
+       return count;
+}
+
 static inline int call_unpack_fn(const struct cache_entry * const *src,
                                 struct unpack_trees_options *o)
 {
@@ -1494,6 +1546,20 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
        clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress);
 }
 
+static void populate_from_existing_patterns(struct unpack_trees_options *o,
+                                           struct pattern_list *pl)
+{
+       char *sparse = git_pathdup("info/sparse-checkout");
+
+       pl->use_cone_patterns = core_sparse_checkout_cone;
+       if (add_patterns_from_file_to_list(sparse, "", 0, pl, NULL) < 0)
+               o->skip_sparse_checkout = 1;
+       else
+               o->pl = pl;
+       free(sparse);
+}
+
+
 static int verify_absent(const struct cache_entry *,
                         enum unpack_trees_error_types,
                         struct unpack_trees_options *);
@@ -1508,22 +1574,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        int i, ret;
        static struct cache_entry *dfc;
        struct pattern_list pl;
+       int free_pattern_list = 0;
 
        if (len > MAX_UNPACK_TREES)
                die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
 
        trace_performance_enter();
-       memset(&pl, 0, sizeof(pl));
        if (!core_apply_sparse_checkout || !o->update)
                o->skip_sparse_checkout = 1;
        if (!o->skip_sparse_checkout && !o->pl) {
-               char *sparse = git_pathdup("info/sparse-checkout");
-               pl.use_cone_patterns = core_sparse_checkout_cone;
-               if (add_patterns_from_file_to_list(sparse, "", 0, &pl, NULL) < 0)
-                       o->skip_sparse_checkout = 1;
-               else
-                       o->pl = &pl;
-               free(sparse);
+               memset(&pl, 0, sizeof(pl));
+               free_pattern_list = 1;
+               populate_from_existing_patterns(o, &pl);
        }
 
        memset(&o->result, 0, sizeof(o->result));
@@ -1619,7 +1681,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 
                /*
                 * Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1
-                * If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
+                * If they will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE
                 * so apply_sparse_checkout() won't attempt to remove it from worktree
                 */
                mark_new_skip_worktree(o->pl, &o->result,
@@ -1639,23 +1701,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                         * correct CE_NEW_SKIP_WORKTREE
                         */
                        if (ce->ce_flags & CE_ADDED &&
-                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
-                               if (!o->show_all_errors)
-                                       goto return_failed;
-                               ret = -1;
-                       }
+                           verify_absent(ce, WARNING_SPARSE_ORPHANED_NOT_OVERWRITTEN, o))
+                               ret = 1;
+
+                       if (apply_sparse_checkout(&o->result, ce, o))
+                               ret = 1;
 
-                       if (apply_sparse_checkout(&o->result, ce, o)) {
-                               if (!o->show_all_errors)
-                                       goto return_failed;
-                               ret = -1;
-                       }
                        if (!ce_skip_worktree(ce))
                                empty_worktree = 0;
-
                }
-               if (ret < 0)
-                       goto return_failed;
                /*
                 * Sparse checkout is meant to narrow down checkout area
                 * but it does not make sense to narrow down to empty working
@@ -1666,9 +1720,18 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                        ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
                        goto done;
                }
+               if (ret == 1) {
+                       /*
+                        * Inability to sparsify or de-sparsify individual
+                        * paths is not an error, but just a warning.
+                        */
+                       if (o->show_all_errors)
+                               display_warning_msgs(o);
+                       ret = 0;
+               }
        }
 
-       ret = check_updates(o) ? (-2) : 0;
+       ret = check_updates(o, &o->result) ? (-2) : 0;
        if (o->dst_index) {
                move_index_extensions(&o->result, o->src_index);
                if (!ret) {
@@ -1691,9 +1754,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        o->src_index = NULL;
 
 done:
-       trace_performance_leave("unpack_trees");
-       if (!o->keep_pattern_list)
+       if (free_pattern_list)
                clear_pattern_list(&pl);
+       trace_performance_leave("unpack_trees");
        return ret;
 
 return_failed:
@@ -1706,6 +1769,91 @@ return_failed:
        goto done;
 }
 
+/*
+ * Update SKIP_WORKTREE bits according to sparsity patterns, and update
+ * working directory to match.
+ *
+ * CE_NEW_SKIP_WORKTREE is used internally.
+ */
+enum update_sparsity_result update_sparsity(struct unpack_trees_options *o)
+{
+       enum update_sparsity_result ret = UPDATE_SPARSITY_SUCCESS;
+       struct pattern_list pl;
+       int i, empty_worktree;
+       unsigned old_show_all_errors;
+       int free_pattern_list = 0;
+
+       old_show_all_errors = o->show_all_errors;
+       o->show_all_errors = 1;
+
+       /* Sanity checks */
+       if (!o->update || o->index_only || o->skip_sparse_checkout)
+               BUG("update_sparsity() is for reflecting sparsity patterns in working directory");
+       if (o->src_index != o->dst_index || o->fn)
+               BUG("update_sparsity() called wrong");
+
+       trace_performance_enter();
+
+       /* If we weren't given patterns, use the recorded ones */
+       if (!o->pl) {
+               memset(&pl, 0, sizeof(pl));
+               free_pattern_list = 1;
+               populate_from_existing_patterns(o, &pl);
+               if (o->skip_sparse_checkout)
+                       goto skip_sparse_checkout;
+       }
+
+       /* Set NEW_SKIP_WORKTREE on existing entries. */
+       mark_all_ce_unused(o->src_index);
+       mark_new_skip_worktree(o->pl, o->src_index, 0,
+                              CE_NEW_SKIP_WORKTREE, o->verbose_update);
+
+       /* Then loop over entries and update/remove as needed */
+       ret = UPDATE_SPARSITY_SUCCESS;
+       empty_worktree = 1;
+       for (i = 0; i < o->src_index->cache_nr; i++) {
+               struct cache_entry *ce = o->src_index->cache[i];
+
+
+               if (ce_stage(ce)) {
+                       /* -1 because for loop will increment by 1 */
+                       i += warn_conflicted_path(o->src_index, i, o) - 1;
+                       ret = UPDATE_SPARSITY_WARNINGS;
+                       continue;
+               }
+
+               if (apply_sparse_checkout(o->src_index, ce, o))
+                       ret = UPDATE_SPARSITY_WARNINGS;
+
+               if (!ce_skip_worktree(ce))
+                       empty_worktree = 0;
+       }
+
+       /*
+        * Sparse checkout is meant to narrow down checkout area
+        * but it does not make sense to narrow down to empty working
+        * tree. This is usually a mistake in sparse checkout rules.
+        * Do not allow users to do that.
+        */
+       if (o->src_index->cache_nr && empty_worktree) {
+               unpack_failed(o, "Sparse checkout leaves no entry on working directory");
+               ret = UPDATE_SPARSITY_INDEX_UPDATE_FAILURES;
+               goto done;
+       }
+
+skip_sparse_checkout:
+       if (check_updates(o, o->src_index))
+               ret = UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES;
+
+done:
+       display_warning_msgs(o);
+       o->show_all_errors = old_show_all_errors;
+       if (free_pattern_list)
+               clear_pattern_list(&pl);
+       trace_performance_leave("update_sparsity");
+       return ret;
+}
+
 /* Here come the merge functions */
 
 static int reject_merge(const struct cache_entry *ce,
@@ -1790,7 +1938,7 @@ int verify_uptodate(const struct cache_entry *ce,
 static int verify_uptodate_sparse(const struct cache_entry *ce,
                                  struct unpack_trees_options *o)
 {
-       return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
+       return verify_uptodate_1(ce, o, WARNING_SPARSE_NOT_UPTODATE_FILE);
 }
 
 /*
@@ -2028,11 +2176,7 @@ static int verify_absent_sparse(const struct cache_entry *ce,
                                enum unpack_trees_error_types error_type,
                                struct unpack_trees_options *o)
 {
-       enum unpack_trees_error_types orphaned_error = error_type;
-       if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
-               orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
-
-       return verify_absent_1(ce, orphaned_error, o);
+       return verify_absent_1(ce, error_type, o);
 }
 
 static int merged_entry(const struct cache_entry *ce,
index ad41b45a7139aacad7ded1ddcf6774d5b031e7a7..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;
@@ -91,6 +94,15 @@ struct unpack_trees_options {
 int unpack_trees(unsigned n, struct tree_desc *t,
                 struct unpack_trees_options *options);
 
+enum update_sparsity_result {
+       UPDATE_SPARSITY_SUCCESS = 0,
+       UPDATE_SPARSITY_WARNINGS = 1,
+       UPDATE_SPARSITY_INDEX_UPDATE_FAILURES = -1,
+       UPDATE_SPARSITY_WORKTREE_UPDATE_FAILURES = -2
+};
+
+enum update_sparsity_result update_sparsity(struct unpack_trees_options *options);
+
 int verify_uptodate(const struct cache_entry *ce,
                    struct unpack_trees_options *o);
 
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 069a8284cbb09aa5c141cb4ed87332ec81cbcecd..1df884ef0bbfbba5a751eafc1922369ad818fce5 100644 (file)
@@ -225,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++) {
@@ -269,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 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);
        }