]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'da/mergetools-special-case-xxdiff-exit-128'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Oct 2021 23:06:58 +0000 (16:06 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Oct 2021 23:06:58 +0000 (16:06 -0700)
The xxdiff difftool backend can exit with status 128, which the
difftool-helper that launches the backend takes as a significant
failure, when it is not significant at all.  Work it around.

* da/mergetools-special-case-xxdiff-exit-128:
  mergetools/xxdiff: prevent segfaults from stopping difftool

441 files changed:
.github/workflows/l10n.yml [new file with mode: 0644]
.github/workflows/main.yml
.gitignore
Documentation/Makefile
Documentation/MyFirstContribution.txt
Documentation/RelNotes/2.34.0.txt [new file with mode: 0644]
Documentation/blame-options.txt
Documentation/config.txt
Documentation/config/advice.txt
Documentation/config/branch.txt
Documentation/config/color.txt
Documentation/config/gpg.txt
Documentation/config/help.txt
Documentation/config/pack.txt
Documentation/config/pull.txt
Documentation/config/user.txt
Documentation/diff-format.txt
Documentation/git-add.txt
Documentation/git-blame.txt
Documentation/git-bundle.txt
Documentation/git-cat-file.txt
Documentation/git-checkout.txt
Documentation/git-for-each-ref.txt
Documentation/git-help.txt
Documentation/git-http-backend.txt
Documentation/git-index-pack.txt
Documentation/git-maintenance.txt
Documentation/git-multi-pack-index.txt
Documentation/git-pull.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-repack.txt
Documentation/git-reset.txt
Documentation/git-rm.txt
Documentation/git-send-pack.txt
Documentation/git-sparse-checkout.txt
Documentation/git-status.txt
Documentation/git-svn.txt
Documentation/git-upload-pack.txt
Documentation/git.txt
Documentation/gitfaq.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/rev-list-options.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/api-trace2.txt
Documentation/technical/bitmap-format.txt
Documentation/technical/http-protocol.txt
Documentation/technical/multi-pack-index.txt
Documentation/technical/protocol-v2.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
add-interactive.c
advice.c
advice.h
attr.c
bisect.c
branch.c
builtin.h
builtin/add.c
builtin/am.c
builtin/bisect--helper.c
builtin/blame.c
builtin/branch.c
builtin/bugreport.c
builtin/bundle.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit-graph.c
builtin/commit.c
builtin/config.c
builtin/credential-cache.c
builtin/credential-store.c
builtin/difftool.c
builtin/fast-export.c
builtin/fetch.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/grep.c
builtin/help.c
builtin/index-pack.c
builtin/log.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/merge.c
builtin/mktag.c
builtin/multi-pack-index.c
builtin/mv.c
builtin/pack-objects.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/replace.c
builtin/reset.c
builtin/rev-parse.c
builtin/revert.c
builtin/rm.c
builtin/send-pack.c
builtin/show-branch.c
builtin/sparse-checkout.c
builtin/stash.c
builtin/submodule--helper.c
builtin/tag.c
builtin/upload-pack.c
builtin/worktree.c
bundle.c
bundle.h
cache-tree.c
cache.h
cbtree.h
checkout.c
ci/install-dependencies.sh
ci/lib.sh
ci/run-build-and-tests.sh
commit-graph.c
commit-graph.h
commit.c
commit.h
compat/linux/procinfo.c [new file with mode: 0644]
compat/nedmalloc/nedmalloc.c
compat/simple-ipc/ipc-unix-socket.c
compat/simple-ipc/ipc-win32.c
compat/stub/procinfo.c [new file with mode: 0644]
compat/terminal.c
compat/terminal.h
compat/vcbuild/README
compat/win32/lazyload.h
config.c
config.h
config.mak.dev
config.mak.uname
connected.c
connected.h
contrib/buildsystems/CMakeLists.txt
contrib/coccinelle/xopen.cocci
contrib/completion/git-completion.bash
contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
contrib/credential/libsecret/git-credential-libsecret.c
contrib/rerere-train.sh
credential.c
daemon.c
diff.c
diffcore-rename.c
diffcore.h
dir.c
dir.h
editor.c
entry.c
entry.h
environment.c
fetch-negotiator.c
fetch-pack.c
fmt-merge-msg.c
generate-hooklist.sh [new file with mode: 0755]
gettext.h
git-bisect.sh
git-compat-util.h
git-curl-compat.h [new file with mode: 0644]
git-rebase--preserve-merges.sh [deleted file]
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/gitweb.perl
gpg-interface.c
gpg-interface.h
grep.c
grep.h
help.c
help.h
hook.c [new file with mode: 0644]
hook.h [new file with mode: 0644]
http-backend.c
http-push.c
http-walker.c
http.c
http.h
imap-send.c
list-objects.c
list.h
lockfile.h
log-tree.c
log-tree.h
ls-refs.c
ls-refs.h
merge-ort.c
merge-recursive.c
merge.c
mergesort.c
midx.c
midx.h
notes-merge.c
object-file.c
object-name.c
object-store.h
object.c
object.h
oid-array.h
oidset.c
oidset.h
pack-bitmap-write.c
pack-bitmap.c
pack-bitmap.h
pack-check.c
packfile.c
packfile.h
parse-options.c
parse-options.h
path.c
path.h
pathspec.c
pkt-line.c
pkt-line.h
pretty.c
protocol-caps.c
protocol-caps.h
quote.c
quote.h
read-cache.c
rebase-interactive.c
rebase-interactive.h
rebase.c
rebase.h
ref-filter.c
ref-filter.h
reflog-walk.c
refs.c
refs.h
refs/debug.c
refs/files-backend.c
refs/packed-backend.c
refs/packed-backend.h
refs/ref-cache.c
refs/ref-cache.h
refs/refs-internal.h
remote-curl.c
remote.c
repo-settings.c
repository.c
repository.h
reset.c
revision.c
revision.h
run-command.c
run-command.h
send-pack.c
sequencer.c
sequencer.h
serve.c
serve.h
shallow.h
simple-ipc.h
sparse-index.c
sparse-index.h
strbuf.c
strbuf.h
streaming.c
string-list.c
string-list.h
strvec.h
submodule-config.c
submodule-config.h
submodule.c
submodule.h
t/README
t/helper/test-bitmap.c
t/helper/test-dump-untracked-cache.c
t/helper/test-mergesort.c
t/helper/test-parse-options.c
t/helper/test-read-midx.c
t/helper/test-run-command.c
t/helper/test-serve-v2.c
t/helper/test-simple-ipc.c
t/helper/test-submodule-nested-repo-config.c
t/lib-bitmap.sh
t/lib-gpg.sh
t/lib-httpd/apache.conf
t/lib-midx.sh [new file with mode: 0644]
t/lib-subtest.sh [new file with mode: 0644]
t/oid-info/oid
t/perf/aggregate.perl
t/perf/config [new file with mode: 0644]
t/perf/lib-bitmap.sh [new file with mode: 0644]
t/perf/p0071-sort.sh
t/perf/p3400-rebase.sh
t/perf/p5310-pack-bitmaps.sh
t/perf/p5326-multi-pack-bitmaps.sh [new file with mode: 0755]
t/perf/perf-lib.sh
t/perf/run
t/t0000-basic.sh
t/t0004-unwritable.sh
t/t0011-hashmap.sh
t/t0012-help.sh
t/t0016-oidmap.sh
t/t0017-env-helper.sh
t/t0018-advice.sh
t/t0030-stripspace.sh
t/t0040-parse-options.sh
t/t0060-path-utils.sh
t/t0063-string-list.sh
t/t0071-sort.sh [new file with mode: 0755]
t/t0090-cache-tree.sh
t/t0091-bugreport.sh
t/t0210/scrub_normal.perl
t/t0211/scrub_perf.perl
t/t0212/parse_events.perl
t/t0301-credential-cache.sh
t/t0410-partial-clone.sh
t/t1001-read-tree-m-2way.sh
t/t1006-cat-file.sh
t/t1013-read-tree-submodule.sh
t/t1091-sparse-checkout-builtin.sh
t/t1092-sparse-checkout-compatibility.sh
t/t1405-main-ref-store.sh
t/t1410-reflog.sh
t/t1430-bad-ref-name.sh
t/t1450-fsck.sh
t/t1502-rev-parse-parseopt.sh
t/t1503-rev-parse-verify.sh
t/t1600-index.sh
t/t1700-split-index.sh
t/t2021-checkout-overwrite.sh
t/t2200-add-update.sh
t/t2402-worktree-list.sh
t/t2500-untracked-overwriting.sh [new file with mode: 0755]
t/t3203-branch-output.sh
t/t3320-notes-merge-worktrees.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t3408-rebase-multi-line.sh
t/t3409-rebase-preserve-merges.sh [deleted file]
t/t3410-rebase-preserve-dropped-merges.sh [deleted file]
t/t3411-rebase-preserve-around-merges.sh [deleted file]
t/t3412-rebase-root.sh
t/t3414-rebase-preserve-onto.sh [deleted file]
t/t3418-rebase-continue.sh
t/t3421-rebase-topology-linear.sh
t/t3422-rebase-incompatible-options.sh
t/t3425-rebase-topology-merges.sh
t/t3427-rebase-subtree.sh
t/t3435-rebase-gpg-sign.sh
t/t3501-revert-cherry-pick.sh
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t3602-rm-sparse-checkout.sh
t/t3705-add-sparse-checkout.sh
t/t3905-stash-include-untracked.sh
t/t4018/java-class-member-function
t/t4018/java-enum-constant [new file with mode: 0644]
t/t4018/java-method-return-generic-bounded [new file with mode: 0644]
t/t4018/java-method-return-generic-wildcard [new file with mode: 0644]
t/t4018/java-nested-field [new file with mode: 0644]
t/t4018/php-enum [new file with mode: 0644]
t/t4060-diff-submodule-option-diff-format.sh
t/t4202-log.sh
t/t5304-prune.sh
t/t5310-pack-bitmaps.sh
t/t5312-prune-corruption.sh
t/t5318-commit-graph.sh
t/t5319-multi-pack-index.sh
t/t5326-multi-pack-bitmaps.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5526-fetch-submodules.sh
t/t5531-deep-submodule-push.sh
t/t5534-push-signed.sh
t/t5545-push-options.sh
t/t5551-http-fetch-smart.sh
t/t5555-http-smart-common.sh [new file with mode: 0755]
t/t5572-pull-submodule.sh
t/t5600-clone-fail-cleanup.sh
t/t5606-clone-options.sh
t/t5607-clone-bundle.sh
t/t5701-git-serve.sh
t/t6000-rev-list-misc.sh
t/t6001-rev-list-graft.sh
t/t6030-bisect-porcelain.sh
t/t6050-replace.sh
t/t6120-describe.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t6415-merge-dir-to-symlink.sh
t/t6424-merge-unrelated-index-changes.sh
t/t6430-merge-recursive.sh
t/t6436-merge-overwrite.sh
t/t6437-submodule-merge.sh
t/t6500-gc.sh
t/t7002-mv-sparse-checkout.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7030-verify-tag.sh
t/t7031-verify-tag-signed-ssh.sh [new file with mode: 0755]
t/t7064-wtstatus-pv2.sh
t/t7112-reset-submodule.sh
t/t7201-co.sh
t/t7418-submodule-sparse-gitmodules.sh
t/t7505-prepare-commit-msg-hook.sh
t/t7510-signed-commit.sh
t/t7517-per-repo-email.sh
t/t7519-status-fsmonitor.sh
t/t7528-signed-commit-ssh.sh [new file with mode: 0755]
t/t7600-merge.sh
t/t7700-repack.sh
t/t7703-repack-geometric.sh
t/t7800-difftool.sh
t/t7814-grep-recurse-submodules.sh
t/t7900-maintenance.sh
t/test-lib-functions.sh
t/test-lib.sh
trace.h
trace2.c
trace2.h
trace2/tr2_tgt.h
trace2/tr2_tgt_event.c
trace2/tr2_tgt_normal.c
trace2/tr2_tgt_perf.c
trace2/tr2_tls.c
transport-helper.c
transport-internal.h
transport.c
transport.h
unpack-trees.c
unpack-trees.h
upload-pack.c
upload-pack.h
urlmatch.h
userdiff.c
wrapper.c
write-or-die.c
wt-status.c

diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml
new file mode 100644 (file)
index 0000000..27f72f0
--- /dev/null
@@ -0,0 +1,105 @@
+name: git-l10n
+
+on: [push, pull_request_target]
+
+jobs:
+  git-po-helper:
+    if: >-
+      endsWith(github.repository, '/git-po') ||
+      contains(github.head_ref, 'l10n') ||
+      contains(github.ref, 'l10n')
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    steps:
+      - name: Setup base and head objects
+        id: setup-tips
+        run: |
+          if test "${{ github.event_name }}" = "pull_request_target"
+          then
+            base=${{ github.event.pull_request.base.sha }}
+            head=${{ github.event.pull_request.head.sha }}
+          else
+            base=${{ github.event.before }}
+            head=${{ github.event.after }}
+          fi
+          echo "::set-output name=base::$base"
+          echo "::set-output name=head::$head"
+      - name: Run partial clone
+        run: |
+          git -c init.defaultBranch=master init --bare .
+          git remote add \
+            --mirror=fetch \
+            origin \
+            https://github.com/${{ github.repository }}
+          # Fetch tips that may be unreachable from github.ref:
+          # - For a forced push, "$base" may be unreachable.
+          # - For a "pull_request_target" event, "$head" may be unreachable.
+          args=
+          for commit in \
+            ${{ steps.setup-tips.outputs.base }} \
+            ${{ steps.setup-tips.outputs.head }}
+          do
+            case $commit in
+            *[^0]*)
+              args="$args $commit"
+              ;;
+            *)
+              # Should not fetch ZERO-OID.
+              ;;
+            esac
+          done
+          git -c protocol.version=2 fetch \
+            --progress \
+            --no-tags \
+            --no-write-fetch-head \
+            --filter=blob:none \
+            origin \
+            ${{ github.ref }} \
+            $args
+      - uses: actions/setup-go@v2
+        with:
+          go-version: '>=1.16'
+      - name: Install git-po-helper
+        run: go install github.com/git-l10n/git-po-helper@main
+      - name: Install other dependencies
+        run: |
+          sudo apt-get update -q &&
+          sudo apt-get install -q -y gettext
+      - name: Run git-po-helper
+        id: check-commits
+        run: |
+          exit_code=0
+          git-po-helper check-commits \
+            --github-action-event="${{ github.event_name }}" -- \
+            ${{ steps.setup-tips.outputs.base }}..${{ steps.setup-tips.outputs.head }} \
+            >git-po-helper.out 2>&1 || exit_code=$?
+          if test $exit_code -ne 0 || grep -q WARNING git-po-helper.out
+          then
+            # Remove ANSI colors which are proper for console logs but not
+            # proper for PR comment.
+            echo "COMMENT_BODY<<EOF" >>$GITHUB_ENV
+            perl -pe 's/\e\[[0-9;]*m//g; s/\bEOF$//g' git-po-helper.out >>$GITHUB_ENV
+            echo "EOF" >>$GITHUB_ENV
+          fi
+          cat git-po-helper.out
+          exit $exit_code
+      - name: Create comment in pull request for report
+        uses: mshick/add-pr-comment@v1
+        if: >-
+          always() &&
+          github.event_name == 'pull_request_target' &&
+          env.COMMENT_BODY != ''
+        with:
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          repo-token-user-login: 'github-actions[bot]'
+          message: >
+            ${{ steps.check-commits.outcome == 'failure' && 'Errors and warnings' || 'Warnings' }}
+            found by [git-po-helper](https://github.com/git-l10n/git-po-helper#readme) in workflow
+            [#${{ github.run_number }}](${{ env.GITHUB_SERVER_URL }}/${{ github.repository }}/actions/runs/${{ github.run_id }}):
+
+            ```
+
+            ${{ env.COMMENT_BODY }}
+
+            ```
index b053b01c66e93ea065357bf69b6742cfcab318b7..6ed6a9e80761a22b8e0280a0f100164d5147571e 100644 (file)
@@ -88,7 +88,7 @@ jobs:
       env:
         HOME: ${{runner.workspace}}
         NO_PERL: 1
-      run: ci/make-test-artifacts.sh artifacts
+      run: . /etc/profile && ci/make-test-artifacts.sh artifacts
     - name: zip up tracked files
       run: git archive -o artifacts/tracked.tar.gz HEAD
     - name: upload tracked files and build artifacts
@@ -115,7 +115,7 @@ jobs:
     - uses: git-for-windows/setup-git-for-windows-sdk@v1
     - name: test
       shell: bash
-      run: ci/run-test-slice.sh ${{matrix.nr}} 10
+      run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10
     - name: ci/print-test-failures.sh
       if: failure()
       shell: bash
@@ -198,8 +198,7 @@ jobs:
       shell: bash
       env:
         NO_SVN_TESTS: 1
-        GIT_TEST_SKIP_REBASE_P: 1
-      run: ci/run-test-slice.sh ${{matrix.nr}} 10
+      run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10
     - name: ci/print-test-failures.sh
       if: failure()
       shell: bash
@@ -232,6 +231,9 @@ jobs:
           - jobname: linux-gcc-default
             cc: gcc
             pool: ubuntu-latest
+          - jobname: linux-leaks
+            cc: gcc
+            pool: ubuntu-latest
     env:
       CC: ${{matrix.vector.cc}}
       jobname: ${{matrix.vector.jobname}}
index 311841f9bed577151a81f87174ccdd6939aa3eee..054249b20a8d91d77a569294f8104e53bdd12f51 100644 (file)
 /git-range-diff
 /git-read-tree
 /git-rebase
-/git-rebase--preserve-merges
 /git-receive-pack
 /git-reflog
 /git-remote
 /gitweb/static/gitweb.min.*
 /config-list.h
 /command-list.h
+/hook-list.h
 *.tar.gz
 *.dsc
 *.deb
 *.lib
 *.res
 *.sln
+*.sp
 *.suo
 *.ncb
 *.vcproj
index f5605b7767f3c7a82105511f65db35ea4392bf30..2021568cd5a5cf314a3c9f77164c3c94e025aa1e 100644 (file)
@@ -90,6 +90,7 @@ SP_ARTICLES += $(API_DOCS)
 TECH_DOCS += MyFirstContribution
 TECH_DOCS += MyFirstObjectWalk
 TECH_DOCS += SubmittingPatches
+TECH_DOCS += technical/bundle-format
 TECH_DOCS += technical/hash-function-transition
 TECH_DOCS += technical/http-protocol
 TECH_DOCS += technical/index-format
index 015cf24631d97c52c1564a3411b4403d08ccb14c..b20bc8e91449a17e5417f0dd9573b04a9559a810 100644 (file)
@@ -1029,22 +1029,42 @@ kidding - be patient!)
 [[v2-git-send-email]]
 === Sending v2
 
-Skip ahead to <<reviewing,Responding to Reviews>> for information on how to
-handle comments from reviewers. Continue this section when your topic branch is
-shaped the way you want it to look for your patchset v2.
+This section will focus on how to send a v2 of your patchset. To learn what
+should go into v2, skip ahead to <<reviewing,Responding to Reviews>> for
+information on how to handle comments from reviewers.
+
+We'll reuse our `psuh` topic branch for v2. Before we make any changes, we'll
+mark the tip of our v1 branch for easy reference:
 
-When you're ready with the next iteration of your patch, the process is fairly
-similar.
+----
+$ git checkout psuh
+$ git branch psuh-v1
+----
 
-First, generate your v2 patches again:
+Refine your patch series by using `git rebase -i` to adjust commits based upon
+reviewer comments. Once the patch series is ready for submission, generate your
+patches again, but with some new flags:
 
 ----
-$ git format-patch -v2 --cover-letter -o psuh/ master..psuh
+$ git format-patch -v2 --cover-letter -o psuh/ --range-diff master..psuh-v1 master..
 ----
 
-This will add your v2 patches, all named like `v2-000n-my-commit-subject.patch`,
-to the `psuh/` directory. You may notice that they are sitting alongside the v1
-patches; that's fine, but be careful when you are ready to send them.
+The `--range-diff master..psuh-v1` parameter tells `format-patch` to include a
+range-diff between `psuh-v1` and `psuh` in the cover letter (see
+linkgit:git-range-diff[1]). This helps tell reviewers about the differences
+between your v1 and v2 patches.
+
+The `-v2` parameter tells `format-patch` to output your patches
+as version "2". For instance, you may notice that your v2 patches are
+all named like `v2-000n-my-commit-subject.patch`. `-v2` will also format
+your patches by prefixing them with "[PATCH v2]" instead of "[PATCH]",
+and your range-diff will be prefaced with "Range-diff against v1".
+
+Afer you run this command, `format-patch` will output the patches to the `psuh/`
+directory, alongside the v1 patches. Using a single directory makes it easy to
+refer to the old v1 patches while proofreading the v2 patches, but you will need
+to be careful to send out only the v2 patches. We will use a pattern like
+"psuh/v2-*.patch" (not "psuh/*.patch", which would match v1 and v2 patches).
 
 Edit your cover letter again. Now is a good time to mention what's different
 between your last version and now, if it's something significant. You do not
@@ -1082,7 +1102,7 @@ to the command:
 ----
 $ git send-email --to=target@example.com
                 --in-reply-to="<foo.12345.author@example.com>"
-                psuh/v2*
+                psuh/v2-*.patch
 ----
 
 [[single-patch]]
diff --git a/Documentation/RelNotes/2.34.0.txt b/Documentation/RelNotes/2.34.0.txt
new file mode 100644 (file)
index 0000000..c85385d
--- /dev/null
@@ -0,0 +1,345 @@
+Git 2.34 Release Notes
+======================
+
+Updates since Git 2.33
+----------------------
+
+Backward compatibility notes
+
+ * The "--preserve-merges" option of "git rebase" has been removed.
+
+
+UI, Workflows & Features
+
+ * Pathname expansion (like "~username/") learned a way to specify a
+   location relative to Git installation (e.g. its $sharedir which is
+   $(prefix)/share), with "%(prefix)".
+
+ * Use `ort` instead of `recursive` as the default merge strategy.
+
+ * The userdiff pattern for "java" language has been updated.
+
+ * "git rebase" by default skips changes that are equivalent to
+   commits that are already in the history the branch is rebased onto;
+   give messages when this happens to let the users be aware of
+   skipped commits, and also teach them how to tell "rebase" to keep
+   duplicated changes.
+
+ * The advice message that "git cherry-pick" gives when it asks
+   conflicted replay of a commit to be resolved by the end user has
+   been updated.
+
+ * After "git clone --recurse-submodules", all submodules are cloned
+   but they are not by default recursed into by other commands.  With
+   submodule.stickyRecursiveClone configuration set, submodule.recurse
+   configuration is set to true in a repository created by "clone"
+   with "--recurse-submodules" option.
+
+ * The logic for auto-correction of misspelt subcommands learned to go
+   interactive when the help.autocorrect configuration variable is set
+   to 'prompt'.
+
+ * "git maintenance" scheduler learned to use systemd timers as a
+   possible backend.
+
+ * "git diff --submodule=diff" showed failure from run_command() when
+   trying to run diff inside a submodule, when the user manually
+   removes the submodule directory.
+
+ * "git bundle unbundle" learned to show progress display.
+
+ * In cone mode, the sparse-index code path learned to remove ignored
+   files (like build artifacts) outside the sparse cone, allowing the
+   entire directory outside the sparse cone to be removed, which is
+   especially useful when the sparse patterns change.
+
+ * Taking advantage of the CGI interface, http-backend has been
+   updated to enable protocol v2 automatically when the other side
+   asks for it.
+
+ * The credential-cache helper has been adjusted to Windows.
+
+ * The error in "git help no-such-git-command" is handled better.
+
+ * The unicode character width table (used for output alignment) has
+   been updated.
+
+ * The ref iteration code used to optionally allow dangling refs to be
+   shown, which has been tightened up.
+
+ * "git add", "git mv", and "git rm" have been adjusted to avoid
+   updating paths outside of the sparse-checkout definition unless
+   the user specifies a "--sparse" option.
+
+ * "git repack" has been taught to generate multi-pack reachability
+   bitmaps.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * "git bisect" spawned "git show-branch" only to pretty-print the
+   title of the commit after checking out the next version to be
+   tested; this has been rewritten in C.
+
+ * "git add" can work better with the sparse index.
+
+ * Support for ancient versions of cURL library (pre 7.19.4) has been
+   dropped.
+
+ * A handful of tests that assumed implementation details of files
+   backend for refs have been cleaned up.
+
+ * trace2 logs learned to show parent process name to see in what
+   context Git was invoked.
+
+ * Loading of ref tips to prepare for common ancestry negotiation in
+   "git fetch-pack" has been optimized by taking advantage of the
+   commit graph when available.
+
+ * Remind developers that the userdiff patterns should be kept simple
+   and permissive, assuming that the contents they apply are always
+   syntactically correct.
+
+ * The current implementation of GIT_TEST_FAIL_PREREQS is broken in
+   that checking for the lack of a prerequisite would not work.  Avoid
+   the use of "if ! test_have_prereq X" in a test script.
+
+ * The revision traversal API has been optimized by taking advantage
+   of the commit-graph, when available, to determine if a commit is
+   reachable from any of the existing refs.
+
+ * "git fetch --quiet" optimization to avoid useless computation of
+   info that will never be displayed.
+
+ * Callers from older advice_config[] based API has been updated to
+   use the newer advice_if_enabled() and advice_enabled() API.
+
+ * Teach "test_pause" and "debug" helpers to allow using the HOME and
+   TERM environment variables the user usually uses.
+
+ * "make INSTALL_STRIP=-s install" allows the installation step to use
+   "install -s" to strip the binaries as they get installed.
+
+ * Code that handles large number of refs in the "git fetch" code
+   path has been optimized.
+
+ * The reachability bitmap file used to be generated only for a single
+   pack, but now we've learned to generate bitmaps for history that
+   span across multiple packfiles.
+
+ * The code to make "git grep" recurse into submodules has been
+   updated to migrate away from the "add submodule's object store as
+   an alternate object store" mechanism (which is suboptimal).
+
+ * The tracing of process ancestry information has been enhanced.
+
+ * Reduce number of write(2) system calls while sending the
+   ref advertisement.
+
+ * Update the build procedure to use the "-pedantic" build when
+   DEVELOPER makefile macro is in effect.
+
+ * Large part of "git submodule add" gets rewritten in C.
+
+ * The run-command API has been updated so that the callers can easily
+   ask the file descriptors open for packfiles to be closed immediately
+   before spawning commands that may trigger auto-gc.
+
+ * An oddball OPTION_ARGUMENT feature has been removed from the
+   parse-options API.
+
+ * The mergesort implementation used to sort linked list has been
+   optimized.
+
+ * Remove external declaration of functions that no longer exist.
+
+ * "git multi-pack-index write --bitmap" learns to propagate the
+   hashcache from original bitmap to resulting bitmap.
+
+ * CI learns to run the leak sanitizer builds.
+
+ * "git grep --recurse-submodules" takes trees and blobs from the
+   submodule repository, but the textconv settings when processing a
+   blob from the submodule is not taken from the submodule repository.
+   A test is added to demonstrate the issue, without fixing it.
+
+ * Teach "git help -c" into helping the command line completion of
+   configuration variables.
+
+ * When "git cmd -h" shows more than one line of usage text (e.g.
+   the cmd subcommand may take sub-sub-command), parse-options API
+   learned to align these lines, even across i18n/l10n.
+
+ * Prevent "make sparse" from running for the source files that
+   haven't been modified.
+
+
+Fixes since v2.33
+-----------------
+
+ * Input validation of "git pack-objects --stdin-packs" has been
+   corrected.
+
+ * Bugfix for common ancestor negotiation recently introduced in "git
+   push" code path.
+
+ * "git pull" had various corner cases that were not well thought out
+   around its --rebase backend, e.g. "git pull --ff-only" did not stop
+   but went ahead and rebased when the history on other side is not a
+   descendant of our history.  The series tries to fix them up.
+
+ * "git apply" miscounted the bytes and failed to read to the end of
+   binary hunks.
+
+ * "git range-diff" code clean-up.
+
+ * "git commit --fixup" now works with "--edit" again, after it was
+   broken in v2.32.
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+   new version has a blocker bug for that architecture.
+
+ * Checking out all the paths from HEAD during the last conflicted
+   step in "git rebase" and continuing would cause the step to be
+   skipped (which is expected), but leaves MERGE_MSG file behind in
+   $GIT_DIR and confuses the next "git commit", which has been
+   corrected.
+
+ * Various bugs in "git rebase -r" have been fixed.
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+   failure, which has been corrected to just return an error to the
+   caller to be handled.
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+   when there are unmerged paths.
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+   even when --quiet and/or --no-progress options were given.
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+   ref that points at a missing commit, which has been corrected.
+
+ * Build update for Apple clang.
+
+ * The parser for the "--nl" option of "git column" has been
+   corrected.
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+   forgot to take the ref namespaces into account when handling
+   want-ref requests.
+
+ * The sparse-index support can corrupt the index structure by storing
+   a stale and/or uninitialized data, which has been corrected.
+
+ * Buggy tests could damage repositories outside the throw-away test
+   area we created.  We now by default export GIT_CEILING_DIRECTORIES
+   to limit the damage from such a stray test.
+
+ * Even when running "git send-email" without its own threaded
+   discussion support, a threading related header in one message is
+   carried over to the subsequent message to result in an unwanted
+   threading, which has been corrected.
+
+ * The output from "git fast-export", when its anonymization feature
+   is in use, showed an annotated tag incorrectly.
+
+ * Doc update plus improved error reporting.
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+
+ * Regression fix.
+
+ * The "git apply -3" code path learned not to bother the lower level
+   merge machinery when the three-way merge can be trivially resolved
+   without the content level merge.  This fixes a regression caused by
+   recent "-3way first and fall back to direct application" change.
+
+ * The code that optionally creates the *.rev reverse index file has
+   been optimized to avoid needless computation when it is not writing
+   the file out.
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+   corrected.
+
+ * The order in which various files that make up a single (conceptual)
+   packfile has been reevaluated and straightened up.  This matters in
+   correctness, as an incomplete set of files must not be shown to a
+   running Git.
+
+ * The "mode" word is useless in a call to open(2) that does not
+   create a new file.  Such a call in the files backend of the ref
+   subsystem has been cleaned up.
+
+ * "git update-ref --stdin" failed to flush its output as needed,
+   which potentially led the conversation to a deadlock.
+
+ * When "git am --abort" fails to abort correctly, it still exited
+   with exit status of 0, which has been corrected.
+
+ * Correct nr and alloc members of strvec struct to be of type size_t.
+
+ * "git stash", where the tentative change involves changing a
+   directory to a file (or vice versa), was confused, which has been
+   corrected.
+
+ * "git clone" from a repository whose HEAD is unborn into a bare
+   repository didn't follow the branch name the other side used, which
+   is corrected.
+
+ * "git cvsserver" had a long-standing bug in its authentication code,
+   which has finally been corrected (it is unclear and is a separate
+   question if anybody is seriously using it, though).
+
+ * "git difftool --dir-diff" mishandled symbolic links.
+
+ * Sensitive data in the HTTP trace were supposed to be redacted, but
+   we failed to do so in HTTP/2 requests.
+
+ * "make clean" has been updated to remove leftover .depend/
+   directories, even when it is not told to use them to compute header
+   dependencies.
+
+ * Protocol v0 clients can get stuck parsing a malformed feature line.
+
+ * A few kinds of changes "git status" can show were not documented.
+   (merge d2a534c515 ja/doc-status-types-and-copies later to maint).
+
+ * The mergesort implementation used to sort linked list has been
+   optimized.
+   (merge c90cfc225b rs/mergesort later to maint).
+
+ * An editor session launched during a Git operation (e.g. during 'git
+   commit') can leave the terminal in a funny state.  The code path
+   has updated to save the terminal state before, and restore it
+   after, it spawns an editor.
+   (merge 3d411afabc cm/save-restore-terminal later to maint).
+
+ * "git cat-file --batch" with the "--batch-all-objects" option is
+   supposed to iterate over all the objects found in a repository, but
+   it used to translate these object names using the replace mechanism,
+   which defeats the point of enumerating all objects in the repository.
+   This has been corrected.
+   (merge bf972896d7 jk/cat-file-batch-all-wo-replace later to maint).
+
+ * Recent sparse-index work broke safety against attempts to add paths
+   with trailing slashes to the index, which has been corrected.
+   (merge c8ad9d04c6 rs/make-verify-path-really-verify-again later to maint).
+
+ * The "--color-lines" and "--color-by-age" options of "git blame"
+   have been missing, which are now documented.
+   (merge 8c32856133 bs/doc-blame-color-lines later to maint).
+
+ * The PATH used in CI job may be too wide and let incompatible dlls
+   to be grabbed, which can cause the build&test to fail.  Tighten it.
+   (merge 7491ef6198 js/windows-ci-path-fix later to maint).
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge f188160be9 ab/bundle-remove-verbose-option later to maint).
+   (merge 8c6b4332b4 rs/close-pack-leakfix later to maint).
+   (merge 51b04c05b7 bs/difftool-msg-tweak later to maint).
+   (merge dd20e4a6db ab/make-compdb-fix later to maint).
+   (merge 6ffb990dc4 os/status-docfix later to maint).
+   (merge 100c2da2d3 rs/p3400-lose-tac later to maint).
+   (merge 76f3b69896 tb/aggregate-ignore-leading-whitespaces later to maint).
+   (merge 6e4fd8bfcd tz/doc-link-to-bundle-format-fix later to maint).
index 117f4cf80645e13b991534318ed7d7eb33e62fa1..9a663535f443c0f46a0a244c4dc454acdf5ffdef 100644 (file)
@@ -136,5 +136,16 @@ take effect.
        option.  An empty file name, `""`, will clear the list of revs from
        previously processed files.
 
+--color-lines::
+       Color line annotations in the default format differently if they come from
+       the same commit as the preceding line. This makes it easier to distinguish
+       code blocks introduced by different commits. The color defaults to cyan and
+       can be adjusted using the `color.blame.repeatedLines` config option.
+
+--color-by-age::
+       Color line annotations depending on the age of the line in the default format.
+       The `color.blame.highlightRecent` config option controls what color is used for
+       each range of age.
+
 -h::
        Show help message.
index bf82766a6a272d3c42d93249ba64cf1a880088e1..0c0e6b859f1ed28753bd9d1bea9d2969388bd39f 100644 (file)
@@ -298,6 +298,15 @@ pathname::
        tilde expansion happens to such a string: `~/`
        is expanded to the value of `$HOME`, and `~user/` to the
        specified user's home directory.
++
+If a path starts with `%(prefix)/`, the remainder is interpreted as a
+path relative to Git's "runtime prefix", i.e. relative to the location
+where Git itself was installed. For example, `%(prefix)/bin/` refers to
+the directory in which the Git executable itself lives. If Git was
+compiled without runtime prefix support, the compiled-in prefix will be
+subsituted instead. In the unlikely event that a literal path needs to
+be specified that should _not_ be expanded, it needs to be prefixed by
+`./`, like so: `./%(prefix)/bin`.
 
 
 Variables
index 8b2849ff7b3f5c014dd64504a519eaf78daa516a..063eec2511d37edd3c00037dec3ce9298ce20506 100644 (file)
@@ -44,6 +44,9 @@ advice.*::
                Shown when linkgit:git-push[1] rejects a forced update of
                a branch when its remote-tracking ref has updates that we
                do not have locally.
+       skippedCherryPicks::
+               Shown when linkgit:git-rebase[1] skips a commit that has already
+               been cherry-picked onto the upstream branch.
        statusAheadBehind::
                Shown when linkgit:git-status[1] computes the ahead/behind
                counts for a local ref compared to its remote tracking ref,
index cc5f3249fc58a39da1580b49ad0145c44ec1544f..d323d7327f6ba60b0a460019057f9dd188500873 100644 (file)
@@ -85,10 +85,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
 so that the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
 +
-When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
-`--preserve-merges` along to 'git rebase' so that locally committed merge
-commits will not be flattened by running 'git pull'.
-+
 When the value is `interactive` (or just 'i'), the rebase is run in interactive
 mode.
 +
index e05d520a867490ce6b5388e2c48d1ce53de4e07e..dd2d2e0d84e7cdd657c4b196a9a7a84829081de8 100644 (file)
@@ -9,26 +9,29 @@ color.advice.hint::
        Use customized color for hints.
 
 color.blame.highlightRecent::
-       This can be used to color the metadata of a blame line depending
-       on age of the line.
+       Specify the line annotation color for `git blame --color-by-age`
+       depending upon the age of the line.
 +
-This setting should be set to a comma-separated list of color and date settings,
-starting and ending with a color, the dates should be set from oldest to newest.
-The metadata will be colored given the colors if the line was introduced
-before the given timestamp, overwriting older timestamped colors.
+This setting should be set to a comma-separated list of color and
+date settings, starting and ending with a color, the dates should be
+set from oldest to newest. The metadata will be colored with the
+specified colors if the line was introduced before the given
+timestamp, overwriting older timestamped colors.
+
 +
-Instead of an absolute timestamp relative timestamps work as well, e.g.
-2.weeks.ago is valid to address anything older than 2 weeks.
+Instead of an absolute timestamp relative timestamps work as well,
+e.g. `2.weeks.ago` is valid to address anything older than 2 weeks.
+
 +
-It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
-everything older than one year blue, recent changes between one month and
-one year old are kept white, and lines introduced within the last month are
-colored red.
+It defaults to `blue,12 month ago,white,1 month ago,red`, which
+colors everything older than one year blue, recent changes between
+one month and one year old are kept white, and lines introduced
+within the last month are colored red.
 
 color.blame.repeatedLines::
-       Use the customized color for the part of git-blame output that
-       is repeated meta information per line (such as commit id,
-       author name, date and timezone). Defaults to cyan.
+       Use the specified color to colorize line annotations for
+       `git blame --color-lines`, if they come from the same commit as the
+       preceding line. Defaults to cyan.
 
 color.branch::
        A boolean to enable/disable color in the output of
index d94025cb3684d8e20f2875716cc16dfad47cf6ff..4f30c7dbdd9f88480ab5ba54ba71f016c6571114 100644 (file)
@@ -11,13 +11,13 @@ gpg.program::
 
 gpg.format::
        Specifies which key format to use when signing with `--gpg-sign`.
-       Default is "openpgp" and another possible value is "x509".
+       Default is "openpgp". Other possible values are "x509", "ssh".
 
 gpg.<format>.program::
        Use this to customize the program used for the signing format you
        chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
        be used as a legacy synonym for `gpg.openpgp.program`. The default
-       value for `gpg.x509.program` is "gpgsm".
+       value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen".
 
 gpg.minTrustLevel::
        Specifies a minimum trust level for signature verification.  If
@@ -33,3 +33,42 @@ gpg.minTrustLevel::
 * `marginal`
 * `fully`
 * `ultimate`
+
+gpg.ssh.defaultKeyCommand:
+       This command that will be run when user.signingkey is not set and a ssh
+       signature is requested. On successful exit a valid ssh public key is
+       expected in the first line of its output. To automatically use the first
+       available key from your ssh-agent set this to "ssh-add -L".
+
+gpg.ssh.allowedSignersFile::
+       A file containing ssh public keys which you are willing to trust.
+       The file consists of one or more lines of principals followed by an ssh
+       public key.
+       e.g.: user1@example.com,user2@example.com ssh-rsa AAAAX1...
+       See ssh-keygen(1) "ALLOWED SIGNERS" for details.
+       The principal is only used to identify the key and is available when
+       verifying a signature.
++
+SSH has no concept of trust levels like gpg does. To be able to differentiate
+between valid signatures and trusted signatures the trust level of a signature
+verification is set to `fully` when the public key is present in the allowedSignersFile.
+Otherwise the trust level is `undefined` and git verify-commit/tag will fail.
++
+This file can be set to a location outside of the repository and every developer
+maintains their own trust store. A central repository server could generate this
+file automatically from ssh keys with push access to verify the code against.
+In a corporate setting this file is probably generated at a global location
+from automation that already handles developer ssh keys.
++
+A repository that only allows signed commits can store the file
+in the repository itself using a path relative to the top-level of the working tree.
+This way only committers with an already valid key can add or change keys in the keyring.
++
+Using a SSH CA key with the cert-authority option
+(see ssh-keygen(1) "CERTIFICATES") is also valid.
+
+gpg.ssh.revocationFile::
+       Either a SSH KRL or a list of revoked public keys (without the principal prefix).
+       See ssh-keygen(1) for details.
+       If a public key is found in this file then it will always be treated
+       as having trust level "never" and signatures will show as invalid.
index 783a90a0f935f0d453a5f9498147ea544e7f6e15..610701f9a3745e0253edf64a65ef1e313eec6660 100644 (file)
@@ -9,13 +9,15 @@ help.format::
 
 help.autoCorrect::
        If git detects typos and can identify exactly one valid command similar
-       to the error, git will automatically run the intended command after
-       waiting a duration of time defined by this configuration value in
-       deciseconds (0.1 sec).  If this value is 0, the suggested corrections
-       will be shown, but not executed. If it is a negative integer, or
-       "immediate", the suggested command
-       is run immediately. If "never", suggestions are not shown at all. The
-       default value is zero.
+       to the error, git will try to suggest the correct command or even
+       run the suggestion automatically. Possible config values are:
+        - 0 (default): show the suggested command.
+        - positive number: run the suggested command after specified
+deciseconds (0.1 sec).
+        - "immediate": run the suggested command immediately.
+        - "prompt": show the suggestion and prompt for confirmation to run
+the command.
+        - "never": don't run or show any suggested command.
 
 help.htmlPath::
        Specify the path where the HTML documentation resides. File system paths
index 763f7af7c4d742423320414c7c3f1fb9d8451cf9..ad7f73a1eade701492ae7b773cc5bb5dc37e5053 100644 (file)
@@ -159,6 +159,10 @@ pack.writeBitmapHashCache::
        between an older, bitmapped pack and objects that have been
        pushed since the last gc). The downside is that it consumes 4
        bytes per object of disk space. Defaults to true.
++
+When writing a multi-pack reachability bitmap, no new namehashes are
+computed; instead, any namehashes stored in an existing bitmap are
+permuted into their appropriate location when writing a new bitmap.
 
 pack.writeReverseIndex::
        When true, git will write a corresponding .rev file (see:
index 5404830609569d78dbcd597847c86e4302123b75..9349e09261b25ce0d80b2543810fd4e8a3e08bf6 100644 (file)
@@ -18,10 +18,6 @@ When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase'
 so that the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
 +
-When `preserve` (or just 'p', deprecated in favor of `merges`), also pass
-`--preserve-merges` along to 'git rebase' so that locally committed merge
-commits will not be flattened by running 'git pull'.
-+
 When the value is `interactive` (or just 'i'), the rebase is run in interactive
 mode.
 +
index 59aec7c3aed32aac0a8d7e015c0602705e4ccc6d..ad78dce9ecbfc6faf633be133f8f88218844bd9a 100644 (file)
@@ -36,3 +36,10 @@ user.signingKey::
        commit, you can override the default selection with this variable.
        This option is passed unchanged to gpg's --local-user parameter,
        so you may specify a key using any method that gpg supports.
+       If gpg.format is set to "ssh" this can contain the literal ssh public
+       key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and
+       corresponds to the private key used for signing. The private key
+       needs to be available via ssh-agent. Alternatively it can be set to
+       a file containing a private key directly. If not set git will call
+       gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first
+       key available.
index fbbd410a8418db705654bbe87db32b50f71a4b99..7a9c3b6ff4c6a2231da829edb3414a9c80533cd3 100644 (file)
@@ -59,7 +59,7 @@ Possible status letters are:
 - D: deletion of a file
 - M: modification of the contents or mode of a file
 - R: renaming of a file
-- T: change in the type of the file
+- T: change in the type of the file (regular file, symbolic link or submodule)
 - U: file is unmerged (you must complete the merge before it can
   be committed)
 - X: "unknown" change type (most probably a bug, please report it)
index be5e3ac54b858778c8454970e8abae81b0c4841f..11eb70f16c7287d53b567368754a19f33d4c7fb2 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
-         [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
+         [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse]
          [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
          [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
          [--] [<pathspec>...]
@@ -79,6 +79,13 @@ in linkgit:gitglossary[7].
 --force::
        Allow adding otherwise ignored files.
 
+--sparse::
+       Allow updating index entries outside of the sparse-checkout cone.
+       Normally, `git add` refuses to update index entries whose paths do
+       not fit within the sparse-checkout cone, since those files might
+       be removed from the working tree without warning. See
+       linkgit:git-sparse-checkout[1] for more details.
+
 -i::
 --interactive::
        Add modified contents in the working tree interactively to
index 3bf5d5d8b4abe78a1be0f811640e516d6868b346..d7a46cc67441939a9fc80ac7d6ec39eb6b44f752 100644 (file)
@@ -11,8 +11,8 @@ SYNOPSIS
 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
            [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
            [--ignore-rev <rev>] [--ignore-revs-file <file>]
-           [--progress] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>..<rev>]
-           [--] <file>
+           [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
+           [<rev> | --contents <file> | --reverse <rev>..<rev>] [--] <file>
 
 DESCRIPTION
 -----------
@@ -93,6 +93,19 @@ include::blame-options.txt[]
        is used for a caret to mark the boundary commit.
 
 
+THE DEFAULT FORMAT
+------------------
+
+When neither `--porcelain` nor `--incremental` option is specified,
+`git blame` will output annotation for each line with:
+
+- abbreviated object name for the commit the line came from;
+- author ident (by default author name and date, unless `-s` or `-e`
+  is specified); and
+- line number
+
+before the line contents.
+
 THE PORCELAIN FORMAT
 --------------------
 
index ac0d0038350f54d3adf593da9885c16633aa89c0..71b5ecabd1f08386e50063d249ecb7e71fb2b5bb 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
                    [--version=<version>] <file> <git-rev-list-args>
 'git bundle' verify [-q | --quiet] <file>
 'git bundle' list-heads <file> [<refname>...]
-'git bundle' unbundle <file> [<refname>...]
+'git bundle' unbundle [--progress] <file> [<refname>...]
 
 DESCRIPTION
 -----------
index 4eb0421b3fd946e709318d835783530bb89e7caa..27b27e2b300c49bb07f348291a8b04af3d128f30 100644 (file)
@@ -94,8 +94,10 @@ OPTIONS
        Instead of reading a list of objects on stdin, perform the
        requested batch operation on all objects in the repository and
        any alternate object stores (not just reachable objects).
-       Requires `--batch` or `--batch-check` be specified. Note that
-       the objects are visited in order sorted by their hashes.
+       Requires `--batch` or `--batch-check` be specified. By default,
+       the objects are visited in order sorted by their hashes; see
+       also `--unordered` below. Objects are presented as-is, without
+       respecting the "replace" mechanism of linkgit:git-replace[1].
 
 --buffer::
        Normally batch output is flushed after each object is output, so
index b1a6fe4499730690777de66558621446e1e58bac..d473c9bf38753ee0786d10e7509a83bde7e58c97 100644 (file)
@@ -118,8 +118,9 @@ OPTIONS
 -f::
 --force::
        When switching branches, proceed even if the index or the
-       working tree differs from `HEAD`.  This is used to throw away
-       local changes.
+       working tree differs from `HEAD`, and even if there are untracked
+       files in the way.  This is used to throw away local changes and
+       any untracked files or directories that are in the way.
 +
 When checking out paths from the index, do not fail upon unmerged
 entries; instead, unmerged entries are ignored.
index 2ae2478de706ce064f6e3d62eed204d9a7c48325..6da899c62964275a97854c0b338f83f51db4bdfe 100644 (file)
@@ -235,6 +235,15 @@ and `date` to extract the named component.  For email fields (`authoremail`,
 without angle brackets, and `:localpart` to get the part before the `@` symbol
 out of the trimmed email.
 
+The raw data in an object is `raw`.
+
+raw:size::
+       The raw data size of the object.
+
+Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`,
+because such language may not support arbitrary binary data in their string
+variable type.
+
 The message in a commit or a tag object is `contents`, from which
 `contents:<part>` can be used to extract various parts out of:
 
index 44fe8860b3f17bcb64d5d90ed22b6f8898ca93fa..96d5f598b4b583332bd49b13954f9fcf77294ff8 100644 (file)
@@ -8,8 +8,10 @@ git-help - Display help information about Git
 SYNOPSIS
 --------
 [verse]
-'git help' [-a|--all [--[no-]verbose]] [-g|--guides]
-          [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE]
+'git help' [-a|--all [--[no-]verbose]]
+          [[-i|--info] [-m|--man] [-w|--web]] [COMMAND|GUIDE]
+'git help' [-g|--guides]
+'git help' [-c|--config]
 
 DESCRIPTION
 -----------
@@ -58,8 +60,7 @@ OPTIONS
 
 -g::
 --guides::
-       Prints a list of the Git concept guides on the standard output. This
-       option overrides any given command or guide name.
+       Prints a list of the Git concept guides on the standard output.
 
 -i::
 --info::
index 558966aa837930538010217752c3d94dee29dccc..0c5c0dde19f0b0f4fe271c1d9e7a5dcc01efe48e 100644 (file)
@@ -16,7 +16,9 @@ A simple CGI program to serve the contents of a Git repository to Git
 clients accessing the repository over http:// and https:// protocols.
 The program supports clients fetching using both the smart HTTP protocol
 and the backwards-compatible dumb HTTP protocol, as well as clients
-pushing using the smart HTTP protocol.
+pushing using the smart HTTP protocol. It also supports Git's
+more-efficient "v2" protocol if properly configured; see the
+discussion of `GIT_PROTOCOL` in the ENVIRONMENT section below.
 
 It verifies that the directory has the magic file
 "git-daemon-export-ok", and it will refuse to export any Git directory
@@ -77,6 +79,18 @@ Apache 2.x::
 SetEnv GIT_PROJECT_ROOT /var/www/git
 SetEnv GIT_HTTP_EXPORT_ALL
 ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
+
+# This is not strictly necessary using Apache and a modern version of
+# git-http-backend, as the webserver will pass along the header in the
+# environment as HTTP_GIT_PROTOCOL, and http-backend will copy that into
+# GIT_PROTOCOL. But you may need this line (or something similar if you
+# are using a different webserver), or if you want to support older Git
+# versions that did not do that copying.
+#
+# Having the webserver set up GIT_PROTOCOL is perfectly fine even with
+# modern versions (and will take precedence over HTTP_GIT_PROTOCOL,
+# which means it can be used to override the client's request).
+SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
 ----------------------------------------------------------------
 +
 To enable anonymous read access but authenticated write access,
@@ -264,6 +278,16 @@ a repository with an extremely large number of refs.  The value can be
 specified with a unit (e.g., `100M` for 100 megabytes). The default is
 10 megabytes.
 
+Clients may probe for optional protocol capabilities (like the v2
+protocol) using the `Git-Protocol` HTTP header. In order to support
+these, the contents of that header must appear in the `GIT_PROTOCOL`
+environment variable. Most webservers will pass this header to the CGI
+via the `HTTP_GIT_PROTOCOL` variable, and `git-http-backend` will
+automatically copy that to `GIT_PROTOCOL`. However, some webservers may
+be more selective about which headers they'll pass, in which case they
+need to be configured explicitly (see the mention of `Git-Protocol` in
+the Apache config from the earlier EXAMPLES section).
+
 The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
 GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
 ensuring that any reflogs created by 'git-receive-pack' contain some
index 7fa74b9e79876bdbf8f4410185a9983c9f15a064..1f1e3592251259960f164094c3f82bf9eae455cc 100644 (file)
@@ -82,6 +82,12 @@ OPTIONS
 --strict::
        Die, if the pack contains broken objects or links.
 
+--progress-title::
+       For internal use only.
++
+Set the title of the progress bar. The title is "Receiving objects" by
+default and "Indexing objects" when `--stdin` is specified.
+
 --check-self-contained-and-connected::
        Die if the pack contains broken links. For internal use only.
 
index 1e738ad398320a70219b0a664dabbdf9a8103578..e2cfb68ab57907f640a618409533a98c7427af1d 100644 (file)
@@ -179,6 +179,17 @@ OPTIONS
        `maintenance.<task>.enabled` configured as `true` are considered.
        See the 'TASKS' section for the list of accepted `<task>` values.
 
+--scheduler=auto|crontab|systemd-timer|launchctl|schtasks::
+       When combined with the `start` subcommand, specify the scheduler
+       for running the hourly, daily and weekly executions of
+       `git maintenance run`.
+       Possible values for `<scheduler>` are `auto`, `crontab`
+       (POSIX), `systemd-timer` (Linux), `launchctl` (macOS), and
+       `schtasks` (Windows). When `auto` is specified, the
+       appropriate platform-specific scheduler is used; on Linux,
+       `systemd-timer` is used if available, otherwise
+       `crontab`. Default is `auto`.
+
 
 TROUBLESHOOTING
 ---------------
@@ -277,6 +288,52 @@ schedule to ensure you are executing the correct binaries in your
 schedule.
 
 
+BACKGROUND MAINTENANCE ON LINUX SYSTEMD SYSTEMS
+-----------------------------------------------
+
+While Linux supports `cron`, depending on the distribution, `cron` may
+be an optional package not necessarily installed. On modern Linux
+distributions, systemd timers are superseding it.
+
+If user systemd timers are available, they will be used as a replacement
+of `cron`.
+
+In this case, `git maintenance start` will create user systemd timer units
+and start the timers. The current list of user-scheduled tasks can be found
+by running `systemctl --user list-timers`. The timers written by `git
+maintenance start` are similar to this:
+
+-----------------------------------------------------------------------
+$ systemctl --user list-timers
+NEXT                         LEFT          LAST                         PASSED     UNIT                         ACTIVATES
+Thu 2021-04-29 19:00:00 CEST 42min left    Thu 2021-04-29 18:00:11 CEST 17min ago  git-maintenance@hourly.timer git-maintenance@hourly.service
+Fri 2021-04-30 00:00:00 CEST 5h 42min left Thu 2021-04-29 00:00:11 CEST 18h ago    git-maintenance@daily.timer  git-maintenance@daily.service
+Mon 2021-05-03 00:00:00 CEST 3 days left   Mon 2021-04-26 00:00:11 CEST 3 days ago git-maintenance@weekly.timer git-maintenance@weekly.service
+-----------------------------------------------------------------------
+
+One timer is registered for each `--schedule=<frequency>` option.
+
+The definition of the systemd units can be inspected in the following files:
+
+-----------------------------------------------------------------------
+~/.config/systemd/user/git-maintenance@.timer
+~/.config/systemd/user/git-maintenance@.service
+~/.config/systemd/user/timers.target.wants/git-maintenance@hourly.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@daily.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@weekly.timer
+-----------------------------------------------------------------------
+
+`git maintenance start` will overwrite these files and start the timer
+again with `systemctl --user`, so any customization should be done by
+creating a drop-in file, i.e. a `.conf` suffixed file in the
+`~/.config/systemd/user/git-maintenance@.service.d` directory.
+
+`git maintenance stop` will stop the user systemd timers and delete
+the above mentioned files.
+
+For more details, see `systemd.timer(5)`.
+
+
 BACKGROUND MAINTENANCE ON MACOS SYSTEMS
 ---------------------------------------
 
index ffd601bc17b4dc8f453e6a5b28fb4d6239176d96..b008ce2850ddd5bb4e43a34ec88a2777057e97bd 100644 (file)
@@ -9,8 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes
 SYNOPSIS
 --------
 [verse]
-'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress]
-       [--preferred-pack=<pack>] <subcommand>
+'git multi-pack-index' [--object-dir=<dir>] [--[no-]bitmap] <sub-command>
 
 DESCRIPTION
 -----------
@@ -23,10 +22,13 @@ OPTIONS
        Use given directory for the location of Git objects. We check
        `<dir>/packs/multi-pack-index` for the current MIDX file, and
        `<dir>/packs` for the pack-files to index.
++
+`<dir>` must be an alternate of the current repository.
 
 --[no-]progress::
        Turn progress on/off explicitly. If neither is specified, progress is
-       shown if standard error is connected to a terminal.
+       shown if standard error is connected to a terminal. Supported by
+       sub-commands `write`, `verify`, `expire`, and `repack.
 
 The following subcommands are available:
 
@@ -37,9 +39,31 @@ write::
 --
        --preferred-pack=<pack>::
                Optionally specify the tie-breaking pack used when
-               multiple packs contain the same object. If not given,
-               ties are broken in favor of the pack with the lowest
-               mtime.
+               multiple packs contain the same object. `<pack>` must
+               contain at least one object. If not given, ties are
+               broken in favor of the pack with the lowest mtime.
+
+       --[no-]bitmap::
+               Control whether or not a multi-pack bitmap is written.
+
+       --stdin-packs::
+               Write a multi-pack index containing only the set of
+               line-delimited pack index basenames provided over stdin.
+
+       --refs-snapshot=<path>::
+               With `--bitmap`, optionally specify a file which
+               contains a "refs snapshot" taken prior to repacking.
++
+A reference snapshot is composed of line-delimited OIDs corresponding to
+the reference tips, usually taken by `git repack` prior to generating a
+new pack. A line may optionally start with a `+` character to indicate
+that the reference which corresponds to that OID is "preferred" (see
+linkgit:git-config[1]'s `pack.preferBitmapTips`.)
++
+The file given at `<path>` is expected to be readable, and can contain
+duplicates. (If a given OID is given more than once, it is marked as
+preferred if at least one instance of it begins with the special `+`
+marker).
 --
 
 verify::
@@ -81,6 +105,13 @@ EXAMPLES
 $ git multi-pack-index write
 -----------------------------------------------
 
+* Write a MIDX file for the packfiles in the current .git folder with a
+corresponding bitmap.
++
+-------------------------------------------------------------
+$ git multi-pack-index write --preferred-pack=<pack> --bitmap
+-------------------------------------------------------------
+
 * Write a MIDX file for the packfiles in an alternate object store.
 +
 -----------------------------------------------
index aef757ec89c880aeeeb7466a6c3b838d2cbf711e..0e14f8b5b25924c98c4bda92729787f2361ada9c 100644 (file)
@@ -105,7 +105,7 @@ Options related to merging
 include::merge-options.txt[]
 
 -r::
---rebase[=false|true|merges|preserve|interactive]::
+--rebase[=false|true|merges|interactive]::
        When true, rebase the current branch on top of the upstream
        branch after fetching. If there is a remote-tracking branch
        corresponding to the upstream branch and the upstream branch
@@ -116,10 +116,6 @@ When set to `merges`, rebase using `git rebase --rebase-merges` so that
 the local merge commits are included in the rebase (see
 linkgit:git-rebase[1] for details).
 +
-When set to `preserve` (deprecated in favor of `merges`), rebase with the
-`--preserve-merges` option passed to `git rebase` so that locally created
-merge commits will not be flattened.
-+
 When false, merge the upstream branch into the current branch.
 +
 When `interactive`, enable the interactive mode of rebase.
index 5fa8bab64c2d0bb80f88ace023b5eafc898b05f1..8c3aceb832475f25f5712709040033ab2f5fab59 100644 (file)
@@ -10,8 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
-               [-u [--exclude-per-directory=<gitignore>] | -i]]
-               [--index-output=<file>] [--no-sparse-checkout]
+               [-u | -i]] [--index-output=<file>] [--no-sparse-checkout]
                (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
@@ -39,8 +38,9 @@ OPTIONS
 
 --reset::
        Same as -m, except that unmerged entries are discarded instead
-       of failing. When used with `-u`, updates leading to loss of
-       working tree changes will not abort the operation.
+       of failing.  When used with `-u`, updates leading to loss of
+       working tree changes or untracked files or directories will not
+       abort the operation.
 
 -u::
        After a successful merge, update the files in the work
@@ -88,21 +88,6 @@ OPTIONS
        The command will refuse to overwrite entries that already
        existed in the original index file.
 
---exclude-per-directory=<gitignore>::
-       When running the command with `-u` and `-m` options, the
-       merge result may need to overwrite paths that are not
-       tracked in the current branch.  The command usually
-       refuses to proceed with the merge to avoid losing such a
-       path.  However this safety valve sometimes gets in the
-       way.  For example, it often happens that the other
-       branch added a file that used to be a generated file in
-       your branch, and the safety valve triggers when you try
-       to switch to that branch after you ran `make` but before
-       running `make clean` to remove the generated file.  This
-       option tells the command to read per-directory exclude
-       file (usually '.gitignore') and allows such an untracked
-       but explicitly ignored file to be overwritten.
-
 --index-output=<file>::
        Instead of writing the results out to `$GIT_INDEX_FILE`,
        write the resulting index in the named file.  While the
index 73d49ec8d911505dffd664392321e2f064327c34..a1af21fcefe6098df8506c46e71a91585625281e 100644 (file)
@@ -79,9 +79,10 @@ remain the checked-out branch.
 
 If the upstream branch already contains a change you have made (e.g.,
 because you mailed a patch which was applied upstream), then that commit
-will be skipped. For example, running `git rebase master` on the
-following history (in which `A'` and `A` introduce the same set of changes,
-but have different committer information):
+will be skipped and warnings will be issued (if the `merge` backend is
+used).  For example, running `git rebase master` on the following
+history (in which `A'` and `A` introduce the same set of changes, but
+have different committer information):
 
 ------------
           A---B---C topic
@@ -312,7 +313,10 @@ See also INCOMPATIBLE OPTIONS below.
 By default (or if `--no-reapply-cherry-picks` is given), these commits
 will be automatically dropped.  Because this necessitates reading all
 upstream commits, this can be expensive in repos with a large number
-of upstream commits that need to be read.
+of upstream commits that need to be read.  When using the `merge`
+backend, warnings will be issued for each dropped commit (unless
+`--quiet` is given). Advice will also be issued unless
+`advice.skippedCherryPicks` is set to false (see linkgit:git-config[1]).
 +
 `--reapply-cherry-picks` allows rebase to forgo reading all upstream
 commits, potentially improving performance.
@@ -352,8 +356,8 @@ See also INCOMPATIBLE OPTIONS below.
 
 -s <strategy>::
 --strategy=<strategy>::
-       Use the given merge strategy, instead of the default
-       `recursive`.  This implies `--merge`.
+       Use the given merge strategy, instead of the default `ort`.
+       This implies `--merge`.
 +
 Because 'git rebase' replays each commit from the working branch
 on top of the <upstream> branch using the given strategy, using
@@ -366,7 +370,7 @@ See also INCOMPATIBLE OPTIONS below.
 --strategy-option=<strategy-option>::
        Pass the <strategy-option> through to the merge strategy.
        This implies `--merge` and, if no strategy has been
-       specified, `-s recursive`.  Note the reversal of 'ours' and
+       specified, `-s ort`.  Note the reversal of 'ours' and
        'theirs' as noted above for the `-m` option.
 +
 See also INCOMPATIBLE OPTIONS below.
@@ -442,7 +446,8 @@ When --fork-point is active, 'fork_point' will be used instead of
 ends up being empty, the <upstream> will be used as a fallback.
 +
 If <upstream> is given on the command line, then the default is
-`--no-fork-point`, otherwise the default is `--fork-point`.
+`--no-fork-point`, otherwise the default is `--fork-point`. See also
+`rebase.forkpoint` in linkgit:git-config[1].
 +
 If your branch was based on <upstream> but <upstream> was rewound and
 your branch contains commits which were dropped, this option can be used
@@ -522,29 +527,12 @@ i.e. commits that would be excluded by linkgit:git-log[1]'s
 the `rebase-cousins` mode is turned on, such commits are instead rebased
 onto `<upstream>` (or `<onto>`, if specified).
 +
-The `--rebase-merges` mode is similar in spirit to the deprecated
-`--preserve-merges` but works with interactive rebases,
-where commits can be reordered, inserted and dropped at will.
-+
 It is currently only possible to recreate the merge commits using the
-`recursive` merge strategy; different merge strategies can be used only via
+`ort` merge strategy; different merge strategies can be used only via
 explicit `exec git merge -s <strategy> [...]` commands.
 +
 See also REBASING MERGES and INCOMPATIBLE OPTIONS below.
 
--p::
---preserve-merges::
-       [DEPRECATED: use `--rebase-merges` instead] Recreate merge commits
-       instead of flattening the history by replaying commits a merge commit
-       introduces. Merge conflict resolutions or manual amendments to merge
-       commits are not preserved.
-+
-This uses the `--interactive` machinery internally, but combining it
-with the `--interactive` option explicitly is generally not a good
-idea unless you know what you are doing (see BUGS below).
-+
-See also INCOMPATIBLE OPTIONS below.
-
 -x <cmd>::
 --exec <cmd>::
        Append "exec <cmd>" after each line creating a commit in the
@@ -576,9 +564,6 @@ See also INCOMPATIBLE OPTIONS below.
        the root commit(s) on a branch.  When used with --onto, it
        will skip changes already contained in <newbase> (instead of
        <upstream>) whereas without --onto it will operate on every change.
-       When used together with both --onto and --preserve-merges,
-       'all' root commits will be rewritten to have <newbase> as parent
-       instead.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -640,7 +625,6 @@ are incompatible with the following options:
  * --allow-empty-message
  * --[no-]autosquash
  * --rebase-merges
- * --preserve-merges
  * --interactive
  * --exec
  * --no-keep-empty
@@ -651,13 +635,6 @@ are incompatible with the following options:
 
 In addition, the following pairs of options are incompatible:
 
- * --preserve-merges and --interactive
- * --preserve-merges and --signoff
- * --preserve-merges and --rebase-merges
- * --preserve-merges and --empty=
- * --preserve-merges and --ignore-whitespace
- * --preserve-merges and --committer-date-is-author-date
- * --preserve-merges and --ignore-date
  * --keep-base and --onto
  * --keep-base and --root
  * --fork-point and --root
@@ -1216,16 +1193,16 @@ successful merge so that the user can edit the message.
 If a `merge` command fails for any reason other than merge conflicts (i.e.
 when the merge operation did not even start), it is rescheduled immediately.
 
-By default, the `merge` command will use the `recursive` merge
-strategy for regular merges, and `octopus` for octopus merges.  One
-can specify a default strategy for all merges using the `--strategy`
-argument when invoking rebase, or can override specific merges in the
-interactive list of commands by using an `exec` command to call `git
-merge` explicitly with a `--strategy` argument.  Note that when
-calling `git merge` explicitly like this, you can make use of the fact
-that the labels are worktree-local refs (the ref `refs/rewritten/onto`
-would correspond to the label `onto`, for example) in order to refer
-to the branches you want to merge.
+By default, the `merge` command will use the `ort` merge strategy for
+regular merges, and `octopus` for octopus merges.  One can specify a
+default strategy for all merges using the `--strategy` argument when
+invoking rebase, or can override specific merges in the interactive
+list of commands by using an `exec` command to call `git merge`
+explicitly with a `--strategy` argument.  Note that when calling `git
+merge` explicitly like this, you can make use of the fact that the
+labels are worktree-local refs (the ref `refs/rewritten/onto` would
+correspond to the label `onto`, for example) in order to refer to the
+branches you want to merge.
 
 Note: the first command (`label onto`) labels the revision onto which
 the commits are rebased; The name `onto` is just a convention, as a nod
@@ -1275,29 +1252,6 @@ CONFIGURATION
 include::config/rebase.txt[]
 include::config/sequencer.txt[]
 
-BUGS
-----
-The todo list presented by the deprecated `--preserve-merges --interactive`
-does not represent the topology of the revision graph (use `--rebase-merges`
-instead).  Editing commits and rewording their commit messages should work
-fine, but attempts to reorder commits tend to produce counterintuitive results.
-Use `--rebase-merges` in such scenarios instead.
-
-For example, an attempt to rearrange
-------------
-1 --- 2 --- 3 --- 4 --- 5
-------------
-to
-------------
-1 --- 2 --- 4 --- 3 --- 5
-------------
-by moving the "pick 4" line will result in the following history:
-------------
-       3
-       /
-1 --- 2 --- 4 --- 5
-------------
-
 GIT
 ---
 Part of the linkgit:git[1] suite
index 25702ed73072f7375a08294a024e076862a5a170..014a78409b9473c10f62f85b21f9d9c6642ff844 100644 (file)
@@ -41,6 +41,11 @@ OPTIONS
 <directory>::
        The repository to sync into.
 
+--http-backend-info-refs::
+       Used by linkgit:git-http-backend[1] to serve up
+       `$GIT_URL/info/refs?service=git-receive-pack` requests. See
+       `--http-backend-info-refs` in linkgit:git-upload-pack[1].
+
 PRE-RECEIVE HOOK
 ----------------
 Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists
index 24c00c9384f40b3d6ff487d21d74d8fc19292bee..7183fb498f4ccec69e6bd75dcef44f54974a159b 100644 (file)
@@ -9,7 +9,7 @@ git-repack - Pack unpacked objects in a repository
 SYNOPSIS
 --------
 [verse]
-'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>]
+'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>] [--write-midx]
 
 DESCRIPTION
 -----------
@@ -128,10 +128,11 @@ depth is 4095.
 -b::
 --write-bitmap-index::
        Write a reachability bitmap index as part of the repack. This
-       only makes sense when used with `-a` or `-A`, as the bitmaps
+       only makes sense when used with `-a`, `-A` or `-m`, as the bitmaps
        must be able to refer to all reachable objects. This option
-       overrides the setting of `repack.writeBitmaps`.  This option
-       has no effect if multiple packfiles are created.
+       overrides the setting of `repack.writeBitmaps`. This option
+       has no effect if multiple packfiles are created, unless writing a
+       MIDX (in which case a multi-pack bitmap is created).
 
 --pack-kept-objects::
        Include objects in `.keep` files when repacking.  Note that we
@@ -189,6 +190,15 @@ this "roll-up", without respect to their reachability. This is subject
 to change in the future. This option (implying a drastically different
 repack mode) is not guaranteed to work with all other combinations of
 option to `git repack`.
++
+When writing a multi-pack bitmap, `git repack` selects the largest resulting
+pack as the preferred pack for object selection by the MIDX (see
+linkgit:git-multi-pack-index[1]).
+
+-m::
+--write-midx::
+       Write a multi-pack index (see linkgit:git-multi-pack-index[1])
+       containing the non-redundant packs.
 
 CONFIGURATION
 -------------
index 252e2d4e47d10404226cce6abfc0132a4d2e9b74..6f7685f53d5aa054b714690ed57e19b22575d8ba 100644 (file)
@@ -69,7 +69,8 @@ linkgit:git-add[1]).
 
 --hard::
        Resets the index and working tree. Any changes to tracked files in the
-       working tree since `<commit>` are discarded.
+       working tree since `<commit>` are discarded.  Any untracked files or
+       directories in the way of writing any tracked files are simply deleted.
 
 --merge::
        Resets the index and updates the files in the working tree that are
index 26e9b2847047c62b066730c20873d805b6790f51..81bc23f3cdbb56fbb8e0bd79c2bae40d35772cac 100644 (file)
@@ -72,6 +72,12 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
 --ignore-unmatch::
        Exit with a zero status even if no files matched.
 
+--sparse::
+       Allow updating index entries outside of the sparse-checkout cone.
+       Normally, `git rm` refuses to update index entries whose paths do
+       not fit within the sparse-checkout cone. See
+       linkgit:git-sparse-checkout[1] for more.
+
 -q::
 --quiet::
        `git rm` normally outputs one line (in the form of an `rm` command)
index 44fd146b9120305112c59b800c9e989bbc60f450..be41f119740ea7f5e2f53d3211e608bf4255544f 100644 (file)
@@ -9,10 +9,10 @@ git-send-pack - Push objects over Git protocol to another repository
 SYNOPSIS
 --------
 [verse]
-'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
+'git send-pack' [--dry-run] [--force] [--receive-pack=<git-receive-pack>]
                [--verbose] [--thin] [--atomic]
                [--[no-]signed|--signed=(true|false|if-asked)]
-               [<host>:]<directory> [<ref>...]
+               [<host>:]<directory> (--all | <ref>...)
 
 DESCRIPTION
 -----------
index fdcf43f87cb373caa4cfd0b1cb307454bd84f9c0..42056ee9ff99dafa417def2a69d89c046ad57df4 100644 (file)
@@ -210,6 +210,16 @@ case-insensitive check. This corrects for case mismatched filenames in the
 'git sparse-checkout set' command to reflect the expected cone in the working
 directory.
 
+When changing the sparse-checkout patterns in cone mode, Git will inspect each
+tracked directory that is not within the sparse-checkout cone to see if it
+contains any untracked files. If all of those files are ignored due to the
+`.gitignore` patterns, then the directory will be deleted. If any of the
+untracked files within that directory is not ignored, then no deletions will
+occur within that directory and a warning message will appear. If these files
+are important, then reset your sparse-checkout definition so they are included,
+use `git add` and `git commit` to store them, then remove any remaining files
+manually to ensure Git can behave optimally.
+
 
 SUBMODULES
 ----------
index 83f38e31981420e3fbbb52ab00407cca6c7fceab..4a2c3e04081e27dd7cace80c3b63ab2cec843750 100644 (file)
@@ -207,26 +207,29 @@ show tracked paths:
 
 * ' ' = unmodified
 * 'M' = modified
+* 'T' = file type changed (regular file, symbolic link or submodule)
 * 'A' = added
 * 'D' = deleted
 * 'R' = renamed
-* 'C' = copied
+* 'C' = copied (if config option status.renames is set to "copies")
 * 'U' = updated but unmerged
 
 ....
 X          Y     Meaning
 -------------------------------------------------
         [AMD]   not updated
-M        [ MD]   updated in index
-A        [ MD]   added to index
+M        [ MTD]  updated in index
+T        [ MTD]  type changed in index
+A        [ MTD]  added to index
 D                deleted from index
-R        [ MD]   renamed in index
-C        [ MD]   copied in index
-[MARC]           index and work tree matches
-[ MARC]     M    work tree changed since index
-[ MARC]     D    deleted in work tree
-[ D]        R    renamed in work tree
-[ D]        C    copied in work tree
+R        [ MTD]  renamed in index
+C        [ MTD]  copied in index
+[MTARC]          index and work tree matches
+[ MTARC]    M    work tree changed since index
+[ MTARC]    T    type changed in work tree since index
+[ MTARC]    D    deleted in work tree
+           R    renamed in work tree
+           C    copied in work tree
 -------------------------------------------------
 D           D    unmerged, both deleted
 A           U    unmerged, added by us
@@ -363,7 +366,7 @@ Field       Meaning
 Unmerged entries have the following format; the first character is
 a "u" to distinguish from ordinary changed entries.
 
-    u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+    u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
 
 ....
 Field       Meaning
index d5776ffcfd5149d1d204973af7239db94d4a38c8..222b556d7a91fa7ce0925e634e6c9a4383a2e402 100644 (file)
@@ -678,7 +678,6 @@ config key: svn.authorsProg
 --strategy=<strategy>::
 -p::
 --rebase-merges::
---preserve-merges (DEPRECATED)::
        These are only used with the 'dcommit' and 'rebase' commands.
 +
 Passed directly to 'git rebase' when using 'dcommit' if a
index 9822c1eb1add168cdb30426bab04d925504d0205..8f87b23ea86a3dfecb2a736d943a6728b6c9f767 100644 (file)
@@ -36,14 +36,26 @@ OPTIONS
        This fits with the HTTP POST request processing model where
        a program may read the request, write a response, and must exit.
 
---advertise-refs::
-       Only the initial ref advertisement is output, and the program exits
-       immediately. This fits with the HTTP GET request model, where
-       no request content is received but a response must be produced.
+--http-backend-info-refs::
+       Used by linkgit:git-http-backend[1] to serve up
+       `$GIT_URL/info/refs?service=git-upload-pack` requests. See
+       "Smart Clients" in link:technical/http-protocol.html[the HTTP
+       transfer protocols] documentation and "HTTP Transport" in
+       link:technical/protocol-v2.html[the Git Wire Protocol, Version
+       2] documentation. Also understood by
+       linkgit:git-receive-pack[1].
 
 <directory>::
        The repository to sync from.
 
+ENVIRONMENT
+-----------
+
+`GIT_PROTOCOL`::
+       Internal variable used for handshaking the wire protocol. Server
+       admins may need to configure some transports to allow this
+       variable to be passed. See the discussion in linkgit:git[1].
+
 SEE ALSO
 --------
 linkgit:gitnamespaces[7]
index 95fe6f31b4f08710715e6296e9eadb8a8ea82a5b..d63c65e67d825d6aac6181de79689557bd76a019 100644 (file)
@@ -867,15 +867,16 @@ for full details.
        end user, to be recorded in the body of the reflog.
 
 `GIT_REF_PARANOIA`::
-       If set to `1`, include broken or badly named refs when iterating
-       over lists of refs. In a normal, non-corrupted repository, this
-       does nothing. However, enabling it may help git to detect and
-       abort some operations in the presence of broken refs. Git sets
-       this variable automatically when performing destructive
-       operations like linkgit:git-prune[1]. You should not need to set
-       it yourself unless you want to be paranoid about making sure
-       an operation has touched every ref (e.g., because you are
-       cloning a repository to make a backup).
+       If set to `0`, ignore broken or badly named refs when iterating
+       over lists of refs. Normally Git will try to include any such
+       refs, which may cause some operations to fail. This is usually
+       preferable, as potentially destructive operations (e.g.,
+       linkgit:git-prune[1]) are better off aborting rather than
+       ignoring broken refs (and thus considering the history they
+       point to as not worth saving). The default value is `1` (i.e.,
+       be paranoid about detecting and aborting all operations). You
+       should not normally need to set this to `0`, but it may be
+       useful when trying to salvage data from a corrupted repository.
 
 `GIT_ALLOW_PROTOCOL`::
        If set to a colon-separated list of protocols, behave as if
@@ -898,6 +899,21 @@ for full details.
        Contains a colon ':' separated list of keys with optional values
        'key[=value]'.  Presence of unknown keys and values must be
        ignored.
++
+Note that servers may need to be configured to allow this variable to
+pass over some transports. It will be propagated automatically when
+accessing local repositories (i.e., `file://` or a filesystem path), as
+well as over the `git://` protocol. For git-over-http, it should work
+automatically in most configurations, but see the discussion in
+linkgit:git-http-backend[1]. For git-over-ssh, the ssh server may need
+to be configured to allow clients to pass this variable (e.g., by using
+`AcceptEnv GIT_PROTOCOL` with OpenSSH).
++
+This configuration is optional. If the variable is not propagated, then
+clients will fall back to the original "v0" protocol (but may miss out
+on some performance improvements or features). This variable currently
+only affects clones and fetches; it is not yet used for pushes (but may
+be in the future).
 
 `GIT_OPTIONAL_LOCKS`::
        If set to `0`, Git will complete any requested operation without
index afdaeab8503c30e24119d4b10544ba86d201a96b..8c1f2d56751dce27a63440f551341140bd4c785b 100644 (file)
@@ -275,7 +275,7 @@ best to always use a regular merge commit.
 
 [[merge-two-revert-one]]
 If I make a change on two branches but revert it on one, why does the merge of those branches include the change?::
-       By default, when Git does a merge, it uses a strategy called the recursive
+       By default, when Git does a merge, it uses a strategy called the `ort`
        strategy, which does a fancy three-way merge.  In such a case, when Git
        performs the merge, it considers exactly three points: the two heads and a
        third point, called the _merge base_, which is usually the common ancestor of
index 86f277a9945fd395e86a417cc61173b5a379f445..61ec157c2f38b4b89039403c334edce10f8303d1 100644 (file)
@@ -144,7 +144,7 @@ endif::git-pull[]
        Use the given merge strategy; can be supplied more than
        once to specify them in the order they should be tried.
        If there is no `-s` option, a built-in list of strategies
-       is used instead (`recursive` when merging a single head,
+       is used instead (`ort` when merging a single head,
        `octopus` otherwise).
 
 -X <option>::
index 210f0f850b24f082037f6a653d6a1409c2f035c6..5fc54ec060b9638e11c66231eed6304de1772313 100644 (file)
@@ -6,21 +6,23 @@ backend 'merge strategies' to be chosen with `-s` option.  Some strategies
 can also take their own options, which can be passed by giving `-X<option>`
 arguments to `git merge` and/or `git pull`.
 
-recursive::
-       This can only resolve two heads using a 3-way merge
-       algorithm.  When there is more than one common
-       ancestor that can be used for 3-way merge, it creates a
-       merged tree of the common ancestors and uses that as
-       the reference tree for the 3-way merge.  This has been
-       reported to result in fewer merge conflicts without
-       causing mismerges by tests done on actual merge commits
-       taken from Linux 2.6 kernel development history.
-       Additionally this can detect and handle merges involving
-       renames.  It does not make use of detected copies.  This
-       is the default merge strategy when pulling or merging one
-       branch.
+ort::
+       This is the default merge strategy when pulling or merging one
+       branch.  This strategy can only resolve two heads using a
+       3-way merge algorithm.  When there is more than one common
+       ancestor that can be used for 3-way merge, it creates a merged
+       tree of the common ancestors and uses that as the reference
+       tree for the 3-way merge.  This has been reported to result in
+       fewer merge conflicts without causing mismerges by tests done
+       on actual merge commits taken from Linux 2.6 kernel
+       development history.  Additionally this strategy can detect
+       and handle merges involving renames.  It does not make use of
+       detected copies.  The name for this algorithm is an acronym
+       ("Ostensibly Recursive's Twin") and came from the fact that it
+       was written as a replacement for the previous default
+       algorithm, `recursive`.
 +
-The 'recursive' strategy can take the following options:
+The 'ort' strategy can take the following options:
 
 ours;;
        This option forces conflicting hunks to be auto-resolved cleanly by
@@ -36,16 +38,6 @@ theirs;;
        This is the opposite of 'ours'; note that, unlike 'ours', there is
        no 'theirs' merge strategy to confuse this merge option with.
 
-patience;;
-       Deprecated synonym for `diff-algorithm=patience`.
-
-diff-algorithm=[patience|minimal|histogram|myers];;
-       Use a different diff algorithm while merging, which can help
-       avoid mismerges that occur due to unimportant matching lines
-       (such as braces from distinct functions).  See also
-       linkgit:git-diff[1] `--diff-algorithm`.  Defaults to the
-       `diff.algorithm` config setting.
-
 ignore-space-change;;
 ignore-all-space;;
 ignore-space-at-eol;;
@@ -74,11 +66,6 @@ no-renormalize;;
        Disables the `renormalize` option.  This overrides the
        `merge.renormalize` configuration variable.
 
-no-renames;;
-       Turn off rename detection. This overrides the `merge.renames`
-       configuration variable.
-       See also linkgit:git-diff[1] `--no-renames`.
-
 find-renames[=<n>];;
        Turn on rename detection, optionally setting the similarity
        threshold.  This is the default. This overrides the
@@ -95,19 +82,39 @@ subtree[=<path>];;
        is prefixed (or stripped from the beginning) to make the shape of
        two trees to match.
 
-ort::
-       This is meant as a drop-in replacement for the `recursive`
-       algorithm (as reflected in its acronym -- "Ostensibly
-       Recursive's Twin"), and will likely replace it in the future.
-       It fixes corner cases that the `recursive` strategy handles
-       suboptimally, and is significantly faster in large
-       repositories -- especially when many renames are involved.
+recursive::
+       This can only resolve two heads using a 3-way merge
+       algorithm.  When there is more than one common
+       ancestor that can be used for 3-way merge, it creates a
+       merged tree of the common ancestors and uses that as
+       the reference tree for the 3-way merge.  This has been
+       reported to result in fewer merge conflicts without
+       causing mismerges by tests done on actual merge commits
+       taken from Linux 2.6 kernel development history.
+       Additionally this can detect and handle merges involving
+       renames.  It does not make use of detected copies.  This was
+       the default strategy for resolving two heads from Git v0.99.9k
+       until v2.33.0.
 +
-The `ort` strategy takes all the same options as `recursive`.
-However, it ignores three of those options: `no-renames`,
-`patience` and `diff-algorithm`.  It always runs with rename
-detection (it handles it much faster than `recursive` does), and
-it specifically uses `diff-algorithm=histogram`.
+The 'recursive' strategy takes the same options as 'ort'.  However,
+there are three additional options that 'ort' ignores (not documented
+above) that are potentially useful with the 'recursive' strategy:
+
+patience;;
+       Deprecated synonym for `diff-algorithm=patience`.
+
+diff-algorithm=[patience|minimal|histogram|myers];;
+       Use a different diff algorithm while merging, which can help
+       avoid mismerges that occur due to unimportant matching lines
+       (such as braces from distinct functions).  See also
+       linkgit:git-diff[1] `--diff-algorithm`.  Note that `ort`
+       specifically uses `diff-algorithm=histogram`, while `recursive`
+       defaults to the `diff.algorithm` config setting.
+
+no-renames;;
+       Turn off rename detection. This overrides the `merge.renames`
+       configuration variable.
+       See also linkgit:git-diff[1] `--no-renames`.
 
 resolve::
        This can only resolve two heads (i.e. the current branch
@@ -131,13 +138,13 @@ ours::
        the 'recursive' merge strategy.
 
 subtree::
-       This is a modified recursive strategy. When merging trees A and
+       This is a modified `ort` strategy. When merging trees A and
        B, if B corresponds to a subtree of A, B is first adjusted to
        match the tree structure of A, instead of reading the trees at
        the same level. This adjustment is also done to the common
        ancestor tree.
 
-With the strategies that use 3-way merge (including the default, 'recursive'),
+With the strategies that use 3-way merge (including the default, 'ort'),
 if a change is made on both branches, but later reverted on one of the
 branches, that change will be present in the merged result; some people find
 this behavior confusing.  It occurs because only the heads and the merge base
index 24569b06d19d1df690d180000a133008e381d4a3..b7bd27e1713be969b9e2fdc5a7c88cf1d1a5ebe8 100644 (file)
@@ -968,6 +968,11 @@ list of the missing objects.  Object IDs are prefixed with a ``?'' character.
        objects.
 endif::git-rev-list[]
 
+--unsorted-input::
+       Show commits in the order they were given on the command line instead
+       of sorting them in reverse chronological order by commit time. Cannot
+       be combined with `--no-walk` or `--no-walk=sorted`.
+
 --no-walk[=(sorted|unsorted)]::
        Only show the given commits, but do not traverse their ancestors.
        This has no effect if a range is specified. If the argument
@@ -975,7 +980,8 @@ endif::git-rev-list[]
        given on the command line. Otherwise (if `sorted` or no argument
        was given), the commits are shown in reverse chronological order
        by commit time.
-       Cannot be combined with `--graph`.
+       Cannot be combined with `--graph`. Cannot be combined with
+       `--unsorted-input` if `sorted` or no argument was given.
 
 --do-walk::
        Overrides a previous `--no-walk`.
index 5a60bbfa7f4141eca54ac921642dbc2c8ada7ab3..acfd5dc1d8b2f6103ddb556956b63f71b83a4d1b 100644 (file)
@@ -198,11 +198,6 @@ There are some macros to easily define options:
        The filename will be prefixed by passing the filename along with
        the prefix argument of `parse_options()` to `prefix_filename()`.
 
-`OPT_ARGUMENT(long, &int_var, description)`::
-       Introduce a long-option argument that will be kept in `argv[]`.
-       If this option was seen, `int_var` will be set to one (except
-       if a `NULL` pointer was passed).
-
 `OPT_NUMBER_CALLBACK(&var, description, func_ptr)`::
        Recognize numerical options like -123 and feed the integer as
        if it was an argument to the function given by `func_ptr`.
index 037a91cbcaf8fa5ec8238059f64574dcb86ac5d1..ef7fe02a8f7103cec56e29b986c0779e42cb7e68 100644 (file)
@@ -493,6 +493,20 @@ about specific error arguments.
 }
 ------------
 
+`"cmd_ancestry"`::
+       This event contains the text command name for the parent (and earlier
+       generations of parents) of the current process, in an array ordered from
+       nearest parent to furthest great-grandparent. It may not be implemented
+       on all platforms.
++
+------------
+{
+       "event":"cmd_ancestry",
+       ...
+       "ancestry":["bash","tmux: server","systemd"]
+}
+------------
+
 `"cmd_name"`::
        This event contains the command name for this git process
        and the hierarchy of commands from parent git processes.
@@ -599,6 +613,46 @@ stopping after the waitpid() and includes OS process creation overhead).
 So this time will be slightly larger than the atexit time reported by
 the child process itself.
 
+`"child_ready"`::
+       This event is generated after the current process has started
+       a background process and released all handles to it.
++
+------------
+{
+       "event":"child_ready",
+       ...
+       "child_id":2,
+       "pid":14708,     # child PID
+       "ready":"ready", # child ready state
+       "t_rel":0.110605 # observed run-time of child process
+}
+------------
++
+Note that the session-id of the child process is not available to
+the current/spawning process, so the child's PID is reported here as
+a hint for post-processing.  (But it is only a hint because the child
+process may be a shell script which doesn't have a session-id.)
++
+This event is generated after the child is started in the background
+and given a little time to boot up and start working.  If the child
+startups normally and while the parent is still waiting, the "ready"
+field will have the value "ready".
+If the child is too slow to start and the parent times out, the field
+will have the value "timeout".
+If the child starts but the parent is unable to probe it, the field
+will have the value "error".
++
+After the parent process emits this event, it will release all of its
+handles to the child process and treat the child as a background
+daemon.  So even if the child does eventually finish booting up,
+the parent will not emit an updated event.
++
+Note that the `t_rel` field contains the observed run time in seconds
+when the parent released the child process into the background.
+The child is assumed to be a long-running daemon process and may
+outlive the parent process.  So the parent's child event times should
+not be compared to the child's atexit times.
+
 `"exec"`::
        This event is generated before git attempts to `exec()`
        another command rather than starting a child process.
index f8c18a0f7aec2b1a6d1a9eeb336c080f1b397479..04b3ec2178574a5e7f24dadb19daf8effaafab23 100644 (file)
@@ -1,6 +1,44 @@
 GIT bitmap v1 format
 ====================
 
+== Pack and multi-pack bitmaps
+
+Bitmaps store reachability information about the set of objects in a packfile,
+or a multi-pack index (MIDX). The former is defined obviously, and the latter is
+defined as the union of objects in packs contained in the MIDX.
+
+A bitmap may belong to either one pack, or the repository's multi-pack index (if
+it exists). A repository may have at most one bitmap.
+
+An object is uniquely described by its bit position within a bitmap:
+
+       - If the bitmap belongs to a packfile, the __n__th bit corresponds to
+       the __n__th object in pack order. For a function `offset` which maps
+       objects to their byte offset within a pack, pack order is defined as
+       follows:
+
+               o1 <= o2 <==> offset(o1) <= offset(o2)
+
+       - If the bitmap belongs to a MIDX, the __n__th bit corresponds to the
+       __n__th object in MIDX order. With an additional function `pack` which
+       maps objects to the pack they were selected from by the MIDX, MIDX order
+       is defined as follows:
+
+               o1 <= o2 <==> pack(o1) <= pack(o2) /\ offset(o1) <= offset(o2)
+
+       The ordering between packs is done according to the MIDX's .rev file.
+       Notably, the preferred pack sorts ahead of all other packs.
+
+The on-disk representation (described below) of a bitmap is the same regardless
+of whether or not that bitmap belongs to a packfile or a MIDX. The only
+difference is the interpretation of the bits, which is described above.
+
+Certain bitmap extensions are supported (see: Appendix B). No extensions are
+required for bitmaps corresponding to packfiles. For bitmaps that correspond to
+MIDXs, both the bit-cache and rev-cache extensions are required.
+
+== On-disk format
+
        - A header appears at the beginning:
 
                4-byte signature: {'B', 'I', 'T', 'M'}
@@ -14,17 +52,19 @@ GIT bitmap v1 format
                        The following flags are supported:
 
                        - BITMAP_OPT_FULL_DAG (0x1) REQUIRED
-                       This flag must always be present. It implies that the bitmap
-                       index has been generated for a packfile with full closure
-                       (i.e. where every single object in the packfile can find
-                        its parent links inside the same packfile). This is a
-                       requirement for the bitmap index format, also present in JGit,
-                       that greatly reduces the complexity of the implementation.
+                       This flag must always be present. It implies that the
+                       bitmap index has been generated for a packfile or
+                       multi-pack index (MIDX) with full closure (i.e. where
+                       every single object in the packfile/MIDX can find its
+                       parent links inside the same packfile/MIDX). This is a
+                       requirement for the bitmap index format, also present in
+                       JGit, that greatly reduces the complexity of the
+                       implementation.
 
                        - BITMAP_OPT_HASH_CACHE (0x4)
                        If present, the end of the bitmap file contains
                        `N` 32-bit name-hash values, one per object in the
-                       pack. The format and meaning of the name-hash is
+                       pack/MIDX. The format and meaning of the name-hash is
                        described below.
 
                4-byte entry count (network byte order)
@@ -33,7 +73,8 @@ GIT bitmap v1 format
 
                20-byte checksum
 
-                       The SHA1 checksum of the pack this bitmap index belongs to.
+                       The SHA1 checksum of the pack/MIDX this bitmap index
+                       belongs to.
 
        - 4 EWAH bitmaps that act as type indexes
 
@@ -50,7 +91,7 @@ GIT bitmap v1 format
                        - Tags
 
                In each bitmap, the `n`th bit is set to true if the `n`th object
-               in the packfile is of that type.
+               in the packfile or multi-pack index is of that type.
 
                The obvious consequence is that the OR of all 4 bitmaps will result
                in a full set (all bits set), and the AND of all 4 bitmaps will
@@ -62,8 +103,9 @@ GIT bitmap v1 format
                Each entry contains the following:
 
                - 4-byte object position (network byte order)
-                       The position **in the index for the packfile** where the
-                       bitmap for this commit is found.
+                       The position **in the index for the packfile or
+                       multi-pack index** where the bitmap for this commit is
+                       found.
 
                - 1-byte XOR-offset
                        The xor offset used to compress this bitmap. For an entry
@@ -146,10 +188,11 @@ Name-hash cache
 ---------------
 
 If the BITMAP_OPT_HASH_CACHE flag is set, the end of the bitmap contains
-a cache of 32-bit values, one per object in the pack. The value at
+a cache of 32-bit values, one per object in the pack/MIDX. The value at
 position `i` is the hash of the pathname at which the `i`th object
-(counting in index order) in the pack can be found.  This can be fed
-into the delta heuristics to compare objects with similar pathnames.
+(counting in index or multi-pack index order) in the pack/MIDX can be found.
+This can be fed into the delta heuristics to compare objects with similar
+pathnames.
 
 The hash algorithm used is:
 
index 96d89ea9b226136603a041e96789709c979883fd..cc5126cfedaac2e14e3de48d9cafeb3c58bbb03e 100644 (file)
@@ -225,6 +225,9 @@ The client may send Extra Parameters (see
 Documentation/technical/pack-protocol.txt) as a colon-separated string
 in the Git-Protocol HTTP header.
 
+Uses the `--http-backend-info-refs` option to
+linkgit:git-upload-pack[1].
+
 Dumb Server Response
 ^^^^^^^^^^^^^^^^^^^^
 Dumb servers MUST respond with the dumb server reply format.
index fb688976c4c0337ef6850b9786daf2173f29e66c..86f40f24909a1ec485b1254b1fdde15a2d81c4cf 100644 (file)
@@ -36,7 +36,9 @@ Design Details
   directory of an alternate. It refers only to packfiles in that
   same directory.
 
-- The core.multiPackIndex config setting must be on to consume MIDX files.
+- The core.multiPackIndex config setting must be on (which is the
+  default) to consume MIDX files.  Setting it to `false` prevents
+  Git from reading a MIDX file, even if one exists.
 
 - The file format includes parameters for the object ID hash
   function, so a future change of hash algorithm does not require
@@ -71,14 +73,10 @@ Future Work
   still reducing the number of binary searches required for object
   lookups.
 
-- The reachability bitmap is currently paired directly with a single
-  packfile, using the pack-order as the object order to hopefully
-  compress the bitmaps well using run-length encoding. This could be
-  extended to pair a reachability bitmap with a multi-pack-index. If
-  the multi-pack-index is extended to store a "stable object order"
+- If the multi-pack-index is extended to store a "stable object order"
   (a function Order(hash) = integer that is constant for a given hash,
-  even as the multi-pack-index is updated) then a reachability bitmap
-  could point to a multi-pack-index and be updated independently.
+  even as the multi-pack-index is updated) then MIDX bitmaps could be
+  updated independently of the MIDX.
 
 - Packfiles can be marked as "special" using empty files that share
   the initial name but replace ".pack" with ".keep" or ".promisor".
index 1040d853198b87ef913ed9157301965dd4a50cd9..21e8258ccf39fe6e07638fddca457b437f7616f8 100644 (file)
@@ -42,7 +42,8 @@ Initial Client Request
 In general a client can request to speak protocol v2 by sending
 `version=2` through the respective side-channel for the transport being
 used which inevitably sets `GIT_PROTOCOL`.  More information can be
-found in `pack-protocol.txt` and `http-protocol.txt`.  In all cases the
+found in `pack-protocol.txt` and `http-protocol.txt`, as well as the
+`GIT_PROTOCOL` definition in `git.txt`. In all cases the
 response from the server is the capability advertisement.
 
 Git Transport
@@ -58,6 +59,8 @@ SSH and File Transport
 
 When using either the ssh:// or file:// transport, the GIT_PROTOCOL
 environment variable must be set explicitly to include "version=2".
+The server may need to be configured to allow this environment variable
+to pass.
 
 HTTP Transport
 ~~~~~~~~~~~~~~
@@ -81,6 +84,12 @@ A v2 server would reply:
 Subsequent requests are then made directly to the service
 `$GIT_URL/git-upload-pack`. (This works the same for git-receive-pack).
 
+Uses the `--http-backend-info-refs` option to
+linkgit:git-upload-pack[1].
+
+The server may need to be configured to pass this header's contents via
+the `GIT_PROTOCOL` variable. See the discussion in `git-http-backend.txt`.
+
 Capability Advertisement
 ------------------------
 
@@ -190,7 +199,11 @@ ls-refs takes in the following arguments:
        Show peeled tags.
     ref-prefix <prefix>
        When specified, only references having a prefix matching one of
-       the provided prefixes are displayed.
+       the provided prefixes are displayed. Multiple instances may be
+       given, in which case references matching any prefix will be
+       shown. Note that this is purely for optimization; a server MAY
+       show refs not matching the prefix if it chooses, and clients
+       should filter the result themselves.
 
 If the 'unborn' feature is advertised the following argument can be
 included in the client's request.
index 96240598e3f59f5bb86a4832bd5a5ced24ff63d0..865074bed4eacac0ba3da5edb8f0094dc411227c 100644 (file)
@@ -3190,7 +3190,7 @@ that *updated* thing--the old state that you added originally ends up
 not being pointed to by any commit or tree, so it's now a dangling blob
 object.
 
-Similarly, when the "recursive" merge strategy runs, and finds that
+Similarly, when the "ort" merge strategy runs, and finds that
 there are criss-cross merges and thus more than one merge base (which is
 fairly unusual, but it does happen), it will generate one temporary
 midway tree (or possibly even more, if you had lots of criss-crossing
index b1c0d4eb2e683d5f085784d65906ef1cce9734eb..b48559d654a178c12bf4d39b67e0b6def3f41ede 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.33.0
+DEF_VER=v2.33.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 66389ce05915d776e5c8ff49aea40e20bc40eb62..4140a3f5c8b6946ca821c2a876cd4390a1a05f1b 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -138,12 +138,15 @@ Issues of note:
          BLK_SHA1.  Also included is a version optimized for PowerPC
          (PPC_SHA1).
 
-       - "libcurl" library is used by git-http-fetch, git-fetch, and, if
-         the curl version >= 7.34.0, for git-imap-send.  You might also
-         want the "curl" executable for debugging purposes. If you do not
-         use http:// or https:// repositories, and do not want to put
-         patches into an IMAP mailbox, you do not have to have them
-         (use NO_CURL).
+       - "libcurl" library is used for fetching and pushing
+         repositories over http:// or https://, as well as by
+         git-imap-send if the curl version is >= 7.34.0. If you do
+         not need that functionality, use NO_CURL to build without
+         it.
+
+         Git requires version "7.19.4" or later of "libcurl" to build
+         without NO_CURL. This version requirement may be bumped in
+         the future.
 
        - "expat" library; git-http-push uses it for remote lock
          management over DAV.  Similar to "curl" above, this is optional
index 3df0ab5c7651197e3ba5c339eb61d3a3d4084494..381bed2c1d23b4bcc9d739b6fc34cc2cf80d87f3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -409,15 +409,6 @@ all::
 # Define NEEDS_LIBRT if your platform requires linking with librt (glibc version
 # before 2.17) for clock_gettime and CLOCK_MONOTONIC.
 #
-# Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
-# compiles the following initialization:
-#
-#   static const char s[] = ("FOO");
-#
-# and define it to "no" if you need to remove the parentheses () around the
-# constant.  The default is "auto", which means to use parentheses if your
-# compiler is detected to support it.
-#
 # Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
 #
 # Define HAVE_GETDELIM if your system has the getdelim() function.
@@ -465,6 +456,9 @@ all::
 # the global variable _wpgmptr containing the absolute path of the current
 # executable (this is the case on Windows).
 #
+# INSTALL_STRIP can be set to "-s" to strip binaries during installation,
+# if your $(INSTALL) command supports the option.
+#
 # Define GENERATE_COMPILATION_DATABASE to "yes" to generate JSON compilation
 # database entries during compilation if your compiler supports it, using the
 # `-MJ` flag. The JSON entries will be placed in the `compile_commands/`
@@ -495,10 +489,9 @@ all::
 #        setting this flag the exceptions are removed, and all of
 #        -Wextra is used.
 #
-#    pedantic:
+#    no-pedantic:
 #
-#        Enable -pedantic compilation. This also disables
-#        USE_PARENS_AROUND_GETTEXT_N to produce only relevant warnings.
+#        Disable -pedantic compilation.
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -616,7 +609,6 @@ SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
 SCRIPT_LIB += git-mergetool--lib
-SCRIPT_LIB += git-rebase--preserve-merges
 SCRIPT_LIB += git-sh-i18n
 SCRIPT_LIB += git-sh-setup
 
@@ -824,6 +816,10 @@ XDIFF_LIB = xdiff/lib.a
 
 GENERATED_H += command-list.h
 GENERATED_H += config-list.h
+GENERATED_H += hook-list.h
+
+.PHONY: generated-hdrs
+generated-hdrs: $(GENERATED_H)
 
 LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null || \
        $(FIND) . \
@@ -909,6 +905,7 @@ LIB_OBJS += hash-lookup.o
 LIB_OBJS += hashmap.o
 LIB_OBJS += help.o
 LIB_OBJS += hex.o
+LIB_OBJS += hook.o
 LIB_OBJS += ident.o
 LIB_OBJS += json-writer.o
 LIB_OBJS += kwset.o
@@ -1221,6 +1218,9 @@ PTHREAD_CFLAGS =
 SPARSE_FLAGS ?=
 SP_EXTRA_FLAGS = -Wno-universal-initializer
 
+# For informing GIT-BUILD-OPTIONS of the SANITIZE=leak target
+SANITIZE_LEAK =
+
 # For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will
 # usually result in less CPU usage at the cost of higher peak memory.
 # Setting it to 0 will feed all files in a single spatch invocation.
@@ -1265,6 +1265,7 @@ BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS
 endif
 ifneq ($(filter leak,$(SANITIZERS)),)
 BASIC_CFLAGS += -DSUPPRESS_ANNOTATED_LEAKS
+SANITIZE_LEAK = YesCompiledWithIt
 endif
 ifneq ($(filter address,$(SANITIZERS)),)
 NO_REGEX = NeededForASAN
@@ -1285,6 +1286,7 @@ endif
 
 ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
 dep_check = $(shell $(CC) $(ALL_CFLAGS) \
+       -Wno-pedantic \
        -c -MF /dev/null -MQ /dev/null -MMD -MP \
        -x c /dev/null -o /dev/null 2>&1; \
        echo $$?)
@@ -1310,6 +1312,7 @@ endif
 
 ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
 compdb_check = $(shell $(CC) $(ALL_CFLAGS) \
+       -Wno-pedantic \
        -c -MJ /dev/null \
        -x c /dev/null -o /dev/null 2>&1; \
        echo $$?)
@@ -1347,14 +1350,6 @@ ifneq (,$(SOCKLEN_T))
        BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T)
 endif
 
-ifeq (yes,$(USE_PARENS_AROUND_GETTEXT_N))
-       BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=1
-else
-ifeq (no,$(USE_PARENS_AROUND_GETTEXT_N))
-       BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
-endif
-endif
-
 ifeq ($(uname_S),Darwin)
        ifndef NO_FINK
                ifeq ($(shell test -d /sw/lib && echo y),y)
@@ -1436,15 +1431,8 @@ else
        REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)
        PROGRAM_OBJS += http-fetch.o
        PROGRAMS += $(REMOTE_CURL_NAMES)
-       curl_check := $(shell (echo 070908; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
-       ifeq "$(curl_check)" "070908"
-               ifndef NO_EXPAT
-                       PROGRAM_OBJS += http-push.o
-               else
-                       EXCLUDED_PROGRAMS += git-http-push
-               endif
-       else
-               EXCLUDED_PROGRAMS += git-http-push
+       ifndef NO_EXPAT
+               PROGRAM_OBJS += http-push.o
        endif
        curl_check := $(shell (echo 072200; $(CURL_CONFIG) --vernum | sed -e '/^70[BC]/s/^/0/') 2>/dev/null | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "072200"
@@ -1917,6 +1905,10 @@ ifneq ($(PROCFS_EXECUTABLE_PATH),)
        BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
 endif
 
+ifndef HAVE_PLATFORM_PROCINFO
+       COMPAT_OBJS += compat/stub/procinfo.o
+endif
+
 ifdef HAVE_NS_GET_EXECUTABLE_PATH
        BASIC_CFLAGS += -DHAVE_NS_GET_EXECUTABLE_PATH
 endif
@@ -2223,8 +2215,9 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
                $(filter %.o,$^) $(LIBS)
 
 help.sp help.s help.o: command-list.h
+hook.sp hook.s hook.o: hook-list.h
 
-builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX
+builtin/help.sp builtin/help.s builtin/help.o: config-list.h hook-list.h GIT-PREFIX
 builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
        '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
@@ -2247,15 +2240,17 @@ $(BUILT_INS): git$X
 config-list.h: generate-configlist.sh
 
 config-list.h: Documentation/*config.txt Documentation/config/*.txt
-       $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh \
-               >$@+ && mv $@+ $@
+       $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh >$@
 
 command-list.h: generate-cmdlist.sh command-list.txt
 
 command-list.h: $(wildcard Documentation/git*.txt)
        $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh \
                $(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \
-               command-list.txt >$@+ && mv $@+ $@
+               command-list.txt >$@
+
+hook-list.h: generate-hooklist.sh Documentation/githooks.txt
+       $(QUIET_GEN)$(SHELL_PATH) ./generate-hooklist.sh >$@
 
 SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
        $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
@@ -2517,13 +2512,6 @@ ifneq ($(dep_files_present),)
 include $(dep_files_present)
 endif
 else
-# Dependencies on header files, for platforms that do not support
-# the gcc -MMD option.
-#
-# Dependencies on automatically generated headers such as command-list.h
-# should _not_ be included here, since they are necessary even when
-# building an object for the first time.
-
 $(OBJECTS): $(LIB_H) $(GENERATED_H)
 endif
 
@@ -2648,7 +2636,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
        --keyword=__ --keyword=N__ --keyword="__n:1,2"
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH = $(SCRIPT_SH)
-LOCALIZED_SH += git-rebase--preserve-merges.sh
 LOCALIZED_SH += git-sh-setup.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
 
@@ -2745,19 +2732,25 @@ FIND_SOURCE_FILES = ( \
                | sed -e 's|^\./||' \
        )
 
-$(ETAGS_TARGET): FORCE
-       $(QUIET_GEN)$(RM) "$(ETAGS_TARGET)+" && \
-       $(FIND_SOURCE_FILES) | xargs etags -a -o "$(ETAGS_TARGET)+" && \
-       mv "$(ETAGS_TARGET)+" "$(ETAGS_TARGET)"
+FOUND_SOURCE_FILES = $(shell $(FIND_SOURCE_FILES))
+
+$(ETAGS_TARGET): $(FOUND_SOURCE_FILES)
+       $(QUIET_GEN)$(RM) $@+ && \
+       echo $(FOUND_SOURCE_FILES) | xargs etags -a -o $@+ && \
+       mv $@+ $@
+
+tags: $(FOUND_SOURCE_FILES)
+       $(QUIET_GEN)$(RM) $@+ && \
+       echo $(FOUND_SOURCE_FILES) | xargs ctags -a -o $@+ && \
+       mv $@+ $@
 
-tags: FORCE
-       $(QUIET_GEN)$(RM) tags+ && \
-       $(FIND_SOURCE_FILES) | xargs ctags -a -o tags+ && \
-       mv tags+ tags
+cscope.out: $(FOUND_SOURCE_FILES)
+       $(QUIET_GEN)$(RM) $@+ && \
+       echo $(FOUND_SOURCE_FILES) | xargs cscope -f$@+ -b && \
+       mv $@+ $@
 
-cscope:
-       $(RM) cscope*
-       $(FIND_SOURCE_FILES) | xargs cscope -b
+.PHONY: cscope
+cscope: cscope.out
 
 ### Detect prefix changes
 TRACK_PREFIX = $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
@@ -2807,6 +2800,7 @@ GIT-BUILD-OPTIONS: FORCE
        @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
        @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+
        @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+
+       @echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+
        @echo X=\'$(X)\' >>$@+
 ifdef TEST_OUTPUT_DIRECTORY
        @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
@@ -2847,6 +2841,11 @@ ifdef GIT_TEST_INDEX_VERSION
 endif
 ifdef GIT_TEST_PERL_FATAL_WARNINGS
        @echo GIT_TEST_PERL_FATAL_WARNINGS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_PERL_FATAL_WARNINGS)))'\' >>$@+
+endif
+ifdef RUNTIME_PREFIX
+       @echo RUNTIME_PREFIX=\'true\' >>$@+
+else
+       @echo RUNTIME_PREFIX=\'false\' >>$@+
 endif
        @if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi
 
@@ -2902,14 +2901,16 @@ check-sha1:: t/helper/test-tool$X
 
 SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ))
 
-$(SP_OBJ): %.sp: %.c GIT-CFLAGS FORCE
+$(SP_OBJ): %.sp: %.c %.o GIT-CFLAGS
        $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
-               $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $<
+               -Wsparse-error \
+               $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
+       >$@
 
-.PHONY: sparse $(SP_OBJ)
+.PHONY: sparse
 sparse: $(SP_OBJ)
 
-EXCEPT_HDRS := command-list.h config-list.h unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
 ifndef GCRYPT_SHA256
        EXCEPT_HDRS += sha256/gcrypt.h
 endif
@@ -2931,7 +2932,8 @@ hdr-check: $(HCO)
 style:
        git clang-format --style file --diff --extensions c,h
 
-check: config-list.h command-list.h
+.PHONY: check
+check: $(GENERATED_H)
        @if sparse; \
        then \
                echo >&2 "Use 'make sparse' instead"; \
@@ -2941,7 +2943,7 @@ check: config-list.h command-list.h
                exit 1; \
        fi
 
-FOUND_C_SOURCES = $(filter %.c,$(shell $(FIND_SOURCE_FILES)))
+FOUND_C_SOURCES = $(filter %.c,$(FOUND_SOURCE_FILES))
 COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES))
 
 %.cocci.patch: %.cocci $(COCCI_SOURCES)
@@ -2994,7 +2996,8 @@ mergetools_instdir = $(prefix)/$(mergetoolsdir)
 endif
 mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
 
-install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
+install_bindir_xprograms := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X))
+install_bindir_programs := $(install_bindir_xprograms) $(BINDIR_PROGRAMS_NO_X)
 
 .PHONY: profile-install profile-fast-install
 profile-install: profile
@@ -3003,12 +3006,17 @@ profile-install: profile
 profile-fast-install: profile-fast
        $(MAKE) install
 
+INSTALL_STRIP =
+
 install: all
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-       $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+       $(INSTALL) $(INSTALL_STRIP) $(PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+       $(INSTALL) $(SCRIPTS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
-       $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) $(INSTALL_STRIP) $(install_bindir_xprograms) '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) $(BINDIR_PROGRAMS_NO_X) '$(DESTDIR_SQ)$(bindir_SQ)'
+
 ifdef MSVC
        # We DO NOT install the individual foo.o.pdb files because they
        # have already been rolled up into the exe's pdb file.
@@ -3227,6 +3235,7 @@ clean: profile-clean coverage-clean cocciclean
        $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
        $(RM) $(FUZZ_PROGRAMS)
+       $(RM) $(SP_OBJ)
        $(RM) $(HCC)
        $(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json
        $(RM) -r po/build/
@@ -3265,7 +3274,7 @@ endif
 
 .PHONY: all install profile-clean cocciclean clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
-.PHONY: FORCE cscope
+.PHONY: FORCE
 
 ### Check documentation
 #
index 567659e5a35b719ddc61cdd7b9079cce48e4838d..6c94469548115b2f3265611116bdc1f13f64a382 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.33.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.34.0.txt
\ No newline at end of file
index 36ebdbdf7e2c8d6a5598e3e773b44d53b290ada7..6498ae196f1e1ed34001e2da87d64defa64c8507 100644 (file)
@@ -102,8 +102,12 @@ struct prefix_item_list {
        int *selected; /* for multi-selections */
        size_t min_length, max_length;
 };
-#define PREFIX_ITEM_LIST_INIT \
-       { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 }
+#define PREFIX_ITEM_LIST_INIT { \
+       .items = STRING_LIST_INIT_DUP, \
+       .sorted = STRING_LIST_INIT_NODUP, \
+       .min_length = 1, \
+       .max_length = 4, \
+}
 
 static void prefix_item_list_clear(struct prefix_item_list *list)
 {
index 337e8f342bcd30a44f577ae217e7aafb35a781e7..1dfc91d176702d4a99301a1299f4bb0d7a479ec2 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -4,37 +4,6 @@
 #include "help.h"
 #include "string-list.h"
 
-int advice_fetch_show_forced_updates = 1;
-int advice_push_update_rejected = 1;
-int advice_push_non_ff_current = 1;
-int advice_push_non_ff_matching = 1;
-int advice_push_already_exists = 1;
-int advice_push_fetch_first = 1;
-int advice_push_needs_force = 1;
-int advice_push_unqualified_ref_name = 1;
-int advice_push_ref_needs_update = 1;
-int advice_status_hints = 1;
-int advice_status_u_option = 1;
-int advice_status_ahead_behind_warning = 1;
-int advice_commit_before_merge = 1;
-int advice_reset_quiet_warning = 1;
-int advice_resolve_conflict = 1;
-int advice_sequencer_in_use = 1;
-int advice_implicit_identity = 1;
-int advice_detached_head = 1;
-int advice_set_upstream_failure = 1;
-int advice_object_name_warning = 1;
-int advice_amworkdir = 1;
-int advice_rm_hints = 1;
-int advice_add_embedded_repo = 1;
-int advice_ignored_hook = 1;
-int advice_waiting_for_editor = 1;
-int advice_graft_file_deprecated = 1;
-int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_submodule_alternate_error_strategy_die = 1;
-int advice_add_ignored_file = 1;
-int advice_add_empty_pathspec = 1;
-
 static int advice_use_color = -1;
 static char advice_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@ -62,50 +31,13 @@ static const char *advise_get_color(enum color_advice ix)
        return "";
 }
 
-static struct {
-       const char *name;
-       int *preference;
-} advice_config[] = {
-       { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates },
-       { "pushUpdateRejected", &advice_push_update_rejected },
-       { "pushNonFFCurrent", &advice_push_non_ff_current },
-       { "pushNonFFMatching", &advice_push_non_ff_matching },
-       { "pushAlreadyExists", &advice_push_already_exists },
-       { "pushFetchFirst", &advice_push_fetch_first },
-       { "pushNeedsForce", &advice_push_needs_force },
-       { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
-       { "pushRefNeedsUpdate", &advice_push_ref_needs_update },
-       { "statusHints", &advice_status_hints },
-       { "statusUoption", &advice_status_u_option },
-       { "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
-       { "commitBeforeMerge", &advice_commit_before_merge },
-       { "resetQuiet", &advice_reset_quiet_warning },
-       { "resolveConflict", &advice_resolve_conflict },
-       { "sequencerInUse", &advice_sequencer_in_use },
-       { "implicitIdentity", &advice_implicit_identity },
-       { "detachedHead", &advice_detached_head },
-       { "setUpstreamFailure", &advice_set_upstream_failure },
-       { "objectNameWarning", &advice_object_name_warning },
-       { "amWorkDir", &advice_amworkdir },
-       { "rmHints", &advice_rm_hints },
-       { "addEmbeddedRepo", &advice_add_embedded_repo },
-       { "ignoredHook", &advice_ignored_hook },
-       { "waitingForEditor", &advice_waiting_for_editor },
-       { "graftFileDeprecated", &advice_graft_file_deprecated },
-       { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
-       { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
-       { "addIgnoredFile", &advice_add_ignored_file },
-       { "addEmptyPathspec", &advice_add_empty_pathspec },
-
-       /* make this an alias for backward compatibility */
-       { "pushNonFastForward", &advice_push_update_rejected }
-};
-
 static struct {
        const char *key;
        int enabled;
 } advice_setting[] = {
        [ADVICE_ADD_EMBEDDED_REPO]                      = { "addEmbeddedRepo", 1 },
+       [ADVICE_ADD_EMPTY_PATHSPEC]                     = { "addEmptyPathspec", 1 },
+       [ADVICE_ADD_IGNORED_FILE]                       = { "addIgnoredFile", 1 },
        [ADVICE_AM_WORK_DIR]                            = { "amWorkDir", 1 },
        [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = { "checkoutAmbiguousRemoteBranchName", 1 },
        [ADVICE_COMMIT_BEFORE_MERGE]                    = { "commitBeforeMerge", 1 },
@@ -133,6 +65,7 @@ static struct {
        [ADVICE_RM_HINTS]                               = { "rmHints", 1 },
        [ADVICE_SEQUENCER_IN_USE]                       = { "sequencerInUse", 1 },
        [ADVICE_SET_UPSTREAM_FAILURE]                   = { "setUpstreamFailure", 1 },
+       [ADVICE_SKIPPED_CHERRY_PICKS]                   = { "skippedCherryPicks", 1 },
        [ADVICE_STATUS_AHEAD_BEHIND_WARNING]            = { "statusAheadBehindWarning", 1 },
        [ADVICE_STATUS_HINTS]                           = { "statusHints", 1 },
        [ADVICE_STATUS_U_OPTION]                        = { "statusUoption", 1 },
@@ -221,13 +154,6 @@ int git_default_advice_config(const char *var, const char *value)
        if (!skip_prefix(var, "advice.", &k))
                return 0;
 
-       for (i = 0; i < ARRAY_SIZE(advice_config); i++) {
-               if (strcasecmp(k, advice_config[i].name))
-                       continue;
-               *advice_config[i].preference = git_config_bool(var, value);
-               break;
-       }
-
        for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
                if (strcasecmp(k, advice_setting[i].key))
                        continue;
@@ -262,7 +188,7 @@ int error_resolve_conflict(const char *me)
                error(_("It is not possible to %s because you have unmerged files."),
                        me);
 
-       if (advice_resolve_conflict)
+       if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
                /*
                 * Message used both when 'git commit' fails and when
                 * other commands doing a merge do.
@@ -281,7 +207,7 @@ void NORETURN die_resolve_conflict(const char *me)
 void NORETURN die_conclude_merge(void)
 {
        error(_("You have not concluded your merge (MERGE_HEAD exists)."));
-       if (advice_resolve_conflict)
+       if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
                advise(_("Please, commit your changes before merging."));
        die(_("Exiting because of unfinished merge."));
 }
@@ -298,15 +224,16 @@ void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
        if (!pathspec_list->nr)
                return;
 
-       fprintf(stderr, _("The following pathspecs didn't match any"
-                         " eligible path, but they do match index\n"
-                         "entries outside the current sparse checkout:\n"));
+       fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
+                         "outside of your sparse-checkout definition, so will not be\n"
+                         "updated in the index:\n"));
        for_each_string_list_item(item, pathspec_list)
                fprintf(stderr, "%s\n", item->string);
 
        advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
-                         _("Disable or modify the sparsity rules if you intend"
-                           " to update such entries."));
+                         _("If you intend to update such entries, try one of the following:\n"
+                           "* Use the --sparse option.\n"
+                           "* Disable or modify the sparsity rules."));
 }
 
 void detach_advice(const char *new_name)
index a7227ca7031369cc6835dac8ad0e776d2f880411..601265fd1070da02246eae167a85e855b3979eae 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -5,37 +5,6 @@
 
 struct string_list;
 
-extern int advice_fetch_show_forced_updates;
-extern int advice_push_update_rejected;
-extern int advice_push_non_ff_current;
-extern int advice_push_non_ff_matching;
-extern int advice_push_already_exists;
-extern int advice_push_fetch_first;
-extern int advice_push_needs_force;
-extern int advice_push_unqualified_ref_name;
-extern int advice_push_ref_needs_update;
-extern int advice_status_hints;
-extern int advice_status_u_option;
-extern int advice_status_ahead_behind_warning;
-extern int advice_commit_before_merge;
-extern int advice_reset_quiet_warning;
-extern int advice_resolve_conflict;
-extern int advice_sequencer_in_use;
-extern int advice_implicit_identity;
-extern int advice_detached_head;
-extern int advice_set_upstream_failure;
-extern int advice_object_name_warning;
-extern int advice_amworkdir;
-extern int advice_rm_hints;
-extern int advice_add_embedded_repo;
-extern int advice_ignored_hook;
-extern int advice_waiting_for_editor;
-extern int advice_graft_file_deprecated;
-extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_submodule_alternate_error_strategy_die;
-extern int advice_add_ignored_file;
-extern int advice_add_empty_pathspec;
-
 /*
  * To add a new advice, you need to:
  * Define a new advice_type.
@@ -45,6 +14,8 @@ extern int advice_add_empty_pathspec;
  */
  enum advice_type {
        ADVICE_ADD_EMBEDDED_REPO,
+       ADVICE_ADD_EMPTY_PATHSPEC,
+       ADVICE_ADD_IGNORED_FILE,
        ADVICE_AM_WORK_DIR,
        ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
        ADVICE_COMMIT_BEFORE_MERGE,
@@ -75,6 +46,7 @@ extern int advice_add_empty_pathspec;
        ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
        ADVICE_UPDATE_SPARSE_PATH,
        ADVICE_WAITING_FOR_EDITOR,
+       ADVICE_SKIPPED_CHERRY_PICKS,
 };
 
 int git_default_advice_config(const char *var, const char *value);
diff --git a/attr.c b/attr.c
index d029e681f2880a7cbb48cd3272d0c962a3e1edcd..79adaa50ea1e099fa7f8bee28efa6f7f7d92223b 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -14,6 +14,7 @@
 #include "utf8.h"
 #include "quote.h"
 #include "thread-utils.h"
+#include "dir.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -744,6 +745,20 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
        if (!istate)
                return NULL;
 
+       /*
+        * The .gitattributes file only applies to files within its
+        * parent directory. In the case of cone-mode sparse-checkout,
+        * the .gitattributes file is sparse if and only if all paths
+        * within that directory are also sparse. Thus, don't load the
+        * .gitattributes file since it will not matter.
+        *
+        * In the case of a sparse index, it is critical that we don't go
+        * looking for a .gitattributes file, as doing so would cause the
+        * index to expand.
+        */
+       if (!path_in_cone_mode_sparse_checkout(path, istate))
+               return NULL;
+
        buf = read_blob_data_from_index(istate, path, NULL);
        if (!buf)
                return NULL;
index af2863d044b704f3b7e9d6617dbb2226d4c8a1fe..888949fba6b5d5301b49daff323f977daf9f4e01 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -23,7 +23,6 @@ static struct oid_array skipped_revs;
 static struct object_id *current_bad_oid;
 
 static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
-static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 
 static const char *term_bad;
 static const char *term_good;
@@ -728,7 +727,9 @@ static int is_expected_rev(const struct object_id *oid)
 static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout)
 {
        char bisect_rev_hex[GIT_MAX_HEXSZ + 1];
-       enum bisect_error res = BISECT_OK;
+       struct commit *commit;
+       struct pretty_print_context pp = {0};
+       struct strbuf commit_msg = STRBUF_INIT;
 
        oid_to_hex_r(bisect_rev_hex, bisect_rev);
        update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
@@ -738,24 +739,21 @@ static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int
                update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
                           UPDATE_REFS_DIE_ON_ERR);
        } else {
-               res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
-               if (res)
+               if (run_command_v_opt(argv_checkout, RUN_GIT_CMD))
                        /*
                         * Errors in `run_command()` itself, signaled by res < 0,
                         * and errors in the child process, signaled by res > 0
-                        * can both be treated as regular BISECT_FAILURE (-1).
+                        * can both be treated as regular BISECT_FAILED (-1).
                         */
-                       return -abs(res);
+                       return BISECT_FAILED;
        }
 
-       argv_show_branch[1] = bisect_rev_hex;
-       res = run_command_v_opt(argv_show_branch, RUN_GIT_CMD);
-       /*
-        * Errors in `run_command()` itself, signaled by res < 0,
-        * and errors in the child process, signaled by res > 0
-        * can both be treated as regular BISECT_FAILURE (-1).
-        */
-       return -abs(res);
+       commit = lookup_commit_reference(the_repository, bisect_rev);
+       format_commit_message(commit, "[%H] %s%n", &commit_msg, &pp);
+       fputs(commit_msg.buf, stdout);
+       strbuf_release(&commit_msg);
+
+       return BISECT_OK;
 }
 
 static struct commit *get_commit_reference(struct repository *r,
index 7a88a4861e7eb41e5415f141d4d143170c8ca5e0..07a46430b3846f0fa4595f9ea0f98c2523e8d5f1 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -271,7 +271,7 @@ void create_branch(struct repository *r,
        real_ref = NULL;
        if (get_oid_mb(start_name, &oid)) {
                if (explicit_tracking) {
-                       if (advice_set_upstream_failure) {
+                       if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
                                error(_(upstream_missing), start_name);
                                advise(_(upstream_advice));
                                exit(1);
index 16ecd5586f0beeae1e65efb06f25836723d8f4e9..8a58743ed63d039f762f56c18f5c6e6debe0faba 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -225,7 +225,6 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 int cmd_switch(int argc, const char **argv, const char *prefix);
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 int cmd_tag(int argc, const char **argv, const char *prefix);
-int cmd_tar_tree(int argc, const char **argv, const char *prefix);
 int cmd_unpack_file(int argc, const char **argv, const char *prefix);
 int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 int cmd_update_index(int argc, const char **argv, const char *prefix);
index c37c95b45b323d69326b9ebede6886dff685f9c2..ef6b619c45ed9e44266a04f425f6523d4d1da657 100644 (file)
@@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
 static int add_renormalize;
 static int pathspec_file_nul;
+static int include_sparse;
 static const char *pathspec_from_file;
 static int legacy_stash_p; /* support for the scripted `git stash` */
 
@@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
                struct cache_entry *ce = active_cache[i];
                int err;
 
-               if (ce_skip_worktree(ce))
+               if (!include_sparse &&
+                   (ce_skip_worktree(ce) ||
+                    !path_in_sparse_checkout(ce->name, &the_index)))
                        continue;
 
                if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
@@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q,
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                const char *path = p->one->path;
+
+               if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
+                       continue;
+
                switch (fix_unmerged_status(p, data)) {
                default:
                        die(_("unexpected diff status %c"), p->status);
@@ -144,12 +151,12 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
 {
        int i, retval = 0;
 
-       /* TODO: audit for interaction with sparse-index. */
-       ensure_full_index(&the_index);
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
 
-               if (ce_skip_worktree(ce))
+               if (!include_sparse &&
+                   (ce_skip_worktree(ce) ||
+                    !path_in_sparse_checkout(ce->name, &the_index)))
                        continue;
                if (ce_stage(ce))
                        continue; /* do not touch unmerged paths */
@@ -198,7 +205,10 @@ static int refresh(int verbose, const struct pathspec *pathspec)
                      _("Unstaged changes after refreshing the index:"));
        for (i = 0; i < pathspec->nr; i++) {
                if (!seen[i]) {
-                       if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) {
+                       const char *path = pathspec->items[i].original;
+
+                       if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
+                           !path_in_sparse_checkout(path, &the_index)) {
                                string_list_append(&only_match_skip_worktree,
                                                   pathspec->items[i].original);
                        } else {
@@ -376,6 +386,7 @@ static struct option builtin_add_options[] = {
        OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
        OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
        OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
+       OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
        OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
                   N_("override the executable bit of the listed files")),
        OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
@@ -417,6 +428,7 @@ static const char embedded_advice[] = N_(
 static void check_embedded_repo(const char *path)
 {
        struct strbuf name = STRBUF_INIT;
+       static int adviced_on_embedded_repo = 0;
 
        if (!warn_on_embedded_repo)
                return;
@@ -428,10 +440,10 @@ static void check_embedded_repo(const char *path)
        strbuf_strip_suffix(&name, "/");
 
        warning(_("adding embedded git repository: %s"), name.buf);
-       if (advice_add_embedded_repo) {
+       if (!adviced_on_embedded_repo &&
+           advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) {
                advise(embedded_advice, name.buf, name.buf);
-               /* there may be multiple entries; advise only once */
-               advice_add_embedded_repo = 0;
+               adviced_on_embedded_repo = 1;
        }
 
        strbuf_release(&name);
@@ -440,12 +452,13 @@ static void check_embedded_repo(const char *path)
 static int add_files(struct dir_struct *dir, int flags)
 {
        int i, exit_status = 0;
+       struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
 
        if (dir->ignored_nr) {
                fprintf(stderr, _(ignore_error));
                for (i = 0; i < dir->ignored_nr; i++)
                        fprintf(stderr, "%s\n", dir->ignored[i]->name);
-               if (advice_add_ignored_file)
+               if (advice_enabled(ADVICE_ADD_IGNORED_FILE))
                        advise(_("Use -f if you really want to add them.\n"
                                "Turn this message off by running\n"
                                "\"git config advice.addIgnoredFile false\""));
@@ -453,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags)
        }
 
        for (i = 0; i < dir->nr; i++) {
+               if (!include_sparse &&
+                   !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) {
+                       string_list_append(&matched_sparse_paths,
+                                          dir->entries[i]->name);
+                       continue;
+               }
                if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
                        if (!ignore_add_errors)
                                die(_("adding files failed"));
@@ -461,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags)
                        check_embedded_repo(dir->entries[i]->name);
                }
        }
+
+       if (matched_sparse_paths.nr) {
+               advise_on_updating_sparse_paths(&matched_sparse_paths);
+               exit_status = 1;
+       }
+
+       string_list_clear(&matched_sparse_paths, 0);
+
        return exit_status;
 }
 
@@ -526,6 +553,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
        require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 
        /*
@@ -551,7 +581,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        if (require_pathspec && pathspec.nr == 0) {
                fprintf(stderr, _("Nothing specified, nothing added.\n"));
-               if (advice_add_empty_pathspec)
+               if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC))
                        advise( _("Maybe you wanted to say 'git add .'?\n"
                                "Turn this message off by running\n"
                                "\"git config advice.addEmptyPathspec false\""));
@@ -622,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        if (seen[i])
                                continue;
 
-                       if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
+                       if (!include_sparse &&
+                           matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
                                string_list_append(&only_match_skip_worktree,
                                                   pathspec.items[i].original);
                                continue;
index c79e0167e98259494fdb13832d0ee63995311c08..8677ea2348ab5b8017fc82cd1f8b79e4a7d20122 100644 (file)
@@ -11,6 +11,7 @@
 #include "parse-options.h"
 #include "dir.h"
 #include "run-command.h"
+#include "hook.h"
 #include "quote.h"
 #include "tempfile.h"
 #include "lockfile.h"
@@ -1820,7 +1821,7 @@ static void am_run(struct am_state *state, int resume)
                        printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
                                linelen(state->msg), state->msg);
 
-                       if (advice_amworkdir)
+                       if (advice_enabled(ADVICE_AM_WORK_DIR))
                                advise(_("Use 'git am --show-current-patch=diff' to see the failed patch"));
 
                        die_user_resolve(state);
@@ -1848,7 +1849,6 @@ next:
         */
        if (!state->rebasing) {
                am_destroy(state);
-               close_object_store(the_repository->objects);
                run_auto_maintenance(state->quiet);
        }
 }
@@ -1918,7 +1918,8 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
        opts.dst_index = &the_index;
        opts.update = 1;
        opts.merge = 1;
-       opts.reset = reset;
+       opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
+       opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
        opts.fn = twoway_merge;
        init_tree_desc(&t[0], head->buffer, head->size);
        init_tree_desc(&t[1], remote->buffer, remote->size);
index f184eaeac6d0fd60ae8d255c458f6b873f2bd3cf..28a2e6a5750b737d18412cde48394b73e9a74c30 100644 (file)
@@ -18,10 +18,10 @@ static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
 static GIT_PATH_FUNC(git_path_head_name, "head-name")
 static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
 static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
+static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN")
 
 static const char * const git_bisect_helper_usage[] = {
        N_("git bisect--helper --bisect-reset [<commit>]"),
-       N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
        N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
        N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
                                            " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
@@ -30,6 +30,8 @@ static const char * const git_bisect_helper_usage[] = {
        N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
        N_("git bisect--helper --bisect-replay <filename>"),
        N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
+       N_("git bisect--helper --bisect-visualize"),
+       N_("git bisect--helper --bisect-run <cmd>..."),
        NULL
 };
 
@@ -143,6 +145,19 @@ static int append_to_file(const char *path, const char *format, ...)
        return res;
 }
 
+static int print_file_to_stdout(const char *path)
+{
+       int fd = open(path, O_RDONLY);
+       int ret = 0;
+
+       if (fd < 0)
+               return error_errno(_("cannot open file '%s' for reading"), path);
+       if (copy_fd(fd, 1) < 0)
+               ret = error_errno(_("failed to read '%s'"), path);
+       close(fd);
+       return ret;
+}
+
 static int check_term_format(const char *term, const char *orig_term)
 {
        int res;
@@ -1036,6 +1051,125 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **ar
        return res;
 }
 
+static int bisect_visualize(struct bisect_terms *terms, const char **argv, int argc)
+{
+       struct strvec args = STRVEC_INIT;
+       int flags = RUN_COMMAND_NO_STDIN, res = 0;
+       struct strbuf sb = STRBUF_INIT;
+
+       if (bisect_next_check(terms, NULL) != 0)
+               return BISECT_FAILED;
+
+       if (!argc) {
+               if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") ||
+                    getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) {
+                       strvec_push(&args, "gitk");
+               } else {
+                       strvec_push(&args, "log");
+                       flags |= RUN_GIT_CMD;
+               }
+       } else {
+               if (argv[0][0] == '-') {
+                       strvec_push(&args, "log");
+                       flags |= RUN_GIT_CMD;
+               } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git"))
+                       flags |= RUN_GIT_CMD;
+
+               strvec_pushv(&args, argv);
+       }
+
+       strvec_pushl(&args, "--bisect", "--", NULL);
+
+       strbuf_read_file(&sb, git_path_bisect_names(), 0);
+       sq_dequote_to_strvec(sb.buf, &args);
+       strbuf_release(&sb);
+
+       res = run_command_v_opt(args.v, flags);
+       strvec_clear(&args);
+       return res;
+}
+
+static int bisect_run(struct bisect_terms *terms, const char **argv, int argc)
+{
+       int res = BISECT_OK;
+       struct strbuf command = STRBUF_INIT;
+       struct strvec args = STRVEC_INIT;
+       struct strvec run_args = STRVEC_INIT;
+       const char *new_state;
+       int temporary_stdout_fd, saved_stdout;
+
+       if (bisect_next_check(terms, NULL))
+               return BISECT_FAILED;
+
+       if (argc)
+               sq_quote_argv(&command, argv);
+       else {
+               error(_("bisect run failed: no command provided."));
+               return BISECT_FAILED;
+       }
+
+       strvec_push(&run_args, command.buf);
+
+       while (1) {
+               strvec_clear(&args);
+
+               printf(_("running %s\n"), command.buf);
+               res = run_command_v_opt(run_args.v, RUN_USING_SHELL);
+
+               if (res < 0 || 128 <= res) {
+                       error(_("bisect run failed: exit code %d from"
+                               " '%s' is < 0 or >= 128"), res, command.buf);
+                       strbuf_release(&command);
+                       return res;
+               }
+
+               if (res == 125)
+                       new_state = "skip";
+               else if (!res)
+                       new_state = terms->term_good;
+               else
+                       new_state = terms->term_bad;
+
+               temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
+
+               if (temporary_stdout_fd < 0)
+                       return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run());
+
+               fflush(stdout);
+               saved_stdout = dup(1);
+               dup2(temporary_stdout_fd, 1);
+
+               res = bisect_state(terms, &new_state, 1);
+
+               fflush(stdout);
+               dup2(saved_stdout, 1);
+               close(saved_stdout);
+               close(temporary_stdout_fd);
+
+               print_file_to_stdout(git_path_bisect_run());
+
+               if (res == BISECT_ONLY_SKIPPED_LEFT)
+                       error(_("bisect run cannot continue any more"));
+               else if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) {
+                       printf(_("bisect run success"));
+                       res = BISECT_OK;
+               } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
+                       printf(_("bisect found first bad commit"));
+                       res = BISECT_OK;
+               } else if (res) {
+                       error(_("bisect run failed: 'git bisect--helper --bisect-state"
+                       " %s' exited with error code %d"), args.v[0], res);
+               } else {
+                       continue;
+               }
+
+               strbuf_release(&command);
+               strvec_clear(&args);
+               strvec_clear(&run_args);
+               return res;
+       }
+}
+
 int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
 {
        enum {
@@ -1048,7 +1182,9 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
                BISECT_STATE,
                BISECT_LOG,
                BISECT_REPLAY,
-               BISECT_SKIP
+               BISECT_SKIP,
+               BISECT_VISUALIZE,
+               BISECT_RUN,
        } cmdmode = 0;
        int res = 0, nolog = 0;
        struct option options[] = {
@@ -1070,6 +1206,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
                         N_("replay the bisection process from the given file"), BISECT_REPLAY),
                OPT_CMDMODE(0, "bisect-skip", &cmdmode,
                         N_("skip some commits for checkout"), BISECT_SKIP),
+               OPT_CMDMODE(0, "bisect-visualize", &cmdmode,
+                        N_("visualize the bisection"), BISECT_VISUALIZE),
+               OPT_CMDMODE(0, "bisect-run", &cmdmode,
+                        N_("use <cmd>... to automatically bisect."), BISECT_RUN),
                OPT_BOOL(0, "no-log", &nolog,
                         N_("no log for BISECT_WRITE")),
                OPT_END()
@@ -1089,12 +1229,6 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
                        return error(_("--bisect-reset requires either no argument or a commit"));
                res = bisect_reset(argc ? argv[0] : NULL);
                break;
-       case BISECT_NEXT_CHECK:
-               if (argc != 2 && argc != 3)
-                       return error(_("--bisect-next-check requires 2 or 3 arguments"));
-               set_terms(&terms, argv[1], argv[0]);
-               res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL);
-               break;
        case BISECT_TERMS:
                if (argc > 1)
                        return error(_("--bisect-terms requires 0 or 1 argument"));
@@ -1131,6 +1265,16 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
                get_terms(&terms);
                res = bisect_skip(&terms, argv, argc);
                break;
+       case BISECT_VISUALIZE:
+               get_terms(&terms);
+               res = bisect_visualize(&terms, argv, argc);
+               break;
+       case BISECT_RUN:
+               if (!argc)
+                       return error(_("bisect run failed: no command provided."));
+               get_terms(&terms);
+               res = bisect_run(&terms, argv, argc);
+               break;
        default:
                BUG("unknown subcommand %d", cmdmode);
        }
index 641523ff9af693bcbef8e77e66b3a5a607f7219f..1c31a996403903f94235ca3ea488c4edf5e11ef0 100644 (file)
@@ -101,6 +101,16 @@ struct commit_info {
        struct strbuf summary;
 };
 
+#define COMMIT_INFO_INIT { \
+       .author = STRBUF_INIT, \
+       .author_mail = STRBUF_INIT, \
+       .author_tz = STRBUF_INIT, \
+       .committer = STRBUF_INIT, \
+       .committer_mail = STRBUF_INIT, \
+       .committer_tz = STRBUF_INIT, \
+       .summary = STRBUF_INIT, \
+}
+
 /*
  * Parse author/committer line in the commit object buffer
  */
@@ -160,18 +170,6 @@ static void get_ac_line(const char *inbuf, const char *what,
        strbuf_add(name, namebuf, namelen);
 }
 
-static void commit_info_init(struct commit_info *ci)
-{
-
-       strbuf_init(&ci->author, 0);
-       strbuf_init(&ci->author_mail, 0);
-       strbuf_init(&ci->author_tz, 0);
-       strbuf_init(&ci->committer, 0);
-       strbuf_init(&ci->committer_mail, 0);
-       strbuf_init(&ci->committer_tz, 0);
-       strbuf_init(&ci->summary, 0);
-}
-
 static void commit_info_destroy(struct commit_info *ci)
 {
 
@@ -192,8 +190,6 @@ static void get_commit_info(struct commit *commit,
        const char *subject, *encoding;
        const char *message;
 
-       commit_info_init(ret);
-
        encoding = get_log_output_encoding();
        message = logmsg_reencode(commit, NULL, encoding);
        get_ac_line(message, "\nauthor ",
@@ -246,7 +242,7 @@ static void write_filename_info(struct blame_origin *suspect)
  */
 static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat)
 {
-       struct commit_info ci;
+       struct commit_info ci = COMMIT_INFO_INIT;
 
        if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
                return 0;
@@ -440,7 +436,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
        int cnt;
        const char *cp;
        struct blame_origin *suspect = ent->suspect;
-       struct commit_info ci;
+       struct commit_info ci = COMMIT_INFO_INIT;
        char hex[GIT_MAX_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
        const char *default_color = NULL, *color = NULL, *reset = NULL;
@@ -630,7 +626,7 @@ static void find_alignment(struct blame_scoreboard *sb, int *option)
                if (longest_file < num)
                        longest_file = num;
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-                       struct commit_info ci;
+                       struct commit_info ci = COMMIT_INFO_INIT;
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
                        if (*option & OUTPUT_SHOW_EMAIL)
index 03c7b7253a5861ebc759fca876fa231e4ad4cf6d..0b7ed82654af1fd52170e7a27fcc3f69fd2f4825 100644 (file)
@@ -427,7 +427,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
 
        memset(&array, 0, sizeof(array));
 
-       filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
+       filter_refs(&array, filter, filter->kind);
 
        if (filter->verbose)
                maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
index 06ed10dc92d5e59e81721b62293c011d66b2e186..9de32bc96e7a6f2171473b8d20f5cde3834e5c42 100644 (file)
@@ -3,7 +3,8 @@
 #include "strbuf.h"
 #include "help.h"
 #include "compat/compiler.h"
-#include "run-command.h"
+#include "hook.h"
+#include "hook-list.h"
 
 
 static void get_system_info(struct strbuf *sys_info)
@@ -41,39 +42,7 @@ static void get_system_info(struct strbuf *sys_info)
 
 static void get_populated_hooks(struct strbuf *hook_info, int nongit)
 {
-       /*
-        * NEEDSWORK: Doesn't look like there is a list of all possible hooks;
-        * so below is a transcription of `git help hooks`. Later, this should
-        * be replaced with some programmatically generated list (generated from
-        * doc or else taken from some library which tells us about all the
-        * hooks)
-        */
-       static const char *hook[] = {
-               "applypatch-msg",
-               "pre-applypatch",
-               "post-applypatch",
-               "pre-commit",
-               "pre-merge-commit",
-               "prepare-commit-msg",
-               "commit-msg",
-               "post-commit",
-               "pre-rebase",
-               "post-checkout",
-               "post-merge",
-               "pre-push",
-               "pre-receive",
-               "update",
-               "post-receive",
-               "post-update",
-               "push-to-checkout",
-               "pre-auto-gc",
-               "post-rewrite",
-               "sendemail-validate",
-               "fsmonitor-watchman",
-               "p4-pre-submit",
-               "post-index-change",
-       };
-       int i;
+       const char **p;
 
        if (nongit) {
                strbuf_addstr(hook_info,
@@ -81,9 +50,12 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit)
                return;
        }
 
-       for (i = 0; i < ARRAY_SIZE(hook); i++)
-               if (find_hook(hook[i]))
-                       strbuf_addf(hook_info, "%s\n", hook[i]);
+       for (p = hook_name_list; *p; p++) {
+               const char *hook = *p;
+
+               if (hook_exists(hook))
+                       strbuf_addf(hook_info, "%s\n", hook);
+       }
 }
 
 static const char * const bugreport_usage[] = {
index 053a51bea1b2ef5c6448deaa70698b7b0d164f44..5a85d7cd0fe48da00cd4d844576b29af7c3c9afc 100644 (file)
@@ -39,8 +39,6 @@ static const char * const builtin_bundle_unbundle_usage[] = {
   NULL
 };
 
-static int verbose;
-
 static int parse_options_cmd_bundle(int argc,
                const char **argv,
                const char* prefix,
@@ -162,10 +160,15 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
        struct bundle_header header = BUNDLE_HEADER_INIT;
        int bundle_fd = -1;
        int ret;
+       int progress = isatty(2);
+
        struct option options[] = {
+               OPT_BOOL(0, "progress", &progress,
+                        N_("show progress meter")),
                OPT_END()
        };
        char *bundle_file;
+       struct strvec extra_index_pack_args = STRVEC_INIT;
 
        argc = parse_options_cmd_bundle(argc, argv, prefix,
                        builtin_bundle_unbundle_usage, options, &bundle_file);
@@ -177,7 +180,11 @@ static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix)
        }
        if (!startup_info->have_repository)
                die(_("Need a repository to unbundle."));
-       ret = !!unbundle(the_repository, &header, bundle_fd, 0) ||
+       if (progress)
+               strvec_pushl(&extra_index_pack_args, "-v", "--progress-title",
+                            _("Unbundling objects"), NULL);
+       ret = !!unbundle(the_repository, &header, bundle_fd,
+                        &extra_index_pack_args) ||
                list_bundle_refs(&header, argc, argv);
        bundle_header_release(&header);
 cleanup:
@@ -188,7 +195,6 @@ cleanup:
 int cmd_bundle(int argc, const char **argv, const char *prefix)
 {
        struct option options[] = {
-               OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
                OPT_END()
        };
        int result;
index 243fe6844bc67a2140900decc066247dac6935a0..86fc03242b87c36784bb31a88eb21f6c727da893 100644 (file)
@@ -355,18 +355,34 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d
        }
 }
 
+/*
+ * If "pack" is non-NULL, then "offset" is the byte offset within the pack from
+ * which the object may be accessed (though note that we may also rely on
+ * data->oid, too). If "pack" is NULL, then offset is ignored.
+ */
 static void batch_object_write(const char *obj_name,
                               struct strbuf *scratch,
                               struct batch_options *opt,
-                              struct expand_data *data)
+                              struct expand_data *data,
+                              struct packed_git *pack,
+                              off_t offset)
 {
-       if (!data->skip_object_info &&
-           oid_object_info_extended(the_repository, &data->oid, &data->info,
-                                    OBJECT_INFO_LOOKUP_REPLACE) < 0) {
-               printf("%s missing\n",
-                      obj_name ? obj_name : oid_to_hex(&data->oid));
-               fflush(stdout);
-               return;
+       if (!data->skip_object_info) {
+               int ret;
+
+               if (pack)
+                       ret = packed_object_info(the_repository, pack, offset,
+                                                &data->info);
+               else
+                       ret = oid_object_info_extended(the_repository,
+                                                      &data->oid, &data->info,
+                                                      OBJECT_INFO_LOOKUP_REPLACE);
+               if (ret < 0) {
+                       printf("%s missing\n",
+                              obj_name ? obj_name : oid_to_hex(&data->oid));
+                       fflush(stdout);
+                       return;
+               }
        }
 
        strbuf_reset(scratch);
@@ -428,7 +444,7 @@ static void batch_one_object(const char *obj_name,
                return;
        }
 
-       batch_object_write(obj_name, scratch, opt, data);
+       batch_object_write(obj_name, scratch, opt, data, NULL, 0);
 }
 
 struct object_cb_data {
@@ -442,7 +458,8 @@ static int batch_object_cb(const struct object_id *oid, void *vdata)
 {
        struct object_cb_data *data = vdata;
        oidcpy(&data->expand->oid, oid);
-       batch_object_write(NULL, data->scratch, data->opt, data->expand);
+       batch_object_write(NULL, data->scratch, data->opt, data->expand,
+                          NULL, 0);
        return 0;
 }
 
@@ -463,21 +480,26 @@ static int collect_packed_object(const struct object_id *oid,
        return 0;
 }
 
-static int batch_unordered_object(const struct object_id *oid, void *vdata)
+static int batch_unordered_object(const struct object_id *oid,
+                                 struct packed_git *pack, off_t offset,
+                                 void *vdata)
 {
        struct object_cb_data *data = vdata;
 
        if (oidset_insert(data->seen, oid))
                return 0;
 
-       return batch_object_cb(oid, data);
+       oidcpy(&data->expand->oid, oid);
+       batch_object_write(NULL, data->scratch, data->opt, data->expand,
+                          pack, offset);
+       return 0;
 }
 
 static int batch_unordered_loose(const struct object_id *oid,
                                 const char *path,
                                 void *data)
 {
-       return batch_unordered_object(oid, data);
+       return batch_unordered_object(oid, NULL, 0, data);
 }
 
 static int batch_unordered_packed(const struct object_id *oid,
@@ -485,7 +507,9 @@ static int batch_unordered_packed(const struct object_id *oid,
                                  uint32_t pos,
                                  void *data)
 {
-       return batch_unordered_object(oid, data);
+       return batch_unordered_object(oid, pack,
+                                     nth_packed_object_offset(pack, pos),
+                                     data);
 }
 
 static int batch_objects(struct batch_options *opt)
@@ -529,6 +553,8 @@ static int batch_objects(struct batch_options *opt)
                if (has_promisor_remote())
                        warning("This repository uses promisor remotes. Some objects may not be loaded.");
 
+               read_replace_refs = 0;
+
                cb.opt = opt;
                cb.expand = &data;
                cb.scratch = &output;
index b23bc149d17b83686709c843e540f834e54620bc..cbf73b8c9f65ae2fdd1d96265bd4972aeaa9bd68 100644 (file)
@@ -646,7 +646,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
        opts.head_idx = -1;
        opts.update = worktree;
        opts.skip_unmerged = !worktree;
-       opts.reset = 1;
+       opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED :
+                               UNPACK_RESET_PROTECT_UNTRACKED;
+       opts.preserve_ignored = (!o->force && !o->overwrite_ignore);
        opts.merge = 1;
        opts.fn = oneway_merge;
        opts.verbose_update = o->show_progress;
@@ -746,11 +748,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                                       new_branch_info->commit ?
                                       &new_branch_info->commit->object.oid :
                                       &new_branch_info->oid, NULL);
-               if (opts->overwrite_ignore) {
-                       topts.dir = xcalloc(1, sizeof(*topts.dir));
-                       topts.dir->flags |= DIR_SHOW_IGNORED;
-                       setup_standard_excludes(topts.dir);
-               }
+               topts.preserve_ignored = !opts->overwrite_ignore;
                tree = parse_tree_indirect(old_branch_info->commit ?
                                           &old_branch_info->commit->object.oid :
                                           the_hash_algo->empty_tree);
@@ -918,7 +916,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                           REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old_branch_info->path &&
-                           advice_detached_head && !opts->force_detach)
+                           advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
                                detach_advice(new_branch_info->name);
                        describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
                }
@@ -1011,7 +1009,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
                sb.buf);
        strbuf_release(&sb);
 
-       if (advice_detached_head)
+       if (advice_enabled(ADVICE_DETACHED_HEAD))
                fprintf(stderr,
                        Q_(
                        /* The singular version */
@@ -1182,7 +1180,7 @@ static const char *parse_remote_branch(const char *arg,
        }
 
        if (!remote && num_matches > 1) {
-           if (advice_checkout_ambiguous_remote_branch_name) {
+           if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
                    advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
                             "you can do so by fully qualifying the name with the --track option:\n"
                             "\n"
index 7743dc07d2bdb4198d0b6219ab6a0d17e773b37d..559acf9e03685e5b7f1fa4936685cc6570d716fc 100644 (file)
@@ -217,120 +217,6 @@ static char *get_repo_path(const char *repo, int *is_bundle)
        return canon;
 }
 
-static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
-{
-       const char *end = repo + strlen(repo), *start, *ptr;
-       size_t len;
-       char *dir;
-
-       /*
-        * Skip scheme.
-        */
-       start = strstr(repo, "://");
-       if (start == NULL)
-               start = repo;
-       else
-               start += 3;
-
-       /*
-        * Skip authentication data. The stripping does happen
-        * greedily, such that we strip up to the last '@' inside
-        * the host part.
-        */
-       for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
-               if (*ptr == '@')
-                       start = ptr + 1;
-       }
-
-       /*
-        * Strip trailing spaces, slashes and /.git
-        */
-       while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
-               end--;
-       if (end - start > 5 && is_dir_sep(end[-5]) &&
-           !strncmp(end - 4, ".git", 4)) {
-               end -= 5;
-               while (start < end && is_dir_sep(end[-1]))
-                       end--;
-       }
-
-       /*
-        * Strip trailing port number if we've got only a
-        * hostname (that is, there is no dir separator but a
-        * colon). This check is required such that we do not
-        * strip URI's like '/foo/bar:2222.git', which should
-        * result in a dir '2222' being guessed due to backwards
-        * compatibility.
-        */
-       if (memchr(start, '/', end - start) == NULL
-           && memchr(start, ':', end - start) != NULL) {
-               ptr = end;
-               while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
-                       ptr--;
-               if (start < ptr && ptr[-1] == ':')
-                       end = ptr - 1;
-       }
-
-       /*
-        * Find last component. To remain backwards compatible we
-        * also regard colons as path separators, such that
-        * cloning a repository 'foo:bar.git' would result in a
-        * directory 'bar' being guessed.
-        */
-       ptr = end;
-       while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
-               ptr--;
-       start = ptr;
-
-       /*
-        * Strip .{bundle,git}.
-        */
-       len = end - start;
-       strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
-
-       if (!len || (len == 1 && *start == '/'))
-               die(_("No directory name could be guessed.\n"
-                     "Please specify a directory on the command line"));
-
-       if (is_bare)
-               dir = xstrfmt("%.*s.git", (int)len, start);
-       else
-               dir = xstrndup(start, len);
-       /*
-        * Replace sequences of 'control' characters and whitespace
-        * with one ascii space, remove leading and trailing spaces.
-        */
-       if (*dir) {
-               char *out = dir;
-               int prev_space = 1 /* strip leading whitespace */;
-               for (end = dir; *end; ++end) {
-                       char ch = *end;
-                       if ((unsigned char)ch < '\x20')
-                               ch = '\x20';
-                       if (isspace(ch)) {
-                               if (prev_space)
-                                       continue;
-                               prev_space = 1;
-                       } else
-                               prev_space = 0;
-                       *out++ = ch;
-               }
-               *out = '\0';
-               if (out > dir && prev_space)
-                       out[-1] = '\0';
-       }
-       return dir;
-}
-
-static void strip_trailing_slashes(char *dir)
-{
-       char *end = dir + strlen(dir);
-
-       while (dir < end - 1 && is_dir_sep(end[-1]))
-               end--;
-       *end = '\0';
-}
-
 static int add_one_reference(struct string_list_item *item, void *cb_data)
 {
        struct strbuf err = STRBUF_INIT;
@@ -657,7 +543,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
        }
 }
 
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
@@ -668,13 +554,11 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
         */
        while (ref && !ref->peer_ref)
                ref = ref->next;
-       /* Returning -1 notes "end of list" to the caller. */
        if (!ref)
-               return -1;
+               return NULL;
 
-       oidcpy(oid, &ref->old_oid);
        *rm = ref->next;
-       return 0;
+       return &ref->old_oid;
 }
 
 static void update_remote_refs(const struct ref *refs,
@@ -786,7 +670,7 @@ static int checkout(int submodule_progress)
                return 0;
        }
        if (!strcmp(head, "HEAD")) {
-               if (advice_detached_head)
+               if (advice_enabled(ADVICE_DETACHED_HEAD))
                        detach_advice(oid_to_hex(&oid));
                FREE_AND_NULL(head);
        } else {
@@ -803,6 +687,7 @@ static int checkout(int submodule_progress)
        opts.update = 1;
        opts.merge = 1;
        opts.clone = 1;
+       opts.preserve_ignored = 0;
        opts.fn = oneway_merge;
        opts.verbose_update = (option_verbosity >= 0);
        opts.src_index = &the_index;
@@ -1041,8 +926,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (argc == 2)
                dir = xstrdup(argv[1]);
        else
-               dir = guess_dir_name(repo_name, is_bundle, option_bare);
-       strip_trailing_slashes(dir);
+               dir = git_url_basename(repo_name, is_bundle, option_bare);
+       strip_dir_trailing_slashes(dir);
 
        dest_exists = path_exists(dir);
        if (dest_exists && !is_empty_dir(dir))
@@ -1114,6 +999,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (option_recurse_submodules.nr > 0) {
                struct string_list_item *item;
                struct strbuf sb = STRBUF_INIT;
+               int val;
 
                /* remove duplicates */
                string_list_sort(&option_recurse_submodules);
@@ -1130,6 +1016,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                                           strbuf_detach(&sb, NULL));
                }
 
+               if (!git_config_get_bool("submodule.stickyRecursiveClone", &val) &&
+                   val)
+                       string_list_append(&option_config, "submodule.recurse=true");
+
                if (option_required_reference.nr &&
                    option_optional_reference.nr)
                        die(_("clone --recursive is not compatible with "
index cd8631522167e1c1f23da9038bf58ce87c17fa94..3c3de3a156f1e2afc2b4952cf38e8d1136ad4716 100644 (file)
@@ -9,26 +9,29 @@
 #include "progress.h"
 #include "tag.h"
 
-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[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
-          "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
-          "<split options>"),
+#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \
+       N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]")
+
+#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \
+       N_("git commit-graph write [--object-dir <objdir>] [--append] " \
+          "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " \
+          "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " \
+          "<split options>")
+
+static const char * builtin_commit_graph_verify_usage[] = {
+       BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
        NULL
 };
 
-static const char * const builtin_commit_graph_verify_usage[] = {
-       N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
+static const char * builtin_commit_graph_write_usage[] = {
+       BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
        NULL
 };
 
-static const char * const builtin_commit_graph_write_usage[] = {
-       N_("git commit-graph write [--object-dir <objdir>] [--append] "
-          "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
-          "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
-          "<split options>"),
-       NULL
+static char const * const builtin_commit_graph_usage[] = {
+       BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
+       BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
+       NULL,
 };
 
 static struct opts_commit_graph {
@@ -43,26 +46,16 @@ static struct opts_commit_graph {
        int enable_changed_paths;
 } opts;
 
-static struct object_directory *find_odb(struct repository *r,
-                                        const char *obj_dir)
-{
-       struct object_directory *odb;
-       char *obj_dir_real = real_pathdup(obj_dir, 1);
-       struct strbuf odb_path_real = STRBUF_INIT;
-
-       prepare_alt_odb(r);
-       for (odb = r->objects->odb; odb; odb = odb->next) {
-               strbuf_realpath(&odb_path_real, odb->path, 1);
-               if (!strcmp(obj_dir_real, odb_path_real.buf))
-                       break;
-       }
-
-       free(obj_dir_real);
-       strbuf_release(&odb_path_real);
+static struct option common_opts[] = {
+       OPT_STRING(0, "object-dir", &opts.obj_dir,
+                  N_("dir"),
+                  N_("the object directory to store the graph")),
+       OPT_END()
+};
 
-       if (!odb)
-               die(_("could not find object directory matching %s"), obj_dir);
-       return odb;
+static struct option *add_common_options(struct option *to)
+{
+       return parse_options_concat(common_opts, to);
 }
 
 static int graph_verify(int argc, const char **argv)
@@ -76,21 +69,22 @@ static int graph_verify(int argc, const char **argv)
        int flags = 0;
 
        static struct option builtin_commit_graph_verify_options[] = {
-               OPT_STRING(0, "object-dir", &opts.obj_dir,
-                          N_("dir"),
-                          N_("the object directory to store the graph")),
                OPT_BOOL(0, "shallow", &opts.shallow,
                         N_("if the commit-graph is split, only verify the tip file")),
-               OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
+               OPT_BOOL(0, "progress", &opts.progress,
+                        N_("force progress reporting")),
                OPT_END(),
        };
+       struct option *options = add_common_options(builtin_commit_graph_verify_options);
 
        trace2_cmd_mode("verify");
 
        opts.progress = isatty(2);
        argc = parse_options(argc, argv, NULL,
-                            builtin_commit_graph_verify_options,
+                            options,
                             builtin_commit_graph_verify_usage, 0);
+       if (argc)
+               usage_with_options(builtin_commit_graph_verify_usage, options);
 
        if (!opts.obj_dir)
                opts.obj_dir = get_object_directory();
@@ -106,6 +100,7 @@ static int graph_verify(int argc, const char **argv)
                die_errno(_("Could not open commit-graph '%s'"), graph_name);
 
        FREE_AND_NULL(graph_name);
+       FREE_AND_NULL(options);
 
        if (open_ok)
                graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
@@ -206,9 +201,6 @@ static int graph_write(int argc, const char **argv)
        struct progress *progress = NULL;
 
        static struct option builtin_commit_graph_write_options[] = {
-               OPT_STRING(0, "object-dir", &opts.obj_dir,
-                       N_("dir"),
-                       N_("the object directory to store the graph")),
                OPT_BOOL(0, "reachable", &opts.reachable,
                        N_("start walk at all refs")),
                OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@@ -219,7 +211,6 @@ static int graph_write(int argc, const char **argv)
                        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_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
                        N_("allow writing an incremental commit-graph file"),
                        PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
@@ -233,8 +224,11 @@ static int graph_write(int argc, const char **argv)
                OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters,
                        NULL, N_("maximum number of changed-path Bloom filters to compute"),
                        0, write_option_max_new_filters),
+               OPT_BOOL(0, "progress", &opts.progress,
+                        N_("force progress reporting")),
                OPT_END(),
        };
+       struct option *options = add_common_options(builtin_commit_graph_write_options);
 
        opts.progress = isatty(2);
        opts.enable_changed_paths = -1;
@@ -248,8 +242,10 @@ static int graph_write(int argc, const char **argv)
        git_config(git_commit_graph_write_config, &opts);
 
        argc = parse_options(argc, argv, NULL,
-                            builtin_commit_graph_write_options,
+                            options,
                             builtin_commit_graph_write_usage, 0);
+       if (argc)
+               usage_with_options(builtin_commit_graph_write_usage, options);
 
        if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
                die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
@@ -304,6 +300,7 @@ static int graph_write(int argc, const char **argv)
                result = 1;
 
 cleanup:
+       FREE_AND_NULL(options);
        string_list_clear(&pack_indexes, 0);
        strbuf_release(&buf);
        return result;
@@ -311,32 +308,25 @@ cleanup:
 
 int cmd_commit_graph(int argc, const char **argv, const char *prefix)
 {
-       static struct option builtin_commit_graph_options[] = {
-               OPT_STRING(0, "object-dir", &opts.obj_dir,
-                       N_("dir"),
-                       N_("the object directory to store the graph")),
-               OPT_END(),
-       };
-
-       if (argc == 2 && !strcmp(argv[1], "-h"))
-               usage_with_options(builtin_commit_graph_usage,
-                                  builtin_commit_graph_options);
+       struct option *builtin_commit_graph_options = common_opts;
 
        git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix,
                             builtin_commit_graph_options,
                             builtin_commit_graph_usage,
                             PARSE_OPT_STOP_AT_NON_OPTION);
+       if (!argc)
+               goto usage;
 
        save_commit_buffer = 0;
 
-       if (argc > 0) {
-               if (!strcmp(argv[0], "verify"))
-                       return graph_verify(argc, argv);
-               if (!strcmp(argv[0], "write"))
-                       return graph_write(argc, argv);
-       }
+       if (!strcmp(argv[0], "verify"))
+               return graph_verify(argc, argv);
+       else if (argc && !strcmp(argv[0], "write"))
+               return graph_write(argc, argv);
 
+       error(_("unrecognized subcommand: %s"), argv[0]);
+usage:
        usage_with_options(builtin_commit_graph_usage,
                           builtin_commit_graph_options);
 }
index 7c9b1e7be3adee271d1f910a36023b0c9a974631..883c16256c87f75c8367573ba42a8ee8e04346a7 100644 (file)
@@ -19,6 +19,7 @@
 #include "revision.h"
 #include "wt-status.h"
 #include "run-command.h"
+#include "hook.h"
 #include "refs.h"
 #include "log-tree.h"
 #include "strbuf.h"
@@ -203,7 +204,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn)
        init_diff_ui_defaults();
        git_config(fn, s);
        determine_whence(s);
-       s->hints = advice_status_hints; /* must come after git_config() */
+       s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */
 }
 
 static void rollback_index_files(void)
@@ -1033,7 +1034,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         */
        if (!committable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(current_head))) {
-               s->hints = advice_status_hints;
+               s->hints = advice_enabled(ADVICE_STATUS_HINTS);
                s->display_comment_prefix = old_display_comment_prefix;
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
@@ -1051,7 +1052,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                return 0;
        }
 
-       if (!no_verify && find_hook("pre-commit")) {
+       if (!no_verify && hook_exists("pre-commit")) {
                /*
                 * Re-read the index as pre-commit hook could have updated it,
                 * and write it out as a tree.  We must do this before we invoke
index 865fddd6ce8535522e540c3302c60c1ec19737a7..542d8d02b2b00a8675d6bff9245019d0251d043f 100644 (file)
@@ -575,7 +575,7 @@ static int get_urlmatch(const char *var, const char *url)
        int ret;
        char *section_tail;
        struct string_list_item *item;
-       struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+       struct urlmatch_config config = URLMATCH_CONFIG_INIT;
        struct string_list values = STRING_LIST_INIT_DUP;
 
        config.collect_fn = urlmatch_collect_fn;
index 76a6ba37223fa94347192bdd7762da29cf6de920..78c02ad53192323e8237a3b0555b4f4f1b4dbceb 100644 (file)
 #define FLAG_SPAWN 0x1
 #define FLAG_RELAY 0x2
 
+#ifdef GIT_WINDOWS_NATIVE
+
+static int connection_closed(int error)
+{
+       return (error == EINVAL);
+}
+
+static int connection_fatally_broken(int error)
+{
+       return (error != ENOENT) && (error != ENETDOWN);
+}
+
+#else
+
+static int connection_closed(int error)
+{
+       return (error == ECONNRESET);
+}
+
+static int connection_fatally_broken(int error)
+{
+       return (error != ENOENT) && (error != ECONNREFUSED);
+}
+
+#endif
+
 static int send_request(const char *socket, const struct strbuf *out)
 {
        int got_data = 0;
@@ -28,7 +54,7 @@ static int send_request(const char *socket, const struct strbuf *out)
                int r;
 
                r = read_in_full(fd, in, sizeof(in));
-               if (r == 0 || (r < 0 && errno == ECONNRESET))
+               if (r == 0 || (r < 0 && connection_closed(errno)))
                        break;
                if (r < 0)
                        die_errno("read error from cache daemon");
@@ -75,7 +101,7 @@ static void do_cache(const char *socket, const char *action, int timeout,
        }
 
        if (send_request(socket, &buf) < 0) {
-               if (errno != ENOENT && errno != ECONNREFUSED)
+               if (connection_fatally_broken(errno))
                        die_errno("unable to connect to cache daemon");
                if (flags & FLAG_SPAWN) {
                        spawn_daemon(socket);
@@ -90,7 +116,7 @@ static char *get_socket_path(void)
 {
        struct stat sb;
        char *old_dir, *socket;
-       old_dir = expand_user_path("~/.git-credential-cache", 0);
+       old_dir = interpolate_path("~/.git-credential-cache", 0);
        if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
                socket = xstrfmt("%s/socket", old_dir);
        else
index ae3c1ba75fe60306f8819b1038660e73749aa83b..62a4f3c26531432da689dabe38b724c6e5484c7d 100644 (file)
@@ -173,7 +173,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix)
        if (file) {
                string_list_append(&fns, file);
        } else {
-               if ((file = expand_user_path("~/.git-credentials", 0)))
+               if ((file = interpolate_path("~/.git-credentials", 0)))
                        string_list_append_nodup(&fns, file);
                file = xdg_config_home("credentials");
                if (file)
index 90c0bfcd88c5d7bbc5399c7e32e20ea6ec6d8a97..4931c108451721ebd49a3009adb431dbe41eb662 100644 (file)
@@ -252,16 +252,6 @@ static void changed_files(struct hashmap *result, const char *index_path,
        strbuf_release(&buf);
 }
 
-static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
-{
-       struct strbuf buf = STRBUF_INIT;
-       strbuf_addstr(&buf, tmpdir);
-       remove_dir_recursively(&buf, 0);
-       if (exit_code)
-               warning(_("failed: %d"), exit_code);
-       exit(exit_code);
-}
-
 static int ensure_leading_directories(char *path)
 {
        switch (safe_create_leading_directories(path)) {
@@ -330,19 +320,44 @@ static int checkout_path(unsigned mode, struct object_id *oid,
        return ret;
 }
 
+static void write_file_in_directory(struct strbuf *dir, size_t dir_len,
+                       const char *path, const char *content)
+{
+       add_path(dir, dir_len, path);
+       ensure_leading_directories(dir->buf);
+       unlink(dir->buf);
+       write_file(dir->buf, "%s", content);
+}
+
+/* Write the file contents for the left and right sides of the difftool
+ * dir-diff representation for submodules and symlinks. Symlinks and submodules
+ * are written as regular text files so that external diff tools can diff them
+ * as text files, resulting in behavior that is analogous to to what "git diff"
+ * displays for symlink and submodule diffs.
+ */
+static void write_standin_files(struct pair_entry *entry,
+                       struct strbuf *ldir, size_t ldir_len,
+                       struct strbuf *rdir, size_t rdir_len)
+{
+       if (*entry->left)
+               write_file_in_directory(ldir, ldir_len, entry->path, entry->left);
+       if (*entry->right)
+               write_file_in_directory(rdir, rdir_len, entry->path, entry->right);
+}
+
 static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
-                       int argc, const char **argv)
+                       struct child_process *child)
 {
-       char tmpdir[PATH_MAX];
        struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
        struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
        struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
        struct strbuf wtdir = STRBUF_INIT;
-       char *lbase_dir, *rbase_dir;
+       struct strbuf tmpdir = STRBUF_INIT;
+       char *lbase_dir = NULL, *rbase_dir = NULL;
        size_t ldir_len, rdir_len, wtdir_len;
        const char *workdir, *tmp;
        int ret = 0, i;
-       FILE *fp;
+       FILE *fp = NULL;
        struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,
                                                        NULL);
        struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);
@@ -351,8 +366,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        struct pair_entry *entry;
        struct index_state wtindex;
        struct checkout lstate, rstate;
-       int rc, flags = RUN_GIT_CMD, err = 0;
-       struct child_process child = CHILD_PROCESS_INIT;
+       int flags = RUN_GIT_CMD, err = 0;
        const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
        struct hashmap wt_modified, tmp_modified;
        int indices_loaded = 0;
@@ -361,11 +375,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
 
        /* Setup temp directories */
        tmp = getenv("TMPDIR");
-       xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
-       if (!mkdtemp(tmpdir))
-               return error("could not create '%s'", tmpdir);
-       strbuf_addf(&ldir, "%s/left/", tmpdir);
-       strbuf_addf(&rdir, "%s/right/", tmpdir);
+       strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp");
+       strbuf_trim_trailing_dir_sep(&tmpdir);
+       strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX");
+       if (!mkdtemp(tmpdir.buf)) {
+               ret = error("could not create '%s'", tmpdir.buf);
+               goto finish;
+       }
+       strbuf_addf(&ldir, "%s/left/", tmpdir.buf);
+       strbuf_addf(&rdir, "%s/right/", tmpdir.buf);
        strbuf_addstr(&wtdir, workdir);
        if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
                strbuf_addch(&wtdir, '/');
@@ -387,19 +405,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        rdir_len = rdir.len;
        wtdir_len = wtdir.len;
 
-       child.no_stdin = 1;
-       child.git_cmd = 1;
-       child.use_shell = 0;
-       child.clean_on_exit = 1;
-       child.dir = prefix;
-       child.out = -1;
-       strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
-                    NULL);
-       for (i = 0; i < argc; i++)
-               strvec_push(&child.args, argv[i]);
-       if (start_command(&child))
+       child->no_stdin = 1;
+       child->git_cmd = 1;
+       child->use_shell = 0;
+       child->clean_on_exit = 1;
+       child->dir = prefix;
+       child->out = -1;
+       if (start_command(child))
                die("could not obtain raw diff");
-       fp = xfdopen(child.out, "r");
+       fp = xfdopen(child->out, "r");
 
        /* Build index info for left and right sides of the diff */
        i = 0;
@@ -410,9 +424,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                const char *src_path, *dst_path;
 
                if (starts_with(info.buf, "::"))
-                       die(N_("combined diff formats('-c' and '--cc') are "
+                       die(N_("combined diff formats ('-c' and '--cc') are "
                               "not supported in\n"
-                              "directory diff mode('-d' and '--dir-diff')."));
+                              "directory diff mode ('-d' and '--dir-diff')."));
 
                if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
                                     &status))
@@ -525,7 +539,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
 
        fclose(fp);
        fp = NULL;
-       if (finish_command(&child)) {
+       if (finish_command(child)) {
                ret = error("error occurred running diff --raw");
                goto finish;
        }
@@ -540,40 +554,19 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
         */
        hashmap_for_each_entry(&submodules, &iter, entry,
                                entry /* member name */) {
-               if (*entry->left) {
-                       add_path(&ldir, ldir_len, entry->path);
-                       ensure_leading_directories(ldir.buf);
-                       write_file(ldir.buf, "%s", entry->left);
-               }
-               if (*entry->right) {
-                       add_path(&rdir, rdir_len, entry->path);
-                       ensure_leading_directories(rdir.buf);
-                       write_file(rdir.buf, "%s", entry->right);
-               }
+               write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
        }
 
        /*
-        * Symbolic links require special treatment.The standard "git diff"
+        * Symbolic links require special treatment. The standard "git diff"
         * shows only the link itself, not the contents of the link target.
         * This loop replicates that behavior.
         */
        hashmap_for_each_entry(&symlinks2, &iter, entry,
                                entry /* member name */) {
-               if (*entry->left) {
-                       add_path(&ldir, ldir_len, entry->path);
-                       ensure_leading_directories(ldir.buf);
-                       unlink(ldir.buf);
-                       write_file(ldir.buf, "%s", entry->left);
-               }
-               if (*entry->right) {
-                       add_path(&rdir, rdir_len, entry->path);
-                       ensure_leading_directories(rdir.buf);
-                       unlink(rdir.buf);
-                       write_file(rdir.buf, "%s", entry->right);
-               }
-       }
 
-       strbuf_release(&buf);
+               write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
+       }
 
        strbuf_setlen(&ldir, ldir_len);
        helper_argv[1] = ldir.buf;
@@ -585,7 +578,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                flags = 0;
        } else
                setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
-       rc = run_command_v_opt(helper_argv, flags);
+       ret = run_command_v_opt(helper_argv, flags);
 
        /* TODO: audit for interaction with sparse-index. */
        ensure_full_index(&wtindex);
@@ -619,7 +612,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
                if (!indices_loaded) {
                        struct lock_file lock = LOCK_INIT;
                        strbuf_reset(&buf);
-                       strbuf_addf(&buf, "%s/wtindex", tmpdir);
+                       strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
                        if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
                            write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
                                ret = error("could not write %s", buf.buf);
@@ -649,11 +642,14 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
        }
 
        if (err) {
-               warning(_("temporary files exist in '%s'."), tmpdir);
+               warning(_("temporary files exist in '%s'."), tmpdir.buf);
                warning(_("you may want to cleanup or recover these."));
-               exit(1);
-       } else
-               exit_cleanup(tmpdir, rc);
+               ret = 1;
+       } else {
+               remove_dir_recursively(&tmpdir, 0);
+               if (ret)
+                       warning(_("failed: %d"), ret);
+       }
 
 finish:
        if (fp)
@@ -665,30 +661,29 @@ finish:
        strbuf_release(&rdir);
        strbuf_release(&wtdir);
        strbuf_release(&buf);
+       strbuf_release(&tmpdir);
 
-       return ret;
+       return (ret < 0) ? 1 : ret;
 }
 
 static int run_file_diff(int prompt, const char *prefix,
-                        int argc, const char **argv)
+                        struct child_process *child)
 {
-       struct strvec args = STRVEC_INIT;
        const char *env[] = {
                "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
                NULL
        };
-       int i;
 
        if (prompt > 0)
                env[2] = "GIT_DIFFTOOL_PROMPT=true";
        else if (!prompt)
                env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
 
+       child->git_cmd = 1;
+       child->dir = prefix;
+       strvec_pushv(&child->env_array, env);
 
-       strvec_push(&args, "diff");
-       for (i = 0; i < argc; i++)
-               strvec_push(&args, argv[i]);
-       return run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
+       return run_command(child);
 }
 
 int cmd_difftool(int argc, const char **argv, const char *prefix)
@@ -715,12 +710,13 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
                            "`--tool`")),
                OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
                         N_("make 'git-difftool' exit when an invoked diff "
-                           "tool returns a non - zero exit code")),
+                           "tool returns a non-zero exit code")),
                OPT_STRING('x', "extcmd", &extcmd, N_("command"),
                           N_("specify a custom command for viewing diffs")),
-               OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")),
+               OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")),
                OPT_END()
        };
+       struct child_process child = CHILD_PROCESS_INIT;
 
        git_config(difftool_config, NULL);
        symlinks = has_symlinks;
@@ -770,7 +766,14 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
         * will invoke a separate instance of 'git-difftool--helper' for
         * each file that changed.
         */
+       strvec_push(&child.args, "diff");
+       if (no_index)
+               strvec_push(&child.args, "--no-index");
+       if (dir_diff)
+               strvec_pushl(&child.args, "--raw", "--no-abbrev", "-z", NULL);
+       strvec_pushv(&child.args, argv);
+
        if (dir_diff)
-               return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
-       return run_file_diff(prompt, prefix, argc, argv);
+               return run_dir_diff(extcmd, symlinks, prefix, &child);
+       return run_file_diff(prompt, prefix, &child);
 }
index 95e8e89e81f0ec5ac2993ea5dc0cd8d4ce095e9d..8e2caf7281970cfb1bd3cf3bad07b488e61b6f07 100644 (file)
@@ -312,7 +312,7 @@ static void export_blob(const struct object_id *oid)
                if (!buf)
                        die("could not read blob %s", oid_to_hex(oid));
                if (check_object_signature(the_repository, oid, buf, size,
-                                          type_name(type)) < 0)
+                                          type_name(type), NULL) < 0)
                        die("oid mismatch in blob %s", oid_to_hex(oid));
                object = parse_object_buffer(the_repository, oid, type,
                                             size, buf, &eaten);
index e064687dbdcf38d6f11939058016fead20a2f996..f7abbc31ff1414d46ff87cbace917b96181dac13 100644 (file)
@@ -712,7 +712,7 @@ static void adjust_refcol_width(const struct ref *ref)
        int max, rlen, llen, len;
 
        /* uptodate lines are only shown on high verbosity level */
-       if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
+       if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
                return;
 
        max    = term_columns();
@@ -748,6 +748,9 @@ static void prepare_format_display(struct ref *ref_map)
        struct ref *rm;
        const char *format = "full";
 
+       if (verbosity < 0)
+               return;
+
        git_config_get_string_tmp("fetch.output", &format);
        if (!strcasecmp(format, "full"))
                compact_format = 0;
@@ -827,7 +830,12 @@ static void format_display(struct strbuf *display, char code,
                           const char *remote, const char *local,
                           int summary_width)
 {
-       int width = (summary_width + strlen(summary) - gettext_width(summary));
+       int width;
+
+       if (verbosity < 0)
+               return;
+
+       width = (summary_width + strlen(summary) - gettext_width(summary));
 
        strbuf_addf(display, "%c %-*s ", code, width, summary);
        if (!compact_format)
@@ -846,13 +854,11 @@ static int update_local_ref(struct ref *ref,
                            int summary_width)
 {
        struct commit *current = NULL, *updated;
-       enum object_type type;
        struct branch *current_branch = branch_get(NULL);
        const char *pretty_ref = prettify_refname(ref->name);
        int fast_forward = 0;
 
-       type = oid_object_info(the_repository, &ref->new_oid, NULL);
-       if (type < 0)
+       if (!repo_has_object_file(the_repository, &ref->new_oid))
                die(_("object %s not found"), oid_to_hex(&ref->new_oid));
 
        if (oideq(&ref->old_oid, &ref->new_oid)) {
@@ -964,7 +970,7 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
@@ -972,10 +978,9 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
        while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
                ref = ref->next;
        if (!ref)
-               return -1; /* end of the list */
+               return NULL;
        *rm = ref->next;
-       oidcpy(oid, &ref->old_oid);
-       return 0;
+       return &ref->old_oid;
 }
 
 struct fetch_head {
@@ -1074,7 +1079,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                              int connectivity_checked, struct ref *ref_map)
 {
        struct fetch_head fetch_head;
-       struct commit *commit;
        int url_len, i, rc = 0;
        struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
        struct ref_transaction *transaction = NULL;
@@ -1122,6 +1126,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
             want_status <= FETCH_HEAD_IGNORE;
             want_status++) {
                for (rm = ref_map; rm; rm = rm->next) {
+                       struct commit *commit = NULL;
                        struct ref *ref = NULL;
 
                        if (rm->status == REF_STATUS_REJECT_SHALLOW) {
@@ -1131,11 +1136,23 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                continue;
                        }
 
-                       commit = lookup_commit_reference_gently(the_repository,
-                                                               &rm->old_oid,
-                                                               1);
-                       if (!commit)
-                               rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+                       /*
+                        * References in "refs/tags/" are often going to point
+                        * to annotated tags, which are not part of the
+                        * commit-graph. We thus only try to look up refs in
+                        * the graph which are not in that namespace to not
+                        * regress performance in repositories with many
+                        * annotated tags.
+                        */
+                       if (!starts_with(rm->name, "refs/tags/"))
+                               commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
+                       if (!commit) {
+                               commit = lookup_commit_reference_gently(the_repository,
+                                                                       &rm->old_oid,
+                                                                       1);
+                               if (!commit)
+                                       rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+                       }
 
                        if (rm->fetch_head_status != want_status)
                                continue;
@@ -1202,13 +1219,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                               "FETCH_HEAD", summary_width);
                        }
                        if (note.len) {
-                               if (verbosity >= 0 && !shown_url) {
+                               if (!shown_url) {
                                        fprintf(stderr, _("From %.*s\n"),
                                                        url_len, url);
                                        shown_url = 1;
                                }
-                               if (verbosity >= 0)
-                                       fprintf(stderr, " %s\n", note.buf);
+                               fprintf(stderr, " %s\n", note.buf);
                        }
                }
        }
@@ -1229,7 +1245,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
 
-       if (advice_fetch_show_forced_updates) {
+       if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) {
                if (!fetch_show_forced_updates) {
                        warning(_(warn_show_forced_updates));
                } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
@@ -1282,37 +1298,35 @@ static int check_exist_and_connected(struct ref *ref_map)
        return check_connected(iterate_ref_map, &rm, &opt);
 }
 
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
+static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map)
 {
-       int ret = check_exist_and_connected(ref_map);
+       int connectivity_checked = 1;
+       int ret;
+
+       /*
+        * We don't need to perform a fetch in case we can already satisfy all
+        * refs.
+        */
+       ret = check_exist_and_connected(ref_map);
        if (ret) {
                trace2_region_enter("fetch", "fetch_refs", the_repository);
                ret = transport_fetch_refs(transport, ref_map);
                trace2_region_leave("fetch", "fetch_refs", the_repository);
+               if (ret)
+                       goto out;
+               connectivity_checked = transport->smart_options ?
+                       transport->smart_options->connectivity_checked : 0;
        }
-       if (!ret)
-               /*
-                * Keep the new pack's ".keep" file around to allow the caller
-                * time to update refs to reference the new objects.
-                */
-               return 0;
-       transport_unlock_pack(transport);
-       return ret;
-}
 
-/* Update local refs based on the ref values fetched from a remote */
-static int consume_refs(struct transport *transport, struct ref *ref_map)
-{
-       int connectivity_checked = transport->smart_options
-               ? transport->smart_options->connectivity_checked : 0;
-       int ret;
        trace2_region_enter("fetch", "consume_refs", the_repository);
        ret = store_updated_refs(transport->url,
                                 transport->remote->name,
                                 connectivity_checked,
                                 ref_map);
-       transport_unlock_pack(transport);
        trace2_region_leave("fetch", "consume_refs", the_repository);
+
+out:
+       transport_unlock_pack(transport);
        return ret;
 }
 
@@ -1501,8 +1515,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
        transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
        transport_set_option(transport, TRANS_OPT_DEPTH, "0");
        transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
-       if (!fetch_refs(transport, ref_map))
-               consume_refs(transport, ref_map);
+       fetch_and_consume_refs(transport, ref_map);
 
        if (gsecondary) {
                transport_disconnect(gsecondary);
@@ -1593,7 +1606,7 @@ static int do_fetch(struct transport *transport,
                                   transport->url);
                }
        }
-       if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) {
+       if (fetch_and_consume_refs(transport, ref_map)) {
                free_refs(ref_map);
                retcode = 1;
                goto cleanup;
@@ -2135,8 +2148,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                                             NULL);
        }
 
-       close_object_store(the_repository->objects);
-
        if (enable_auto_gc)
                run_auto_maintenance(verbosity < 0);
 
index 89cb6307d46faea7082ca834472f0285b2053b0c..642b4b888fbe88eaa3d1b28a7bd6eaed092540f1 100644 (file)
@@ -77,7 +77,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 
        filter.name_patterns = argv;
        filter.match_as_path = 1;
-       filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
+       filter_refs(&array, &filter, FILTER_REFS_ALL);
        ref_array_sort(sorting, &array);
 
        if (!maxcount || array.nr < maxcount)
index b42b6fe21f7347b546d53e8f6a250e50659befe3..30a516da29eab319f4b5b404c6b397b4637668d5 100644 (file)
@@ -593,18 +593,43 @@ static void get_default_heads(void)
        }
 }
 
+struct for_each_loose_cb
+{
+       struct progress *progress;
+       struct strbuf obj_type;
+};
+
 static int fsck_loose(const struct object_id *oid, const char *path, void *data)
 {
+       struct for_each_loose_cb *cb_data = data;
        struct object *obj;
-       enum object_type type;
+       enum object_type type = OBJ_NONE;
        unsigned long size;
        void *contents;
        int eaten;
+       struct object_info oi = OBJECT_INFO_INIT;
+       struct object_id real_oid = *null_oid();
+       int err = 0;
 
-       if (read_loose_object(path, oid, &type, &size, &contents) < 0) {
+       strbuf_reset(&cb_data->obj_type);
+       oi.type_name = &cb_data->obj_type;
+       oi.sizep = &size;
+       oi.typep = &type;
+
+       if (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) {
+               if (contents && !oideq(&real_oid, oid))
+                       err = error(_("%s: hash-path mismatch, found at: %s"),
+                                   oid_to_hex(&real_oid), path);
+               else
+                       err = error(_("%s: object corrupt or missing: %s"),
+                                   oid_to_hex(oid), path);
+       }
+       if (type != OBJ_NONE && type < 0)
+               err = error(_("%s: object is of unknown type '%s': %s"),
+                           oid_to_hex(&real_oid), cb_data->obj_type.buf,
+                           path);
+       if (err < 0) {
                errors_found |= ERROR_OBJECT;
-               error(_("%s: object corrupt or missing: %s"),
-                     oid_to_hex(oid), path);
                return 0; /* keep checking other objects */
        }
 
@@ -640,8 +665,10 @@ static int fsck_cruft(const char *basename, const char *path, void *data)
        return 0;
 }
 
-static int fsck_subdir(unsigned int nr, const char *path, void *progress)
+static int fsck_subdir(unsigned int nr, const char *path, void *data)
 {
+       struct for_each_loose_cb *cb_data = data;
+       struct progress *progress = cb_data->progress;
        display_progress(progress, nr + 1);
        return 0;
 }
@@ -649,6 +676,10 @@ static int fsck_subdir(unsigned int nr, const char *path, void *progress)
 static void fsck_object_dir(const char *path)
 {
        struct progress *progress = NULL;
+       struct for_each_loose_cb cb_data = {
+               .obj_type = STRBUF_INIT,
+               .progress = progress,
+       };
 
        if (verbose)
                fprintf_ln(stderr, _("Checking object directory"));
@@ -657,9 +688,10 @@ static void fsck_object_dir(const char *path)
                progress = start_progress(_("Checking object directories"), 256);
 
        for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
-                                     progress);
+                                     &cb_data);
        display_progress(progress, 256);
        stop_progress(&progress);
+       strbuf_release(&cb_data.obj_type);
 }
 
 static int fsck_head_link(const char *head_ref_name,
index ac60662619e9d14ab5100712c2964a5b6eee8619..6b3de3dd51438e2553e82d734206c3828f9c96aa 100644 (file)
@@ -663,8 +663,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        gc_before_repack();
 
        if (!repository_format_precious_objects) {
-               close_object_store(the_repository->objects);
-               if (run_command_v_opt(repack.v, RUN_GIT_CMD))
+               if (run_command_v_opt(repack.v,
+                                     RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE))
                        die(FAILED_RUN, repack.v[0]);
 
                if (prune_expire) {
@@ -848,7 +848,7 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts)
 {
        struct child_process child = CHILD_PROCESS_INIT;
 
-       child.git_cmd = 1;
+       child.git_cmd = child.close_object_store = 1;
        strvec_pushl(&child.args, "commit-graph", "write",
                     "--split", "--reachable", NULL);
 
@@ -864,7 +864,6 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
        if (!the_repository->settings.core_commit_graph)
                return 0;
 
-       close_object_store(the_repository->objects);
        if (run_write_commit_graph(opts)) {
                error(_("failed to write commit-graph"));
                return 1;
@@ -913,7 +912,7 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)
 {
        struct child_process child = CHILD_PROCESS_INIT;
 
-       child.git_cmd = 1;
+       child.git_cmd = child.close_object_store = 1;
        strvec_push(&child.args, "gc");
 
        if (opts->auto_flag)
@@ -923,7 +922,6 @@ static int maintenance_task_gc(struct maintenance_run_opts *opts)
        else
                strvec_push(&child.args, "--no-quiet");
 
-       close_object_store(the_repository->objects);
        return run_command(&child);
 }
 
@@ -1097,14 +1095,12 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts)
 {
        struct child_process child = CHILD_PROCESS_INIT;
 
-       child.git_cmd = 1;
+       child.git_cmd = child.close_object_store = 1;
        strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
 
        if (opts->quiet)
                strvec_push(&child.args, "--no-progress");
 
-       close_object_store(the_repository->objects);
-
        if (run_command(&child))
                return error(_("'git multi-pack-index expire' failed"));
 
@@ -1155,7 +1151,7 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts)
 {
        struct child_process child = CHILD_PROCESS_INIT;
 
-       child.git_cmd = 1;
+       child.git_cmd = child.close_object_store = 1;
        strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
 
        if (opts->quiet)
@@ -1164,8 +1160,6 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts)
        strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
                                  (uintmax_t)get_auto_pack_size());
 
-       close_object_store(the_repository->objects);
-
        if (run_command(&child))
                return error(_("'git multi-pack-index repack' failed"));
 
@@ -1529,6 +1523,93 @@ static const char *get_frequency(enum schedule_priority schedule)
        }
 }
 
+/*
+ * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
+ * to mock the schedulers that `git maintenance start` rely on.
+ *
+ * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
+ * list of colon-separated key/value pairs where each pair contains a scheduler
+ * and its corresponding mock.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
+ *   arguments unmodified.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
+ *   In this case, the *cmd value is read as input.
+ *
+ *   * if the input value *cmd is the key of one of the comma-separated list
+ *     item, then *is_available is set to true and *cmd is modified and becomes
+ *     the mock command.
+ *
+ *   * if the input value *cmd isn’t the key of any of the comma-separated list
+ *     item, then *is_available is set to false.
+ *
+ * Ex.:
+ *   GIT_TEST_MAINT_SCHEDULER not set
+ *     +-------+-------------------------------------------------+
+ *     | Input |                     Output                      |
+ *     | *cmd  | return code |       *cmd        | *is_available |
+ *     +-------+-------------+-------------------+---------------+
+ *     | "foo" |    false    | "foo" (unchanged) |  (unchanged)  |
+ *     +-------+-------------+-------------------+---------------+
+ *
+ *   GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
+ *     +-------+-------------------------------------------------+
+ *     | Input |                     Output                      |
+ *     | *cmd  | return code |       *cmd        | *is_available |
+ *     +-------+-------------+-------------------+---------------+
+ *     | "foo" |    true     |  "./mock.foo.sh"  |     true      |
+ *     | "qux" |    true     | "qux" (unchanged) |     false     |
+ *     +-------+-------------+-------------------+---------------+
+ */
+static int get_schedule_cmd(const char **cmd, int *is_available)
+{
+       char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
+       struct string_list_item *item;
+       struct string_list list = STRING_LIST_INIT_NODUP;
+
+       if (!testing)
+               return 0;
+
+       if (is_available)
+               *is_available = 0;
+
+       string_list_split_in_place(&list, testing, ',', -1);
+       for_each_string_list_item(item, &list) {
+               struct string_list pair = STRING_LIST_INIT_NODUP;
+
+               if (string_list_split_in_place(&pair, item->string, ':', 2) != 2)
+                       continue;
+
+               if (!strcmp(*cmd, pair.items[0].string)) {
+                       *cmd = pair.items[1].string;
+                       if (is_available)
+                               *is_available = 1;
+                       string_list_clear(&list, 0);
+                       UNLEAK(testing);
+                       return 1;
+               }
+       }
+
+       string_list_clear(&list, 0);
+       free(testing);
+       return 1;
+}
+
+static int is_launchctl_available(void)
+{
+       const char *cmd = "launchctl";
+       int is_available;
+       if (get_schedule_cmd(&cmd, &is_available))
+               return is_available;
+
+#ifdef __APPLE__
+       return 1;
+#else
+       return 0;
+#endif
+}
+
 static char *launchctl_service_name(const char *frequency)
 {
        struct strbuf label = STRBUF_INIT;
@@ -1542,7 +1623,7 @@ static char *launchctl_service_filename(const char *name)
        struct strbuf filename = STRBUF_INIT;
        strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
 
-       expanded = expand_user_path(filename.buf, 1);
+       expanded = interpolate_path(filename.buf, 1);
        if (!expanded)
                die(_("failed to expand path '%s'"), filename.buf);
 
@@ -1555,19 +1636,17 @@ static char *launchctl_get_uid(void)
        return xstrfmt("gui/%d", getuid());
 }
 
-static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
+static int launchctl_boot_plist(int enable, const char *filename)
 {
+       const char *cmd = "launchctl";
        int result;
        struct child_process child = CHILD_PROCESS_INIT;
        char *uid = launchctl_get_uid();
 
+       get_schedule_cmd(&cmd, NULL);
        strvec_split(&child.args, cmd);
-       if (enable)
-               strvec_push(&child.args, "bootstrap");
-       else
-               strvec_push(&child.args, "bootout");
-       strvec_push(&child.args, uid);
-       strvec_push(&child.args, filename);
+       strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
+                    filename, NULL);
 
        child.no_stderr = 1;
        child.no_stdout = 1;
@@ -1581,30 +1660,28 @@ static int launchctl_boot_plist(int enable, const char *filename, const char *cm
        return result;
 }
 
-static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
+static int launchctl_remove_plist(enum schedule_priority schedule)
 {
        const char *frequency = get_frequency(schedule);
        char *name = launchctl_service_name(frequency);
        char *filename = launchctl_service_filename(name);
-       int result = launchctl_boot_plist(0, filename, cmd);
+       int result = launchctl_boot_plist(0, filename);
        unlink(filename);
        free(filename);
        free(name);
        return result;
 }
 
-static int launchctl_remove_plists(const char *cmd)
+static int launchctl_remove_plists(void)
 {
-       return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
-               launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
-               launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
+       return launchctl_remove_plist(SCHEDULE_HOURLY) ||
+              launchctl_remove_plist(SCHEDULE_DAILY) ||
+              launchctl_remove_plist(SCHEDULE_WEEKLY);
 }
 
 static int launchctl_list_contains_plist(const char *name, const char *cmd)
 {
-       int result;
        struct child_process child = CHILD_PROCESS_INIT;
-       char *uid = launchctl_get_uid();
 
        strvec_split(&child.args, cmd);
        strvec_pushl(&child.args, "list", name, NULL);
@@ -1615,15 +1692,11 @@ static int launchctl_list_contains_plist(const char *name, const char *cmd)
        if (start_command(&child))
                die(_("failed to start launchctl"));
 
-       result = finish_command(&child);
-
-       free(uid);
-
        /* Returns failure if 'name' doesn't exist. */
-       return !result;
+       return !finish_command(&child);
 }
 
-static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
 {
        int i, fd;
        const char *preamble, *repeat;
@@ -1634,7 +1707,9 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
        static unsigned long lock_file_timeout_ms = ULONG_MAX;
        struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
        struct stat st;
+       const char *cmd = "launchctl";
 
+       get_schedule_cmd(&cmd, NULL);
        preamble = "<?xml version=\"1.0\"?>\n"
                   "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
                   "<plist version=\"1.0\">"
@@ -1715,8 +1790,8 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
                        die_errno(_("could not write '%s'"), filename);
 
                /* bootout might fail if not already running, so ignore */
-               launchctl_boot_plist(0, filename, cmd);
-               if (launchctl_boot_plist(1, filename, cmd))
+               launchctl_boot_plist(0, filename);
+               if (launchctl_boot_plist(1, filename))
                        die(_("failed to bootstrap service %s"), filename);
        }
 
@@ -1727,21 +1802,35 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
        return 0;
 }
 
-static int launchctl_add_plists(const char *cmd)
+static int launchctl_add_plists(void)
 {
        const char *exec_path = git_exec_path();
 
-       return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
-               launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
-               launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
+       return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
+              launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
+              launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
 }
 
-static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int launchctl_update_schedule(int run_maintenance, int fd)
 {
        if (run_maintenance)
-               return launchctl_add_plists(cmd);
+               return launchctl_add_plists();
        else
-               return launchctl_remove_plists(cmd);
+               return launchctl_remove_plists();
+}
+
+static int is_schtasks_available(void)
+{
+       const char *cmd = "schtasks";
+       int is_available;
+       if (get_schedule_cmd(&cmd, &is_available))
+               return is_available;
+
+#ifdef GIT_WINDOWS_NATIVE
+       return 1;
+#else
+       return 0;
+#endif
 }
 
 static char *schtasks_task_name(const char *frequency)
@@ -1751,13 +1840,15 @@ static char *schtasks_task_name(const char *frequency)
        return strbuf_detach(&label, NULL);
 }
 
-static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
+static int schtasks_remove_task(enum schedule_priority schedule)
 {
+       const char *cmd = "schtasks";
        int result;
        struct strvec args = STRVEC_INIT;
        const char *frequency = get_frequency(schedule);
        char *name = schtasks_task_name(frequency);
 
+       get_schedule_cmd(&cmd, NULL);
        strvec_split(&args, cmd);
        strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
 
@@ -1768,15 +1859,16 @@ static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd
        return result;
 }
 
-static int schtasks_remove_tasks(const char *cmd)
+static int schtasks_remove_tasks(void)
 {
-       return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
-               schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
-               schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
+       return schtasks_remove_task(SCHEDULE_HOURLY) ||
+              schtasks_remove_task(SCHEDULE_DAILY) ||
+              schtasks_remove_task(SCHEDULE_WEEKLY);
 }
 
-static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
 {
+       const char *cmd = "schtasks";
        int result;
        struct child_process child = CHILD_PROCESS_INIT;
        const char *xml;
@@ -1785,6 +1877,8 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
        char *name = schtasks_task_name(frequency);
        struct strbuf tfilename = STRBUF_INIT;
 
+       get_schedule_cmd(&cmd, NULL);
+
        strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
                    get_git_common_dir(), frequency);
        tfile = xmks_tempfile(tfilename.buf);
@@ -1889,28 +1983,52 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
        return result;
 }
 
-static int schtasks_schedule_tasks(const char *cmd)
+static int schtasks_schedule_tasks(void)
 {
        const char *exec_path = git_exec_path();
 
-       return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
-               schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
-               schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
+       return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
+              schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
+              schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
 }
 
-static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int schtasks_update_schedule(int run_maintenance, int fd)
 {
        if (run_maintenance)
-               return schtasks_schedule_tasks(cmd);
+               return schtasks_schedule_tasks();
        else
-               return schtasks_remove_tasks(cmd);
+               return schtasks_remove_tasks();
+}
+
+static int is_crontab_available(void)
+{
+       const char *cmd = "crontab";
+       int is_available;
+       struct child_process child = CHILD_PROCESS_INIT;
+
+       if (get_schedule_cmd(&cmd, &is_available))
+               return is_available;
+
+       strvec_split(&child.args, cmd);
+       strvec_push(&child.args, "-l");
+       child.no_stdin = 1;
+       child.no_stdout = 1;
+       child.no_stderr = 1;
+       child.silent_exec_failure = 1;
+
+       if (start_command(&child))
+               return 0;
+       /* Ignore exit code, as an empty crontab will return error. */
+       finish_command(&child);
+       return 1;
 }
 
 #define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
 #define END_LINE "# END GIT MAINTENANCE SCHEDULE"
 
-static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int crontab_update_schedule(int run_maintenance, int fd)
 {
+       const char *cmd = "crontab";
        int result = 0;
        int in_old_region = 0;
        struct child_process crontab_list = CHILD_PROCESS_INIT;
@@ -1918,6 +2036,7 @@ static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
        FILE *cron_list, *cron_in;
        struct strbuf line = STRBUF_INIT;
 
+       get_schedule_cmd(&cmd, NULL);
        strvec_split(&crontab_list.args, cmd);
        strvec_push(&crontab_list.args, "-l");
        crontab_list.in = -1;
@@ -1994,66 +2113,376 @@ done_editing:
        return result;
 }
 
+static int real_is_systemd_timer_available(void)
+{
+       struct child_process child = CHILD_PROCESS_INIT;
+
+       strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
+       child.no_stdin = 1;
+       child.no_stdout = 1;
+       child.no_stderr = 1;
+       child.silent_exec_failure = 1;
+
+       if (start_command(&child))
+               return 0;
+       if (finish_command(&child))
+               return 0;
+       return 1;
+}
+
+static int is_systemd_timer_available(void)
+{
+       const char *cmd = "systemctl";
+       int is_available;
+
+       if (get_schedule_cmd(&cmd, &is_available))
+               return is_available;
+
+       return real_is_systemd_timer_available();
+}
+
+static char *xdg_config_home_systemd(const char *filename)
+{
+       return xdg_config_home_for("systemd/user", filename);
+}
+
+static int systemd_timer_enable_unit(int enable,
+                                    enum schedule_priority schedule)
+{
+       const char *cmd = "systemctl";
+       struct child_process child = CHILD_PROCESS_INIT;
+       const char *frequency = get_frequency(schedule);
+
+       /*
+        * Disabling the systemd unit while it is already disabled makes
+        * systemctl print an error.
+        * Let's ignore it since it means we already are in the expected state:
+        * the unit is disabled.
+        *
+        * On the other hand, enabling a systemd unit which is already enabled
+        * produces no error.
+        */
+       if (!enable)
+               child.no_stderr = 1;
+
+       get_schedule_cmd(&cmd, NULL);
+       strvec_split(&child.args, cmd);
+       strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
+                    "--now", NULL);
+       strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency);
+
+       if (start_command(&child))
+               return error(_("failed to start systemctl"));
+       if (finish_command(&child))
+               /*
+                * Disabling an already disabled systemd unit makes
+                * systemctl fail.
+                * Let's ignore this failure.
+                *
+                * Enabling an enabled systemd unit doesn't fail.
+                */
+               if (enable)
+                       return error(_("failed to run systemctl"));
+       return 0;
+}
+
+static int systemd_timer_delete_unit_templates(void)
+{
+       int ret = 0;
+       char *filename = xdg_config_home_systemd("git-maintenance@.timer");
+       if (unlink(filename) && !is_missing_file_error(errno))
+               ret = error_errno(_("failed to delete '%s'"), filename);
+       FREE_AND_NULL(filename);
+
+       filename = xdg_config_home_systemd("git-maintenance@.service");
+       if (unlink(filename) && !is_missing_file_error(errno))
+               ret = error_errno(_("failed to delete '%s'"), filename);
+
+       free(filename);
+       return ret;
+}
+
+static int systemd_timer_delete_units(void)
+{
+       return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
+              systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
+              systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
+              systemd_timer_delete_unit_templates();
+}
+
+static int systemd_timer_write_unit_templates(const char *exec_path)
+{
+       char *filename;
+       FILE *file;
+       const char *unit;
+
+       filename = xdg_config_home_systemd("git-maintenance@.timer");
+       if (safe_create_leading_directories(filename)) {
+               error(_("failed to create directories for '%s'"), filename);
+               goto error;
+       }
+       file = fopen_or_warn(filename, "w");
+       if (file == NULL)
+               goto error;
+
+       unit = "# This file was created and is maintained by Git.\n"
+              "# Any edits made in this file might be replaced in the future\n"
+              "# by a Git command.\n"
+              "\n"
+              "[Unit]\n"
+              "Description=Optimize Git repositories data\n"
+              "\n"
+              "[Timer]\n"
+              "OnCalendar=%i\n"
+              "Persistent=true\n"
+              "\n"
+              "[Install]\n"
+              "WantedBy=timers.target\n";
+       if (fputs(unit, file) == EOF) {
+               error(_("failed to write to '%s'"), filename);
+               fclose(file);
+               goto error;
+       }
+       if (fclose(file) == EOF) {
+               error_errno(_("failed to flush '%s'"), filename);
+               goto error;
+       }
+       free(filename);
+
+       filename = xdg_config_home_systemd("git-maintenance@.service");
+       file = fopen_or_warn(filename, "w");
+       if (file == NULL)
+               goto error;
+
+       unit = "# This file was created and is maintained by Git.\n"
+              "# Any edits made in this file might be replaced in the future\n"
+              "# by a Git command.\n"
+              "\n"
+              "[Unit]\n"
+              "Description=Optimize Git repositories data\n"
+              "\n"
+              "[Service]\n"
+              "Type=oneshot\n"
+              "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n"
+              "LockPersonality=yes\n"
+              "MemoryDenyWriteExecute=yes\n"
+              "NoNewPrivileges=yes\n"
+              "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n"
+              "RestrictNamespaces=yes\n"
+              "RestrictRealtime=yes\n"
+              "RestrictSUIDSGID=yes\n"
+              "SystemCallArchitectures=native\n"
+              "SystemCallFilter=@system-service\n";
+       if (fprintf(file, unit, exec_path, exec_path) < 0) {
+               error(_("failed to write to '%s'"), filename);
+               fclose(file);
+               goto error;
+       }
+       if (fclose(file) == EOF) {
+               error_errno(_("failed to flush '%s'"), filename);
+               goto error;
+       }
+       free(filename);
+       return 0;
+
+error:
+       free(filename);
+       systemd_timer_delete_unit_templates();
+       return -1;
+}
+
+static int systemd_timer_setup_units(void)
+{
+       const char *exec_path = git_exec_path();
+
+       int ret = systemd_timer_write_unit_templates(exec_path) ||
+                 systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
+                 systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
+                 systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
+       if (ret)
+               systemd_timer_delete_units();
+       return ret;
+}
+
+static int systemd_timer_update_schedule(int run_maintenance, int fd)
+{
+       if (run_maintenance)
+               return systemd_timer_setup_units();
+       else
+               return systemd_timer_delete_units();
+}
+
+enum scheduler {
+       SCHEDULER_INVALID = -1,
+       SCHEDULER_AUTO,
+       SCHEDULER_CRON,
+       SCHEDULER_SYSTEMD,
+       SCHEDULER_LAUNCHCTL,
+       SCHEDULER_SCHTASKS,
+};
+
+static const struct {
+       const char *name;
+       int (*is_available)(void);
+       int (*update_schedule)(int run_maintenance, int fd);
+} scheduler_fn[] = {
+       [SCHEDULER_CRON] = {
+               .name = "crontab",
+               .is_available = is_crontab_available,
+               .update_schedule = crontab_update_schedule,
+       },
+       [SCHEDULER_SYSTEMD] = {
+               .name = "systemctl",
+               .is_available = is_systemd_timer_available,
+               .update_schedule = systemd_timer_update_schedule,
+       },
+       [SCHEDULER_LAUNCHCTL] = {
+               .name = "launchctl",
+               .is_available = is_launchctl_available,
+               .update_schedule = launchctl_update_schedule,
+       },
+       [SCHEDULER_SCHTASKS] = {
+               .name = "schtasks",
+               .is_available = is_schtasks_available,
+               .update_schedule = schtasks_update_schedule,
+       },
+};
+
+static enum scheduler parse_scheduler(const char *value)
+{
+       if (!value)
+               return SCHEDULER_INVALID;
+       else if (!strcasecmp(value, "auto"))
+               return SCHEDULER_AUTO;
+       else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
+               return SCHEDULER_CRON;
+       else if (!strcasecmp(value, "systemd") ||
+                !strcasecmp(value, "systemd-timer"))
+               return SCHEDULER_SYSTEMD;
+       else if (!strcasecmp(value, "launchctl"))
+               return SCHEDULER_LAUNCHCTL;
+       else if (!strcasecmp(value, "schtasks"))
+               return SCHEDULER_SCHTASKS;
+       else
+               return SCHEDULER_INVALID;
+}
+
+static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
+                                    int unset)
+{
+       enum scheduler *scheduler = opt->value;
+
+       BUG_ON_OPT_NEG(unset);
+
+       *scheduler = parse_scheduler(arg);
+       if (*scheduler == SCHEDULER_INVALID)
+               return error(_("unrecognized --scheduler argument '%s'"), arg);
+       return 0;
+}
+
+struct maintenance_start_opts {
+       enum scheduler scheduler;
+};
+
+static enum scheduler resolve_scheduler(enum scheduler scheduler)
+{
+       if (scheduler != SCHEDULER_AUTO)
+               return scheduler;
+
 #if defined(__APPLE__)
-static const char platform_scheduler[] = "launchctl";
+       return SCHEDULER_LAUNCHCTL;
+
 #elif defined(GIT_WINDOWS_NATIVE)
-static const char platform_scheduler[] = "schtasks";
+       return SCHEDULER_SCHTASKS;
+
+#elif defined(__linux__)
+       if (is_systemd_timer_available())
+               return SCHEDULER_SYSTEMD;
+       else if (is_crontab_available())
+               return SCHEDULER_CRON;
+       else
+               die(_("neither systemd timers nor crontab are available"));
+
 #else
-static const char platform_scheduler[] = "crontab";
+       return SCHEDULER_CRON;
 #endif
+}
 
-static int update_background_schedule(int enable)
+static void validate_scheduler(enum scheduler scheduler)
 {
-       int result;
-       const char *scheduler = platform_scheduler;
-       const char *cmd = scheduler;
-       char *testing;
+       if (scheduler == SCHEDULER_INVALID)
+               BUG("invalid scheduler");
+       if (scheduler == SCHEDULER_AUTO)
+               BUG("resolve_scheduler should have been called before");
+
+       if (!scheduler_fn[scheduler].is_available())
+               die(_("%s scheduler is not available"),
+                   scheduler_fn[scheduler].name);
+}
+
+static int update_background_schedule(const struct maintenance_start_opts *opts,
+                                     int enable)
+{
+       unsigned int i;
+       int result = 0;
        struct lock_file lk;
        char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
 
-       testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
-       if (testing) {
-               char *sep = strchr(testing, ':');
-               if (!sep)
-                       die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
-               *sep = '\0';
-               scheduler = testing;
-               cmd = sep + 1;
+       if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+               free(lock_path);
+               return error(_("another process is scheduling background maintenance"));
        }
 
-       if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
-               result = error(_("another process is scheduling background maintenance"));
-               goto cleanup;
+       for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
+               if (enable && opts->scheduler == i)
+                       continue;
+               if (!scheduler_fn[i].is_available())
+                       continue;
+               scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
        }
 
-       if (!strcmp(scheduler, "launchctl"))
-               result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
-       else if (!strcmp(scheduler, "schtasks"))
-               result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd);
-       else if (!strcmp(scheduler, "crontab"))
-               result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd);
-       else
-               die("unknown background scheduler: %s", scheduler);
+       if (enable)
+               result = scheduler_fn[opts->scheduler].update_schedule(
+                       1, get_lock_file_fd(&lk));
 
        rollback_lock_file(&lk);
 
-cleanup:
        free(lock_path);
-       free(testing);
        return result;
 }
 
-static int maintenance_start(void)
+static const char *const builtin_maintenance_start_usage[] = {
+       N_("git maintenance start [--scheduler=<scheduler>]"),
+       NULL
+};
+
+static int maintenance_start(int argc, const char **argv, const char *prefix)
 {
+       struct maintenance_start_opts opts = { 0 };
+       struct option options[] = {
+               OPT_CALLBACK_F(
+                       0, "scheduler", &opts.scheduler, N_("scheduler"),
+                       N_("scheduler to trigger git maintenance run"),
+                       PARSE_OPT_NONEG, maintenance_opt_scheduler),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options,
+                            builtin_maintenance_start_usage, 0);
+       if (argc)
+               usage_with_options(builtin_maintenance_start_usage, options);
+
+       opts.scheduler = resolve_scheduler(opts.scheduler);
+       validate_scheduler(opts.scheduler);
+
        if (maintenance_register())
                warning(_("failed to add repo to global config"));
-
-       return update_background_schedule(1);
+       return update_background_schedule(&opts, 1);
 }
 
 static int maintenance_stop(void)
 {
-       return update_background_schedule(0);
+       return update_background_schedule(NULL, 0);
 }
 
 static const char builtin_maintenance_usage[] =        N_("git maintenance <subcommand> [<options>]");
@@ -2067,7 +2496,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "run"))
                return maintenance_run(argc - 1, argv + 1, prefix);
        if (!strcmp(argv[1], "start"))
-               return maintenance_start();
+               return maintenance_start(argc - 1, argv + 1, prefix);
        if (!strcmp(argv[1], "stop"))
                return maintenance_stop();
        if (!strcmp(argv[1], "register"))
index 7d2f8e5adb69c428847bc23b81bf822c916ccdea..8af5249a7bb118333da4eede706d3fd2de8a287b 100644 (file)
@@ -65,6 +65,9 @@ static int todo_done;
 /* Has all work items been added? */
 static int all_work_added;
 
+static struct repository **repos_to_free;
+static size_t repos_to_free_nr, repos_to_free_alloc;
+
 /* This lock protects all the variables above. */
 static pthread_mutex_t grep_mutex;
 
@@ -168,6 +171,19 @@ static void work_done(struct work_item *w)
        grep_unlock();
 }
 
+static void free_repos(void)
+{
+       int i;
+
+       for (i = 0; i < repos_to_free_nr; i++) {
+               repo_clear(repos_to_free[i]);
+               free(repos_to_free[i]);
+       }
+       FREE_AND_NULL(repos_to_free);
+       repos_to_free_nr = 0;
+       repos_to_free_alloc = 0;
+}
+
 static void *run(void *arg)
 {
        int hit = 0;
@@ -333,7 +349,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
        struct grep_source gs;
 
        grep_source_name(opt, filename, tree_name_len, &pathbuf);
-       grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid);
+       grep_source_init_oid(&gs, pathbuf.buf, path, oid, opt->repo);
        strbuf_release(&pathbuf);
 
        if (num_threads > 1) {
@@ -359,7 +375,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        struct grep_source gs;
 
        grep_source_name(opt, filename, 0, &buf);
-       grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
+       grep_source_init_file(&gs, buf.buf, filename);
        strbuf_release(&buf);
 
        if (num_threads > 1) {
@@ -415,19 +431,21 @@ static int grep_submodule(struct grep_opt *opt,
                          const struct object_id *oid,
                          const char *filename, const char *path, int cached)
 {
-       struct repository subrepo;
+       struct repository *subrepo;
        struct repository *superproject = opt->repo;
-       const struct submodule *sub;
        struct grep_opt subopt;
-       int hit;
-
-       sub = submodule_from_path(superproject, null_oid(), path);
+       int hit = 0;
 
        if (!is_submodule_active(superproject, path))
                return 0;
 
-       if (repo_submodule_init(&subrepo, superproject, sub))
+       subrepo = xmalloc(sizeof(*subrepo));
+       if (repo_submodule_init(subrepo, superproject, path, null_oid())) {
+               free(subrepo);
                return 0;
+       }
+       ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc);
+       repos_to_free[repos_to_free_nr++] = subrepo;
 
        /*
         * NEEDSWORK: repo_read_gitmodules() might call
@@ -438,53 +456,49 @@ static int grep_submodule(struct grep_opt *opt,
         * subrepo's odbs to the in-memory alternates list.
         */
        obj_read_lock();
-       repo_read_gitmodules(&subrepo, 0);
+       repo_read_gitmodules(subrepo, 0);
 
        /*
-        * NEEDSWORK: This adds the submodule's object directory to the list of
-        * alternates for the single in-memory object store.  This has some bad
-        * consequences for memory (processed objects will never be freed) and
-        * performance (this increases the number of pack files git has to pay
-        * attention to, to the sum of the number of pack files in all the
-        * repositories processed so far).  This can be removed once the object
-        * store is no longer global and instead is a member of the repository
-        * object.
+        * All code paths tested by test code no longer need submodule ODBs to
+        * be added as alternates, but add it to the list just in case.
+        * Submodule ODBs added through add_submodule_odb_by_path() will be
+        * lazily registered as alternates when needed (and except in an
+        * unexpected code interaction, it won't be needed).
         */
-       add_to_alternates_memory(subrepo.objects->odb->path);
+       add_submodule_odb_by_path(subrepo->objects->odb->path);
        obj_read_unlock();
 
        memcpy(&subopt, opt, sizeof(subopt));
-       subopt.repo = &subrepo;
+       subopt.repo = subrepo;
 
        if (oid) {
-               struct object *object;
+               enum object_type object_type;
                struct tree_desc tree;
                void *data;
                unsigned long size;
                struct strbuf base = STRBUF_INIT;
 
                obj_read_lock();
-               object = parse_object_or_die(oid, NULL);
+               object_type = oid_object_info(subrepo, oid, NULL);
                obj_read_unlock();
-               data = read_object_with_reference(&subrepo,
-                                                 &object->oid, tree_type,
+               data = read_object_with_reference(subrepo,
+                                                 oid, tree_type,
                                                  &size, NULL);
                if (!data)
-                       die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
+                       die(_("unable to read tree (%s)"), oid_to_hex(oid));
 
                strbuf_addstr(&base, filename);
                strbuf_addch(&base, '/');
 
                init_tree_desc(&tree, data, size);
                hit = grep_tree(&subopt, pathspec, &tree, &base, base.len,
-                               object->type == OBJ_COMMIT);
+                               object_type == OBJ_COMMIT);
                strbuf_release(&base);
                free(data);
        } else {
                hit = grep_cache(&subopt, pathspec, cached);
        }
 
-       repo_clear(&subrepo);
        return hit;
 }
 
@@ -1182,5 +1196,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                run_pager(&opt, prefix);
        clear_pathspec(&pathspec);
        free_grep_patterns(&opt);
+       free_repos();
        return !hit;
 }
index b7eec06c3de8a943fdbd22ba83502d69335726d0..75cd2fb407f6ebd3ce24b8e3588cf384d7b48699 100644 (file)
@@ -7,7 +7,6 @@
 #include "exec-cmd.h"
 #include "parse-options.h"
 #include "run-command.h"
-#include "column.h"
 #include "config-list.h"
 #include "help.h"
 #include "alias.h"
@@ -34,32 +33,52 @@ enum help_format {
        HELP_FORMAT_WEB
 };
 
-static const char *html_path;
+enum show_config_type {
+       SHOW_CONFIG_HUMAN,
+       SHOW_CONFIG_VARS,
+       SHOW_CONFIG_SECTIONS,
+};
+
+static enum help_action {
+       HELP_ACTION_ALL = 1,
+       HELP_ACTION_GUIDES,
+       HELP_ACTION_CONFIG,
+       HELP_ACTION_CONFIG_FOR_COMPLETION,
+       HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+} cmd_mode;
 
-static int show_all = 0;
-static int show_guides = 0;
-static int show_config;
+static const char *html_path;
 static int verbose = 1;
-static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
 static int exclude_guides;
 static struct option builtin_help_options[] = {
-       OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
+       OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"),
+                   HELP_ACTION_ALL),
        OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
-       OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
-       OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")),
-       OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN),
        OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
        OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
                        HELP_FORMAT_WEB),
        OPT_SET_INT('i', "info", &help_format, N_("show info page"),
                        HELP_FORMAT_INFO),
        OPT__VERBOSE(&verbose, N_("print command description")),
+
+       OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"),
+                   HELP_ACTION_GUIDES),
+       OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"),
+                   HELP_ACTION_CONFIG),
+       OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "",
+                   HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+       OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
+                   HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+
        OPT_END(),
 };
 
 static const char * const builtin_help_usage[] = {
-       N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
+       N_("git help [-a|--all] [--[no-]verbose]]\n"
+          "         [[-i|--info] [-m|--man] [-w|--web]] [<command>]"),
+       N_("git help [-g|--guides]"),
+       N_("git help [-c|--config]"),
        NULL
 };
 
@@ -70,7 +89,7 @@ struct slot_expansion {
        int found;
 };
 
-static void list_config_help(int for_human)
+static void list_config_help(enum show_config_type type)
 {
        struct slot_expansion slot_expansions[] = {
                { "advice", "*", list_config_advices },
@@ -88,6 +107,8 @@ static void list_config_help(int for_human)
        const char **p;
        struct slot_expansion *e;
        struct string_list keys = STRING_LIST_INIT_DUP;
+       struct string_list keys_uniq = STRING_LIST_INIT_DUP;
+       struct string_list_item *item;
        int i;
 
        for (p = config_name_list; *p; p++) {
@@ -118,34 +139,46 @@ static void list_config_help(int for_human)
        for (i = 0; i < keys.nr; i++) {
                const char *var = keys.items[i].string;
                const char *wildcard, *tag, *cut;
+               const char *dot = NULL;
+               struct strbuf sb = STRBUF_INIT;
 
-               if (for_human) {
+               switch (type) {
+               case SHOW_CONFIG_HUMAN:
                        puts(var);
                        continue;
+               case SHOW_CONFIG_SECTIONS:
+                       dot = strchr(var, '.');
+                       break;
+               case SHOW_CONFIG_VARS:
+                       break;
                }
-
                wildcard = strchr(var, '*');
                tag = strchr(var, '<');
 
-               if (!wildcard && !tag) {
-                       puts(var);
+               if (!dot && !wildcard && !tag) {
+                       string_list_append(&keys_uniq, var);
                        continue;
                }
 
-               if (wildcard && !tag)
+               if (dot)
+                       cut = dot;
+               else if (wildcard && !tag)
                        cut = wildcard;
                else if (!wildcard && tag)
                        cut = tag;
                else
                        cut = wildcard < tag ? wildcard : tag;
 
-               /*
-                * We may produce duplicates, but that's up to
-                * git-completion.bash to handle
-                */
-               printf("%.*s\n", (int)(cut - var), var);
+               strbuf_add(&sb, var, cut - var);
+               string_list_append(&keys_uniq, sb.buf);
+               strbuf_release(&sb);
+
        }
        string_list_clear(&keys, 0);
+       string_list_remove_duplicates(&keys_uniq, 0);
+       for_each_string_list_item(item, &keys_uniq)
+               puts(item->string);
+       string_list_clear(&keys_uniq, 0);
 }
 
 static enum help_format parse_help_format(const char *format)
@@ -349,8 +382,6 @@ static int add_man_viewer_info(const char *var, const char *value)
 
 static int git_help_config(const char *var, const char *value, void *cb)
 {
-       if (starts_with(var, "column."))
-               return git_column_config(var, value, "help", &colopts);
        if (!strcmp(var, "help.format")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -467,11 +498,14 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
        if (!html_path)
                html_path = to_free = system_path(GIT_HTML_PATH);
 
-       /* Check that we have a git documentation directory. */
+       /*
+        * Check that the page we're looking for exists.
+        */
        if (!strstr(html_path, "://")) {
-               if (stat(mkpath("%s/git.html", html_path), &st)
+               if (stat(mkpath("%s/%s.html", html_path, page), &st)
                    || !S_ISREG(st.st_mode))
-                       die("'%s': not a documentation directory.", html_path);
+                       die("'%s/%s.html': documentation file not found.",
+                               html_path, page);
        }
 
        strbuf_init(page_path, 0);
@@ -541,6 +575,13 @@ static const char *check_git_cmd(const char* cmd)
        return cmd;
 }
 
+static void no_extra_argc(int argc)
+{
+       if (argc)
+               usage_msg_opt(_("this option doesn't take any other arguments"),
+                             builtin_help_usage, builtin_help_options);
+}
+
 int cmd_help(int argc, const char **argv, const char *prefix)
 {
        int nongit;
@@ -551,8 +592,8 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                        builtin_help_usage, 0);
        parsed_help_format = help_format;
 
-       if (show_all) {
-               git_config(git_help_config, NULL);
+       switch (cmd_mode) {
+       case HELP_ACTION_ALL:
                if (verbose) {
                        setup_pager();
                        list_all_cmds_help();
@@ -560,30 +601,27 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                }
                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
                load_command_list("git-", &main_cmds, &other_cmds);
-               list_commands(colopts, &main_cmds, &other_cmds);
-       }
-
-       if (show_config) {
-               int for_human = show_config == 1;
-
-               if (!for_human) {
-                       list_config_help(for_human);
-                       return 0;
-               }
-               setup_pager();
-               list_config_help(for_human);
-               printf("\n%s\n", _("'git help config' for more information"));
-               return 0;
-       }
-
-       if (show_guides)
+               list_commands(&main_cmds, &other_cmds);
+               printf("%s\n", _(git_more_info_string));
+               break;
+       case HELP_ACTION_GUIDES:
+               no_extra_argc(argc);
                list_guides_help();
-
-       if (show_all || show_guides) {
                printf("%s\n", _(git_more_info_string));
-               /*
-               * We're done. Ignore any remaining args
-               */
+               return 0;
+       case HELP_ACTION_CONFIG_FOR_COMPLETION:
+               no_extra_argc(argc);
+               list_config_help(SHOW_CONFIG_VARS);
+               return 0;
+       case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION:
+               no_extra_argc(argc);
+               list_config_help(SHOW_CONFIG_SECTIONS);
+               return 0;
+       case HELP_ACTION_CONFIG:
+               no_extra_argc(argc);
+               setup_pager();
+               list_config_help(SHOW_CONFIG_HUMAN);
+               printf("\n%s\n", _("'git help config' for more information"));
                return 0;
        }
 
index f267dce49e2d8c3731c384429e934c29e6c1dd5b..15ae406e6b7e221f720070f3abbea951e2b914a9 100644 (file)
@@ -122,6 +122,7 @@ static int strict;
 static int do_fsck_object;
 static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES;
 static int verbose;
+static const char *progress_title;
 static int show_resolving_progress;
 static int show_stat;
 static int check_self_contained_and_connected;
@@ -187,9 +188,7 @@ static void init_thread(void)
        pthread_key_create(&key, NULL);
        CALLOC_ARRAY(thread_data, nr_threads);
        for (i = 0; i < nr_threads; i++) {
-               thread_data[i].pack_fd = open(curr_pack, O_RDONLY);
-               if (thread_data[i].pack_fd == -1)
-                       die_errno(_("unable to open %s"), curr_pack);
+               thread_data[i].pack_fd = xopen(curr_pack, O_RDONLY);
        }
 
        threads_active = 1;
@@ -1153,6 +1152,7 @@ static void parse_pack_objects(unsigned char *hash)
 
        if (verbose)
                progress = start_progress(
+                               progress_title ? progress_title :
                                from_stdin ? _("Receiving objects") : _("Indexing objects"),
                                nr_objects);
        for (i = 0; i < nr_objects; i++) {
@@ -1415,7 +1415,7 @@ static void fix_unresolved_deltas(struct hashfile *f)
 
                if (check_object_signature(the_repository, &d->oid,
                                           data, size,
-                                          type_name(type)))
+                                          type_name(type), NULL))
                        die(_("local object %s is corrupt"), oid_to_hex(&d->oid));
 
                /*
@@ -1800,6 +1800,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                input_len = sizeof(*hdr);
                        } else if (!strcmp(arg, "-v")) {
                                verbose = 1;
+                       } else if (!strcmp(arg, "--progress-title")) {
+                               if (progress_title || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               progress_title = argv[++i];
                        } else if (!strcmp(arg, "--show-resolving-progress")) {
                                show_resolving_progress = 1;
                        } else if (!strcmp(arg, "--report-end-of-input")) {
index 3d7717ba5ca234c06595e74aaeeab7c5f91114dd..f75d87e8d7fea489639a8e38b7a4704bc75ba0cd 100644 (file)
@@ -637,7 +637,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        repo_init_revisions(the_repository, &rev, prefix);
        rev.diff = 1;
        rev.always_show_header = 1;
-       rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
+       rev.no_walk = 1;
        rev.diffopt.stat_width = -1;    /* Scale to real terminal size */
 
        memset(&opt, 0, sizeof(opt));
index 29a26ad8ae483b6055268ca2a238b8b7e9c09b74..a2000ed6bf2578ae248be18cb00623eb2b8d1fae 100644 (file)
@@ -209,10 +209,8 @@ static void show_submodule(struct repository *superproject,
                           struct dir_struct *dir, const char *path)
 {
        struct repository subrepo;
-       const struct submodule *sub = submodule_from_path(superproject,
-                                                         null_oid(), path);
 
-       if (repo_submodule_init(&subrepo, superproject, sub))
+       if (repo_submodule_init(&subrepo, superproject, path, null_oid()))
                return;
 
        if (repo_read_index(&subrepo) < 0)
@@ -614,7 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
        struct option builtin_ls_files_options[] = {
                /* Think twice before adding "--nul" synonym to this */
                OPT_SET_INT('z', NULL, &line_terminator,
-                       N_("paths are separated with NUL character"), '\0'),
+                       N_("separate paths with the NUL character"), '\0'),
                OPT_BOOL('t', NULL, &show_tag,
                        N_("identify the file status with tags")),
                OPT_BOOL('v', NULL, &show_valid_bit,
@@ -651,7 +649,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                        N_("skip files matching pattern"),
                        PARSE_OPT_NONEG, option_parse_exclude),
                OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"),
-                       N_("exclude patterns are read from <file>"),
+                       N_("read exclude patterns from <file>"),
                        PARSE_OPT_NONEG, option_parse_exclude_from),
                OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
                        N_("read additional per-directory exclude patterns in <file>")),
index f4fd823af831a3b8682d9845ee1dba901f3f0dc5..318949c3d75327d5adf9524f3f550548f89a20d2 100644 (file)
@@ -7,8 +7,8 @@
 
 static const char * const ls_remote_usage[] = {
        N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
-          "                     [-q | --quiet] [--exit-code] [--get-url]\n"
-          "                     [--symref] [<repository> [<refs>...]]"),
+          "              [-q | --quiet] [--exit-code] [--get-url]\n"
+          "              [--symref] [<repository> [<refs>...]]"),
        NULL
 };
 
index 8949a9c2fb7c2acf0f94ea6281c7a5df059e6533..cc4a910c69bb17769f88076a6f4459d399d5a996 100644 (file)
@@ -13,6 +13,7 @@
 #include "builtin.h"
 #include "lockfile.h"
 #include "run-command.h"
+#include "hook.h"
 #include "diff.h"
 #include "diff-merges.h"
 #include "refs.h"
@@ -88,9 +89,9 @@ static int autostash;
 static int no_verify;
 
 static struct strategy all_strategy[] = {
-       { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
+       { "recursive",  NO_TRIVIAL },
        { "octopus",    DEFAULT_OCTOPUS },
-       { "ort",        NO_TRIVIAL },
+       { "ort",        DEFAULT_TWOHEAD | NO_TRIVIAL },
        { "resolve",    0 },
        { "ours",       NO_FAST_FORWARD | NO_TRIVIAL },
        { "subtree",    NO_FAST_FORWARD | NO_TRIVIAL },
@@ -469,7 +470,6 @@ static void finish(struct commit *head_commit,
                         * We ignore errors in 'gc --auto', since the
                         * user should see them.
                         */
-                       close_object_store(the_repository->objects);
                        run_auto_maintenance(verbosity < 0);
                }
        }
@@ -681,6 +681,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head,
        opts.verbose_update = 1;
        opts.trivial_merges_only = 1;
        opts.merge = 1;
+       opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
        trees[nr_trees] = parse_tree_indirect(common);
        if (!trees[nr_trees++])
                return -1;
@@ -849,7 +850,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
         * and write it out as a tree.  We must do this before we invoke
         * the editor and after we invoke run_status above.
         */
-       if (find_hook("pre-merge-commit"))
+       if (hook_exists("pre-merge-commit"))
                discard_cache();
        read_cache_from(index_file);
        strbuf_addbuf(&msg, &merge_msg);
@@ -1276,6 +1277,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_merge_usage, builtin_merge_options);
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        /*
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
@@ -1368,14 +1372,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 * There is no unmerged entry, don't advise 'git
                 * add/rm <file>', just 'git commit'.
                 */
-               if (advice_resolve_conflict)
+               if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
                        die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
                                  "Please, commit your changes before you merge."));
                else
                        die(_("You have not concluded your merge (MERGE_HEAD exists)."));
        }
        if (ref_exists("CHERRY_PICK_HEAD")) {
-               if (advice_resolve_conflict)
+               if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
                        die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
                            "Please, commit your changes before you merge."));
                else
@@ -1485,6 +1489,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        fast_forward = FF_NO;
        }
 
+       if (!use_strategies && !pull_twohead &&
+           remoteheads && !remoteheads->next) {
+               char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
+               if (default_strategy)
+                       append_strategy(get_strategy(default_strategy));
+       }
        if (!use_strategies) {
                if (!remoteheads)
                        ; /* already up-to-date */
index dddcccdd368328261db51d47b3b5a7b444fbaca5..3b2dbbb37e6fe1afc11af79aae774ca526496413 100644 (file)
@@ -62,7 +62,8 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type)
 
        repl = lookup_replace_object(the_repository, tagged_oid);
        ret = check_object_signature(the_repository, repl,
-                                    buffer, size, type_name(*tagged_type));
+                                    buffer, size, type_name(*tagged_type),
+                                    NULL);
        free(buffer);
 
        return ret;
index 8ff0dee2ecbb86881f77bc58ecf86f714a1d8622..075d15d706246a7e4f6a4ec3faecaebce220399f 100644 (file)
@@ -7,7 +7,8 @@
 #include "object-store.h"
 
 #define BUILTIN_MIDX_WRITE_USAGE \
-       N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]")
+       N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \
+          "[--refs-snapshot=<path>]")
 
 #define BUILTIN_MIDX_VERIFY_USAGE \
        N_("git multi-pack-index [<options>] verify")
@@ -45,14 +46,15 @@ static char const * const builtin_multi_pack_index_usage[] = {
 static struct opts_multi_pack_index {
        const char *object_dir;
        const char *preferred_pack;
+       const char *refs_snapshot;
        unsigned long batch_size;
        unsigned flags;
+       int stdin_packs;
 } opts;
 
 static struct option common_opts[] = {
        OPT_FILENAME(0, "object-dir", &opts.object_dir,
          N_("object directory containing set of packfile and pack-index pairs")),
-       OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS),
        OPT_END(),
 };
 
@@ -61,6 +63,33 @@ static struct option *add_common_options(struct option *prev)
        return parse_options_concat(common_opts, prev);
 }
 
+static int git_multi_pack_index_write_config(const char *var, const char *value,
+                                            void *cb)
+{
+       if (!strcmp(var, "pack.writebitmaphashcache")) {
+               if (git_config_bool(var, value))
+                       opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
+               else
+                       opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE;
+       }
+
+       /*
+        * We should never make a fall-back call to 'git_default_config', since
+        * this was already called in 'cmd_multi_pack_index()'.
+        */
+       return 0;
+}
+
+static void read_packs_from_stdin(struct string_list *to)
+{
+       struct strbuf buf = STRBUF_INIT;
+       while (strbuf_getline(&buf, stdin) != EOF)
+               string_list_append(to, buf.buf);
+       string_list_sort(to);
+
+       strbuf_release(&buf);
+}
+
 static int cmd_multi_pack_index_write(int argc, const char **argv)
 {
        struct option *options;
@@ -68,13 +97,27 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
                OPT_STRING(0, "preferred-pack", &opts.preferred_pack,
                           N_("preferred-pack"),
                           N_("pack for reuse when computing a multi-pack bitmap")),
+               OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
+                       MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
+               OPT_BIT(0, "progress", &opts.flags,
+                       N_("force progress reporting"), MIDX_PROGRESS),
+               OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
+                        N_("write multi-pack index containing only given indexes")),
+               OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot,
+                            N_("refs snapshot for selecting bitmap commits")),
                OPT_END(),
        };
 
+       opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
+
+       git_config(git_multi_pack_index_write_config, NULL);
+
        options = add_common_options(builtin_multi_pack_index_write_options);
 
        trace2_cmd_mode(argv[0]);
 
+       if (isatty(2))
+               opts.flags |= MIDX_PROGRESS;
        argc = parse_options(argc, argv, NULL,
                             options, builtin_multi_pack_index_write_usage,
                             PARSE_OPT_KEEP_UNKNOWN);
@@ -84,16 +127,39 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
 
        FREE_AND_NULL(options);
 
+       if (opts.stdin_packs) {
+               struct string_list packs = STRING_LIST_INIT_DUP;
+               int ret;
+
+               read_packs_from_stdin(&packs);
+
+               ret = write_midx_file_only(opts.object_dir, &packs,
+                                          opts.preferred_pack,
+                                          opts.refs_snapshot, opts.flags);
+
+               string_list_clear(&packs, 0);
+
+               return ret;
+
+       }
        return write_midx_file(opts.object_dir, opts.preferred_pack,
-                              opts.flags);
+                              opts.refs_snapshot, opts.flags);
 }
 
 static int cmd_multi_pack_index_verify(int argc, const char **argv)
 {
-       struct option *options = common_opts;
+       struct option *options;
+       static struct option builtin_multi_pack_index_verify_options[] = {
+               OPT_BIT(0, "progress", &opts.flags,
+                       N_("force progress reporting"), MIDX_PROGRESS),
+               OPT_END(),
+       };
+       options = add_common_options(builtin_multi_pack_index_verify_options);
 
        trace2_cmd_mode(argv[0]);
 
+       if (isatty(2))
+               opts.flags |= MIDX_PROGRESS;
        argc = parse_options(argc, argv, NULL,
                             options, builtin_multi_pack_index_verify_usage,
                             PARSE_OPT_KEEP_UNKNOWN);
@@ -106,10 +172,18 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv)
 
 static int cmd_multi_pack_index_expire(int argc, const char **argv)
 {
-       struct option *options = common_opts;
+       struct option *options;
+       static struct option builtin_multi_pack_index_expire_options[] = {
+               OPT_BIT(0, "progress", &opts.flags,
+                       N_("force progress reporting"), MIDX_PROGRESS),
+               OPT_END(),
+       };
+       options = add_common_options(builtin_multi_pack_index_expire_options);
 
        trace2_cmd_mode(argv[0]);
 
+       if (isatty(2))
+               opts.flags |= MIDX_PROGRESS;
        argc = parse_options(argc, argv, NULL,
                             options, builtin_multi_pack_index_expire_usage,
                             PARSE_OPT_KEEP_UNKNOWN);
@@ -126,6 +200,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
        static struct option builtin_multi_pack_index_repack_options[] = {
                OPT_MAGNITUDE(0, "batch-size", &opts.batch_size,
                  N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
+               OPT_BIT(0, "progress", &opts.flags,
+                 N_("force progress reporting"), MIDX_PROGRESS),
                OPT_END(),
        };
 
@@ -133,6 +209,8 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv)
 
        trace2_cmd_mode(argv[0]);
 
+       if (isatty(2))
+               opts.flags |= MIDX_PROGRESS;
        argc = parse_options(argc, argv, NULL,
                             options,
                             builtin_multi_pack_index_repack_usage,
@@ -154,8 +232,6 @@ int cmd_multi_pack_index(int argc, const char **argv,
 
        git_config(git_default_config, NULL);
 
-       if (isatty(2))
-               opts.flags |= MIDX_PROGRESS;
        argc = parse_options(argc, argv, prefix,
                             builtin_multi_pack_index_options,
                             builtin_multi_pack_index_usage,
@@ -164,7 +240,7 @@ int cmd_multi_pack_index(int argc, const char **argv,
        if (!opts.object_dir)
                opts.object_dir = get_object_directory();
 
-       if (argc == 0)
+       if (!argc)
                goto usage;
 
        if (!strcmp(argv[0], "repack"))
@@ -175,10 +251,9 @@ int cmd_multi_pack_index(int argc, const char **argv,
                return cmd_multi_pack_index_verify(argc, argv);
        else if (!strcmp(argv[0], "expire"))
                return cmd_multi_pack_index_expire(argc, argv);
-       else {
-               error(_("unrecognized subcommand: %s"), argv[0]);
+
+       error(_("unrecognized subcommand: %s"), argv[0]);
 usage:
-               usage_with_options(builtin_multi_pack_index_usage,
-                                  builtin_multi_pack_index_options);
-       }
+       usage_with_options(builtin_multi_pack_index_usage,
+                          builtin_multi_pack_index_options);
 }
index c2f96c8e89572dd838eb5a5768d8d1375bd63b74..83a465ba831adf94561a13f3d358926f516fe9ac 100644 (file)
@@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length,
 int cmd_mv(int argc, const char **argv, const char *prefix)
 {
        int i, flags, gitmodules_modified = 0;
-       int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+       int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0;
        struct option builtin_mv_options[] = {
                OPT__VERBOSE(&verbose, N_("be verbose")),
                OPT__DRY_RUN(&show_only, N_("dry run")),
                OPT__FORCE(&force, N_("force move/rename even if target exists"),
                           PARSE_OPT_NOCOMPLETE),
                OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
+               OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
                OPT_END(),
        };
        const char **source, **destination, **dest_path, **submodule_gitfile;
-       enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
+       enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes;
        struct stat st;
        struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
        struct lock_file lock_file = LOCK_INIT;
        struct cache_entry *ce;
+       struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
 
        git_config(git_default_config, NULL);
 
@@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                const char *src = source[i], *dst = destination[i];
                int length, src_is_dir;
                const char *bad = NULL;
+               int skip_sparse = 0;
 
                if (show_only)
                        printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
 
                length = strlen(src);
-               if (lstat(src, &st) < 0)
-                       bad = _("bad source");
-               else if (!strncmp(src, dst, length) &&
+               if (lstat(src, &st) < 0) {
+                       /* only error if existence is expected. */
+                       if (modes[i] != SPARSE)
+                               bad = _("bad source");
+               } else if (!strncmp(src, dst, length) &&
                                (dst[length] == 0 || dst[length] == '/')) {
                        bad = _("can not move directory into itself");
                } else if ((src_is_dir = S_ISDIR(st.st_mode))
@@ -212,11 +217,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                dst_len = strlen(dst);
 
                                for (j = 0; j < last - first; j++) {
-                                       const char *path = active_cache[first + j]->name;
+                                       const struct cache_entry *ce = active_cache[first + j];
+                                       const char *path = ce->name;
                                        source[argc + j] = path;
                                        destination[argc + j] =
                                                prefix_path(dst, dst_len, path + length + 1);
-                                       modes[argc + j] = INDEX;
+                                       modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX;
                                        submodule_gitfile[argc + j] = NULL;
                                }
                                argc += last - first;
@@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                        bad = _("multiple sources for the same target");
                else if (is_dir_sep(dst[strlen(dst) - 1]))
                        bad = _("destination directory does not exist");
-               else
+               else {
+                       /*
+                        * We check if the paths are in the sparse-checkout
+                        * definition as a very final check, since that
+                        * allows us to point the user to the --sparse
+                        * option as a way to have a successful run.
+                        */
+                       if (!ignore_sparse &&
+                           !path_in_sparse_checkout(src, &the_index)) {
+                               string_list_append(&only_match_skip_worktree, src);
+                               skip_sparse = 1;
+                       }
+                       if (!ignore_sparse &&
+                           !path_in_sparse_checkout(dst, &the_index)) {
+                               string_list_append(&only_match_skip_worktree, dst);
+                               skip_sparse = 1;
+                       }
+
+                       if (skip_sparse)
+                               goto remove_entry;
+
                        string_list_insert(&src_for_dst, dst);
+               }
 
                if (!bad)
                        continue;
                if (!ignore_errors)
                        die(_("%s, source=%s, destination=%s"),
                             bad, src, dst);
+remove_entry:
                if (--argc > 0) {
                        int n = argc - i;
                        memmove(source + i, source + i + 1,
@@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (only_match_skip_worktree.nr) {
+               advise_on_updating_sparse_paths(&only_match_skip_worktree);
+               if (!ignore_errors)
+                       return 1;
+       }
+
        for (i = 0; i < argc; i++) {
                const char *src = source[i], *dst = destination[i];
                enum update_mode mode = modes[i];
@@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                        printf(_("Renaming %s to %s\n"), src, dst);
                if (show_only)
                        continue;
-               if (mode != INDEX && rename(src, dst) < 0) {
+               if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) {
                        if (ignore_errors)
                                continue;
                        die_errno(_("renaming '%s' failed"), src);
index a01767a384b4387bba6926bbb373a3e85ea0543e..1a3dd445f83f4852f695b1caacab80764fb0ac79 100644 (file)
@@ -1124,6 +1124,11 @@ static void write_reused_pack(struct hashfile *f)
                                break;
 
                        offset += ewah_bit_ctz64(word >> offset);
+                       /*
+                        * Can use bit positions directly, even for MIDX
+                        * bitmaps. See comment in try_partial_reuse()
+                        * for why.
+                        */
                        write_reused_pack_one(pos + offset, f, &w_curs);
                        display_progress(progress_state, ++written);
                }
@@ -1259,7 +1264,8 @@ static void write_pack_file(void)
 
                                bitmap_writer_show_progress(progress);
                                bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
-                               bitmap_writer_build(&to_pack);
+                               if (bitmap_writer_build(&to_pack) < 0)
+                                       die(_("failed to write bitmap index"));
                                bitmap_writer_finish(written_list, nr_written,
                                                     tmpname.buf, write_bitmap_options);
                                write_bitmap_index = 0;
@@ -3412,13 +3418,9 @@ static void read_object_list_from_stdin(void)
        }
 }
 
-/* Remember to update object flag allocation in object.h */
-#define OBJECT_ADDED (1u<<20)
-
 static void show_commit(struct commit *commit, void *data)
 {
        add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0);
-       commit->object.flags |= OBJECT_ADDED;
 
        if (write_bitmap_index)
                index_commit_for_bitmap(commit);
@@ -3431,7 +3433,6 @@ static void show_object(struct object *obj, const char *name, void *data)
 {
        add_preferred_base_object(name);
        add_object_entry(&obj->oid, obj->type, name, 0);
-       obj->flags |= OBJECT_ADDED;
 
        if (use_delta_islands) {
                const char *p;
@@ -3512,79 +3513,23 @@ static void show_edge(struct commit *commit)
        add_preferred_base(&commit->object.oid);
 }
 
-struct in_pack_object {
-       off_t offset;
-       struct object *object;
-};
-
-struct in_pack {
-       unsigned int alloc;
-       unsigned int nr;
-       struct in_pack_object *array;
-};
-
-static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+static int add_object_in_unpacked_pack(const struct object_id *oid,
+                                      struct packed_git *pack,
+                                      uint32_t pos,
+                                      void *_data)
 {
-       in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p);
-       in_pack->array[in_pack->nr].object = object;
-       in_pack->nr++;
-}
-
-/*
- * Compare the objects in the offset order, in order to emulate the
- * "git rev-list --objects" output that produced the pack originally.
- */
-static int ofscmp(const void *a_, const void *b_)
-{
-       struct in_pack_object *a = (struct in_pack_object *)a_;
-       struct in_pack_object *b = (struct in_pack_object *)b_;
-
-       if (a->offset < b->offset)
-               return -1;
-       else if (a->offset > b->offset)
-               return 1;
-       else
-               return oidcmp(&a->object->oid, &b->object->oid);
+       add_object_entry(oid, OBJ_NONE, "", 0);
+       return 0;
 }
 
 static void add_objects_in_unpacked_packs(void)
 {
-       struct packed_git *p;
-       struct in_pack in_pack;
-       uint32_t i;
-
-       memset(&in_pack, 0, sizeof(in_pack));
-
-       for (p = get_all_packs(the_repository); p; p = p->next) {
-               struct object_id oid;
-               struct object *o;
-
-               if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
-                       continue;
-               if (open_pack_index(p))
-                       die(_("cannot open pack index"));
-
-               ALLOC_GROW(in_pack.array,
-                          in_pack.nr + p->num_objects,
-                          in_pack.alloc);
-
-               for (i = 0; i < p->num_objects; i++) {
-                       nth_packed_object_id(&oid, p, i);
-                       o = lookup_unknown_object(the_repository, &oid);
-                       if (!(o->flags & OBJECT_ADDED))
-                               mark_in_pack_object(o, p, &in_pack);
-                       o->flags |= OBJECT_ADDED;
-               }
-       }
-
-       if (in_pack.nr) {
-               QSORT(in_pack.array, in_pack.nr, ofscmp);
-               for (i = 0; i < in_pack.nr; i++) {
-                       struct object *o = in_pack.array[i].object;
-                       add_object_entry(&o->oid, o->type, "", 0);
-               }
-       }
-       free(in_pack.array);
+       if (for_each_packed_object(add_object_in_unpacked_pack, NULL,
+                                  FOR_EACH_OBJECT_PACK_ORDER |
+                                  FOR_EACH_OBJECT_LOCAL_ONLY |
+                                  FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
+                                  FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))
+               die(_("cannot open pack index"));
 }
 
 static int add_loose_object(const struct object_id *oid, const char *path,
index 02c6ab7cbaafbac6492fd458bedc0f162e81d683..485c9a3c56ff96aa59d9d9b4bfec1ea894ac32dd 100644 (file)
@@ -143,7 +143,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
        expire = TIME_MAX;
        save_commit_buffer = 0;
        read_replace_refs = 0;
-       ref_paranoia = 1;
        repo_init_revisions(the_repository, &revs, prefix);
 
        argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
index b311ea6b9dfda0479b36c96408d21ff4cdda7ae9..ae9f5bd7ccde012abb9412f99f5e96cb98253150 100644 (file)
 #include "wt-status.h"
 #include "commit-reach.h"
 #include "sequencer.h"
+#include "packfile.h"
 
 /**
  * Parses the value of --rebase. If value is a false value, returns
  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
- * "merges", returns REBASE_MERGES. If value is "preserve", returns
- * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if
- * fatal is true, otherwise returns REBASE_INVALID.
+ * "merges", returns REBASE_MERGES. If value is a invalid value, dies with
+ * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
  */
 static enum rebase_type parse_config_rebase(const char *key, const char *value,
                int fatal)
@@ -126,7 +126,7 @@ static struct option pull_options[] = {
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        OPT_CALLBACK_F('r', "rebase", &opt_rebase,
-               "(false|true|merges|preserve|interactive)",
+               "(false|true|merges|interactive)",
                N_("incorporate changes by rebasing rather than merging"),
                PARSE_OPT_OPTARG, parse_opt_rebase),
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
@@ -577,7 +577,7 @@ static int run_fetch(const char *repo, const char **refspecs)
                strvec_pushv(&args, refspecs);
        } else if (*refspecs)
                BUG("refspecs without repo?");
-       ret = run_command_v_opt(args.v, RUN_GIT_CMD);
+       ret = run_command_v_opt(args.v, RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE);
        strvec_clear(&args);
        return ret;
 }
@@ -883,8 +883,6 @@ static int run_rebase(const struct object_id *newbase,
        /* Options passed to git-rebase */
        if (opt_rebase == REBASE_MERGES)
                strvec_push(&args, "--rebase-merges");
-       else if (opt_rebase == REBASE_PRESERVE)
-               strvec_push(&args, "--preserve-merges");
        else if (opt_rebase == REBASE_INTERACTIVE)
                strvec_push(&args, "--interactive");
        if (opt_diffstat)
index e8b10a9b7eda996f3ade72eebeb53cbecc11ed60..4b026ce6c6a90aea7bf50e8f193a6420471c2df3 100644 (file)
@@ -289,42 +289,42 @@ static const char message_advice_ref_needs_update[] =
 
 static void advise_pull_before_push(void)
 {
-       if (!advice_push_non_ff_current || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_pull_before_push));
 }
 
 static void advise_checkout_pull_push(void)
 {
-       if (!advice_push_non_ff_matching || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_NON_FF_MATCHING) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_checkout_pull_push));
 }
 
 static void advise_ref_already_exists(void)
 {
-       if (!advice_push_already_exists || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_ALREADY_EXISTS) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_ref_already_exists));
 }
 
 static void advise_ref_fetch_first(void)
 {
-       if (!advice_push_fetch_first || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_FETCH_FIRST) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_ref_fetch_first));
 }
 
 static void advise_ref_needs_force(void)
 {
-       if (!advice_push_needs_force || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_NEEDS_FORCE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_ref_needs_force));
 }
 
 static void advise_ref_needs_update(void)
 {
-       if (!advice_push_ref_needs_update || !advice_push_update_rejected)
+       if (!advice_enabled(ADVICE_PUSH_REF_NEEDS_UPDATE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
                return;
        advise(_(message_advice_ref_needs_update));
 }
index 485e7b0479488cd0c1de685fc7837738ecf88f0d..2109c4c9e5c1c747ea08c8e2152b610c5159ddbe 100644 (file)
@@ -38,7 +38,7 @@ static int list_tree(struct object_id *oid)
 }
 
 static const char * const read_tree_usage[] = {
-       N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
+       N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
        NULL
 };
 
@@ -53,24 +53,16 @@ static int index_output_cb(const struct option *opt, const char *arg,
 static int exclude_per_directory_cb(const struct option *opt, const char *arg,
                                    int unset)
 {
-       struct dir_struct *dir;
        struct unpack_trees_options *opts;
 
        BUG_ON_OPT_NEG(unset);
 
        opts = (struct unpack_trees_options *)opt->value;
 
-       if (opts->dir)
-               die("more than one --exclude-per-directory given.");
-
-       dir = xcalloc(1, sizeof(*opts->dir));
-       dir->flags |= DIR_SHOW_IGNORED;
-       dir->exclude_per_dir = arg;
-       opts->dir = dir;
-       /* We do not need to nor want to do read-directory
-        * here; we are merely interested in reusing the
-        * per directory ignore stack mechanism.
-        */
+       if (!opts->update)
+               die("--exclude-per-directory is meaningless unless -u");
+       if (strcmp(arg, ".gitignore"))
+               die("--exclude-per-directory argument must be .gitignore");
        return 0;
 }
 
@@ -174,6 +166,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
        if (1 < opts.merge + opts.reset + prefix_set)
                die("Which one? -m, --reset, or --prefix?");
 
+       if (opts.reset)
+               opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
+
        /*
         * NEEDSWORK
         *
@@ -209,8 +204,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
        if ((opts.update || opts.index_only) && !opts.merge)
                die("%s is meaningless without -m, --reset, or --prefix",
                    opts.update ? "-u" : "-i");
-       if ((opts.dir && !opts.update))
-               die("--exclude-per-directory is meaningless unless -u");
+       if (opts.update && !opts.reset)
+               opts.preserve_ignored = 0;
+       /* otherwise, opts.preserve_ignored is irrelevant */
        if (opts.merge && !opts.index_only)
                setup_work_tree();
 
index 66a0a0f0d03bf7a5de08f74670b1b2300cbffdb6..34b4744e5f3b7ccd5c4a215f477a622cfa06e4a0 100644 (file)
@@ -48,8 +48,7 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
 enum rebase_type {
        REBASE_UNSPECIFIED = -1,
        REBASE_APPLY,
-       REBASE_MERGE,
-       REBASE_PRESERVE_MERGES
+       REBASE_MERGE
 };
 
 enum empty_type {
@@ -163,12 +162,7 @@ enum action {
        ACTION_ABORT,
        ACTION_QUIT,
        ACTION_EDIT_TODO,
-       ACTION_SHOW_CURRENT_PATCH,
-       ACTION_SHORTEN_OIDS,
-       ACTION_EXPAND_OIDS,
-       ACTION_CHECK_TODO_LIST,
-       ACTION_REARRANGE_SQUASH,
-       ACTION_ADD_EXEC
+       ACTION_SHOW_CURRENT_PATCH
 };
 
 static const char *action_names[] = { "undefined",
@@ -179,81 +173,6 @@ static const char *action_names[] = { "undefined",
                                      "edit_todo",
                                      "show_current_patch" };
 
-static int add_exec_commands(struct string_list *commands)
-{
-       const char *todo_file = rebase_path_todo();
-       struct todo_list todo_list = TODO_LIST_INIT;
-       int res;
-
-       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
-               return error_errno(_("could not read '%s'."), todo_file);
-
-       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
-                                       &todo_list)) {
-               todo_list_release(&todo_list);
-               return error(_("unusable todo list: '%s'"), todo_file);
-       }
-
-       todo_list_add_exec_commands(&todo_list, commands);
-       res = todo_list_write_to_file(the_repository, &todo_list,
-                                     todo_file, NULL, NULL, -1, 0);
-       todo_list_release(&todo_list);
-
-       if (res)
-               return error_errno(_("could not write '%s'."), todo_file);
-       return 0;
-}
-
-static int rearrange_squash_in_todo_file(void)
-{
-       const char *todo_file = rebase_path_todo();
-       struct todo_list todo_list = TODO_LIST_INIT;
-       int res = 0;
-
-       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
-               return error_errno(_("could not read '%s'."), todo_file);
-       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
-                                       &todo_list)) {
-               todo_list_release(&todo_list);
-               return error(_("unusable todo list: '%s'"), todo_file);
-       }
-
-       res = todo_list_rearrange_squash(&todo_list);
-       if (!res)
-               res = todo_list_write_to_file(the_repository, &todo_list,
-                                             todo_file, NULL, NULL, -1, 0);
-
-       todo_list_release(&todo_list);
-
-       if (res)
-               return error_errno(_("could not write '%s'."), todo_file);
-       return 0;
-}
-
-static int transform_todo_file(unsigned flags)
-{
-       const char *todo_file = rebase_path_todo();
-       struct todo_list todo_list = TODO_LIST_INIT;
-       int res;
-
-       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
-               return error_errno(_("could not read '%s'."), todo_file);
-
-       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
-                                       &todo_list)) {
-               todo_list_release(&todo_list);
-               return error(_("unusable todo list: '%s'"), todo_file);
-       }
-
-       res = todo_list_write_to_file(the_repository, &todo_list, todo_file,
-                                     NULL, NULL, -1, flags);
-       todo_list_release(&todo_list);
-
-       if (res)
-               return error_errno(_("could not write '%s'."), todo_file);
-       return 0;
-}
-
 static int edit_todo_file(unsigned flags)
 {
        const char *todo_file = rebase_path_todo();
@@ -403,8 +322,8 @@ static int run_sequencer_rebase(struct rebase_options *opts,
        flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
        flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
-       flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
        flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
+       flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0;
 
        switch (command) {
        case ACTION_NONE: {
@@ -438,24 +357,6 @@ static int run_sequencer_rebase(struct rebase_options *opts,
 
                break;
        }
-       case ACTION_SHORTEN_OIDS:
-       case ACTION_EXPAND_OIDS:
-               ret = transform_todo_file(flags);
-               break;
-       case ACTION_CHECK_TODO_LIST:
-               ret = check_todo_list_from_file(the_repository);
-               break;
-       case ACTION_REARRANGE_SQUASH:
-               ret = rearrange_squash_in_todo_file();
-               break;
-       case ACTION_ADD_EXEC: {
-               struct string_list commands = STRING_LIST_INIT_DUP;
-
-               split_exec_commands(opts->cmd, &commands);
-               ret = add_exec_commands(&commands);
-               string_list_clear(&commands, 0);
-               break;
-       }
        default:
                BUG("invalid command '%d'", command);
        }
@@ -477,102 +378,9 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,
        return 0;
 }
 
-static const char * const builtin_rebase_interactive_usage[] = {
-       N_("git rebase--interactive [<options>]"),
-       NULL
-};
-
-int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
-{
-       struct rebase_options opts = REBASE_OPTIONS_INIT;
-       struct object_id squash_onto = *null_oid();
-       enum action command = ACTION_NONE;
-       struct option options[] = {
-               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
-                          REBASE_FORCE),
-               OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
-                       N_("keep commits which start empty"),
-                       PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
-                       parse_opt_keep_empty),
-               OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
-                          N_("allow commits with empty messages"),
-                          PARSE_OPT_HIDDEN),
-               OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
-               OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
-                        N_("keep original branch points of cousins")),
-               OPT_BOOL(0, "autosquash", &opts.autosquash,
-                        N_("move commits that begin with squash!/fixup!")),
-               OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
-               OPT_BIT('v', "verbose", &opts.flags,
-                       N_("display a diffstat of what changed upstream"),
-                       REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
-               OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
-                           ACTION_CONTINUE),
-               OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP),
-               OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
-                           ACTION_EDIT_TODO),
-               OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
-                           ACTION_SHOW_CURRENT_PATCH),
-               OPT_CMDMODE(0, "shorten-ids", &command,
-                       N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS),
-               OPT_CMDMODE(0, "expand-ids", &command,
-                       N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS),
-               OPT_CMDMODE(0, "check-todo-list", &command,
-                       N_("check the todo list"), ACTION_CHECK_TODO_LIST),
-               OPT_CMDMODE(0, "rearrange-squash", &command,
-                       N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH),
-               OPT_CMDMODE(0, "add-exec-commands", &command,
-                       N_("insert exec commands in todo list"), ACTION_ADD_EXEC),
-               { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"),
-                 PARSE_OPT_NONEG, parse_opt_commit, 0 },
-               { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision,
-                 N_("restrict-revision"), N_("restrict revision"),
-                 PARSE_OPT_NONEG, parse_opt_commit, 0 },
-               { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"),
-                 N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 },
-               { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"),
-                 N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit,
-                 0 },
-               OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")),
-               { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"),
-                       N_("GPG-sign commits"),
-                       PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
-               OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
-                          N_("rebase strategy")),
-               OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"),
-                          N_("strategy options")),
-               OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"),
-                          N_("the branch or commit to checkout")),
-               OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")),
-               OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")),
-               OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate),
-               OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
-                        N_("automatically re-schedule any `exec` that fails")),
-               OPT_END()
-       };
-
-       opts.rebase_cousins = -1;
-
-       if (argc == 1)
-               usage_with_options(builtin_rebase_interactive_usage, options);
-
-       argc = parse_options(argc, argv, prefix, options,
-                       builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
-
-       if (!is_null_oid(&squash_onto))
-               opts.squash_onto = &squash_onto;
-
-       if (opts.rebase_cousins >= 0 && !opts.rebase_merges)
-               warning(_("--[no-]rebase-cousins has no effect without "
-                         "--rebase-merges"));
-
-       return !!run_sequencer_rebase(&opts, command);
-}
-
 static int is_merge(struct rebase_options *opts)
 {
-       return opts->type == REBASE_MERGE ||
-               opts->type == REBASE_PRESERVE_MERGES;
+       return opts->type == REBASE_MERGE;
 }
 
 static void imply_merge(struct rebase_options *opts, const char *option)
@@ -582,7 +390,6 @@ static void imply_merge(struct rebase_options *opts, const char *option)
                die(_("%s requires the merge backend"), option);
                break;
        case REBASE_MERGE:
-       case REBASE_PRESERVE_MERGES:
                break;
        default:
                opts->type = REBASE_MERGE; /* implied */
@@ -740,7 +547,6 @@ static int finish_rebase(struct rebase_options *opts)
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
        unlink(git_path_auto_merge(the_repository));
        apply_autostash(state_dir_path("autostash", opts));
-       close_object_store(the_repository->objects);
        /*
         * We ignore errors in 'git maintenance run --auto', since the
         * user should see them.
@@ -762,28 +568,6 @@ static int finish_rebase(struct rebase_options *opts)
        return ret;
 }
 
-static struct commit *peel_committish(const char *name)
-{
-       struct object *obj;
-       struct object_id oid;
-
-       if (get_oid(name, &oid))
-               return NULL;
-       obj = parse_object(the_repository, &oid);
-       return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
-}
-
-static void add_var(struct strbuf *buf, const char *name, const char *value)
-{
-       if (!value)
-               strbuf_addf(buf, "unset %s; ", name);
-       else {
-               strbuf_addf(buf, "%s=", name);
-               sq_quote_buf(buf, value);
-               strbuf_addstr(buf, "; ");
-       }
-}
-
 static int move_to_original_branch(struct rebase_options *opts)
 {
        struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -940,10 +724,7 @@ static int run_am(struct rebase_options *opts)
 
 static int run_specific_rebase(struct rebase_options *opts, enum action action)
 {
-       const char *argv[] = { NULL, NULL };
-       struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
        int status;
-       const char *backend, *backend_func;
 
        if (opts->type == REBASE_MERGE) {
                /* Run sequencer-based rebase */
@@ -960,87 +741,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
                }
 
                status = run_sequencer_rebase(opts, action);
-               goto finished_rebase;
-       }
-
-       if (opts->type == REBASE_APPLY) {
+       } else if (opts->type == REBASE_APPLY)
                status = run_am(opts);
-               goto finished_rebase;
-       }
-
-       add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
-       add_var(&script_snippet, "state_dir", opts->state_dir);
-
-       add_var(&script_snippet, "upstream_name", opts->upstream_name);
-       add_var(&script_snippet, "upstream", opts->upstream ?
-               oid_to_hex(&opts->upstream->object.oid) : NULL);
-       add_var(&script_snippet, "head_name",
-               opts->head_name ? opts->head_name : "detached HEAD");
-       add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
-       add_var(&script_snippet, "onto", opts->onto ?
-               oid_to_hex(&opts->onto->object.oid) : NULL);
-       add_var(&script_snippet, "onto_name", opts->onto_name);
-       add_var(&script_snippet, "revisions", opts->revisions);
-       add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
-               oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
-       sq_quote_argv_pretty(&buf, opts->git_am_opts.v);
-       add_var(&script_snippet, "git_am_opt", buf.buf);
-       strbuf_release(&buf);
-       add_var(&script_snippet, "verbose",
-               opts->flags & REBASE_VERBOSE ? "t" : "");
-       add_var(&script_snippet, "diffstat",
-               opts->flags & REBASE_DIFFSTAT ? "t" : "");
-       add_var(&script_snippet, "force_rebase",
-               opts->flags & REBASE_FORCE ? "t" : "");
-       if (opts->switch_to)
-               add_var(&script_snippet, "switch_to", opts->switch_to);
-       add_var(&script_snippet, "action", opts->action ? opts->action : "");
-       add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
-       add_var(&script_snippet, "allow_rerere_autoupdate",
-               opts->allow_rerere_autoupdate ?
-                       opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
-                       "--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
-       add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
-       add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
-       add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
-       add_var(&script_snippet, "cmd", opts->cmd);
-       add_var(&script_snippet, "allow_empty_message",
-               opts->allow_empty_message ?  "--allow-empty-message" : "");
-       add_var(&script_snippet, "rebase_merges",
-               opts->rebase_merges ? "t" : "");
-       add_var(&script_snippet, "rebase_cousins",
-               opts->rebase_cousins ? "t" : "");
-       add_var(&script_snippet, "strategy", opts->strategy);
-       add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
-       add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
-       add_var(&script_snippet, "squash_onto",
-               opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
-       add_var(&script_snippet, "git_format_patch_opt",
-               opts->git_format_patch_opt.buf);
-
-       if (is_merge(opts) &&
-           !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
-               strbuf_addstr(&script_snippet,
-                             "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
-               opts->autosquash = 0;
-       }
-
-       switch (opts->type) {
-       case REBASE_PRESERVE_MERGES:
-               backend = "git-rebase--preserve-merges";
-               backend_func = "git_rebase__preserve_merges";
-               break;
-       default:
+       else
                BUG("Unhandled rebase type %d", opts->type);
-               break;
-       }
-
-       strbuf_addf(&script_snippet,
-                   ". git-sh-setup && . %s && %s", backend, backend_func);
-       argv[0] = script_snippet.buf;
 
-       status = run_command_v_opt(argv, RUN_USING_SHELL);
-finished_rebase:
        if (opts->dont_finish_rebase)
                ; /* do nothing */
        else if (opts->type == REBASE_MERGE)
@@ -1058,8 +763,6 @@ finished_rebase:
                die("Nothing to do");
        }
 
-       strbuf_release(&script_snippet);
-
        return status ? -1 : 0;
 }
 
@@ -1195,7 +898,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
-/* -i followed by -p is still explicitly interactive, but -p alone is not */
+/* -i followed by -r is still explicitly interactive, but -r alone is not */
 static int parse_opt_interactive(const struct option *opt, const char *arg,
                                 int unset)
 {
@@ -1313,6 +1016,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        char *squash_onto_name = NULL;
        int reschedule_failed_exec = -1;
        int allow_preemptive_ff = 1;
+       int preserve_merges_selected = 0;
        struct option builtin_rebase_options[] = {
                OPT_STRING(0, "onto", &options.onto_name,
                           N_("revision"),
@@ -1377,10 +1081,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        N_("let the user edit the list of commits to rebase"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        parse_opt_interactive),
-               OPT_SET_INT_F('p', "preserve-merges", &options.type,
+               OPT_SET_INT_F('p', "preserve-merges", &preserve_merges_selected,
                              N_("(DEPRECATED) try to recreate merges instead of "
                                 "ignoring them"),
-                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
+                             1, PARSE_OPT_HIDDEN),
                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
                OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
                               N_("how to handle commits that become empty"),
@@ -1430,6 +1134,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        options.allow_empty_message = 1;
        git_config(rebase_config, &options);
        /* options.gpg_sign_opt will be either "-S" or NULL */
@@ -1448,8 +1155,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                strbuf_reset(&buf);
                strbuf_addf(&buf, "%s/rewritten", merge_dir());
                if (is_directory(buf.buf)) {
-                       options.type = REBASE_PRESERVE_MERGES;
-                       options.flags |= REBASE_INTERACTIVE_EXPLICIT;
+                       die("`rebase -p` is no longer supported");
                } else {
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "%s/interactive", merge_dir());
@@ -1470,6 +1176,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                             builtin_rebase_options,
                             builtin_rebase_usage, 0);
 
+       if (preserve_merges_selected)
+               die(_("--preserve-merges was replaced by --rebase-merges"));
+
        if (action != ACTION_NONE && total_argc != 2) {
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
@@ -1479,10 +1188,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
 
-       if (options.type == REBASE_PRESERVE_MERGES)
-               warning(_("git rebase --preserve-merges is deprecated. "
-                         "Use --rebase-merges instead."));
-
        if (keep_base) {
                if (options.onto_name)
                        die(_("cannot combine '--keep-base' with '--onto'"));
@@ -1574,7 +1279,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        die(_("could not move back to %s"),
                            oid_to_hex(&options.orig_head));
                remove_branch_state(the_repository, 0);
-               ret = !!finish_rebase(&options);
+               ret = finish_rebase(&options);
                goto cleanup;
        }
        case ACTION_QUIT: {
@@ -1583,11 +1288,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        struct replay_opts replay = REPLAY_OPTS_INIT;
 
                        replay.action = REPLAY_INTERACTIVE_REBASE;
-                       ret = !!sequencer_remove_state(&replay);
+                       ret = sequencer_remove_state(&replay);
                } else {
                        strbuf_reset(&buf);
                        strbuf_addstr(&buf, options.state_dir);
-                       ret = !!remove_dir_recursively(&buf, 0);
+                       ret = remove_dir_recursively(&buf, 0);
                        if (ret)
                                error(_("could not remove '%s'"),
                                       options.state_dir);
@@ -1702,7 +1407,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                if (options.ignore_date)
                        strvec_push(&options.git_am_opts, "--ignore-date");
        } else {
-               /* REBASE_MERGE and PRESERVE_MERGES */
+               /* REBASE_MERGE */
                if (ignore_whitespace) {
                        string_list_append(&strategy_options,
                                           "ignore-space-change");
@@ -1713,7 +1418,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                int i;
 
                if (!options.strategy)
-                       options.strategy = "recursive";
+                       options.strategy = "ort";
 
                strbuf_reset(&buf);
                for (i = 0; i < strategy_options.nr; i++)
@@ -1728,7 +1433,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                case REBASE_APPLY:
                        die(_("--strategy requires --merge or --interactive"));
                case REBASE_MERGE:
-               case REBASE_PRESERVE_MERGES:
                        /* compatible */
                        break;
                case REBASE_UNSPECIFIED:
@@ -1780,7 +1484,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        switch (options.type) {
        case REBASE_MERGE:
-       case REBASE_PRESERVE_MERGES:
                options.state_dir = merge_dir();
                break;
        case REBASE_APPLY:
@@ -1805,28 +1508,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                options.reschedule_failed_exec = reschedule_failed_exec;
 
        if (options.signoff) {
-               if (options.type == REBASE_PRESERVE_MERGES)
-                       die("cannot combine '--signoff' with "
-                           "'--preserve-merges'");
                strvec_push(&options.git_am_opts, "--signoff");
                options.flags |= REBASE_FORCE;
        }
 
-       if (options.type == REBASE_PRESERVE_MERGES) {
-               /*
-                * Note: incompatibility with --signoff handled in signoff block above
-                * Note: incompatibility with --interactive is just a strong warning;
-                *       git-rebase.txt caveats with "unless you know what you are doing"
-                */
-               if (options.rebase_merges)
-                       die(_("cannot combine '--preserve-merges' with "
-                             "'--rebase-merges'"));
-
-               if (options.reschedule_failed_exec)
-                       die(_("error: cannot combine '--preserve-merges' with "
-                             "'--reschedule-failed-exec'"));
-       }
-
        if (!options.root) {
                if (argc < 1) {
                        struct branch *branch;
@@ -1845,7 +1530,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        if (!strcmp(options.upstream_name, "-"))
                                options.upstream_name = "@{-1}";
                }
-               options.upstream = peel_committish(options.upstream_name);
+               options.upstream =
+                       lookup_commit_reference_by_name(options.upstream_name);
                if (!options.upstream)
                        die(_("invalid upstream '%s'"), options.upstream_name);
                options.upstream_arg = options.upstream_name;
@@ -1888,7 +1574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                options.onto = lookup_commit_or_die(&merge_base,
                                                    options.onto_name);
        } else {
-               options.onto = peel_committish(options.onto_name);
+               options.onto =
+                       lookup_commit_reference_by_name(options.onto_name);
                if (!options.onto)
                        die(_("Does not point to a valid commit '%s'"),
                                options.onto_name);
@@ -1913,13 +1600,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        die_if_checked_out(buf.buf, 1);
                        options.head_name = xstrdup(buf.buf);
                /* If not is it a valid ref (branch or commit)? */
-               } else if (!get_oid(branch_name, &options.orig_head) &&
-                          lookup_commit_reference(the_repository,
-                                                  &options.orig_head))
+               } else {
+                       struct commit *commit =
+                               lookup_commit_reference_by_name(branch_name);
+                       if (!commit)
+                               die(_("no such branch/commit '%s'"),
+                                   branch_name);
+                       oidcpy(&options.orig_head, &commit->object.oid);
                        options.head_name = NULL;
-               else
-                       die(_("no such branch/commit '%s'"),
-                           branch_name);
+               }
        } else if (argc == 0) {
                /* Do not need to switch branches, we are already on it. */
                options.head_name =
@@ -1959,7 +1648,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
        if (require_clean_work_tree(the_repository, "rebase",
                                    _("Please commit or stash them."), 1, 1)) {
-               ret = 1;
+               ret = -1;
                goto cleanup;
        }
 
@@ -1994,7 +1683,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                                               RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
                                               NULL, buf.buf,
                                               DEFAULT_REFLOG_ACTION) < 0) {
-                                       ret = !!error(_("could not switch to "
+                                       ret = error(_("could not switch to "
                                                        "%s"),
                                                      options.switch_to);
                                        goto cleanup;
@@ -2009,7 +1698,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        else
                                printf(_("Current branch %s is up to date.\n"),
                                       branch_name);
-                       ret = !!finish_rebase(&options);
+                       ret = finish_rebase(&options);
                        goto cleanup;
                } else if (!(options.flags & REBASE_NO_QUIET))
                        ; /* be quiet */
@@ -2087,7 +1776,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                           RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
                           DEFAULT_REFLOG_ACTION);
                strbuf_release(&msg);
-               ret = !!finish_rebase(&options);
+               ret = finish_rebase(&options);
                goto cleanup;
        }
 
@@ -2101,7 +1790,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
        options.revisions = revisions.buf;
 
 run_rebase:
-       ret = !!run_specific_rebase(&options, action);
+       ret = run_specific_rebase(&options, action);
 
 cleanup:
        strbuf_release(&buf);
@@ -2112,5 +1801,5 @@ cleanup:
        free(options.strategy);
        strbuf_release(&options.git_format_patch_opt);
        free(squash_onto_name);
-       return ret;
+       return !!ret;
 }
index 2d1f97e1ca7b5346d9cae3239165d6824df309af..49b846d960522ad1a5f29f7394bca4fa24e5b622 100644 (file)
@@ -7,6 +7,7 @@
 #include "pkt-line.h"
 #include "sideband.h"
 #include "run-command.h"
+#include "hook.h"
 #include "exec-cmd.h"
 #include "commit.h"
 #include "object.h"
@@ -131,6 +132,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
 {
        int status = parse_hide_refs_config(var, value, "receive");
 
+       if (status)
+               return status;
+
+       status = git_gpg_config(var, value, NULL);
        if (status)
                return status;
 
@@ -1306,7 +1311,7 @@ static void refuse_unconfigured_deny_delete_current(void)
        rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
 }
 
-static int command_singleton_iterator(void *cb_data, struct object_id *oid);
+static const struct object_id *command_singleton_iterator(void *cb_data);
 static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
 {
        struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
@@ -1463,7 +1468,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w
 
        strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
 
-       if (!find_hook(push_to_checkout_hook))
+       if (!hook_exists(push_to_checkout_hook))
                retval = push_to_deploy(sha1, &env, work_tree);
        else
                retval = push_to_checkout(sha1, &env, work_tree);
@@ -1731,16 +1736,15 @@ static void check_aliased_updates(struct command *commands)
        string_list_clear(&ref_list, 0);
 }
 
-static int command_singleton_iterator(void *cb_data, struct object_id *oid)
+static const struct object_id *command_singleton_iterator(void *cb_data)
 {
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
 
        if (!cmd || is_null_oid(&cmd->new_oid))
-               return -1; /* end of list */
+               return NULL;
        *cmd_list = NULL; /* this returns only one */
-       oidcpy(oid, &cmd->new_oid);
-       return 0;
+       return &cmd->new_oid;
 }
 
 static void set_connectivity_errors(struct command *commands,
@@ -1770,7 +1774,7 @@ struct iterate_data {
        struct shallow_info *si;
 };
 
-static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_receive_command_list(void *cb_data)
 {
        struct iterate_data *data = cb_data;
        struct command **cmd_list = &data->cmds;
@@ -1781,13 +1785,11 @@ static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
                        /* to be checked in update_shallow_ref() */
                        continue;
                if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
-                       oidcpy(oid, &cmd->new_oid);
                        *cmd_list = cmd->next;
-                       return 0;
+                       return &cmd->new_oid;
                }
        }
-       *cmd_list = NULL;
-       return -1; /* end of list */
+       return NULL;
 }
 
 static void reject_updates_to_hidden(struct command *commands)
@@ -2477,7 +2479,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT__QUIET(&quiet, N_("quiet")),
                OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL),
-               OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL),
+               OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, NULL),
+               OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
                OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL),
                OPT_END()
        };
@@ -2580,10 +2583,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        proc.no_stdin = 1;
                        proc.stdout_to_stderr = 1;
                        proc.err = use_sideband ? -1 : 0;
-                       proc.git_cmd = 1;
+                       proc.git_cmd = proc.close_object_store = 1;
                        proc.argv = argv_gc_auto;
 
-                       close_object_store(the_repository->objects);
                        if (!start_command(&proc)) {
                                if (use_sideband)
                                        copy_to_sideband(proc.err, -1, NULL);
index 09541d1c80483c1a20802ba34a747a1674811165..bd4c669918d3d0884c015e9e2650bb65ea507d7a 100644 (file)
@@ -629,8 +629,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                free_worktrees(worktrees);
                for (i = 0; i < collected.nr; i++) {
                        struct collected_reflog *e = collected.e[i];
+
                        set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
-                       status |= reflog_expire(e->reflog, &e->oid, flags,
+                       status |= reflog_expire(e->reflog, flags,
                                                reflog_expiry_prepare,
                                                should_expire_reflog_ent,
                                                reflog_expiry_cleanup,
@@ -642,13 +643,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 
        for (; i < argc; i++) {
                char *ref;
-               struct object_id oid;
-               if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
+               if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) {
                        status |= error(_("%s points nowhere!"), argv[i]);
                        continue;
                }
                set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
-               status |= reflog_expire(ref, &oid, flags,
+               status |= reflog_expire(ref, flags,
                                        reflog_expiry_prepare,
                                        should_expire_reflog_ent,
                                        reflog_expiry_cleanup,
@@ -700,7 +700,6 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
 
        for ( ; i < argc; i++) {
                const char *spec = strstr(argv[i], "@{");
-               struct object_id oid;
                char *ep, *ref;
                int recno;
 
@@ -709,7 +708,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
                        continue;
                }
 
-               if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
+               if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {
                        status |= error(_("no reflog for '%s'"), argv[i]);
                        continue;
                }
@@ -724,7 +723,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
                        cb.cmd.expire_total = 0;
                }
 
-               status |= reflog_expire(ref, &oid, flags,
+               status |= reflog_expire(ref, flags,
                                        reflog_expiry_prepare,
                                        should_expire_reflog_ent,
                                        reflog_expiry_cleanup,
index 7f88e6ce9deb7666b7ce5cb9179b895a25281e64..299c466116debac6b8c69658c5f499454496fdbd 100644 (file)
@@ -318,6 +318,9 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                 * truth value with >= REBASE_TRUE.
                 */
                info->rebase = rebase_parse_value(value);
+               if (info->rebase == REBASE_INVALID)
+                       warning(_("unhandled branch.%s.rebase=%s; assuming "
+                                 "'true'"), name, value);
                break;
        case PUSH_REMOTE:
                if (info->push_remote_name)
@@ -344,6 +347,14 @@ struct ref_states {
        int queried;
 };
 
+#define REF_STATES_INIT { \
+       .new_refs = STRING_LIST_INIT_DUP, \
+       .stale = STRING_LIST_INIT_DUP, \
+       .tracked = STRING_LIST_INIT_DUP, \
+       .heads = STRING_LIST_INIT_DUP, \
+       .push = STRING_LIST_INIT_DUP, \
+}
+
 static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
 {
        struct ref *fetch_map = NULL, **tail = &fetch_map;
@@ -355,9 +366,6 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
                        die(_("Could not get fetch map for refspec %s"),
                                states->remote->fetch.raw[i]);
 
-       states->new_refs.strdup_strings = 1;
-       states->tracked.strdup_strings = 1;
-       states->stale.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
                if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
                        string_list_append(&states->new_refs, abbrev_branch(ref->name));
@@ -406,7 +414,6 @@ static int get_push_ref_states(const struct ref *remote_refs,
 
        match_push_refs(local_refs, &push_map, &remote->push, MATCH_REFS_NONE);
 
-       states->push.strdup_strings = 1;
        for (ref = push_map; ref; ref = ref->next) {
                struct string_list_item *item;
                struct push_info *info;
@@ -449,7 +456,6 @@ static int get_push_ref_states_noquery(struct ref_states *states)
        if (remote->mirror)
                return 0;
 
-       states->push.strdup_strings = 1;
        if (!remote->push.nr) {
                item = string_list_append(&states->push, _("(matching)"));
                info = item->util = xcalloc(1, sizeof(struct push_info));
@@ -483,7 +489,6 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
        refspec.force = 0;
        refspec.pattern = 1;
        refspec.src = refspec.dst = "refs/heads/*";
-       states->heads.strdup_strings = 1;
        get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
        matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
                                    fetch_map, 1);
@@ -970,26 +975,31 @@ static int get_remote_ref_states(const char *name,
 }
 
 struct show_info {
-       struct string_list *list;
-       struct ref_states *states;
+       struct string_list list;
+       struct ref_states states;
        int width, width2;
        int any_rebase;
 };
 
+#define SHOW_INFO_INIT { \
+       .list = STRING_LIST_INIT_DUP, \
+       .states = REF_STATES_INIT, \
+}
+
 static int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
 {
        struct show_info *info = cb_data;
        int n = strlen(item->string);
        if (n > info->width)
                info->width = n;
-       string_list_insert(info->list, item->string);
+       string_list_insert(&info->list, item->string);
        return 0;
 }
 
 static int show_remote_info_item(struct string_list_item *item, void *cb_data)
 {
        struct show_info *info = cb_data;
-       struct ref_states *states = info->states;
+       struct ref_states *states = &info->states;
        const char *name = item->string;
 
        if (states->queried) {
@@ -1016,7 +1026,7 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data)
 static int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
 {
        struct show_info *show_info = cb_data;
-       struct ref_states *states = show_info->states;
+       struct ref_states *states = &show_info->states;
        struct branch_info *branch_info = branch_item->util;
        struct string_list_item *item;
        int n;
@@ -1029,7 +1039,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb
        if (branch_info->rebase >= REBASE_TRUE)
                show_info->any_rebase = 1;
 
-       item = string_list_insert(show_info->list, branch_item->string);
+       item = string_list_insert(&show_info->list, branch_item->string);
        item->util = branch_info;
 
        return 0;
@@ -1084,7 +1094,7 @@ static int add_push_to_show_info(struct string_list_item *push_item, void *cb_da
                show_info->width = n;
        if ((n = strlen(push_info->dest)) > show_info->width2)
                show_info->width2 = n;
-       item = string_list_append(show_info->list, push_item->string);
+       item = string_list_append(&show_info->list, push_item->string);
        item->util = push_item->util;
        return 0;
 }
@@ -1212,9 +1222,7 @@ static int show(int argc, const char **argv)
                OPT_BOOL('n', NULL, &no_query, N_("do not query remotes")),
                OPT_END()
        };
-       struct ref_states states;
-       struct string_list info_list = STRING_LIST_INIT_NODUP;
-       struct show_info info;
+       struct show_info info = SHOW_INFO_INIT;
 
        argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage,
                             0);
@@ -1225,26 +1233,22 @@ static int show(int argc, const char **argv)
        if (!no_query)
                query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
 
-       memset(&states, 0, sizeof(states));
-       memset(&info, 0, sizeof(info));
-       info.states = &states;
-       info.list = &info_list;
        for (; argc; argc--, argv++) {
                int i;
                const char **url;
                int url_nr;
 
-               get_remote_ref_states(*argv, &states, query_flag);
+               get_remote_ref_states(*argv, &info.states, query_flag);
 
                printf_ln(_("* remote %s"), *argv);
-               printf_ln(_("  Fetch URL: %s"), states.remote->url_nr > 0 ?
-                      states.remote->url[0] : _("(no URL)"));
-               if (states.remote->pushurl_nr) {
-                       url = states.remote->pushurl;
-                       url_nr = states.remote->pushurl_nr;
+               printf_ln(_("  Fetch URL: %s"), info.states.remote->url_nr > 0 ?
+                      info.states.remote->url[0] : _("(no URL)"));
+               if (info.states.remote->pushurl_nr) {
+                       url = info.states.remote->pushurl;
+                       url_nr = info.states.remote->pushurl_nr;
                } else {
-                       url = states.remote->url;
-                       url_nr = states.remote->url_nr;
+                       url = info.states.remote->url;
+                       url_nr = info.states.remote->url_nr;
                }
                for (i = 0; i < url_nr; i++)
                        /*
@@ -1257,57 +1261,57 @@ static int show(int argc, const char **argv)
                        printf_ln(_("  Push  URL: %s"), _("(no URL)"));
                if (no_query)
                        printf_ln(_("  HEAD branch: %s"), _("(not queried)"));
-               else if (!states.heads.nr)
+               else if (!info.states.heads.nr)
                        printf_ln(_("  HEAD branch: %s"), _("(unknown)"));
-               else if (states.heads.nr == 1)
-                       printf_ln(_("  HEAD branch: %s"), states.heads.items[0].string);
+               else if (info.states.heads.nr == 1)
+                       printf_ln(_("  HEAD branch: %s"), info.states.heads.items[0].string);
                else {
                        printf(_("  HEAD branch (remote HEAD is ambiguous,"
                                 " may be one of the following):\n"));
-                       for (i = 0; i < states.heads.nr; i++)
-                               printf("    %s\n", states.heads.items[i].string);
+                       for (i = 0; i < info.states.heads.nr; i++)
+                               printf("    %s\n", info.states.heads.items[i].string);
                }
 
                /* remote branch info */
                info.width = 0;
-               for_each_string_list(&states.new_refs, add_remote_to_show_info, &info);
-               for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
-               for_each_string_list(&states.stale, add_remote_to_show_info, &info);
-               if (info.list->nr)
+               for_each_string_list(&info.states.new_refs, add_remote_to_show_info, &info);
+               for_each_string_list(&info.states.tracked, add_remote_to_show_info, &info);
+               for_each_string_list(&info.states.stale, add_remote_to_show_info, &info);
+               if (info.list.nr)
                        printf_ln(Q_("  Remote branch:%s",
                                     "  Remote branches:%s",
-                                    info.list->nr),
+                                    info.list.nr),
                                  no_query ? _(" (status not queried)") : "");
-               for_each_string_list(info.list, show_remote_info_item, &info);
-               string_list_clear(info.list, 0);
+               for_each_string_list(&info.list, show_remote_info_item, &info);
+               string_list_clear(&info.list, 0);
 
                /* git pull info */
                info.width = 0;
                info.any_rebase = 0;
                for_each_string_list(&branch_list, add_local_to_show_info, &info);
-               if (info.list->nr)
+               if (info.list.nr)
                        printf_ln(Q_("  Local branch configured for 'git pull':",
                                     "  Local branches configured for 'git pull':",
-                                    info.list->nr));
-               for_each_string_list(info.list, show_local_info_item, &info);
-               string_list_clear(info.list, 0);
+                                    info.list.nr));
+               for_each_string_list(&info.list, show_local_info_item, &info);
+               string_list_clear(&info.list, 0);
 
                /* git push info */
-               if (states.remote->mirror)
+               if (info.states.remote->mirror)
                        printf_ln(_("  Local refs will be mirrored by 'git push'"));
 
                info.width = info.width2 = 0;
-               for_each_string_list(&states.push, add_push_to_show_info, &info);
-               QSORT(info.list->items, info.list->nr, cmp_string_with_push);
-               if (info.list->nr)
+               for_each_string_list(&info.states.push, add_push_to_show_info, &info);
+               QSORT(info.list.items, info.list.nr, cmp_string_with_push);
+               if (info.list.nr)
                        printf_ln(Q_("  Local ref configured for 'git push'%s:",
                                     "  Local refs configured for 'git push'%s:",
-                                    info.list->nr),
+                                    info.list.nr),
                                  no_query ? _(" (status not queried)") : "");
-               for_each_string_list(info.list, show_push_info_item, &info);
-               string_list_clear(info.list, 0);
+               for_each_string_list(&info.list, show_push_info_item, &info);
+               string_list_clear(&info.list, 0);
 
-               free_remote_ref_states(&states);
+               free_remote_ref_states(&info.states);
        }
 
        return result;
@@ -1334,8 +1338,7 @@ static int set_head(int argc, const char **argv)
        if (!opt_a && !opt_d && argc == 2) {
                head_name = xstrdup(argv[1]);
        } else if (opt_a && !opt_d && argc == 1) {
-               struct ref_states states;
-               memset(&states, 0, sizeof(states));
+               struct ref_states states = REF_STATES_INIT;
                get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
                if (!states.heads.nr)
                        result |= error(_("Cannot determine remote HEAD"));
@@ -1374,14 +1377,13 @@ static int set_head(int argc, const char **argv)
 static int prune_remote(const char *remote, int dry_run)
 {
        int result = 0;
-       struct ref_states states;
+       struct ref_states states = REF_STATES_INIT;
        struct string_list refs_to_prune = STRING_LIST_INIT_NODUP;
        struct string_list_item *item;
        const char *dangling_msg = dry_run
                ? _(" %s will become dangling!")
                : _(" %s has become dangling!");
 
-       memset(&states, 0, sizeof(states));
        get_remote_ref_states(remote, &states, GET_REF_STATES);
 
        if (!states.stale.nr) {
index c3e4771609363cac3958ccbfbbe1f2392bf0fc5f..0b2d1e5d82bedb38570ab056f029bdd3c24e1c5f 100644 (file)
@@ -15,6 +15,8 @@
 #include "promisor-remote.h"
 #include "shallow.h"
 #include "pack.h"
+#include "pack-bitmap.h"
+#include "refs.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -94,12 +96,14 @@ static void remove_pack_on_signal(int signo)
 }
 
 /*
- * Adds all packs hex strings to the fname list, which do not
- * have a corresponding .keep file. These packs are not to
- * be kept if we are going to pack everything into one file.
+ * Adds all packs hex strings to either fname_nonkept_list or
+ * fname_kept_list based on whether each pack has a corresponding
+ * .keep file or not.  Packs without a .keep file are not to be kept
+ * if we are going to pack everything into one file.
  */
-static void get_non_kept_pack_filenames(struct string_list *fname_list,
-                                       const struct string_list *extra_keep)
+static void collect_pack_filenames(struct string_list *fname_nonkept_list,
+                                  struct string_list *fname_kept_list,
+                                  const struct string_list *extra_keep)
 {
        DIR *dir;
        struct dirent *e;
@@ -112,21 +116,20 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
                size_t len;
                int i;
 
+               if (!strip_suffix(e->d_name, ".pack", &len))
+                       continue;
+
                for (i = 0; i < extra_keep->nr; i++)
                        if (!fspathcmp(e->d_name, extra_keep->items[i].string))
                                break;
-               if (extra_keep->nr > 0 && i < extra_keep->nr)
-                       continue;
-
-               if (!strip_suffix(e->d_name, ".pack", &len))
-                       continue;
 
                fname = xmemdupz(e->d_name, len);
 
-               if (!file_exists(mkpath("%s/%s.keep", packdir, fname)))
-                       string_list_append_nodup(fname_list, fname);
+               if ((extra_keep->nr > 0 && i < extra_keep->nr) ||
+                   (file_exists(mkpath("%s/%s.keep", packdir, fname))))
+                       string_list_append_nodup(fname_kept_list, fname);
                else
-                       free(fname);
+                       string_list_append_nodup(fname_nonkept_list, fname);
        }
        closedir(dir);
 }
@@ -422,6 +425,25 @@ static void split_pack_geometry(struct pack_geometry *geometry, int factor)
        geometry->split = split;
 }
 
+static struct packed_git *get_largest_active_pack(struct pack_geometry *geometry)
+{
+       if (!geometry) {
+               /*
+                * No geometry means either an all-into-one repack (in which
+                * case there is only one pack left and it is the largest) or an
+                * incremental one.
+                *
+                * If repacking incrementally, then we could check the size of
+                * all packs to determine which should be preferred, but leave
+                * this for later.
+                */
+               return NULL;
+       }
+       if (geometry->split == geometry->pack_nr)
+               return NULL;
+       return geometry->pack[geometry->pack_nr - 1];
+}
+
 static void clear_pack_geometry(struct pack_geometry *geometry)
 {
        if (!geometry)
@@ -433,17 +455,162 @@ static void clear_pack_geometry(struct pack_geometry *geometry)
        geometry->split = 0;
 }
 
+struct midx_snapshot_ref_data {
+       struct tempfile *f;
+       struct oidset seen;
+       int preferred;
+};
+
+static int midx_snapshot_ref_one(const char *refname,
+                                const struct object_id *oid,
+                                int flag, void *_data)
+{
+       struct midx_snapshot_ref_data *data = _data;
+       struct object_id peeled;
+
+       if (!peel_iterated_oid(oid, &peeled))
+               oid = &peeled;
+
+       if (oidset_insert(&data->seen, oid))
+               return 0; /* already seen */
+
+       if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
+               return 0;
+
+       fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "",
+               oid_to_hex(oid));
+
+       return 0;
+}
+
+static void midx_snapshot_refs(struct tempfile *f)
+{
+       struct midx_snapshot_ref_data data;
+       const struct string_list *preferred = bitmap_preferred_tips(the_repository);
+
+       data.f = f;
+       data.preferred = 0;
+       oidset_init(&data.seen, 0);
+
+       if (!fdopen_tempfile(f, "w"))
+                die(_("could not open tempfile %s for writing"),
+                    get_tempfile_path(f));
+
+       if (preferred) {
+               struct string_list_item *item;
+
+               data.preferred = 1;
+               for_each_string_list_item(item, preferred)
+                       for_each_ref_in(item->string, midx_snapshot_ref_one, &data);
+               data.preferred = 0;
+       }
+
+       for_each_ref(midx_snapshot_ref_one, &data);
+
+       if (close_tempfile_gently(f)) {
+               int save_errno = errno;
+               delete_tempfile(&f);
+               errno = save_errno;
+               die_errno(_("could not close refs snapshot tempfile"));
+       }
+
+       oidset_clear(&data.seen);
+}
+
+static void midx_included_packs(struct string_list *include,
+                               struct string_list *existing_nonkept_packs,
+                               struct string_list *existing_kept_packs,
+                               struct string_list *names,
+                               struct pack_geometry *geometry)
+{
+       struct string_list_item *item;
+
+       for_each_string_list_item(item, existing_kept_packs)
+               string_list_insert(include, xstrfmt("%s.idx", item->string));
+       for_each_string_list_item(item, names)
+               string_list_insert(include, xstrfmt("pack-%s.idx", item->string));
+       if (geometry) {
+               struct strbuf buf = STRBUF_INIT;
+               uint32_t i;
+               for (i = geometry->split; i < geometry->pack_nr; i++) {
+                       struct packed_git *p = geometry->pack[i];
+
+                       strbuf_addstr(&buf, pack_basename(p));
+                       strbuf_strip_suffix(&buf, ".pack");
+                       strbuf_addstr(&buf, ".idx");
+
+                       string_list_insert(include, strbuf_detach(&buf, NULL));
+               }
+       } else {
+               for_each_string_list_item(item, existing_nonkept_packs) {
+                       if (item->util)
+                               continue;
+                       string_list_insert(include, xstrfmt("%s.idx", item->string));
+               }
+       }
+}
+
+static int write_midx_included_packs(struct string_list *include,
+                                    struct pack_geometry *geometry,
+                                    const char *refs_snapshot,
+                                    int show_progress, int write_bitmaps)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       struct string_list_item *item;
+       struct packed_git *largest = get_largest_active_pack(geometry);
+       FILE *in;
+       int ret;
+
+       if (!include->nr)
+               return 0;
+
+       cmd.in = -1;
+       cmd.git_cmd = 1;
+
+       strvec_push(&cmd.args, "multi-pack-index");
+       strvec_pushl(&cmd.args, "write", "--stdin-packs", NULL);
+
+       if (show_progress)
+               strvec_push(&cmd.args, "--progress");
+       else
+               strvec_push(&cmd.args, "--no-progress");
+
+       if (write_bitmaps)
+               strvec_push(&cmd.args, "--bitmap");
+
+       if (largest)
+               strvec_pushf(&cmd.args, "--preferred-pack=%s",
+                            pack_basename(largest));
+
+       if (refs_snapshot)
+               strvec_pushf(&cmd.args, "--refs-snapshot=%s", refs_snapshot);
+
+       ret = start_command(&cmd);
+       if (ret)
+               return ret;
+
+       in = xfdopen(cmd.in, "w");
+       for_each_string_list_item(item, include)
+               fprintf(in, "%s\n", item->string);
+       fclose(in);
+
+       return finish_command(&cmd);
+}
+
 int cmd_repack(int argc, const char **argv, const char *prefix)
 {
        struct child_process cmd = CHILD_PROCESS_INIT;
        struct string_list_item *item;
        struct string_list names = STRING_LIST_INIT_DUP;
        struct string_list rollback = STRING_LIST_INIT_NODUP;
-       struct string_list existing_packs = STRING_LIST_INIT_DUP;
+       struct string_list existing_nonkept_packs = STRING_LIST_INIT_DUP;
+       struct string_list existing_kept_packs = STRING_LIST_INIT_DUP;
        struct pack_geometry *geometry = NULL;
        struct strbuf line = STRBUF_INIT;
+       struct tempfile *refs_snapshot = NULL;
        int i, ext, ret;
        FILE *out;
+       int show_progress = isatty(2);
 
        /* variables to be filled by option parsing */
        int pack_everything = 0;
@@ -454,6 +621,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        int no_update_server_info = 0;
        struct pack_objects_args po_args = {NULL};
        int geometric_factor = 0;
+       int write_midx = 0;
 
        struct option builtin_repack_options[] = {
                OPT_BIT('a', NULL, &pack_everything,
@@ -496,6 +664,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                N_("do not repack this pack")),
                OPT_INTEGER('g', "geometric", &geometric_factor,
                            N_("find a geometric progression with factor <N>")),
+               OPT_BOOL('m', "write-midx", &write_midx,
+                          N_("write a multi-pack index of the resulting packs")),
                OPT_END()
        };
 
@@ -512,16 +682,32 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                die(_("--keep-unreachable and -A are incompatible"));
 
        if (write_bitmaps < 0) {
-               if (!(pack_everything & ALL_INTO_ONE) ||
-                   !is_bare_repository())
+               if (!write_midx &&
+                   (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository()))
                        write_bitmaps = 0;
+       } else if (write_bitmaps &&
+                  git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0) &&
+                  git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0)) {
+               write_bitmaps = 0;
        }
        if (pack_kept_objects < 0)
                pack_kept_objects = write_bitmaps > 0;
 
-       if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
+       if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx)
                die(_(incremental_bitmap_conflict_error));
 
+       if (write_midx && write_bitmaps) {
+               struct strbuf path = STRBUF_INIT;
+
+               strbuf_addf(&path, "%s/%s_XXXXXX", get_object_directory(),
+                           "bitmap-ref-tips");
+
+               refs_snapshot = xmks_tempfile(path.buf);
+               midx_snapshot_refs(refs_snapshot);
+
+               strbuf_release(&path);
+       }
+
        if (geometric_factor) {
                if (pack_everything)
                        die(_("--geometric is incompatible with -A, -a"));
@@ -561,19 +747,22 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        }
        if (has_promisor_remote())
                strvec_push(&cmd.args, "--exclude-promisor-objects");
-       if (write_bitmaps > 0)
-               strvec_push(&cmd.args, "--write-bitmap-index");
-       else if (write_bitmaps < 0)
-               strvec_push(&cmd.args, "--write-bitmap-index-quiet");
+       if (!write_midx) {
+               if (write_bitmaps > 0)
+                       strvec_push(&cmd.args, "--write-bitmap-index");
+               else if (write_bitmaps < 0)
+                       strvec_push(&cmd.args, "--write-bitmap-index-quiet");
+       }
        if (use_delta_islands)
                strvec_push(&cmd.args, "--delta-islands");
 
-       if (pack_everything & ALL_INTO_ONE) {
-               get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
+       collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs,
+                              &keep_pack_list);
 
+       if (pack_everything & ALL_INTO_ONE) {
                repack_promisor_objects(&po_args, &names);
 
-               if (existing_packs.nr && delete_redundant) {
+               if (existing_nonkept_packs.nr && delete_redundant) {
                        for_each_string_list_item(item, &names) {
                                strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack",
                                             packtmp_name, item->string);
@@ -582,15 +771,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                strvec_pushf(&cmd.args,
                                             "--unpack-unreachable=%s",
                                             unpack_unreachable);
-                               strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
                        } else if (pack_everything & LOOSEN_UNREACHABLE) {
                                strvec_push(&cmd.args,
                                            "--unpack-unreachable");
                        } else if (keep_unreachable) {
                                strvec_push(&cmd.args, "--keep-unreachable");
                                strvec_push(&cmd.args, "--pack-loose-unreachable");
-                       } else {
-                               strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
                        }
                }
        } else if (geometry) {
@@ -676,20 +862,48 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        }
        /* End of pack replacement. */
 
-       reprepare_packed_git(the_repository);
-
-       if (delete_redundant) {
+       if (delete_redundant && pack_everything & ALL_INTO_ONE) {
                const int hexsz = the_hash_algo->hexsz;
-               int opts = 0;
                string_list_sort(&names);
-               for_each_string_list_item(item, &existing_packs) {
+               for_each_string_list_item(item, &existing_nonkept_packs) {
                        char *sha1;
                        size_t len = strlen(item->string);
                        if (len < hexsz)
                                continue;
                        sha1 = item->string + len - hexsz;
-                       if (!string_list_has_string(&names, sha1))
-                               remove_redundant_pack(packdir, item->string);
+                       /*
+                        * Mark this pack for deletion, which ensures that this
+                        * pack won't be included in a MIDX (if `--write-midx`
+                        * was given) and that we will actually delete this pack
+                        * (if `-d` was given).
+                        */
+                       item->util = (void*)(intptr_t)!string_list_has_string(&names, sha1);
+               }
+       }
+
+       if (write_midx) {
+               struct string_list include = STRING_LIST_INIT_NODUP;
+               midx_included_packs(&include, &existing_nonkept_packs,
+                                   &existing_kept_packs, &names, geometry);
+
+               ret = write_midx_included_packs(&include, geometry,
+                                               refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,
+                                               show_progress, write_bitmaps > 0);
+
+               string_list_clear(&include, 0);
+
+               if (ret)
+                       return ret;
+       }
+
+       reprepare_packed_git(the_repository);
+
+       if (delete_redundant) {
+               int opts = 0;
+               for_each_string_list_item(item, &existing_nonkept_packs) {
+                       if (!item->util)
+                               continue;
+                       remove_redundant_pack(packdir, item->string);
                }
 
                if (geometry) {
@@ -710,7 +924,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                        }
                        strbuf_release(&buf);
                }
-               if (!po_args.quiet && isatty(2))
+               if (!po_args.quiet && show_progress)
                        opts |= PRUNE_PACKED_VERBOSE;
                prune_packed_objects(opts);
 
@@ -725,12 +939,17 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                update_server_info(0);
        remove_temporary_files();
 
-       if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
-               write_midx_file(get_object_directory(), NULL, 0);
+       if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {
+               unsigned flags = 0;
+               if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0))
+                       flags |= MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX;
+               write_midx_file(get_object_directory(), NULL, NULL, flags);
+       }
 
        string_list_clear(&names, 0);
        string_list_clear(&rollback, 0);
-       string_list_clear(&existing_packs, 0);
+       string_list_clear(&existing_nonkept_packs, 0);
+       string_list_clear(&existing_kept_packs, 0);
        clear_pack_geometry(geometry);
        strbuf_release(&line);
 
index cd48765911746420766736eaa3be7160cc790ce2..946938d011ee8ea69040b931c98acd270bbcd46d 100644 (file)
@@ -507,7 +507,7 @@ static int convert_graft_file(int force)
        if (!fp)
                return -1;
 
-       advice_graft_file_deprecated = 0;
+       no_graft_file_deprecated_advice = 1;
        while (strbuf_getline(&buf, fp) != EOF) {
                if (*buf.buf == '#')
                        continue;
index 43e855cb887650b87390e6f5e96294b4c967d2af..739359534947e4d0bb5578ee243e9c179fd1c36b 100644 (file)
@@ -67,12 +67,18 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t
        case KEEP:
        case MERGE:
                opts.update = 1;
+               opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
                break;
        case HARD:
                opts.update = 1;
-               /* fallthrough */
+               opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
+               break;
+       case MIXED:
+               opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
+               /* but opts.update=0, so working tree not updated */
+               break;
        default:
-               opts.reset = 1;
+               BUG("invalid reset_type passed to reset_index");
        }
 
        read_cache_unmerged();
@@ -412,7 +418,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                                refresh_index(&the_index, flags, NULL, NULL,
                                              _("Unstaged changes after reset:"));
                                t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
-                               if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
+                               if (advice_enabled(ADVICE_RESET_QUIET_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
                                        printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset.  You can\n"
                                                "use '--quiet' to avoid this.  Set the config setting reset.quiet to true\n"
                                                "to make this the default.\n"), t_delta_in_ms / 1000.0);
index 22c4e1a4ff0f2164f477d02ad230a08ce2a978c8..8480a59f573e28d7777ee7e0dd21a2a640657641 100644 (file)
@@ -863,8 +863,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--bisect")) {
-                               for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0);
-                               for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0);
+                               for_each_fullref_in("refs/bisect/bad", show_reference, NULL);
+                               for_each_fullref_in("refs/bisect/good", anti_reference, NULL);
                                continue;
                        }
                        if (opt_with_value(arg, "--branches", &arg)) {
index 237f2f18d4c0355a661a6dc7e64879870a32ac5d..51776abea63adf400e02339a716531feb26ad03e 100644 (file)
@@ -136,6 +136,9 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
                        PARSE_OPT_KEEP_ARGV0 |
                        PARSE_OPT_KEEP_UNKNOWN);
 
+       prepare_repo_settings(the_repository);
+       the_repository->settings.command_requires_full_index = 0;
+
        /* implies allow_empty */
        if (opts->keep_redundant_commits)
                opts->allow_empty = 1;
@@ -191,7 +194,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
                struct setup_revision_opt s_r_opt;
                opts->revs = xmalloc(sizeof(*opts->revs));
                repo_init_revisions(the_repository, opts->revs, NULL);
-               opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+               opts->revs->no_walk = 1;
+               opts->revs->unsorted_input = 1;
                if (argc < 2)
                        usage_with_options(usage_str, options);
                if (!strcmp(argv[1], "-"))
index 8a24c715e02bab24098af5f3e354c631ee9abf3c..3d0967cdc11308b966e6225d6b95dd7ae90ab364 100644 (file)
@@ -55,7 +55,7 @@ static void print_error_files(struct string_list *files_list,
                        strbuf_addf(&err_msg,
                                    "\n    %s",
                                    files_list->items[i].string);
-               if (advice_rm_hints)
+               if (advice_enabled(ADVICE_RM_HINTS))
                        strbuf_addstr(&err_msg, hints_msg);
                *errs = error("%s", err_msg.buf);
                strbuf_release(&err_msg);
@@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only)
 
 static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
 static int ignore_unmatch = 0, pathspec_file_nul;
+static int include_sparse;
 static char *pathspec_from_file;
 
 static struct option builtin_rm_options[] = {
@@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = {
        OPT_BOOL('r', NULL,             &recursive,  N_("allow recursive removal")),
        OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
                                N_("exit with a zero status even if nothing matched")),
+       OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
        OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
        OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
        OPT_END(),
@@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        ensure_full_index(&the_index);
        for (i = 0; i < active_nr; i++) {
                const struct cache_entry *ce = active_cache[i];
-               if (ce_skip_worktree(ce))
+
+               if (!include_sparse &&
+                   (ce_skip_worktree(ce) ||
+                    !path_in_sparse_checkout(ce->name, &the_index)))
                        continue;
                if (!ce_path_match(&the_index, ce, &pathspec, seen))
                        continue;
@@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                                seen_any = 1;
                        else if (ignore_unmatch)
                                continue;
-                       else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
+                       else if (!include_sparse &&
+                                matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
                                string_list_append(&only_match_skip_worktree, original);
                        else
                                die(_("pathspec '%s' did not match any files"), original);
index 729dea1d255d354b044e477732eafc3a29a284b0..89321423125f94574150258a3d9f6d1a34bf0c10 100644 (file)
 #include "protocol.h"
 
 static const char * const send_pack_usage[] = {
-       N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
-         "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
-         "[<host>:]<directory> [<ref>...]\n"
-         "  --all and explicit <ref> specification are mutually exclusive."),
+       N_("git send-pack [--mirror] [--dry-run] [--force]\n"
+          "              [--receive-pack=<git-receive-pack>]\n"
+          "              [--verbose] [--thin] [--atomic]\n"
+          "              [<host>:]<directory> (--all | <ref>...)"),
        NULL,
 };
 
index bea4bbf46808df826f7aa36cb8259eb86e634e05..082449293b56388456520cbc67ef873ed3dc767a 100644 (file)
@@ -11,9 +11,9 @@
 
 static const char* show_branch_usage[] = {
     N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n"
-       "               [--current] [--color[=<when>] | --no-color] [--sparse]\n"
-       "               [--more=<n> | --list | --independent | --merge-base]\n"
-       "               [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
+       "                [--current] [--color[=<when>] | --no-color] [--sparse]\n"
+       "                [--more=<n> | --list | --independent | --merge-base]\n"
+       "                [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
     N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"),
     NULL
 };
index 8ba9f13787b058aafa4b349881b7d1518e69eee7..d0f5c4702be69d0c9fade08ffbf5183c5f3dbe7e 100644 (file)
@@ -100,6 +100,98 @@ static int sparse_checkout_list(int argc, const char **argv)
        return 0;
 }
 
+static void clean_tracked_sparse_directories(struct repository *r)
+{
+       int i, was_full = 0;
+       struct strbuf path = STRBUF_INIT;
+       size_t pathlen;
+       struct string_list_item *item;
+       struct string_list sparse_dirs = STRING_LIST_INIT_DUP;
+
+       /*
+        * If we are not using cone mode patterns, then we cannot
+        * delete directories outside of the sparse cone.
+        */
+       if (!r || !r->index || !r->worktree)
+               return;
+       if (init_sparse_checkout_patterns(r->index) ||
+           !r->index->sparse_checkout_patterns->use_cone_patterns)
+               return;
+
+       /*
+        * Use the sparse index as a data structure to assist finding
+        * directories that are safe to delete. This conversion to a
+        * sparse index will not delete directories that contain
+        * conflicted entries or submodules.
+        */
+       if (!r->index->sparse_index) {
+               /*
+                * If something, such as a merge conflict or other concern,
+                * prevents us from converting to a sparse index, then do
+                * not try deleting files.
+                */
+               if (convert_to_sparse(r->index, SPARSE_INDEX_MEMORY_ONLY))
+                       return;
+               was_full = 1;
+       }
+
+       strbuf_addstr(&path, r->worktree);
+       strbuf_complete(&path, '/');
+       pathlen = path.len;
+
+       /*
+        * Collect directories that have gone out of scope but also
+        * exist on disk, so there is some work to be done. We need to
+        * store the entries in a list before exploring, since that might
+        * expand the sparse-index again.
+        */
+       for (i = 0; i < r->index->cache_nr; i++) {
+               struct cache_entry *ce = r->index->cache[i];
+
+               if (S_ISSPARSEDIR(ce->ce_mode) &&
+                   repo_file_exists(r, ce->name))
+                       string_list_append(&sparse_dirs, ce->name);
+       }
+
+       for_each_string_list_item(item, &sparse_dirs) {
+               struct dir_struct dir = DIR_INIT;
+               struct pathspec p = { 0 };
+               struct strvec s = STRVEC_INIT;
+
+               strbuf_setlen(&path, pathlen);
+               strbuf_addstr(&path, item->string);
+
+               dir.flags |= DIR_SHOW_IGNORED_TOO;
+
+               setup_standard_excludes(&dir);
+               strvec_push(&s, path.buf);
+
+               parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v);
+               fill_directory(&dir, r->index, &p);
+
+               if (dir.nr) {
+                       warning(_("directory '%s' contains untracked files,"
+                                 " but is not in the sparse-checkout cone"),
+                               item->string);
+               } else if (remove_dir_recursively(&path, 0)) {
+                       /*
+                        * Removal is "best effort". If something blocks
+                        * the deletion, then continue with a warning.
+                        */
+                       warning(_("failed to remove directory '%s'"),
+                               item->string);
+               }
+
+               dir_clear(&dir);
+       }
+
+       string_list_clear(&sparse_dirs, 0);
+       strbuf_release(&path);
+
+       if (was_full)
+               ensure_full_index(r->index);
+}
+
 static int update_working_directory(struct pattern_list *pl)
 {
        enum update_sparsity_result result;
@@ -141,6 +233,8 @@ static int update_working_directory(struct pattern_list *pl)
        else
                rollback_lock_file(&lock_file);
 
+       clean_tracked_sparse_directories(r);
+
        r->index->sparse_checkout_patterns = NULL;
        return result;
 }
index 5512f4942cd67bbe3aaecef8ccc05af19be2da7a..a0ccc8654dff70bd3014dca9d804a75bdd4c3cbc 100644 (file)
@@ -85,7 +85,7 @@ static const char * const git_stash_push_usage[] = {
 
 static const char * const git_stash_save_usage[] = {
        N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
-          "          [-u|--include-untracked] [-a|--all] [<message>]"),
+          "               [-u|--include-untracked] [-a|--all] [<message>]"),
        NULL
 };
 
@@ -256,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
        opts.merge = 1;
-       opts.reset = reset;
+       opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
        opts.update = update;
+       if (update)
+               opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
        opts.fn = oneway_merge;
 
        if (unpack_trees(nr_trees, t, &opts))
@@ -1533,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
                } else {
                        struct child_process cp = CHILD_PROCESS_INIT;
                        cp.git_cmd = 1;
+                       /* BUG: this nukes untracked files in the way */
                        strvec_pushl(&cp.args, "reset", "--hard", "-q",
                                     "--no-recurse-submodules", NULL);
                        if (run_command(&cp)) {
index ef2776a9e4523894c6ff044bffcd9a2c60d2b98f..6298cbdd4e5f23aba5e47b5863737379bd60135e 100644 (file)
@@ -199,34 +199,28 @@ static char *relative_url(const char *remote_url,
        return strbuf_detach(&sb, NULL);
 }
 
-static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet)
 {
-       char *remoteurl = NULL;
+       char *remoteurl, *resolved_url;
        char *remote = get_default_remote();
-       const char *up_path = NULL;
-       char *res;
-       const char *url;
-       struct strbuf sb = STRBUF_INIT;
-
-       if (argc != 2 && argc != 3)
-               die("resolve-relative-url only accepts one or two arguments");
-
-       url = argv[1];
-       strbuf_addf(&sb, "remote.%s.url", remote);
-       free(remote);
+       struct strbuf remotesb = STRBUF_INIT;
 
-       if (git_config_get_string(sb.buf, &remoteurl))
-               /* the repository is its own authoritative upstream */
+       strbuf_addf(&remotesb, "remote.%s.url", remote);
+       if (git_config_get_string(remotesb.buf, &remoteurl)) {
+               if (!quiet)
+                       warning(_("could not look up configuration '%s'. "
+                                 "Assuming this repository is its own "
+                                 "authoritative upstream."),
+                               remotesb.buf);
                remoteurl = xgetcwd();
+       }
+       resolved_url = relative_url(remoteurl, rel_url, up_path);
 
-       if (argc == 3)
-               up_path = argv[2];
-
-       res = relative_url(remoteurl, url, up_path);
-       puts(res);
-       free(res);
+       free(remote);
        free(remoteurl);
-       return 0;
+       strbuf_release(&remotesb);
+
+       return resolved_url;
 }
 
 static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
@@ -313,7 +307,7 @@ struct module_list {
        const struct cache_entry **entries;
        int alloc, nr;
 };
-#define MODULE_LIST_INIT { NULL, 0, 0 }
+#define MODULE_LIST_INIT { 0 }
 
 static int module_list_compute(int argc, const char **argv,
                               const char *prefix,
@@ -590,31 +584,11 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
-static char *compute_submodule_clone_url(const char *rel_url)
-{
-       char *remoteurl, *relurl;
-       char *remote = get_default_remote();
-       struct strbuf remotesb = STRBUF_INIT;
-
-       strbuf_addf(&remotesb, "remote.%s.url", remote);
-       if (git_config_get_string(remotesb.buf, &remoteurl)) {
-               warning(_("could not look up configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf);
-               remoteurl = xgetcwd();
-       }
-       relurl = relative_url(remoteurl, rel_url, NULL);
-
-       free(remote);
-       free(remoteurl);
-       strbuf_release(&remotesb);
-
-       return relurl;
-}
-
 struct init_cb {
        const char *prefix;
        unsigned int flags;
 };
-#define INIT_CB_INIT { NULL, 0 }
+#define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
                           unsigned int flags)
@@ -660,7 +634,7 @@ static void init_submodule(const char *path, const char *prefix,
                if (starts_with_dot_dot_slash(url) ||
                    starts_with_dot_slash(url)) {
                        char *oldurl = url;
-                       url = compute_submodule_clone_url(oldurl);
+                       url = resolve_relative_url(oldurl, NULL, 0);
                        free(oldurl);
                }
 
@@ -743,7 +717,7 @@ struct status_cb {
        const char *prefix;
        unsigned int flags;
 };
-#define STATUS_CB_INIT { NULL, 0 }
+#define STATUS_CB_INIT { 0 }
 
 static void print_status(unsigned int flags, char state, const char *path,
                         const struct object_id *oid, const char *displaypath)
@@ -937,13 +911,13 @@ struct module_cb {
        char status;
        const char *sm_path;
 };
-#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+#define MODULE_CB_INIT { 0 }
 
 struct module_cb_list {
        struct module_cb **entries;
        int alloc, nr;
 };
-#define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+#define MODULE_CB_LIST_INIT { 0 }
 
 struct summary_cb {
        int argc;
@@ -954,7 +928,7 @@ struct summary_cb {
        unsigned int files: 1;
        int summary_limit;
 };
-#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+#define SUMMARY_CB_INIT { 0 }
 
 enum diff_cmd {
        DIFF_INDEX,
@@ -1360,7 +1334,7 @@ struct sync_cb {
        const char *prefix;
        unsigned int flags;
 };
-#define SYNC_CB_INIT { NULL, 0 }
+#define SYNC_CB_INIT { 0 }
 
 static void sync_submodule(const char *path, const char *prefix,
                           unsigned int flags)
@@ -1380,20 +1354,10 @@ static void sync_submodule(const char *path, const char *prefix,
        if (sub && sub->url) {
                if (starts_with_dot_dot_slash(sub->url) ||
                    starts_with_dot_slash(sub->url)) {
-                       char *remote_url, *up_path;
-                       char *remote = get_default_remote();
-                       strbuf_addf(&sb, "remote.%s.url", remote);
-
-                       if (git_config_get_string(sb.buf, &remote_url))
-                               remote_url = xgetcwd();
-
-                       up_path = get_up_path(path);
-                       sub_origin_url = relative_url(remote_url, sub->url, up_path);
-                       super_config_url = relative_url(remote_url, sub->url, NULL);
-
-                       free(remote);
+                       char *up_path = get_up_path(path);
+                       sub_origin_url = resolve_relative_url(sub->url, up_path, 1);
+                       super_config_url = resolve_relative_url(sub->url, NULL, 1);
                        free(up_path);
-                       free(remote_url);
                } else {
                        sub_origin_url = xstrdup(sub->url);
                        super_config_url = xstrdup(sub->url);
@@ -1516,7 +1480,7 @@ struct deinit_cb {
        const char *prefix;
        unsigned int flags;
 };
-#define DEINIT_CB_INIT { NULL, 0 }
+#define DEINIT_CB_INIT { 0 }
 
 static void deinit_submodule(const char *path, const char *prefix,
                             unsigned int flags)
@@ -1683,8 +1647,9 @@ struct submodule_alternate_setup {
        } error_mode;
        struct string_list *reference;
 };
-#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
-       SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
+#define SUBMODULE_ALTERNATE_SETUP_INIT { \
+       .error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE, \
+}
 
 static const char alternate_error_advice[] = N_(
 "An alternate computed from a superproject's alternate is invalid.\n"
@@ -1704,18 +1669,24 @@ static int add_possible_reference_from_superproject(
         * standard layout with .git/(modules/<name>)+/objects
         */
        if (strip_suffix(odb->path, "/objects", &len)) {
+               struct repository alternate;
                char *sm_alternate;
                struct strbuf sb = STRBUF_INIT;
                struct strbuf err = STRBUF_INIT;
                strbuf_add(&sb, odb->path, len);
 
+               repo_init(&alternate, sb.buf, NULL);
+
                /*
                 * We need to end the new path with '/' to mark it as a dir,
                 * otherwise a submodule name containing '/' will be broken
                 * as the last part of a missing submodule reference would
                 * be taken as a file name.
                 */
-               strbuf_addf(&sb, "/modules/%s/", sas->submodule_name);
+               strbuf_reset(&sb);
+               submodule_name_to_gitdir(&sb, &alternate, sas->submodule_name);
+               strbuf_addch(&sb, '/');
+               repo_clear(&alternate);
 
                sm_alternate = compute_alternate_path(sb.buf, &err);
                if (sm_alternate) {
@@ -1724,7 +1695,7 @@ static int add_possible_reference_from_superproject(
                } else {
                        switch (sas->error_mode) {
                        case SUBMODULE_ALTERNATE_ERROR_DIE:
-                               if (advice_submodule_alternate_error_strategy_die)
+                               if (advice_enabled(ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE))
                                        advise(_(alternate_error_advice));
                                die(_("submodule '%s' cannot add alternate: %s"),
                                    sas->submodule_name, err.buf);
@@ -1785,7 +1756,7 @@ static int clone_submodule(struct module_clone_data *clone_data)
        struct strbuf sb = STRBUF_INIT;
        struct child_process cp = CHILD_PROCESS_INIT;
 
-       strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name);
+       submodule_name_to_gitdir(&sb, the_repository, clone_data->name);
        sm_gitdir = absolute_pathdup(sb.buf);
        strbuf_reset(&sb);
 
@@ -2045,6 +2016,20 @@ struct submodule_update_clone {
        .max_jobs = 1, \
 }
 
+struct update_data {
+       const char *recursive_prefix;
+       const char *sm_path;
+       const char *displaypath;
+       struct object_id oid;
+       struct object_id suboid;
+       struct submodule_update_strategy update_strategy;
+       int depth;
+       unsigned int force: 1;
+       unsigned int quiet: 1;
+       unsigned int nofetch: 1;
+       unsigned int just_cloned: 1;
+};
+#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
                struct strbuf *out, const char *displaypath)
@@ -2134,7 +2119,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
        if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) {
                if (starts_with_dot_slash(sub->url) ||
                    starts_with_dot_dot_slash(sub->url)) {
-                       url = compute_submodule_clone_url(sub->url);
+                       url = resolve_relative_url(sub->url, NULL, 0);
                        need_free_url = 1;
                } else
                        url = sub->url;
@@ -2298,6 +2283,181 @@ static int git_update_clone_config(const char *var, const char *value,
        return 0;
 }
 
+static int is_tip_reachable(const char *path, struct object_id *oid)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+       struct strbuf rev = STRBUF_INIT;
+       char *hex = oid_to_hex(oid);
+
+       cp.git_cmd = 1;
+       cp.dir = xstrdup(path);
+       cp.no_stderr = 1;
+       strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL);
+
+       prepare_submodule_repo_env(&cp.env_array);
+
+       if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len)
+               return 0;
+
+       return 1;
+}
+
+static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+
+       prepare_submodule_repo_env(&cp.env_array);
+       cp.git_cmd = 1;
+       cp.dir = xstrdup(module_path);
+
+       strvec_push(&cp.args, "fetch");
+       if (quiet)
+               strvec_push(&cp.args, "--quiet");
+       if (depth)
+               strvec_pushf(&cp.args, "--depth=%d", depth);
+       if (oid) {
+               char *hex = oid_to_hex(oid);
+               char *remote = get_default_remote();
+               strvec_pushl(&cp.args, remote, hex, NULL);
+       }
+
+       return run_command(&cp);
+}
+
+static int run_update_command(struct update_data *ud, int subforce)
+{
+       struct strvec args = STRVEC_INIT;
+       struct strvec child_env = STRVEC_INIT;
+       char *oid = oid_to_hex(&ud->oid);
+       int must_die_on_failure = 0;
+       int git_cmd;
+
+       switch (ud->update_strategy.type) {
+       case SM_UPDATE_CHECKOUT:
+               git_cmd = 1;
+               strvec_pushl(&args, "checkout", "-q", NULL);
+               if (subforce)
+                       strvec_push(&args, "-f");
+               break;
+       case SM_UPDATE_REBASE:
+               git_cmd = 1;
+               strvec_push(&args, "rebase");
+               if (ud->quiet)
+                       strvec_push(&args, "--quiet");
+               must_die_on_failure = 1;
+               break;
+       case SM_UPDATE_MERGE:
+               git_cmd = 1;
+               strvec_push(&args, "merge");
+               if (ud->quiet)
+                       strvec_push(&args, "--quiet");
+               must_die_on_failure = 1;
+               break;
+       case SM_UPDATE_COMMAND:
+               git_cmd = 0;
+               strvec_push(&args, ud->update_strategy.command);
+               must_die_on_failure = 1;
+               break;
+       default:
+               BUG("unexpected update strategy type: %s",
+                   submodule_strategy_to_string(&ud->update_strategy));
+       }
+       strvec_push(&args, oid);
+
+       prepare_submodule_repo_env(&child_env);
+       if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
+                                    ud->sm_path, child_env.v)) {
+               switch (ud->update_strategy.type) {
+               case SM_UPDATE_CHECKOUT:
+                       printf(_("Unable to checkout '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_REBASE:
+                       printf(_("Unable to rebase '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_MERGE:
+                       printf(_("Unable to merge '%s' in submodule path '%s'"),
+                              oid, ud->displaypath);
+                       break;
+               case SM_UPDATE_COMMAND:
+                       printf(_("Execution of '%s %s' failed in submodule path '%s'"),
+                              ud->update_strategy.command, oid, ud->displaypath);
+                       break;
+               default:
+                       BUG("unexpected update strategy type: %s",
+                           submodule_strategy_to_string(&ud->update_strategy));
+               }
+               /*
+                * NEEDSWORK: We are currently printing to stdout with error
+                * return so that the shell caller handles the error output
+                * properly. Once we start handling the error messages within
+                * C, we should use die() instead.
+                */
+               if (must_die_on_failure)
+                       return 2;
+               /*
+                * This signifies to the caller in shell that the command
+                * failed without dying
+                */
+               return 1;
+       }
+
+       switch (ud->update_strategy.type) {
+       case SM_UPDATE_CHECKOUT:
+               printf(_("Submodule path '%s': checked out '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_REBASE:
+               printf(_("Submodule path '%s': rebased into '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_MERGE:
+               printf(_("Submodule path '%s': merged in '%s'\n"),
+                      ud->displaypath, oid);
+               break;
+       case SM_UPDATE_COMMAND:
+               printf(_("Submodule path '%s': '%s %s'\n"),
+                      ud->displaypath, ud->update_strategy.command, oid);
+               break;
+       default:
+               BUG("unexpected update strategy type: %s",
+                   submodule_strategy_to_string(&ud->update_strategy));
+       }
+
+       return 0;
+}
+
+static int do_run_update_procedure(struct update_data *ud)
+{
+       int subforce = is_null_oid(&ud->suboid) || ud->force;
+
+       if (!ud->nofetch) {
+               /*
+                * Run fetch only if `oid` isn't present or it
+                * is not reachable from a ref.
+                */
+               if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+                   fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, NULL) &&
+                   !ud->quiet)
+                       fprintf_ln(stderr,
+                                  _("Unable to fetch in submodule path '%s'; "
+                                    "trying to directly fetch %s:"),
+                                  ud->displaypath, oid_to_hex(&ud->oid));
+               /*
+                * Now we tried the usual fetch, but `oid` may
+                * not be reachable from any of the refs.
+                */
+               if (!is_tip_reachable(ud->sm_path, &ud->oid) &&
+                   fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid))
+                       die(_("Fetched in submodule path '%s', but it did not "
+                             "contain %s. Direct fetching of that commit failed."),
+                           ud->displaypath, oid_to_hex(&ud->oid));
+       }
+
+       return run_update_command(ud, subforce);
+}
+
 static void update_submodule(struct update_clone_data *ucd)
 {
        fprintf(stdout, "dummy %s %d\t%s\n",
@@ -2395,6 +2555,73 @@ static int update_clone(int argc, const char **argv, const char *prefix)
        return update_submodules(&suc);
 }
 
+static int run_update_procedure(int argc, const char **argv, const char *prefix)
+{
+       int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
+       char *prefixed_path, *update = NULL;
+       struct update_data update_data = UPDATE_DATA_INIT;
+
+       struct option options[] = {
+               OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
+               OPT__FORCE(&force, N_("force checkout updates"), 0),
+               OPT_BOOL('N', "no-fetch", &nofetch,
+                        N_("don't fetch new objects from the remote site")),
+               OPT_BOOL(0, "just-cloned", &just_cloned,
+                        N_("overrides update mode in case the repository is a fresh clone")),
+               OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("path into the working tree")),
+               OPT_STRING(0, "update", &update,
+                          N_("string"),
+                          N_("rebase, merge, checkout or none")),
+               OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+                          N_("path into the working tree, across nested "
+                             "submodule boundaries")),
+               OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+                              N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
+                              parse_opt_object_id),
+               OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+                              N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
+                              parse_opt_object_id),
+               OPT_END()
+       };
+
+       const char *const usage[] = {
+               N_("git submodule--helper run-update-procedure [<options>] <path>"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+       if (argc != 1)
+               usage_with_options(usage, options);
+
+       update_data.force = !!force;
+       update_data.quiet = !!quiet;
+       update_data.nofetch = !!nofetch;
+       update_data.just_cloned = !!just_cloned;
+       update_data.sm_path = argv[0];
+
+       if (update_data.recursive_prefix)
+               prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+       else
+               prefixed_path = xstrdup(update_data.sm_path);
+
+       update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+
+       determine_submodule_update_strategy(the_repository, update_data.just_cloned,
+                                           update_data.sm_path, update,
+                                           &update_data.update_strategy);
+
+       free(prefixed_path);
+
+       if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
+               return do_run_update_procedure(&update_data);
+
+       return 3;
+}
+
 static int resolve_relative_path(int argc, const char **argv, const char *prefix)
 {
        struct strbuf sb = STRBUF_INIT;
@@ -2540,7 +2767,6 @@ static int push_check(int argc, const char **argv, const char *prefix)
 
 static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 {
-       const struct submodule *sub;
        const char *path;
        const char *cw;
        struct repository subrepo;
@@ -2550,11 +2776,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 
        path = argv[1];
 
-       sub = submodule_from_path(the_repository, null_oid(), path);
-       if (!sub)
-               BUG("We could get the submodule handle before?");
-
-       if (repo_submodule_init(&subrepo, the_repository, sub))
+       if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
                die(_("could not get a repository handle for submodule '%s'"), path);
 
        if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) {
@@ -2765,7 +2987,7 @@ struct add_data {
        const char *prefix;
        const char *branch;
        const char *reference_path;
-       const char *sm_path;
+       char *sm_path;
        const char *sm_name;
        const char *repo;
        const char *realrepo;
@@ -2864,6 +3086,10 @@ static int add_submodule(const struct add_data *add_data)
                prepare_submodule_repo_env(&cp.env_array);
                cp.git_cmd = 1;
                cp.dir = add_data->sm_path;
+               /*
+                * NOTE: we only get here if add_data->force is true, so
+                * passing --force to checkout is reasonable.
+                */
                strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
 
                if (add_data->branch) {
@@ -2877,61 +3103,244 @@ static int add_submodule(const struct add_data *add_data)
        return 0;
 }
 
-static int add_clone(int argc, const char **argv, const char *prefix)
+static int config_submodule_in_gitmodules(const char *name, const char *var, const char *value)
+{
+       char *key;
+       int ret;
+
+       if (!is_writing_gitmodules_ok())
+               die(_("please make sure that the .gitmodules file is in the working tree"));
+
+       key = xstrfmt("submodule.%s.%s", name, var);
+       ret = config_set_in_gitmodules_file_gently(key, value);
+       free(key);
+
+       return ret;
+}
+
+static void configure_added_submodule(struct add_data *add_data)
+{
+       char *key;
+       char *val = NULL;
+       struct child_process add_submod = CHILD_PROCESS_INIT;
+       struct child_process add_gitmodules = CHILD_PROCESS_INIT;
+
+       key = xstrfmt("submodule.%s.url", add_data->sm_name);
+       git_config_set_gently(key, add_data->realrepo);
+       free(key);
+
+       add_submod.git_cmd = 1;
+       strvec_pushl(&add_submod.args, "add",
+                    "--no-warn-embedded-repo", NULL);
+       if (add_data->force)
+               strvec_push(&add_submod.args, "--force");
+       strvec_pushl(&add_submod.args, "--", add_data->sm_path, NULL);
+
+       if (run_command(&add_submod))
+               die(_("Failed to add submodule '%s'"), add_data->sm_path);
+
+       if (config_submodule_in_gitmodules(add_data->sm_name, "path", add_data->sm_path) ||
+           config_submodule_in_gitmodules(add_data->sm_name, "url", add_data->repo))
+               die(_("Failed to register submodule '%s'"), add_data->sm_path);
+
+       if (add_data->branch) {
+               if (config_submodule_in_gitmodules(add_data->sm_name,
+                                                  "branch", add_data->branch))
+                       die(_("Failed to register submodule '%s'"), add_data->sm_path);
+       }
+
+       add_gitmodules.git_cmd = 1;
+       strvec_pushl(&add_gitmodules.args,
+                    "add", "--force", "--", ".gitmodules", NULL);
+
+       if (run_command(&add_gitmodules))
+               die(_("Failed to register submodule '%s'"), add_data->sm_path);
+
+       /*
+        * NEEDSWORK: In a multi-working-tree world this needs to be
+        * set in the per-worktree config.
+        */
+       /*
+        * NEEDSWORK: In the longer run, we need to get rid of this
+        * pattern of querying "submodule.active" before calling
+        * is_submodule_active(), since that function needs to find
+        * out the value of "submodule.active" again anyway.
+        */
+       if (!git_config_get_string("submodule.active", &val) && val) {
+               /*
+                * If the submodule being added isn't already covered by the
+                * current configured pathspec, set the submodule's active flag
+                */
+               if (!is_submodule_active(the_repository, add_data->sm_path)) {
+                       key = xstrfmt("submodule.%s.active", add_data->sm_name);
+                       git_config_set_gently(key, "true");
+                       free(key);
+               }
+       } else {
+               key = xstrfmt("submodule.%s.active", add_data->sm_name);
+               git_config_set_gently(key, "true");
+               free(key);
+       }
+}
+
+static void die_on_index_match(const char *path, int force)
+{
+       struct pathspec ps;
+       const char *args[] = { path, NULL };
+       parse_pathspec(&ps, 0, PATHSPEC_PREFER_CWD, NULL, args);
+
+       if (read_cache_preload(NULL) < 0)
+               die(_("index file corrupt"));
+
+       if (ps.nr) {
+               int i;
+               char *ps_matched = xcalloc(ps.nr, 1);
+
+               /* TODO: audit for interaction with sparse-index. */
+               ensure_full_index(&the_index);
+
+               /*
+                * Since there is only one pathspec, we just need
+                * need to check ps_matched[0] to know if a cache
+                * entry matched.
+                */
+               for (i = 0; i < active_nr; i++) {
+                       ce_path_match(&the_index, active_cache[i], &ps,
+                                     ps_matched);
+
+                       if (ps_matched[0]) {
+                               if (!force)
+                                       die(_("'%s' already exists in the index"),
+                                           path);
+                               if (!S_ISGITLINK(active_cache[i]->ce_mode))
+                                       die(_("'%s' already exists in the index "
+                                             "and is not a submodule"), path);
+                               break;
+                       }
+               }
+               free(ps_matched);
+       }
+}
+
+static void die_on_repo_without_commits(const char *path)
 {
-       int force = 0, quiet = 0, dissociate = 0, progress = 0;
+       struct strbuf sb = STRBUF_INIT;
+       strbuf_addstr(&sb, path);
+       if (is_nonbare_repository_dir(&sb)) {
+               struct object_id oid;
+               if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+                       die(_("'%s' does not have a commit checked out"), path);
+       }
+}
+
+static int module_add(int argc, const char **argv, const char *prefix)
+{
+       int force = 0, quiet = 0, progress = 0, dissociate = 0;
        struct add_data add_data = ADD_DATA_INIT;
 
        struct option options[] = {
-               OPT_STRING('b', "branch", &add_data.branch,
-                          N_("branch"),
-                          N_("branch of repository to checkout on cloning")),
-               OPT_STRING(0, "prefix", &prefix,
-                          N_("path"),
-                          N_("alternative anchor for relative paths")),
-               OPT_STRING(0, "path", &add_data.sm_path,
-                          N_("path"),
-                          N_("where the new submodule will be cloned to")),
-               OPT_STRING(0, "name", &add_data.sm_name,
-                          N_("string"),
-                          N_("name of the new submodule")),
-               OPT_STRING(0, "url", &add_data.realrepo,
-                          N_("string"),
-                          N_("url where to clone the submodule from")),
-               OPT_STRING(0, "reference", &add_data.reference_path,
-                          N_("repo"),
-                          N_("reference repository")),
-               OPT_BOOL(0, "dissociate", &dissociate,
-                        N_("use --reference only while cloning")),
-               OPT_INTEGER(0, "depth", &add_data.depth,
-                           N_("depth for shallow clones")),
-               OPT_BOOL(0, "progress", &progress,
-                        N_("force cloning progress")),
+               OPT_STRING('b', "branch", &add_data.branch, N_("branch"),
+                          N_("branch of repository to add as submodule")),
                OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
                           PARSE_OPT_NOCOMPLETE),
-               OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
+               OPT__QUIET(&quiet, N_("print only error messages")),
+               OPT_BOOL(0, "progress", &progress, N_("force cloning progress")),
+               OPT_STRING(0, "reference", &add_data.reference_path, N_("repository"),
+                          N_("reference repository")),
+               OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
+               OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
+                          N_("sets the submodule’s name to the given string "
+                             "instead of defaulting to its path")),
+               OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")),
                OPT_END()
        };
 
        const char *const usage[] = {
-               N_("git submodule--helper add-clone [<options>...] "
-                  "--url <url> --path <path> --name <name>"),
+               N_("git submodule--helper add [<options>] [--] <repository> [<path>]"),
                NULL
        };
 
        argc = parse_options(argc, argv, prefix, options, usage, 0);
 
-       if (argc != 0)
+       if (!is_writing_gitmodules_ok())
+               die(_("please make sure that the .gitmodules file is in the working tree"));
+
+       if (prefix && *prefix &&
+           add_data.reference_path && !is_absolute_path(add_data.reference_path))
+               add_data.reference_path = xstrfmt("%s%s", prefix, add_data.reference_path);
+
+       if (argc == 0 || argc > 2)
                usage_with_options(usage, options);
 
+       add_data.repo = argv[0];
+       if (argc == 1)
+               add_data.sm_path = git_url_basename(add_data.repo, 0, 0);
+       else
+               add_data.sm_path = xstrdup(argv[1]);
+
+       if (prefix && *prefix && !is_absolute_path(add_data.sm_path))
+               add_data.sm_path = xstrfmt("%s%s", prefix, add_data.sm_path);
+
+       if (starts_with_dot_dot_slash(add_data.repo) ||
+           starts_with_dot_slash(add_data.repo)) {
+               if (prefix)
+                       die(_("Relative path can only be used from the toplevel "
+                             "of the working tree"));
+
+               /* dereference source url relative to parent's url */
+               add_data.realrepo = resolve_relative_url(add_data.repo, NULL, 1);
+       } else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) {
+               add_data.realrepo = add_data.repo;
+       } else {
+               die(_("repo URL: '%s' must be absolute or begin with ./|../"),
+                   add_data.repo);
+       }
+
+       /*
+        * normalize path:
+        * multiple //; leading ./; /./; /../;
+        */
+       normalize_path_copy(add_data.sm_path, add_data.sm_path);
+       strip_dir_trailing_slashes(add_data.sm_path);
+
+       die_on_index_match(add_data.sm_path, force);
+       die_on_repo_without_commits(add_data.sm_path);
+
+       if (!force) {
+               int exit_code = -1;
+               struct strbuf sb = STRBUF_INIT;
+               struct child_process cp = CHILD_PROCESS_INIT;
+               cp.git_cmd = 1;
+               cp.no_stdout = 1;
+               strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing",
+                            "--no-warn-embedded-repo", add_data.sm_path, NULL);
+               if ((exit_code = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) {
+                       strbuf_complete_line(&sb);
+                       fputs(sb.buf, stderr);
+                       free(add_data.sm_path);
+                       return exit_code;
+               }
+               strbuf_release(&sb);
+       }
+
+       if(!add_data.sm_name)
+               add_data.sm_name = add_data.sm_path;
+
+       if (check_submodule_name(add_data.sm_name))
+               die(_("'%s' is not a valid submodule name"), add_data.sm_name);
+
        add_data.prefix = prefix;
-       add_data.progress = !!progress;
-       add_data.dissociate = !!dissociate;
        add_data.force = !!force;
        add_data.quiet = !!quiet;
+       add_data.progress = !!progress;
+       add_data.dissociate = !!dissociate;
 
-       if (add_submodule(&add_data))
+       if (add_submodule(&add_data)) {
+               free(add_data.sm_path);
                return 1;
+       }
+       configure_added_submodule(&add_data);
+       free(add_data.sm_path);
 
        return 0;
 }
@@ -2948,12 +3357,12 @@ static struct cmd_struct commands[] = {
        {"list", module_list, 0},
        {"name", module_name, 0},
        {"clone", module_clone, 0},
-       {"add-clone", add_clone, 0},
+       {"add", module_add, SUPPORT_SUPER_PREFIX},
        {"update-module-mode", module_update_module_mode, 0},
        {"update-clone", update_clone, 0},
+       {"run-update-procedure", run_update_procedure, 0},
        {"ensure-core-worktree", ensure_core_worktree, 0},
        {"relative-path", resolve_relative_path, 0},
-       {"resolve-relative-url", resolve_relative_url, 0},
        {"resolve-relative-url-test", resolve_relative_url_test, 0},
        {"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
        {"init", module_init, SUPPORT_SUPER_PREFIX},
index 87552ae6ac54ca48405d3b55ece2bfd5bfef513b..6535ed27ee9e6b419c67349dd470af02265586b3 100644 (file)
 
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n"
-               "\t\t<tagname> [<head>]"),
+          "        <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
-               "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
+          "        [--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
        N_("git tag -v [--format=<format>] <tagname>..."),
        NULL
 };
@@ -146,7 +146,7 @@ static int verify_tag(const char *name, const char *ref,
                      const struct object_id *oid, void *cb_data)
 {
        int flags;
-       const struct ref_format *format = cb_data;
+       struct ref_format *format = cb_data;
        flags = GPG_VERIFY_VERBOSE;
 
        if (format->format)
index 6da8fa2607c6b831c69a76c5bb2ec5caec444d54..125af53885f89fbd4b691f5d9463774b12a66559 100644 (file)
@@ -16,16 +16,18 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
 {
        const char *dir;
        int strict = 0;
-       struct upload_pack_options opts = { 0 };
-       struct serve_options serve_opts = SERVE_OPTIONS_INIT;
+       int advertise_refs = 0;
+       int stateless_rpc = 0;
+       int timeout = 0;
        struct option options[] = {
-               OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
+               OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
                         N_("quit after a single request/response exchange")),
-               OPT_BOOL(0, "advertise-refs", &opts.advertise_refs,
-                        N_("exit immediately after initial ref advertisement")),
+               OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs,
+                               N_("serve up the info/refs for git-http-backend")),
+               OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
                OPT_BOOL(0, "strict", &strict,
                         N_("do not try <directory>/.git/ if <directory> is no Git directory")),
-               OPT_INTEGER(0, "timeout", &opts.timeout,
+               OPT_INTEGER(0, "timeout", &timeout,
                            N_("interrupt transfer after <n> seconds of inactivity")),
                OPT_END()
        };
@@ -38,9 +40,6 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
        if (argc != 1)
                usage_with_options(upload_pack_usage, options);
 
-       if (opts.timeout)
-               opts.daemon_mode = 1;
-
        setup_path();
 
        dir = argv[0];
@@ -50,21 +49,22 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
 
        switch (determine_protocol_version_server()) {
        case protocol_v2:
-               serve_opts.advertise_capabilities = opts.advertise_refs;
-               serve_opts.stateless_rpc = opts.stateless_rpc;
-               serve(&serve_opts);
+               if (advertise_refs)
+                       protocol_v2_advertise_capabilities();
+               else
+                       protocol_v2_serve_loop(stateless_rpc);
                break;
        case protocol_v1:
                /*
                 * v1 is just the original protocol with a version string,
                 * so just fall through after writing the version string.
                 */
-               if (opts.advertise_refs || !opts.stateless_rpc)
+               if (advertise_refs || !stateless_rpc)
                        packet_write_fmt(1, "version 1\n");
 
                /* fallthrough */
        case protocol_v0:
-               upload_pack(&opts);
+               upload_pack(advertise_refs, stateless_rpc, timeout);
                break;
        case protocol_unknown_version:
                BUG("unknown protocol version");
index 0d0a80da61f1ee4944a8728091ac472389dc6255..d22ece93e1a80597e2a14950d7362c37b10c2945 100644 (file)
@@ -8,6 +8,7 @@
 #include "branch.h"
 #include "refs.h"
 #include "run-command.h"
+#include "hook.h"
 #include "sigchain.h"
 #include "submodule.h"
 #include "utf8.h"
index ab63f4022616ba85a81fd2d96ff016fe96224fc4..a0bb687b0f4ea30d44883dcb9d4de5bf1e5963ea 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -569,18 +569,18 @@ err:
 }
 
 int unbundle(struct repository *r, struct bundle_header *header,
-            int bundle_fd, int flags)
+            int bundle_fd, struct strvec *extra_index_pack_args)
 {
-       const char *argv_index_pack[] = {"index-pack",
-                                        "--fix-thin", "--stdin", NULL, NULL};
        struct child_process ip = CHILD_PROCESS_INIT;
+       strvec_pushl(&ip.args, "index-pack", "--fix-thin", "--stdin", NULL);
 
-       if (flags & BUNDLE_VERBOSE)
-               argv_index_pack[3] = "-v";
+       if (extra_index_pack_args) {
+               strvec_pushv(&ip.args, extra_index_pack_args->v);
+               strvec_clear(extra_index_pack_args);
+       }
 
        if (verify_bundle(r, header, 0))
                return -1;
-       ip.argv = argv_index_pack;
        ip.in = bundle_fd;
        ip.no_stdout = 1;
        ip.git_cmd = 1;
index 1927d8cd6a4a4764a0a84e4ed35a6c9eb7b15fd6..06009fe6b1f00b939350ba88ba7bf8f72565de4c 100644 (file)
--- a/bundle.h
+++ b/bundle.h
@@ -26,9 +26,19 @@ int create_bundle(struct repository *r, const char *path,
                  int argc, const char **argv, struct strvec *pack_options,
                  int version);
 int verify_bundle(struct repository *r, struct bundle_header *header, int verbose);
-#define BUNDLE_VERBOSE 1
+
+/**
+ * Unbundle after reading the header with read_bundle_header().
+ *
+ * We'll invoke "git index-pack --stdin --fix-thin" for you on the
+ * provided `bundle_fd` from read_bundle_header().
+ *
+ * Provide "extra_index_pack_args" to pass any extra arguments
+ * (e.g. "-v" for verbose/progress), NULL otherwise. The provided
+ * "extra_index_pack_args" (if any) will be strvec_clear()'d for you.
+ */
 int unbundle(struct repository *r, struct bundle_header *header,
-            int bundle_fd, int flags);
+            int bundle_fd, struct strvec *extra_index_pack_args);
 int list_bundle_refs(struct bundle_header *header,
                int argc, const char **argv);
 
index 90919f9e3454791e2132236a2319b5f51c9c1d41..79d168192d74b829f4cd80cf45407074032a9593 100644 (file)
@@ -440,8 +440,9 @@ static int update_one(struct cache_tree *it,
        } else if (dryrun) {
                hash_object_file(the_hash_algo, buffer.buf, buffer.len,
                                 tree_type, &it->oid);
-       } else if (write_object_file(buffer.buf, buffer.len, tree_type,
-                                    &it->oid)) {
+       } else if (write_object_file_flags(buffer.buf, buffer.len, tree_type,
+                                          &it->oid, flags & WRITE_TREE_SILENT
+                                          ? HASH_SILENT : 0)) {
                strbuf_release(&buffer);
                return -1;
        }
@@ -826,10 +827,17 @@ static void verify_one_sparse(struct repository *r,
                    path->buf);
 }
 
-static void verify_one(struct repository *r,
-                      struct index_state *istate,
-                      struct cache_tree *it,
-                      struct strbuf *path)
+/*
+ * Returns:
+ *  0 - Verification completed.
+ *  1 - Restart verification - a call to ensure_full_index() freed the cache
+ *      tree that is being verified and verification needs to be restarted from
+ *      the new toplevel cache tree.
+ */
+static int verify_one(struct repository *r,
+                     struct index_state *istate,
+                     struct cache_tree *it,
+                     struct strbuf *path)
 {
        int i, pos, len = path->len;
        struct strbuf tree_buf = STRBUF_INIT;
@@ -837,21 +845,30 @@ static void verify_one(struct repository *r,
 
        for (i = 0; i < it->subtree_nr; i++) {
                strbuf_addf(path, "%s/", it->down[i]->name);
-               verify_one(r, istate, it->down[i]->cache_tree, path);
+               if (verify_one(r, istate, it->down[i]->cache_tree, path))
+                       return 1;
                strbuf_setlen(path, len);
        }
 
        if (it->entry_count < 0 ||
            /* no verification on tests (t7003) that replace trees */
            lookup_replace_object(r, &it->oid) != &it->oid)
-               return;
+               return 0;
 
        if (path->len) {
+               /*
+                * If the index is sparse and the cache tree is not
+                * index_name_pos() may trigger ensure_full_index() which will
+                * free the tree that is being verified.
+                */
+               int is_sparse = istate->sparse_index;
                pos = index_name_pos(istate, path->buf, path->len);
+               if (is_sparse && !istate->sparse_index)
+                       return 1;
 
                if (pos >= 0) {
                        verify_one_sparse(r, istate, it, path, pos);
-                       return;
+                       return 0;
                }
 
                pos = -pos - 1;
@@ -899,6 +916,7 @@ static void verify_one(struct repository *r,
                    oid_to_hex(&new_oid), oid_to_hex(&it->oid));
        strbuf_setlen(path, len);
        strbuf_release(&tree_buf);
+       return 0;
 }
 
 void cache_tree_verify(struct repository *r, struct index_state *istate)
@@ -907,6 +925,10 @@ void cache_tree_verify(struct repository *r, struct index_state *istate)
 
        if (!istate->cache_tree)
                return;
-       verify_one(r, istate, istate->cache_tree, &path);
+       if (verify_one(r, istate, istate->cache_tree, &path)) {
+               strbuf_reset(&path);
+               if (verify_one(r, istate, istate->cache_tree, &path))
+                       BUG("ensure_full_index() called twice while verifying cache tree");
+       }
        strbuf_release(&path);
 }
diff --git a/cache.h b/cache.h
index 0a8d44ce71d89e3f533ff63328d9b9ea0ba4a417..eba12487b99caae71cbd4367e609e156ea9867ff 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -887,6 +887,7 @@ int ie_modified(struct index_state *, const struct cache_entry *, struct stat *,
 #define HASH_WRITE_OBJECT 1
 #define HASH_FORMAT_CHECK 2
 #define HASH_RENORMALIZE  4
+#define HASH_SILENT 8
 int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
 int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags);
 
@@ -994,14 +995,6 @@ extern const char *core_fsmonitor;
 extern int core_apply_sparse_checkout;
 extern int core_sparse_checkout_cone;
 
-/*
- * Include broken refs in all ref iterations, which will
- * generally choke dangerous operations rather than letting
- * them silently proceed without taking the broken ref into
- * account.
- */
-extern int ref_paranoia;
-
 /*
  * Returns the boolean value of $GIT_OPTIONAL_LOCKS (or the default value).
  */
@@ -1210,51 +1203,10 @@ enum scld_error safe_create_leading_directories(char *path);
 enum scld_error safe_create_leading_directories_const(const char *path);
 enum scld_error safe_create_leading_directories_no_share(char *path);
 
-/*
- * Callback function for raceproof_create_file(). This function is
- * expected to do something that makes dirname(path) permanent despite
- * the fact that other processes might be cleaning up empty
- * directories at the same time. Usually it will create a file named
- * path, but alternatively it could create another file in that
- * directory, or even chdir() into that directory. The function should
- * return 0 if the action was completed successfully. On error, it
- * should return a nonzero result and set errno.
- * raceproof_create_file() treats two errno values specially:
- *
- * - ENOENT -- dirname(path) does not exist. In this case,
- *             raceproof_create_file() tries creating dirname(path)
- *             (and any parent directories, if necessary) and calls
- *             the function again.
- *
- * - EISDIR -- the file already exists and is a directory. In this
- *             case, raceproof_create_file() removes the directory if
- *             it is empty (and recursively any empty directories that
- *             it contains) and calls the function again.
- *
- * Any other errno causes raceproof_create_file() to fail with the
- * callback's return value and errno.
- *
- * Obviously, this function should be OK with being called again if it
- * fails with ENOENT or EISDIR. In other scenarios it will not be
- * called again.
- */
-typedef int create_file_fn(const char *path, void *cb);
-
-/*
- * Create a file in dirname(path) by calling fn, creating leading
- * directories if necessary. Retry a few times in case we are racing
- * with another process that is trying to clean up the directory that
- * contains path. See the documentation for create_file_fn for more
- * details.
- *
- * Return the value and set the errno that resulted from the most
- * recent call of fn. fn is always called at least once, and will be
- * called more than once if it returns ENOENT or EISDIR.
- */
-int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
-
 int mkdir_in_gitdir(const char *path);
-char *expand_user_path(const char *path, int real_home);
+char *interpolate_path(const char *path, int real_home);
+/* NEEDSWORK: remove this synonym once in-flight topics have migrated */
+#define expand_user_path interpolate_path
 const char *enter_repo(const char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
@@ -1294,6 +1246,13 @@ int is_ntfs_dotmailmap(const char *name);
  */
 int looks_like_command_line_option(const char *str);
 
+/**
+ * Return a newly allocated string with the evaluation of
+ * "$XDG_CONFIG_HOME/$subdir/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
+ * "$HOME/.config/$subdir/$filename". Return NULL upon error.
+ */
+char *xdg_config_home_for(const char *subdir, const char *filename);
+
 /**
  * Return a newly allocated string with the evaluation of
  * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
@@ -1310,11 +1269,50 @@ char *xdg_cache_home(const char *filename);
 
 int git_open_cloexec(const char *name, int flags);
 #define git_open(name) git_open_cloexec(name, O_RDONLY)
-int unpack_loose_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
-int parse_loose_header(const char *hdr, unsigned long *sizep);
+
+/**
+ * unpack_loose_header() initializes the data stream needed to unpack
+ * a loose object header.
+ *
+ * Returns:
+ *
+ * - ULHR_OK on success
+ * - ULHR_BAD on error
+ * - ULHR_TOO_LONG if the header was too long
+ *
+ * It will only parse up to MAX_HEADER_LEN bytes unless an optional
+ * "hdrbuf" argument is non-NULL. This is intended for use with
+ * OBJECT_INFO_ALLOW_UNKNOWN_TYPE to extract the bad type for (error)
+ * reporting. The full header will be extracted to "hdrbuf" for use
+ * with parse_loose_header(), ULHR_TOO_LONG will still be returned
+ * from this function to indicate that the header was too long.
+ */
+enum unpack_loose_header_result {
+       ULHR_OK,
+       ULHR_BAD,
+       ULHR_TOO_LONG,
+};
+enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
+                                                   unsigned char *map,
+                                                   unsigned long mapsize,
+                                                   void *buffer,
+                                                   unsigned long bufsiz,
+                                                   struct strbuf *hdrbuf);
+
+/**
+ * parse_loose_header() parses the starting "<type> <len>\0" of an
+ * object. If it doesn't follow that format -1 is returned. To check
+ * the validity of the <type> populate the "typep" in the "struct
+ * object_info". It will be OBJ_BAD if the object type is unknown. The
+ * parsed <len> can be retrieved via "oi->sizep", and from there
+ * passed to unpack_loose_rest().
+ */
+struct object_info;
+int parse_loose_header(const char *hdr, struct object_info *oi);
 
 int check_object_signature(struct repository *r, const struct object_id *oid,
-                          void *buf, unsigned long size, const char *type);
+                          void *buf, unsigned long size, const char *type,
+                          struct object_id *real_oidp);
 
 int finalize_object_file(const char *tmpfile, const char *filename);
 
@@ -1659,7 +1657,9 @@ struct cache_def {
        int track_flags;
        int prefix_len_stat_func;
 };
-#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
+#define CACHE_DEF_INIT { \
+       .path = STRBUF_INIT, \
+}
 static inline void cache_def_clear(struct cache_def *cache)
 {
        strbuf_release(&cache->path);
@@ -1716,13 +1716,6 @@ int update_server_info(int);
 const char *get_log_output_encoding(void);
 const char *get_commit_output_encoding(void);
 
-/*
- * This is a hack for test programs like test-dump-untracked-cache to
- * ensure that they do not modify the untracked cache when reading it.
- * Do not use it otherwise!
- */
-extern int ignore_untracked_cache_config;
-
 int committer_ident_sufficiently_given(void);
 int author_ident_sufficiently_given(void);
 
@@ -1735,6 +1728,8 @@ extern const char *git_mailmap_blob;
 void maybe_flush_or_die(FILE *, const char *);
 __attribute__((format (printf, 2, 3)))
 void fprintf_or_die(FILE *, const char *fmt, ...);
+void fwrite_or_die(FILE *f, const void *buf, size_t count);
+void fflush_or_die(FILE *f);
 
 #define COPY_READ_ERROR (-2)
 #define COPY_WRITE_ERROR (-3)
index a04a312c3f5bd14873e96edc227524a2245c6c35..dedbb8e2a459c1a704ce9198ca44c13c24a25251 100644 (file)
--- a/cbtree.h
+++ b/cbtree.h
@@ -37,11 +37,12 @@ enum cb_next {
        CB_BREAK = 1
 };
 
-#define CBTREE_INIT { .root = NULL }
+#define CBTREE_INIT { 0 }
 
 static inline void cb_init(struct cb_tree *t)
 {
-       t->root = NULL;
+       struct cb_tree blank = CBTREE_INIT;
+       memcpy(t, &blank, sizeof(*t));
 }
 
 struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen);
index 6586e30ca5a64fe61ec982f3d5dbe188d9194258..2e39dae684f8f0a048d172f36b21740f92e7de57 100644 (file)
@@ -14,7 +14,7 @@ struct tracking_name_data {
        struct object_id *default_dst_oid;
 };
 
-#define TRACKING_NAME_DATA_INIT { NULL, NULL, NULL, 0, NULL, NULL, NULL }
+#define TRACKING_NAME_DATA_INIT { 0 }
 
 static int check_tracking_name(struct remote *remote, void *cb_data)
 {
index 5772081b6e55dac5cd6973df8609852036d9cfe9..1d0e48f451558e685602ac362270d169b42caa92 100755 (executable)
@@ -12,7 +12,7 @@ UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev
  libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl"
 
 case "$jobname" in
-linux-clang|linux-gcc)
+linux-clang|linux-gcc|linux-leaks)
        sudo apt-add-repository -y "ppa:ubuntu-toolchain-r/test"
        sudo apt-get -q update
        sudo apt-get -q -y install language-pack-is libsvn-perl apache2 \
index 476c3f369f549d69f52723c21d65bd4bf8e93da6..82cb17f8eea732967860ffe5b6c82f5be92d96c7 100755 (executable)
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -183,7 +183,7 @@ export GIT_TEST_CLONE_2GB=true
 export SKIP_DASHED_BUILT_INS=YesPlease
 
 case "$jobname" in
-linux-clang|linux-gcc)
+linux-clang|linux-gcc|linux-leaks)
        if [ "$jobname" = linux-gcc ]
        then
                export CC=gcc-8
@@ -233,4 +233,11 @@ linux-musl)
        ;;
 esac
 
+case "$jobname" in
+linux-leaks)
+       export SANITIZE=leak
+       export GIT_TEST_PASSING_SANITIZE_LEAK=true
+       ;;
+esac
+
 MAKEFLAGS="$MAKEFLAGS CC=${CC:-cc}"
index f3aba5d6cbb9311749b05b487d316754f226a018..cc62616d8063cb5eadc18636d561b52a71253d90 100755 (executable)
@@ -28,6 +28,7 @@ linux-gcc)
        export GIT_TEST_COMMIT_GRAPH=1
        export GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1
        export GIT_TEST_MULTI_PACK_INDEX=1
+       export GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=1
        export GIT_TEST_ADD_I_USE_BUILTIN=1
        export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
        export GIT_TEST_WRITE_REV_INDEX=1
index 3860a0d847737930ed39286646d89c4e252e8367..2706683acfe1c1fc982b1c3119b3e895cac57006 100644 (file)
@@ -713,6 +713,7 @@ static void close_commit_graph_one(struct commit_graph *g)
        if (!g)
                return;
 
+       clear_commit_graph_data_slab(&commit_graph_data_slab);
        close_commit_graph_one(g->base_graph);
        free_commit_graph(g);
 }
@@ -723,7 +724,7 @@ void close_commit_graph(struct raw_object_store *o)
        o->commit_graph = NULL;
 }
 
-static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
+static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos)
 {
        return bsearch_hash(oid->hash, g->chunk_oid_fanout,
                            g->chunk_oid_lookup, g->hash_len, pos);
@@ -864,26 +865,55 @@ static int fill_commit_in_graph(struct repository *r,
        return 1;
 }
 
-static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
+static int search_commit_pos_in_graph(const struct object_id *id, struct commit_graph *g, uint32_t *pos)
+{
+       struct commit_graph *cur_g = g;
+       uint32_t lex_index;
+
+       while (cur_g && !bsearch_graph(cur_g, id, &lex_index))
+               cur_g = cur_g->base_graph;
+
+       if (cur_g) {
+               *pos = lex_index + cur_g->num_commits_in_base;
+               return 1;
+       }
+
+       return 0;
+}
+
+static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
 {
        uint32_t graph_pos = commit_graph_position(item);
        if (graph_pos != COMMIT_NOT_FROM_GRAPH) {
                *pos = graph_pos;
                return 1;
        } else {
-               struct commit_graph *cur_g = g;
-               uint32_t lex_index;
+               return search_commit_pos_in_graph(&item->object.oid, g, pos);
+       }
+}
+
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
+{
+       struct commit *commit;
+       uint32_t pos;
 
-               while (cur_g && !bsearch_graph(cur_g, &(item->object.oid), &lex_index))
-                       cur_g = cur_g->base_graph;
+       if (!repo->objects->commit_graph)
+               return NULL;
+       if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
+               return NULL;
+       if (!repo_has_object_file(repo, id))
+               return NULL;
 
-               if (cur_g) {
-                       *pos = lex_index + cur_g->num_commits_in_base;
-                       return 1;
-               }
+       commit = lookup_commit(repo, id);
+       if (!commit)
+               return NULL;
+       if (commit->object.parsed)
+               return commit;
 
-               return 0;
-       }
+       if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
+               return NULL;
+
+       return commit;
 }
 
 static int parse_commit_in_graph_one(struct repository *r,
@@ -895,7 +925,7 @@ static int parse_commit_in_graph_one(struct repository *r,
        if (item->object.parsed)
                return 1;
 
-       if (find_commit_in_graph(item, g, &pos))
+       if (find_commit_pos_in_graph(item, g, &pos))
                return fill_commit_in_graph(r, item, g, pos);
 
        return 0;
@@ -921,7 +951,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item)
        uint32_t pos;
        if (!prepare_commit_graph(r))
                return;
-       if (find_commit_in_graph(item, r->objects->commit_graph, &pos))
+       if (find_commit_pos_in_graph(item, r->objects->commit_graph, &pos))
                fill_commit_graph_info(item, r->objects->commit_graph, pos);
 }
 
@@ -1091,9 +1121,9 @@ static int write_graph_chunk_data(struct hashfile *f,
                                edge_value += ctx->new_num_commits_in_base;
                        else if (ctx->new_base_graph) {
                                uint32_t pos;
-                               if (find_commit_in_graph(parent->item,
-                                                        ctx->new_base_graph,
-                                                        &pos))
+                               if (find_commit_pos_in_graph(parent->item,
+                                                            ctx->new_base_graph,
+                                                            &pos))
                                        edge_value = pos;
                        }
 
@@ -1122,9 +1152,9 @@ static int write_graph_chunk_data(struct hashfile *f,
                                edge_value += ctx->new_num_commits_in_base;
                        else if (ctx->new_base_graph) {
                                uint32_t pos;
-                               if (find_commit_in_graph(parent->item,
-                                                        ctx->new_base_graph,
-                                                        &pos))
+                               if (find_commit_pos_in_graph(parent->item,
+                                                            ctx->new_base_graph,
+                                                            &pos))
                                        edge_value = pos;
                        }
 
@@ -1235,9 +1265,9 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
                                edge_value += ctx->new_num_commits_in_base;
                        else if (ctx->new_base_graph) {
                                uint32_t pos;
-                               if (find_commit_in_graph(parent->item,
-                                                        ctx->new_base_graph,
-                                                        &pos))
+                               if (find_commit_pos_in_graph(parent->item,
+                                                            ctx->new_base_graph,
+                                                            &pos))
                                        edge_value = pos;
                        }
 
@@ -2096,7 +2126,7 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
 
        ctx->num_extra_edges = 0;
        for (i = 0; i < ctx->commits.nr; i++) {
-               display_progress(ctx->progress, i);
+               display_progress(ctx->progress, i + 1);
 
                if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
                          &ctx->commits.list[i]->object.oid)) {
index 96c24fb5777de48ccb5d5144543b3c2ff6f7efae..04a94e18302d8d2e9b91933497cfb868a3cf3c12 100644 (file)
@@ -40,6 +40,14 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
  */
 int parse_commit_in_graph(struct repository *r, struct commit *item);
 
+/*
+ * Look up the given commit ID in the commit-graph. This will only return a
+ * commit if the ID exists both in the graph and in the object database such
+ * that we don't return commits whose object has been pruned. Otherwise, this
+ * function returns `NULL`.
+ */
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id);
+
 /*
  * It is possible that we loaded commit contents from the commit buffer,
  * but we also want to ensure the commit-graph content is correctly
index 143f472c0f24bfeece3209a4c8bf70be9494b557..551de4903c1f4f5d21a4e30bc012109c593054c8 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -25,6 +25,7 @@
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
 int save_commit_buffer = 1;
+int no_graft_file_deprecated_advice;
 
 const char *commit_type = "commit";
 
@@ -190,7 +191,8 @@ static int read_graft_file(struct repository *r, const char *graft_file)
        struct strbuf buf = STRBUF_INIT;
        if (!fp)
                return -1;
-       if (advice_graft_file_deprecated)
+       if (!no_graft_file_deprecated_advice &&
+           advice_enabled(ADVICE_GRAFT_FILE_DEPRECATED))
                advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
                         "and will be removed in a future Git version.\n"
                         "\n"
index df42eb434f314bd9cf89e920efdb0fc29a86c4c5..3ea32766bcb00ada1c55f948ab6cc503b04c72a7 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ struct commit {
 };
 
 extern int save_commit_buffer;
+extern int no_graft_file_deprecated_advice;
 extern const char *commit_type;
 
 /* While we can decorate any object with a name, it's only used for commits.. */
diff --git a/compat/linux/procinfo.c b/compat/linux/procinfo.c
new file mode 100644 (file)
index 0000000..bc2f938
--- /dev/null
@@ -0,0 +1,176 @@
+#include "cache.h"
+
+#include "strbuf.h"
+#include "strvec.h"
+#include "trace2.h"
+
+/*
+ * We need more complex parsing in stat_parent_pid() and
+ * parse_proc_stat() below than a dumb fscanf(). That's because while
+ * the statcomm field is surrounded by parentheses, the process itself
+ * is free to insert any arbitrary byte sequence its its name. That
+ * can include newlines, spaces, closing parentheses etc.
+ *
+ * See do_task_stat() in fs/proc/array.c in linux.git, this is in
+ * contrast with the escaped version of the name found in
+ * /proc/%d/status.
+ *
+ * So instead of using fscanf() we'll read N bytes from it, look for
+ * the first "(", and then the last ")", anything in-between is our
+ * process name.
+ *
+ * How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
+ * default, but it can be raised set to values of up to 2^22. So
+ * that's 7 digits for a PID. We have 2 PIDs in the first four fields
+ * we're interested in, so 2 * 7 = 14.
+ *
+ * We then have 3 spaces between those four values, and we'd like to
+ * get to the space between the 4th and the 5th (the "pgrp" field) to
+ * make sure we read the entire "ppid" field. So that brings us up to
+ * 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
+ * and it's 20. The "state" value itself is then one character (now at
+ * 21).
+ *
+ * Finally the maximum length of the "comm" name itself is 15
+ * characters, e.g. a setting of "123456789abcdefg" will be truncated
+ * to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
+ * we'd need to read 21 + 15 = 36 bytes.
+ *
+ * Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
+ * grows past 2^22 we'll be future-proof. We'll then anchor at the
+ * last ")" we find to locate the parent PID.
+ */
+#define STAT_PARENT_PID_READ_N 64
+
+static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
+                           int *statppid)
+{
+       const char *comm_lhs = strchr(sb->buf, '(');
+       const char *comm_rhs = strrchr(sb->buf, ')');
+       const char *ppid_lhs, *ppid_rhs;
+       char *p;
+       pid_t ppid;
+
+       if (!comm_lhs || !comm_rhs)
+               goto bad_kernel;
+
+       /*
+        * We're at the ")", that's followed by " X ", where X is a
+        * single "state" character. So advance by 4 bytes.
+        */
+       ppid_lhs = comm_rhs + 4;
+
+       /*
+        * Read until the space between the "ppid" and "pgrp" fields
+        * to make sure we're anchored after the untruncated "ppid"
+        * field..
+        */
+       ppid_rhs = strchr(ppid_lhs, ' ');
+       if (!ppid_rhs)
+               goto bad_kernel;
+
+       ppid = strtol(ppid_lhs, &p, 10);
+       if (ppid_rhs == p) {
+               const char *comm = comm_lhs + 1;
+               size_t commlen = comm_rhs - comm;
+
+               strbuf_add(name, comm, commlen);
+               *statppid = ppid;
+
+               return 0;
+       }
+
+bad_kernel:
+       /*
+        * We were able to read our STAT_PARENT_PID_READ_N bytes from
+        * /proc/%d/stat, but the content is bad. Broken kernel?
+        * Should not happen, but handle it gracefully.
+        */
+       return -1;
+}
+
+static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
+{
+       struct strbuf procfs_path = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT;
+       FILE *fp;
+       int ret = -1;
+
+       /* try to use procfs if it's present. */
+       strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
+       fp = fopen(procfs_path.buf, "r");
+       if (!fp)
+               goto cleanup;
+
+       /*
+        * We could be more strict here and assert that we read at
+        * least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
+        * that on any modern kernel (at least since 2.6.0 released in
+        * 2003) even if all the mandatory numeric fields were zero'd
+        * out we'd get at least 100 bytes, but let's just check that
+        * we got anything at all and trust the parse_proc_stat()
+        * function to handle its "Bad Kernel?" error checking.
+        */
+       if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
+               goto cleanup;
+       if (parse_proc_stat(&sb, name, statppid) < 0)
+               goto cleanup;
+
+       ret = 0;
+cleanup:
+       if (fp)
+               fclose(fp);
+       strbuf_release(&procfs_path);
+       strbuf_release(&sb);
+
+       return ret;
+}
+
+static void push_ancestry_name(struct strvec *names, pid_t pid)
+{
+       struct strbuf name = STRBUF_INIT;
+       int ppid;
+
+       if (stat_parent_pid(pid, &name, &ppid) < 0)
+               goto cleanup;
+
+       strvec_push(names, name.buf);
+
+       /*
+        * Both errors and reaching the end of the process chain are
+        * reported as fields of 0 by proc(5)
+        */
+       if (ppid)
+               push_ancestry_name(names, ppid);
+cleanup:
+       strbuf_release(&name);
+
+       return;
+}
+
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+       struct strvec names = STRVEC_INIT;
+
+       if (!trace2_is_enabled())
+               return;
+
+       switch (reason) {
+       case TRACE2_PROCESS_INFO_EXIT:
+               /*
+                * The Windows version of this calls its
+                * get_peak_memory_info() here. We may want to insert
+                * similar process-end statistics here in the future.
+                */
+               break;
+       case TRACE2_PROCESS_INFO_STARTUP:
+               push_ancestry_name(&names, getppid());
+
+               if (names.nr)
+                       trace2_cmd_ancestry(names.v);
+               strvec_clear(&names);
+               break;
+       }
+
+       return;
+}
index 1cc31c350223b84cd97939afccf1bcc1caa070b4..edb438a7776aed1c1559352ce3b32e051cac806a 100644 (file)
@@ -510,7 +510,7 @@ static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *me
        assert(idx<=THREADCACHEMAXBINS);
        if(tck==*binsptr)
        {
-               fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck);
+               fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck);
                abort();
        }
 #ifdef FULLSANITYCHECKS
index 1927e6ef4bca8eb7f7a6827078e4c56cd5d40a0f..4e28857a0a1e22aaa7538be2ef6d766a373dbca5 100644 (file)
@@ -168,7 +168,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)
 
 int ipc_client_send_command_to_connection(
        struct ipc_client_connection *connection,
-       const char *message, struct strbuf *answer)
+       const char *message, size_t message_len,
+       struct strbuf *answer)
 {
        int ret = 0;
 
@@ -176,7 +177,7 @@ int ipc_client_send_command_to_connection(
 
        trace2_region_enter("ipc-client", "send-command", NULL);
 
-       if (write_packetized_from_buf_no_flush(message, strlen(message),
+       if (write_packetized_from_buf_no_flush(message, message_len,
                                               connection->fd) < 0 ||
            packet_flush_gently(connection->fd) < 0) {
                ret = error(_("could not send IPC command"));
@@ -197,7 +198,8 @@ done:
 
 int ipc_client_send_command(const char *path,
                            const struct ipc_client_connect_options *options,
-                           const char *message, struct strbuf *answer)
+                           const char *message, size_t message_len,
+                           struct strbuf *answer)
 {
        int ret = -1;
        enum ipc_active_state state;
@@ -208,7 +210,9 @@ int ipc_client_send_command(const char *path,
        if (state != IPC_STATE__LISTENING)
                return ret;
 
-       ret = ipc_client_send_command_to_connection(connection, message, answer);
+       ret = ipc_client_send_command_to_connection(connection,
+                                                   message, message_len,
+                                                   answer);
 
        ipc_client_close_connection(connection);
 
@@ -503,7 +507,7 @@ static int worker_thread__do_io(
        if (ret >= 0) {
                ret = worker_thread_data->server_data->application_cb(
                        worker_thread_data->server_data->application_data,
-                       buf.buf, do_io_reply_callback, &reply_data);
+                       buf.buf, buf.len, do_io_reply_callback, &reply_data);
 
                packet_flush_gently(reply_data.fd);
        }
index 8dc7bda087da5f6f5996e7f79e2e0631d09ace4e..20ea7b65e0ba6311b22678fba5201e4a7ebeb8ce 100644 (file)
@@ -3,6 +3,8 @@
 #include "strbuf.h"
 #include "pkt-line.h"
 #include "thread-utils.h"
+#include "accctrl.h"
+#include "aclapi.h"
 
 #ifndef SUPPORTS_SIMPLE_IPC
 /*
@@ -49,6 +51,9 @@ static enum ipc_active_state get_active_state(wchar_t *pipe_path)
        if (GetLastError() == ERROR_FILE_NOT_FOUND)
                return IPC_STATE__PATH_NOT_FOUND;
 
+       trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle",
+                          (intmax_t)GetLastError());
+
        return IPC_STATE__OTHER_ERROR;
 }
 
@@ -109,9 +114,15 @@ static enum ipc_active_state connect_to_server(
                        t_start_ms = (DWORD)(getnanotime() / 1000000);
 
                        if (!WaitNamedPipeW(wpath, timeout_ms)) {
-                               if (GetLastError() == ERROR_SEM_TIMEOUT)
+                               DWORD gleWait = GetLastError();
+
+                               if (gleWait == ERROR_SEM_TIMEOUT)
                                        return IPC_STATE__NOT_LISTENING;
 
+                               trace2_data_intmax("ipc-debug", NULL,
+                                                  "connect/waitpipe/gle",
+                                                  (intmax_t)gleWait);
+
                                return IPC_STATE__OTHER_ERROR;
                        }
 
@@ -133,17 +144,31 @@ static enum ipc_active_state connect_to_server(
                        break; /* try again */
 
                default:
+                       trace2_data_intmax("ipc-debug", NULL,
+                                          "connect/createfile/gle",
+                                          (intmax_t)gle);
+
                        return IPC_STATE__OTHER_ERROR;
                }
        }
 
        if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
+               gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL,
+                                  "connect/setpipestate/gle",
+                                  (intmax_t)gle);
+
                CloseHandle(hPipe);
                return IPC_STATE__OTHER_ERROR;
        }
 
        *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
        if (*pfd < 0) {
+               gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL,
+                                  "connect/openosfhandle/gle",
+                                  (intmax_t)gle);
+
                CloseHandle(hPipe);
                return IPC_STATE__OTHER_ERROR;
        }
@@ -208,7 +233,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)
 
 int ipc_client_send_command_to_connection(
        struct ipc_client_connection *connection,
-       const char *message, struct strbuf *answer)
+       const char *message, size_t message_len,
+       struct strbuf *answer)
 {
        int ret = 0;
 
@@ -216,7 +242,7 @@ int ipc_client_send_command_to_connection(
 
        trace2_region_enter("ipc-client", "send-command", NULL);
 
-       if (write_packetized_from_buf_no_flush(message, strlen(message),
+       if (write_packetized_from_buf_no_flush(message, message_len,
                                               connection->fd) < 0 ||
            packet_flush_gently(connection->fd) < 0) {
                ret = error(_("could not send IPC command"));
@@ -239,7 +265,8 @@ done:
 
 int ipc_client_send_command(const char *path,
                            const struct ipc_client_connect_options *options,
-                           const char *message, struct strbuf *response)
+                           const char *message, size_t message_len,
+                           struct strbuf *response)
 {
        int ret = -1;
        enum ipc_active_state state;
@@ -250,7 +277,9 @@ int ipc_client_send_command(const char *path,
        if (state != IPC_STATE__LISTENING)
                return ret;
 
-       ret = ipc_client_send_command_to_connection(connection, message, response);
+       ret = ipc_client_send_command_to_connection(connection,
+                                                   message, message_len,
+                                                   response);
 
        ipc_client_close_connection(connection);
 
@@ -458,7 +487,7 @@ static int do_io(struct ipc_server_thread_data *server_thread_data)
        if (ret >= 0) {
                ret = server_thread_data->server_data->application_cb(
                        server_thread_data->server_data->application_data,
-                       buf.buf, do_io_reply_callback, &reply_data);
+                       buf.buf, buf.len, do_io_reply_callback, &reply_data);
 
                packet_flush_gently(reply_data.fd);
 
@@ -565,11 +594,132 @@ finished:
        return NULL;
 }
 
+/*
+ * We need to build a Windows "SECURITY_ATTRIBUTES" object and use it
+ * to apply an ACL when we create the initial instance of the Named
+ * Pipe.  The construction is somewhat involved and consists of
+ * several sequential steps and intermediate objects.
+ *
+ * We use this structure to hold these intermediate pointers so that
+ * we can free them as a group.  (It is unclear from the docs whether
+ * some of these intermediate pointers can be freed before we are
+ * finished using the "lpSA" member.)
+ */
+struct my_sa_data
+{
+       PSID pEveryoneSID;
+       PACL pACL;
+       PSECURITY_DESCRIPTOR pSD;
+       LPSECURITY_ATTRIBUTES lpSA;
+};
+
+static void init_sa(struct my_sa_data *d)
+{
+       memset(d, 0, sizeof(*d));
+}
+
+static void release_sa(struct my_sa_data *d)
+{
+       if (d->pEveryoneSID)
+               FreeSid(d->pEveryoneSID);
+       if (d->pACL)
+               LocalFree(d->pACL);
+       if (d->pSD)
+               LocalFree(d->pSD);
+       if (d->lpSA)
+               LocalFree(d->lpSA);
+
+       memset(d, 0, sizeof(*d));
+}
+
+/*
+ * Create SECURITY_ATTRIBUTES to apply to the initial named pipe.  The
+ * creator of the first server instance gets to set the ACLs on it.
+ *
+ * We allow the well-known group `EVERYONE` to have read+write access
+ * to the named pipe so that clients can send queries to the daemon
+ * and receive the response.
+ *
+ * Normally, this is not necessary since the daemon is usually
+ * automatically started by a foreground command like `git status`,
+ * but in those cases where an elevated Git command started the daemon
+ * (such that the daemon itself runs with elevation), we need to add
+ * the ACL so that non-elevated commands can write to it.
+ *
+ * The following document was helpful:
+ * https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
+ *
+ * Returns d->lpSA set to a SA or NULL.
+ */
+static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d)
+{
+       SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY;
+#define NR_EA (1)
+       EXPLICIT_ACCESS ea[NR_EA];
+       DWORD dwResult;
+
+       if (!AllocateAndInitializeSid(&sid_auth_world, 1,
+                                     SECURITY_WORLD_RID, 0,0,0,0,0,0,0,
+                                     &d->pEveryoneSID)) {
+               DWORD gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle",
+                                  (intmax_t)gle);
+               goto fail;
+       }
+
+       memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS));
+
+       ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
+       ea[0].grfAccessMode = SET_ACCESS;
+       ea[0].grfInheritance = NO_INHERITANCE;
+       ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+       ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+       ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+       ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID;
+
+       dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL);
+       if (dwResult != ERROR_SUCCESS) {
+               DWORD gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle",
+                                  (intmax_t)gle);
+               trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw",
+                                  (intmax_t)dwResult);
+               goto fail;
+       }
+
+       d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(
+               LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+       if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) {
+               DWORD gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle);
+               goto fail;
+       }
+
+       if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) {
+               DWORD gle = GetLastError();
+               trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle);
+               goto fail;
+       }
+
+       d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
+       d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES);
+       d->lpSA->lpSecurityDescriptor = d->pSD;
+       d->lpSA->bInheritHandle = FALSE;
+
+       return d->lpSA;
+
+fail:
+       release_sa(d);
+       return NULL;
+}
+
 static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
 {
        HANDLE hPipe;
        DWORD dwOpenMode, dwPipeMode;
-       LPSECURITY_ATTRIBUTES lpsa = NULL;
+       struct my_sa_data my_sa_data;
+
+       init_sa(&my_sa_data);
 
        dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
                FILE_FLAG_OVERLAPPED;
@@ -585,20 +735,15 @@ static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
                 * set the ACL / Security Attributes on the named
                 * pipe; subsequent instances inherit and cannot
                 * change them.
-                *
-                * TODO Should we allow the application layer to
-                * specify security attributes, such as `LocalService`
-                * or `LocalSystem`, when we create the named pipe?
-                * This question is probably not important when the
-                * daemon is started by a foreground user process and
-                * only needs to talk to the current user, but may be
-                * if the daemon is run via the Control Panel as a
-                * System Service.
                 */
+               get_sa(&my_sa_data);
        }
 
        hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
-                                PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
+                                PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
+                                my_sa_data.lpSA);
+
+       release_sa(&my_sa_data);
 
        return hPipe;
 }
diff --git a/compat/stub/procinfo.c b/compat/stub/procinfo.c
new file mode 100644 (file)
index 0000000..12c0a23
--- /dev/null
@@ -0,0 +1,11 @@
+#include "git-compat-util.h"
+
+#include "trace2.h"
+
+/*
+ * Stub. See sample implementations in compat/linux/procinfo.c and
+ * compat/win32/trace2_win32_process_info.c.
+ */
+void trace2_collect_process_info(enum trace2_process_info_reason reason)
+{
+}
index 43b73ddc75891a090d81b5e4ef34864f83c653a8..5b903e7c7e3e32c25e59efcc151217d00d243d65 100644 (file)
@@ -8,8 +8,6 @@
 
 #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
 
-static void restore_term(void);
-
 static void restore_term_on_signal(int sig)
 {
        restore_term();
@@ -25,7 +23,7 @@ static void restore_term_on_signal(int sig)
 static int term_fd = -1;
 static struct termios old_term;
 
-static void restore_term(void)
+void restore_term(void)
 {
        if (term_fd < 0)
                return;
@@ -35,15 +33,22 @@ static void restore_term(void)
        term_fd = -1;
 }
 
+int save_term(int full_duplex)
+{
+       if (term_fd < 0)
+               term_fd = open("/dev/tty", O_RDWR);
+
+       return (term_fd < 0) ? -1 : tcgetattr(term_fd, &old_term);
+}
+
 static int disable_bits(tcflag_t bits)
 {
        struct termios t;
 
-       term_fd = open("/dev/tty", O_RDWR);
-       if (tcgetattr(term_fd, &t) < 0)
+       if (save_term(0) < 0)
                goto error;
 
-       old_term = t;
+       t = old_term;
        sigchain_push_common(restore_term_on_signal);
 
        t.c_lflag &= ~bits;
@@ -75,9 +80,10 @@ static int enable_non_canonical(void)
 static int use_stty = 1;
 static struct string_list stty_restore = STRING_LIST_INIT_DUP;
 static HANDLE hconin = INVALID_HANDLE_VALUE;
-static DWORD cmode;
+static HANDLE hconout = INVALID_HANDLE_VALUE;
+static DWORD cmode_in, cmode_out;
 
-static void restore_term(void)
+void restore_term(void)
 {
        if (use_stty) {
                int i;
@@ -97,9 +103,42 @@ static void restore_term(void)
        if (hconin == INVALID_HANDLE_VALUE)
                return;
 
-       SetConsoleMode(hconin, cmode);
+       SetConsoleMode(hconin, cmode_in);
+       CloseHandle(hconin);
+       if (cmode_out) {
+               assert(hconout != INVALID_HANDLE_VALUE);
+               SetConsoleMode(hconout, cmode_out);
+               CloseHandle(hconout);
+       }
+
+       hconin = hconout = INVALID_HANDLE_VALUE;
+}
+
+int save_term(int full_duplex)
+{
+       hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
+           FILE_SHARE_READ, NULL, OPEN_EXISTING,
+           FILE_ATTRIBUTE_NORMAL, NULL);
+       if (hconin == INVALID_HANDLE_VALUE)
+               return -1;
+
+       if (full_duplex) {
+               hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
+                       FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+                       FILE_ATTRIBUTE_NORMAL, NULL);
+               if (hconout == INVALID_HANDLE_VALUE)
+                       goto error;
+
+               GetConsoleMode(hconout, &cmode_out);
+       }
+
+       GetConsoleMode(hconin, &cmode_in);
+       use_stty = 0;
+       return 0;
+error:
        CloseHandle(hconin);
        hconin = INVALID_HANDLE_VALUE;
+       return -1;
 }
 
 static int disable_bits(DWORD bits)
@@ -135,15 +174,11 @@ static int disable_bits(DWORD bits)
                use_stty = 0;
        }
 
-       hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
-           FILE_SHARE_READ, NULL, OPEN_EXISTING,
-           FILE_ATTRIBUTE_NORMAL, NULL);
-       if (hconin == INVALID_HANDLE_VALUE)
+       if (save_term(0) < 0)
                return -1;
 
-       GetConsoleMode(hconin, &cmode);
        sigchain_push_common(restore_term_on_signal);
-       if (!SetConsoleMode(hconin, cmode & ~bits)) {
+       if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
                CloseHandle(hconin);
                hconin = INVALID_HANDLE_VALUE;
                return -1;
@@ -361,6 +396,16 @@ int read_key_without_echo(struct strbuf *buf)
 
 #else
 
+int save_term(int full_duplex)
+{
+       /* full_duplex == 1, but no support available */
+       return -full_duplex;
+}
+
+void restore_term(void)
+{
+}
+
 char *git_terminal_prompt(const char *prompt, int echo)
 {
        return getpass(prompt);
index a9d52b8464e2f6e3c39bc107078cb5b2a36aa5d5..e1770c575b2c5c501d3fbf3bcbe3873d288a1c92 100644 (file)
@@ -1,6 +1,9 @@
 #ifndef COMPAT_TERMINAL_H
 #define COMPAT_TERMINAL_H
 
+int save_term(int full_duplex);
+void restore_term(void);
+
 char *git_terminal_prompt(const char *prompt, int echo);
 
 /* Read a single keystroke, without echoing it to the terminal */
index 51fb083dbbe213351bab20a4352013aa5a69a6dd..29ec1d0f104b804bb83aade5a4630d6ff88dab50 100644 (file)
@@ -92,7 +92,7 @@ The Steps of Build Git with VS2008
    the git operations.
 
 3. Inside Git's directory run the command:
-       make command-list.h config-list.h
+       make generated-hdrs
    to generate the header file needed to compile git.
 
 4. Then either build Git with the GNU Make Makefile in the Git projects
index 9e631c8593ff158ef9b025d76602849a524d1f65..2b3637135f68a3e7c2acadf0971e143cf689db02 100644 (file)
  *                        source, target);
  */
 
+typedef void (*FARVOIDPROC)(void);
+
 struct proc_addr {
        const char *const dll;
        const char *const function;
-       FARPROC pfunction;
+       FARVOIDPROC pfunction;
        unsigned initialized : 1;
 };
 
@@ -26,7 +28,8 @@ struct proc_addr {
 #define DECLARE_PROC_ADDR(dll, rettype, function, ...) \
        static struct proc_addr proc_addr_##function = \
        { #dll, #function, NULL, 0 }; \
-       static rettype (WINAPI *function)(__VA_ARGS__)
+       typedef rettype (WINAPI *proc_type_##function)(__VA_ARGS__); \
+       static proc_type_##function function
 
 /*
  * Loads a function from a DLL (once-only).
@@ -35,9 +38,9 @@ struct proc_addr {
  * This function is not thread-safe.
  */
 #define INIT_PROC_ADDR(function) \
-       (function = get_proc_addr(&proc_addr_##function))
+       (function = (proc_type_##function)get_proc_addr(&proc_addr_##function))
 
-static inline void *get_proc_addr(struct proc_addr *proc)
+static inline FARVOIDPROC get_proc_addr(struct proc_addr *proc)
 {
        /* only do this once */
        if (!proc->initialized) {
@@ -46,7 +49,8 @@ static inline void *get_proc_addr(struct proc_addr *proc)
                hnd = LoadLibraryExA(proc->dll, NULL,
                                     LOAD_LIBRARY_SEARCH_SYSTEM32);
                if (hnd)
-                       proc->pfunction = GetProcAddress(hnd, proc->function);
+                       proc->pfunction = (FARVOIDPROC)GetProcAddress(hnd,
+                                                       proc->function);
        }
        /* set ENOSYS if DLL or function was not found */
        if (!proc->pfunction)
index 7a6ff18ffa6c4697c6b4513b1a31b8f47e97a7b2..2dcbe901b6b7a05f56a66056ac79d9b729454382 100644 (file)
--- a/config.c
+++ b/config.c
@@ -136,7 +136,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        if (!path)
                return config_error_nonbool("include.path");
 
-       expanded = expand_user_path(path, 0);
+       expanded = interpolate_path(path, 0);
        if (!expanded)
                return error(_("could not expand include path '%s'"), path);
        path = expanded;
@@ -184,7 +184,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
        char *expanded;
        int prefix = 0;
 
-       expanded = expand_user_path(pat->buf, 1);
+       expanded = interpolate_path(pat->buf, 1);
        if (expanded) {
                strbuf_reset(pat);
                strbuf_addstr(pat, expanded);
@@ -425,7 +425,7 @@ static inline int iskeychar(int c)
  * baselen - pointer to size_t which will hold the length of the
  *           section + subsection part, can be NULL
  */
-static int git_config_parse_key_1(const char *key, char **store_key, size_t *baselen_, int quiet)
+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
 {
        size_t i, baselen;
        int dot;
@@ -437,14 +437,12 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
         */
 
        if (last_dot == NULL || last_dot == key) {
-               if (!quiet)
-                       error(_("key does not contain a section: %s"), key);
+               error(_("key does not contain a section: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
 
        if (!last_dot[1]) {
-               if (!quiet)
-                       error(_("key does not contain variable name: %s"), key);
+               error(_("key does not contain variable name: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
 
@@ -455,8 +453,7 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
        /*
         * Validate the key and while at it, lower case it for matching.
         */
-       if (store_key)
-               *store_key = xmallocz(strlen(key));
+       *store_key = xmallocz(strlen(key));
 
        dot = 0;
        for (i = 0; key[i]; i++) {
@@ -467,39 +464,24 @@ static int git_config_parse_key_1(const char *key, char **store_key, size_t *bas
                if (!dot || i > baselen) {
                        if (!iskeychar(c) ||
                            (i == baselen + 1 && !isalpha(c))) {
-                               if (!quiet)
-                                       error(_("invalid key: %s"), key);
+                               error(_("invalid key: %s"), key);
                                goto out_free_ret_1;
                        }
                        c = tolower(c);
                } else if (c == '\n') {
-                       if (!quiet)
-                               error(_("invalid key (newline): %s"), key);
+                       error(_("invalid key (newline): %s"), key);
                        goto out_free_ret_1;
                }
-               if (store_key)
-                       (*store_key)[i] = c;
+               (*store_key)[i] = c;
        }
 
        return 0;
 
 out_free_ret_1:
-       if (store_key) {
-               FREE_AND_NULL(*store_key);
-       }
+       FREE_AND_NULL(*store_key);
        return -CONFIG_INVALID_KEY;
 }
 
-int git_config_parse_key(const char *key, char **store_key, size_t *baselen)
-{
-       return git_config_parse_key_1(key, store_key, baselen, 0);
-}
-
-int git_config_key_is_valid(const char *key)
-{
-       return !git_config_parse_key_1(key, NULL, NULL, 1);
-}
-
 static int config_parse_pair(const char *key, const char *value,
                          config_fn_t fn, void *data)
 {
@@ -1269,7 +1251,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
 {
        if (!value)
                return config_error_nonbool(var);
-       *dest = expand_user_path(value, 0);
+       *dest = interpolate_path(value, 0);
        if (!*dest)
                die(_("failed to expand user dir in: '%s'"), value);
        return 0;
@@ -1793,6 +1775,7 @@ int git_config_from_mem(config_fn_t fn,
 
 int git_config_from_blob_oid(config_fn_t fn,
                              const char *name,
+                             struct repository *repo,
                              const struct object_id *oid,
                              void *data)
 {
@@ -1801,7 +1784,7 @@ int git_config_from_blob_oid(config_fn_t fn,
        unsigned long size;
        int ret;
 
-       buf = read_object_file(oid, &type, &size);
+       buf = repo_read_object_file(repo, oid, &type, &size);
        if (!buf)
                return error(_("unable to load config blob object '%s'"), name);
        if (type != OBJ_BLOB) {
@@ -1817,14 +1800,15 @@ int git_config_from_blob_oid(config_fn_t fn,
 }
 
 static int git_config_from_blob_ref(config_fn_t fn,
+                                   struct repository *repo,
                                    const char *name,
                                    void *data)
 {
        struct object_id oid;
 
-       if (get_oid(name, &oid) < 0)
+       if (repo_get_oid(repo, name, &oid) < 0)
                return error(_("unable to resolve config blob '%s'"), name);
-       return git_config_from_blob_oid(fn, name, &oid, data);
+       return git_config_from_blob_oid(fn, name, repo, &oid, data);
 }
 
 char *git_system_config(void)
@@ -1842,7 +1826,7 @@ void git_global_config(char **user_out, char **xdg_out)
        char *xdg_config = NULL;
 
        if (!user_config) {
-               user_config = expand_user_path("~/.gitconfig", 0);
+               user_config = interpolate_path("~/.gitconfig", 0);
                xdg_config = xdg_config_home("config");
        }
 
@@ -1955,12 +1939,16 @@ int config_with_options(config_fn_t fn, void *data,
         * If we have a specific filename, use it. Otherwise, follow the
         * regular lookup sequence.
         */
-       if (config_source && config_source->use_stdin)
+       if (config_source && config_source->use_stdin) {
                return git_config_from_stdin(fn, data);
-       else if (config_source && config_source->file)
+       } else if (config_source && config_source->file) {
                return git_config_from_file(fn, config_source->file, data);
-       else if (config_source && config_source->blob)
-               return git_config_from_blob_ref(fn, config_source->blob, data);
+       } else if (config_source && config_source->blob) {
+               struct repository *repo = config_source->repo ?
+                       config_source->repo : the_repository;
+               return git_config_from_blob_ref(fn, repo, config_source->blob,
+                                               data);
+       }
 
        return do_git_config_sequence(opts, fn, data);
 }
index a2200f311156c47431e6ff17fbfd87e3ff07cdae..f119de01309ccf5ce5b0a6434d38fa70c472bab0 100644 (file)
--- a/config.h
+++ b/config.h
@@ -49,6 +49,8 @@ const char *config_scope_name(enum config_scope scope);
 struct git_config_source {
        unsigned int use_stdin:1;
        const char *file;
+       /* The repository if blob is not NULL; leave blank for the_repository */
+       struct repository *repo;
        const char *blob;
        enum config_scope scope;
 };
@@ -136,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,
                        const char *buf, size_t len,
                        void *data, const struct config_options *opts);
 int git_config_from_blob_oid(config_fn_t fn, const char *name,
+                            struct repository *repo,
                             const struct object_id *oid, void *data);
 void git_config_push_parameter(const char *text);
 void git_config_push_env(const char *spec);
@@ -256,7 +259,6 @@ int git_config_set_gently(const char *, const char *);
 void git_config_set(const char *, const char *);
 
 int git_config_parse_key(const char *, char **, size_t *);
-int git_config_key_is_valid(const char *key);
 
 /*
  * The following macros specify flag bits that alter the behavior
@@ -606,7 +608,6 @@ int git_config_get_maybe_bool(const char *key, int *dest);
 int git_config_get_pathname(const char *key, const char **dest);
 
 int git_config_get_index_threads(int *dest);
-int git_config_get_untracked_cache(void);
 int git_config_get_split_index(void);
 int git_config_get_max_percent_split_change(void);
 int git_config_get_fsmonitor(void);
index 022fb58218029a0196609e143073c559811089de..7673fed11425409c9a7fd584fb4387e79678d498 100644 (file)
@@ -1,13 +1,24 @@
+ifndef COMPILER_FEATURES
+COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
+endif
+
 ifeq ($(filter no-error,$(DEVOPTS)),)
 DEVELOPER_CFLAGS += -Werror
 SPARSE_FLAGS += -Wsparse-error
 endif
-ifneq ($(filter pedantic,$(DEVOPTS)),)
+
+DEVELOPER_CFLAGS += -Wall
+ifeq ($(filter no-pedantic,$(DEVOPTS)),)
 DEVELOPER_CFLAGS += -pedantic
-# don't warn for each N_ use
-DEVELOPER_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0
+ifneq (($or $(filter gcc5,$(COMPILER_FEATURES)),$(filter clang4,$(COMPILER_FEATURES))),)
+DEVELOPER_CFLAGS += -Wpedantic
+ifneq ($(filter gcc10,$(COMPILER_FEATURES)),)
+ifeq ($(uname_S),MINGW)
+DEVELOPER_CFLAGS += -Wno-pedantic-ms-format
+endif
+endif
+endif
 endif
-DEVELOPER_CFLAGS += -Wall
 DEVELOPER_CFLAGS += -Wdeclaration-after-statement
 DEVELOPER_CFLAGS += -Wformat-security
 DEVELOPER_CFLAGS += -Wold-style-definition
@@ -18,10 +29,6 @@ DEVELOPER_CFLAGS += -Wunused
 DEVELOPER_CFLAGS += -Wvla
 DEVELOPER_CFLAGS += -fno-common
 
-ifndef COMPILER_FEATURES
-COMPILER_FEATURES := $(shell ./detect-compiler $(CC))
-endif
-
 ifneq ($(filter clang4,$(COMPILER_FEATURES)),)
 DEVELOPER_CFLAGS += -Wtautological-constant-out-of-range-compare
 endif
index 69413fb3dc0ad860a16ea044df2e90212d9ea522..3236a4918a319b31e7350475a2bdba941cdcecbb 100644 (file)
@@ -11,6 +11,10 @@ uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
 uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
 uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not')
 
+ifneq ($(findstring MINGW,$(uname_S)),)
+       uname_S := MINGW
+endif
+
 ifdef MSVC
        # avoid the MingW and Cygwin configuration sections
        uname_S := Windows
@@ -58,6 +62,8 @@ ifeq ($(uname_S),Linux)
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        BASIC_CFLAGS += -DHAVE_SYSINFO
        PROCFS_EXECUTABLE_PATH = /proc/self/exe
+       HAVE_PLATFORM_PROCINFO = YesPlease
+       COMPAT_OBJS += compat/linux/procinfo.o
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
        HAVE_ALLOCA_H = YesPlease
@@ -586,7 +592,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
        SHELL_PATH = /usr/coreutils/bin/bash
 endif
-ifneq (,$(findstring MINGW,$(uname_S)))
+ifeq ($(uname_S),MINGW)
        pathsep = ;
        HAVE_ALLOCA_H = YesPlease
        NO_PREAD = YesPlease
@@ -617,6 +623,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        ETAGS_TARGET = ETAGS
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
+       HAVE_PLATFORM_PROCINFO = YesPlease
        BASIC_LDFLAGS += -municode
        COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
@@ -732,9 +739,9 @@ vcxproj:
         echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets
        git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets
 
-       # Add command-list.h and config-list.h
-       $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 config-list.h command-list.h
-       git add -f config-list.h command-list.h
+       # Add generated headers
+       $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(GENERATED_H)
+       git add -f $(GENERATED_H)
 
        # Add scripts
        rm -f perl/perl.mak
index b18299fdf0e5224924915810d66196a1d38cbe56..cf68e37a97b612d7ac4c05370c72421b2b714c3f 100644 (file)
@@ -24,7 +24,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
        struct child_process rev_list = CHILD_PROCESS_INIT;
        FILE *rev_list_in;
        struct check_connected_options defaults = CHECK_CONNECTED_INIT;
-       struct object_id oid;
+       const struct object_id *oid;
        int err = 0;
        struct packed_git *new_pack = NULL;
        struct transport *transport;
@@ -34,7 +34,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                opt = &defaults;
        transport = opt->transport;
 
-       if (fn(cb_data, &oid)) {
+       oid = fn(cb_data);
+       if (!oid) {
                if (opt->err_fd)
                        close(opt->err_fd);
                return err;
@@ -73,7 +74,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                        for (p = get_all_packs(the_repository); p; p = p->next) {
                                if (!p->pack_promisor)
                                        continue;
-                               if (find_pack_entry_one(oid.hash, p))
+                               if (find_pack_entry_one(oid->hash, p))
                                        goto promisor_pack_found;
                        }
                        /*
@@ -83,7 +84,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
                        goto no_promisor_pack_found;
 promisor_pack_found:
                        ;
-               } while (!fn(cb_data, &oid));
+               } while ((oid = fn(cb_data)) != NULL);
                return 0;
        }
 
@@ -106,6 +107,7 @@ no_promisor_pack_found:
        if (opt->progress)
                strvec_pushf(&rev_list.args, "--progress=%s",
                             _("Checking connectivity"));
+       strvec_push(&rev_list.args, "--unsorted-input");
 
        rev_list.git_cmd = 1;
        rev_list.env = opt->env;
@@ -132,12 +134,12 @@ no_promisor_pack_found:
                 * are sure the ref is good and not sending it to
                 * rev-list for verification.
                 */
-               if (new_pack && find_pack_entry_one(oid.hash, new_pack))
+               if (new_pack && find_pack_entry_one(oid->hash, new_pack))
                        continue;
 
-               if (fprintf(rev_list_in, "%s\n", oid_to_hex(&oid)) < 0)
+               if (fprintf(rev_list_in, "%s\n", oid_to_hex(oid)) < 0)
                        break;
-       } while (!fn(cb_data, &oid));
+       } while ((oid = fn(cb_data)) != NULL);
 
        if (ferror(rev_list_in) || fflush(rev_list_in)) {
                if (errno != EPIPE && errno != EINVAL)
index 8d5a6b3ad6fe4bb0f9ca0930f8eea82543968306..6e59c92aa33c0c10067c0ed1c095c5ea9fa4f731 100644 (file)
@@ -9,7 +9,7 @@ struct transport;
  * When called after returning the name for the last object, return -1
  * to signal EOF, otherwise return 0.
  */
-typedef int (*oid_iterate_fn)(void *, struct object_id *oid);
+typedef const struct object_id *(*oid_iterate_fn)(void *);
 
 /*
  * Named-arguments struct for check_connected. All arguments are
index 171b4124afef5880376c10950c6b7fab8e41170d..fd1399c440f84ac63a8e7190fbe271c8c6f90fef 100644 (file)
@@ -624,6 +624,13 @@ if(NOT EXISTS ${CMAKE_BINARY_DIR}/config-list.h)
                        OUTPUT_FILE ${CMAKE_BINARY_DIR}/config-list.h)
 endif()
 
+if(NOT EXISTS ${CMAKE_BINARY_DIR}/hook-list.h)
+       message("Generating hook-list.h")
+       execute_process(COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/generate-hooklist.sh
+                       WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+                       OUTPUT_FILE ${CMAKE_BINARY_DIR}/hook-list.h)
+endif()
+
 include_directories(${CMAKE_BINARY_DIR})
 
 #build
index 814d7b8a1a1c75af49a1e648d96901587709329c..b71db670194c8025d5cb4c9c52a2c1faa90e342e 100644 (file)
@@ -2,15 +2,18 @@
 identifier fd;
 identifier die_fn =~ "^(die|die_errno)$";
 @@
-(
-  fd =
+  int fd =
 - open
 + xopen
   (...);
-|
-  int fd =
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
+
+@@
+expression fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+  fd =
 - open
 + xopen
   (...);
-)
 - if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
index 8108eda1e8614728063c67f5658bccdc0e1d8ec0..eb5fd4783d4b3a3efa2369ae21d270caea395358 100644 (file)
@@ -2503,7 +2503,14 @@ __git_config_vars=
 __git_compute_config_vars ()
 {
        test -n "$__git_config_vars" ||
-       __git_config_vars="$(git help --config-for-completion | sort -u)"
+       __git_config_vars="$(git help --config-for-completion)"
+}
+
+__git_config_sections=
+__git_compute_config_sections ()
+{
+       test -n "$__git_config_sections" ||
+       __git_config_sections="$(git help --config-sections-for-completion)"
 }
 
 # Completes possible values of various configuration variables.
@@ -2543,7 +2550,7 @@ __git_complete_config_variable_value ()
                return
                ;;
        branch.*.rebase)
-               __gitcomp "false true merges preserve interactive" "" "$cur_"
+               __gitcomp "false true merges interactive" "" "$cur_"
                return
                ;;
        remote.pushdefault)
@@ -2717,16 +2724,8 @@ __git_complete_config_variable_name ()
                __gitcomp "$__git_config_vars" "" "$cur_" "$sfx"
                ;;
        *)
-               __git_compute_config_vars
-               __gitcomp "$(echo "$__git_config_vars" |
-                               awk -F . '{
-                                       sections[$1] = 1
-                               }
-                               END {
-                                       for (s in sections)
-                                               print s "."
-                               }
-                               ')" "" "$cur_"
+               __git_compute_config_sections
+               __gitcomp "$__git_config_sections" "" "$cur_" "."
                ;;
        esac
 }
index d389bfadceeb26c6d82c05a9c0eac27b67ad8549..5927e27ae6e4af4b42c2a8ec4f6f5044026008ec 100644 (file)
@@ -138,7 +138,7 @@ struct credential {
        char *password;
 };
 
-#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+#define CREDENTIAL_INIT { 0 }
 
 typedef int (*credential_op_cb)(struct credential *);
 
index e6598b63833963115d43c62d9dd794bc03e120f1..2c5d76d789f0fabbdcd30d2684a4021b647731db 100644 (file)
@@ -41,7 +41,7 @@ struct credential {
        char *password;
 };
 
-#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+#define CREDENTIAL_INIT { 0 }
 
 typedef int (*credential_op_cb)(struct credential *);
 
index eeee45dd341b25d51b7497c4def555c4133c4267..75125d6ae003fcd213a32a0f893ae0ae48b48a1a 100755 (executable)
@@ -91,7 +91,7 @@ do
                git checkout -q $commit -- .
                git rerere
        fi
-       git reset -q --hard
+       git reset -q --hard  # Might nuke untracked files...
 done
 
 if test -z "$branch"
index 000ac7a8d430e4d14472c068b9817e1fc8ee69bd..e7240f3f636f9d687836b7fe6df65b548ce285fd 100644 (file)
@@ -105,7 +105,7 @@ static int match_partial_url(const char *url, void *cb)
 static void credential_apply_config(struct credential *c)
 {
        char *normalized_url;
-       struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+       struct urlmatch_config config = URLMATCH_CONFIG_INIT;
        struct strbuf url = STRBUF_INIT;
 
        if (!c->host)
index 5c4cbad62d0caa723a874392097512a0df5933cd..d80d009d1a1b63815858d8fb9a78c1a058f4610b 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -63,6 +63,12 @@ struct hostinfo {
        unsigned int hostname_lookup_done:1;
        unsigned int saw_extended_args:1;
 };
+#define HOSTINFO_INIT { \
+       .hostname = STRBUF_INIT, \
+       .canon_hostname = STRBUF_INIT, \
+       .ip_address = STRBUF_INIT, \
+       .tcp_port = STRBUF_INIT, \
+}
 
 static void lookup_hostname(struct hostinfo *hi);
 
@@ -727,15 +733,6 @@ static void lookup_hostname(struct hostinfo *hi)
        }
 }
 
-static void hostinfo_init(struct hostinfo *hi)
-{
-       memset(hi, 0, sizeof(*hi));
-       strbuf_init(&hi->hostname, 0);
-       strbuf_init(&hi->canon_hostname, 0);
-       strbuf_init(&hi->ip_address, 0);
-       strbuf_init(&hi->tcp_port, 0);
-}
-
 static void hostinfo_clear(struct hostinfo *hi)
 {
        strbuf_release(&hi->hostname);
@@ -760,11 +757,9 @@ static int execute(void)
        char *line = packet_buffer;
        int pktlen, len, i;
        char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
-       struct hostinfo hi;
+       struct hostinfo hi = HOSTINFO_INIT;
        struct strvec env = STRVEC_INIT;
 
-       hostinfo_init(&hi);
-
        if (addr)
                loginfo("Connection from %s:%s", addr, port);
 
diff --git a/diff.c b/diff.c
index a8113f170700282e4245702145b4537da5c5c227..861282db1c3283ad5cd6234237408bb5cf223d2b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -26,6 +26,7 @@
 #include "parse-options.h"
 #include "help.h"
 #include "promisor-remote.h"
+#include "dir.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -774,13 +775,13 @@ struct emitted_diff_symbol {
        int indent_width; /* The visual width of the indentation */
        enum diff_symbol s;
 };
-#define EMITTED_DIFF_SYMBOL_INIT {NULL}
+#define EMITTED_DIFF_SYMBOL_INIT { 0 }
 
 struct emitted_diff_symbols {
        struct emitted_diff_symbol *buf;
        int nr, alloc;
 };
-#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}
+#define EMITTED_DIFF_SYMBOLS_INIT { 0 }
 
 static void append_emitted_diff_symbol(struct diff_options *o,
                                       struct emitted_diff_symbol *e)
@@ -3907,6 +3908,13 @@ static int reuse_worktree_file(struct index_state *istate,
        if (!want_file && would_convert_to_git(istate, name))
                return 0;
 
+       /*
+        * If this path does not match our sparse-checkout definition,
+        * then the file will not be in the working directory.
+        */
+       if (!path_in_sparse_checkout(name, istate))
+               return 0;
+
        len = strlen(name);
        pos = index_name_pos(istate, name, len);
        if (pos < 0)
index c95857b51ff0f19643a698d4226e03763e973d79..bebd4ed6a42a1612f666f9221010479bc1263ccd 100644 (file)
@@ -317,10 +317,11 @@ static int find_identical_files(struct hashmap *srcs,
 }
 
 static void insert_file_table(struct repository *r,
+                             struct mem_pool *pool,
                              struct hashmap *table, int index,
                              struct diff_filespec *filespec)
 {
-       struct file_similarity *entry = xmalloc(sizeof(*entry));
+       struct file_similarity *entry = mem_pool_alloc(pool, sizeof(*entry));
 
        entry->index = index;
        entry->filespec = filespec;
@@ -336,7 +337,8 @@ static void insert_file_table(struct repository *r,
  * and then during the second round we try to match
  * cache-dirty entries as well.
  */
-static int find_exact_renames(struct diff_options *options)
+static int find_exact_renames(struct diff_options *options,
+                             struct mem_pool *pool)
 {
        int i, renames = 0;
        struct hashmap file_table;
@@ -346,7 +348,7 @@ static int find_exact_renames(struct diff_options *options)
         */
        hashmap_init(&file_table, NULL, NULL, rename_src_nr);
        for (i = rename_src_nr-1; i >= 0; i--)
-               insert_file_table(options->repo,
+               insert_file_table(options->repo, pool,
                                  &file_table, i,
                                  rename_src[i].p->one);
 
@@ -354,8 +356,8 @@ static int find_exact_renames(struct diff_options *options)
        for (i = 0; i < rename_dst_nr; i++)
                renames += find_identical_files(&file_table, i, options);
 
-       /* Free the hash data structure and entries */
-       hashmap_clear_and_free(&file_table, struct file_similarity, entry);
+       /* Free the hash data structure (entries will be freed with the pool) */
+       hashmap_clear(&file_table);
 
        return renames;
 }
@@ -1330,7 +1332,47 @@ static void handle_early_known_dir_renames(struct dir_rename_info *info,
        rename_src_nr = new_num_src;
 }
 
+static void free_filespec_data(struct diff_filespec *spec)
+{
+       if (!--spec->count)
+               diff_free_filespec_data(spec);
+}
+
+static void pool_free_filespec(struct mem_pool *pool,
+                              struct diff_filespec *spec)
+{
+       if (!pool) {
+               free_filespec(spec);
+               return;
+       }
+
+       /*
+        * Similar to free_filespec(), but only frees the data.  The spec
+        * itself was allocated in the pool and should not be individually
+        * freed.
+        */
+       free_filespec_data(spec);
+}
+
+void pool_diff_free_filepair(struct mem_pool *pool,
+                            struct diff_filepair *p)
+{
+       if (!pool) {
+               diff_free_filepair(p);
+               return;
+       }
+
+       /*
+        * Similar to diff_free_filepair() but only frees the data from the
+        * filespecs; not the filespecs or the filepair which were
+        * allocated from the pool.
+        */
+       free_filespec_data(p->one);
+       free_filespec_data(p->two);
+}
+
 void diffcore_rename_extended(struct diff_options *options,
+                             struct mem_pool *pool,
                              struct strintmap *relevant_sources,
                              struct strintmap *dirs_removed,
                              struct strmap *dir_rename_count,
@@ -1345,6 +1387,7 @@ void diffcore_rename_extended(struct diff_options *options,
        int num_destinations, dst_cnt;
        int num_sources, want_copies;
        struct progress *progress = NULL;
+       struct mem_pool local_pool;
        struct dir_rename_info info;
        struct diff_populate_filespec_options dpf_options = {
                .check_binary = 0,
@@ -1413,11 +1456,18 @@ void diffcore_rename_extended(struct diff_options *options,
                goto cleanup; /* nothing to do */
 
        trace2_region_enter("diff", "exact renames", options->repo);
+       mem_pool_init(&local_pool, 32*1024);
        /*
         * We really want to cull the candidates list early
         * with cheap tests in order to avoid doing deltas.
         */
-       rename_count = find_exact_renames(options);
+       rename_count = find_exact_renames(options, &local_pool);
+       /*
+        * Discard local_pool immediately instead of at "cleanup:" in order
+        * to reduce maximum memory usage; inexact rename detection uses up
+        * a fair amount of memory, and mem_pools can too.
+        */
+       mem_pool_discard(&local_pool, 0);
        trace2_region_leave("diff", "exact renames", options->repo);
 
        /* Did we only want exact renames? */
@@ -1636,7 +1686,7 @@ void diffcore_rename_extended(struct diff_options *options,
                        pair_to_free = p;
 
                if (pair_to_free)
-                       diff_free_filepair(pair_to_free);
+                       pool_diff_free_filepair(pool, pair_to_free);
        }
        diff_debug_queue("done copying original", &outq);
 
@@ -1646,7 +1696,7 @@ void diffcore_rename_extended(struct diff_options *options,
 
        for (i = 0; i < rename_dst_nr; i++)
                if (rename_dst[i].filespec_to_free)
-                       free_filespec(rename_dst[i].filespec_to_free);
+                       pool_free_filespec(pool, rename_dst[i].filespec_to_free);
 
        cleanup_dir_rename_info(&info, dirs_removed, dir_rename_count != NULL);
        FREE_AND_NULL(rename_dst);
@@ -1663,5 +1713,5 @@ void diffcore_rename_extended(struct diff_options *options,
 
 void diffcore_rename(struct diff_options *options)
 {
-       diffcore_rename_extended(options, NULL, NULL, NULL, NULL);
+       diffcore_rename_extended(options, NULL, NULL, NULL, NULL, NULL);
 }
index 533b30e21e7fe283350f8c6c408d002da1aad08d..badc2261c201831a620fcc7c29edcc1fd3bdbf1e 100644 (file)
@@ -127,6 +127,8 @@ struct diff_filepair {
 #define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode)
 
 void diff_free_filepair(struct diff_filepair *);
+void pool_diff_free_filepair(struct mem_pool *pool,
+                            struct diff_filepair *p);
 
 int diff_unmodified_pair(struct diff_filepair *);
 
@@ -179,6 +181,7 @@ void partial_clear_dir_rename_count(struct strmap *dir_rename_count);
 void diffcore_break(struct repository *, int);
 void diffcore_rename(struct diff_options *);
 void diffcore_rename_extended(struct diff_options *options,
+                             struct mem_pool *pool,
                              struct strintmap *relevant_sources,
                              struct strintmap *dirs_removed,
                              struct strmap *dir_rename_count,
diff --git a/dir.c b/dir.c
index 03c4d212672bc4b68bc45a514fe16180f57ee1f4..a4306ab8747e4faecb2219d68930f4be0714521b 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -1294,7 +1294,7 @@ int match_pathname(const char *pathname, int pathlen,
                 * then our prefix match is all we need; we
                 * do not need to call fnmatch at all.
                 */
-               if (!patternlen && !namelen)
+               if (!patternlen && (!namelen || (flags & PATTERN_FLAG_MUSTBEDIR)))
                        return 1;
        }
 
@@ -1303,6 +1303,44 @@ int match_pathname(const char *pathname, int pathlen,
                                 WM_PATHNAME) == 0;
 }
 
+static int path_matches_dir_pattern(const char *pathname,
+                                   int pathlen,
+                                   struct strbuf **path_parent,
+                                   int *dtype,
+                                   struct path_pattern *pattern,
+                                   struct index_state *istate)
+{
+       if (!*path_parent) {
+               char *slash;
+               CALLOC_ARRAY(*path_parent, 1);
+               strbuf_add(*path_parent, pathname, pathlen);
+               slash = find_last_dir_sep((*path_parent)->buf);
+
+               if (slash)
+                       strbuf_setlen(*path_parent, slash - (*path_parent)->buf);
+               else
+                       strbuf_setlen(*path_parent, 0);
+       }
+
+       /*
+        * If the parent directory matches the pattern, then we do not
+        * need to check for dtype.
+        */
+       if ((*path_parent)->len &&
+           match_pathname((*path_parent)->buf, (*path_parent)->len,
+                          pattern->base,
+                          pattern->baselen ? pattern->baselen - 1 : 0,
+                          pattern->pattern, pattern->nowildcardlen,
+                          pattern->patternlen, pattern->flags))
+               return 1;
+
+       *dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
+       if (*dtype != DT_DIR)
+               return 0;
+
+       return 1;
+}
+
 /*
  * Scan the given exclude list in reverse to see whether pathname
  * should be ignored.  The first match (i.e. the last on the list), if
@@ -1318,6 +1356,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
 {
        struct path_pattern *res = NULL; /* undecided */
        int i;
+       struct strbuf *path_parent = NULL;
 
        if (!pl->nr)
                return NULL;    /* undefined */
@@ -1327,11 +1366,10 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
                const char *exclude = pattern->pattern;
                int prefix = pattern->nowildcardlen;
 
-               if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
-                       *dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
-                       if (*dtype != DT_DIR)
-                               continue;
-               }
+               if (pattern->flags & PATTERN_FLAG_MUSTBEDIR &&
+                   !path_matches_dir_pattern(pathname, pathlen, &path_parent,
+                                             dtype, pattern, istate))
+                       continue;
 
                if (pattern->flags & PATTERN_FLAG_NODIR) {
                        if (match_basename(basename,
@@ -1355,6 +1393,12 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
                        break;
                }
        }
+
+       if (path_parent) {
+               strbuf_release(path_parent);
+               free(path_parent);
+       }
+
        return res;
 }
 
@@ -1439,6 +1483,58 @@ done:
        return result;
 }
 
+int init_sparse_checkout_patterns(struct index_state *istate)
+{
+       if (!core_apply_sparse_checkout)
+               return 1;
+       if (istate->sparse_checkout_patterns)
+               return 0;
+
+       CALLOC_ARRAY(istate->sparse_checkout_patterns, 1);
+
+       if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0) {
+               FREE_AND_NULL(istate->sparse_checkout_patterns);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int path_in_sparse_checkout_1(const char *path,
+                                    struct index_state *istate,
+                                    int require_cone_mode)
+{
+       const char *base;
+       int dtype = DT_REG;
+
+       /*
+        * We default to accepting a path if there are no patterns or
+        * they are of the wrong type.
+        */
+       if (init_sparse_checkout_patterns(istate) ||
+           (require_cone_mode &&
+            !istate->sparse_checkout_patterns->use_cone_patterns))
+               return 1;
+
+       base = strrchr(path, '/');
+       return path_matches_pattern_list(path, strlen(path), base ? base + 1 : path,
+                                        &dtype,
+                                        istate->sparse_checkout_patterns,
+                                        istate) > 0;
+}
+
+int path_in_sparse_checkout(const char *path,
+                           struct index_state *istate)
+{
+       return path_in_sparse_checkout_1(path, istate, 0);
+}
+
+int path_in_cone_mode_sparse_checkout(const char *path,
+                                    struct index_state *istate)
+{
+       return path_in_sparse_checkout_1(path, istate, 1);
+}
+
 static struct path_pattern *last_matching_pattern_from_lists(
                struct dir_struct *dir, struct index_state *istate,
                const char *pathname, int pathlen,
@@ -2970,6 +3066,120 @@ int is_empty_dir(const char *path)
        return ret;
 }
 
+char *git_url_basename(const char *repo, int is_bundle, int is_bare)
+{
+       const char *end = repo + strlen(repo), *start, *ptr;
+       size_t len;
+       char *dir;
+
+       /*
+        * Skip scheme.
+        */
+       start = strstr(repo, "://");
+       if (start == NULL)
+               start = repo;
+       else
+               start += 3;
+
+       /*
+        * Skip authentication data. The stripping does happen
+        * greedily, such that we strip up to the last '@' inside
+        * the host part.
+        */
+       for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
+               if (*ptr == '@')
+                       start = ptr + 1;
+       }
+
+       /*
+        * Strip trailing spaces, slashes and /.git
+        */
+       while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+               end--;
+       if (end - start > 5 && is_dir_sep(end[-5]) &&
+           !strncmp(end - 4, ".git", 4)) {
+               end -= 5;
+               while (start < end && is_dir_sep(end[-1]))
+                       end--;
+       }
+
+       /*
+        * Strip trailing port number if we've got only a
+        * hostname (that is, there is no dir separator but a
+        * colon). This check is required such that we do not
+        * strip URI's like '/foo/bar:2222.git', which should
+        * result in a dir '2222' being guessed due to backwards
+        * compatibility.
+        */
+       if (memchr(start, '/', end - start) == NULL
+           && memchr(start, ':', end - start) != NULL) {
+               ptr = end;
+               while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
+                       ptr--;
+               if (start < ptr && ptr[-1] == ':')
+                       end = ptr - 1;
+       }
+
+       /*
+        * Find last component. To remain backwards compatible we
+        * also regard colons as path separators, such that
+        * cloning a repository 'foo:bar.git' would result in a
+        * directory 'bar' being guessed.
+        */
+       ptr = end;
+       while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
+               ptr--;
+       start = ptr;
+
+       /*
+        * Strip .{bundle,git}.
+        */
+       len = end - start;
+       strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
+
+       if (!len || (len == 1 && *start == '/'))
+               die(_("No directory name could be guessed.\n"
+                     "Please specify a directory on the command line"));
+
+       if (is_bare)
+               dir = xstrfmt("%.*s.git", (int)len, start);
+       else
+               dir = xstrndup(start, len);
+       /*
+        * Replace sequences of 'control' characters and whitespace
+        * with one ascii space, remove leading and trailing spaces.
+        */
+       if (*dir) {
+               char *out = dir;
+               int prev_space = 1 /* strip leading whitespace */;
+               for (end = dir; *end; ++end) {
+                       char ch = *end;
+                       if ((unsigned char)ch < '\x20')
+                               ch = '\x20';
+                       if (isspace(ch)) {
+                               if (prev_space)
+                                       continue;
+                               prev_space = 1;
+                       } else
+                               prev_space = 0;
+                       *out++ = ch;
+               }
+               *out = '\0';
+               if (out > dir && prev_space)
+                       out[-1] = '\0';
+       }
+       return dir;
+}
+
+void strip_dir_trailing_slashes(char *dir)
+{
+       char *end = dir + strlen(dir);
+
+       while (dir < end - 1 && is_dir_sep(end[-1]))
+               end--;
+       *end = '\0';
+}
+
 static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
 {
        DIR *dir;
@@ -3633,7 +3843,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
                strbuf_reset(&sub_wt);
                strbuf_reset(&sub_gd);
                strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path);
-               strbuf_addf(&sub_gd, "%s/modules/%s", sub_gitdir, sub->name);
+               submodule_name_to_gitdir(&sub_gd, &subrepo, sub->name);
 
                connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);
        }
diff --git a/dir.h b/dir.h
index b3e1a54a97145d6be7385d044968a080e2f17ae8..83f46c0fb4c4415c79d3a9fcdddbcbf372b35415 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -394,6 +394,14 @@ enum pattern_match_result path_matches_pattern_list(const char *pathname,
                                const char *basename, int *dtype,
                                struct pattern_list *pl,
                                struct index_state *istate);
+
+int init_sparse_checkout_patterns(struct index_state *state);
+
+int path_in_sparse_checkout(const char *path,
+                           struct index_state *istate);
+int path_in_cone_mode_sparse_checkout(const char *path,
+                                     struct index_state *istate);
+
 struct dir_entry *dir_add_ignored(struct dir_struct *dir,
                                  struct index_state *istate,
                                  const char *pathname, int len);
@@ -453,6 +461,17 @@ static inline int is_dot_or_dotdot(const char *name)
 
 int is_empty_dir(const char *dir);
 
+/*
+ * Retrieve the "humanish" basename of the given Git URL.
+ *
+ * For example:
+ *     /path/to/repo.git => "repo"
+ *     host.xz:foo/.git => "foo"
+ *     http://example.com/user/bar.baz => "bar.baz"
+ */
+char *git_url_basename(const char *repo, int is_bundle, int is_bare);
+void strip_dir_trailing_slashes(char *dir);
+
 void setup_standard_excludes(struct dir_struct *dir);
 
 char *get_sparse_checkout_filename(void);
index 6303ae0ab0d52b7b54a00bd9bf687a91aac998fd..674309eed8bd358e4ccc5020f45457215bef290a 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -3,6 +3,7 @@
 #include "strbuf.h"
 #include "run-command.h"
 #include "sigchain.h"
+#include "compat/terminal.h"
 
 #ifndef DEFAULT_EDITOR
 #define DEFAULT_EDITOR "vi"
@@ -50,6 +51,8 @@ const char *git_sequence_editor(void)
 static int launch_specified_editor(const char *editor, const char *path,
                                   struct strbuf *buffer, const char *const *env)
 {
+       int term_fail;
+
        if (!editor)
                return error("Terminal is dumb, but EDITOR unset");
 
@@ -58,7 +61,7 @@ static int launch_specified_editor(const char *editor, const char *path,
                const char *args[] = { editor, NULL, NULL };
                struct child_process p = CHILD_PROCESS_INIT;
                int ret, sig;
-               int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
+               int print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);
 
                if (print_waiting_for_editor) {
                        /*
@@ -83,7 +86,10 @@ static int launch_specified_editor(const char *editor, const char *path,
                p.env = env;
                p.use_shell = 1;
                p.trace2_child_class = "editor";
+               term_fail = save_term(1);
                if (start_command(&p) < 0) {
+                       if (!term_fail)
+                               restore_term();
                        strbuf_release(&realpath);
                        return error("unable to start editor '%s'", editor);
                }
@@ -91,6 +97,8 @@ static int launch_specified_editor(const char *editor, const char *path,
                sigchain_push(SIGINT, SIG_IGN);
                sigchain_push(SIGQUIT, SIG_IGN);
                ret = finish_command(&p);
+               if (!term_fail)
+                       restore_term();
                strbuf_release(&realpath);
                sig = ret - 128;
                sigchain_pop(SIGINT);
diff --git a/entry.c b/entry.c
index 044e8ec92c6c3188028a31d4f5e497ab46178272..9b0f968a70c9cbeba7c00ec09d0e5dbf42110533 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -163,24 +163,21 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
                            int show_progress)
 {
        int errs = 0;
-       unsigned delayed_object_count;
+       unsigned processed_paths = 0;
        off_t filtered_bytes = 0;
        struct string_list_item *filter, *path;
-       struct progress *progress;
+       struct progress *progress = NULL;
        struct delayed_checkout *dco = state->delayed_checkout;
 
        if (!state->delayed_checkout)
                return errs;
 
        dco->state = CE_RETRY;
-       delayed_object_count = dco->paths.nr;
-       progress = show_progress
-               ? start_delayed_progress(_("Filtering content"), delayed_object_count)
-               : NULL;
+       if (show_progress)
+               progress = start_delayed_progress(_("Filtering content"), dco->paths.nr);
        while (dco->filters.nr > 0) {
                for_each_string_list_item(filter, &dco->filters) {
                        struct string_list available_paths = STRING_LIST_INIT_NODUP;
-                       display_progress(progress, delayed_object_count - dco->paths.nr);
 
                        if (!async_query_available_blobs(filter->string, &available_paths)) {
                                /* Filter reported an error */
@@ -227,6 +224,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
                                ce = index_file_exists(state->istate, path->string,
                                                       strlen(path->string), 0);
                                if (ce) {
+                                       display_progress(progress, ++processed_paths);
                                        errs |= checkout_entry(ce, state, NULL, nr_checkouts);
                                        filtered_bytes += ce->ce_stat_data.sd_size;
                                        display_throughput(progress, filtered_bytes);
diff --git a/entry.h b/entry.h
index 7c889e58fd7831086afe9b9267f537bcd9a80dca..2254c62727fdcf1d530d66bc9e70597a8aece1ad 100644 (file)
--- a/entry.h
+++ b/entry.h
@@ -16,7 +16,7 @@ struct checkout {
                 clone:1,
                 refresh_cache:1;
 };
-#define CHECKOUT_INIT { NULL, "" }
+#define CHECKOUT_INIT { .base_dir = "" }
 
 #define TEMPORARY_FILENAME_LENGTH 25
 /*
index b4ba4fa22dbe41f3651fe8c39c3d5b108775e470..9da7f3c1a19ee5d3b6c727e4f9c6ec8c599fd7e8 100644 (file)
@@ -31,7 +31,6 @@ int prefer_symlink_refs;
 int is_bare_repository_cfg = -1; /* unspecified */
 int warn_ambiguous_refs = 1;
 int warn_on_object_refname_ambiguity = 1;
-int ref_paranoia = -1;
 int repository_format_precious_objects;
 int repository_format_worktree_config;
 const char *git_commit_encoding;
@@ -95,13 +94,6 @@ int auto_comment_line_char;
 /* Parallel index stat data preload? */
 int core_preload_index = 1;
 
-/*
- * This is a hack for test programs like test-dump-untracked-cache to
- * ensure that they do not modify the untracked cache when reading it.
- * Do not use it otherwise!
- */
-int ignore_untracked_cache_config;
-
 /* This is set by setup_git_dir_gently() and/or git_default_config() */
 char *git_work_tree_cfg;
 
@@ -329,8 +321,7 @@ char *get_graft_file(struct repository *r)
 
 static void set_git_dir_1(const char *path)
 {
-       if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
-               die(_("could not set GIT_DIR to '%s'"), path);
+       xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
        setup_git_env(path);
 }
 
index 57ed5784e1468aa661d59446273ba9cbbad760ca..273390229fe4c442915e911b421339e2391fc37c 100644 (file)
@@ -19,7 +19,6 @@ void fetch_negotiator_init(struct repository *r,
                return;
 
        case FETCH_NEGOTIATION_DEFAULT:
-       default:
                default_negotiator_init(negotiator);
                return;
        }
index b0c7be717c7fdde0d45d58c113172c0f07eac78b..a9604f35a3ea9055732d48e39b63a39f041f18f3 100644 (file)
@@ -119,6 +119,11 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
 {
        enum object_type type;
        struct object_info info = { .typep = &type };
+       struct commit *commit;
+
+       commit = lookup_commit_in_graph(the_repository, oid);
+       if (commit)
+               return commit;
 
        while (1) {
                if (oid_object_info_extended(the_repository, oid, &info,
@@ -137,8 +142,14 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
                        break;
                }
        }
-       if (type == OBJ_COMMIT)
-               return (struct commit *) parse_object(the_repository, oid);
+
+       if (type == OBJ_COMMIT) {
+               struct commit *commit = lookup_commit(the_repository, oid);
+               if (!commit || repo_parse_commit(the_repository, commit))
+                       return NULL;
+               return commit;
+       }
+
        return NULL;
 }
 
@@ -1906,16 +1917,15 @@ static void update_shallow(struct fetch_pack_args *args,
        oid_array_clear(&ref);
 }
 
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
 {
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
 
        if (!ref)
-               return -1; /* end of the list */
+               return NULL;
        *rm = ref->next;
-       oidcpy(oid, &ref->old_oid);
-       return 0;
+       return &ref->old_oid;
 }
 
 struct ref *fetch_pack(struct fetch_pack_args *args,
index b969dc6ebb6800addbafe72adcfbabdadb6af121..5216191488e20115949418aab527988ba7b81e7c 100644 (file)
@@ -9,6 +9,7 @@
 #include "branch.h"
 #include "fmt-merge-msg.h"
 #include "commit-reach.h"
+#include "gpg-interface.h"
 
 static int use_branch_desc;
 static int suppress_dest_pattern_seen;
@@ -16,6 +17,8 @@ static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;
 
 int fmt_merge_msg_config(const char *key, const char *value, void *cb)
 {
+       int status = 0;
+
        if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
                int is_bool;
                merge_log_config = git_config_bool_or_int(key, value, &is_bool);
@@ -34,6 +37,9 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
                        string_list_append(&suppress_dest_patterns, value);
                suppress_dest_pattern_seen = 1;
        } else {
+               status = git_gpg_config(key, value, NULL);
+               if (status)
+                       return status;
                return git_default_config(key, value, cb);
        }
        return 0;
@@ -528,11 +534,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
                        buf = payload.buf;
                        len = payload.len;
                        if (check_signature(payload.buf, payload.len, sig.buf,
-                                        sig.len, &sigc) &&
-                               !sigc.gpg_output)
+                                           sig.len, &sigc) &&
+                           !sigc.output)
                                strbuf_addstr(&sig, "gpg verification failed.\n");
                        else
-                               strbuf_addstr(&sig, sigc.gpg_output);
+                               strbuf_addstr(&sig, sigc.output);
                }
                signature_check_clear(&sigc);
 
diff --git a/generate-hooklist.sh b/generate-hooklist.sh
new file mode 100755 (executable)
index 0000000..2f9f54e
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Usage: ./generate-hooklist.sh >hook-list.h
+
+cat <<EOF
+/* Automatically generated by generate-hooklist.sh */
+
+static const char *hook_name_list[] = {
+EOF
+
+sed -n \
+       -e '/^~~~~*$/ {x; s/^.*$/       "&",/; p;}' \
+       -e 'x' \
+       <Documentation/githooks.txt |
+       LC_ALL=C sort
+
+cat <<EOF
+       NULL,
+};
+EOF
index c8b34fd61229e39fc99fd29a2135de86c2b6dac3..d209911ebb80c164fb93073d054fd1ff7acb8da3 100644 (file)
--- a/gettext.h
+++ b/gettext.h
@@ -55,31 +55,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
 }
 
 /* Mark msgid for translation but do not translate it. */
-#if !USE_PARENS_AROUND_GETTEXT_N
 #define N_(msgid) msgid
-#else
-/*
- * Strictly speaking, this will lead to invalid C when
- * used this way:
- *     static const char s[] = N_("FOO");
- * which will expand to
- *     static const char s[] = ("FOO");
- * and in valid C, the initializer on the right hand side must
- * be without the parentheses.  But many compilers do accept it
- * as a language extension and it will allow us to catch mistakes
- * like:
- *     static const char *msgs[] = {
- *             N_("one")
- *             N_("two"),
- *             N_("three"),
- *             NULL
- *     };
- * (notice the missing comma on one of the lines) by forcing
- * a compilation error, because parenthesised ("one") ("two")
- * will not get silently turned into ("onetwo").
- */
-#define N_(msgid) (msgid)
-#endif
 
 const char *get_preferred_languages(void);
 int is_utf8_locale(void);
index 6a7afaea8da09fa3fb3670d1a688421fbf152747..405cf76f2a3d94d5cb03b50d59ff729b745842ad 100755 (executable)
@@ -34,94 +34,9 @@ Please use "git help bisect" to get the full man page.'
 OPTIONS_SPEC=
 . git-sh-setup
 
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 TERM_BAD=bad
 TERM_GOOD=good
 
-bisect_visualize() {
-       git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
-
-       if test $# = 0
-       then
-               if test -n "${DISPLAY+set}${SESSIONNAME+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" &&
-                       type gitk >/dev/null 2>&1
-               then
-                       set gitk
-               else
-                       set git log
-               fi
-       else
-               case "$1" in
-               git*|tig) ;;
-               -*)     set git log "$@" ;;
-               *)      set git "$@" ;;
-               esac
-       fi
-
-       eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES")
-}
-
-bisect_run () {
-       git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit
-
-       test -n "$*" || die "$(gettext "bisect run failed: no command provided.")"
-
-       while true
-       do
-               command="$@"
-               eval_gettextln "running \$command"
-               "$@"
-               res=$?
-
-               # Check for really bad run error.
-               if [ $res -lt 0 -o $res -ge 128 ]
-               then
-                       eval_gettextln "bisect run failed:
-exit code \$res from '\$command' is < 0 or >= 128" >&2
-                       exit $res
-               fi
-
-               # Find current state depending on run success or failure.
-               # A special exit code of 125 means cannot test.
-               if [ $res -eq 125 ]
-               then
-                       state='skip'
-               elif [ $res -gt 0 ]
-               then
-                       state="$TERM_BAD"
-               else
-                       state="$TERM_GOOD"
-               fi
-
-               git bisect--helper --bisect-state $state >"$GIT_DIR/BISECT_RUN"
-               res=$?
-
-               cat "$GIT_DIR/BISECT_RUN"
-
-               if sane_grep "first $TERM_BAD commit could be any of" "$GIT_DIR/BISECT_RUN" \
-                       >/dev/null
-               then
-                       gettextln "bisect run cannot continue any more" >&2
-                       exit $res
-               fi
-
-               if [ $res -ne 0 ]
-               then
-                       eval_gettextln "bisect run failed:
-'bisect-state \$state' exited with error code \$res" >&2
-                       exit $res
-               fi
-
-               if sane_grep "is the first $TERM_BAD commit" "$GIT_DIR/BISECT_RUN" >/dev/null
-               then
-                       gettextln "bisect run success"
-                       exit 0;
-               fi
-
-       done
-}
-
 get_terms () {
        if test -s "$GIT_DIR/BISECT_TERMS"
        then
@@ -152,7 +67,7 @@ case "$#" in
                # Not sure we want "next" at the UI level anymore.
                git bisect--helper --bisect-next "$@" || exit ;;
        visualize|view)
-               bisect_visualize "$@" ;;
+               git bisect--helper --bisect-visualize "$@" || exit;;
        reset)
                git bisect--helper --bisect-reset "$@" ;;
        replay)
@@ -160,7 +75,7 @@ case "$#" in
        log)
                git bisect--helper --bisect-log || exit ;;
        run)
-               bisect_run "$@" ;;
+               git bisect--helper --bisect-run "$@" || exit;;
        terms)
                git bisect--helper --bisect-terms "$@" || exit;;
        *)
index b46605300abf818d27bc04f8d0a727bc6a272330..141bb86351e63cba6f5c92e5badfba7c78a5eb9f 100644 (file)
 # endif
 #define WIN32_LEAN_AND_MEAN  /* stops windows.h including winsock.h */
 #include <winsock2.h>
+#ifndef NO_UNIX_SOCKETS
+#include <afunix.h>
+#endif
 #include <windows.h>
 #define GIT_WINDOWS_NATIVE
 #endif
@@ -875,6 +878,8 @@ void *xmemdupz(const void *data, size_t len);
 char *xstrndup(const char *str, size_t len);
 void *xrealloc(void *ptr, size_t size);
 void *xcalloc(size_t nmemb, size_t size);
+void xsetenv(const char *name, const char *value, int overwrite);
+void xunsetenv(const char *name);
 void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 const char *mmap_os_err(void);
 void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
@@ -1253,10 +1258,6 @@ int warn_on_fopen_errors(const char *path);
  */
 int open_nofollow(const char *path, int flags);
 
-#if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__)
-#define USE_PARENS_AROUND_GETTEXT_N 1
-#endif
-
 #ifndef SHELL_PATH
 # define SHELL_PATH "/bin/sh"
 #endif
diff --git a/git-curl-compat.h b/git-curl-compat.h
new file mode 100644 (file)
index 0000000..56a83b6
--- /dev/null
@@ -0,0 +1,129 @@
+#ifndef GIT_CURL_COMPAT_H
+#define GIT_CURL_COMPAT_H
+#include <curl/curl.h>
+
+/**
+ * This header centralizes the declaration of our libcurl dependencies
+ * to make it easy to discover the oldest versions we support, and to
+ * inform decisions about removing support for older libcurl in the
+ * future.
+ *
+ * The oldest supported version of curl is documented in the "INSTALL"
+ * document.
+ *
+ * The source of truth for what versions have which symbols is
+ * https://github.com/curl/curl/blob/master/docs/libcurl/symbols-in-versions;
+ * the release dates are taken from curl.git (at
+ * https://github.com/curl/curl/).
+ *
+ * For each X symbol we need from curl we define our own
+ * GIT_CURL_HAVE_X. If multiple similar symbols with the same prefix
+ * were defined in the same version we pick one and check for that name.
+ *
+ * We may also define a missing CURL_* symbol to its known value, if
+ * doing so is sufficient to add support for it to older versions that
+ * don't have it.
+ *
+ * Keep any symbols in date order of when their support was
+ * introduced, oldest first, in the official version of cURL library.
+ */
+
+/**
+ * CURL_SOCKOPT_OK was added in 7.21.5, released in April 2011.
+ */
+#if LIBCURL_VERSION_NUM < 0x071505
+#define CURL_SOCKOPT_OK 0
+#endif
+
+/**
+ * CURLOPT_TCP_KEEPALIVE was added in 7.25.0, released in March 2012.
+ */
+#if LIBCURL_VERSION_NUM >= 0x071900
+#define GITCURL_HAVE_CURLOPT_TCP_KEEPALIVE 1
+#endif
+
+
+/**
+ * CURLOPT_LOGIN_OPTIONS was added in 7.34.0, released in December
+ * 2013.
+ *
+ * If we start requiring 7.34.0 we might also be able to remove the
+ * code conditional on USE_CURL_FOR_IMAP_SEND in imap-send.c, see
+ * 1e16b255b95 (git-imap-send: use libcurl for implementation,
+ * 2014-11-09) and the check it added for "072200" in the Makefile.
+
+ */
+#if LIBCURL_VERSION_NUM >= 0x072200
+#define GIT_CURL_HAVE_CURLOPT_LOGIN_OPTIONS 1
+#endif
+
+/**
+ * CURL_SSLVERSION_TLSv1_[012] was added in 7.34.0, released in
+ * December 2013.
+ */
+#if LIBCURL_VERSION_NUM >= 0x072200
+#define GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_0
+#endif
+
+/**
+ * CURLOPT_PINNEDPUBLICKEY was added in 7.39.0, released in November
+ * 2014. CURLE_SSL_PINNEDPUBKEYNOTMATCH was added in that same version.
+ */
+#if LIBCURL_VERSION_NUM >= 0x072c00
+#define GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY 1
+#define GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH 1
+#endif
+
+/**
+ * CURL_HTTP_VERSION_2 was added in 7.43.0, released in June 2015.
+ *
+ * The CURL_HTTP_VERSION_2 alias (but not CURL_HTTP_VERSION_2_0) has
+ * always been a macro, not an enum field (checked on curl version
+ * 7.78.0)
+ */
+#if LIBCURL_VERSION_NUM >= 0x072b00
+#define GIT_CURL_HAVE_CURL_HTTP_VERSION_2 1
+#endif
+
+/**
+ * CURLSSLOPT_NO_REVOKE was added in 7.44.0, released in August 2015.
+ *
+ * The CURLSSLOPT_NO_REVOKE is, has always been a macro, not an enum
+ * field (checked on curl version 7.78.0)
+ */
+#if LIBCURL_VERSION_NUM >= 0x072c00
+#define GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE 1
+#endif
+
+/**
+ * CURLOPT_PROXY_CAINFO was added in 7.52.0, released in August 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO 1
+#endif
+
+/**
+ * CURLOPT_PROXY_{KEYPASSWD,SSLCERT,SSLKEY} was added in 7.52.0,
+ * released in August 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD 1
+#endif
+
+/**
+ * CURL_SSLVERSION_TLSv1_3 was added in 7.53.0, released in February
+ * 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073400
+#define GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_3 1
+#endif
+
+/**
+ * CURLSSLSET_{NO_BACKENDS,OK,TOO_LATE,UNKNOWN_BACKEND} were added in
+ * 7.56.0, released in September 2017.
+ */
+#if LIBCURL_VERSION_NUM >= 0x073800
+#define GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
+#endif
+
+#endif
diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh
deleted file mode 100644 (file)
index b9c71d2..0000000
+++ /dev/null
@@ -1,1057 +0,0 @@
-# This shell script fragment is sourced by git-rebase to implement its
-# preserve-merges mode.
-#
-# Copyright (c) 2006 Johannes E. Schindelin
-#
-# The file containing rebase commands, comments, and empty lines.
-# This file is created by "git rebase -i" then edited by the user.  As
-# the lines are processed, they are removed from the front of this
-# file and written to the tail of $done.
-todo="$state_dir"/git-rebase-todo
-
-# The rebase command lines that have already been processed.  A line
-# is moved here when it is first handled, before any associated user
-# actions.
-done="$state_dir"/done
-
-# The commit message that is planned to be used for any changes that
-# need to be committed following a user interaction.
-msg="$state_dir"/message
-
-# The file into which is accumulated the suggested commit message for
-# squash/fixup commands.  When the first of a series of squash/fixups
-# is seen, the file is created and the commit message from the
-# previous commit and from the first squash/fixup commit are written
-# to it.  The commit message for each subsequent squash/fixup commit
-# is appended to the file as it is processed.
-#
-# The first line of the file is of the form
-#     # This is a combination of $count commits.
-# where $count is the number of commits whose messages have been
-# written to the file so far (including the initial "pick" commit).
-# Each time that a commit message is processed, this line is read and
-# updated.  It is deleted just before the combined commit is made.
-squash_msg="$state_dir"/message-squash
-
-# If the current series of squash/fixups has not yet included a squash
-# command, then this file exists and holds the commit message of the
-# original "pick" commit.  (If the series ends without a "squash"
-# command, then this can be used as the commit message of the combined
-# commit without opening the editor.)
-fixup_msg="$state_dir"/message-fixup
-
-# $rewritten is the name of a directory containing files for each
-# commit that is reachable by at least one merge base of $head and
-# $upstream. They are not necessarily rewritten, but their children
-# might be.  This ensures that commits on merged, but otherwise
-# unrelated side branches are left alone. (Think "X" in the man page's
-# example.)
-rewritten="$state_dir"/rewritten
-
-dropped="$state_dir"/dropped
-
-end="$state_dir"/end
-msgnum="$state_dir"/msgnum
-
-# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE that will be used for the commit that is currently
-# being rebased.
-author_script="$state_dir"/author-script
-
-# When an "edit" rebase command is being processed, the SHA1 of the
-# commit to be edited is recorded in this file.  When "git rebase
-# --continue" is executed, if there are any staged changes then they
-# will be amended to the HEAD commit, but only provided the HEAD
-# commit is still the commit to be edited.  When any other rebase
-# command is processed, this file is deleted.
-amend="$state_dir"/amend
-
-# For the post-rewrite hook, we make a list of rewritten commits and
-# their new sha1s.  The rewritten-pending list keeps the sha1s of
-# commits that have been processed, but not committed yet,
-# e.g. because they are waiting for a 'squash' command.
-rewritten_list="$state_dir"/rewritten-list
-rewritten_pending="$state_dir"/rewritten-pending
-
-# Work around Git for Windows' Bash whose "read" does not strip CRLF
-# and leaves CR at the end instead.
-cr=$(printf "\015")
-
-resolvemsg="
-$(gettext 'Resolve all conflicts manually, mark them as resolved with
-"git add/rm <conflicted_files>", then run "git rebase --continue".
-You can instead skip this commit: run "git rebase --skip".
-To abort and get back to the state before "git rebase", run "git rebase --abort".')
-"
-
-write_basic_state () {
-       echo "$head_name" > "$state_dir"/head-name &&
-       echo "$onto" > "$state_dir"/onto &&
-       echo "$orig_head" > "$state_dir"/orig-head &&
-       test t = "$GIT_QUIET" && : > "$state_dir"/quiet
-       test t = "$verbose" && : > "$state_dir"/verbose
-       test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy
-       test -n "$strategy_opts" && echo "$strategy_opts" > \
-               "$state_dir"/strategy_opts
-       test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \
-               "$state_dir"/allow_rerere_autoupdate
-       test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
-       test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
-       test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec
-}
-
-apply_autostash () {
-       if test -f "$state_dir/autostash"
-       then
-               stash_sha1=$(cat "$state_dir/autostash")
-               if git stash apply $stash_sha1 >/dev/null 2>&1
-               then
-                       echo "$(gettext 'Applied autostash.')" >&2
-               else
-                       git stash store -m "autostash" -q $stash_sha1 ||
-                       die "$(eval_gettext "Cannot store \$stash_sha1")"
-                       gettext 'Applying autostash resulted in conflicts.
-Your changes are safe in the stash.
-You can run "git stash pop" or "git stash drop" at any time.
-' >&2
-               fi
-       fi
-}
-
-output () {
-       case "$verbose" in
-       '')
-               output=$("$@" 2>&1 )
-               status=$?
-               test $status != 0 && printf "%s\n" "$output"
-               return $status
-               ;;
-       *)
-               "$@"
-               ;;
-       esac
-}
-
-strategy_args=${strategy:+--strategy=$strategy}
-test -n "$strategy_opts" &&
-eval '
-       for strategy_opt in '"$strategy_opts"'
-       do
-               strategy_args="$strategy_args -X$(git rev-parse --sq-quote "${strategy_opt#--}")"
-       done
-'
-
-GIT_CHERRY_PICK_HELP="$resolvemsg"
-export GIT_CHERRY_PICK_HELP
-
-comment_char=$(git config --get core.commentchar 2>/dev/null)
-case "$comment_char" in
-'' | auto)
-       comment_char="#"
-       ;;
-?)
-       ;;
-*)
-       comment_char=$(echo "$comment_char" | cut -c1)
-       ;;
-esac
-
-warn () {
-       printf '%s\n' "$*" >&2
-}
-
-# Output the commit message for the specified commit.
-commit_message () {
-       git cat-file commit "$1" | sed "1,/^$/d"
-}
-
-orig_reflog_action="$GIT_REFLOG_ACTION"
-
-comment_for_reflog () {
-       case "$orig_reflog_action" in
-       ''|rebase*)
-               GIT_REFLOG_ACTION="rebase -i ($1)"
-               export GIT_REFLOG_ACTION
-               ;;
-       esac
-}
-
-last_count=
-mark_action_done () {
-       sed -e 1q < "$todo" >> "$done"
-       sed -e 1d < "$todo" >> "$todo".new
-       mv -f "$todo".new "$todo"
-       new_count=$(( $(git stripspace --strip-comments <"$done" | wc -l) ))
-       echo $new_count >"$msgnum"
-       total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
-       echo $total >"$end"
-       if test "$last_count" != "$new_count"
-       then
-               last_count=$new_count
-               eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r"
-               test -z "$verbose" || echo
-       fi
-}
-
-append_todo_help () {
-       gettext "
-Commands:
-p, pick <commit> = use commit
-r, reword <commit> = use commit, but edit the commit message
-e, edit <commit> = use commit, but stop for amending
-s, squash <commit> = use commit, but meld into previous commit
-f, fixup <commit> = like \"squash\", but discard this commit's log message
-x, exec <commit> = run command (the rest of the line) using shell
-d, drop <commit> = remove commit
-l, label <label> = label current HEAD with a name
-t, reset <label> = reset HEAD to a label
-m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
-.       create a merge commit using the original merge commit's
-.       message (or the oneline, if no original merge commit was
-.       specified). Use -c <commit> to reword the commit message.
-
-These lines can be re-ordered; they are executed from top to bottom.
-" | git stripspace --comment-lines >>"$todo"
-
-       if test $(get_missing_commit_check_level) = error
-       then
-               gettext "
-Do not remove any line. Use 'drop' explicitly to remove a commit.
-" | git stripspace --comment-lines >>"$todo"
-       else
-               gettext "
-If you remove a line here THAT COMMIT WILL BE LOST.
-" | git stripspace --comment-lines >>"$todo"
-       fi
-}
-
-make_patch () {
-       sha1_and_parents="$(git rev-list --parents -1 "$1")"
-       case "$sha1_and_parents" in
-       ?*' '?*' '?*)
-               git diff --cc $sha1_and_parents
-               ;;
-       ?*' '?*)
-               git diff-tree -p "$1^!"
-               ;;
-       *)
-               echo "Root commit"
-               ;;
-       esac > "$state_dir"/patch
-       test -f "$msg" ||
-               commit_message "$1" > "$msg"
-       test -f "$author_script" ||
-               get_author_ident_from_commit "$1" > "$author_script"
-}
-
-die_with_patch () {
-       echo "$1" > "$state_dir"/stopped-sha
-       git update-ref REBASE_HEAD "$1"
-       make_patch "$1"
-       die "$2"
-}
-
-exit_with_patch () {
-       echo "$1" > "$state_dir"/stopped-sha
-       git update-ref REBASE_HEAD "$1"
-       make_patch $1
-       git rev-parse --verify HEAD > "$amend"
-       gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-       warn "$(eval_gettext "\
-You can amend the commit now, with
-
-       git commit --amend \$gpg_sign_opt_quoted
-
-Once you are satisfied with your changes, run
-
-       git rebase --continue")"
-       warn
-       exit $2
-}
-
-die_abort () {
-       apply_autostash
-       rm -rf "$state_dir"
-       die "$1"
-}
-
-has_action () {
-       test -n "$(git stripspace --strip-comments <"$1")"
-}
-
-is_empty_commit() {
-       tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || {
-               sha1=$1
-               die "$(eval_gettext "\$sha1: not a commit that can be picked")"
-       }
-       ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
-               ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
-       test "$tree" = "$ptree"
-}
-
-is_merge_commit()
-{
-       git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
-}
-
-# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
-# GIT_AUTHOR_DATE exported from the current environment.
-do_with_author () {
-       (
-               export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-               "$@"
-       )
-}
-
-git_sequence_editor () {
-       if test -z "$GIT_SEQUENCE_EDITOR"
-       then
-               GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
-               if [ -z "$GIT_SEQUENCE_EDITOR" ]
-               then
-                       GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
-               fi
-       fi
-
-       eval "$GIT_SEQUENCE_EDITOR" '"$@"'
-}
-
-pick_one () {
-       ff=--ff
-
-       case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
-       case "$force_rebase" in '') ;; ?*) ff= ;; esac
-       output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")"
-
-       if is_empty_commit "$sha1"
-       then
-               empty_args="--allow-empty"
-       fi
-
-       pick_one_preserving_merges "$@"
-}
-
-pick_one_preserving_merges () {
-       fast_forward=t
-       case "$1" in
-       -n)
-               fast_forward=f
-               sha1=$2
-               ;;
-       *)
-               sha1=$1
-               ;;
-       esac
-       sha1=$(git rev-parse $sha1)
-
-       if test -f "$state_dir"/current-commit && test "$fast_forward" = t
-       then
-               while read current_commit
-               do
-                       git rev-parse HEAD > "$rewritten"/$current_commit
-               done <"$state_dir"/current-commit
-               rm "$state_dir"/current-commit ||
-                       die "$(gettext "Cannot write current commit's replacement sha1")"
-       fi
-
-       echo $sha1 >> "$state_dir"/current-commit
-
-       # rewrite parents; if none were rewritten, we can fast-forward.
-       new_parents=
-       pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)"
-       if test "$pend" = " "
-       then
-               pend=" root"
-       fi
-       while [ "$pend" != "" ]
-       do
-               p=$(expr "$pend" : ' \([^ ]*\)')
-               pend="${pend# $p}"
-
-               if test -f "$rewritten"/$p
-               then
-                       new_p=$(cat "$rewritten"/$p)
-
-                       # If the todo reordered commits, and our parent is marked for
-                       # rewriting, but hasn't been gotten to yet, assume the user meant to
-                       # drop it on top of the current HEAD
-                       if test -z "$new_p"
-                       then
-                               new_p=$(git rev-parse HEAD)
-                       fi
-
-                       test $p != $new_p && fast_forward=f
-                       case "$new_parents" in
-                       *$new_p*)
-                               ;; # do nothing; that parent is already there
-                       *)
-                               new_parents="$new_parents $new_p"
-                               ;;
-                       esac
-               else
-                       if test -f "$dropped"/$p
-                       then
-                               fast_forward=f
-                               replacement="$(cat "$dropped"/$p)"
-                               test -z "$replacement" && replacement=root
-                               pend=" $replacement$pend"
-                       else
-                               new_parents="$new_parents $p"
-                       fi
-               fi
-       done
-       case $fast_forward in
-       t)
-               output warn "$(eval_gettext "Fast-forward to \$sha1")"
-               output git reset --hard $sha1 ||
-                       die "$(eval_gettext "Cannot fast-forward to \$sha1")"
-               ;;
-       f)
-               first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
-
-               if [ "$1" != "-n" ]
-               then
-                       # detach HEAD to current parent
-                       output git checkout $first_parent 2> /dev/null ||
-                               die "$(eval_gettext "Cannot move HEAD to \$first_parent")"
-               fi
-
-               case "$new_parents" in
-               ' '*' '*)
-                       test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")"
-
-                       # redo merge
-                       author_script_content=$(get_author_ident_from_commit $sha1)
-                       eval "$author_script_content"
-                       msg_content="$(commit_message $sha1)"
-                       # No point in merging the first parent, that's HEAD
-                       new_parents=${new_parents# $first_parent}
-                       merge_args="--no-log --no-ff"
-                       if ! do_with_author output eval \
-                               git merge ${gpg_sign_opt:+$(git rev-parse \
-                                       --sq-quote "$gpg_sign_opt")} \
-                               $allow_rerere_autoupdate "$merge_args" \
-                               "$strategy_args" \
-                               -m "$(git rev-parse --sq-quote "$msg_content")" \
-                               "$new_parents"
-                       then
-                               printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
-                               die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")"
-                       fi
-                       echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
-                       ;;
-               *)
-                       output eval git cherry-pick $allow_rerere_autoupdate \
-                               $allow_empty_message \
-                               ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
-                               "$strategy_args" "$@" ||
-                               die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
-                       ;;
-               esac
-               ;;
-       esac
-}
-
-this_nth_commit_message () {
-       n=$1
-       eval_gettext "This is the commit message #\${n}:"
-}
-
-skip_nth_commit_message () {
-       n=$1
-       eval_gettext "The commit message #\${n} will be skipped:"
-}
-
-update_squash_messages () {
-       if test -f "$squash_msg"; then
-               mv "$squash_msg" "$squash_msg".bak || exit
-               count=$(($(sed -n \
-                       -e "1s/^$comment_char[^0-9]*\([0-9][0-9]*\).*/\1/p" \
-                       -e "q" < "$squash_msg".bak)+1))
-               {
-                       printf '%s\n' "$comment_char $(eval_ngettext \
-                               "This is a combination of \$count commit." \
-                               "This is a combination of \$count commits." \
-                               $count)"
-                       sed -e 1d -e '2,/^./{
-                               /^$/d
-                       }' <"$squash_msg".bak
-               } >"$squash_msg"
-       else
-               commit_message HEAD >"$fixup_msg" ||
-               die "$(eval_gettext "Cannot write \$fixup_msg")"
-               count=2
-               {
-                       printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")"
-                       printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")"
-                       echo
-                       cat "$fixup_msg"
-               } >"$squash_msg"
-       fi
-       case $1 in
-       squash)
-               rm -f "$fixup_msg"
-               echo
-               printf '%s\n' "$comment_char $(this_nth_commit_message $count)"
-               echo
-               commit_message $2
-               ;;
-       fixup)
-               echo
-               printf '%s\n' "$comment_char $(skip_nth_commit_message $count)"
-               echo
-               # Change the space after the comment character to TAB:
-               commit_message $2 | git stripspace --comment-lines | sed -e 's/ /       /'
-               ;;
-       esac >>"$squash_msg"
-}
-
-peek_next_command () {
-       git stripspace --strip-comments <"$todo" | sed -n -e 's/ .*//p' -e q
-}
-
-# A squash/fixup has failed.  Prepare the long version of the squash
-# commit message, then die_with_patch.  This code path requires the
-# user to edit the combined commit message for all commits that have
-# been squashed/fixedup so far.  So also erase the old squash
-# messages, effectively causing the combined commit to be used as the
-# new basis for any further squash/fixups.  Args: sha1 rest
-die_failed_squash() {
-       sha1=$1
-       rest=$2
-       mv "$squash_msg" "$msg" || exit
-       rm -f "$fixup_msg"
-       cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
-       warn
-       warn "$(eval_gettext "Could not apply \$sha1... \$rest")"
-       die_with_patch $sha1 ""
-}
-
-flush_rewritten_pending() {
-       test -s "$rewritten_pending" || return
-       newsha1="$(git rev-parse HEAD^0)"
-       sed "s/$/ $newsha1/" < "$rewritten_pending" >> "$rewritten_list"
-       rm -f "$rewritten_pending"
-}
-
-record_in_rewritten() {
-       oldsha1="$(git rev-parse $1)"
-       echo "$oldsha1" >> "$rewritten_pending"
-
-       case "$(peek_next_command)" in
-       squash|s|fixup|f)
-               ;;
-       *)
-               flush_rewritten_pending
-               ;;
-       esac
-}
-
-do_pick () {
-       sha1=$1
-       rest=$2
-       if test "$(git rev-parse HEAD)" = "$squash_onto"
-       then
-               # Set the correct commit message and author info on the
-               # sentinel root before cherry-picking the original changes
-               # without committing (-n).  Finally, update the sentinel again
-               # to include these changes.  If the cherry-pick results in a
-               # conflict, this means our behaviour is similar to a standard
-               # failed cherry-pick during rebase, with a dirty index to
-               # resolve before manually running git commit --amend then git
-               # rebase --continue.
-               git commit --allow-empty --allow-empty-message --amend \
-                          --no-post-rewrite -n -q -C $sha1 $signoff &&
-                       pick_one -n $sha1 &&
-                       git commit --allow-empty --allow-empty-message \
-                                  --amend --no-post-rewrite -n -q -C $sha1 $signoff \
-                                  ${gpg_sign_opt:+"$gpg_sign_opt"} ||
-                                  die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
-       else
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
-       fi
-}
-
-do_next () {
-       rm -f "$msg" "$author_script" "$amend" "$state_dir"/stopped-sha || exit
-       read -r command sha1 rest < "$todo"
-       case "$command" in
-       "$comment_char"*|''|noop|drop|d)
-               mark_action_done
-               ;;
-       "$cr")
-               # Work around CR left by "read" (e.g. with Git for Windows' Bash).
-               mark_action_done
-               ;;
-       pick|p)
-               comment_for_reflog pick
-
-               mark_action_done
-               do_pick $sha1 "$rest"
-               record_in_rewritten $sha1
-               ;;
-       reword|r)
-               comment_for_reflog reword
-
-               mark_action_done
-               do_pick $sha1 "$rest"
-               git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} \
-                       $allow_empty_message || {
-                       warn "$(eval_gettext "\
-Could not amend commit after successfully picking \$sha1... \$rest
-This is most likely due to an empty commit message, or the pre-commit hook
-failed. If the pre-commit hook failed, you may need to resolve the issue before
-you are able to reword the commit.")"
-                       exit_with_patch $sha1 1
-               }
-               record_in_rewritten $sha1
-               ;;
-       edit|e)
-               comment_for_reflog edit
-
-               mark_action_done
-               do_pick $sha1 "$rest"
-               sha1_abbrev=$(git rev-parse --short $sha1)
-               warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")"
-               exit_with_patch $sha1 0
-               ;;
-       squash|s|fixup|f)
-               case "$command" in
-               squash|s)
-                       squash_style=squash
-                       ;;
-               fixup|f)
-                       squash_style=fixup
-                       ;;
-               esac
-               comment_for_reflog $squash_style
-
-               test -f "$done" && has_action "$done" ||
-                       die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")"
-
-               mark_action_done
-               update_squash_messages $squash_style $sha1
-               author_script_content=$(get_author_ident_from_commit HEAD)
-               echo "$author_script_content" > "$author_script"
-               eval "$author_script_content"
-               if ! pick_one -n $sha1
-               then
-                       git rev-parse --verify HEAD >"$amend"
-                       die_failed_squash $sha1 "$rest"
-               fi
-               case "$(peek_next_command)" in
-               squash|s|fixup|f)
-                       # This is an intermediate commit; its message will only be
-                       # used in case of trouble.  So use the long version:
-                       do_with_author output git commit --amend --no-verify -F "$squash_msg" \
-                               ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-                               die_failed_squash $sha1 "$rest"
-                       ;;
-               *)
-                       # This is the final command of this squash/fixup group
-                       if test -f "$fixup_msg"
-                       then
-                               do_with_author git commit --amend --no-verify -F "$fixup_msg" \
-                                       ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-                                       die_failed_squash $sha1 "$rest"
-                       else
-                               cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit
-                               rm -f "$GIT_DIR"/MERGE_MSG
-                               do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \
-                                       ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-                                       die_failed_squash $sha1 "$rest"
-                       fi
-                       rm -f "$squash_msg" "$fixup_msg"
-                       ;;
-               esac
-               record_in_rewritten $sha1
-               ;;
-       x|"exec")
-               read -r command rest < "$todo"
-               mark_action_done
-               eval_gettextln "Executing: \$rest"
-               "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution
-               status=$?
-               # Run in subshell because require_clean_work_tree can die.
-               dirty=f
-               (require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
-               if test "$status" -ne 0
-               then
-                       warn "$(eval_gettext "Execution failed: \$rest")"
-                       test "$dirty" = f ||
-                               warn "$(gettext "and made changes to the index and/or the working tree")"
-
-                       warn "$(gettext "\
-You can fix the problem, and then run
-
-       git rebase --continue")"
-                       warn
-                       if test $status -eq 127         # command not found
-                       then
-                               status=1
-                       fi
-                       exit "$status"
-               elif test "$dirty" = t
-               then
-                       # TRANSLATORS: after these lines is a command to be issued by the user
-                       warn "$(eval_gettext "\
-Execution succeeded: \$rest
-but left changes to the index and/or the working tree
-Commit or stash your changes, and then run
-
-       git rebase --continue")"
-                       warn
-                       exit 1
-               fi
-               ;;
-       *)
-               warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")"
-               fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")"
-               if git rev-parse --verify -q "$sha1" >/dev/null
-               then
-                       die_with_patch $sha1 "$fixtodo"
-               else
-                       die "$fixtodo"
-               fi
-               ;;
-       esac
-       test -s "$todo" && return
-
-       comment_for_reflog finish &&
-       newhead=$(git rev-parse HEAD) &&
-       case $head_name in
-       refs/*)
-               message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
-               git update-ref -m "$message" $head_name $newhead $orig_head &&
-               git symbolic-ref \
-                 -m "$GIT_REFLOG_ACTION: returning to $head_name" \
-                 HEAD $head_name
-               ;;
-       esac && {
-               test ! -f "$state_dir"/verbose ||
-                       git diff-tree --stat $orig_head..HEAD
-       } &&
-       {
-               test -s "$rewritten_list" &&
-               git notes copy --for-rewrite=rebase < "$rewritten_list" ||
-               true # we don't care if this copying failed
-       } &&
-       hook="$(git rev-parse --git-path hooks/post-rewrite)"
-       if test -x "$hook" && test -s "$rewritten_list"; then
-               "$hook" rebase < "$rewritten_list"
-               true # we don't care if this hook failed
-       fi &&
-               warn "$(eval_gettext "Successfully rebased and updated \$head_name.")"
-
-       return 1 # not failure; just to break the do_rest loop
-}
-
-# can only return 0, when the infinite loop breaks
-do_rest () {
-       while :
-       do
-               do_next || break
-       done
-}
-
-expand_todo_ids() {
-       git rebase--interactive --expand-ids
-}
-
-collapse_todo_ids() {
-       git rebase--interactive --shorten-ids
-}
-
-# Switch to the branch in $into and notify it in the reflog
-checkout_onto () {
-       GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
-       output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
-       git update-ref ORIG_HEAD $orig_head
-}
-
-get_missing_commit_check_level () {
-       check_level=$(git config --get rebase.missingCommitsCheck)
-       check_level=${check_level:-ignore}
-       # Don't be case sensitive
-       printf '%s' "$check_level" | tr 'A-Z' 'a-z'
-}
-
-# Initiate an action. If the cannot be any
-# further action it  may exec a command
-# or exit and not return.
-#
-# TODO: Consider a cleaner return model so it
-# never exits and always return 0 if process
-# is complete.
-#
-# Parameter 1 is the action to initiate.
-#
-# Returns 0 if the action was able to complete
-# and if 1 if further processing is required.
-initiate_action () {
-       case "$1" in
-       continue)
-               # do we have anything to commit?
-               if git diff-index --cached --quiet HEAD --
-               then
-                       # Nothing to commit -- skip this commit
-
-                       test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
-                       rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
-                       die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
-               else
-                       if ! test -f "$author_script"
-                       then
-                               gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-                               die "$(eval_gettext "\
-You have staged changes in your working tree.
-If these changes are meant to be
-squashed into the previous commit, run:
-
-  git commit --amend \$gpg_sign_opt_quoted
-
-If they are meant to go into a new commit, run:
-
-  git commit \$gpg_sign_opt_quoted
-
-In both cases, once you're done, continue with:
-
-  git rebase --continue
-")"
-                       fi
-                       . "$author_script" ||
-                               die "$(gettext "Error trying to find the author identity to amend commit")"
-                       if test -f "$amend"
-                       then
-                               current_head=$(git rev-parse --verify HEAD)
-                               test "$current_head" = $(cat "$amend") ||
-                               die "$(gettext "\
-You have uncommitted changes in your working tree. Please commit them
-first and then run 'git rebase --continue' again.")"
-                               do_with_author git commit --amend --no-verify -F "$msg" -e \
-                                       ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-                                       die "$(gettext "Could not commit staged changes.")"
-                       else
-                               do_with_author git commit --no-verify -F "$msg" -e \
-                                       ${gpg_sign_opt:+"$gpg_sign_opt"} $allow_empty_message ||
-                                       die "$(gettext "Could not commit staged changes.")"
-                       fi
-               fi
-
-               if test -r "$state_dir"/stopped-sha
-               then
-                       record_in_rewritten "$(cat "$state_dir"/stopped-sha)"
-               fi
-
-               require_clean_work_tree "rebase"
-               do_rest
-               return 0
-               ;;
-       skip)
-               git rerere clear
-               do_rest
-               return 0
-               ;;
-       edit-todo)
-               git stripspace --strip-comments <"$todo" >"$todo".new
-               mv -f "$todo".new "$todo"
-               collapse_todo_ids
-               append_todo_help
-               gettext "
-You are editing the todo file of an ongoing interactive rebase.
-To continue rebase after editing, run:
-    git rebase --continue
-
-" | git stripspace --comment-lines >>"$todo"
-
-               git_sequence_editor "$todo" ||
-                       die "$(gettext "Could not execute editor")"
-               expand_todo_ids
-
-               exit
-               ;;
-       show-current-patch)
-               exec git show REBASE_HEAD --
-               ;;
-       *)
-               return 1 # continue
-               ;;
-       esac
-}
-
-setup_reflog_action () {
-       comment_for_reflog start
-
-       if test ! -z "$switch_to"
-       then
-               GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
-               output git checkout "$switch_to" -- ||
-                       die "$(eval_gettext "Could not checkout \$switch_to")"
-
-               comment_for_reflog start
-       fi
-}
-
-init_basic_state () {
-       orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
-       mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
-       rm -f "$(git rev-parse --git-path REBASE_HEAD)"
-
-       : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
-       write_basic_state
-}
-
-init_revisions_and_shortrevisions () {
-       shorthead=$(git rev-parse --short $orig_head)
-       shortonto=$(git rev-parse --short $onto)
-       if test -z "$rebase_root"
-               # this is now equivalent to ! -z "$upstream"
-       then
-               shortupstream=$(git rev-parse --short $upstream)
-               revisions=$upstream...$orig_head
-               shortrevisions=$shortupstream..$shorthead
-       else
-               revisions=$onto...$orig_head
-               shortrevisions=$shorthead
-               test -z "$squash_onto" ||
-               echo "$squash_onto" >"$state_dir"/squash-onto
-       fi
-}
-
-complete_action() {
-       test -s "$todo" || echo noop >> "$todo"
-       test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
-       test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
-
-       todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
-       todocount=${todocount##* }
-
-cat >>"$todo" <<EOF
-
-$comment_char $(eval_ngettext \
-       "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
-       "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
-       "$todocount")
-EOF
-       append_todo_help
-       gettext "
-However, if you remove everything, the rebase will be aborted.
-
-" | git stripspace --comment-lines >>"$todo"
-
-       if test -z "$keep_empty"
-       then
-               printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
-       fi
-
-
-       has_action "$todo" ||
-               return 2
-
-       cp "$todo" "$todo".backup
-       collapse_todo_ids
-       git_sequence_editor "$todo" ||
-               die_abort "$(gettext "Could not execute editor")"
-
-       has_action "$todo" ||
-               return 2
-
-       git rebase--interactive --check-todo-list || {
-               ret=$?
-               checkout_onto
-               exit $ret
-       }
-
-       expand_todo_ids
-       checkout_onto
-       do_rest
-}
-
-git_rebase__preserve_merges () {
-       initiate_action "$action"
-       ret=$?
-       if test $ret = 0; then
-               return 0
-       fi
-
-       setup_reflog_action
-       init_basic_state
-
-       if test -z "$rebase_root"
-       then
-               mkdir "$rewritten" &&
-               for c in $(git merge-base --all $orig_head $upstream)
-               do
-                       echo $onto > "$rewritten"/$c ||
-                               die "$(gettext "Could not init rewritten commits")"
-               done
-       else
-               mkdir "$rewritten" &&
-               echo $onto > "$rewritten"/root ||
-                       die "$(gettext "Could not init rewritten commits")"
-       fi
-
-       init_revisions_and_shortrevisions
-
-       format=$(git config --get rebase.instructionFormat)
-       # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
-       git rev-list --format="%m%H ${format:-%s}" \
-               --reverse --left-right --topo-order \
-               $revisions ${restrict_revision+^$restrict_revision} | \
-               sed -n "s/^>//p" |
-       while read -r sha1 rest
-       do
-               if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
-               then
-                       comment_out="$comment_char "
-               else
-                       comment_out=
-               fi
-
-               if test -z "$rebase_root"
-               then
-                       preserve=t
-                       for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)
-                       do
-                               if test -f "$rewritten"/$p
-                               then
-                                       preserve=f
-                               fi
-                       done
-               else
-                       preserve=f
-               fi
-               if test f = "$preserve"
-               then
-                       touch "$rewritten"/$sha1
-                       printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
-               fi
-       done
-
-       # Watch for commits that been dropped by --cherry-pick
-       mkdir "$dropped"
-       # Save all non-cherry-picked changes
-       git rev-list $revisions --left-right --cherry-pick | \
-               sed -n "s/^>//p" > "$state_dir"/not-cherry-picks
-       # Now all commits and note which ones are missing in
-       # not-cherry-picks and hence being dropped
-       git rev-list $revisions |
-       while read rev
-       do
-               if test -f "$rewritten"/$rev &&
-                  ! sane_grep "$rev" "$state_dir"/not-cherry-picks >/dev/null
-               then
-                       # Use -f2 because if rev-list is telling us this commit is
-                       # not worthwhile, we don't want to track its multiple heads,
-                       # just the history of its first-parent for others that will
-                       # be rebasing on top of it
-                       git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$dropped"/$rev
-                       sha1=$(git rev-list -1 $rev)
-                       sane_grep -v "^[a-z][a-z]* $sha1" <"$todo" > "${todo}2" ; mv "${todo}2" "$todo"
-                       rm "$rewritten"/$rev
-               fi
-       done
-
-       complete_action
-}
index 10d976418568556e83b47df89905a6a95a7d8915..cee053cdc388a9390ef4c23730d334e0bba376db 100644 (file)
@@ -223,9 +223,6 @@ require_clean_work_tree () {
                "rewrite branches")
                        gettextln "Cannot rewrite branches: You have unstaged changes." >&2
                        ;;
-               "pull with rebase")
-                       gettextln "Cannot pull with rebase: You have unstaged changes." >&2
-                       ;;
                *)
                        eval_gettextln "Cannot \$action: You have unstaged changes." >&2
                        ;;
@@ -242,9 +239,6 @@ require_clean_work_tree () {
                        rebase)
                                gettextln "Cannot rebase: Your index contains uncommitted changes." >&2
                                ;;
-                       "pull with rebase")
-                               gettextln "Cannot pull with rebase: Your index contains uncommitted changes." >&2
-                               ;;
                        *)
                                eval_gettextln "Cannot \$action: Your index contains uncommitted changes." >&2
                                ;;
index dbd2ec205037e062b6fb4ae52205bbf84e023001..652861aa66a400940c24ea8405177f372009eafe 100755 (executable)
@@ -63,11 +63,6 @@ isnumber()
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
 }
 
-# Given a full hex object ID, is this the zero OID?
-is_zero_oid () {
-       echo "$1" | sane_egrep '^0+$' >/dev/null 2>&1
-}
-
 # Sanitize the local git environment for use within a submodule. We
 # can't simply use clear_local_git_env since we want to preserve some
 # of the settings from GIT_CONFIG_PARAMETERS.
@@ -145,130 +140,12 @@ cmd_add()
                shift
        done
 
-       if ! git submodule--helper config --check-writeable >/dev/null 2>&1
-       then
-                die "fatal: $(eval_gettext "please make sure that the .gitmodules file is in the working tree")"
-       fi
-
-       if test -n "$reference_path"
+       if test -z "$1"
        then
-               is_absolute_path "$reference_path" ||
-               reference_path="$wt_prefix$reference_path"
-
-               reference="--reference=$reference_path"
-       fi
-
-       repo=$1
-       sm_path=$2
-
-       if test -z "$sm_path"; then
-               sm_path=$(printf '%s\n' "$repo" |
-                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-       fi
-
-       if test -z "$repo" || test -z "$sm_path"; then
                usage
        fi
 
-       is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
-
-       # assure repo is absolute or relative to parent
-       case "$repo" in
-       ./*|../*)
-               test -z "$wt_prefix" ||
-               die "fatal: $(gettext "Relative path can only be used from the toplevel of the working tree")"
-
-               # dereference source url relative to parent's url
-               realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
-               ;;
-       *:*|/*)
-               # absolute url
-               realrepo=$repo
-               ;;
-       *)
-               die "fatal: $(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
-       ;;
-       esac
-
-       # normalize path:
-       # multiple //; leading ./; /./; /../; trailing /
-       sm_path=$(printf '%s/\n' "$sm_path" |
-               sed -e '
-                       s|//*|/|g
-                       s|^\(\./\)*||
-                       s|/\(\./\)*|/|g
-                       :start
-                       s|\([^/]*\)/\.\./||
-                       tstart
-                       s|/*$||
-               ')
-       if test -z "$force"
-       then
-               git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
-               die "fatal: $(eval_gettext "'\$sm_path' already exists in the index")"
-       else
-               git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 &&
-               die "fatal: $(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
-       fi
-
-       if test -d "$sm_path" &&
-               test -z $(git -C "$sm_path" rev-parse --show-cdup 2>/dev/null)
-       then
-           git -C "$sm_path" rev-parse --verify -q HEAD >/dev/null ||
-           die "fatal: $(eval_gettext "'\$sm_path' does not have a commit checked out")"
-       fi
-
-       if test -z "$force"
-       then
-           dryerr=$(git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" 2>&1 >/dev/null)
-           res=$?
-           if test $res -ne 0
-           then
-                echo >&2 "$dryerr"
-                exit $res
-           fi
-       fi
-
-       if test -n "$custom_name"
-       then
-               sm_name="$custom_name"
-       else
-               sm_name="$sm_path"
-       fi
-
-       if ! git submodule--helper check-name "$sm_name"
-       then
-               die "fatal: $(eval_gettext "'$sm_name' is not a valid submodule name")"
-       fi
-
-       git submodule--helper add-clone ${GIT_QUIET:+--quiet} ${force:+"--force"} ${progress:+"--progress"} ${branch:+--branch "$branch"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
-       git config submodule."$sm_name".url "$realrepo"
-
-       git add --no-warn-embedded-repo $force "$sm_path" ||
-       die "fatal: $(eval_gettext "Failed to add submodule '\$sm_path'")"
-
-       git submodule--helper config submodule."$sm_name".path "$sm_path" &&
-       git submodule--helper config submodule."$sm_name".url "$repo" &&
-       if test -n "$branch"
-       then
-               git submodule--helper config submodule."$sm_name".branch "$branch"
-       fi &&
-       git add --force .gitmodules ||
-       die "fatal: $(eval_gettext "Failed to register submodule '\$sm_path'")"
-
-       # NEEDSWORK: In a multi-working-tree world, this needs to be
-       # set in the per-worktree config.
-       if git config --get submodule.active >/dev/null
-       then
-               # If the submodule being adding isn't already covered by the
-               # current configured pathspec, set the submodule's active flag
-               if ! git submodule--helper is-active "$sm_path"
-               then
-                       git config submodule."$sm_name".active "true"
-               fi
-       else
-               git config submodule."$sm_name".active "true"
-       fi
+       git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${GIT_QUIET:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@"
 }
 
 #
@@ -369,13 +246,6 @@ cmd_deinit()
        git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-is_tip_reachable () (
-       sanitize_submodule_env &&
-       cd "$1" &&
-       rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) &&
-       test -z "$rev"
-)
-
 # usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
 # Because arguments are positional, use an empty string to omit <depth>
 # but include <sha1>.
@@ -519,14 +389,13 @@ cmd_update()
 
                git submodule--helper ensure-core-worktree "$sm_path" || exit 1
 
-               update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
-
                displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
                if test $just_cloned -eq 1
                then
                        subsha1=
                else
+                       just_cloned=
                        subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
                        die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
@@ -547,70 +416,38 @@ cmd_update()
                        die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
                fi
 
-               if test "$subsha1" != "$sha1" || test -n "$force"
-               then
-                       subforce=$force
-                       # If we don't already have a -f flag and the submodule has never been checked out
-                       if test -z "$subsha1" && test -z "$force"
-                       then
-                               subforce="-f"
-                       fi
-
-                       if test -z "$nofetch"
-                       then
-                               # Run fetch only if $sha1 isn't present or it
-                               # is not reachable from a ref.
-                               is_tip_reachable "$sm_path" "$sha1" ||
-                               fetch_in_submodule "$sm_path" $depth ||
-                               say "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'; trying to directly fetch \$sha1:")"
-
-                               # Now we tried the usual fetch, but $sha1 may
-                               # not be reachable from any of the refs
-                               is_tip_reachable "$sm_path" "$sha1" ||
-                               fetch_in_submodule "$sm_path" "$depth" "$sha1" ||
-                               die "fatal: $(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
-                       fi
-
-                       must_die_on_failure=
-                       case "$update_module" in
-                       checkout)
-                               command="git checkout $subforce -q"
-                               die_msg="fatal: $(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
-                               ;;
-                       rebase)
-                               command="git rebase ${GIT_QUIET:+--quiet}"
-                               die_msg="fatal: $(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       merge)
-                               command="git merge ${GIT_QUIET:+--quiet}"
-                               die_msg="fatal: $(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       !*)
-                               command="${update_module#!}"
-                               die_msg="fatal: $(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")"
-                               say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")"
-                               must_die_on_failure=yes
-                               ;;
-                       *)
-                               die "fatal: $(eval_gettext "Invalid update mode '$update_module' for submodule path '$path'")"
-                       esac
-
-                       if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1")
-                       then
-                               say "$say_msg"
-                       elif test -n "$must_die_on_failure"
-                       then
-                               die_with_status 2 "$die_msg"
-                       else
-                               err="${err};$die_msg"
-                               continue
-                       fi
-               fi
+               out=$(git submodule--helper run-update-procedure \
+                         ${wt_prefix:+--prefix "$wt_prefix"} \
+                         ${GIT_QUIET:+--quiet} \
+                         ${force:+--force} \
+                         ${just_cloned:+--just-cloned} \
+                         ${nofetch:+--no-fetch} \
+                         ${depth:+"$depth"} \
+                         ${update:+--update "$update"} \
+                         ${prefix:+--recursive-prefix "$prefix"} \
+                         ${sha1:+--oid "$sha1"} \
+                         ${subsha1:+--suboid "$subsha1"} \
+                         "--" \
+                         "$sm_path")
+
+               # exit codes for run-update-procedure:
+               # 0: update was successful, say command output
+               # 1: update procedure failed, but should not die
+               # 2 or 128: subcommand died during execution
+               # 3: no update procedure was run
+               res="$?"
+               case $res in
+               0)
+                       say "$out"
+                       ;;
+               1)
+                       err="${err};fatal: $out"
+                       continue
+                       ;;
+               2|128)
+                       die_with_status $res "fatal: $out"
+                       ;;
+               esac
 
                if test -n "$recursive"
                then
index 70cb5e2a83b5bec6e9f95ea0cb7feb112e2dfec5..be987e316f92acda1ff83c880ead3c6848db487c 100755 (executable)
@@ -273,7 +273,6 @@ my %cmd = (
                          'fetch-all|all' => \$_fetch_all,
                          'dry-run|n' => \$_dry_run,
                          'rebase-merges|p' => \$_rebase_merges,
-                         'preserve-merges|p' => \$_rebase_merges,
                          %fc_opts } ],
        'commit-diff' => [ \&cmd_commit_diff,
                           'Commit a diff between two trees',
diff --git a/git.c b/git.c
index 18bed9a99647aa310ad37d4cd8dc683c66084b41..5ff21be21f323c794399a3605f7396e1b3bba686 100644 (file)
--- a/git.c
+++ b/git.c
@@ -561,7 +561,7 @@ static struct cmd_struct commands[] = {
        { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
        { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
        { "mktree", cmd_mktree, RUN_SETUP },
-       { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY },
+       { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
        { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
        { "name-rev", cmd_name_rev, RUN_SETUP },
        { "notes", cmd_notes, RUN_SETUP },
@@ -577,7 +577,6 @@ static struct cmd_struct commands[] = {
        { "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
        { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
        { "rebase", cmd_rebase, RUN_SETUP | NEED_WORK_TREE },
-       { "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE },
        { "receive-pack", cmd_receive_pack },
        { "reflog", cmd_reflog, RUN_SETUP },
        { "remote", cmd_remote, RUN_SETUP },
index e09e024a09b00f420a4ca63bb5f12546ae5bf27b..fbd1c20a232bb098509e0e25981de5016dea555f 100755 (executable)
@@ -3796,7 +3796,8 @@ sub git_get_heads_list {
        my @headslist;
 
        open my $fd, '-|', git_cmd(), 'for-each-ref',
-               ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+               ($limit ? '--count='.($limit+1) : ()),
+               '--sort=-HEAD', '--sort=-committerdate',
                '--format=%(objectname) %(refname) %(subject)%00%(committer)',
                @patterns
                or return;
index 127aecfc2b071f9a745a871a9ea205931eeb672f..800d8caa677d4d2baf4fcd3c8aebf9649650a8b3 100644 (file)
@@ -3,11 +3,14 @@
 #include "config.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "dir.h"
 #include "gpg-interface.h"
 #include "sigchain.h"
 #include "tempfile.h"
+#include "alias.h"
 
 static char *configured_signing_key;
+static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file;
 static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
 
 struct gpg_format {
@@ -15,6 +18,14 @@ struct gpg_format {
        const char *program;
        const char **verify_args;
        const char **sigs;
+       int (*verify_signed_buffer)(struct signature_check *sigc,
+                                   struct gpg_format *fmt, const char *payload,
+                                   size_t payload_size, const char *signature,
+                                   size_t signature_size);
+       int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,
+                          const char *signing_key);
+       const char *(*get_default_key)(void);
+       const char *(*get_key_id)(void);
 };
 
 static const char *openpgp_verify_args[] = {
@@ -35,14 +46,59 @@ static const char *x509_sigs[] = {
        NULL
 };
 
+static const char *ssh_verify_args[] = { NULL };
+static const char *ssh_sigs[] = {
+       "-----BEGIN SSH SIGNATURE-----",
+       NULL
+};
+
+static int verify_gpg_signed_buffer(struct signature_check *sigc,
+                                   struct gpg_format *fmt, const char *payload,
+                                   size_t payload_size, const char *signature,
+                                   size_t signature_size);
+static int verify_ssh_signed_buffer(struct signature_check *sigc,
+                                   struct gpg_format *fmt, const char *payload,
+                                   size_t payload_size, const char *signature,
+                                   size_t signature_size);
+static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
+                          const char *signing_key);
+static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
+                          const char *signing_key);
+
+static const char *get_default_ssh_signing_key(void);
+
+static const char *get_ssh_key_id(void);
+
 static struct gpg_format gpg_format[] = {
-       { .name = "openpgp", .program = "gpg",
-         .verify_args = openpgp_verify_args,
-         .sigs = openpgp_sigs
+       {
+               .name = "openpgp",
+               .program = "gpg",
+               .verify_args = openpgp_verify_args,
+               .sigs = openpgp_sigs,
+               .verify_signed_buffer = verify_gpg_signed_buffer,
+               .sign_buffer = sign_buffer_gpg,
+               .get_default_key = NULL,
+               .get_key_id = NULL,
+       },
+       {
+               .name = "x509",
+               .program = "gpgsm",
+               .verify_args = x509_verify_args,
+               .sigs = x509_sigs,
+               .verify_signed_buffer = verify_gpg_signed_buffer,
+               .sign_buffer = sign_buffer_gpg,
+               .get_default_key = NULL,
+               .get_key_id = NULL,
        },
-       { .name = "x509", .program = "gpgsm",
-         .verify_args = x509_verify_args,
-         .sigs = x509_sigs
+       {
+               .name = "ssh",
+               .program = "ssh-keygen",
+               .verify_args = ssh_verify_args,
+               .sigs = ssh_sigs,
+               .verify_signed_buffer = verify_ssh_signed_buffer,
+               .sign_buffer = sign_buffer_ssh,
+               .get_default_key = get_default_ssh_signing_key,
+               .get_key_id = get_ssh_key_id,
        },
 };
 
@@ -72,7 +128,7 @@ static struct gpg_format *get_format_by_sig(const char *sig)
 void signature_check_clear(struct signature_check *sigc)
 {
        FREE_AND_NULL(sigc->payload);
-       FREE_AND_NULL(sigc->gpg_output);
+       FREE_AND_NULL(sigc->output);
        FREE_AND_NULL(sigc->gpg_status);
        FREE_AND_NULL(sigc->signer);
        FREE_AND_NULL(sigc->key);
@@ -257,16 +313,16 @@ error:
        FREE_AND_NULL(sigc->key);
 }
 
-static int verify_signed_buffer(const char *payload, size_t payload_size,
-                               const char *signature, size_t signature_size,
-                               struct strbuf *gpg_output,
-                               struct strbuf *gpg_status)
+static int verify_gpg_signed_buffer(struct signature_check *sigc,
+                                   struct gpg_format *fmt, const char *payload,
+                                   size_t payload_size, const char *signature,
+                                   size_t signature_size)
 {
        struct child_process gpg = CHILD_PROCESS_INIT;
-       struct gpg_format *fmt;
        struct tempfile *temp;
        int ret;
-       struct strbuf buf = STRBUF_INIT;
+       struct strbuf gpg_stdout = STRBUF_INIT;
+       struct strbuf gpg_stderr = STRBUF_INIT;
 
        temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
        if (!temp)
@@ -279,10 +335,6 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
                return -1;
        }
 
-       fmt = get_format_by_sig(signature);
-       if (!fmt)
-               BUG("bad signature '%s'", signature);
-
        strvec_push(&gpg.args, fmt->program);
        strvec_pushv(&gpg.args, fmt->verify_args);
        strvec_pushl(&gpg.args,
@@ -290,18 +342,220 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
                     "--verify", temp->filename.buf, "-",
                     NULL);
 
-       if (!gpg_status)
-               gpg_status = &buf;
-
        sigchain_push(SIGPIPE, SIG_IGN);
-       ret = pipe_command(&gpg, payload, payload_size,
-                          gpg_status, 0, gpg_output, 0);
+       ret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,
+                          &gpg_stderr, 0);
        sigchain_pop(SIGPIPE);
 
        delete_tempfile(&temp);
 
-       ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
-       strbuf_release(&buf); /* no matter it was used or not */
+       ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ");
+       sigc->payload = xmemdupz(payload, payload_size);
+       sigc->output = strbuf_detach(&gpg_stderr, NULL);
+       sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);
+
+       parse_gpg_output(sigc);
+
+       strbuf_release(&gpg_stdout);
+       strbuf_release(&gpg_stderr);
+
+       return ret;
+}
+
+static void parse_ssh_output(struct signature_check *sigc)
+{
+       const char *line, *principal, *search;
+       char *to_free;
+       char *key = NULL;
+
+       /*
+        * ssh-keygen output should be:
+        * Good "git" signature for PRINCIPAL with RSA key SHA256:FINGERPRINT
+        *
+        * or for valid but unknown keys:
+        * Good "git" signature with RSA key SHA256:FINGERPRINT
+        *
+        * Note that "PRINCIPAL" can contain whitespace, "RSA" and
+        * "SHA256" part could be a different token that names of
+        * the algorithms used, and "FINGERPRINT" is a hexadecimal
+        * string.  By finding the last occurence of " with ", we can
+        * reliably parse out the PRINCIPAL.
+        */
+       sigc->result = 'B';
+       sigc->trust_level = TRUST_NEVER;
+
+       line = to_free = xmemdupz(sigc->output, strcspn(sigc->output, "\n"));
+
+       if (skip_prefix(line, "Good \"git\" signature for ", &line)) {
+               /* Valid signature and known principal */
+               sigc->result = 'G';
+               sigc->trust_level = TRUST_FULLY;
+
+               /* Search for the last "with" to get the full principal */
+               principal = line;
+               do {
+                       search = strstr(line, " with ");
+                       if (search)
+                               line = search + 1;
+               } while (search != NULL);
+               sigc->signer = xmemdupz(principal, line - principal - 1);
+       } else if (skip_prefix(line, "Good \"git\" signature with ", &line)) {
+               /* Valid signature, but key unknown */
+               sigc->result = 'G';
+               sigc->trust_level = TRUST_UNDEFINED;
+       } else {
+               goto cleanup;
+       }
+
+       key = strstr(line, "key");
+       if (key) {
+               sigc->fingerprint = xstrdup(strstr(line, "key") + 4);
+               sigc->key = xstrdup(sigc->fingerprint);
+       } else {
+               /*
+                * Output did not match what we expected
+                * Treat the signature as bad
+                */
+               sigc->result = 'B';
+       }
+
+cleanup:
+       free(to_free);
+}
+
+static int verify_ssh_signed_buffer(struct signature_check *sigc,
+                                   struct gpg_format *fmt, const char *payload,
+                                   size_t payload_size, const char *signature,
+                                   size_t signature_size)
+{
+       struct child_process ssh_keygen = CHILD_PROCESS_INIT;
+       struct tempfile *buffer_file;
+       int ret = -1;
+       const char *line;
+       size_t trust_size;
+       char *principal;
+       struct strbuf ssh_principals_out = STRBUF_INIT;
+       struct strbuf ssh_principals_err = STRBUF_INIT;
+       struct strbuf ssh_keygen_out = STRBUF_INIT;
+       struct strbuf ssh_keygen_err = STRBUF_INIT;
+
+       if (!ssh_allowed_signers) {
+               error(_("gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification"));
+               return -1;
+       }
+
+       buffer_file = mks_tempfile_t(".git_vtag_tmpXXXXXX");
+       if (!buffer_file)
+               return error_errno(_("could not create temporary file"));
+       if (write_in_full(buffer_file->fd, signature, signature_size) < 0 ||
+           close_tempfile_gently(buffer_file) < 0) {
+               error_errno(_("failed writing detached signature to '%s'"),
+                           buffer_file->filename.buf);
+               delete_tempfile(&buffer_file);
+               return -1;
+       }
+
+       /* Find the principal from the signers */
+       strvec_pushl(&ssh_keygen.args, fmt->program,
+                    "-Y", "find-principals",
+                    "-f", ssh_allowed_signers,
+                    "-s", buffer_file->filename.buf,
+                    NULL);
+       ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,
+                          &ssh_principals_err, 0);
+       if (ret && strstr(ssh_principals_err.buf, "usage:")) {
+               error(_("ssh-keygen -Y find-principals/verify is needed for ssh signature verification (available in openssh version 8.2p1+)"));
+               goto out;
+       }
+       if (ret || !ssh_principals_out.len) {
+               /*
+                * We did not find a matching principal in the allowedSigners
+                * Check without validation
+                */
+               child_process_init(&ssh_keygen);
+               strvec_pushl(&ssh_keygen.args, fmt->program,
+                            "-Y", "check-novalidate",
+                            "-n", "git",
+                            "-s", buffer_file->filename.buf,
+                            NULL);
+               pipe_command(&ssh_keygen, payload, payload_size,
+                                  &ssh_keygen_out, 0, &ssh_keygen_err, 0);
+
+               /*
+                * Fail on unknown keys
+                * we still call check-novalidate to display the signature info
+                */
+               ret = -1;
+       } else {
+               /* Check every principal we found (one per line) */
+               for (line = ssh_principals_out.buf; *line;
+                    line = strchrnul(line + 1, '\n')) {
+                       while (*line == '\n')
+                               line++;
+                       if (!*line)
+                               break;
+
+                       trust_size = strcspn(line, "\n");
+                       principal = xmemdupz(line, trust_size);
+
+                       child_process_init(&ssh_keygen);
+                       strbuf_release(&ssh_keygen_out);
+                       strbuf_release(&ssh_keygen_err);
+                       strvec_push(&ssh_keygen.args, fmt->program);
+                       /*
+                        * We found principals
+                        * Try with each until we find a match
+                        */
+                       strvec_pushl(&ssh_keygen.args, "-Y", "verify",
+                                    "-n", "git",
+                                    "-f", ssh_allowed_signers,
+                                    "-I", principal,
+                                    "-s", buffer_file->filename.buf,
+                                    NULL);
+
+                       if (ssh_revocation_file) {
+                               if (file_exists(ssh_revocation_file)) {
+                                       strvec_pushl(&ssh_keygen.args, "-r",
+                                                    ssh_revocation_file, NULL);
+                               } else {
+                                       warning(_("ssh signing revocation file configured but not found: %s"),
+                                               ssh_revocation_file);
+                               }
+                       }
+
+                       sigchain_push(SIGPIPE, SIG_IGN);
+                       ret = pipe_command(&ssh_keygen, payload, payload_size,
+                                          &ssh_keygen_out, 0, &ssh_keygen_err, 0);
+                       sigchain_pop(SIGPIPE);
+
+                       FREE_AND_NULL(principal);
+
+                       if (!ret)
+                               ret = !starts_with(ssh_keygen_out.buf, "Good");
+
+                       if (!ret)
+                               break;
+               }
+       }
+
+       sigc->payload = xmemdupz(payload, payload_size);
+       strbuf_stripspace(&ssh_keygen_out, 0);
+       strbuf_stripspace(&ssh_keygen_err, 0);
+       /* Add stderr outputs to show the user actual ssh-keygen errors */
+       strbuf_add(&ssh_keygen_out, ssh_principals_err.buf, ssh_principals_err.len);
+       strbuf_add(&ssh_keygen_out, ssh_keygen_err.buf, ssh_keygen_err.len);
+       sigc->output = strbuf_detach(&ssh_keygen_out, NULL);
+       sigc->gpg_status = xstrdup(sigc->output);
+
+       parse_ssh_output(sigc);
+
+out:
+       if (buffer_file)
+               delete_tempfile(&buffer_file);
+       strbuf_release(&ssh_principals_out);
+       strbuf_release(&ssh_principals_err);
+       strbuf_release(&ssh_keygen_out);
+       strbuf_release(&ssh_keygen_err);
 
        return ret;
 }
@@ -309,35 +563,32 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
 int check_signature(const char *payload, size_t plen, const char *signature,
        size_t slen, struct signature_check *sigc)
 {
-       struct strbuf gpg_output = STRBUF_INIT;
-       struct strbuf gpg_status = STRBUF_INIT;
+       struct gpg_format *fmt;
        int status;
 
        sigc->result = 'N';
        sigc->trust_level = -1;
 
-       status = verify_signed_buffer(payload, plen, signature, slen,
-                                     &gpg_output, &gpg_status);
-       if (status && !gpg_output.len)
-               goto out;
-       sigc->payload = xmemdupz(payload, plen);
-       sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
-       sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
-       parse_gpg_output(sigc);
+       fmt = get_format_by_sig(signature);
+       if (!fmt)
+               die(_("bad/incompatible signature '%s'"), signature);
+
+       status = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,
+                                          slen);
+
+       if (status && !sigc->output)
+               return !!status;
+
        status |= sigc->result != 'G';
        status |= sigc->trust_level < configured_min_trust_level;
 
- out:
-       strbuf_release(&gpg_status);
-       strbuf_release(&gpg_output);
-
        return !!status;
 }
 
 void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
 {
-       const char *output = flags & GPG_VERIFY_RAW ?
-               sigc->gpg_status : sigc->gpg_output;
+       const char *output = flags & GPG_VERIFY_RAW ? sigc->gpg_status :
+                                                           sigc->output;
 
        if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
                fputs(sigc->payload, stdout);
@@ -419,12 +670,33 @@ int git_gpg_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "gpg.ssh.defaultkeycommand")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return git_config_string(&ssh_default_key_command, var, value);
+       }
+
+       if (!strcmp(var, "gpg.ssh.allowedsignersfile")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return git_config_pathname(&ssh_allowed_signers, var, value);
+       }
+
+       if (!strcmp(var, "gpg.ssh.revocationfile")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               return git_config_pathname(&ssh_revocation_file, var, value);
+       }
+
        if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
                fmtname = "openpgp";
 
        if (!strcmp(var, "gpg.x509.program"))
                fmtname = "x509";
 
+       if (!strcmp(var, "gpg.ssh.program"))
+               fmtname = "ssh";
+
        if (fmtname) {
                fmt = get_format_by_name(fmtname);
                return git_config_string(&fmt->program, var, value);
@@ -433,18 +705,148 @@ int git_gpg_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
+static char *get_ssh_key_fingerprint(const char *signing_key)
+{
+       struct child_process ssh_keygen = CHILD_PROCESS_INIT;
+       int ret = -1;
+       struct strbuf fingerprint_stdout = STRBUF_INIT;
+       struct strbuf **fingerprint;
+       char *fingerprint_ret;
+
+       /*
+        * With SSH Signing this can contain a filename or a public key
+        * For textual representation we usually want a fingerprint
+        */
+       if (starts_with(signing_key, "ssh-")) {
+               strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", "-", NULL);
+               ret = pipe_command(&ssh_keygen, signing_key,
+                                  strlen(signing_key), &fingerprint_stdout, 0,
+                                  NULL, 0);
+       } else {
+               strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf",
+                            configured_signing_key, NULL);
+               ret = pipe_command(&ssh_keygen, NULL, 0, &fingerprint_stdout, 0,
+                                  NULL, 0);
+       }
+
+       if (!!ret)
+               die_errno(_("failed to get the ssh fingerprint for key '%s'"),
+                         signing_key);
+
+       fingerprint = strbuf_split_max(&fingerprint_stdout, ' ', 3);
+       if (!fingerprint[1])
+               die_errno(_("failed to get the ssh fingerprint for key '%s'"),
+                         signing_key);
+
+       fingerprint_ret = strbuf_detach(fingerprint[1], NULL);
+       strbuf_list_free(fingerprint);
+       strbuf_release(&fingerprint_stdout);
+       return fingerprint_ret;
+}
+
+/* Returns the first public key from an ssh-agent to use for signing */
+static const char *get_default_ssh_signing_key(void)
+{
+       struct child_process ssh_default_key = CHILD_PROCESS_INIT;
+       int ret = -1;
+       struct strbuf key_stdout = STRBUF_INIT, key_stderr = STRBUF_INIT;
+       struct strbuf **keys;
+       char *key_command = NULL;
+       const char **argv;
+       int n;
+       char *default_key = NULL;
+
+       if (!ssh_default_key_command)
+               die(_("either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured"));
+
+       key_command = xstrdup(ssh_default_key_command);
+       n = split_cmdline(key_command, &argv);
+
+       if (n < 0)
+               die("malformed build-time gpg.ssh.defaultKeyCommand: %s",
+                   split_cmdline_strerror(n));
+
+       strvec_pushv(&ssh_default_key.args, argv);
+       ret = pipe_command(&ssh_default_key, NULL, 0, &key_stdout, 0,
+                          &key_stderr, 0);
+
+       if (!ret) {
+               keys = strbuf_split_max(&key_stdout, '\n', 2);
+               if (keys[0] && starts_with(keys[0]->buf, "ssh-")) {
+                       default_key = strbuf_detach(keys[0], NULL);
+               } else {
+                       warning(_("gpg.ssh.defaultKeycommand succeeded but returned no keys: %s %s"),
+                               key_stderr.buf, key_stdout.buf);
+               }
+
+               strbuf_list_free(keys);
+       } else {
+               warning(_("gpg.ssh.defaultKeyCommand failed: %s %s"),
+                       key_stderr.buf, key_stdout.buf);
+       }
+
+       free(key_command);
+       free(argv);
+       strbuf_release(&key_stdout);
+
+       return default_key;
+}
+
+static const char *get_ssh_key_id(void) {
+       return get_ssh_key_fingerprint(get_signing_key());
+}
+
+/* Returns a textual but unique representation of the signing key */
+const char *get_signing_key_id(void)
+{
+       if (use_format->get_key_id) {
+               return use_format->get_key_id();
+       }
+
+       /* GPG/GPGSM only store a key id on this variable */
+       return get_signing_key();
+}
+
 const char *get_signing_key(void)
 {
        if (configured_signing_key)
                return configured_signing_key;
-       return git_committer_info(IDENT_STRICT|IDENT_NO_DATE);
+       if (use_format->get_default_key) {
+               return use_format->get_default_key();
+       }
+
+       return git_committer_info(IDENT_STRICT | IDENT_NO_DATE);
 }
 
 int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
+{
+       return use_format->sign_buffer(buffer, signature, signing_key);
+}
+
+/*
+ * Strip CR from the line endings, in case we are on Windows.
+ * NEEDSWORK: make it trim only CRs before LFs and rename
+ */
+static void remove_cr_after(struct strbuf *buffer, size_t offset)
+{
+       size_t i, j;
+
+       for (i = j = offset; i < buffer->len; i++) {
+               if (buffer->buf[i] != '\r') {
+                       if (i != j)
+                               buffer->buf[j] = buffer->buf[i];
+                       j++;
+               }
+       }
+       strbuf_setlen(buffer, j);
+}
+
+static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
+                         const char *signing_key)
 {
        struct child_process gpg = CHILD_PROCESS_INIT;
        int ret;
-       size_t i, j, bottom;
+       size_t bottom;
        struct strbuf gpg_status = STRBUF_INIT;
 
        strvec_pushl(&gpg.args,
@@ -470,13 +872,98 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
                return error(_("gpg failed to sign the data"));
 
        /* Strip CR from the line endings, in case we are on Windows. */
-       for (i = j = bottom; i < signature->len; i++)
-               if (signature->buf[i] != '\r') {
-                       if (i != j)
-                               signature->buf[j] = signature->buf[i];
-                       j++;
-               }
-       strbuf_setlen(signature, j);
+       remove_cr_after(signature, bottom);
 
        return 0;
 }
+
+static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
+                          const char *signing_key)
+{
+       struct child_process signer = CHILD_PROCESS_INIT;
+       int ret = -1;
+       size_t bottom, keylen;
+       struct strbuf signer_stderr = STRBUF_INIT;
+       struct tempfile *key_file = NULL, *buffer_file = NULL;
+       char *ssh_signing_key_file = NULL;
+       struct strbuf ssh_signature_filename = STRBUF_INIT;
+
+       if (!signing_key || signing_key[0] == '\0')
+               return error(
+                       _("user.signingkey needs to be set for ssh signing"));
+
+       if (starts_with(signing_key, "ssh-")) {
+               /* A literal ssh key */
+               key_file = mks_tempfile_t(".git_signing_key_tmpXXXXXX");
+               if (!key_file)
+                       return error_errno(
+                               _("could not create temporary file"));
+               keylen = strlen(signing_key);
+               if (write_in_full(key_file->fd, signing_key, keylen) < 0 ||
+                   close_tempfile_gently(key_file) < 0) {
+                       error_errno(_("failed writing ssh signing key to '%s'"),
+                                   key_file->filename.buf);
+                       goto out;
+               }
+               ssh_signing_key_file = strbuf_detach(&key_file->filename, NULL);
+       } else {
+               /* We assume a file */
+               ssh_signing_key_file = expand_user_path(signing_key, 1);
+       }
+
+       buffer_file = mks_tempfile_t(".git_signing_buffer_tmpXXXXXX");
+       if (!buffer_file) {
+               error_errno(_("could not create temporary file"));
+               goto out;
+       }
+
+       if (write_in_full(buffer_file->fd, buffer->buf, buffer->len) < 0 ||
+           close_tempfile_gently(buffer_file) < 0) {
+               error_errno(_("failed writing ssh signing key buffer to '%s'"),
+                           buffer_file->filename.buf);
+               goto out;
+       }
+
+       strvec_pushl(&signer.args, use_format->program,
+                    "-Y", "sign",
+                    "-n", "git",
+                    "-f", ssh_signing_key_file,
+                    buffer_file->filename.buf,
+                    NULL);
+
+       sigchain_push(SIGPIPE, SIG_IGN);
+       ret = pipe_command(&signer, NULL, 0, NULL, 0, &signer_stderr, 0);
+       sigchain_pop(SIGPIPE);
+
+       if (ret) {
+               if (strstr(signer_stderr.buf, "usage:"))
+                       error(_("ssh-keygen -Y sign is needed for ssh signing (available in openssh version 8.2p1+)"));
+
+               error("%s", signer_stderr.buf);
+               goto out;
+       }
+
+       bottom = signature->len;
+
+       strbuf_addbuf(&ssh_signature_filename, &buffer_file->filename);
+       strbuf_addstr(&ssh_signature_filename, ".sig");
+       if (strbuf_read_file(signature, ssh_signature_filename.buf, 0) < 0) {
+               error_errno(
+                       _("failed reading ssh signing data buffer from '%s'"),
+                       ssh_signature_filename.buf);
+       }
+       unlink_or_warn(ssh_signature_filename.buf);
+
+       /* Strip CR from the line endings, in case we are on Windows. */
+       remove_cr_after(signature, bottom);
+
+out:
+       if (key_file)
+               delete_tempfile(&key_file);
+       if (buffer_file)
+               delete_tempfile(&buffer_file);
+       strbuf_release(&signer_stderr);
+       strbuf_release(&ssh_signature_filename);
+       FREE_AND_NULL(ssh_signing_key_file);
+       return ret;
+}
index 80567e4894868d5d7a192cab0afb9ca9c09cb70d..beefacbb1e9025b8d65a83aea74c6ce3913535cd 100644 (file)
@@ -17,7 +17,7 @@ enum signature_trust_level {
 
 struct signature_check {
        char *payload;
-       char *gpg_output;
+       char *output;
        char *gpg_status;
 
        /*
@@ -64,6 +64,12 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature,
 int git_gpg_config(const char *, const char *, void *);
 void set_signing_key(const char *);
 const char *get_signing_key(void);
+
+/*
+ * Returns a textual unique representation of the signing key in use
+ * Either a GPG KeyID or a SSH Key Fingerprint
+ */
+const char *get_signing_key_id(void);
 int check_signature(const char *payload, size_t plen,
                    const char *signature, size_t slen,
                    struct signature_check *sigc);
diff --git a/grep.c b/grep.c
index 424a39591b05e519d166e064a9f4e3e6d5973681..14fe8a0fd23a9e4d196c7813c64b8e24edb74a6f 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -867,7 +867,7 @@ void free_grep_patterns(struct grep_opt *opt)
        free_pattern_expr(opt->pattern_expression);
 }
 
-static char *end_of_line(char *cp, unsigned long *left)
+static const char *end_of_line(const char *cp, unsigned long *left)
 {
        unsigned long l = *left;
        while (l && *cp != '\n') {
@@ -908,7 +908,8 @@ static void show_name(struct grep_opt *opt, const char *name)
        opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
 }
 
-static int patmatch(struct grep_pat *p, char *line, char *eol,
+static int patmatch(struct grep_pat *p,
+                   const char *line, const char *eol,
                    regmatch_t *match, int eflags)
 {
        int hit;
@@ -922,20 +923,16 @@ static int patmatch(struct grep_pat *p, char *line, char *eol,
        return hit;
 }
 
-static int strip_timestamp(char *bol, char **eol_p)
+static void strip_timestamp(const char *bol, const char **eol_p)
 {
-       char *eol = *eol_p;
-       int ch;
+       const char *eol = *eol_p;
 
        while (bol < --eol) {
                if (*eol != '>')
                        continue;
                *eol_p = ++eol;
-               ch = *eol;
-               *eol = '\0';
-               return ch;
+               break;
        }
-       return 0;
 }
 
 static struct {
@@ -947,12 +944,12 @@ static struct {
        { "reflog ", 7 },
 };
 
-static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
+static int match_one_pattern(struct grep_pat *p,
+                            const char *bol, const char *eol,
                             enum grep_context ctx,
                             regmatch_t *pmatch, int eflags)
 {
        int hit = 0;
-       int saved_ch = 0;
        const char *start = bol;
 
        if ((p->token != GREP_PATTERN) &&
@@ -971,7 +968,7 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
                switch (p->field) {
                case GREP_HEADER_AUTHOR:
                case GREP_HEADER_COMMITTER:
-                       saved_ch = strip_timestamp(bol, &eol);
+                       strip_timestamp(bol, &eol);
                        break;
                default:
                        break;
@@ -1021,8 +1018,6 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
                                goto again;
                }
        }
-       if (p->token == GREP_PATTERN_HEAD && saved_ch)
-               *eol = saved_ch;
        if (hit) {
                pmatch[0].rm_so += bol - start;
                pmatch[0].rm_eo += bol - start;
@@ -1030,8 +1025,9 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
        return hit;
 }
 
-static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
-                          char *eol, enum grep_context ctx, ssize_t *col,
+static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x,
+                          const char *bol, const char *eol,
+                          enum grep_context ctx, ssize_t *col,
                           ssize_t *icol, int collect_hits)
 {
        int h = 0;
@@ -1098,7 +1094,8 @@ static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
        return h;
 }
 
-static int match_expr(struct grep_opt *opt, char *bol, char *eol,
+static int match_expr(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      enum grep_context ctx, ssize_t *col,
                      ssize_t *icol, int collect_hits)
 {
@@ -1106,7 +1103,8 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol,
        return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
 }
 
-static int match_line(struct grep_opt *opt, char *bol, char *eol,
+static int match_line(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      ssize_t *col, ssize_t *icol,
                      enum grep_context ctx, int collect_hits)
 {
@@ -1138,7 +1136,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,
        return hit;
 }
 
-static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
+static int match_next_pattern(struct grep_pat *p,
+                             const char *bol, const char *eol,
                              enum grep_context ctx,
                              regmatch_t *pmatch, int eflags)
 {
@@ -1159,7 +1158,8 @@ static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
        return 1;
 }
 
-static int next_match(struct grep_opt *opt, char *bol, char *eol,
+static int next_match(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      enum grep_context ctx, regmatch_t *pmatch, int eflags)
 {
        struct grep_pat *p;
@@ -1215,7 +1215,8 @@ static void show_line_header(struct grep_opt *opt, const char *name,
        }
 }
 
-static void show_line(struct grep_opt *opt, char *bol, char *eol,
+static void show_line(struct grep_opt *opt,
+                     const char *bol, const char *eol,
                      const char *name, unsigned lno, ssize_t cno, char sign)
 {
        int rest = eol - bol;
@@ -1246,7 +1247,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
        if (opt->color || opt->only_matching) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
-               int ch = *eol;
                int eflags = 0;
 
                if (opt->color) {
@@ -1261,7 +1261,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        else if (sign == '=')
                                line_color = opt->colors[GREP_COLOR_FUNCTION];
                }
-               *eol = '\0';
                while (next_match(opt, bol, eol, ctx, &match, eflags)) {
                        if (match.rm_so == match.rm_eo)
                                break;
@@ -1279,7 +1278,6 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
                }
-               *eol = ch;
        }
        if (!opt->only_matching) {
                output_color(opt, bol, rest, line_color);
@@ -1307,7 +1305,8 @@ static inline void grep_attr_unlock(void)
                pthread_mutex_unlock(&grep_attr_mutex);
 }
 
-static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bol, char *eol)
+static int match_funcname(struct grep_opt *opt, struct grep_source *gs,
+                         const char *bol, const char *eol)
 {
        xdemitconf_t *xecfg = opt->priv;
        if (xecfg && !xecfg->find_func) {
@@ -1334,10 +1333,10 @@ static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bo
 }
 
 static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
-                              char *bol, unsigned lno)
+                              const char *bol, unsigned lno)
 {
        while (bol > gs->buf) {
-               char *eol = --bol;
+               const char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
@@ -1356,7 +1355,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
 static int is_empty_line(const char *bol, const char *eol);
 
 static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
-                            char *bol, char *end, unsigned lno)
+                            const char *bol, const char *end, unsigned lno)
 {
        unsigned cur = lno, from = 1, funcname_lno = 0, orig_from;
        int funcname_needed = !!opt->funcname, comment_needed = 0;
@@ -1376,8 +1375,8 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
        /* Rewind. */
        while (bol > gs->buf && cur > from) {
-               char *next_bol = bol;
-               char *eol = --bol;
+               const char *next_bol = bol;
+               const char *eol = --bol;
 
                while (bol > gs->buf && bol[-1] != '\n')
                        bol--;
@@ -1408,7 +1407,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
        /* Back forward. */
        while (cur < lno) {
-               char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
+               const char *eol = bol, sign = (cur == funcname_lno) ? '=' : '-';
 
                while (*eol != '\n')
                        eol++;
@@ -1436,12 +1435,12 @@ static int should_lookahead(struct grep_opt *opt)
 static int look_ahead(struct grep_opt *opt,
                      unsigned long *left_p,
                      unsigned *lno_p,
-                     char **bol_p)
+                     const char **bol_p)
 {
        unsigned lno = *lno_p;
-       char *bol = *bol_p;
+       const char *bol = *bol_p;
        struct grep_pat *p;
-       char *sp, *last_bol;
+       const char *sp, *last_bol;
        regoff_t earliest = -1;
 
        for (p = opt->pattern_list; p; p = p->next) {
@@ -1543,8 +1542,8 @@ static int is_empty_line(const char *bol, const char *eol)
 
 static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
 {
-       char *bol;
-       char *peek_bol = NULL;
+       const char *bol;
+       const char *peek_bol = NULL;
        unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
@@ -1626,7 +1625,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
        bol = gs->buf;
        left = gs->size;
        while (left) {
-               char *eol, ch;
+               const char *eol;
                int hit;
                ssize_t cno;
                ssize_t col = -1, icol = -1;
@@ -1647,14 +1646,11 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                    && look_ahead(opt, &left, &lno, &bol))
                        break;
                eol = end_of_line(bol, &left);
-               ch = *eol;
-               *eol = 0;
 
                if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
                        ctx = GREP_CONTEXT_BODY;
 
                hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
-               *eol = ch;
 
                if (collect_hits)
                        goto next_line;
@@ -1713,7 +1709,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                }
                if (show_function && (!peek_bol || peek_bol < bol)) {
                        unsigned long peek_left = left;
-                       char *peek_eol = eol;
+                       const char *peek_eol = eol;
 
                        /*
                         * Trailing empty lines are not interesting.
@@ -1825,14 +1821,25 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs)
        return grep_source_1(opt, gs, 0);
 }
 
-int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
+static void grep_source_init_buf(struct grep_source *gs,
+                                const char *buf,
+                                unsigned long size)
+{
+       gs->type = GREP_SOURCE_BUF;
+       gs->name = NULL;
+       gs->path = NULL;
+       gs->buf = buf;
+       gs->size = size;
+       gs->driver = NULL;
+       gs->identifier = NULL;
+}
+
+int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size)
 {
        struct grep_source gs;
        int r;
 
-       grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL, NULL);
-       gs.buf = buf;
-       gs.size = size;
+       grep_source_init_buf(&gs, buf, size);
 
        r = grep_source(opt, &gs);
 
@@ -1840,28 +1847,30 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
        return r;
 }
 
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
-                     const char *name, const char *path,
-                     const void *identifier)
+void grep_source_init_file(struct grep_source *gs, const char *name,
+                          const char *path)
 {
-       gs->type = type;
+       gs->type = GREP_SOURCE_FILE;
        gs->name = xstrdup_or_null(name);
        gs->path = xstrdup_or_null(path);
        gs->buf = NULL;
        gs->size = 0;
        gs->driver = NULL;
+       gs->identifier = xstrdup(path);
+}
 
-       switch (type) {
-       case GREP_SOURCE_FILE:
-               gs->identifier = xstrdup(identifier);
-               break;
-       case GREP_SOURCE_OID:
-               gs->identifier = oiddup(identifier);
-               break;
-       case GREP_SOURCE_BUF:
-               gs->identifier = NULL;
-               break;
-       }
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+                         const char *path, const struct object_id *oid,
+                         struct repository *repo)
+{
+       gs->type = GREP_SOURCE_OID;
+       gs->name = xstrdup_or_null(name);
+       gs->path = xstrdup_or_null(path);
+       gs->buf = NULL;
+       gs->size = 0;
+       gs->driver = NULL;
+       gs->identifier = oiddup(oid);
+       gs->repo = repo;
 }
 
 void grep_source_clear(struct grep_source *gs)
@@ -1877,7 +1886,9 @@ void grep_source_clear_data(struct grep_source *gs)
        switch (gs->type) {
        case GREP_SOURCE_FILE:
        case GREP_SOURCE_OID:
-               FREE_AND_NULL(gs->buf);
+               /* these types own the buffer */
+               free((char *)gs->buf);
+               gs->buf = NULL;
                gs->size = 0;
                break;
        case GREP_SOURCE_BUF:
@@ -1890,7 +1901,8 @@ static int grep_source_load_oid(struct grep_source *gs)
 {
        enum object_type type;
 
-       gs->buf = read_object_file(gs->identifier, &type, &gs->size);
+       gs->buf = repo_read_object_file(gs->repo, gs->identifier, &type,
+                                       &gs->size);
        if (!gs->buf)
                return error(_("'%s': unable to read %s"),
                             gs->name,
diff --git a/grep.h b/grep.h
index 72f82b1e302397b20f8ae4ebd75a0b534fff99f5..3c75ed1fd868dab4791981f1c46d0abbd65c96ac 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -120,7 +120,20 @@ struct grep_opt {
        struct grep_pat *header_list;
        struct grep_pat **header_tail;
        struct grep_expr *pattern_expression;
+
+       /*
+        * NEEDSWORK: See if we can remove this field, because the repository
+        * should probably be per-source. That is, grep.c functions using this
+        * field should probably start using "repo" in "struct grep_source"
+        * instead.
+        *
+        * This is potentially the cause of at least one bug - "git grep"
+        * using the textconv attributes from the superproject on the
+        * submodules. See the failing "git grep --textconv" tests in
+        * t7814-grep-recurse-submodules.sh for more information.
+        */
        struct repository *repo;
+
        const char *prefix;
        int prefix_length;
        regex_t regexp;
@@ -176,7 +189,7 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *orig
 void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 void compile_grep_patterns(struct grep_opt *opt);
 void free_grep_patterns(struct grep_opt *opt);
-int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size);
+int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size);
 
 struct grep_source {
        char *name;
@@ -187,17 +200,20 @@ struct grep_source {
                GREP_SOURCE_BUF,
        } type;
        void *identifier;
+       struct repository *repo; /* if GREP_SOURCE_OID */
 
-       char *buf;
+       const char *buf;
        unsigned long size;
 
        char *path; /* for attribute lookups */
        struct userdiff_driver *driver;
 };
 
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
-                     const char *name, const char *path,
-                     const void *identifier);
+void grep_source_init_file(struct grep_source *gs, const char *name,
+                          const char *path);
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+                         const char *path, const struct object_id *oid,
+                         struct repository *repo);
 void grep_source_clear_data(struct grep_source *gs);
 void grep_source_clear(struct grep_source *gs);
 void grep_source_load_driver(struct grep_source *gs,
@@ -207,7 +223,6 @@ void grep_source_load_driver(struct grep_source *gs,
 int grep_source(struct grep_opt *opt, struct grep_source *gs);
 
 struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
-int grep_threads_ok(const struct grep_opt *opt);
 
 /*
  * Mutex used around access to the attributes machinery if
diff --git a/help.c b/help.c
index 3c3bdec21356d9e346ef2185d7ddaffa8229d6d0..973e47cdc30ce05603fb935b6574482b3556b31a 100644 (file)
--- a/help.c
+++ b/help.c
@@ -11,6 +11,7 @@
 #include "version.h"
 #include "refs.h"
 #include "parse-options.h"
+#include "prompt.h"
 
 struct category_description {
        uint32_t category;
@@ -292,9 +293,21 @@ void load_command_list(const char *prefix,
        exclude_cmds(other_cmds, main_cmds);
 }
 
-void list_commands(unsigned int colopts,
-                  struct cmdnames *main_cmds, struct cmdnames *other_cmds)
+static int get_colopts(const char *var, const char *value, void *data)
 {
+       unsigned int *colopts = data;
+
+       if (starts_with(var, "column."))
+               return git_column_config(var, value, "help", colopts);
+
+       return 0;
+}
+
+void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds)
+{
+       unsigned int colopts = 0;
+       git_config(get_colopts, &colopts);
+
        if (main_cmds->cnt) {
                const char *exec_path = git_exec_path();
                printf_ln(_("available git commands in '%s'"), exec_path);
@@ -472,6 +485,7 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
 static int autocorrect;
 static struct cmdnames aliases;
 
+#define AUTOCORRECT_PROMPT (-3)
 #define AUTOCORRECT_NEVER (-2)
 #define AUTOCORRECT_IMMEDIATELY (-1)
 
@@ -486,6 +500,8 @@ static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
                        autocorrect = AUTOCORRECT_NEVER;
                } else if (!strcmp(value, "immediate")) {
                        autocorrect = AUTOCORRECT_IMMEDIATELY;
+               } else if (!strcmp(value, "prompt")) {
+                       autocorrect = AUTOCORRECT_PROMPT;
                } else {
                        int v = git_config_int(var, value);
                        autocorrect = (v < 0)
@@ -539,6 +555,12 @@ const char *help_unknown_cmd(const char *cmd)
 
        read_early_config(git_unknown_cmd_config, NULL);
 
+       /*
+        * Disable autocorrection prompt in a non-interactive session
+        */
+       if ((autocorrect == AUTOCORRECT_PROMPT) && (!isatty(0) || !isatty(2)))
+               autocorrect = AUTOCORRECT_NEVER;
+
        if (autocorrect == AUTOCORRECT_NEVER) {
                fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
                exit(1);
@@ -618,7 +640,16 @@ const char *help_unknown_cmd(const char *cmd)
                                   _("Continuing under the assumption that "
                                     "you meant '%s'."),
                                   assumed);
-               else {
+               else if (autocorrect == AUTOCORRECT_PROMPT) {
+                       char *answer;
+                       struct strbuf msg = STRBUF_INIT;
+                       strbuf_addf(&msg, _("Run '%s' instead? (y/N)"), assumed);
+                       answer = git_prompt(msg.buf, PROMPT_ECHO);
+                       strbuf_release(&msg);
+                       if (!(starts_with(answer, "y") ||
+                             starts_with(answer, "Y")))
+                               exit(1);
+               } else {
                        fprintf_ln(stderr,
                                   _("Continuing in %0.1f seconds, "
                                     "assuming that you meant '%s'."),
diff --git a/help.h b/help.h
index 5871e93ba2de04753905a7cf8220261e17159003..9d383f1a0b22a9a6d29be5b21e7fe09fcae5ddb8 100644 (file)
--- a/help.h
+++ b/help.h
@@ -37,7 +37,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len);
 /* Here we require that excludes is a sorted list. */
 void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
 int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
+void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds);
 void get_version_info(struct strbuf *buf, int show_build_options);
 
 /*
diff --git a/hook.c b/hook.c
new file mode 100644 (file)
index 0000000..55e1145
--- /dev/null
+++ b/hook.c
@@ -0,0 +1,42 @@
+#include "cache.h"
+#include "hook.h"
+#include "run-command.h"
+
+const char *find_hook(const char *name)
+{
+       static struct strbuf path = STRBUF_INIT;
+
+       strbuf_reset(&path);
+       strbuf_git_path(&path, "hooks/%s", name);
+       if (access(path.buf, X_OK) < 0) {
+               int err = errno;
+
+#ifdef STRIP_EXTENSION
+               strbuf_addstr(&path, STRIP_EXTENSION);
+               if (access(path.buf, X_OK) >= 0)
+                       return path.buf;
+               if (errno == EACCES)
+                       err = errno;
+#endif
+
+               if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
+                       static struct string_list advise_given = STRING_LIST_INIT_DUP;
+
+                       if (!string_list_lookup(&advise_given, name)) {
+                               string_list_insert(&advise_given, name);
+                               advise(_("The '%s' hook was ignored because "
+                                        "it's not set as executable.\n"
+                                        "You can disable this warning with "
+                                        "`git config advice.ignoredHook false`."),
+                                      path.buf);
+                       }
+               }
+               return NULL;
+       }
+       return path.buf;
+}
+
+int hook_exists(const char *name)
+{
+       return !!find_hook(name);
+}
diff --git a/hook.h b/hook.h
new file mode 100644 (file)
index 0000000..6aa36fc
--- /dev/null
+++ b/hook.h
@@ -0,0 +1,16 @@
+#ifndef HOOK_H
+#define HOOK_H
+
+/*
+ * Returns the path to the hook file, or NULL if the hook is missing
+ * or disabled. Note that this points to static storage that will be
+ * overwritten by further calls to find_hook and run_hook_*.
+ */
+const char *find_hook(const char *name);
+
+/**
+ * A boolean version of find_hook()
+ */
+int hook_exists(const char *hookname);
+
+#endif
index b329bf63f097fadda16cbd4c8e1c06134b28d6a8..e7c0eeab2308f14c589c54bcbb81bed1176d93b6 100644 (file)
@@ -534,7 +534,7 @@ static void get_info_refs(struct strbuf *hdr, char *arg)
 
        if (service_name) {
                const char *argv[] = {NULL /* service name */,
-                       "--stateless-rpc", "--advertise-refs",
+                       "--http-backend-info-refs",
                        ".", NULL};
                struct rpc_service *svc = select_service(hdr, service_name);
 
@@ -739,6 +739,7 @@ static int bad_request(struct strbuf *hdr, const struct service_cmd *c)
 int cmd_main(int argc, const char **argv)
 {
        char *method = getenv("REQUEST_METHOD");
+       const char *proto_header;
        char *dir;
        struct service_cmd *cmd = NULL;
        char *cmd_arg = NULL;
@@ -789,6 +790,9 @@ int cmd_main(int argc, const char **argv)
        http_config();
        max_request_buffer = git_env_ulong("GIT_HTTP_MAX_REQUEST_BUFFER",
                                           max_request_buffer);
+       proto_header = getenv("HTTP_GIT_PROTOCOL");
+       if (proto_header)
+               setenv(GIT_PROTOCOL_ENVIRONMENT, proto_header, 0);
 
        cmd->imp(&hdr, cmd_arg);
        return 0;
index d7cb1675a2d5d5697e681a9ba777131410cc12c7..3309aaf004a4db175bc4c0b94b5c63efebcb30e3 100644 (file)
@@ -203,10 +203,8 @@ static void curl_setup_http(CURL *curl, const char *url,
        curl_easy_setopt(curl, CURLOPT_INFILE, buffer);
        curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
-#ifndef NO_CURL_IOCTL
        curl_easy_setopt(curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
        curl_easy_setopt(curl, CURLOPT_IOCTLDATA, buffer);
-#endif
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn);
        curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, custom_req);
@@ -249,8 +247,6 @@ static void process_response(void *callback_data)
        finish_request(request);
 }
 
-#ifdef USE_CURL_MULTI
-
 static void start_fetch_loose(struct transfer_request *request)
 {
        struct active_request_slot *slot;
@@ -299,7 +295,6 @@ static void start_mkcol(struct transfer_request *request)
                FREE_AND_NULL(request->url);
        }
 }
-#endif
 
 static void start_fetch_packed(struct transfer_request *request)
 {
@@ -605,7 +600,6 @@ static void finish_request(struct transfer_request *request)
        }
 }
 
-#ifdef USE_CURL_MULTI
 static int is_running_queue;
 static int fill_active_slot(void *unused)
 {
@@ -629,7 +623,6 @@ static int fill_active_slot(void *unused)
        }
        return 0;
 }
-#endif
 
 static void get_remote_object_list(unsigned char parent);
 
@@ -658,10 +651,8 @@ static void add_fetch_request(struct object *obj)
        request->next = request_queue_head;
        request_queue_head = request;
 
-#ifdef USE_CURL_MULTI
        fill_active_slots();
        step_active_slots();
-#endif
 }
 
 static int add_send_request(struct object *obj, struct remote_lock *lock)
@@ -696,10 +687,8 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
        request->next = request_queue_head;
        request_queue_head = request;
 
-#ifdef USE_CURL_MULTI
        fill_active_slots();
        step_active_slots();
-#endif
 
        return 1;
 }
@@ -894,7 +883,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        slot->results = &results;
        curl_setup_http(slot->curl, url, DAV_LOCK, &out_buffer, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
 
        CALLOC_ARRAY(lock, 1);
        lock->timeout = -1;
@@ -1153,7 +1142,7 @@ static void remote_ls(const char *path, int flags,
        curl_setup_http(slot->curl, url, DAV_PROPFIND,
                        &out_buffer, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
@@ -1227,7 +1216,7 @@ static int locking_available(void)
        curl_setup_http(slot->curl, repo->url, DAV_PROPFIND,
                        &out_buffer, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &in_buffer);
 
        if (start_active_slot(slot)) {
                run_active_slot(slot);
@@ -1682,21 +1671,15 @@ static int delete_remote_branch(const char *pattern, int force)
 
 static void run_request_queue(void)
 {
-#ifdef USE_CURL_MULTI
        is_running_queue = 1;
        fill_active_slots();
        add_fill_function(NULL, fill_active_slot);
-#endif
        do {
                finish_all_active_slots();
-#ifdef USE_CURL_MULTI
                fill_active_slots();
-#endif
        } while (request_queue_head && !aborted);
 
-#ifdef USE_CURL_MULTI
        is_running_queue = 0;
-#endif
 }
 
 int cmd_main(int argc, const char **argv)
@@ -1770,10 +1753,6 @@ int cmd_main(int argc, const char **argv)
                break;
        }
 
-#ifndef USE_CURL_MULTI
-       die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI");
-#endif
-
        if (!repo->url)
                usage(http_push_usage);
 
@@ -1786,9 +1765,7 @@ int cmd_main(int argc, const char **argv)
 
        http_init(NULL, repo->url, 1);
 
-#ifdef USE_CURL_MULTI
        is_running_queue = 0;
-#endif
 
        /* Verify DAV compliance/lock support */
        if (!locking_available()) {
index 90d8ecb57ef8629e1691f7e785fea1446c09017c..910fae539b89e6aea2af299b61dd64ff645b3578 100644 (file)
@@ -127,7 +127,6 @@ static void release_object_request(struct object_request *obj_req)
        free(obj_req);
 }
 
-#ifdef USE_CURL_MULTI
 static int fill_active_slot(struct walker *walker)
 {
        struct object_request *obj_req;
@@ -146,7 +145,6 @@ static int fill_active_slot(struct walker *walker)
        }
        return 0;
 }
-#endif
 
 static void prefetch(struct walker *walker, unsigned char *sha1)
 {
@@ -163,10 +161,8 @@ static void prefetch(struct walker *walker, unsigned char *sha1)
        http_is_verbose = walker->get_verbosely;
        list_add_tail(&newreq->node, &object_queue_head);
 
-#ifdef USE_CURL_MULTI
        fill_active_slots();
        step_active_slots();
-#endif
 }
 
 static int is_alternate_allowed(const char *url)
@@ -357,11 +353,9 @@ static void fetch_alternates(struct walker *walker, const char *base)
         * wait for them to arrive and return to processing this request's
         * curl message
         */
-#ifdef USE_CURL_MULTI
        while (cdata->got_alternates == 0) {
                step_active_slots();
        }
-#endif
 
        /* Nothing to do if they've already been fetched */
        if (cdata->got_alternates == 1)
@@ -384,7 +378,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
        alt_req.walker = walker;
        slot->callback_data = &alt_req;
 
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf);
 
@@ -505,12 +499,8 @@ static int fetch_object(struct walker *walker, unsigned char *hash)
                return 0;
        }
 
-#ifdef USE_CURL_MULTI
        while (obj_req->state == WAITING)
                step_active_slots();
-#else
-       start_object_request(walker, obj_req);
-#endif
 
        /*
         * obj_req->req might change when fetching alternates in the callback
@@ -623,9 +613,7 @@ struct walker *get_http_walker(const char *url)
        walker->cleanup = cleanup;
        walker->data = data;
 
-#ifdef USE_CURL_MULTI
        add_fill_function(walker, (int (*)(void *)) fill_active_slot);
-#endif
 
        return walker;
 }
diff --git a/http.c b/http.c
index fdb6e227549d7a89f5c6204eb9b2a18534687ca0..f92859f43fa53e0352b239ca43a769c5f9ff4aae 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,4 +1,5 @@
 #include "git-compat-util.h"
+#include "git-curl-compat.h"
 #include "http.h"
 #include "config.h"
 #include "pack.h"
 static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
 static int trace_curl_data = 1;
 static int trace_curl_redact = 1;
-#if LIBCURL_VERSION_NUM >= 0x070a08
 long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
-#else
-long int git_curl_ipresolve;
-#endif
 int active_requests;
 int http_is_verbose;
 ssize_t http_post_buffer = 16 * LARGE_PACKET_MAX;
 
-#if LIBCURL_VERSION_NUM >= 0x070a06
-#define LIBCURL_CAN_HANDLE_AUTH_ANY
-#endif
-
 static int min_curl_sessions = 1;
 static int curl_session_count;
-#ifdef USE_CURL_MULTI
 static int max_requests = -1;
 static CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
 static CURL *curl_default;
-#endif
 
 #define PREV_BUF_SIZE 4096
 
@@ -59,25 +48,19 @@ static struct {
        { "sslv2", CURL_SSLVERSION_SSLv2 },
        { "sslv3", CURL_SSLVERSION_SSLv3 },
        { "tlsv1", CURL_SSLVERSION_TLSv1 },
-#if LIBCURL_VERSION_NUM >= 0x072200
+#ifdef GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_0
        { "tlsv1.0", CURL_SSLVERSION_TLSv1_0 },
        { "tlsv1.1", CURL_SSLVERSION_TLSv1_1 },
        { "tlsv1.2", CURL_SSLVERSION_TLSv1_2 },
 #endif
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURL_SSLVERSION_TLSv1_3
        { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 },
 #endif
 };
-#if LIBCURL_VERSION_NUM >= 0x070903
 static const char *ssl_key;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
 static const char *ssl_capath;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x071304
 static const char *curl_no_proxy;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
 static const char *ssl_pinnedkey;
 #endif
 static const char *ssl_cainfo;
@@ -101,9 +84,7 @@ static struct {
        { "digest", CURLAUTH_DIGEST },
        { "negotiate", CURLAUTH_GSSNEGOTIATE },
        { "ntlm", CURLAUTH_NTLM },
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
        { "anyauth", CURLAUTH_ANY },
-#endif
        /*
         * CURLAUTH_DIGEST_IE has no corresponding command-line option in
         * curl(1) and is not included in CURLAUTH_ANY, so we leave it out
@@ -133,27 +114,15 @@ static int curl_empty_auth = -1;
 
 enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
 
-#if LIBCURL_VERSION_NUM >= 0x071700
-/* Use CURLOPT_KEYPASSWD as is */
-#elif LIBCURL_VERSION_NUM >= 0x070903
-#define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD
-#else
-#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
-#endif
-
 static struct credential cert_auth = CREDENTIAL_INIT;
 static int ssl_cert_password_required;
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
 static unsigned long http_auth_methods = CURLAUTH_ANY;
 static int http_auth_methods_restricted;
 /* Modes for which empty_auth cannot actually help us. */
 static unsigned long empty_auth_useless =
        CURLAUTH_BASIC
-#ifdef CURLAUTH_DIGEST_IE
        | CURLAUTH_DIGEST_IE
-#endif
        | CURLAUTH_DIGEST;
-#endif
 
 static struct curl_slist *pragma_header;
 static struct curl_slist *no_pragma_header;
@@ -186,7 +155,6 @@ size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
        return size / eltsize;
 }
 
-#ifndef NO_CURL_IOCTL
 curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
 {
        struct buffer *buffer = clientp;
@@ -203,7 +171,6 @@ curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
                return CURLIOE_UNKNOWNCMD;
        }
 }
-#endif
 
 size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
 {
@@ -237,12 +204,8 @@ static void finish_active_slot(struct active_request_slot *slot)
        if (slot->results != NULL) {
                slot->results->curl_result = slot->curl_result;
                slot->results->http_code = slot->http_code;
-#if LIBCURL_VERSION_NUM >= 0x070a08
                curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
                                  &slot->results->auth_avail);
-#else
-               slot->results->auth_avail = 0;
-#endif
 
                curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CONNECTCODE,
                        &slot->results->http_connectcode);
@@ -255,12 +218,9 @@ static void finish_active_slot(struct active_request_slot *slot)
 
 static void xmulti_remove_handle(struct active_request_slot *slot)
 {
-#ifdef USE_CURL_MULTI
        curl_multi_remove_handle(curlm, slot->curl);
-#endif
 }
 
-#ifdef USE_CURL_MULTI
 static void process_curl_messages(void)
 {
        int num_messages;
@@ -288,7 +248,6 @@ static void process_curl_messages(void)
                curl_message = curl_multi_info_read(curlm, &num_messages);
        }
 }
-#endif
 
 static int http_options(const char *var, const char *value, void *cb)
 {
@@ -305,14 +264,10 @@ static int http_options(const char *var, const char *value, void *cb)
                return git_config_string(&ssl_version, var, value);
        if (!strcmp("http.sslcert", var))
                return git_config_pathname(&ssl_cert, var, value);
-#if LIBCURL_VERSION_NUM >= 0x070903
        if (!strcmp("http.sslkey", var))
                return git_config_pathname(&ssl_key, var, value);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
        if (!strcmp("http.sslcapath", var))
                return git_config_pathname(&ssl_capath, var, value);
-#endif
        if (!strcmp("http.sslcainfo", var))
                return git_config_pathname(&ssl_cainfo, var, value);
        if (!strcmp("http.sslcertpasswordprotected", var)) {
@@ -341,18 +296,14 @@ static int http_options(const char *var, const char *value, void *cb)
 
        if (!strcmp("http.minsessions", var)) {
                min_curl_sessions = git_config_int(var, value);
-#ifndef USE_CURL_MULTI
                if (min_curl_sessions > 1)
                        min_curl_sessions = 1;
-#endif
                return 0;
        }
-#ifdef USE_CURL_MULTI
        if (!strcmp("http.maxrequests", var)) {
                max_requests = git_config_int(var, value);
                return 0;
        }
-#endif
        if (!strcmp("http.lowspeedlimit", var)) {
                curl_low_speed_limit = (long)git_config_int(var, value);
                return 0;
@@ -423,10 +374,10 @@ static int http_options(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp("http.pinnedpubkey", var)) {
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
                return git_config_pathname(&ssl_pinnedkey, var, value);
 #else
-               warning(_("Public key pinning not supported with cURL < 7.44.0"));
+               warning(_("Public key pinning not supported with cURL < 7.39.0"));
                return 0;
 #endif
        }
@@ -461,12 +412,6 @@ static int curl_empty_auth_enabled(void)
        if (curl_empty_auth >= 0)
                return curl_empty_auth;
 
-#ifndef LIBCURL_CAN_HANDLE_AUTH_ANY
-       /*
-        * Our libcurl is too old to do AUTH_ANY in the first place;
-        * just default to turning the feature off.
-        */
-#else
        /*
         * In the automatic case, kick in the empty-auth
         * hack as long as we would potentially try some
@@ -479,7 +424,6 @@ static int curl_empty_auth_enabled(void)
        if (http_auth_methods_restricted &&
            (http_auth_methods & ~empty_auth_useless))
                return 1;
-#endif
        return 0;
 }
 
@@ -493,24 +437,8 @@ static void init_curl_http_auth(CURL *result)
 
        credential_fill(&http_auth);
 
-#if LIBCURL_VERSION_NUM >= 0x071301
        curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username);
        curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password);
-#else
-       {
-               static struct strbuf up = STRBUF_INIT;
-               /*
-                * Note that we assume we only ever have a single set of
-                * credentials in a given program run, so we do not have
-                * to worry about updating this buffer, only setting its
-                * initial value.
-                */
-               if (!up.len)
-                       strbuf_addf(&up, "%s:%s",
-                               http_auth.username, http_auth.password);
-               curl_easy_setopt(result, CURLOPT_USERPWD, up.buf);
-       }
-#endif
 }
 
 /* *var must be free-able */
@@ -524,22 +452,10 @@ static void var_override(const char **var, char *value)
 
 static void set_proxyauth_name_password(CURL *result)
 {
-#if LIBCURL_VERSION_NUM >= 0x071301
                curl_easy_setopt(result, CURLOPT_PROXYUSERNAME,
                        proxy_auth.username);
                curl_easy_setopt(result, CURLOPT_PROXYPASSWORD,
                        proxy_auth.password);
-#else
-               struct strbuf s = STRBUF_INIT;
-
-               strbuf_addstr_urlencode(&s, proxy_auth.username,
-                                       is_rfc3986_unreserved);
-               strbuf_addch(&s, ':');
-               strbuf_addstr_urlencode(&s, proxy_auth.password,
-                                       is_rfc3986_unreserved);
-               curl_proxyuserpwd = strbuf_detach(&s, NULL);
-               curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd);
-#endif
 }
 
 static void init_curl_proxy_auth(CURL *result)
@@ -552,7 +468,6 @@ static void init_curl_proxy_auth(CURL *result)
 
        var_override(&http_proxy_authmethod, getenv("GIT_HTTP_PROXY_AUTHMETHOD"));
 
-#if LIBCURL_VERSION_NUM >= 0x070a07 /* CURLOPT_PROXYAUTH and CURLAUTH_ANY */
        if (http_proxy_authmethod) {
                int i;
                for (i = 0; i < ARRAY_SIZE(proxy_authmethods); i++) {
@@ -570,7 +485,6 @@ static void init_curl_proxy_auth(CURL *result)
        }
        else
                curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
-#endif
 }
 
 static int has_cert_password(void)
@@ -587,7 +501,7 @@ static int has_cert_password(void)
        return 1;
 }
 
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD
 static int has_proxy_cert_password(void)
 {
        if (http_proxy_ssl_cert == NULL || proxy_ssl_cert_password_required != 1)
@@ -603,13 +517,13 @@ static int has_proxy_cert_password(void)
 }
 #endif
 
-#if LIBCURL_VERSION_NUM >= 0x071900
+#ifdef GITCURL_HAVE_CURLOPT_TCP_KEEPALIVE
 static void set_curl_keepalive(CURL *c)
 {
        curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1);
 }
 
-#elif LIBCURL_VERSION_NUM >= 0x071000
+#else
 static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
 {
        int ka = 1;
@@ -623,19 +537,13 @@ static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
        if (rc < 0)
                warning_errno("unable to set SO_KEEPALIVE on socket");
 
-       return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */
+       return CURL_SOCKOPT_OK;
 }
 
 static void set_curl_keepalive(CURL *c)
 {
        curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
 }
-
-#else
-static void set_curl_keepalive(CURL *c)
-{
-       /* not supported on older curl versions */
-}
 #endif
 
 static void redact_sensitive_header(struct strbuf *header)
@@ -809,7 +717,6 @@ void setup_curl_trace(CURL *handle)
        curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL);
 }
 
-#ifdef CURLPROTO_HTTP
 static long get_curl_allowed_protocols(int from_user)
 {
        long allowed_protocols = 0;
@@ -825,9 +732,8 @@ static long get_curl_allowed_protocols(int from_user)
 
        return allowed_protocols;
 }
-#endif
 
-#if LIBCURL_VERSION_NUM >=0x072f00
+#ifdef GIT_CURL_HAVE_CURL_HTTP_VERSION_2
 static int get_curl_http_version_opt(const char *version_string, long *opt)
 {
        int i;
@@ -869,7 +775,7 @@ static CURL *get_curl_handle(void)
                curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
        }
 
-#if LIBCURL_VERSION_NUM >= 0x072f00 // 7.47.0
+#ifdef GIT_CURL_HAVE_CURL_HTTP_VERSION_2
     if (curl_http_version) {
                long opt;
                if (!get_curl_http_version_opt(curl_http_version, &opt)) {
@@ -879,12 +785,8 @@ static CURL *get_curl_handle(void)
     }
 #endif
 
-#if LIBCURL_VERSION_NUM >= 0x070907
        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
-#endif
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
-#endif
 
 #ifdef CURLGSSAPI_DELEGATION_FLAG
        if (curl_deleg) {
@@ -904,7 +806,7 @@ static CURL *get_curl_handle(void)
 
        if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
            !http_schannel_check_revoke) {
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLSSLOPT_NO_REVOKE
                curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
 #else
                warning(_("CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0"));
@@ -940,28 +842,24 @@ static CURL *get_curl_handle(void)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
        if (has_cert_password())
                curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
-#if LIBCURL_VERSION_NUM >= 0x070903
        if (ssl_key != NULL)
                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
        if (ssl_capath != NULL)
                curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x072c00
+#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY
        if (ssl_pinnedkey != NULL)
                curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey);
 #endif
        if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) &&
            !http_schannel_use_ssl_cainfo) {
                curl_easy_setopt(result, CURLOPT_CAINFO, NULL);
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO
                curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL);
 #endif
        } else if (ssl_cainfo != NULL || http_proxy_ssl_ca_info != NULL) {
                if (ssl_cainfo != NULL)
                        curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_CAINFO
                if (http_proxy_ssl_ca_info != NULL)
                        curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
 #endif
@@ -975,19 +873,11 @@ static CURL *get_curl_handle(void)
        }
 
        curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20);
-#if LIBCURL_VERSION_NUM >= 0x071301
        curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
-#elif LIBCURL_VERSION_NUM >= 0x071101
-       curl_easy_setopt(result, CURLOPT_POST301, 1);
-#endif
-#ifdef CURLPROTO_HTTP
        curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS,
                         get_curl_allowed_protocols(0));
        curl_easy_setopt(result, CURLOPT_PROTOCOLS,
                         get_curl_allowed_protocols(-1));
-#else
-       warning(_("Protocol restrictions not supported with cURL < 7.19.4"));
-#endif
        if (getenv("GIT_CURL_VERBOSE"))
                http_trace_curl_no_data();
        setup_curl_trace(result);
@@ -1002,10 +892,8 @@ static CURL *get_curl_handle(void)
        if (curl_ftp_no_epsv)
                curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
 
-#ifdef CURLOPT_USE_SSL
        if (curl_ssl_try)
                curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY);
-#endif
 
        /*
         * CURL also examines these variables as a fallback; but we need to query
@@ -1040,7 +928,6 @@ static CURL *get_curl_handle(void)
                 */
                curl_easy_setopt(result, CURLOPT_PROXY, "");
        } else if (curl_http_proxy) {
-#if LIBCURL_VERSION_NUM >= 0x071800
                if (starts_with(curl_http_proxy, "socks5h"))
                        curl_easy_setopt(result,
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
@@ -1053,8 +940,7 @@ static CURL *get_curl_handle(void)
                else if (starts_with(curl_http_proxy, "socks"))
                        curl_easy_setopt(result,
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x073400
+#ifdef GIT_CURL_HAVE_CURLOPT_PROXY_KEYPASSWD
                else if (starts_with(curl_http_proxy, "https")) {
                        curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
 
@@ -1081,11 +967,9 @@ static CURL *get_curl_handle(void)
                        die("Invalid proxy URL '%s'", curl_http_proxy);
 
                curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
-#if LIBCURL_VERSION_NUM >= 0x071304
                var_override(&curl_no_proxy, getenv("NO_PROXY"));
                var_override(&curl_no_proxy, getenv("no_proxy"));
                curl_easy_setopt(result, CURLOPT_NOPROXY, curl_no_proxy);
-#endif
        }
        init_curl_proxy_auth(result);
 
@@ -1106,7 +990,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        char *low_speed_limit;
        char *low_speed_time;
        char *normalized_url;
-       struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+       struct urlmatch_config config = URLMATCH_CONFIG_INIT;
 
        config.section = "http";
        config.key = NULL;
@@ -1121,7 +1005,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        free(normalized_url);
        string_list_clear(&config.vars, 1);
 
-#if LIBCURL_VERSION_NUM >= 0x073800
+#ifdef GIT_CURL_HAVE_CURLSSLSET_NO_BACKENDS
        if (http_ssl_backend) {
                const curl_ssl_backend **backends;
                struct strbuf buf = STRBUF_INIT;
@@ -1164,7 +1048,6 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma:");
 
-#ifdef USE_CURL_MULTI
        {
                char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
                if (http_max_requests != NULL)
@@ -1174,18 +1057,13 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
        curlm = curl_multi_init();
        if (!curlm)
                die("curl_multi_init failed");
-#endif
 
        if (getenv("GIT_SSL_NO_VERIFY"))
                curl_ssl_verify = 0;
 
        set_from_env(&ssl_cert, "GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070903
        set_from_env(&ssl_key, "GIT_SSL_KEY");
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
        set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
-#endif
        set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
 
        set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
@@ -1201,10 +1079,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
                curl_ssl_verify = 1;
 
        curl_session_count = 0;
-#ifdef USE_CURL_MULTI
        if (max_requests < 1)
                max_requests = DEFAULT_MAX_REQUESTS;
-#endif
 
        set_from_env(&http_proxy_ssl_cert, "GIT_PROXY_SSL_CERT");
        set_from_env(&http_proxy_ssl_key, "GIT_PROXY_SSL_KEY");
@@ -1224,9 +1100,7 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
                        ssl_cert_password_required = 1;
        }
 
-#ifndef NO_CURL_EASY_DUPHANDLE
        curl_default = get_curl_handle();
-#endif
 }
 
 void http_cleanup(void)
@@ -1244,13 +1118,9 @@ void http_cleanup(void)
        }
        active_queue_head = NULL;
 
-#ifndef NO_CURL_EASY_DUPHANDLE
        curl_easy_cleanup(curl_default);
-#endif
 
-#ifdef USE_CURL_MULTI
        curl_multi_cleanup(curlm);
-#endif
        curl_global_cleanup();
 
        string_list_clear(&extra_http_headers, 0);
@@ -1297,7 +1167,6 @@ struct active_request_slot *get_active_slot(void)
        struct active_request_slot *slot = active_queue_head;
        struct active_request_slot *newslot;
 
-#ifdef USE_CURL_MULTI
        int num_transfers;
 
        /* Wait for a slot to open up if the queue is full */
@@ -1306,7 +1175,6 @@ struct active_request_slot *get_active_slot(void)
                if (num_transfers < active_requests)
                        process_curl_messages();
        }
-#endif
 
        while (slot != NULL && slot->in_use)
                slot = slot->next;
@@ -1329,11 +1197,7 @@ struct active_request_slot *get_active_slot(void)
        }
 
        if (slot->curl == NULL) {
-#ifdef NO_CURL_EASY_DUPHANDLE
-               slot->curl = get_curl_handle();
-#else
                slot->curl = curl_easy_duphandle(curl_default);
-#endif
                curl_session_count++;
        }
 
@@ -1367,12 +1231,8 @@ struct active_request_slot *get_active_slot(void)
        else
                curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 0);
 
-#if LIBCURL_VERSION_NUM >= 0x070a08
        curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve);
-#endif
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
        curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods);
-#endif
        if (http_auth.password || curl_empty_auth_enabled())
                init_curl_http_auth(slot->curl);
 
@@ -1381,7 +1241,6 @@ struct active_request_slot *get_active_slot(void)
 
 int start_active_slot(struct active_request_slot *slot)
 {
-#ifdef USE_CURL_MULTI
        CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
        int num_transfers;
 
@@ -1399,11 +1258,9 @@ int start_active_slot(struct active_request_slot *slot)
         * something.
         */
        curl_multi_perform(curlm, &num_transfers);
-#endif
        return 1;
 }
 
-#ifdef USE_CURL_MULTI
 struct fill_chain {
        void *data;
        int (*fill)(void *);
@@ -1462,11 +1319,9 @@ void step_active_slots(void)
                fill_active_slots();
        }
 }
-#endif
 
 void run_active_slot(struct active_request_slot *slot)
 {
-#ifdef USE_CURL_MULTI
        fd_set readfds;
        fd_set writefds;
        fd_set excfds;
@@ -1479,7 +1334,6 @@ void run_active_slot(struct active_request_slot *slot)
                step_active_slots();
 
                if (slot->in_use) {
-#if LIBCURL_VERSION_NUM >= 0x070f04
                        long curl_timeout;
                        curl_multi_timeout(curlm, &curl_timeout);
                        if (curl_timeout == 0) {
@@ -1491,10 +1345,6 @@ void run_active_slot(struct active_request_slot *slot)
                                select_timeout.tv_sec  =  curl_timeout / 1000;
                                select_timeout.tv_usec = (curl_timeout % 1000) * 1000;
                        }
-#else
-                       select_timeout.tv_sec  = 0;
-                       select_timeout.tv_usec = 50000;
-#endif
 
                        max_fd = -1;
                        FD_ZERO(&readfds);
@@ -1517,12 +1367,6 @@ void run_active_slot(struct active_request_slot *slot)
                        select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
                }
        }
-#else
-       while (slot->in_use) {
-               slot->curl_result = curl_easy_perform(slot->curl);
-               finish_active_slot(slot);
-       }
-#endif
 }
 
 static void release_active_slot(struct active_request_slot *slot)
@@ -1536,9 +1380,7 @@ static void release_active_slot(struct active_request_slot *slot)
                        curl_session_count--;
                }
        }
-#ifdef USE_CURL_MULTI
        fill_active_slots();
-#endif
 }
 
 void finish_all_active_slots(void)
@@ -1647,6 +1489,10 @@ static int handle_curl_result(struct slot_results *results)
                 */
                credential_reject(&cert_auth);
                return HTTP_NOAUTH;
+#ifdef GIT_CURL_HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH
+       } else if (results->curl_result == CURLE_SSL_PINNEDPUBKEYNOTMATCH) {
+               return HTTP_NOMATCHPUBLICKEY;
+#endif
        } else if (missing_target(results))
                return HTTP_MISSING_TARGET;
        else if (results->http_code == 401) {
@@ -1654,24 +1500,20 @@ static int handle_curl_result(struct slot_results *results)
                        credential_reject(&http_auth);
                        return HTTP_NOAUTH;
                } else {
-#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY
                        http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
                        if (results->auth_avail) {
                                http_auth_methods &= results->auth_avail;
                                http_auth_methods_restricted = 1;
                        }
-#endif
                        return HTTP_REAUTH;
                }
        } else {
                if (results->http_connectcode == 407)
                        credential_reject(&proxy_auth);
-#if LIBCURL_VERSION_NUM >= 0x070c00
                if (!curl_errorstr[0])
                        strlcpy(curl_errorstr,
                                curl_easy_strerror(results->curl_result),
                                sizeof(curl_errorstr));
-#endif
                return HTTP_ERROR;
        }
 }
@@ -1930,7 +1772,7 @@ static int http_request(const char *url,
                curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
        } else {
                curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
-               curl_easy_setopt(slot->curl, CURLOPT_FILE, result);
+               curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, result);
 
                if (target == HTTP_REQUEST_FILE) {
                        off_t posn = ftello(result);
@@ -2347,7 +2189,7 @@ struct http_pack_request *new_direct_http_pack_request(
        }
 
        preq->slot = get_active_slot();
-       curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
+       curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEDATA, preq->packfile);
        curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
        curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
@@ -2518,7 +2360,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
 
        freq->slot = get_active_slot();
 
-       curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq);
+       curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEDATA, freq);
        curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0);
        curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
        curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr);
diff --git a/http.h b/http.h
index bf3d1270ad8e2f0bd11b76baa183987f7b44d6f7..df1590e53a455787a2d4d28a7896cabf8ac15419 100644 (file)
--- a/http.h
+++ b/http.h
 #include "remote.h"
 #include "url.h"
 
-/*
- * We detect based on the cURL version if multi-transfer is
- * usable in this implementation and define this symbol accordingly.
- * This shouldn't be set by the Makefile or by the user (e.g. via CFLAGS).
- */
-#undef USE_CURL_MULTI
-
-#if LIBCURL_VERSION_NUM >= 0x071000
-#define USE_CURL_MULTI
 #define DEFAULT_MAX_REQUESTS 5
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while (0)
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while (0)
-#elif LIBCURL_VERSION_NUM >= 0x070c00
-#define curl_global_init(a) curl_global_init_mem(a, xmalloc, free, \
-                                               xrealloc, xstrdup, xcalloc)
-#endif
-
-#if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
-#define NO_CURL_EASY_DUPHANDLE
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070a03
-#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070c03
-#define NO_CURL_IOCTL
-#endif
-
-/*
- * CURLOPT_USE_SSL was known as CURLOPT_FTP_SSL up to 7.16.4,
- * and the constants were known as CURLFTPSSL_*
-*/
-#if !defined(CURLOPT_USE_SSL) && defined(CURLOPT_FTP_SSL)
-#define CURLOPT_USE_SSL CURLOPT_FTP_SSL
-#define CURLUSESSL_TRY CURLFTPSSL_TRY
-#endif
 
 struct slot_results {
        CURLcode curl_result;
@@ -82,9 +40,7 @@ struct buffer {
 size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
 size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
 size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf);
-#ifndef NO_CURL_IOCTL
 curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
-#endif
 
 /* Slot lifecycle functions */
 struct active_request_slot *get_active_slot(void);
@@ -101,11 +57,9 @@ void finish_all_active_slots(void);
 int run_one_slot(struct active_request_slot *slot,
                 struct slot_results *results);
 
-#ifdef USE_CURL_MULTI
 void fill_active_slots(void);
 void add_fill_function(void *data, int (*fill)(void *));
 void step_active_slots(void);
-#endif
 
 void http_init(struct remote *remote, const char *url,
               int proactive_auth);
@@ -200,6 +154,7 @@ struct http_get_options {
 #define HTTP_START_FAILED      3
 #define HTTP_REAUTH    4
 #define HTTP_NOAUTH    5
+#define HTTP_NOMATCHPUBLICKEY  6
 
 /*
  * Requests a URL and stores the result in a strbuf.
index a0540ba5cf43e5af6c749f8c1bc7b06a7ff222ad..e6090a0346ad4947791a04ffce71ba1d0a8e8e28 100644 (file)
@@ -1441,7 +1441,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
        curl_easy_setopt(curl, CURLOPT_PORT, server.port);
 
        if (server.auth_method) {
-#if LIBCURL_VERSION_NUM < 0x072200
+#ifndef GIT_CURL_HAVE_CURLOPT_LOGIN_OPTIONS
                warning("No LOGIN_OPTIONS support in this cURL version");
 #else
                struct strbuf auth = STRBUF_INIT;
@@ -1517,11 +1517,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,
        if (cred.username) {
                if (res == CURLE_OK)
                        credential_approve(&cred);
-#if LIBCURL_VERSION_NUM >= 0x070d01
                else if (res == CURLE_LOGIN_DENIED)
-#else
-               else
-#endif
                        credential_reject(&cred);
        }
 
index 473a3324169066e0e569509192960e81098a8771..2f623f8211534dabe732b1da140a2512a5a5fcf5 100644 (file)
@@ -337,8 +337,8 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
        add_pending_object(revs, &tree->object, "");
 }
 
-static void traverse_trees_and_blobs(struct traversal_context *ctx,
-                                    struct strbuf *base)
+static void traverse_non_commits(struct traversal_context *ctx,
+                                struct strbuf *base)
 {
        int i;
 
@@ -410,9 +410,9 @@ static void do_traverse(struct traversal_context *ctx)
                         * needs a reallocation for each commit. Can we pass the
                         * tree directory without allocation churn?
                         */
-                       traverse_trees_and_blobs(ctx, &csp);
+                       traverse_non_commits(ctx, &csp);
        }
-       traverse_trees_and_blobs(ctx, &csp);
+       traverse_non_commits(ctx, &csp);
        strbuf_release(&csp);
 }
 
diff --git a/list.h b/list.h
index eb601192f4ca9a6af126c82f4c2a24cb9145009d..362a4cd7f5f10f17f174086911dc30e60e07e6ec 100644 (file)
--- a/list.h
+++ b/list.h
@@ -46,7 +46,10 @@ struct list_head {
 #define INIT_LIST_HEAD(ptr) \
        (ptr)->next = (ptr)->prev = (ptr)
 
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
+#define LIST_HEAD_INIT(name) { \
+       .next = &(name), \
+       .prev = &(name), \
+}
 
 /* Add new element at the head of the list. */
 static inline void list_add(struct list_head *newp, struct list_head *head)
index db93e6ba73e68ddcd114da0c75e5217a33b5a395..90af4e66b28c8f338cb62cf228a2b8d80000e8a8 100644 (file)
@@ -121,7 +121,7 @@ struct lock_file {
        struct tempfile *tempfile;
 };
 
-#define LOCK_INIT { NULL }
+#define LOCK_INIT { 0 }
 
 /* String appended to a filename to derive the lockfile name: */
 #define LOCK_SUFFIX ".lock"
index 6dc4412268b8ae34f215e066bd94a1f2ddad1ce8..644893fd8cfff6a9ee9cda0b512c2adb9c8a6953 100644 (file)
@@ -515,10 +515,10 @@ static void show_signature(struct rev_info *opt, struct commit *commit)
 
        status = check_signature(payload.buf, payload.len, signature.buf,
                                 signature.len, &sigc);
-       if (status && !sigc.gpg_output)
+       if (status && !sigc.output)
                show_sig_lines(opt, status, "No signature\n");
        else
-               show_sig_lines(opt, status, sigc.gpg_output);
+               show_sig_lines(opt, status, sigc.output);
        signature_check_clear(&sigc);
 
  out:
@@ -585,8 +585,8 @@ static int show_one_mergetag(struct commit *commit,
                /* could have a good signature */
                status = check_signature(payload.buf, payload.len,
                                         signature.buf, signature.len, &sigc);
-               if (sigc.gpg_output)
-                       strbuf_addstr(&verify_message, sigc.gpg_output);
+               if (sigc.output)
+                       strbuf_addstr(&verify_message, sigc.output);
                else
                        strbuf_addstr(&verify_message, "No signature\n");
                signature_check_clear(&sigc);
index 1e8c91dbf21f365efcb2b4e966ba6e1cff23ad95..e7e4641cf83c5b7cfd4457b54a88115723ac979d 100644 (file)
@@ -14,10 +14,8 @@ struct decoration_filter {
 };
 
 int parse_decorate_color_config(const char *var, const char *slot_name, const char *value);
-void init_log_tree_opt(struct rev_info *);
 int log_tree_diff_flush(struct rev_info *);
 int log_tree_commit(struct rev_info *, struct commit *);
-int log_tree_opt_parse(struct rev_info *, const char **, int);
 void show_log(struct rev_info *opt);
 void format_decorations_extended(struct strbuf *sb, const struct commit *commit,
                             int use_color,
index 88f6c3f60d8ef61373a1bfed33f49c877b6af320..54078323dcb92b68463e6c16b23621ae8f47a2f1 100644 (file)
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -40,6 +40,12 @@ static void ensure_config_read(void)
        config_read = 1;
 }
 
+/*
+ * If we see this many or more "ref-prefix" lines from the client, we consider
+ * it "too many" and will avoid using the prefix feature entirely.
+ */
+#define TOO_MANY_PREFIXES 65536
+
 /*
  * Check if one of the prefixes is a prefix of the ref.
  * If no prefixes were provided, all refs match.
@@ -65,6 +71,7 @@ struct ls_refs_data {
        unsigned peel;
        unsigned symrefs;
        struct strvec prefixes;
+       struct strbuf buf;
        unsigned unborn : 1;
 };
 
@@ -73,7 +80,8 @@ static int send_ref(const char *refname, const struct object_id *oid,
 {
        struct ls_refs_data *data = cb_data;
        const char *refname_nons = strip_namespace(refname);
-       struct strbuf refline = STRBUF_INIT;
+
+       strbuf_reset(&data->buf);
 
        if (ref_is_hidden(refname_nons, refname))
                return 0;
@@ -82,9 +90,9 @@ static int send_ref(const char *refname, const struct object_id *oid,
                return 0;
 
        if (oid)
-               strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
+               strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons);
        else
-               strbuf_addf(&refline, "unborn %s", refname_nons);
+               strbuf_addf(&data->buf, "unborn %s", refname_nons);
        if (data->symrefs && flag & REF_ISSYMREF) {
                struct object_id unused;
                const char *symref_target = resolve_ref_unsafe(refname, 0,
@@ -94,20 +102,19 @@ static int send_ref(const char *refname, const struct object_id *oid,
                if (!symref_target)
                        die("'%s' is a symref but it is not?", refname);
 
-               strbuf_addf(&refline, " symref-target:%s",
+               strbuf_addf(&data->buf, " symref-target:%s",
                            strip_namespace(symref_target));
        }
 
        if (data->peel && oid) {
                struct object_id peeled;
                if (!peel_iterated_oid(oid, &peeled))
-                       strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
+                       strbuf_addf(&data->buf, " peeled:%s", oid_to_hex(&peeled));
        }
 
-       strbuf_addch(&refline, '\n');
-       packet_write(1, refline.buf, refline.len);
+       strbuf_addch(&data->buf, '\n');
+       packet_fwrite(stdout, data->buf.buf, data->buf.len);
 
-       strbuf_release(&refline);
        return 0;
 }
 
@@ -138,13 +145,13 @@ static int ls_refs_config(const char *var, const char *value, void *data)
        return parse_hide_refs_config(var, value, "uploadpack");
 }
 
-int ls_refs(struct repository *r, struct strvec *keys,
-           struct packet_reader *request)
+int ls_refs(struct repository *r, struct packet_reader *request)
 {
        struct ls_refs_data data;
 
        memset(&data, 0, sizeof(data));
        strvec_init(&data.prefixes);
+       strbuf_init(&data.buf, 0);
 
        ensure_config_read();
        git_config(ls_refs_config, NULL);
@@ -157,22 +164,35 @@ int ls_refs(struct repository *r, struct strvec *keys,
                        data.peel = 1;
                else if (!strcmp("symrefs", arg))
                        data.symrefs = 1;
-               else if (skip_prefix(arg, "ref-prefix ", &out))
-                       strvec_push(&data.prefixes, out);
+               else if (skip_prefix(arg, "ref-prefix ", &out)) {
+                       if (data.prefixes.nr < TOO_MANY_PREFIXES)
+                               strvec_push(&data.prefixes, out);
+               }
                else if (!strcmp("unborn", arg))
                        data.unborn = allow_unborn;
+               else
+                       die(_("unexpected line: '%s'"), arg);
        }
 
        if (request->status != PACKET_READ_FLUSH)
                die(_("expected flush after ls-refs arguments"));
 
+       /*
+        * If we saw too many prefixes, we must avoid using them at all; as
+        * soon as we have any prefix, they are meant to form a comprehensive
+        * list.
+        */
+       if (data.prefixes.nr >= TOO_MANY_PREFIXES)
+               strvec_clear(&data.prefixes);
+
        send_possibly_unborn_head(&data);
        if (!data.prefixes.nr)
                strvec_push(&data.prefixes, "");
        for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v,
-                                    send_ref, &data, 0);
-       packet_flush(1);
+                                    send_ref, &data);
+       packet_fflush(stdout);
        strvec_clear(&data.prefixes);
+       strbuf_release(&data.buf);
        return 0;
 }
 
index a99e4be0bdde1b287807459dc40934ef4459b227..e2243a547bb0499bef364ff080cd4e09759eb2f6 100644 (file)
--- a/ls-refs.h
+++ b/ls-refs.h
@@ -2,10 +2,8 @@
 #define LS_REFS_H
 
 struct repository;
-struct strvec;
 struct packet_reader;
-int ls_refs(struct repository *r, struct strvec *keys,
-           struct packet_reader *request);
+int ls_refs(struct repository *r, struct packet_reader *request);
 int ls_refs_advertise(struct repository *r, struct strbuf *value);
 
 #endif /* LS_REFS_H */
index b346e23ff2cbeb3ea1ced62b80419af5ed613071..0342f104836b69a7889b3fa686c7c359c27e5dd6 100644 (file)
@@ -32,6 +32,7 @@
 #include "promisor-remote.h"
 #include "revision.h"
 #include "strmap.h"
+#include "submodule-config.h"
 #include "submodule.h"
 #include "tree.h"
 #include "unpack-trees.h"
@@ -303,8 +304,6 @@ struct merge_options_internal {
         *   * these keys serve to intern all the path strings, which allows
         *     us to do pointer comparison on directory names instead of
         *     strcmp; we just have to be careful to use the interned strings.
-        *     (Technically paths_to_free may track some strings that were
-        *      removed from froms paths.)
         *
         * The values of paths:
         *   * either a pointer to a merged_info, or a conflict_info struct
@@ -340,14 +339,14 @@ struct merge_options_internal {
        struct strmap conflicted;
 
        /*
-        * paths_to_free: additional list of strings to free
+        * pool: memory pool for fast allocation/deallocation
         *
-        * If keys are removed from "paths", they are added to paths_to_free
-        * to ensure they are later freed.  We avoid free'ing immediately since
-        * other places (e.g. conflict_info.pathnames[]) may still be
-        * referencing these paths.
+        * We allocate room for lots of filenames and auxiliary data
+        * structures in merge_options_internal, and it tends to all be
+        * freed together too.  Using a memory pool for these provides a
+        * nice speedup.
         */
-       struct string_list paths_to_free;
+       struct mem_pool pool;
 
        /*
         * output: special messages and conflict notices for various paths
@@ -519,64 +518,45 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
 {
        struct rename_info *renames = &opti->renames;
        int i;
-       void (*strmap_func)(struct strmap *, int) =
+       void (*strmap_clear_func)(struct strmap *, int) =
                reinitialize ? strmap_partial_clear : strmap_clear;
-       void (*strintmap_func)(struct strintmap *) =
+       void (*strintmap_clear_func)(struct strintmap *) =
                reinitialize ? strintmap_partial_clear : strintmap_clear;
-       void (*strset_func)(struct strset *) =
+       void (*strset_clear_func)(struct strset *) =
                reinitialize ? strset_partial_clear : strset_clear;
 
-       /*
-        * We marked opti->paths with strdup_strings = 0, so that we
-        * wouldn't have to make another copy of the fullpath created by
-        * make_traverse_path from setup_path_info().  But, now that we've
-        * used it and have no other references to these strings, it is time
-        * to deallocate them.
-        */
-       free_strmap_strings(&opti->paths);
-       strmap_func(&opti->paths, 1);
+       strmap_clear_func(&opti->paths, 0);
 
        /*
         * All keys and values in opti->conflicted are a subset of those in
         * opti->paths.  We don't want to deallocate anything twice, so we
         * don't free the keys and we pass 0 for free_values.
         */
-       strmap_func(&opti->conflicted, 0);
-
-       /*
-        * opti->paths_to_free is similar to opti->paths; we created it with
-        * strdup_strings = 0 to avoid making _another_ copy of the fullpath
-        * but now that we've used it and have no other references to these
-        * strings, it is time to deallocate them.  We do so by temporarily
-        * setting strdup_strings to 1.
-        */
-       opti->paths_to_free.strdup_strings = 1;
-       string_list_clear(&opti->paths_to_free, 0);
-       opti->paths_to_free.strdup_strings = 0;
+       strmap_clear_func(&opti->conflicted, 0);
 
        if (opti->attr_index.cache_nr) /* true iff opt->renormalize */
                discard_index(&opti->attr_index);
 
        /* Free memory used by various renames maps */
        for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
-               strintmap_func(&renames->dirs_removed[i]);
-               strmap_func(&renames->dir_renames[i], 0);
-               strintmap_func(&renames->relevant_sources[i]);
+               strintmap_clear_func(&renames->dirs_removed[i]);
+               strmap_clear_func(&renames->dir_renames[i], 0);
+               strintmap_clear_func(&renames->relevant_sources[i]);
                if (!reinitialize)
                        assert(renames->cached_pairs_valid_side == 0);
                if (i != renames->cached_pairs_valid_side &&
                    -1 != renames->cached_pairs_valid_side) {
-                       strset_func(&renames->cached_target_names[i]);
-                       strmap_func(&renames->cached_pairs[i], 1);
-                       strset_func(&renames->cached_irrelevant[i]);
+                       strset_clear_func(&renames->cached_target_names[i]);
+                       strmap_clear_func(&renames->cached_pairs[i], 1);
+                       strset_clear_func(&renames->cached_irrelevant[i]);
                        partial_clear_dir_rename_count(&renames->dir_rename_count[i]);
                        if (!reinitialize)
                                strmap_clear(&renames->dir_rename_count[i], 1);
                }
        }
        for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
-               strintmap_func(&renames->deferred[i].possible_trivial_merges);
-               strset_func(&renames->deferred[i].target_dirs);
+               strintmap_clear_func(&renames->deferred[i].possible_trivial_merges);
+               strset_clear_func(&renames->deferred[i].target_dirs);
                renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */
        }
        renames->cached_pairs_valid_side = 0;
@@ -603,6 +583,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
                strmap_clear(&opti->output, 0);
        }
 
+       mem_pool_discard(&opti->pool, 0);
+
        /* Clean out callback_data as well. */
        FREE_AND_NULL(renames->callback_data);
        renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -627,6 +609,7 @@ static int err(struct merge_options *opt, const char *err, ...)
 
 static void format_commit(struct strbuf *sb,
                          int indent,
+                         struct repository *repo,
                          struct commit *commit)
 {
        struct merge_remote_desc *desc;
@@ -640,7 +623,7 @@ static void format_commit(struct strbuf *sb,
                return;
        }
 
-       format_commit_message(commit, "%h %s", sb, &ctx);
+       repo_format_commit_message(repo, commit, "%h %s", sb, &ctx);
        strbuf_addch(sb, '\n');
 }
 
@@ -665,6 +648,36 @@ static void path_msg(struct merge_options *opt,
        strbuf_addch(sb, '\n');
 }
 
+static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool,
+                                                const char *path)
+{
+       /* Similar to alloc_filespec(), but allocate from pool and reuse path */
+       struct diff_filespec *spec;
+
+       spec = mem_pool_calloc(pool, 1, sizeof(*spec));
+       spec->path = (char*)path; /* spec won't modify it */
+
+       spec->count = 1;
+       spec->is_binary = -1;
+       return spec;
+}
+
+static struct diff_filepair *pool_diff_queue(struct mem_pool *pool,
+                                            struct diff_queue_struct *queue,
+                                            struct diff_filespec *one,
+                                            struct diff_filespec *two)
+{
+       /* Same code as diff_queue(), except allocate from pool */
+       struct diff_filepair *dp;
+
+       dp = mem_pool_calloc(pool, 1, sizeof(*dp));
+       dp->one = one;
+       dp->two = two;
+       if (queue)
+               diff_q(queue, dp);
+       return dp;
+}
+
 /* add a string to a strbuf, but converting "/" to "_" */
 static void add_flattened_path(struct strbuf *out, const char *s)
 {
@@ -793,8 +806,9 @@ static void setup_path_info(struct merge_options *opt,
        assert(!df_conflict || !resolved); /* df_conflict implies !resolved */
        assert(resolved == (merged_version != NULL));
 
-       mi = xcalloc(1, resolved ? sizeof(struct merged_info) :
-                                  sizeof(struct conflict_info));
+       mi = mem_pool_calloc(&opt->priv->pool, 1,
+                            resolved ? sizeof(struct merged_info) :
+                                       sizeof(struct conflict_info));
        mi->directory_name = current_dir_name;
        mi->basename_offset = current_dir_name_len;
        mi->clean = !!resolved;
@@ -891,11 +905,11 @@ static void add_pair(struct merge_options *opt,
                        return;
        }
 
-       one = alloc_filespec(pathname);
-       two = alloc_filespec(pathname);
+       one = pool_alloc_filespec(&opt->priv->pool, pathname);
+       two = pool_alloc_filespec(&opt->priv->pool, pathname);
        fill_filespec(is_add ? two : one,
                      &names[names_idx].oid, 1, names[names_idx].mode);
-       diff_queue(&renames->pairs[side], one, two);
+       pool_diff_queue(&opt->priv->pool, &renames->pairs[side], one, two);
 }
 
 static void collect_rename_info(struct merge_options *opt,
@@ -1086,7 +1100,7 @@ static int collect_merge_info_callback(int n,
        len = traverse_path_len(info, p->pathlen);
 
        /* +1 in both of the following lines to include the NUL byte */
-       fullpath = xmalloc(len + 1);
+       fullpath = mem_pool_alloc(&opt->priv->pool, len + 1);
        make_traverse_path(fullpath, len + 1, info, p->path, p->pathlen);
 
        /*
@@ -1341,7 +1355,7 @@ static int handle_deferred_entries(struct merge_options *opt,
                copy = renames->deferred[side].possible_trivial_merges;
                strintmap_init_with_options(&renames->deferred[side].possible_trivial_merges,
                                            0,
-                                           NULL,
+                                           &opt->priv->pool,
                                            0);
                strintmap_for_each_entry(&copy, &iter, entry) {
                        const char *path = entry->key;
@@ -1499,7 +1513,6 @@ static int find_first_merges(struct repository *repo,
        xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
                  oid_to_hex(&a->object.oid));
        repo_init_revisions(repo, &revs, NULL);
-       rev_opts.submodule = path;
        /* FIXME: can't handle linked worktrees in submodules yet */
        revs.single_worktree = path != NULL;
        setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
@@ -1509,7 +1522,7 @@ static int find_first_merges(struct repository *repo,
                die("revision walk setup failed");
        while ((commit = get_revision(&revs)) != NULL) {
                struct object *o = &(commit->object);
-               if (in_merge_bases(b, commit))
+               if (repo_in_merge_bases(repo, b, commit))
                        add_object_array(o, NULL, &merges);
        }
        reset_revision_walk();
@@ -1524,7 +1537,7 @@ static int find_first_merges(struct repository *repo,
                contains_another = 0;
                for (j = 0; j < merges.nr; j++) {
                        struct commit *m2 = (struct commit *) merges.objects[j].item;
-                       if (i != j && in_merge_bases(m2, m1)) {
+                       if (i != j && repo_in_merge_bases(repo, m2, m1)) {
                                contains_another = 1;
                                break;
                        }
@@ -1545,10 +1558,12 @@ static int merge_submodule(struct merge_options *opt,
                           const struct object_id *b,
                           struct object_id *result)
 {
+       struct repository subrepo;
+       struct strbuf sb = STRBUF_INIT;
+       int ret = 0;
        struct commit *commit_o, *commit_a, *commit_b;
        int parent_count;
        struct object_array merges;
-       struct strbuf sb = STRBUF_INIT;
 
        int i;
        int search = !opt->priv->call_depth;
@@ -1564,46 +1579,48 @@ static int merge_submodule(struct merge_options *opt,
        if (is_null_oid(b))
                return 0;
 
-       if (add_submodule_odb(path)) {
+       if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
                path_msg(opt, path, 0,
-                        _("Failed to merge submodule %s (not checked out)"),
-                        path);
+                               _("Failed to merge submodule %s (not checked out)"),
+                               path);
                return 0;
        }
 
-       if (!(commit_o = lookup_commit_reference(opt->repo, o)) ||
-           !(commit_a = lookup_commit_reference(opt->repo, a)) ||
-           !(commit_b = lookup_commit_reference(opt->repo, b))) {
+       if (!(commit_o = lookup_commit_reference(&subrepo, o)) ||
+           !(commit_a = lookup_commit_reference(&subrepo, a)) ||
+           !(commit_b = lookup_commit_reference(&subrepo, b))) {
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s (commits not present)"),
                         path);
-               return 0;
+               goto cleanup;
        }
 
        /* check whether both changes are forward */
-       if (!in_merge_bases(commit_o, commit_a) ||
-           !in_merge_bases(commit_o, commit_b)) {
+       if (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||
+           !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s "
                           "(commits don't follow merge-base)"),
                         path);
-               return 0;
+               goto cleanup;
        }
 
        /* Case #1: a is contained in b or vice versa */
-       if (in_merge_bases(commit_a, commit_b)) {
+       if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {
                oidcpy(result, b);
                path_msg(opt, path, 1,
                         _("Note: Fast-forwarding submodule %s to %s"),
                         path, oid_to_hex(b));
-               return 1;
+               ret = 1;
+               goto cleanup;
        }
-       if (in_merge_bases(commit_b, commit_a)) {
+       if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {
                oidcpy(result, a);
                path_msg(opt, path, 1,
                         _("Note: Fast-forwarding submodule %s to %s"),
                         path, oid_to_hex(a));
-               return 1;
+               ret = 1;
+               goto cleanup;
        }
 
        /*
@@ -1615,10 +1632,10 @@ static int merge_submodule(struct merge_options *opt,
 
        /* Skip the search if makes no sense to the calling context.  */
        if (!search)
-               return 0;
+               goto cleanup;
 
        /* find commit which merges them */
-       parent_count = find_first_merges(opt->repo, path, commit_a, commit_b,
+       parent_count = find_first_merges(&subrepo, path, commit_a, commit_b,
                                         &merges);
        switch (parent_count) {
        case 0:
@@ -1626,7 +1643,7 @@ static int merge_submodule(struct merge_options *opt,
                break;
 
        case 1:
-               format_commit(&sb, 4,
+               format_commit(&sb, 4, &subrepo,
                              (struct commit *)merges.objects[0].item);
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s, but a possible merge "
@@ -1643,7 +1660,7 @@ static int merge_submodule(struct merge_options *opt,
                break;
        default:
                for (i = 0; i < merges.nr; i++)
-                       format_commit(&sb, 4,
+                       format_commit(&sb, 4, &subrepo,
                                      (struct commit *)merges.objects[i].item);
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s, but multiple "
@@ -1652,7 +1669,9 @@ static int merge_submodule(struct merge_options *opt,
        }
 
        object_array_clear(&merges);
-       return 0;
+cleanup:
+       repo_clear(&subrepo);
+       return ret;
 }
 
 static void initialize_attr_index(struct merge_options *opt)
@@ -2293,12 +2312,17 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
        VERIFY_CI(ci);
 
        /* Find parent directories missing from opt->priv->paths */
-       cur_path = new_path;
+       cur_path = mem_pool_strdup(&opt->priv->pool, new_path);
+       free((char*)new_path);
+       new_path = (char *)cur_path;
+
        while (1) {
                /* Find the parent directory of cur_path */
                char *last_slash = strrchr(cur_path, '/');
                if (last_slash) {
-                       parent_name = xstrndup(cur_path, last_slash - cur_path);
+                       parent_name = mem_pool_strndup(&opt->priv->pool,
+                                                      cur_path,
+                                                      last_slash - cur_path);
                } else {
                        parent_name = opt->priv->toplevel_dir;
                        break;
@@ -2307,7 +2331,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                /* Look it up in opt->priv->paths */
                entry = strmap_get_entry(&opt->priv->paths, parent_name);
                if (entry) {
-                       free((char*)parent_name);
                        parent_name = entry->key; /* reuse known pointer */
                        break;
                }
@@ -2334,13 +2357,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                parent_name = cur_dir;
        }
 
-       /*
-        * We are removing old_path from opt->priv->paths.  old_path also will
-        * eventually need to be freed, but it may still be used by e.g.
-        * ci->pathnames.  So, store it in another string-list for now.
-        */
-       string_list_append(&opt->priv->paths_to_free, old_path);
-
        assert(ci->filemask == 2 || ci->filemask == 4);
        assert(ci->dirmask == 0);
        strmap_remove(&opt->priv->paths, old_path, 0);
@@ -2374,7 +2390,6 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                new_ci->stages[index].mode = ci->stages[index].mode;
                oidcpy(&new_ci->stages[index].oid, &ci->stages[index].oid);
 
-               free(ci);
                ci = new_ci;
        }
 
@@ -2802,10 +2817,23 @@ static void use_cached_pairs(struct merge_options *opt,
                if (!new_name)
                        new_name = old_name;
 
+               /*
+                * cached_pairs has *copies* of old_name and new_name,
+                * because it has to persist across merges.  Since
+                * pool_alloc_filespec() will just re-use the existing
+                * filenames, which will also get re-used by
+                * opt->priv->paths if they become renames, and then
+                * get freed at the end of the merge, that would leave
+                * the copy in cached_pairs dangling.  Avoid this by
+                * making a copy here.
+                */
+               old_name = mem_pool_strdup(&opt->priv->pool, old_name);
+               new_name = mem_pool_strdup(&opt->priv->pool, new_name);
+
                /* We don't care about oid/mode, only filenames and status */
-               one = alloc_filespec(old_name);
-               two = alloc_filespec(new_name);
-               diff_queue(pairs, one, two);
+               one = pool_alloc_filespec(&opt->priv->pool, old_name);
+               two = pool_alloc_filespec(&opt->priv->pool, new_name);
+               pool_diff_queue(&opt->priv->pool, pairs, one, two);
                pairs->queue[pairs->nr-1]->status = entry->value ? 'R' : 'D';
        }
 }
@@ -2913,6 +2941,7 @@ static int detect_regular_renames(struct merge_options *opt,
        diff_queued_diff = renames->pairs[side_index];
        trace2_region_enter("diff", "diffcore_rename", opt->repo);
        diffcore_rename_extended(&diff_opts,
+                                &opt->priv->pool,
                                 &renames->relevant_sources[side_index],
                                 &renames->dirs_removed[side_index],
                                 &renames->dir_rename_count[side_index],
@@ -2963,7 +2992,7 @@ static int collect_renames(struct merge_options *opt,
 
                if (p->status != 'A' && p->status != 'R') {
                        possibly_cache_new_pair(renames, p, side_index, NULL);
-                       diff_free_filepair(p);
+                       pool_diff_free_filepair(&opt->priv->pool, p);
                        continue;
                }
 
@@ -2976,7 +3005,7 @@ static int collect_renames(struct merge_options *opt,
 
                possibly_cache_new_pair(renames, p, side_index, new_path);
                if (p->status != 'R' && !new_path) {
-                       diff_free_filepair(p);
+                       pool_diff_free_filepair(&opt->priv->pool, p);
                        continue;
                }
 
@@ -3094,7 +3123,7 @@ cleanup:
                side_pairs = &renames->pairs[s];
                for (i = 0; i < side_pairs->nr; ++i) {
                        struct diff_filepair *p = side_pairs->queue[i];
-                       diff_free_filepair(p);
+                       pool_diff_free_filepair(&opt->priv->pool, p);
                }
        }
 
@@ -3107,7 +3136,8 @@ simple_cleanup:
        if (combined.nr) {
                int i;
                for (i = 0; i < combined.nr; i++)
-                       diff_free_filepair(combined.queue[i]);
+                       pool_diff_free_filepair(&opt->priv->pool,
+                                               combined.queue[i]);
                free(combined.queue);
        }
 
@@ -3581,7 +3611,8 @@ static void process_entry(struct merge_options *opt,
                 * the directory to remain here, so we need to move this
                 * path to some new location.
                 */
-               CALLOC_ARRAY(new_ci, 1);
+               new_ci = mem_pool_calloc(&opt->priv->pool, 1, sizeof(*new_ci));
+
                /* We don't really want new_ci->merged.result copied, but it'll
                 * be overwritten below so it doesn't matter.  We also don't
                 * want any directory mode/oid values copied, but we'll zero
@@ -3673,7 +3704,8 @@ static void process_entry(struct merge_options *opt,
                        const char *a_path = NULL, *b_path = NULL;
                        int rename_a = 0, rename_b = 0;
 
-                       new_ci = xmalloc(sizeof(*new_ci));
+                       new_ci = mem_pool_alloc(&opt->priv->pool,
+                                               sizeof(*new_ci));
 
                        if (S_ISREG(a_mode))
                                rename_a = 1;
@@ -3742,17 +3774,8 @@ static void process_entry(struct merge_options *opt,
                                b_path = path;
                        strmap_put(&opt->priv->paths, b_path, new_ci);
 
-                       if (rename_a && rename_b) {
+                       if (rename_a && rename_b)
                                strmap_remove(&opt->priv->paths, path, 0);
-                               /*
-                                * We removed path from opt->priv->paths.  path
-                                * will also eventually need to be freed, but
-                                * it may still be used by e.g.  ci->pathnames.
-                                * So, store it in another string-list for now.
-                                */
-                               string_list_append(&opt->priv->paths_to_free,
-                                                  path);
-                       }
 
                        /*
                         * Do special handling for b_path since process_entry()
@@ -4029,11 +4052,7 @@ static int checkout(struct merge_options *opt,
        unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */
        unpack_opts.verbose_update = (opt->verbosity > 2);
        unpack_opts.fn = twoway_merge;
-       if (1/* FIXME: opts->overwrite_ignore*/) {
-               CALLOC_ARRAY(unpack_opts.dir, 1);
-               unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
-               setup_standard_excludes(unpack_opts.dir);
-       }
+       unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
        parse_tree(prev);
        init_tree_desc(&trees[0], prev->buffer, prev->size);
        parse_tree(next);
@@ -4041,8 +4060,6 @@ static int checkout(struct merge_options *opt,
 
        ret = unpack_trees(2, trees, &unpack_opts);
        clear_unpack_trees_porcelain(&unpack_opts);
-       dir_clear(unpack_opts.dir);
-       FREE_AND_NULL(unpack_opts.dir);
        return ret;
 }
 
@@ -4058,6 +4075,21 @@ static int record_conflicted_index_entries(struct merge_options *opt)
        if (strmap_empty(&opt->priv->conflicted))
                return 0;
 
+       /*
+        * We are in a conflicted state. These conflicts might be inside
+        * sparse-directory entries, so check if any entries are outside
+        * of the sparse-checkout cone preemptively.
+        *
+        * We set original_cache_nr below, but that might change if
+        * index_name_pos() calls ask for paths within sparse directories.
+        */
+       strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
+               if (!path_in_sparse_checkout(e->key, index)) {
+                       ensure_full_index(index);
+                       break;
+               }
+       }
+
        /* If any entries have skip_worktree set, we'll have to check 'em out */
        state.force = 1;
        state.quiet = 1;
@@ -4293,6 +4325,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
 {
        struct rename_info *renames;
        int i;
+       struct mem_pool *pool = NULL;
 
        /* Sanity checks on opt */
        trace2_region_enter("merge", "sanity checks", opt->repo);
@@ -4358,9 +4391,11 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
 
        /* Initialization of various renames fields */
        renames = &opt->priv->renames;
+       mem_pool_init(&opt->priv->pool, 0);
+       pool = &opt->priv->pool;
        for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
                strintmap_init_with_options(&renames->dirs_removed[i],
-                                           NOT_RELEVANT, NULL, 0);
+                                           NOT_RELEVANT, pool, 0);
                strmap_init_with_options(&renames->dir_rename_count[i],
                                         NULL, 1);
                strmap_init_with_options(&renames->dir_renames[i],
@@ -4374,7 +4409,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
                 */
                strintmap_init_with_options(&renames->relevant_sources[i],
                                            -1 /* explicitly invalid */,
-                                           NULL, 0);
+                                           pool, 0);
                strmap_init_with_options(&renames->cached_pairs[i],
                                         NULL, 1);
                strset_init_with_options(&renames->cached_irrelevant[i],
@@ -4384,9 +4419,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
        }
        for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) {
                strintmap_init_with_options(&renames->deferred[i].possible_trivial_merges,
-                                           0, NULL, 0);
+                                           0, pool, 0);
                strset_init_with_options(&renames->deferred[i].target_dirs,
-                                        NULL, 1);
+                                        pool, 1);
                renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */
        }
 
@@ -4394,14 +4429,13 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
         * Although we initialize opt->priv->paths with strdup_strings=0,
         * that's just to avoid making yet another copy of an allocated
         * string.  Putting the entry into paths means we are taking
-        * ownership, so we will later free it.  paths_to_free is similar.
+        * ownership, so we will later free it.
         *
         * In contrast, conflicted just has a subset of keys from paths, so
         * we don't want to free those (it'd be a duplicate free).
         */
-       strmap_init_with_options(&opt->priv->paths, NULL, 0);
-       strmap_init_with_options(&opt->priv->conflicted, NULL, 0);
-       string_list_init_nodup(&opt->priv->paths_to_free);
+       strmap_init_with_options(&opt->priv->paths, pool, 0);
+       strmap_init_with_options(&opt->priv->conflicted, pool, 0);
 
        /*
         * keys & strbufs in output will sometimes need to outlive "paths",
index 3355d50e8ad36b8649e26a340c45d456162f03fe..d9457797dbb73bfed720ac9ca0b9bcf56575c62a 100644 (file)
@@ -24,6 +24,7 @@
 #include "repository.h"
 #include "revision.h"
 #include "string-list.h"
+#include "submodule-config.h"
 #include "submodule.h"
 #include "tag.h"
 #include "tree-walk.h"
@@ -55,10 +56,7 @@ static int path_hashmap_cmp(const void *cmp_data,
        a = container_of(eptr, const struct path_hashmap_entry, e);
        b = container_of(entry_or_key, const struct path_hashmap_entry, e);
 
-       if (ignore_case)
-               return strcasecmp(a->path, key ? key : b->path);
-       else
-               return strcmp(a->path, key ? key : b->path);
+       return fspathcmp(a->path, key ? key : b->path);
 }
 
 /*
@@ -336,7 +334,9 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)
                flush_output(opt);
 }
 
-static void output_commit_title(struct merge_options *opt, struct commit *commit)
+static void repo_output_commit_title(struct merge_options *opt,
+                                    struct repository *repo,
+                                    struct commit *commit)
 {
        struct merge_remote_desc *desc;
 
@@ -345,23 +345,29 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit
        if (desc)
                strbuf_addf(&opt->obuf, "virtual %s\n", desc->name);
        else {
-               strbuf_add_unique_abbrev(&opt->obuf, &commit->object.oid,
-                                        DEFAULT_ABBREV);
+               strbuf_repo_add_unique_abbrev(&opt->obuf, repo,
+                                             &commit->object.oid,
+                                             DEFAULT_ABBREV);
                strbuf_addch(&opt->obuf, ' ');
-               if (parse_commit(commit) != 0)
+               if (repo_parse_commit(repo, commit) != 0)
                        strbuf_addstr(&opt->obuf, _("(bad commit)\n"));
                else {
                        const char *title;
-                       const char *msg = get_commit_buffer(commit, NULL);
+                       const char *msg = repo_get_commit_buffer(repo, commit, NULL);
                        int len = find_commit_subject(msg, &title);
                        if (len)
                                strbuf_addf(&opt->obuf, "%.*s\n", len, title);
-                       unuse_commit_buffer(commit, msg);
+                       repo_unuse_commit_buffer(repo, commit, msg);
                }
        }
        flush_output(opt);
 }
 
+static void output_commit_title(struct merge_options *opt, struct commit *commit)
+{
+       repo_output_commit_title(opt, the_repository, commit);
+}
+
 static int add_cacheinfo(struct merge_options *opt,
                         const struct diff_filespec *blob,
                         const char *path, int stage, int refresh, int options)
@@ -411,8 +417,11 @@ static int unpack_trees_start(struct merge_options *opt,
        memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
        if (opt->priv->call_depth)
                opt->priv->unpack_opts.index_only = 1;
-       else
+       else {
                opt->priv->unpack_opts.update = 1;
+               /* FIXME: should only do this if !overwrite_ignore */
+               opt->priv->unpack_opts.preserve_ignored = 0;
+       }
        opt->priv->unpack_opts.merge = 1;
        opt->priv->unpack_opts.head_idx = 2;
        opt->priv->unpack_opts.fn = threeway_merge;
@@ -1113,7 +1122,6 @@ static int find_first_merges(struct repository *repo,
        xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
                  oid_to_hex(&a->object.oid));
        repo_init_revisions(repo, &revs, NULL);
-       rev_opts.submodule = path;
        /* FIXME: can't handle linked worktrees in submodules yet */
        revs.single_worktree = path != NULL;
        setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
@@ -1123,7 +1131,7 @@ static int find_first_merges(struct repository *repo,
                die("revision walk setup failed");
        while ((commit = get_revision(&revs)) != NULL) {
                struct object *o = &(commit->object);
-               if (in_merge_bases(b, commit))
+               if (repo_in_merge_bases(repo, b, commit))
                        add_object_array(o, NULL, &merges);
        }
        reset_revision_walk();
@@ -1138,7 +1146,7 @@ static int find_first_merges(struct repository *repo,
                contains_another = 0;
                for (j = 0; j < merges.nr; j++) {
                        struct commit *m2 = (struct commit *) merges.objects[j].item;
-                       if (i != j && in_merge_bases(m2, m1)) {
+                       if (i != j && repo_in_merge_bases(repo, m2, m1)) {
                                contains_another = 1;
                                break;
                        }
@@ -1152,14 +1160,14 @@ static int find_first_merges(struct repository *repo,
        return result->nr;
 }
 
-static void print_commit(struct commit *commit)
+static void print_commit(struct repository *repo, struct commit *commit)
 {
        struct strbuf sb = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
        ctx.date_mode.type = DATE_NORMAL;
        /* FIXME: Merge this with output_commit_title() */
        assert(!merge_remote_util(commit));
-       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+       repo_format_commit_message(repo, commit, " %h: %m %s", &sb, &ctx);
        fprintf(stderr, "%s\n", sb.buf);
        strbuf_release(&sb);
 }
@@ -1174,6 +1182,8 @@ static int merge_submodule(struct merge_options *opt,
                           const struct object_id *base, const struct object_id *a,
                           const struct object_id *b)
 {
+       struct repository subrepo;
+       int ret = 0;
        struct commit *commit_base, *commit_a, *commit_b;
        int parent_count;
        struct object_array merges;
@@ -1197,49 +1207,51 @@ static int merge_submodule(struct merge_options *opt,
        if (is_null_oid(b))
                return 0;
 
-       if (add_submodule_odb(path)) {
+       if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
                output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
                return 0;
        }
 
-       if (!(commit_base = lookup_commit_reference(opt->repo, base)) ||
-           !(commit_a = lookup_commit_reference(opt->repo, a)) ||
-           !(commit_b = lookup_commit_reference(opt->repo, b))) {
+       if (!(commit_base = lookup_commit_reference(&subrepo, base)) ||
+           !(commit_a = lookup_commit_reference(&subrepo, a)) ||
+           !(commit_b = lookup_commit_reference(&subrepo, b))) {
                output(opt, 1, _("Failed to merge submodule %s (commits not present)"), path);
-               return 0;
+               goto cleanup;
        }
 
        /* check whether both changes are forward */
-       if (!in_merge_bases(commit_base, commit_a) ||
-           !in_merge_bases(commit_base, commit_b)) {
+       if (!repo_in_merge_bases(&subrepo, commit_base, commit_a) ||
+           !repo_in_merge_bases(&subrepo, commit_base, commit_b)) {
                output(opt, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path);
-               return 0;
+               goto cleanup;
        }
 
        /* Case #1: a is contained in b or vice versa */
-       if (in_merge_bases(commit_a, commit_b)) {
+       if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {
                oidcpy(result, b);
                if (show(opt, 3)) {
                        output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
-                       output_commit_title(opt, commit_b);
+                       repo_output_commit_title(opt, &subrepo, commit_b);
                } else if (show(opt, 2))
                        output(opt, 2, _("Fast-forwarding submodule %s"), path);
                else
                        ; /* no output */
 
-               return 1;
+               ret = 1;
+               goto cleanup;
        }
-       if (in_merge_bases(commit_b, commit_a)) {
+       if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {
                oidcpy(result, a);
                if (show(opt, 3)) {
                        output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
-                       output_commit_title(opt, commit_a);
+                       repo_output_commit_title(opt, &subrepo, commit_a);
                } else if (show(opt, 2))
                        output(opt, 2, _("Fast-forwarding submodule %s"), path);
                else
                        ; /* no output */
 
-               return 1;
+               ret = 1;
+               goto cleanup;
        }
 
        /*
@@ -1251,10 +1263,10 @@ static int merge_submodule(struct merge_options *opt,
 
        /* Skip the search if makes no sense to the calling context.  */
        if (!search)
-               return 0;
+               goto cleanup;
 
        /* find commit which merges them */
-       parent_count = find_first_merges(opt->repo, &merges, path,
+       parent_count = find_first_merges(&subrepo, &merges, path,
                                         commit_a, commit_b);
        switch (parent_count) {
        case 0:
@@ -1264,7 +1276,7 @@ static int merge_submodule(struct merge_options *opt,
        case 1:
                output(opt, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
                output(opt, 2, _("Found a possible merge resolution for the submodule:\n"));
-               print_commit((struct commit *) merges.objects[0].item);
+               print_commit(&subrepo, (struct commit *) merges.objects[0].item);
                output(opt, 2, _(
                       "If this is correct simply add it to the index "
                       "for example\n"
@@ -1277,11 +1289,13 @@ static int merge_submodule(struct merge_options *opt,
        default:
                output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
                for (i = 0; i < merges.nr; i++)
-                       print_commit((struct commit *) merges.objects[i].item);
+                       print_commit(&subrepo, (struct commit *) merges.objects[i].item);
        }
 
        object_array_clear(&merges);
-       return 0;
+cleanup:
+       repo_clear(&subrepo);
+       return ret;
 }
 
 static int merge_mode_and_contents(struct merge_options *opt,
@@ -3750,6 +3764,9 @@ int merge_recursive(struct merge_options *opt,
        assert(opt->ancestor == NULL ||
               !strcmp(opt->ancestor, "constructed merge base"));
 
+       prepare_repo_settings(opt->repo);
+       opt->repo->settings.command_requires_full_index = 1;
+
        if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
                return -1;
        clean = merge_recursive_internal(opt, h1, h2, merge_bases, result);
diff --git a/merge.c b/merge.c
index 6e736881d900bf76f33c1684e411b498e37582bd..2382ff66d351cce20848b6f2ff056258adb2d4d7 100644 (file)
--- a/merge.c
+++ b/merge.c
@@ -53,7 +53,6 @@ int checkout_fast_forward(struct repository *r,
        struct unpack_trees_options opts;
        struct tree_desc t[MAX_UNPACK_TREES];
        int i, nr_trees = 0;
-       struct dir_struct dir = DIR_INIT;
        struct lock_file lock_file = LOCK_INIT;
 
        refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
@@ -80,11 +79,7 @@ int checkout_fast_forward(struct repository *r,
        }
 
        memset(&opts, 0, sizeof(opts));
-       if (overwrite_ignore) {
-               dir.flags |= DIR_SHOW_IGNORED;
-               setup_standard_excludes(&dir);
-               opts.dir = &dir;
-       }
+       opts.preserve_ignored = !overwrite_ignore;
 
        opts.head_idx = 1;
        opts.src_index = r->index;
@@ -101,7 +96,6 @@ int checkout_fast_forward(struct repository *r,
                clear_unpack_trees_porcelain(&opts);
                return -1;
        }
-       dir_clear(&dir);
        clear_unpack_trees_porcelain(&opts);
 
        if (write_locked_index(r->index, &lock_file, COMMIT_LOCK))
index e5fdf2ee4ad9d42b069cfc8647b39ee271166087..6216835566a38297bc6d2d9543cf102ed8f42944 100644 (file)
@@ -1,73 +1,84 @@
 #include "cache.h"
 #include "mergesort.h"
 
-struct mergesort_sublist {
-       void *ptr;
-       unsigned long len;
-};
-
-static void *get_nth_next(void *list, unsigned long n,
-                         void *(*get_next_fn)(const void *))
+/* Combine two sorted lists.  Take from `list` on equality. */
+static void *llist_merge(void *list, void *other,
+                        void *(*get_next_fn)(const void *),
+                        void (*set_next_fn)(void *, void *),
+                        int (*compare_fn)(const void *, const void *))
 {
-       while (n-- && list)
-               list = get_next_fn(list);
-       return list;
-}
+       void *result = list, *tail;
 
-static void *pop_item(struct mergesort_sublist *l,
-                     void *(*get_next_fn)(const void *))
-{
-       void *p = l->ptr;
-       l->ptr = get_next_fn(l->ptr);
-       l->len = l->ptr ? (l->len - 1) : 0;
-       return p;
+       if (compare_fn(list, other) > 0) {
+               result = other;
+               goto other;
+       }
+       for (;;) {
+               do {
+                       tail = list;
+                       list = get_next_fn(list);
+                       if (!list) {
+                               set_next_fn(tail, other);
+                               return result;
+                       }
+               } while (compare_fn(list, other) <= 0);
+               set_next_fn(tail, other);
+       other:
+               do {
+                       tail = other;
+                       other = get_next_fn(other);
+                       if (!other) {
+                               set_next_fn(tail, list);
+                               return result;
+                       }
+               } while (compare_fn(list, other) > 0);
+               set_next_fn(tail, list);
+       }
 }
 
+/*
+ * Perform an iterative mergesort using an array of sublists.
+ *
+ * n is the number of items.
+ * ranks[i] is undefined if n & 2^i == 0, and assumed empty.
+ * ranks[i] contains a sublist of length 2^i otherwise.
+ *
+ * The number of bits in a void pointer limits the number of objects
+ * that can be created, and thus the number of array elements necessary
+ * to be able to sort any valid list.
+ *
+ * Adding an item to this array is like incrementing a binary number;
+ * positional values for set bits correspond to sublist lengths.
+ */
 void *llist_mergesort(void *list,
                      void *(*get_next_fn)(const void *),
                      void (*set_next_fn)(void *, void *),
                      int (*compare_fn)(const void *, const void *))
 {
-       unsigned long l;
-
-       if (!list)
-               return NULL;
-       for (l = 1; ; l *= 2) {
-               void *curr;
-               struct mergesort_sublist p, q;
+       void *ranks[bitsizeof(void *)];
+       size_t n = 0;
+       int i;
 
-               p.ptr = list;
-               q.ptr = get_nth_next(p.ptr, l, get_next_fn);
-               if (!q.ptr)
-                       break;
-               p.len = q.len = l;
+       while (list) {
+               void *next = get_next_fn(list);
+               if (next)
+                       set_next_fn(list, NULL);
+               for (i = 0; n & (1 << i); i++)
+                       list = llist_merge(ranks[i], list, get_next_fn,
+                                          set_next_fn, compare_fn);
+               n++;
+               ranks[i] = list;
+               list = next;
+       }
 
-               if (compare_fn(p.ptr, q.ptr) > 0)
-                       list = curr = pop_item(&q, get_next_fn);
+       for (i = 0; n; i++, n >>= 1) {
+               if (!(n & 1))
+                       continue;
+               if (list)
+                       list = llist_merge(ranks[i], list, get_next_fn,
+                                          set_next_fn, compare_fn);
                else
-                       list = curr = pop_item(&p, get_next_fn);
-
-               while (p.ptr) {
-                       while (p.len || q.len) {
-                               void *prev = curr;
-
-                               if (!p.len)
-                                       curr = pop_item(&q, get_next_fn);
-                               else if (!q.len)
-                                       curr = pop_item(&p, get_next_fn);
-                               else if (compare_fn(p.ptr, q.ptr) > 0)
-                                       curr = pop_item(&q, get_next_fn);
-                               else
-                                       curr = pop_item(&p, get_next_fn);
-                               set_next_fn(prev, curr);
-                       }
-                       p.ptr = q.ptr;
-                       p.len = l;
-                       q.ptr = get_nth_next(p.ptr, l, get_next_fn);
-                       q.len = q.ptr ? l : 0;
-
-               }
-               set_next_fn(curr, NULL);
+                       list = ranks[i];
        }
        return list;
 }
diff --git a/midx.c b/midx.c
index 321c6fdd2f184ad8842be703a072413699a74a9d..7e06e8597561fe1341a53983a36b4a8a0eafe378 100644 (file)
--- a/midx.c
+++ b/midx.c
 #include "repository.h"
 #include "chunk-format.h"
 #include "pack.h"
+#include "pack-bitmap.h"
+#include "refs.h"
+#include "revision.h"
+#include "list-objects.h"
 
 #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
 #define MIDX_VERSION 1
@@ -48,12 +52,12 @@ static uint8_t oid_version(void)
        }
 }
 
-static const unsigned char *get_midx_checksum(struct multi_pack_index *m)
+const unsigned char *get_midx_checksum(struct multi_pack_index *m)
 {
        return m->data + m->data_len - the_hash_algo->rawsz;
 }
 
-static char *get_midx_filename(const char *object_dir)
+char *get_midx_filename(const char *object_dir)
 {
        return xstrfmt("%s/pack/multi-pack-index", object_dir);
 }
@@ -195,6 +199,8 @@ void close_midx(struct multi_pack_index *m)
        if (!m)
                return;
 
+       close_midx(m->next);
+
        munmap((unsigned char *)m->data, m->data_len);
 
        for (i = 0; i < m->num_packs; i++) {
@@ -203,6 +209,7 @@ void close_midx(struct multi_pack_index *m)
        }
        FREE_AND_NULL(m->packs);
        FREE_AND_NULL(m->pack_names);
+       free(m);
 }
 
 int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id)
@@ -276,14 +283,18 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
                        (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
 }
 
-static int nth_midxed_pack_entry(struct repository *r,
-                                struct multi_pack_index *m,
-                                struct pack_entry *e,
-                                uint32_t pos)
+int fill_midx_entry(struct repository * r,
+                   const struct object_id *oid,
+                   struct pack_entry *e,
+                   struct multi_pack_index *m)
 {
+       uint32_t pos;
        uint32_t pack_int_id;
        struct packed_git *p;
 
+       if (!bsearch_midx(oid, m, &pos))
+               return 0;
+
        if (pos >= m->num_objects)
                return 0;
 
@@ -303,15 +314,9 @@ static int nth_midxed_pack_entry(struct repository *r,
        if (!is_pack_valid(p))
                return 0;
 
-       if (p->num_bad_objects) {
-               uint32_t i;
-               struct object_id oid;
-               nth_midxed_object_oid(&oid, m, pos);
-               for (i = 0; i < p->num_bad_objects; i++)
-                       if (hasheq(oid.hash,
-                                  p->bad_object_sha1 + the_hash_algo->rawsz * i))
-                               return 0;
-       }
+       if (oidset_size(&p->bad_objects) &&
+           oidset_contains(&p->bad_objects, oid))
+               return 0;
 
        e->offset = nth_midxed_offset(m, pos);
        e->p = p;
@@ -319,19 +324,6 @@ static int nth_midxed_pack_entry(struct repository *r,
        return 1;
 }
 
-int fill_midx_entry(struct repository * r,
-                   const struct object_id *oid,
-                   struct pack_entry *e,
-                   struct multi_pack_index *m)
-{
-       uint32_t pos;
-
-       if (!bsearch_midx(oid, m, &pos))
-               return 0;
-
-       return nth_midxed_pack_entry(r, m, e, pos);
-}
-
 /* Match "foo.idx" against either "foo.pack" _or_ "foo.idx". */
 static int cmp_idx_or_pack_name(const char *idx_or_pack_name,
                                const char *idx_name)
@@ -468,6 +460,8 @@ struct write_midx_context {
        uint32_t num_large_offsets;
 
        int preferred_pack_idx;
+
+       struct string_list *to_include;
 };
 
 static void add_pack_to_midx(const char *full_path, size_t full_path_len,
@@ -477,8 +471,26 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len,
 
        if (ends_with(file_name, ".idx")) {
                display_progress(ctx->progress, ++ctx->pack_paths_checked);
+               /*
+                * Note that at most one of ctx->m and ctx->to_include are set,
+                * so we are testing midx_contains_pack() and
+                * string_list_has_string() independently (guarded by the
+                * appropriate NULL checks).
+                *
+                * We could support passing to_include while reusing an existing
+                * MIDX, but don't currently since the reuse process drags
+                * forward all packs from an existing MIDX (without checking
+                * whether or not they appear in the to_include list).
+                *
+                * If we added support for that, these next two conditional
+                * should be performed independently (likely checking
+                * to_include before the existing MIDX).
+                */
                if (ctx->m && midx_contains_pack(ctx->m, file_name))
                        return;
+               else if (ctx->to_include &&
+                        !string_list_has_string(ctx->to_include, file_name))
+                       return;
 
                ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc);
 
@@ -882,7 +894,7 @@ static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash,
        strbuf_release(&buf);
 }
 
-static void clear_midx_files_ext(struct repository *r, const char *ext,
+static void clear_midx_files_ext(const char *object_dir, const char *ext,
                                 unsigned char *keep_hash);
 
 static int midx_checksum_valid(struct multi_pack_index *m)
@@ -890,9 +902,216 @@ static int midx_checksum_valid(struct multi_pack_index *m)
        return hashfile_checksum_valid(m->data, m->data_len);
 }
 
-static int write_midx_internal(const char *object_dir, struct multi_pack_index *m,
+static void prepare_midx_packing_data(struct packing_data *pdata,
+                                     struct write_midx_context *ctx)
+{
+       uint32_t i;
+
+       memset(pdata, 0, sizeof(struct packing_data));
+       prepare_packing_data(the_repository, pdata);
+
+       for (i = 0; i < ctx->entries_nr; i++) {
+               struct pack_midx_entry *from = &ctx->entries[ctx->pack_order[i]];
+               struct object_entry *to = packlist_alloc(pdata, &from->oid);
+
+               oe_set_in_pack(pdata, to,
+                              ctx->info[ctx->pack_perm[from->pack_int_id]].p);
+       }
+}
+
+static int add_ref_to_pending(const char *refname,
+                             const struct object_id *oid,
+                             int flag, void *cb_data)
+{
+       struct rev_info *revs = (struct rev_info*)cb_data;
+       struct object *object;
+
+       if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {
+               warning("symbolic ref is dangling: %s", refname);
+               return 0;
+       }
+
+       object = parse_object_or_die(oid, refname);
+       if (object->type != OBJ_COMMIT)
+               return 0;
+
+       add_pending_object(revs, object, "");
+       if (bitmap_is_preferred_refname(revs->repo, refname))
+               object->flags |= NEEDS_BITMAP;
+       return 0;
+}
+
+struct bitmap_commit_cb {
+       struct commit **commits;
+       size_t commits_nr, commits_alloc;
+
+       struct write_midx_context *ctx;
+};
+
+static const struct object_id *bitmap_oid_access(size_t index,
+                                                const void *_entries)
+{
+       const struct pack_midx_entry *entries = _entries;
+       return &entries[index].oid;
+}
+
+static void bitmap_show_commit(struct commit *commit, void *_data)
+{
+       struct bitmap_commit_cb *data = _data;
+       int pos = oid_pos(&commit->object.oid, data->ctx->entries,
+                         data->ctx->entries_nr,
+                         bitmap_oid_access);
+       if (pos < 0)
+               return;
+
+       ALLOC_GROW(data->commits, data->commits_nr + 1, data->commits_alloc);
+       data->commits[data->commits_nr++] = commit;
+}
+
+static int read_refs_snapshot(const char *refs_snapshot,
+                             struct rev_info *revs)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct object_id oid;
+       FILE *f = xfopen(refs_snapshot, "r");
+
+       while (strbuf_getline(&buf, f) != EOF) {
+               struct object *object;
+               int preferred = 0;
+               char *hex = buf.buf;
+               const char *end = NULL;
+
+               if (buf.len && *buf.buf == '+') {
+                       preferred = 1;
+                       hex = &buf.buf[1];
+               }
+
+               if (parse_oid_hex(hex, &oid, &end) < 0)
+                       die(_("could not parse line: %s"), buf.buf);
+               if (*end)
+                       die(_("malformed line: %s"), buf.buf);
+
+               object = parse_object_or_die(&oid, NULL);
+               if (preferred)
+                       object->flags |= NEEDS_BITMAP;
+
+               add_pending_object(revs, object, "");
+       }
+
+       fclose(f);
+       strbuf_release(&buf);
+       return 0;
+}
+
+static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p,
+                                                   const char *refs_snapshot,
+                                                   struct write_midx_context *ctx)
+{
+       struct rev_info revs;
+       struct bitmap_commit_cb cb = {0};
+
+       cb.ctx = ctx;
+
+       repo_init_revisions(the_repository, &revs, NULL);
+       if (refs_snapshot) {
+               read_refs_snapshot(refs_snapshot, &revs);
+       } else {
+               setup_revisions(0, NULL, &revs, NULL);
+               for_each_ref(add_ref_to_pending, &revs);
+       }
+
+       /*
+        * Skipping promisor objects here is intentional, since it only excludes
+        * them from the list of reachable commits that we want to select from
+        * when computing the selection of MIDX'd commits to receive bitmaps.
+        *
+        * Reachability bitmaps do require that their objects be closed under
+        * reachability, but fetching any objects missing from promisors at this
+        * point is too late. But, if one of those objects can be reached from
+        * an another object that is included in the bitmap, then we will
+        * complain later that we don't have reachability closure (and fail
+        * appropriately).
+        */
+       fetch_if_missing = 0;
+       revs.exclude_promisor_objects = 1;
+
+       if (prepare_revision_walk(&revs))
+               die(_("revision walk setup failed"));
+
+       traverse_commit_list(&revs, bitmap_show_commit, NULL, &cb);
+       if (indexed_commits_nr_p)
+               *indexed_commits_nr_p = cb.commits_nr;
+
+       return cb.commits;
+}
+
+static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
+                            struct write_midx_context *ctx,
+                            const char *refs_snapshot,
+                            unsigned flags)
+{
+       struct packing_data pdata;
+       struct pack_idx_entry **index;
+       struct commit **commits = NULL;
+       uint32_t i, commits_nr;
+       uint16_t options = 0;
+       char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
+       int ret;
+
+       if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
+               options |= BITMAP_OPT_HASH_CACHE;
+
+       prepare_midx_packing_data(&pdata, ctx);
+
+       commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, ctx);
+
+       /*
+        * Build the MIDX-order index based on pdata.objects (which is already
+        * in MIDX order; c.f., 'midx_pack_order_cmp()' for the definition of
+        * this order).
+        */
+       ALLOC_ARRAY(index, pdata.nr_objects);
+       for (i = 0; i < pdata.nr_objects; i++)
+               index[i] = &pdata.objects[i].idx;
+
+       bitmap_writer_show_progress(flags & MIDX_PROGRESS);
+       bitmap_writer_build_type_index(&pdata, index, pdata.nr_objects);
+
+       /*
+        * bitmap_writer_finish expects objects in lex order, but pack_order
+        * gives us exactly that. use it directly instead of re-sorting the
+        * array.
+        *
+        * This changes the order of objects in 'index' between
+        * bitmap_writer_build_type_index and bitmap_writer_finish.
+        *
+        * The same re-ordering takes place in the single-pack bitmap code via
+        * write_idx_file(), which is called by finish_tmp_packfile(), which
+        * happens between bitmap_writer_build_type_index() and
+        * bitmap_writer_finish().
+        */
+       for (i = 0; i < pdata.nr_objects; i++)
+               index[ctx->pack_order[i]] = &pdata.objects[i].idx;
+
+       bitmap_writer_select_commits(commits, commits_nr, -1);
+       ret = bitmap_writer_build(&pdata);
+       if (ret < 0)
+               goto cleanup;
+
+       bitmap_writer_set_checksum(midx_hash);
+       bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, options);
+
+cleanup:
+       free(index);
+       free(bitmap_name);
+       return ret;
+}
+
+static int write_midx_internal(const char *object_dir,
+                              struct string_list *packs_to_include,
                               struct string_list *packs_to_drop,
                               const char *preferred_pack_name,
+                              const char *refs_snapshot,
                               unsigned flags)
 {
        char *midx_name;
@@ -901,20 +1120,33 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        struct hashfile *f = NULL;
        struct lock_file lk;
        struct write_midx_context ctx = { 0 };
+       struct multi_pack_index *cur;
        int pack_name_concat_len = 0;
        int dropped_packs = 0;
        int result = 0;
        struct chunkfile *cf;
 
+       /* Ensure the given object_dir is local, or a known alternate. */
+       find_odb(the_repository, object_dir);
+
        midx_name = get_midx_filename(object_dir);
        if (safe_create_leading_directories(midx_name))
                die_errno(_("unable to create leading directories of %s"),
                          midx_name);
 
-       if (m)
-               ctx.m = m;
-       else
-               ctx.m = load_multi_pack_index(object_dir, 1);
+       if (!packs_to_include) {
+               /*
+                * Only reference an existing MIDX when not filtering which
+                * packs to include, since all packs and objects are copied
+                * blindly from an existing MIDX if one is present.
+                */
+               for (cur = get_multi_pack_index(the_repository); cur; cur = cur->next) {
+                       if (!strcmp(object_dir, cur->object_dir)) {
+                               ctx.m = cur;
+                               break;
+                       }
+               }
+       }
 
        if (ctx.m && !midx_checksum_valid(ctx.m)) {
                warning(_("ignoring existing multi-pack-index; checksum mismatch"));
@@ -932,8 +1164,27 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
 
                        ctx.info[ctx.nr].orig_pack_int_id = i;
                        ctx.info[ctx.nr].pack_name = xstrdup(ctx.m->pack_names[i]);
-                       ctx.info[ctx.nr].p = NULL;
+                       ctx.info[ctx.nr].p = ctx.m->packs[i];
                        ctx.info[ctx.nr].expired = 0;
+
+                       if (flags & MIDX_WRITE_REV_INDEX) {
+                               /*
+                                * If generating a reverse index, need to have
+                                * packed_git's loaded to compare their
+                                * mtimes and object count.
+                                */
+                               if (prepare_midx_pack(the_repository, ctx.m, i)) {
+                                       error(_("could not load pack"));
+                                       result = 1;
+                                       goto cleanup;
+                               }
+
+                               if (open_pack_index(ctx.m->packs[i]))
+                                       die(_("could not open index for %s"),
+                                           ctx.m->packs[i]->pack_name);
+                               ctx.info[ctx.nr].p = ctx.m->packs[i];
+                       }
+
                        ctx.nr++;
                }
        }
@@ -944,21 +1195,95 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        else
                ctx.progress = NULL;
 
+       ctx.to_include = packs_to_include;
+
        for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx);
        stop_progress(&ctx.progress);
 
-       if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop)
-               goto cleanup;
+       if ((ctx.m && ctx.nr == ctx.m->num_packs) &&
+           !(packs_to_include || packs_to_drop)) {
+               struct bitmap_index *bitmap_git;
+               int bitmap_exists;
+               int want_bitmap = flags & MIDX_WRITE_BITMAP;
+
+               bitmap_git = prepare_midx_bitmap_git(ctx.m);
+               bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git);
+               free_bitmap_index(bitmap_git);
+
+               if (bitmap_exists || !want_bitmap) {
+                       /*
+                        * The correct MIDX already exists, and so does a
+                        * corresponding bitmap (or one wasn't requested).
+                        */
+                       if (!want_bitmap)
+                               clear_midx_files_ext(object_dir, ".bitmap",
+                                                    NULL);
+                       goto cleanup;
+               }
+       }
 
-       ctx.preferred_pack_idx = -1;
        if (preferred_pack_name) {
+               int found = 0;
                for (i = 0; i < ctx.nr; i++) {
                        if (!cmp_idx_or_pack_name(preferred_pack_name,
                                                  ctx.info[i].pack_name)) {
                                ctx.preferred_pack_idx = i;
+                               found = 1;
                                break;
                        }
                }
+
+               if (!found)
+                       warning(_("unknown preferred pack: '%s'"),
+                               preferred_pack_name);
+       } else if (ctx.nr &&
+                  (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) {
+               struct packed_git *oldest = ctx.info[ctx.preferred_pack_idx].p;
+               ctx.preferred_pack_idx = 0;
+
+               if (packs_to_drop && packs_to_drop->nr)
+                       BUG("cannot write a MIDX bitmap during expiration");
+
+               /*
+                * set a preferred pack when writing a bitmap to ensure that
+                * the pack from which the first object is selected in pseudo
+                * pack-order has all of its objects selected from that pack
+                * (and not another pack containing a duplicate)
+                */
+               for (i = 1; i < ctx.nr; i++) {
+                       struct packed_git *p = ctx.info[i].p;
+
+                       if (!oldest->num_objects || p->mtime < oldest->mtime) {
+                               oldest = p;
+                               ctx.preferred_pack_idx = i;
+                       }
+               }
+
+               if (!oldest->num_objects) {
+                       /*
+                        * If all packs are empty; unset the preferred index.
+                        * This is acceptable since there will be no duplicate
+                        * objects to resolve, so the preferred value doesn't
+                        * matter.
+                        */
+                       ctx.preferred_pack_idx = -1;
+               }
+       } else {
+               /*
+                * otherwise don't mark any pack as preferred to avoid
+                * interfering with expiration logic below
+                */
+               ctx.preferred_pack_idx = -1;
+       }
+
+       if (ctx.preferred_pack_idx > -1) {
+               struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p;
+               if (!preferred->num_objects) {
+                       error(_("cannot select preferred pack %s with no objects"),
+                             preferred->pack_name);
+                       result = 1;
+                       goto cleanup;
+               }
        }
 
        ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr,
@@ -1029,11 +1354,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
                                                      ctx.info, ctx.nr,
                                                      sizeof(*ctx.info),
                                                      idx_or_pack_name_cmp);
-
-               if (!preferred)
-                       warning(_("unknown preferred pack: '%s'"),
-                               preferred_pack_name);
-               else {
+               if (preferred) {
                        uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id];
                        if (perm == PACK_EXPIRED)
                                warning(_("preferred pack '%s' is expired"),
@@ -1048,9 +1369,6 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
        f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk));
 
-       if (ctx.m)
-               close_midx(ctx.m);
-
        if (ctx.nr - dropped_packs == 0) {
                error(_("no pack files to index."));
                result = 1;
@@ -1081,15 +1399,28 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
        free_chunkfile(cf);
 
-       if (flags & MIDX_WRITE_REV_INDEX)
+       if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
                ctx.pack_order = midx_pack_order(&ctx);
 
        if (flags & MIDX_WRITE_REV_INDEX)
                write_midx_reverse_index(midx_name, midx_hash, &ctx);
-       clear_midx_files_ext(the_repository, ".rev", midx_hash);
+       if (flags & MIDX_WRITE_BITMAP) {
+               if (write_midx_bitmap(midx_name, midx_hash, &ctx,
+                                     refs_snapshot, flags) < 0) {
+                       error(_("could not write multi-pack bitmap"));
+                       result = 1;
+                       goto cleanup;
+               }
+       }
+
+       if (ctx.m)
+               close_object_store(the_repository->objects);
 
        commit_lock_file(&lk);
 
+       clear_midx_files_ext(object_dir, ".bitmap", midx_hash);
+       clear_midx_files_ext(object_dir, ".rev", midx_hash);
+
 cleanup:
        for (i = 0; i < ctx.nr; i++) {
                if (ctx.info[i].p) {
@@ -1104,15 +1435,27 @@ cleanup:
        free(ctx.pack_perm);
        free(ctx.pack_order);
        free(midx_name);
+
        return result;
 }
 
 int write_midx_file(const char *object_dir,
                    const char *preferred_pack_name,
+                   const char *refs_snapshot,
                    unsigned flags)
 {
        return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name,
-                                  flags);
+                                  refs_snapshot, flags);
+}
+
+int write_midx_file_only(const char *object_dir,
+                        struct string_list *packs_to_include,
+                        const char *preferred_pack_name,
+                        const char *refs_snapshot,
+                        unsigned flags)
+{
+       return write_midx_internal(object_dir, packs_to_include, NULL,
+                                  preferred_pack_name, refs_snapshot, flags);
 }
 
 struct clear_midx_data {
@@ -1135,7 +1478,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len,
                die_errno(_("failed to remove %s"), full_path);
 }
 
-static void clear_midx_files_ext(struct repository *r, const char *ext,
+static void clear_midx_files_ext(const char *object_dir, const char *ext,
                                 unsigned char *keep_hash)
 {
        struct clear_midx_data data;
@@ -1146,7 +1489,7 @@ static void clear_midx_files_ext(struct repository *r, const char *ext,
                                    hash_to_hex(keep_hash), ext);
        data.ext = ext;
 
-       for_each_file_in_pack_dir(r->objects->odb->path,
+       for_each_file_in_pack_dir(object_dir,
                                  clear_midx_file_ext,
                                  &data);
 
@@ -1165,7 +1508,8 @@ void clear_midx_file(struct repository *r)
        if (remove_path(midx))
                die(_("failed to clear multi-pack-index at %s"), midx);
 
-       clear_midx_files_ext(r, ".rev", NULL);
+       clear_midx_files_ext(r->objects->odb->path, ".bitmap", NULL);
+       clear_midx_files_ext(r->objects->odb->path, ".rev", NULL);
 
        free(midx);
 }
@@ -1390,8 +1734,10 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
 
        free(count);
 
-       if (packs_to_drop.nr)
-               result = write_midx_internal(object_dir, m, &packs_to_drop, NULL, flags);
+       if (packs_to_drop.nr) {
+               result = write_midx_internal(object_dir, NULL, &packs_to_drop, NULL, NULL, flags);
+               m = NULL;
+       }
 
        string_list_clear(&packs_to_drop, 0);
        return result;
@@ -1580,7 +1926,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
                goto cleanup;
        }
 
-       result = write_midx_internal(object_dir, m, NULL, NULL, flags);
+       result = write_midx_internal(object_dir, NULL, NULL, NULL, NULL, flags);
        m = NULL;
 
 cleanup:
diff --git a/midx.h b/midx.h
index 8684cf0fefe81dbc5aa32b6c48203c12ae2c12da..6e32297fa3aaf0edeea852c5760e328b3ed5d061 100644 (file)
--- a/midx.h
+++ b/midx.h
@@ -2,12 +2,15 @@
 #define MIDX_H
 
 #include "repository.h"
+#include "string-list.h"
 
 struct object_id;
 struct pack_entry;
 struct repository;
 
 #define GIT_TEST_MULTI_PACK_INDEX "GIT_TEST_MULTI_PACK_INDEX"
+#define GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP \
+       "GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP"
 
 struct multi_pack_index {
        struct multi_pack_index *next;
@@ -41,7 +44,11 @@ struct multi_pack_index {
 
 #define MIDX_PROGRESS     (1 << 0)
 #define MIDX_WRITE_REV_INDEX (1 << 1)
+#define MIDX_WRITE_BITMAP (1 << 2)
+#define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3)
 
+const unsigned char *get_midx_checksum(struct multi_pack_index *m);
+char *get_midx_filename(const char *object_dir);
 char *get_midx_rev_filename(struct multi_pack_index *m);
 
 struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);
@@ -56,7 +63,19 @@ int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pa
 int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name);
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local);
 
-int write_midx_file(const char *object_dir, const char *preferred_pack_name, unsigned flags);
+/*
+ * Variant of write_midx_file which writes a MIDX containing only the packs
+ * specified in packs_to_include.
+ */
+int write_midx_file(const char *object_dir,
+                   const char *preferred_pack_name,
+                   const char *refs_snapshot,
+                   unsigned flags);
+int write_midx_file_only(const char *object_dir,
+                        struct string_list *packs_to_include,
+                        const char *preferred_pack_name,
+                        const char *refs_snapshot,
+                        unsigned flags);
 void clear_midx_file(struct repository *r);
 int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags);
 int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags);
index 46c1f7c7f11fe221707784be59118b3540839d98..b4a3a903e86f3fdc38064e5f16c63974d2a191e4 100644 (file)
@@ -273,7 +273,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
                 */
                if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
                    !is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
-                       if (advice_resolve_conflict)
+                       if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
                                die(_("You have not concluded your previous "
                                    "notes merge (%s exists).\nPlease, use "
                                    "'git notes merge --commit' or 'git notes "
index a8be8994814933e9d1afaae77edb93ec9934a3ac..02b79702748d1f163bb84f925fde45e49c3bd4b6 100644 (file)
@@ -32,6 +32,7 @@
 #include "packfile.h"
 #include "object-store.h"
 #include "promisor-remote.h"
+#include "submodule.h"
 
 /* The maximum size for an object header. */
 #define MAX_HEADER_LEN 32
@@ -414,74 +415,6 @@ enum scld_error safe_create_leading_directories_const(const char *path)
        return result;
 }
 
-int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
-{
-       /*
-        * The number of times we will try to remove empty directories
-        * in the way of path. This is only 1 because if another
-        * process is racily creating directories that conflict with
-        * us, we don't want to fight against them.
-        */
-       int remove_directories_remaining = 1;
-
-       /*
-        * The number of times that we will try to create the
-        * directories containing path. We are willing to attempt this
-        * more than once, because another process could be trying to
-        * clean up empty directories at the same time as we are
-        * trying to create them.
-        */
-       int create_directories_remaining = 3;
-
-       /* A scratch copy of path, filled lazily if we need it: */
-       struct strbuf path_copy = STRBUF_INIT;
-
-       int ret, save_errno;
-
-       /* Sanity check: */
-       assert(*path);
-
-retry_fn:
-       ret = fn(path, cb);
-       save_errno = errno;
-       if (!ret)
-               goto out;
-
-       if (errno == EISDIR && remove_directories_remaining-- > 0) {
-               /*
-                * A directory is in the way. Maybe it is empty; try
-                * to remove it:
-                */
-               if (!path_copy.len)
-                       strbuf_addstr(&path_copy, path);
-
-               if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
-                       goto retry_fn;
-       } else if (errno == ENOENT && create_directories_remaining-- > 0) {
-               /*
-                * Maybe the containing directory didn't exist, or
-                * maybe it was just deleted by a process that is
-                * racing with us to clean up empty directories. Try
-                * to create it:
-                */
-               enum scld_error scld_result;
-
-               if (!path_copy.len)
-                       strbuf_addstr(&path_copy, path);
-
-               do {
-                       scld_result = safe_create_leading_directories(path_copy.buf);
-                       if (scld_result == SCLD_OK)
-                               goto retry_fn;
-               } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
-       }
-
-out:
-       strbuf_release(&path_copy);
-       errno = save_errno;
-       return ret;
-}
-
 static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)
 {
        int i;
@@ -820,6 +753,27 @@ out:
        return ref_git;
 }
 
+struct object_directory *find_odb(struct repository *r, const char *obj_dir)
+{
+       struct object_directory *odb;
+       char *obj_dir_real = real_pathdup(obj_dir, 1);
+       struct strbuf odb_path_real = STRBUF_INIT;
+
+       prepare_alt_odb(r);
+       for (odb = r->objects->odb; odb; odb = odb->next) {
+               strbuf_realpath(&odb_path_real, odb->path, 1);
+               if (!strcmp(obj_dir_real, odb_path_real.buf))
+                       break;
+       }
+
+       free(obj_dir_real);
+       strbuf_release(&odb_path_real);
+
+       if (!odb)
+               die(_("could not find object directory matching %s"), obj_dir);
+       return odb;
+}
+
 static void fill_alternate_refs_command(struct child_process *cmd,
                                        const char *repo_path)
 {
@@ -1062,9 +1016,11 @@ void *xmmap(void *start, size_t length,
  * the streaming interface and rehash it to do the same.
  */
 int check_object_signature(struct repository *r, const struct object_id *oid,
-                          void *map, unsigned long size, const char *type)
+                          void *map, unsigned long size, const char *type,
+                          struct object_id *real_oidp)
 {
-       struct object_id real_oid;
+       struct object_id tmp;
+       struct object_id *real_oid = real_oidp ? real_oidp : &tmp;
        enum object_type obj_type;
        struct git_istream *st;
        git_hash_ctx c;
@@ -1072,8 +1028,8 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
        int hdrlen;
 
        if (map) {
-               hash_object_file(r->hash_algo, map, size, type, &real_oid);
-               return !oideq(oid, &real_oid) ? -1 : 0;
+               hash_object_file(r->hash_algo, map, size, type, real_oid);
+               return !oideq(oid, real_oid) ? -1 : 0;
        }
 
        st = open_istream(r, oid, &obj_type, &size, NULL);
@@ -1098,9 +1054,9 @@ int check_object_signature(struct repository *r, const struct object_id *oid,
                        break;
                r->hash_algo->update_fn(&c, buf, readlen);
        }
-       r->hash_algo->final_oid_fn(&real_oid, &c);
+       r->hash_algo->final_oid_fn(real_oid, &c);
        close_istream(st);
-       return !oideq(oid, &real_oid) ? -1 : 0;
+       return !oideq(oid, real_oid) ? -1 : 0;
 }
 
 int git_open_cloexec(const char *name, int flags)
@@ -1233,11 +1189,14 @@ void *map_loose_object(struct repository *r,
        return map_loose_object_1(r, NULL, oid, size);
 }
 
-static int unpack_loose_short_header(git_zstream *stream,
-                                    unsigned char *map, unsigned long mapsize,
-                                    void *buffer, unsigned long bufsiz)
+enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
+                                                   unsigned char *map,
+                                                   unsigned long mapsize,
+                                                   void *buffer,
+                                                   unsigned long bufsiz,
+                                                   struct strbuf *header)
 {
-       int ret;
+       int status;
 
        /* Get the data stream */
        memset(stream, 0, sizeof(*stream));
@@ -1248,43 +1207,24 @@ static int unpack_loose_short_header(git_zstream *stream,
 
        git_inflate_init(stream);
        obj_read_unlock();
-       ret = git_inflate(stream, 0);
+       status = git_inflate(stream, 0);
        obj_read_lock();
-
-       return ret;
-}
-
-int unpack_loose_header(git_zstream *stream,
-                       unsigned char *map, unsigned long mapsize,
-                       void *buffer, unsigned long bufsiz)
-{
-       int status = unpack_loose_short_header(stream, map, mapsize,
-                                              buffer, bufsiz);
-
        if (status < Z_OK)
-               return status;
-
-       /* Make sure we have the terminating NUL */
-       if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
-               return -1;
-       return 0;
-}
-
-static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map,
-                                        unsigned long mapsize, void *buffer,
-                                        unsigned long bufsiz, struct strbuf *header)
-{
-       int status;
-
-       status = unpack_loose_short_header(stream, map, mapsize, buffer, bufsiz);
-       if (status < Z_OK)
-               return -1;
+               return ULHR_BAD;
 
        /*
         * Check if entire header is unpacked in the first iteration.
         */
        if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
-               return 0;
+               return ULHR_OK;
+
+       /*
+        * We have a header longer than MAX_HEADER_LEN. The "header"
+        * here is only non-NULL when we run "cat-file
+        * --allow-unknown-type".
+        */
+       if (!header)
+               return ULHR_TOO_LONG;
 
        /*
         * buffer[0..bufsiz] was not large enough.  Copy the partial
@@ -1305,7 +1245,7 @@ static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map
                stream->next_out = buffer;
                stream->avail_out = bufsiz;
        } while (status != Z_STREAM_END);
-       return -1;
+       return ULHR_TOO_LONG;
 }
 
 static void *unpack_loose_rest(git_zstream *stream,
@@ -1363,8 +1303,7 @@ static void *unpack_loose_rest(git_zstream *stream,
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-static int parse_loose_header_extended(const char *hdr, struct object_info *oi,
-                                      unsigned int flags)
+int parse_loose_header(const char *hdr, struct object_info *oi)
 {
        const char *type_buf = hdr;
        unsigned long size;
@@ -1386,15 +1325,6 @@ static int parse_loose_header_extended(const char *hdr, struct object_info *oi,
        type = type_from_string_gently(type_buf, type_len, 1);
        if (oi->type_name)
                strbuf_add(oi->type_name, type_buf, type_len);
-       /*
-        * Set type to 0 if its an unknown object and
-        * we're obtaining the type using '--allow-unknown-type'
-        * option.
-        */
-       if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE) && (type < 0))
-               type = 0;
-       else if (type < 0)
-               die(_("invalid object type"));
        if (oi->typep)
                *oi->typep = type;
 
@@ -1421,15 +1351,14 @@ static int parse_loose_header_extended(const char *hdr, struct object_info *oi,
        /*
         * The length must be followed by a zero byte
         */
-       return *hdr ? -1 : type;
-}
-
-int parse_loose_header(const char *hdr, unsigned long *sizep)
-{
-       struct object_info oi = OBJECT_INFO_INIT;
+       if (*hdr)
+               return -1;
 
-       oi.sizep = sizep;
-       return parse_loose_header_extended(hdr, &oi, 0);
+       /*
+        * The format is valid, but the type may still be bogus. The
+        * Caller needs to check its oi->typep.
+        */
+       return 0;
 }
 
 static int loose_object_info(struct repository *r,
@@ -1443,6 +1372,8 @@ static int loose_object_info(struct repository *r,
        char hdr[MAX_HEADER_LEN];
        struct strbuf hdrbuf = STRBUF_INIT;
        unsigned long size_scratch;
+       enum object_type type_scratch;
+       int allow_unknown = flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
 
        if (oi->delta_base_oid)
                oidclr(oi->delta_base_oid);
@@ -1473,43 +1404,48 @@ static int loose_object_info(struct repository *r,
 
        if (!oi->sizep)
                oi->sizep = &size_scratch;
+       if (!oi->typep)
+               oi->typep = &type_scratch;
 
        if (oi->disk_sizep)
                *oi->disk_sizep = mapsize;
-       if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE)) {
-               if (unpack_loose_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0)
-                       status = error(_("unable to unpack %s header with --allow-unknown-type"),
-                                      oid_to_hex(oid));
-       } else if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
+
+       switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
+                                   allow_unknown ? &hdrbuf : NULL)) {
+       case ULHR_OK:
+               if (parse_loose_header(hdrbuf.len ? hdrbuf.buf : hdr, oi) < 0)
+                       status = error(_("unable to parse %s header"), oid_to_hex(oid));
+               else if (!allow_unknown && *oi->typep < 0)
+                       die(_("invalid object type"));
+
+               if (!oi->contentp)
+                       break;
+               *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
+               if (*oi->contentp)
+                       goto cleanup;
+
+               status = -1;
+               break;
+       case ULHR_BAD:
                status = error(_("unable to unpack %s header"),
                               oid_to_hex(oid));
-       if (status < 0)
-               ; /* Do nothing */
-       else if (hdrbuf.len) {
-               if ((status = parse_loose_header_extended(hdrbuf.buf, oi, flags)) < 0)
-                       status = error(_("unable to parse %s header with --allow-unknown-type"),
-                                      oid_to_hex(oid));
-       } else if ((status = parse_loose_header_extended(hdr, oi, flags)) < 0)
-               status = error(_("unable to parse %s header"), oid_to_hex(oid));
-
-       if (status >= 0 && oi->contentp) {
-               *oi->contentp = unpack_loose_rest(&stream, hdr,
-                                                 *oi->sizep, oid);
-               if (!*oi->contentp) {
-                       git_inflate_end(&stream);
-                       status = -1;
-               }
-       } else
-               git_inflate_end(&stream);
+               break;
+       case ULHR_TOO_LONG:
+               status = error(_("header for %s too long, exceeds %d bytes"),
+                              oid_to_hex(oid), MAX_HEADER_LEN);
+               break;
+       }
 
+       git_inflate_end(&stream);
+cleanup:
        munmap(map, mapsize);
-       if (status && oi->typep)
-               *oi->typep = status;
        if (oi->sizep == &size_scratch)
                oi->sizep = NULL;
        strbuf_release(&hdrbuf);
+       if (oi->typep == &type_scratch)
+               oi->typep = NULL;
        oi->whence = OI_LOOSE;
-       return (status < 0) ? status : 0;
+       return status;
 }
 
 int obj_read_use_lock = 0;
@@ -1592,6 +1528,17 @@ static int do_oid_object_info_extended(struct repository *r,
                                break;
                }
 
+               /*
+                * If r is the_repository, this might be an attempt at
+                * accessing a submodule object as if it were in the_repository
+                * (having called add_submodule_odb() on that submodule's ODB).
+                * If any such ODBs exist, register them and try again.
+                */
+               if (r == the_repository &&
+                   register_all_submodule_odb_as_alternates())
+                       /* We added some alternates; retry */
+                       continue;
+
                /* Check if it is a missing object */
                if (fetch_if_missing && repo_has_promisor_remote(r) &&
                    !already_retried &&
@@ -1616,7 +1563,7 @@ static int do_oid_object_info_extended(struct repository *r,
                return 0;
        rtype = packed_object_info(r, e.p, e.offset, oi);
        if (rtype < 0) {
-               mark_bad_packed_object(e.p, real->hash);
+               mark_bad_packed_object(e.p, real);
                return do_oid_object_info_extended(r, real, oi, 0);
        } else if (oi->whence == OI_PACKED) {
                oi->u.packed.offset = e.offset;
@@ -1725,7 +1672,7 @@ void *read_object_file_extended(struct repository *r,
                die(_("loose object %s (stored in %s) is corrupt"),
                    oid_to_hex(repl), path);
 
-       if ((p = has_packed_and_bad(r, repl->hash)) != NULL)
+       if ((p = has_packed_and_bad(r, repl)) != NULL)
                die(_("packed object %s (stored in %s) is corrupt"),
                    oid_to_hex(repl), p->pack_name);
        obj_read_unlock();
@@ -1915,7 +1862,7 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename)
 
 static int write_loose_object(const struct object_id *oid, char *hdr,
                              int hdrlen, const void *buf, unsigned long len,
-                             time_t mtime)
+                             time_t mtime, unsigned flags)
 {
        int fd, ret;
        unsigned char compressed[4096];
@@ -1929,7 +1876,9 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
 
        fd = create_tmpfile(&tmp_file, filename.buf);
        if (fd < 0) {
-               if (errno == EACCES)
+               if (flags & HASH_SILENT)
+                       return -1;
+               else if (errno == EACCES)
                        return error(_("insufficient permission for adding an object to repository database %s"), get_object_directory());
                else
                        return error_errno(_("unable to create temporary file"));
@@ -1979,7 +1928,8 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
                struct utimbuf utb;
                utb.actime = mtime;
                utb.modtime = mtime;
-               if (utime(tmp_file.buf, &utb) < 0)
+               if (utime(tmp_file.buf, &utb) < 0 &&
+                   !(flags & HASH_SILENT))
                        warning_errno(_("failed utime() on %s"), tmp_file.buf);
        }
 
@@ -2004,8 +1954,9 @@ static int freshen_packed_object(const struct object_id *oid)
        return 1;
 }
 
-int write_object_file(const void *buf, unsigned long len, const char *type,
-                     struct object_id *oid)
+int write_object_file_flags(const void *buf, unsigned long len,
+                           const char *type, struct object_id *oid,
+                           unsigned flags)
 {
        char hdr[MAX_HEADER_LEN];
        int hdrlen = sizeof(hdr);
@@ -2017,7 +1968,7 @@ int write_object_file(const void *buf, unsigned long len, const char *type,
                                  &hdrlen);
        if (freshen_packed_object(oid) || freshen_loose_object(oid))
                return 0;
-       return write_loose_object(oid, hdr, hdrlen, buf, len, 0);
+       return write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags);
 }
 
 int hash_object_file_literally(const void *buf, unsigned long len,
@@ -2037,7 +1988,7 @@ int hash_object_file_literally(const void *buf, unsigned long len,
                goto cleanup;
        if (freshen_packed_object(oid) || freshen_loose_object(oid))
                goto cleanup;
-       status = write_loose_object(oid, header, hdrlen, buf, len, 0);
+       status = write_loose_object(oid, header, hdrlen, buf, len, 0, 0);
 
 cleanup:
        free(header);
@@ -2059,7 +2010,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)
        if (!buf)
                return error(_("cannot read object for %s"), oid_to_hex(oid));
        hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX , type_name(type), (uintmax_t)len) + 1;
-       ret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime);
+       ret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0);
        free(buf);
 
        return ret;
@@ -2566,17 +2517,16 @@ static int check_stream_oid(git_zstream *stream,
 
 int read_loose_object(const char *path,
                      const struct object_id *expected_oid,
-                     enum object_type *type,
-                     unsigned long *size,
-                     void **contents)
+                     struct object_id *real_oid,
+                     void **contents,
+                     struct object_info *oi)
 {
        int ret = -1;
        void *map = NULL;
        unsigned long mapsize;
        git_zstream stream;
        char hdr[MAX_HEADER_LEN];
-
-       *contents = NULL;
+       unsigned long *size = oi->sizep;
 
        map = map_loose_object_1(the_repository, path, NULL, &mapsize);
        if (!map) {
@@ -2584,19 +2534,19 @@ int read_loose_object(const char *path,
                goto out;
        }
 
-       if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) {
+       if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
+                               NULL) < 0) {
                error(_("unable to unpack header of %s"), path);
                goto out;
        }
 
-       *type = parse_loose_header(hdr, size);
-       if (*type < 0) {
+       if (parse_loose_header(hdr, oi) < 0) {
                error(_("unable to parse header of %s"), path);
                git_inflate_end(&stream);
                goto out;
        }
 
-       if (*type == OBJ_BLOB && *size > big_file_threshold) {
+       if (*oi->typep == OBJ_BLOB && *size > big_file_threshold) {
                if (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0)
                        goto out;
        } else {
@@ -2607,10 +2557,7 @@ int read_loose_object(const char *path,
                        goto out;
                }
                if (check_object_signature(the_repository, expected_oid,
-                                          *contents, *size,
-                                          type_name(*type))) {
-                       error(_("hash mismatch for %s (expected %s)"), path,
-                             oid_to_hex(expected_oid));
+                                          *contents, *size, oi->type_name->buf, real_oid)) {
                        free(*contents);
                        goto out;
                }
index 3263c19457fa3ff89d95536a18a7b03f2d9beb2a..fdff4601b2c70cc7e4585096534382ddc5024990 100644 (file)
@@ -806,7 +806,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
                        refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
                        if (refs_found > 0) {
                                warning(warn_msg, len, str);
-                               if (advice_object_name_warning)
+                               if (advice_enabled(ADVICE_OBJECT_NAME_WARNING))
                                        fprintf(stderr, "%s\n", _(object_name_msg));
                        }
                        free(real_ref);
index d24915ced1b2dd2aedb8300741d83372d9ea53a0..952efb6a4be25bbf6947b789246eedc0a09bdcd5 100644 (file)
@@ -10,6 +10,7 @@
 #include "khash.h"
 #include "dir.h"
 #include "oidtree.h"
+#include "oidset.h"
 
 struct object_directory {
        struct object_directory *next;
@@ -38,6 +39,7 @@ KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
 
 void prepare_alt_odb(struct repository *r);
 char *compute_alternate_path(const char *path, struct strbuf *err);
+struct object_directory *find_odb(struct repository *r, const char *obj_dir);
 typedef int alt_odb_fn(struct object_directory *, void *);
 int foreach_alt_odb(alt_odb_fn, void*);
 typedef void alternate_ref_fn(const struct object_id *oid, void *);
@@ -75,9 +77,8 @@ struct packed_git {
        const void *index_data;
        size_t index_size;
        uint32_t num_objects;
-       uint32_t num_bad_objects;
        uint32_t crc_offset;
-       unsigned char *bad_object_sha1;
+       struct oidset bad_objects;
        int index_version;
        time_t mtime;
        int pack_fd;
@@ -222,8 +223,14 @@ int hash_object_file(const struct git_hash_algo *algo, const void *buf,
                     unsigned long len, const char *type,
                     struct object_id *oid);
 
-int write_object_file(const void *buf, unsigned long len,
-                     const char *type, struct object_id *oid);
+int write_object_file_flags(const void *buf, unsigned long len,
+                           const char *type, struct object_id *oid,
+                           unsigned flags);
+static inline int write_object_file(const void *buf, unsigned long len,
+                                   const char *type, struct object_id *oid)
+{
+       return write_object_file_flags(buf, len, type, oid, 0);
+}
 
 int hash_object_file_literally(const void *buf, unsigned long len,
                               const char *type, struct object_id *oid,
@@ -244,6 +251,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime);
 
 /*
  * Open the loose object at path, check its hash, and return the contents,
+ * use the "oi" argument to assert things about the object, or e.g. populate its
  * type, and size. If the object is a blob, then "contents" may return NULL,
  * to allow streaming of large blobs.
  *
@@ -251,9 +259,9 @@ int force_object_loose(const struct object_id *oid, time_t mtime);
  */
 int read_loose_object(const char *path,
                      const struct object_id *expected_oid,
-                     enum object_type *type,
-                     unsigned long *size,
-                     void **contents);
+                     struct object_id *real_oid,
+                     void **contents,
+                     struct object_info *oi);
 
 /* Retry packed storage after checking packed and loose storage */
 #define HAS_OBJECT_RECHECK_PACKED 1
@@ -370,7 +378,7 @@ struct object_info {
  * Initializer for a "struct object_info" that wants no items. You may
  * also memset() the memory to all-zeroes.
  */
-#define OBJECT_INFO_INIT {NULL}
+#define OBJECT_INFO_INIT { 0 }
 
 /* Invoke lookup_replace_object() on the given hash */
 #define OBJECT_INFO_LOOKUP_REPLACE 1
@@ -455,6 +463,12 @@ enum for_each_object_flags {
         * Visit objects within a pack in packfile order rather than .idx order
         */
        FOR_EACH_OBJECT_PACK_ORDER = (1<<2),
+
+       /* Only iterate over packs that are not marked as kept in-core. */
+       FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),
+
+       /* Only iterate over packs that do not have .keep files. */
+       FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
 };
 
 /*
index 4e85955a941168bc4bcd0f97ee98353402561dd1..23a24e678a8e33f82b417fa6f005aae7de7bc29f 100644 (file)
--- a/object.c
+++ b/object.c
@@ -279,7 +279,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid)
        if ((obj && obj->type == OBJ_BLOB && repo_has_object_file(r, oid)) ||
            (!obj && repo_has_object_file(r, oid) &&
             oid_object_info(r, oid, NULL) == OBJ_BLOB)) {
-               if (check_object_signature(r, repl, NULL, 0, NULL) < 0) {
+               if (check_object_signature(r, repl, NULL, 0, NULL, NULL) < 0) {
                        error(_("hash mismatch %s"), oid_to_hex(oid));
                        return NULL;
                }
@@ -290,7 +290,7 @@ struct object *parse_object(struct repository *r, const struct object_id *oid)
        buffer = repo_read_object_file(r, oid, &type, &size);
        if (buffer) {
                if (check_object_signature(r, repl, buffer, size,
-                                          type_name(type)) < 0) {
+                                          type_name(type), NULL) < 0) {
                        free(buffer);
                        error(_("hash mismatch %s"), oid_to_hex(repl));
                        return NULL;
index 3b38c9cc988b8fcc9bd2600ce53ed74614a24b19..cb556ab7753d5ddb01bc9421bbf490c0e684769b 100644 (file)
--- a/object.h
+++ b/object.h
@@ -55,7 +55,7 @@ struct object_array {
        } *objects;
 };
 
-#define OBJECT_ARRAY_INIT { 0, 0, NULL }
+#define OBJECT_ARRAY_INIT { 0 }
 
 /*
  * object flag allocation:
@@ -75,7 +75,6 @@ struct object_array {
  * builtin/fsck.c:           0--3
  * builtin/gc.c:             0
  * builtin/index-pack.c:                                     2021
- * builtin/pack-objects.c:                                   20
  * builtin/reflog.c:                   10--12
  * builtin/show-branch.c:    0-------------------------------------------26
  * builtin/unpack-objects.c:                                 2021
index 72bca78b7dc129313dfd2e0b6d9271179cda1ce0..f60f9af6741dc5acafb61b8a8a1562f49f92a600 100644 (file)
@@ -56,7 +56,7 @@ struct oid_array {
        int sorted;
 };
 
-#define OID_ARRAY_INIT { NULL, 0, 0, 0 }
+#define OID_ARRAY_INIT { 0 }
 
 /**
  * Add an item to the set. The object ID will be placed at the end of the array
index 5aac633c1f405580447001dfae114660e6120e90..b36a2bae86470236a51ffe6bec7792222de478fe 100644 (file)
--- a/oidset.c
+++ b/oidset.c
@@ -36,11 +36,6 @@ 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)
 {
        oidset_parse_file_carefully(set, path, NULL, NULL);
index 01f6560283c38660a010ab84d90c24c9a0219189..ba4a5a2cd3a7a233bc9ca2cb7cf4a58a1e5a122c 100644 (file)
--- a/oidset.h
+++ b/oidset.h
@@ -57,7 +57,10 @@ 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);
+static inline int oidset_size(const struct oidset *set)
+{
+       return kh_size(&set->set);
+}
 
 /**
  * Remove all entries from the oidset, freeing any resources associated with
index 88d9e696a546a8db20d3ff7147a1bb15f8651d36..9c55c1531e1f55c28ecb4767de17e815968d35f6 100644 (file)
@@ -48,7 +48,7 @@ void bitmap_writer_show_progress(int show)
 }
 
 /**
- * Build the initial type index for the packfile
+ * Build the initial type index for the packfile or multi-pack-index
  */
 void bitmap_writer_build_type_index(struct packing_data *to_pack,
                                    struct pack_idx_entry **index,
@@ -125,15 +125,20 @@ static inline void push_bitmapped_commit(struct commit *commit)
        writer.selected_nr++;
 }
 
-static uint32_t find_object_pos(const struct object_id *oid)
+static uint32_t find_object_pos(const struct object_id *oid, int *found)
 {
        struct object_entry *entry = packlist_find(writer.to_pack, oid);
 
        if (!entry) {
-               die("Failed to write bitmap index. Packfile doesn't have full closure "
+               if (found)
+                       *found = 0;
+               warning("Failed to write bitmap index. Packfile doesn't have full closure "
                        "(object %s is missing)", oid_to_hex(oid));
+               return 0;
        }
 
+       if (found)
+               *found = 1;
        return oe_in_pack_pos(writer.to_pack, entry);
 }
 
@@ -331,9 +336,10 @@ static void bitmap_builder_clear(struct bitmap_builder *bb)
        bb->commits_nr = bb->commits_alloc = 0;
 }
 
-static void fill_bitmap_tree(struct bitmap *bitmap,
-                            struct tree *tree)
+static int fill_bitmap_tree(struct bitmap *bitmap,
+                           struct tree *tree)
 {
+       int found;
        uint32_t pos;
        struct tree_desc desc;
        struct name_entry entry;
@@ -342,9 +348,11 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
         * If our bit is already set, then there is nothing to do. Both this
         * tree and all of its children will be set.
         */
-       pos = find_object_pos(&tree->object.oid);
+       pos = find_object_pos(&tree->object.oid, &found);
+       if (!found)
+               return -1;
        if (bitmap_get(bitmap, pos))
-               return;
+               return 0;
        bitmap_set(bitmap, pos);
 
        if (parse_tree(tree) < 0)
@@ -355,11 +363,15 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
        while (tree_entry(&desc, &entry)) {
                switch (object_type(entry.mode)) {
                case OBJ_TREE:
-                       fill_bitmap_tree(bitmap,
-                                        lookup_tree(the_repository, &entry.oid));
+                       if (fill_bitmap_tree(bitmap,
+                                            lookup_tree(the_repository, &entry.oid)) < 0)
+                               return -1;
                        break;
                case OBJ_BLOB:
-                       bitmap_set(bitmap, find_object_pos(&entry.oid));
+                       pos = find_object_pos(&entry.oid, &found);
+                       if (!found)
+                               return -1;
+                       bitmap_set(bitmap, pos);
                        break;
                default:
                        /* Gitlink, etc; not reachable */
@@ -368,15 +380,18 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
        }
 
        free_tree_buffer(tree);
+       return 0;
 }
 
-static void fill_bitmap_commit(struct bb_commit *ent,
-                              struct commit *commit,
-                              struct prio_queue *queue,
-                              struct prio_queue *tree_queue,
-                              struct bitmap_index *old_bitmap,
-                              const uint32_t *mapping)
+static int fill_bitmap_commit(struct bb_commit *ent,
+                             struct commit *commit,
+                             struct prio_queue *queue,
+                             struct prio_queue *tree_queue,
+                             struct bitmap_index *old_bitmap,
+                             const uint32_t *mapping)
 {
+       int found;
+       uint32_t pos;
        if (!ent->bitmap)
                ent->bitmap = bitmap_new();
 
@@ -401,11 +416,16 @@ static void fill_bitmap_commit(struct bb_commit *ent,
                 * Mark ourselves and queue our tree. The commit
                 * walk ensures we cover all parents.
                 */
-               bitmap_set(ent->bitmap, find_object_pos(&c->object.oid));
+               pos = find_object_pos(&c->object.oid, &found);
+               if (!found)
+                       return -1;
+               bitmap_set(ent->bitmap, pos);
                prio_queue_put(tree_queue, get_commit_tree(c));
 
                for (p = c->parents; p; p = p->next) {
-                       int pos = find_object_pos(&p->item->object.oid);
+                       pos = find_object_pos(&p->item->object.oid, &found);
+                       if (!found)
+                               return -1;
                        if (!bitmap_get(ent->bitmap, pos)) {
                                bitmap_set(ent->bitmap, pos);
                                prio_queue_put(queue, p->item);
@@ -413,8 +433,12 @@ static void fill_bitmap_commit(struct bb_commit *ent,
                }
        }
 
-       while (tree_queue->nr)
-               fill_bitmap_tree(ent->bitmap, prio_queue_get(tree_queue));
+       while (tree_queue->nr) {
+               if (fill_bitmap_tree(ent->bitmap,
+                                    prio_queue_get(tree_queue)) < 0)
+                       return -1;
+       }
+       return 0;
 }
 
 static void store_selected(struct bb_commit *ent, struct commit *commit)
@@ -432,7 +456,7 @@ static void store_selected(struct bb_commit *ent, struct commit *commit)
        kh_value(writer.bitmaps, hash_pos) = stored;
 }
 
-void bitmap_writer_build(struct packing_data *to_pack)
+int bitmap_writer_build(struct packing_data *to_pack)
 {
        struct bitmap_builder bb;
        size_t i;
@@ -441,6 +465,7 @@ void bitmap_writer_build(struct packing_data *to_pack)
        struct prio_queue tree_queue = { NULL };
        struct bitmap_index *old_bitmap;
        uint32_t *mapping;
+       int closed = 1; /* until proven otherwise */
 
        writer.bitmaps = kh_init_oid_map();
        writer.to_pack = to_pack;
@@ -463,8 +488,11 @@ void bitmap_writer_build(struct packing_data *to_pack)
                struct commit *child;
                int reused = 0;
 
-               fill_bitmap_commit(ent, commit, &queue, &tree_queue,
-                                  old_bitmap, mapping);
+               if (fill_bitmap_commit(ent, commit, &queue, &tree_queue,
+                                      old_bitmap, mapping) < 0) {
+                       closed = 0;
+                       break;
+               }
 
                if (ent->selected) {
                        store_selected(ent, commit);
@@ -492,6 +520,7 @@ void bitmap_writer_build(struct packing_data *to_pack)
        clear_prio_queue(&queue);
        clear_prio_queue(&tree_queue);
        bitmap_builder_clear(&bb);
+       free_bitmap_index(old_bitmap);
        free(mapping);
 
        trace2_region_leave("pack-bitmap-write", "building_bitmaps_total",
@@ -499,7 +528,9 @@ void bitmap_writer_build(struct packing_data *to_pack)
 
        stop_progress(&writer.progress);
 
-       compute_xor_offsets();
+       if (closed)
+               compute_xor_offsets();
+       return closed ? 0 : -1;
 }
 
 /**
index d999616c9e9d30d66aa68d90ea7935d3d4fe1372..f47a0a7db4dd30a6450b3e59e0ec463c5578da39 100644 (file)
@@ -13,6 +13,7 @@
 #include "repository.h"
 #include "object-store.h"
 #include "list-objects-filter-options.h"
+#include "midx.h"
 #include "config.h"
 
 /*
@@ -35,8 +36,15 @@ struct stored_bitmap {
  * the active bitmap index is the largest one.
  */
 struct bitmap_index {
-       /* Packfile to which this bitmap index belongs to */
+       /*
+        * The pack or multi-pack index (MIDX) that this bitmap index belongs
+        * to.
+        *
+        * Exactly one of these must be non-NULL; this specifies the object
+        * order used to interpret this bitmap.
+        */
        struct packed_git *pack;
+       struct multi_pack_index *midx;
 
        /*
         * Mark the first `reuse_objects` in the packfile as reused:
@@ -71,6 +79,9 @@ struct bitmap_index {
        /* If not NULL, this is a name-hash cache pointing into map. */
        uint32_t *hashes;
 
+       /* The checksum of the packfile or MIDX; points into map. */
+       const unsigned char *checksum;
+
        /*
         * Extended index.
         *
@@ -136,6 +147,13 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index)
        return b;
 }
 
+static uint32_t bitmap_num_objects(struct bitmap_index *index)
+{
+       if (index->midx)
+               return index->midx->num_objects;
+       return index->pack->num_objects;
+}
+
 static int load_bitmap_header(struct bitmap_index *index)
 {
        struct bitmap_disk_header *header = (void *)index->map;
@@ -154,7 +172,7 @@ static int load_bitmap_header(struct bitmap_index *index)
        /* Parse known bitmap format options */
        {
                uint32_t flags = ntohs(header->options);
-               size_t cache_size = st_mult(index->pack->num_objects, sizeof(uint32_t));
+               size_t cache_size = st_mult(bitmap_num_objects(index), sizeof(uint32_t));
                unsigned char *index_end = index->map + index->map_size - the_hash_algo->rawsz;
 
                if ((flags & BITMAP_OPT_FULL_DAG) == 0)
@@ -170,6 +188,7 @@ static int load_bitmap_header(struct bitmap_index *index)
        }
 
        index->entry_count = ntohl(header->entry_count);
+       index->checksum = header->checksum;
        index->map_pos += header_size;
        return 0;
 }
@@ -218,6 +237,15 @@ static inline uint8_t read_u8(const unsigned char *buffer, size_t *pos)
 
 #define MAX_XOR_OFFSET 160
 
+static int nth_bitmap_object_oid(struct bitmap_index *index,
+                                struct object_id *oid,
+                                uint32_t n)
+{
+       if (index->midx)
+               return nth_midxed_object_oid(oid, index->midx, n) ? 0 : -1;
+       return nth_packed_object_id(oid, index->pack, n);
+}
+
 static int load_bitmap_entries_v1(struct bitmap_index *index)
 {
        uint32_t i;
@@ -237,7 +265,7 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
                xor_offset = read_u8(index->map, &index->map_pos);
                flags = read_u8(index->map, &index->map_pos);
 
-               if (nth_packed_object_id(&oid, index->pack, commit_idx_pos) < 0)
+               if (nth_bitmap_object_oid(index, &oid, commit_idx_pos) < 0)
                        return error("corrupt ewah bitmap: commit index %u out of range",
                                     (unsigned)commit_idx_pos);
 
@@ -262,7 +290,14 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
        return 0;
 }
 
-static char *pack_bitmap_filename(struct packed_git *p)
+char *midx_bitmap_filename(struct multi_pack_index *midx)
+{
+       return xstrfmt("%s-%s.bitmap",
+                      get_midx_filename(midx->object_dir),
+                      hash_to_hex(get_midx_checksum(midx)));
+}
+
+char *pack_bitmap_filename(struct packed_git *p)
 {
        size_t len;
 
@@ -271,6 +306,57 @@ static char *pack_bitmap_filename(struct packed_git *p)
        return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
 }
 
+static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
+                             struct multi_pack_index *midx)
+{
+       struct stat st;
+       char *idx_name = midx_bitmap_filename(midx);
+       int fd = git_open(idx_name);
+
+       free(idx_name);
+
+       if (fd < 0)
+               return -1;
+
+       if (fstat(fd, &st)) {
+               close(fd);
+               return -1;
+       }
+
+       if (bitmap_git->pack || bitmap_git->midx) {
+               /* ignore extra bitmap file; we can only handle one */
+               warning("ignoring extra bitmap file: %s",
+                       get_midx_filename(midx->object_dir));
+               close(fd);
+               return -1;
+       }
+
+       bitmap_git->midx = midx;
+       bitmap_git->map_size = xsize_t(st.st_size);
+       bitmap_git->map_pos = 0;
+       bitmap_git->map = xmmap(NULL, bitmap_git->map_size, PROT_READ,
+                               MAP_PRIVATE, fd, 0);
+       close(fd);
+
+       if (load_bitmap_header(bitmap_git) < 0)
+               goto cleanup;
+
+       if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum))
+               goto cleanup;
+
+       if (load_midx_revindex(bitmap_git->midx) < 0) {
+               warning(_("multi-pack bitmap is missing required reverse index"));
+               goto cleanup;
+       }
+       return 0;
+
+cleanup:
+       munmap(bitmap_git->map, bitmap_git->map_size);
+       bitmap_git->map_size = 0;
+       bitmap_git->map = NULL;
+       return -1;
+}
+
 static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git *packfile)
 {
        int fd;
@@ -292,7 +378,8 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
                return -1;
        }
 
-       if (bitmap_git->pack) {
+       if (bitmap_git->pack || bitmap_git->midx) {
+               /* ignore extra bitmap file; we can only handle one */
                warning("ignoring extra bitmap file: %s", packfile->pack_name);
                close(fd);
                return -1;
@@ -319,13 +406,39 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
        return 0;
 }
 
-static int load_pack_bitmap(struct bitmap_index *bitmap_git)
+static int load_reverse_index(struct bitmap_index *bitmap_git)
+{
+       if (bitmap_is_midx(bitmap_git)) {
+               uint32_t i;
+               int ret;
+
+               /*
+                * The multi-pack-index's .rev file is already loaded via
+                * open_pack_bitmap_1().
+                *
+                * But we still need to open the individual pack .rev files,
+                * since we will need to make use of them in pack-objects.
+                */
+               for (i = 0; i < bitmap_git->midx->num_packs; i++) {
+                       if (prepare_midx_pack(the_repository, bitmap_git->midx, i))
+                               die(_("load_reverse_index: could not open pack"));
+                       ret = load_pack_revindex(bitmap_git->midx->packs[i]);
+                       if (ret)
+                               return ret;
+               }
+               return 0;
+       }
+       return load_pack_revindex(bitmap_git->pack);
+}
+
+static int load_bitmap(struct bitmap_index *bitmap_git)
 {
        assert(bitmap_git->map);
 
        bitmap_git->bitmaps = kh_init_oid_map();
        bitmap_git->ext_index.positions = kh_init_oid_pos();
-       if (load_pack_revindex(bitmap_git->pack))
+
+       if (load_reverse_index(bitmap_git))
                goto failed;
 
        if (!(bitmap_git->commits = read_bitmap_1(bitmap_git)) ||
@@ -369,11 +482,46 @@ static int open_pack_bitmap(struct repository *r,
        return ret;
 }
 
+static int open_midx_bitmap(struct repository *r,
+                           struct bitmap_index *bitmap_git)
+{
+       struct multi_pack_index *midx;
+
+       assert(!bitmap_git->map);
+
+       for (midx = get_multi_pack_index(r); midx; midx = midx->next) {
+               if (!open_midx_bitmap_1(bitmap_git, midx))
+                       return 0;
+       }
+       return -1;
+}
+
+static int open_bitmap(struct repository *r,
+                      struct bitmap_index *bitmap_git)
+{
+       assert(!bitmap_git->map);
+
+       if (!open_midx_bitmap(r, bitmap_git))
+               return 0;
+       return open_pack_bitmap(r, bitmap_git);
+}
+
 struct bitmap_index *prepare_bitmap_git(struct repository *r)
 {
        struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
 
-       if (!open_pack_bitmap(r, bitmap_git) && !load_pack_bitmap(bitmap_git))
+       if (!open_bitmap(r, bitmap_git) && !load_bitmap(bitmap_git))
+               return bitmap_git;
+
+       free_bitmap_index(bitmap_git);
+       return NULL;
+}
+
+struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx)
+{
+       struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
+
+       if (!open_midx_bitmap_1(bitmap_git, midx) && !load_bitmap(bitmap_git))
                return bitmap_git;
 
        free_bitmap_index(bitmap_git);
@@ -404,7 +552,7 @@ static inline int bitmap_position_extended(struct bitmap_index *bitmap_git,
 
        if (pos < kh_end(positions)) {
                int bitmap_pos = kh_value(positions, pos);
-               return bitmap_pos + bitmap_git->pack->num_objects;
+               return bitmap_pos + bitmap_num_objects(bitmap_git);
        }
 
        return -1;
@@ -423,10 +571,26 @@ static inline int bitmap_position_packfile(struct bitmap_index *bitmap_git,
        return pos;
 }
 
+static int bitmap_position_midx(struct bitmap_index *bitmap_git,
+                               const struct object_id *oid)
+{
+       uint32_t want, got;
+       if (!bsearch_midx(oid, bitmap_git->midx, &want))
+               return -1;
+
+       if (midx_to_pack_pos(bitmap_git->midx, want, &got) < 0)
+               return -1;
+       return got;
+}
+
 static int bitmap_position(struct bitmap_index *bitmap_git,
                           const struct object_id *oid)
 {
-       int pos = bitmap_position_packfile(bitmap_git, oid);
+       int pos;
+       if (bitmap_is_midx(bitmap_git))
+               pos = bitmap_position_midx(bitmap_git, oid);
+       else
+               pos = bitmap_position_packfile(bitmap_git, oid);
        return (pos >= 0) ? pos : bitmap_position_extended(bitmap_git, oid);
 }
 
@@ -456,7 +620,7 @@ static int ext_index_add_object(struct bitmap_index *bitmap_git,
                bitmap_pos = kh_value(eindex->positions, hash_pos);
        }
 
-       return bitmap_pos + bitmap_git->pack->num_objects;
+       return bitmap_pos + bitmap_num_objects(bitmap_git);
 }
 
 struct bitmap_show_data {
@@ -673,7 +837,7 @@ static void show_extended_objects(struct bitmap_index *bitmap_git,
        for (i = 0; i < eindex->count; ++i) {
                struct object *obj;
 
-               if (!bitmap_get(objects, bitmap_git->pack->num_objects + i))
+               if (!bitmap_get(objects, bitmap_num_objects(bitmap_git) + i))
                        continue;
 
                obj = eindex->objects[i];
@@ -737,6 +901,7 @@ static void show_objects_for_type(
                        continue;
 
                for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+                       struct packed_git *pack;
                        struct object_id oid;
                        uint32_t hash = 0, index_pos;
                        off_t ofs;
@@ -746,14 +911,28 @@ static void show_objects_for_type(
 
                        offset += ewah_bit_ctz64(word >> offset);
 
-                       index_pos = pack_pos_to_index(bitmap_git->pack, pos + offset);
-                       ofs = pack_pos_to_offset(bitmap_git->pack, pos + offset);
-                       nth_packed_object_id(&oid, bitmap_git->pack, index_pos);
+                       if (bitmap_is_midx(bitmap_git)) {
+                               struct multi_pack_index *m = bitmap_git->midx;
+                               uint32_t pack_id;
+
+                               index_pos = pack_pos_to_midx(m, pos + offset);
+                               ofs = nth_midxed_offset(m, index_pos);
+                               nth_midxed_object_oid(&oid, m, index_pos);
+
+                               pack_id = nth_midxed_pack_int_id(m, index_pos);
+                               pack = bitmap_git->midx->packs[pack_id];
+                       } else {
+                               index_pos = pack_pos_to_index(bitmap_git->pack, pos + offset);
+                               ofs = pack_pos_to_offset(bitmap_git->pack, pos + offset);
+                               nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
+
+                               pack = bitmap_git->pack;
+                       }
 
                        if (bitmap_git->hashes)
                                hash = get_be32(bitmap_git->hashes + index_pos);
 
-                       show_reach(&oid, object_type, 0, hash, bitmap_git->pack, ofs);
+                       show_reach(&oid, object_type, 0, hash, pack, ofs);
                }
        }
 }
@@ -765,8 +944,13 @@ static int in_bitmapped_pack(struct bitmap_index *bitmap_git,
                struct object *object = roots->item;
                roots = roots->next;
 
-               if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0)
-                       return 1;
+               if (bitmap_is_midx(bitmap_git)) {
+                       if (bsearch_midx(&object->oid, bitmap_git->midx, NULL))
+                               return 1;
+               } else {
+                       if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0)
+                               return 1;
+               }
        }
 
        return 0;
@@ -832,7 +1016,7 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git,
         * them individually.
         */
        for (i = 0; i < eindex->count; i++) {
-               uint32_t pos = i + bitmap_git->pack->num_objects;
+               uint32_t pos = i + bitmap_num_objects(bitmap_git);
                if (eindex->objects[i]->type == type &&
                    bitmap_get(to_filter, pos) &&
                    !bitmap_get(tips, pos))
@@ -853,23 +1037,35 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
 static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,
                                     uint32_t pos)
 {
-       struct packed_git *pack = bitmap_git->pack;
        unsigned long size;
        struct object_info oi = OBJECT_INFO_INIT;
 
        oi.sizep = &size;
 
-       if (pos < pack->num_objects) {
-               off_t ofs = pack_pos_to_offset(pack, pos);
+       if (pos < bitmap_num_objects(bitmap_git)) {
+               struct packed_git *pack;
+               off_t ofs;
+
+               if (bitmap_is_midx(bitmap_git)) {
+                       uint32_t midx_pos = pack_pos_to_midx(bitmap_git->midx, pos);
+                       uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos);
+
+                       pack = bitmap_git->midx->packs[pack_id];
+                       ofs = nth_midxed_offset(bitmap_git->midx, midx_pos);
+               } else {
+                       pack = bitmap_git->pack;
+                       ofs = pack_pos_to_offset(pack, pos);
+               }
+
                if (packed_object_info(the_repository, pack, ofs, &oi) < 0) {
                        struct object_id oid;
-                       nth_packed_object_id(&oid, pack,
-                                            pack_pos_to_index(pack, pos));
+                       nth_bitmap_object_oid(bitmap_git, &oid,
+                                             pack_pos_to_index(pack, pos));
                        die(_("unable to get size of %s"), oid_to_hex(&oid));
                }
        } else {
                struct eindex *eindex = &bitmap_git->ext_index;
-               struct object *obj = eindex->objects[pos - pack->num_objects];
+               struct object *obj = eindex->objects[pos - bitmap_num_objects(bitmap_git)];
                if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0)
                        die(_("unable to get size of %s"), oid_to_hex(&obj->oid));
        }
@@ -911,7 +1107,7 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git,
        }
 
        for (i = 0; i < eindex->count; i++) {
-               uint32_t pos = i + bitmap_git->pack->num_objects;
+               uint32_t pos = i + bitmap_num_objects(bitmap_git);
                if (eindex->objects[i]->type == OBJ_BLOB &&
                    bitmap_get(to_filter, pos) &&
                    !bitmap_get(tips, pos) &&
@@ -1041,7 +1237,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
        /* try to open a bitmapped pack, but don't parse it yet
         * because we may not need to use it */
        CALLOC_ARRAY(bitmap_git, 1);
-       if (open_pack_bitmap(revs->repo, bitmap_git) < 0)
+       if (open_bitmap(revs->repo, bitmap_git) < 0)
                goto cleanup;
 
        for (i = 0; i < revs->pending.nr; ++i) {
@@ -1085,7 +1281,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
         * from disk. this is the point of no return; after this the rev_list
         * becomes invalidated and we must perform the revwalk through bitmaps
         */
-       if (load_pack_bitmap(bitmap_git) < 0)
+       if (load_bitmap(bitmap_git) < 0)
                goto cleanup;
 
        object_array_clear(&revs->pending);
@@ -1128,22 +1324,49 @@ cleanup:
        return NULL;
 }
 
-static void try_partial_reuse(struct bitmap_index *bitmap_git,
-                             size_t pos,
-                             struct bitmap *reuse,
-                             struct pack_window **w_curs)
+/*
+ * -1 means "stop trying further objects"; 0 means we may or may not have
+ * reused, but you can keep feeding bits.
+ */
+static int try_partial_reuse(struct packed_git *pack,
+                            size_t pos,
+                            struct bitmap *reuse,
+                            struct pack_window **w_curs)
 {
-       off_t offset, header;
+       off_t offset, delta_obj_offset;
        enum object_type type;
        unsigned long size;
 
-       if (pos >= bitmap_git->pack->num_objects)
-               return; /* not actually in the pack */
+       /*
+        * try_partial_reuse() is called either on (a) objects in the
+        * bitmapped pack (in the case of a single-pack bitmap) or (b)
+        * objects in the preferred pack of a multi-pack bitmap.
+        * Importantly, the latter can pretend as if only a single pack
+        * exists because:
+        *
+        *   - The first pack->num_objects bits of a MIDX bitmap are
+        *     reserved for the preferred pack, and
+        *
+        *   - Ties due to duplicate objects are always resolved in
+        *     favor of the preferred pack.
+        *
+        * Therefore we do not need to ever ask the MIDX for its copy of
+        * an object by OID, since it will always select it from the
+        * preferred pack. Likewise, the selected copy of the base
+        * object for any deltas will reside in the same pack.
+        *
+        * This means that we can reuse pos when looking up the bit in
+        * the reuse bitmap, too, since bits corresponding to the
+        * preferred pack precede all bits from other packs.
+        */
+
+       if (pos >= pack->num_objects)
+               return -1; /* not actually in the pack or MIDX preferred pack */
 
-       offset = header = pack_pos_to_offset(bitmap_git->pack, pos);
-       type = unpack_object_header(bitmap_git->pack, w_curs, &offset, &size);
+       offset = delta_obj_offset = pack_pos_to_offset(pack, pos);
+       type = unpack_object_header(pack, w_curs, &offset, &size);
        if (type < 0)
-               return; /* broken packfile, punt */
+               return -1; /* broken packfile, punt */
 
        if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) {
                off_t base_offset;
@@ -1157,12 +1380,12 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
                 * and the normal slow path will complain about it in
                 * more detail.
                 */
-               base_offset = get_delta_base(bitmap_git->pack, w_curs,
-                                            &offset, type, header);
+               base_offset = get_delta_base(pack, w_curs, &offset, type,
+                                            delta_obj_offset);
                if (!base_offset)
-                       return;
-               if (offset_to_pack_pos(bitmap_git->pack, base_offset, &base_pos) < 0)
-                       return;
+                       return 0;
+               if (offset_to_pack_pos(pack, base_offset, &base_pos) < 0)
+                       return 0;
 
                /*
                 * We assume delta dependencies always point backwards. This
@@ -1174,7 +1397,7 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
                 * odd parameters.
                 */
                if (base_pos >= pos)
-                       return;
+                       return 0;
 
                /*
                 * And finally, if we're not sending the base as part of our
@@ -1185,13 +1408,22 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
                 * object_entry code path handle it.
                 */
                if (!bitmap_get(reuse, base_pos))
-                       return;
+                       return 0;
        }
 
        /*
         * If we got here, then the object is OK to reuse. Mark it.
         */
        bitmap_set(reuse, pos);
+       return 0;
+}
+
+uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git)
+{
+       struct multi_pack_index *m = bitmap_git->midx;
+       if (!m)
+               BUG("midx_preferred_pack: requires non-empty MIDX");
+       return nth_midxed_pack_int_id(m, pack_pos_to_midx(bitmap_git->midx, 0));
 }
 
 int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
@@ -1199,20 +1431,37 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
                                       uint32_t *entries,
                                       struct bitmap **reuse_out)
 {
+       struct packed_git *pack;
        struct bitmap *result = bitmap_git->result;
        struct bitmap *reuse;
        struct pack_window *w_curs = NULL;
        size_t i = 0;
        uint32_t offset;
+       uint32_t objects_nr;
 
        assert(result);
 
+       load_reverse_index(bitmap_git);
+
+       if (bitmap_is_midx(bitmap_git))
+               pack = bitmap_git->midx->packs[midx_preferred_pack(bitmap_git)];
+       else
+               pack = bitmap_git->pack;
+       objects_nr = pack->num_objects;
+
        while (i < result->word_alloc && result->words[i] == (eword_t)~0)
                i++;
 
-       /* Don't mark objects not in the packfile */
-       if (i > bitmap_git->pack->num_objects / BITS_IN_EWORD)
-               i = bitmap_git->pack->num_objects / BITS_IN_EWORD;
+       /*
+        * Don't mark objects not in the packfile or preferred pack. This bitmap
+        * marks objects eligible for reuse, but the pack-reuse code only
+        * understands how to reuse a single pack. Since the preferred pack is
+        * guaranteed to have all bases for its deltas (in a multi-pack bitmap),
+        * we use it instead of another pack. In single-pack bitmaps, the choice
+        * is made for us.
+        */
+       if (i > objects_nr / BITS_IN_EWORD)
+               i = objects_nr / BITS_IN_EWORD;
 
        reuse = bitmap_word_alloc(i);
        memset(reuse->words, 0xFF, i * sizeof(eword_t));
@@ -1226,10 +1475,23 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
                                break;
 
                        offset += ewah_bit_ctz64(word >> offset);
-                       try_partial_reuse(bitmap_git, pos + offset, reuse, &w_curs);
+                       if (try_partial_reuse(pack, pos + offset,
+                                             reuse, &w_curs) < 0) {
+                               /*
+                                * try_partial_reuse indicated we couldn't reuse
+                                * any bits, so there is no point in trying more
+                                * bits in the current word, or any other words
+                                * in result.
+                                *
+                                * Jump out of both loops to avoid future
+                                * unnecessary calls to try_partial_reuse.
+                                */
+                               goto done;
+                       }
                }
        }
 
+done:
        unuse_pack(&w_curs);
 
        *entries = bitmap_popcount(reuse);
@@ -1243,7 +1505,7 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
         * need to be handled separately.
         */
        bitmap_and_not(result, reuse);
-       *packfile_out = bitmap_git->pack;
+       *packfile_out = pack;
        *reuse_out = reuse;
        return 0;
 }
@@ -1296,7 +1558,7 @@ static uint32_t count_object_type(struct bitmap_index *bitmap_git,
 
        for (i = 0; i < eindex->count; ++i) {
                if (eindex->objects[i]->type == type &&
-                       bitmap_get(objects, bitmap_git->pack->num_objects + i))
+                       bitmap_get(objects, bitmap_num_objects(bitmap_git) + i))
                        count++;
        }
 
@@ -1325,10 +1587,52 @@ void count_bitmap_commit_list(struct bitmap_index *bitmap_git,
 struct bitmap_test_data {
        struct bitmap_index *bitmap_git;
        struct bitmap *base;
+       struct bitmap *commits;
+       struct bitmap *trees;
+       struct bitmap *blobs;
+       struct bitmap *tags;
        struct progress *prg;
        size_t seen;
 };
 
+static void test_bitmap_type(struct bitmap_test_data *tdata,
+                            struct object *obj, int pos)
+{
+       enum object_type bitmap_type = OBJ_NONE;
+       int bitmaps_nr = 0;
+
+       if (bitmap_get(tdata->commits, pos)) {
+               bitmap_type = OBJ_COMMIT;
+               bitmaps_nr++;
+       }
+       if (bitmap_get(tdata->trees, pos)) {
+               bitmap_type = OBJ_TREE;
+               bitmaps_nr++;
+       }
+       if (bitmap_get(tdata->blobs, pos)) {
+               bitmap_type = OBJ_BLOB;
+               bitmaps_nr++;
+       }
+       if (bitmap_get(tdata->tags, pos)) {
+               bitmap_type = OBJ_TAG;
+               bitmaps_nr++;
+       }
+
+       if (bitmap_type == OBJ_NONE)
+               die("object %s not found in type bitmaps",
+                   oid_to_hex(&obj->oid));
+
+       if (bitmaps_nr > 1)
+               die("object %s does not have a unique type",
+                   oid_to_hex(&obj->oid));
+
+       if (bitmap_type != obj->type)
+               die("object %s: real type %s, expected: %s",
+                   oid_to_hex(&obj->oid),
+                   type_name(obj->type),
+                   type_name(bitmap_type));
+}
+
 static void test_show_object(struct object *object, const char *name,
                             void *data)
 {
@@ -1338,6 +1642,7 @@ static void test_show_object(struct object *object, const char *name,
        bitmap_pos = bitmap_position(tdata->bitmap_git, &object->oid);
        if (bitmap_pos < 0)
                die("Object not in bitmap: %s\n", oid_to_hex(&object->oid));
+       test_bitmap_type(tdata, object, bitmap_pos);
 
        bitmap_set(tdata->base, bitmap_pos);
        display_progress(tdata->prg, ++tdata->seen);
@@ -1352,6 +1657,7 @@ static void test_show_commit(struct commit *commit, void *data)
                                     &commit->object.oid);
        if (bitmap_pos < 0)
                die("Object not in bitmap: %s\n", oid_to_hex(&commit->object.oid));
+       test_bitmap_type(tdata, &commit->object, bitmap_pos);
 
        bitmap_set(tdata->base, bitmap_pos);
        display_progress(tdata->prg, ++tdata->seen);
@@ -1399,6 +1705,10 @@ void test_bitmap_walk(struct rev_info *revs)
 
        tdata.bitmap_git = bitmap_git;
        tdata.base = bitmap_new();
+       tdata.commits = ewah_to_bitmap(bitmap_git->commits);
+       tdata.trees = ewah_to_bitmap(bitmap_git->trees);
+       tdata.blobs = ewah_to_bitmap(bitmap_git->blobs);
+       tdata.tags = ewah_to_bitmap(bitmap_git->tags);
        tdata.prg = start_progress("Verifying bitmap entries", result_popcnt);
        tdata.seen = 0;
 
@@ -1432,6 +1742,33 @@ int test_bitmap_commits(struct repository *r)
        return 0;
 }
 
+int test_bitmap_hashes(struct repository *r)
+{
+       struct bitmap_index *bitmap_git = prepare_bitmap_git(r);
+       struct object_id oid;
+       uint32_t i, index_pos;
+
+       if (!bitmap_git->hashes)
+               goto cleanup;
+
+       for (i = 0; i < bitmap_num_objects(bitmap_git); i++) {
+               if (bitmap_is_midx(bitmap_git))
+                       index_pos = pack_pos_to_midx(bitmap_git->midx, i);
+               else
+                       index_pos = pack_pos_to_index(bitmap_git->pack, i);
+
+               nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
+
+               printf("%s %"PRIu32"\n",
+                      oid_to_hex(&oid), get_be32(bitmap_git->hashes + index_pos));
+       }
+
+cleanup:
+       free_bitmap_index(bitmap_git);
+
+       return 0;
+}
+
 int rebuild_bitmap(const uint32_t *reposition,
                   struct ewah_bitmap *source,
                   struct bitmap *dest)
@@ -1469,19 +1806,32 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
        uint32_t i, num_objects;
        uint32_t *reposition;
 
-       num_objects = bitmap_git->pack->num_objects;
+       if (!bitmap_is_midx(bitmap_git))
+               load_reverse_index(bitmap_git);
+       else if (load_midx_revindex(bitmap_git->midx) < 0)
+               BUG("rebuild_existing_bitmaps: missing required rev-cache "
+                   "extension");
+
+       num_objects = bitmap_num_objects(bitmap_git);
        CALLOC_ARRAY(reposition, num_objects);
 
        for (i = 0; i < num_objects; ++i) {
                struct object_id oid;
                struct object_entry *oe;
+               uint32_t index_pos;
 
-               nth_packed_object_id(&oid, bitmap_git->pack,
-                                    pack_pos_to_index(bitmap_git->pack, i));
+               if (bitmap_is_midx(bitmap_git))
+                       index_pos = pack_pos_to_midx(bitmap_git->midx, i);
+               else
+                       index_pos = pack_pos_to_index(bitmap_git->pack, i);
+               nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
                oe = packlist_find(mapping, &oid);
 
-               if (oe)
+               if (oe) {
                        reposition[i] = oe_in_pack_pos(mapping, oe) + 1;
+                       if (bitmap_git->hashes && !oe->hash)
+                               oe->hash = get_be32(bitmap_git->hashes + index_pos);
+               }
        }
 
        return reposition;
@@ -1503,6 +1853,19 @@ void free_bitmap_index(struct bitmap_index *b)
        free(b->ext_index.hashes);
        bitmap_free(b->result);
        bitmap_free(b->haves);
+       if (bitmap_is_midx(b)) {
+               /*
+                * Multi-pack bitmaps need to have resources associated with
+                * their on-disk reverse indexes unmapped so that stale .rev and
+                * .bitmap files can be removed.
+                *
+                * Unlike pack-based bitmaps, multi-pack bitmaps can be read and
+                * written in the same 'git multi-pack-index write --bitmap'
+                * process. Close resources so they can be removed safely on
+                * platforms like Windows.
+                */
+               close_midx_revindex(b->midx);
+       }
        free(b);
 }
 
@@ -1517,7 +1880,6 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
                                     enum object_type object_type)
 {
        struct bitmap *result = bitmap_git->result;
-       struct packed_git *pack = bitmap_git->pack;
        off_t total = 0;
        struct ewah_iterator it;
        eword_t filter;
@@ -1534,15 +1896,35 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
                        continue;
 
                for (offset = 0; offset < BITS_IN_EWORD; offset++) {
-                       size_t pos;
-
                        if ((word >> offset) == 0)
                                break;
 
                        offset += ewah_bit_ctz64(word >> offset);
-                       pos = base + offset;
-                       total += pack_pos_to_offset(pack, pos + 1) -
-                                pack_pos_to_offset(pack, pos);
+
+                       if (bitmap_is_midx(bitmap_git)) {
+                               uint32_t pack_pos;
+                               uint32_t midx_pos = pack_pos_to_midx(bitmap_git->midx, base + offset);
+                               off_t offset = nth_midxed_offset(bitmap_git->midx, midx_pos);
+
+                               uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos);
+                               struct packed_git *pack = bitmap_git->midx->packs[pack_id];
+
+                               if (offset_to_pack_pos(pack, offset, &pack_pos) < 0) {
+                                       struct object_id oid;
+                                       nth_midxed_object_oid(&oid, bitmap_git->midx, midx_pos);
+
+                                       die(_("could not find %s in pack %s at offset %"PRIuMAX),
+                                           oid_to_hex(&oid),
+                                           pack->pack_name,
+                                           (uintmax_t)offset);
+                               }
+
+                               total += pack_pos_to_offset(pack, pack_pos + 1) - offset;
+                       } else {
+                               size_t pos = base + offset;
+                               total += pack_pos_to_offset(bitmap_git->pack, pos + 1) -
+                                        pack_pos_to_offset(bitmap_git->pack, pos);
+                       }
                }
        }
 
@@ -1552,7 +1934,6 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
 static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git)
 {
        struct bitmap *result = bitmap_git->result;
-       struct packed_git *pack = bitmap_git->pack;
        struct eindex *eindex = &bitmap_git->ext_index;
        off_t total = 0;
        struct object_info oi = OBJECT_INFO_INIT;
@@ -1564,7 +1945,7 @@ static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git)
        for (i = 0; i < eindex->count; i++) {
                struct object *obj = eindex->objects[i];
 
-               if (!bitmap_get(result, pack->num_objects + i))
+               if (!bitmap_get(result, bitmap_num_objects(bitmap_git) + i))
                        continue;
 
                if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0)
@@ -1594,7 +1975,28 @@ off_t get_disk_usage_from_bitmap(struct bitmap_index *bitmap_git,
        return total;
 }
 
+int bitmap_is_midx(struct bitmap_index *bitmap_git)
+{
+       return !!bitmap_git->midx;
+}
+
 const struct string_list *bitmap_preferred_tips(struct repository *r)
 {
        return repo_config_get_value_multi(r, "pack.preferbitmaptips");
 }
+
+int bitmap_is_preferred_refname(struct repository *r, const char *refname)
+{
+       const struct string_list *preferred_tips = bitmap_preferred_tips(r);
+       struct string_list_item *item;
+
+       if (!preferred_tips)
+               return 0;
+
+       for_each_string_list_item(item, preferred_tips) {
+               if (starts_with(refname, item->string))
+                       return 1;
+       }
+
+       return 0;
+}
index 99d733eb264e9f77b83da58033db5bec44b6702b..19a63fa1abc6921df7470b84aa7275c0e7863fac 100644 (file)
@@ -44,6 +44,7 @@ typedef int (*show_reachable_fn)(
 struct bitmap_index;
 
 struct bitmap_index *prepare_bitmap_git(struct repository *r);
+struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx);
 void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits,
                              uint32_t *trees, uint32_t *blobs, uint32_t *tags);
 void traverse_bitmap_commit_list(struct bitmap_index *,
@@ -51,9 +52,11 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
                                 show_reachable_fn show_reachable);
 void test_bitmap_walk(struct rev_info *revs);
 int test_bitmap_commits(struct repository *r);
+int test_bitmap_hashes(struct repository *r);
 struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
                                         struct list_objects_filter_options *filter,
                                         int filter_provided_objects);
+uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git);
 int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
                                       struct packed_git **packfile,
                                       uint32_t *entries,
@@ -87,12 +90,17 @@ struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
                                      struct commit *commit);
 void bitmap_writer_select_commits(struct commit **indexed_commits,
                unsigned int indexed_commits_nr, int max_bitmaps);
-void bitmap_writer_build(struct packing_data *to_pack);
+int bitmap_writer_build(struct packing_data *to_pack);
 void bitmap_writer_finish(struct pack_idx_entry **index,
                          uint32_t index_nr,
                          const char *filename,
                          uint16_t options);
+char *midx_bitmap_filename(struct multi_pack_index *midx);
+char *pack_bitmap_filename(struct packed_git *p);
+
+int bitmap_is_midx(struct bitmap_index *bitmap_git);
 
 const struct string_list *bitmap_preferred_tips(struct repository *r);
+int bitmap_is_preferred_refname(struct repository *r, const char *refname);
 
 #endif
index c8e560d71ab7e558da1a332d0640e6a71df5ac8c..3f418e3a6afb8ec7a8b401dcca89d5550813748f 100644 (file)
@@ -142,7 +142,8 @@ static int verify_packfile(struct repository *r,
                        err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
                                    oid_to_hex(&oid), p->pack_name,
                                    (uintmax_t)entries[i].offset);
-               else if (check_object_signature(r, &oid, data, size, type_name(type)))
+               else if (check_object_signature(r, &oid, data, size,
+                                               type_name(type), NULL))
                        err = error("packed %s from %s is corrupt",
                                    oid_to_hex(&oid), p->pack_name);
                else if (fn) {
index 9ef6d98292808718dfadc3b9efec0fb0dc2275f5..89402cfc69cd0f03b268f26bd6c8c7ad459b3e1b 100644 (file)
@@ -339,6 +339,7 @@ void close_pack(struct packed_git *p)
        close_pack_fd(p);
        close_pack_index(p);
        close_pack_revindex(p);
+       oidset_clear(&p->bad_objects);
 }
 
 void close_object_store(struct raw_object_store *o)
@@ -860,7 +861,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
        if (!strcmp(file_name, "multi-pack-index"))
                return;
        if (starts_with(file_name, "multi-pack-index") &&
-           ends_with(file_name, ".rev"))
+           (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev")))
                return;
        if (ends_with(file_name, ".idx") ||
            ends_with(file_name, ".rev") ||
@@ -1161,31 +1162,19 @@ int unpack_object_header(struct packed_git *p,
        return type;
 }
 
-void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1)
+void mark_bad_packed_object(struct packed_git *p, const struct object_id *oid)
 {
-       unsigned i;
-       const unsigned hashsz = the_hash_algo->rawsz;
-       for (i = 0; i < p->num_bad_objects; i++)
-               if (hasheq(sha1, p->bad_object_sha1 + hashsz * i))
-                       return;
-       p->bad_object_sha1 = xrealloc(p->bad_object_sha1,
-                                     st_mult(GIT_MAX_RAWSZ,
-                                             st_add(p->num_bad_objects, 1)));
-       hashcpy(p->bad_object_sha1 + hashsz * p->num_bad_objects, sha1);
-       p->num_bad_objects++;
+       oidset_insert(&p->bad_objects, oid);
 }
 
 const struct packed_git *has_packed_and_bad(struct repository *r,
-                                           const unsigned char *sha1)
+                                           const struct object_id *oid)
 {
        struct packed_git *p;
-       unsigned i;
 
        for (p = r->objects->packed_git; p; p = p->next)
-               for (i = 0; i < p->num_bad_objects; i++)
-                       if (hasheq(sha1,
-                                  p->bad_object_sha1 + the_hash_algo->rawsz * i))
-                               return p;
+               if (oidset_contains(&p->bad_objects, oid))
+                       return p;
        return NULL;
 }
 
@@ -1272,7 +1261,7 @@ static int retry_bad_packed_offset(struct repository *r,
        if (offset_to_pack_pos(p, obj_offset, &pos) < 0)
                return OBJ_BAD;
        nth_packed_object_id(&oid, p, pack_pos_to_index(p, pos));
-       mark_bad_packed_object(p, oid.hash);
+       mark_bad_packed_object(p, &oid);
        type = oid_object_info(r, &oid, NULL);
        if (type <= OBJ_NONE)
                return OBJ_BAD;
@@ -1722,7 +1711,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
                                nth_packed_object_id(&oid, p, index_pos);
                                error("bad packed object CRC for %s",
                                      oid_to_hex(&oid));
-                               mark_bad_packed_object(p, oid.hash);
+                               mark_bad_packed_object(p, &oid);
                                data = NULL;
                                goto out;
                        }
@@ -1811,7 +1800,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
                                      " at offset %"PRIuMAX" from %s",
                                      oid_to_hex(&base_oid), (uintmax_t)obj_offset,
                                      p->pack_name);
-                               mark_bad_packed_object(p, base_oid.hash);
+                               mark_bad_packed_object(p, &base_oid);
                                base = read_object(r, &base_oid, &type, &base_size);
                                external_base = base;
                        }
@@ -2016,13 +2005,9 @@ static int fill_pack_entry(const struct object_id *oid,
 {
        off_t offset;
 
-       if (p->num_bad_objects) {
-               unsigned i;
-               for (i = 0; i < p->num_bad_objects; i++)
-                       if (hasheq(oid->hash,
-                                  p->bad_object_sha1 + the_hash_algo->rawsz * i))
-                               return 0;
-       }
+       if (oidset_size(&p->bad_objects) &&
+           oidset_contains(&p->bad_objects, oid))
+               return 0;
 
        offset = find_pack_entry_one(oid->hash, p);
        if (!offset)
@@ -2205,6 +2190,12 @@ int for_each_packed_object(each_packed_object_fn cb, void *data,
                if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
                    !p->pack_promisor)
                        continue;
+               if ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+                   p->pack_keep_in_core)
+                       continue;
+               if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+                   p->pack_keep)
+                       continue;
                if (open_pack_index(p)) {
                        pack_errors = 1;
                        continue;
index 3ae117a8aef0faf2f6e7e12abba51235f36fe2f4..186146779d4aade93bf42cd63dd6e5bcd69e69c8 100644 (file)
@@ -159,8 +159,8 @@ int packed_object_info(struct repository *r,
                       struct packed_git *pack,
                       off_t offset, struct object_info *);
 
-void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1);
-const struct packed_git *has_packed_and_bad(struct repository *r, const unsigned char *sha1);
+void mark_bad_packed_object(struct packed_git *, const struct object_id *);
+const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *);
 
 #define ON_DISK_KEEP_PACKS 1
 #define IN_CORE_KEEP_PACKS 2
index 2abff136a17b1d37452695f8b06609a3176fa273..6e0535bdaadda9ecf040f3cf0baec64ed593c848 100644 (file)
@@ -310,19 +310,6 @@ static enum parse_opt_result parse_long_opt(
 again:
                if (!skip_prefix(arg, long_name, &rest))
                        rest = NULL;
-               if (options->type == OPTION_ARGUMENT) {
-                       if (!rest)
-                               continue;
-                       if (*rest == '=')
-                               return error(_("%s takes no value"),
-                                            optname(options, flags));
-                       if (*rest)
-                               continue;
-                       if (options->value)
-                               *(int *)options->value = options->defval;
-                       p->out[p->cpidx++] = arg - 2;
-                       return PARSE_OPT_DONE;
-               }
                if (!rest) {
                        /* abbreviated? */
                        if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
@@ -917,25 +904,77 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
        FILE *outfile = err ? stderr : stdout;
        int need_newline;
 
+       const char *usage_prefix = _("usage: %s");
+       /*
+        * The translation could be anything, but we can count on
+        * msgfmt(1)'s --check option to have asserted that "%s" is in
+        * the translation. So compute the length of the "usage: "
+        * part. We are assuming that the translator wasn't overly
+        * clever and used e.g. "%1$s" instead of "%s", there's only
+        * one "%s" in "usage_prefix" above, so there's no reason to
+        * do so even with a RTL language.
+        */
+       size_t usage_len = strlen(usage_prefix) - strlen("%s");
+       /*
+        * TRANSLATORS: the colon here should align with the
+        * one in "usage: %s" translation.
+        */
+       const char *or_prefix = _("   or: %s");
+       /*
+        * TRANSLATORS: You should only need to translate this format
+        * string if your language is a RTL language (e.g. Arabic,
+        * Hebrew etc.), not if it's a LTR language (e.g. German,
+        * Russian, Chinese etc.).
+        *
+        * When a translated usage string has an embedded "\n" it's
+        * because options have wrapped to the next line. The line
+        * after the "\n" will then be padded to align with the
+        * command name, such as N_("git cmd [opt]\n<8
+        * spaces>[opt2]"), where the 8 spaces are the same length as
+        * "git cmd ".
+        *
+        * This format string prints out that already-translated
+        * line. The "%*s" is whitespace padding to account for the
+        * padding at the start of the line that we add in this
+        * function. The "%s" is a line in the (hopefully already
+        * translated) N_() usage string, which contained embedded
+        * newlines before we split it up.
+        */
+       const char *usage_continued = _("%*s%s");
+       const char *prefix = usage_prefix;
+       int saw_empty_line = 0;
+
        if (!usagestr)
                return PARSE_OPT_HELP;
 
        if (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)
                fprintf(outfile, "cat <<\\EOF\n");
 
-       fprintf_ln(outfile, _("usage: %s"), _(*usagestr++));
-       while (*usagestr && **usagestr)
-               /*
-                * TRANSLATORS: the colon here should align with the
-                * one in "usage: %s" translation.
-                */
-               fprintf_ln(outfile, _("   or: %s"), _(*usagestr++));
        while (*usagestr) {
-               if (**usagestr)
-                       fprintf_ln(outfile, _("    %s"), _(*usagestr));
-               else
-                       fputc('\n', outfile);
-               usagestr++;
+               const char *str = _(*usagestr++);
+               struct string_list list = STRING_LIST_INIT_DUP;
+               unsigned int j;
+
+               if (!saw_empty_line && !*str)
+                       saw_empty_line = 1;
+
+               string_list_split(&list, str, '\n', -1);
+               for (j = 0; j < list.nr; j++) {
+                       const char *line = list.items[j].string;
+
+                       if (saw_empty_line && *line)
+                               fprintf_ln(outfile, _("    %s"), line);
+                       else if (saw_empty_line)
+                               fputc('\n', outfile);
+                       else if (!j)
+                               fprintf_ln(outfile, prefix, line);
+                       else
+                               fprintf_ln(outfile, usage_continued,
+                                          (int)usage_len, "", line);
+               }
+               string_list_clear(&list, 0);
+
+               prefix = or_prefix;
        }
 
        need_newline = 1;
index a845a9d9527476fbe5269756a7fd03aed6c6d4d7..13405472ee82c9028cb441e20535631bf2993f33 100644 (file)
@@ -8,7 +8,6 @@
 enum parse_opt_type {
        /* special types */
        OPTION_END,
-       OPTION_ARGUMENT,
        OPTION_GROUP,
        OPTION_NUMBER,
        OPTION_ALIAS,
@@ -155,8 +154,6 @@ struct option {
 #define OPT_INTEGER_F(s, l, v, h, f)     { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) }
 
 #define OPT_END()                   { OPTION_END }
-#define OPT_ARGUMENT(l, v, h)       { OPTION_ARGUMENT, 0, (l), (v), NULL, \
-                                     (h), PARSE_OPT_NOARG, NULL, 1 }
 #define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
 #define OPT_BIT(s, l, v, h, b)      OPT_BIT_F(s, l, v, h, b, 0)
 #define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \
@@ -169,8 +166,10 @@ struct option {
 #define OPT_BOOL(s, l, v, h)        OPT_BOOL_F(s, l, v, h, 0)
 #define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1}
-#define OPT_CMDMODE(s, l, v, h, i)  { OPTION_SET_INT, (s), (l), (v), NULL, \
-                                     (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, (i) }
+#define OPT_CMDMODE_F(s, l, v, h, i, f)  { OPTION_SET_INT, (s), (l), (v), NULL, \
+                                     (h), PARSE_OPT_CMDMODE|PARSE_OPT_NOARG|PARSE_OPT_NONEG | (f), NULL, (i) }
+#define OPT_CMDMODE(s, l, v, h, i)  OPT_CMDMODE_F(s, l, v, h, i, 0)
+
 #define OPT_INTEGER(s, l, v, h)     OPT_INTEGER_F(s, l, v, h, 0)
 #define OPT_MAGNITUDE(s, l, v, h)   { OPTION_MAGNITUDE, (s), (l), (v), \
                                      N_("n"), (h), PARSE_OPT_NONEG }
diff --git a/path.c b/path.c
index 7bccd830e95890c16a93a52801c008a2b634ca24..2c895471d901411a6b3409fab287271c451c6167 100644 (file)
--- a/path.c
+++ b/path.c
@@ -12,6 +12,7 @@
 #include "packfile.h"
 #include "object-store.h"
 #include "lockfile.h"
+#include "exec-cmd.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -719,19 +720,25 @@ static struct passwd *getpw_str(const char *username, size_t len)
 }
 
 /*
- * Return a string with ~ and ~user expanded via getpw*.  If buf != NULL,
- * then it is a newly allocated string. Returns NULL on getpw failure or
- * if path is NULL.
+ * Return a string with ~ and ~user expanded via getpw*. Returns NULL on getpw
+ * failure or if path is NULL.
  *
- * If real_home is true, strbuf_realpath($HOME) is used in the expansion.
+ * If real_home is true, strbuf_realpath($HOME) is used in the `~/` expansion.
+ *
+ * If the path starts with `%(prefix)/`, the remainder is interpreted as
+ * relative to where Git is installed, and expanded to the absolute path.
  */
-char *expand_user_path(const char *path, int real_home)
+char *interpolate_path(const char *path, int real_home)
 {
        struct strbuf user_path = STRBUF_INIT;
        const char *to_copy = path;
 
        if (path == NULL)
                goto return_null;
+
+       if (skip_prefix(path, "%(prefix)/", &path))
+               return system_path(path);
+
        if (path[0] == '~') {
                const char *first_slash = strchrnul(path, '/');
                const char *username = path + 1;
@@ -812,7 +819,7 @@ const char *enter_repo(const char *path, int strict)
                strbuf_add(&validated_path, path, len);
 
                if (used_path.buf[0] == '~') {
-                       char *newpath = expand_user_path(used_path.buf, 0);
+                       char *newpath = interpolate_path(used_path.buf, 0);
                        if (!newpath)
                                return NULL;
                        strbuf_attach(&used_path, newpath, strlen(newpath),
@@ -1503,21 +1510,28 @@ int looks_like_command_line_option(const char *str)
        return str && str[0] == '-';
 }
 
-char *xdg_config_home(const char *filename)
+char *xdg_config_home_for(const char *subdir, const char *filename)
 {
        const char *home, *config_home;
 
+       assert(subdir);
        assert(filename);
        config_home = getenv("XDG_CONFIG_HOME");
        if (config_home && *config_home)
-               return mkpathdup("%s/git/%s", config_home, filename);
+               return mkpathdup("%s/%s/%s", config_home, subdir, filename);
 
        home = getenv("HOME");
        if (home)
-               return mkpathdup("%s/.config/git/%s", home, filename);
+               return mkpathdup("%s/.config/%s/%s", home, subdir, filename);
+
        return NULL;
 }
 
+char *xdg_config_home(const char *filename)
+{
+       return xdg_config_home_for("git", filename);
+}
+
 char *xdg_cache_home(const char *filename)
 {
        const char *home, *cache_home;
diff --git a/path.h b/path.h
index 251c78d980000af0a40f1388ece5c9d68b354982..b68691a86b80417a82e5124aef37b9c8a9a02c95 100644 (file)
--- a/path.h
+++ b/path.h
@@ -181,10 +181,7 @@ struct path_cache {
        const char *shallow;
 };
 
-#define PATH_CACHE_INIT                                        \
-       {                                                      \
-               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL \
-       }
+#define PATH_CACHE_INIT { 0 }
 
 const char *git_path_squash_msg(struct repository *r);
 const char *git_path_merge_msg(struct repository *r);
index 08f8d3eedc39aa46e8bf3a4cba3220ba9b4e74ab..ddeeba7911496ebc77fe1943b94fdc1d456f17a3 100644 (file)
@@ -37,11 +37,10 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
                        num_unmatched++;
        if (!num_unmatched)
                return;
-       /* TODO: audit for interaction with sparse-index. */
-       ensure_full_index(istate);
        for (i = 0; i < istate->cache_nr; i++) {
                const struct cache_entry *ce = istate->cache[i];
-               if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))
+               if (sw_action == PS_IGNORE_SKIP_WORKTREE &&
+                   (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)))
                        continue;
                ce_path_match(istate, ce, pathspec, seen);
        }
@@ -72,7 +71,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
 
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce = istate->cache[i];
-               if (ce_skip_worktree(ce))
+               if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))
                    ce_path_match(istate, ce, pathspec, seen);
        }
 
index 9f63eae2e643f7a70d23edccdb9944a12ff7cdad..de4a94b437e1d573ef9395314fbab2acfc38b887 100644 (file)
@@ -243,6 +243,43 @@ void packet_write(int fd_out, const char *buf, size_t size)
                die("%s", err.buf);
 }
 
+void packet_fwrite(FILE *f, const char *buf, size_t size)
+{
+       size_t packet_size;
+       char header[4];
+
+       if (size > LARGE_PACKET_DATA_MAX)
+               die(_("packet write failed - data exceeds max packet size"));
+
+       packet_trace(buf, size, 1);
+       packet_size = size + 4;
+
+       set_packet_header(header, packet_size);
+       fwrite_or_die(f, header, 4);
+       fwrite_or_die(f, buf, size);
+}
+
+void packet_fwrite_fmt(FILE *fh, const char *fmt, ...)
+{
+       static struct strbuf buf = STRBUF_INIT;
+       va_list args;
+
+       strbuf_reset(&buf);
+
+       va_start(args, fmt);
+       format_packet(&buf, "", fmt, args);
+       va_end(args);
+
+       fwrite_or_die(fh, buf.buf, buf.len);
+}
+
+void packet_fflush(FILE *f)
+{
+       packet_trace("0000", 4, 1);
+       fwrite_or_die(f, "0000", 4);
+       fflush_or_die(f);
+}
+
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
 {
        va_list args;
index 5af5f456876841744f22bc67b8359e0a330ddc11..82b95e4bdd3b775ad6b75e4c5cea1e1cc704546e 100644 (file)
@@ -35,6 +35,17 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format
 int write_packetized_from_fd_no_flush(int fd_in, int fd_out);
 int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
 
+/*
+ * Stdio versions of packet_write functions. When mixing these with fd
+ * based functions, take care to call fflush(3) before doing fd writes or
+ * closing the fd.
+ */
+void packet_fwrite(FILE *f, const char *buf, size_t size);
+void packet_fwrite_fmt(FILE *f, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+/* packet_fflush writes a flush packet and flushes the stdio buffer of f */
+void packet_fflush(FILE *f);
+
 /*
  * Read a packetized line into the buffer, which must be at least size bytes
  * long. The return value specifies the number of bytes read into the buffer.
index 73b5ead5099d3efdb3e1999865ab868da5053ea6..fe95107ae5a4150797be620e254466f8dce6423f 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1436,8 +1436,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                        check_commit_signature(c->commit, &(c->signature_check));
                switch (placeholder[1]) {
                case 'G':
-                       if (c->signature_check.gpg_output)
-                               strbuf_addstr(sb, c->signature_check.gpg_output);
+                       if (c->signature_check.output)
+                               strbuf_addstr(sb, c->signature_check.output);
                        break;
                case '?':
                        switch (c->signature_check.result) {
index 901b6795e42d055764146b44b37f611841e1ae4a..bbde91810ac6667a1ee624d112ad26cf23776b1e 100644 (file)
@@ -75,8 +75,7 @@ static void send_info(struct repository *r, struct packet_writer *writer,
        strbuf_release(&send_buffer);
 }
 
-int cap_object_info(struct repository *r, struct strvec *keys,
-                   struct packet_reader *request)
+int cap_object_info(struct repository *r, struct packet_reader *request)
 {
        struct requested_info info;
        struct packet_writer writer;
index 0a9f49df115b5cf0914eea64936059cfa64b224b..15c4550360cfdb4d1f05f7df252f08c85d2b8256 100644 (file)
@@ -2,9 +2,7 @@
 #define PROTOCOL_CAPS_H
 
 struct repository;
-struct strvec;
 struct packet_reader;
-int cap_object_info(struct repository *r, struct strvec *keys,
-                   struct packet_reader *request);
+int cap_object_info(struct repository *r, struct packet_reader *request);
 
 #endif /* PROTOCOL_CAPS_H */
diff --git a/quote.c b/quote.c
index 8a3a5e39eb12adff643d8bd2d90d1e625203a2ce..26719d21d1e7555d92289b26402a73030e330606 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -471,6 +471,23 @@ void perl_quote_buf(struct strbuf *sb, const char *src)
        strbuf_addch(sb, sq);
 }
 
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len)
+{
+       const char sq = '\'';
+       const char bq = '\\';
+       const char *c = src;
+       const char *end = src + len;
+
+       strbuf_addch(sb, sq);
+       while (c != end) {
+               if (*c == sq || *c == bq)
+                       strbuf_addch(sb, bq);
+               strbuf_addch(sb, *c);
+               c++;
+       }
+       strbuf_addch(sb, sq);
+}
+
 void python_quote_buf(struct strbuf *sb, const char *src)
 {
        const char sq = '\'';
diff --git a/quote.h b/quote.h
index 049d8dd0b3d7e42547ef42b460dcaaf95a412a68..87ff458b06dd6430855b39f6cb4833664e630746 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -95,6 +95,7 @@ char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigne
 
 /* quoting as a string literal for other languages */
 void perl_quote_buf(struct strbuf *sb, const char *src);
+void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len);
 void python_quote_buf(struct strbuf *sb, const char *src);
 void tcl_quote_buf(struct strbuf *sb, const char *src);
 void basic_regex_quote_buf(struct strbuf *sb, const char *src);
index 9048ef9e905251bacb516be6afdb474b4e59648f..f398659662325d728c2cfe8efc95acf781e5d049 100644 (file)
@@ -738,7 +738,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        int intent_only = flags & ADD_CACHE_INTENT;
        int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
                          (intent_only ? ADD_CACHE_NEW_ONLY : 0));
-       int hash_flags = HASH_WRITE_OBJECT;
+       unsigned hash_flags = pretend ? 0 : HASH_WRITE_OBJECT;
        struct object_id oid;
 
        if (flags & ADD_CACHE_RENORMALIZE)
@@ -849,6 +849,19 @@ struct cache_entry *make_empty_transient_cache_entry(size_t len,
        return xcalloc(1, cache_entry_size(len));
 }
 
+enum verify_path_result {
+       PATH_OK,
+       PATH_INVALID,
+       PATH_DIR_WITH_SEP,
+};
+
+static enum verify_path_result verify_path_internal(const char *, unsigned);
+
+int verify_path(const char *path, unsigned mode)
+{
+       return verify_path_internal(path, mode) == PATH_OK;
+}
+
 struct cache_entry *make_cache_entry(struct index_state *istate,
                                     unsigned int mode,
                                     const struct object_id *oid,
@@ -859,7 +872,7 @@ struct cache_entry *make_cache_entry(struct index_state *istate,
        struct cache_entry *ce, *ret;
        int len;
 
-       if (!verify_path(path, mode)) {
+       if (verify_path_internal(path, mode) == PATH_INVALID) {
                error(_("invalid path '%s'"), path);
                return NULL;
        }
@@ -993,60 +1006,62 @@ static int verify_dotfile(const char *rest, unsigned mode)
        return 1;
 }
 
-int verify_path(const char *path, unsigned mode)
+static enum verify_path_result verify_path_internal(const char *path,
+                                                   unsigned mode)
 {
        char c = 0;
 
        if (has_dos_drive_prefix(path))
-               return 0;
+               return PATH_INVALID;
 
        if (!is_valid_path(path))
-               return 0;
+               return PATH_INVALID;
 
        goto inside;
        for (;;) {
                if (!c)
-                       return 1;
+                       return PATH_OK;
                if (is_dir_sep(c)) {
 inside:
                        if (protect_hfs) {
 
                                if (is_hfs_dotgit(path))
-                                       return 0;
+                                       return PATH_INVALID;
                                if (S_ISLNK(mode)) {
                                        if (is_hfs_dotgitmodules(path))
-                                               return 0;
+                                               return PATH_INVALID;
                                }
                        }
                        if (protect_ntfs) {
 #if defined GIT_WINDOWS_NATIVE || defined __CYGWIN__
                                if (c == '\\')
-                                       return 0;
+                                       return PATH_INVALID;
 #endif
                                if (is_ntfs_dotgit(path))
-                                       return 0;
+                                       return PATH_INVALID;
                                if (S_ISLNK(mode)) {
                                        if (is_ntfs_dotgitmodules(path))
-                                               return 0;
+                                               return PATH_INVALID;
                                }
                        }
 
                        c = *path++;
                        if ((c == '.' && !verify_dotfile(path, mode)) ||
                            is_dir_sep(c))
-                               return 0;
+                               return PATH_INVALID;
                        /*
                         * allow terminating directory separators for
                         * sparse directory entries.
                         */
                        if (c == '\0')
-                               return S_ISDIR(mode);
+                               return S_ISDIR(mode) ? PATH_DIR_WITH_SEP :
+                                                      PATH_INVALID;
                } else if (c == '\\' && protect_ntfs) {
                        if (is_ntfs_dotgit(path))
-                               return 0;
+                               return PATH_INVALID;
                        if (S_ISLNK(mode)) {
                                if (is_ntfs_dotgitmodules(path))
-                                       return 0;
+                                       return PATH_INVALID;
                        }
                }
 
@@ -1349,7 +1364,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
 
        if (!ok_to_add)
                return -1;
-       if (!verify_path(ce->name, ce->ce_mode))
+       if (verify_path_internal(ce->name, ce->ce_mode) == PATH_INVALID)
                return error(_("invalid path '%s'"), ce->name);
 
        if (!skip_df_check &&
@@ -1944,13 +1959,22 @@ static void tweak_untracked_cache(struct index_state *istate)
 
        prepare_repo_settings(r);
 
-       if (r->settings.core_untracked_cache  == UNTRACKED_CACHE_REMOVE) {
+       switch (r->settings.core_untracked_cache) {
+       case UNTRACKED_CACHE_REMOVE:
                remove_untracked_cache(istate);
-               return;
-       }
-
-       if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
+               break;
+       case UNTRACKED_CACHE_WRITE:
                add_untracked_cache(istate);
+               break;
+       case UNTRACKED_CACHE_KEEP:
+               /*
+                * Either an explicit "core.untrackedCache=keep", the
+                * default if "core.untrackedCache" isn't configured,
+                * or a fallback on an unknown "core.untrackedCache"
+                * value.
+                */
+               break;
+       }
 }
 
 static void tweak_split_index(struct index_state *istate)
@@ -2391,9 +2415,21 @@ int read_index_from(struct index_state *istate, const char *path,
        base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
        trace2_region_enter_printf("index", "shared/do_read_index",
                                   the_repository, "%s", base_path);
-       ret = do_read_index(split_index->base, base_path, 1);
+       ret = do_read_index(split_index->base, base_path, 0);
        trace2_region_leave_printf("index", "shared/do_read_index",
                                   the_repository, "%s", base_path);
+       if (!ret) {
+               char *path_copy = xstrdup(path);
+               const char *base_path2 = xstrfmt("%s/sharedindex.%s",
+                                                dirname(path_copy),
+                                                base_oid_hex);
+               free(path_copy);
+               trace2_region_enter_printf("index", "shared/do_read_index",
+                                          the_repository, "%s", base_path2);
+               ret = do_read_index(split_index->base, base_path2, 1);
+               trace2_region_leave_printf("index", "shared/do_read_index",
+                                          the_repository, "%s", base_path2);
+       }
        if (!oideq(&split_index->base_oid, &split_index->base->oid))
                die(_("broken index, expect %s in %s, got %s"),
                    base_oid_hex, base_path,
@@ -2812,11 +2848,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
                }
        }
 
-       if (!istate->version) {
+       if (!istate->version)
                istate->version = get_index_format_default(the_repository);
-               if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
-                       init_split_index(istate);
-       }
 
        /* demote version 3 to version 2 when the latter suffices */
        if (istate->version == 3 || istate->version == 2)
@@ -3069,7 +3102,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
        int ret;
        int was_full = !istate->sparse_index;
 
-       ret = convert_to_sparse(istate);
+       ret = convert_to_sparse(istate, 0);
 
        if (ret) {
                warning(_("failed to convert to a sparse-index"));
@@ -3182,7 +3215,7 @@ static int write_shared_index(struct index_state *istate,
        int ret, was_full = !istate->sparse_index;
 
        move_cache_to_base_index(istate);
-       convert_to_sparse(istate);
+       convert_to_sparse(istate, 0);
 
        trace2_region_enter_printf("index", "shared/do_write_index",
                                   the_repository, "%s", get_tempfile_path(*temp));
@@ -3243,7 +3276,7 @@ static int too_many_not_shared_entries(struct index_state *istate)
 int write_locked_index(struct index_state *istate, struct lock_file *lock,
                       unsigned flags)
 {
-       int new_shared_index, ret;
+       int new_shared_index, ret, test_split_index_env;
        struct split_index *si = istate->split_index;
 
        if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
@@ -3258,7 +3291,10 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
        if (istate->fsmonitor_last_update)
                fill_fsmonitor_bitmap(istate);
 
-       if (!si || alternate_index_output ||
+       test_split_index_env = git_env_bool("GIT_TEST_SPLIT_INDEX", 0);
+
+       if ((!si && !test_split_index_env) ||
+           alternate_index_output ||
            (istate->cache_changed & ~EXTMASK)) {
                if (si)
                        oidclr(&si->base_oid);
@@ -3266,10 +3302,15 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
                goto out;
        }
 
-       if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) {
-               int v = si->base_oid.hash[0];
-               if ((v & 15) < 6)
+       if (test_split_index_env) {
+               if (!si) {
+                       si = init_split_index(istate);
                        istate->cache_changed |= SPLIT_INDEX_ORDERED;
+               } else {
+                       int v = si->base_oid.hash[0];
+                       if ((v & 15) < 6)
+                               istate->cache_changed |= SPLIT_INDEX_ORDERED;
+               }
        }
        if (too_many_not_shared_entries(istate))
                istate->cache_changed |= SPLIT_INDEX_ORDERED;
index b6cbd16a17fddd17690fb17575fa18f4fc8549b3..87649d0c016a371f83710a04140909d54a553aa7 100644 (file)
@@ -226,32 +226,3 @@ int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_
        todo_list_release(&backup);
        return res;
 }
-
-int check_todo_list_from_file(struct repository *r)
-{
-       struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;
-       int res = 0;
-
-       if (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {
-               res = error(_("could not read '%s'."), rebase_path_todo());
-               goto out;
-       }
-
-       if (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {
-               res = error(_("could not read '%s'."), rebase_path_todo_backup());
-               goto out;
-       }
-
-       res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);
-       if (!res)
-               res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);
-       if (res)
-               fprintf(stderr, _(edit_todo_list_advice));
-       if (!res)
-               res = todo_list_check(&old_todo, &new_todo);
-out:
-       todo_list_release(&old_todo);
-       todo_list_release(&new_todo);
-
-       return res;
-}
index dc2cf0ee122c70a9c9b4e614be058ee5c423dd80..7239c60f7919e75b227022971a136a42174d47ac 100644 (file)
@@ -16,6 +16,4 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);
 int todo_list_check_against_backup(struct repository *r,
                                   struct todo_list *todo_list);
 
-int check_todo_list_from_file(struct repository *r);
-
 #endif
index f8137d859b51114b75995f1c5c8f6cd282b1f3c4..6775cddb28434d0780cb483357476f4783e1f596 100644 (file)
--- a/rebase.c
+++ b/rebase.c
@@ -1,5 +1,6 @@
 #include "rebase.h"
 #include "config.h"
+#include "gettext.h"
 
 /*
  * Parses textual value for pull.rebase, branch.<name>.rebase, etc.
@@ -20,12 +21,12 @@ enum rebase_type rebase_parse_value(const char *value)
                return REBASE_FALSE;
        else if (v > 0)
                return REBASE_TRUE;
-       else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
-               return REBASE_PRESERVE;
        else if (!strcmp(value, "merges") || !strcmp(value, "m"))
                return REBASE_MERGES;
        else if (!strcmp(value, "interactive") || !strcmp(value, "i"))
                return REBASE_INTERACTIVE;
+       else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
+               error(_("%s: 'preserve' superseded by 'merges'"), value);
        /*
         * Please update _git_config() in git-completion.bash when you
         * add new rebase modes.
index cc723d4748952e4cf5e83b0b8835d2fd576dcf71..203b43728237454b4f0aa4fbb962d5e17f6b5855 100644 (file)
--- a/rebase.h
+++ b/rebase.h
@@ -5,7 +5,6 @@ enum rebase_type {
        REBASE_INVALID = -1,
        REBASE_FALSE = 0,
        REBASE_TRUE,
-       REBASE_PRESERVE,
        REBASE_MERGES,
        REBASE_INTERACTIVE
 };
index 0cfef7b719bb2b42a5d3a711a8074d167182fa14..add429be7973634ef99c26847b90e15b146d9f4d 100644 (file)
@@ -144,6 +144,7 @@ enum atom_type {
        ATOM_BODY,
        ATOM_TRAILERS,
        ATOM_CONTENTS,
+       ATOM_RAW,
        ATOM_UPSTREAM,
        ATOM_PUSH,
        ATOM_SYMREF,
@@ -156,6 +157,7 @@ enum atom_type {
        ATOM_IF,
        ATOM_THEN,
        ATOM_ELSE,
+       ATOM_REST,
 };
 
 /*
@@ -189,6 +191,9 @@ static struct used_atom {
                        struct process_trailer_options trailer_opts;
                        unsigned int nlines;
                } contents;
+               struct {
+                       enum { RAW_BARE, RAW_LENGTH } option;
+               } raw_data;
                struct {
                        cmp_status cmp_status;
                        const char *str;
@@ -223,7 +228,7 @@ static int strbuf_addf_ret(struct strbuf *sb, int ret, const char *fmt, ...)
        return ret;
 }
 
-static int color_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int color_atom_parser(struct ref_format *format, struct used_atom *atom,
                             const char *color_value, struct strbuf *err)
 {
        if (!color_value)
@@ -261,7 +266,7 @@ static int refname_atom_parser_internal(struct refname_atom *atom, const char *a
        return 0;
 }
 
-static int remote_ref_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int remote_ref_atom_parser(struct ref_format *format, struct used_atom *atom,
                                  const char *arg, struct strbuf *err)
 {
        struct string_list params = STRING_LIST_INIT_DUP;
@@ -308,7 +313,7 @@ static int remote_ref_atom_parser(const struct ref_format *format, struct used_a
        return 0;
 }
 
-static int objecttype_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int objecttype_atom_parser(struct ref_format *format, struct used_atom *atom,
                                  const char *arg, struct strbuf *err)
 {
        if (arg)
@@ -320,7 +325,7 @@ static int objecttype_atom_parser(const struct ref_format *format, struct used_a
        return 0;
 }
 
-static int objectsize_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int objectsize_atom_parser(struct ref_format *format, struct used_atom *atom,
                                  const char *arg, struct strbuf *err)
 {
        if (!arg) {
@@ -340,7 +345,7 @@ static int objectsize_atom_parser(const struct ref_format *format, struct used_a
        return 0;
 }
 
-static int deltabase_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int deltabase_atom_parser(struct ref_format *format, struct used_atom *atom,
                                 const char *arg, struct strbuf *err)
 {
        if (arg)
@@ -352,7 +357,7 @@ static int deltabase_atom_parser(const struct ref_format *format, struct used_at
        return 0;
 }
 
-static int body_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int body_atom_parser(struct ref_format *format, struct used_atom *atom,
                            const char *arg, struct strbuf *err)
 {
        if (arg)
@@ -361,7 +366,7 @@ static int body_atom_parser(const struct ref_format *format, struct used_atom *a
        return 0;
 }
 
-static int subject_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int subject_atom_parser(struct ref_format *format, struct used_atom *atom,
                               const char *arg, struct strbuf *err)
 {
        if (!arg)
@@ -373,7 +378,7 @@ static int subject_atom_parser(const struct ref_format *format, struct used_atom
        return 0;
 }
 
-static int trailers_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom,
                                const char *arg, struct strbuf *err)
 {
        atom->u.contents.trailer_opts.no_divider = 1;
@@ -399,7 +404,7 @@ static int trailers_atom_parser(const struct ref_format *format, struct used_ato
        return 0;
 }
 
-static int contents_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int contents_atom_parser(struct ref_format *format, struct used_atom *atom,
                                const char *arg, struct strbuf *err)
 {
        if (!arg)
@@ -427,7 +432,19 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
        return 0;
 }
 
-static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int raw_atom_parser(struct ref_format *format, struct used_atom *atom,
+                               const char *arg, struct strbuf *err)
+{
+       if (!arg)
+               atom->u.raw_data.option = RAW_BARE;
+       else if (!strcmp(arg, "size"))
+               atom->u.raw_data.option = RAW_LENGTH;
+       else
+               return strbuf_addf_ret(err, -1, _("unrecognized %%(raw) argument: %s"), arg);
+       return 0;
+}
+
+static int oid_atom_parser(struct ref_format *format, struct used_atom *atom,
                           const char *arg, struct strbuf *err)
 {
        if (!arg)
@@ -446,7 +463,7 @@ static int oid_atom_parser(const struct ref_format *format, struct used_atom *at
        return 0;
 }
 
-static int person_email_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int person_email_atom_parser(struct ref_format *format, struct used_atom *atom,
                                    const char *arg, struct strbuf *err)
 {
        if (!arg)
@@ -460,7 +477,7 @@ static int person_email_atom_parser(const struct ref_format *format, struct used
        return 0;
 }
 
-static int refname_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int refname_atom_parser(struct ref_format *format, struct used_atom *atom,
                               const char *arg, struct strbuf *err)
 {
        return refname_atom_parser_internal(&atom->u.refname, arg, atom->name, err);
@@ -477,7 +494,7 @@ static align_type parse_align_position(const char *s)
        return -1;
 }
 
-static int align_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int align_atom_parser(struct ref_format *format, struct used_atom *atom,
                             const char *arg, struct strbuf *err)
 {
        struct align *align = &atom->u.align;
@@ -529,7 +546,7 @@ static int align_atom_parser(const struct ref_format *format, struct used_atom *
        return 0;
 }
 
-static int if_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int if_atom_parser(struct ref_format *format, struct used_atom *atom,
                          const char *arg, struct strbuf *err)
 {
        if (!arg) {
@@ -544,7 +561,16 @@ static int if_atom_parser(const struct ref_format *format, struct used_atom *ato
        return 0;
 }
 
-static int head_atom_parser(const struct ref_format *format, struct used_atom *atom,
+static int rest_atom_parser(struct ref_format *format, struct used_atom *atom,
+                           const char *arg, struct strbuf *err)
+{
+       if (arg)
+               return strbuf_addf_ret(err, -1, _("%%(rest) does not take arguments"));
+       format->use_rest = 1;
+       return 0;
+}
+
+static int head_atom_parser(struct ref_format *format, struct used_atom *atom,
                            const char *arg, struct strbuf *unused_err)
 {
        atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
@@ -555,7 +581,7 @@ static struct {
        const char *name;
        info_source source;
        cmp_type cmp_type;
-       int (*parser)(const struct ref_format *format, struct used_atom *atom,
+       int (*parser)(struct ref_format *format, struct used_atom *atom,
                      const char *arg, struct strbuf *err);
 } valid_atom[] = {
        [ATOM_REFNAME] = { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
@@ -587,6 +613,7 @@ static struct {
        [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
        [ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
        [ATOM_CONTENTS] = { "contents", SOURCE_OBJ, FIELD_STR, contents_atom_parser },
+       [ATOM_RAW] = { "raw", SOURCE_OBJ, FIELD_STR, raw_atom_parser },
        [ATOM_UPSTREAM] = { "upstream", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
        [ATOM_PUSH] = { "push", SOURCE_NONE, FIELD_STR, remote_ref_atom_parser },
        [ATOM_SYMREF] = { "symref", SOURCE_NONE, FIELD_STR, refname_atom_parser },
@@ -599,13 +626,14 @@ static struct {
        [ATOM_IF] = { "if", SOURCE_NONE, FIELD_STR, if_atom_parser },
        [ATOM_THEN] = { "then", SOURCE_NONE },
        [ATOM_ELSE] = { "else", SOURCE_NONE },
+       [ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
        /*
         * Please update $__git_ref_fieldlist in git-completion.bash
         * when you add new atoms
         */
 };
 
-#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+#define REF_FORMATTING_STATE_INIT  { 0 }
 
 struct ref_formatting_stack {
        struct ref_formatting_stack *prev;
@@ -621,16 +649,23 @@ struct ref_formatting_state {
 
 struct atom_value {
        const char *s;
+       ssize_t s_size;
        int (*handler)(struct atom_value *atomv, struct ref_formatting_state *state,
                       struct strbuf *err);
        uintmax_t value; /* used for sorting when not FIELD_STR */
        struct used_atom *atom;
 };
 
+#define ATOM_SIZE_UNSPECIFIED (-1)
+
+#define ATOM_VALUE_INIT { \
+       .s_size = ATOM_SIZE_UNSPECIFIED \
+}
+
 /*
  * Used to parse format string and sort specifiers
  */
-static int parse_ref_filter_atom(const struct ref_format *format,
+static int parse_ref_filter_atom(struct ref_format *format,
                                 const char *atom, const char *ep,
                                 struct strbuf *err)
 {
@@ -645,13 +680,6 @@ static int parse_ref_filter_atom(const struct ref_format *format,
                return strbuf_addf_ret(err, -1, _("malformed field name: %.*s"),
                                       (int)(ep-atom), atom);
 
-       /* Do we have the atom already used elsewhere? */
-       for (i = 0; i < used_atom_cnt; i++) {
-               int len = strlen(used_atom[i].name);
-               if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
-                       return i;
-       }
-
        /*
         * If the atom name has a colon, strip it and everything after
         * it off - it specifies the format for this entry, and
@@ -661,6 +689,13 @@ static int parse_ref_filter_atom(const struct ref_format *format,
        arg = memchr(sp, ':', ep - sp);
        atom_len = (arg ? arg : ep) - sp;
 
+       /* Do we have the atom already used elsewhere? */
+       for (i = 0; i < used_atom_cnt; i++) {
+               int len = strlen(used_atom[i].name);
+               if (len == ep - atom && !memcmp(used_atom[i].name, atom, len))
+                       return i;
+       }
+
        /* Is the atom a valid one? */
        for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
                int len = strlen(valid_atom[i].name);
@@ -710,17 +745,23 @@ static int parse_ref_filter_atom(const struct ref_format *format,
        return at;
 }
 
-static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+static void quote_formatting(struct strbuf *s, const char *str, ssize_t len, int quote_style)
 {
        switch (quote_style) {
        case QUOTE_NONE:
-               strbuf_addstr(s, str);
+               if (len < 0)
+                       strbuf_addstr(s, str);
+               else
+                       strbuf_add(s, str, len);
                break;
        case QUOTE_SHELL:
                sq_quote_buf(s, str);
                break;
        case QUOTE_PERL:
-               perl_quote_buf(s, str);
+               if (len < 0)
+                       perl_quote_buf(s, str);
+               else
+                       perl_quote_buf_with_len(s, str, len);
                break;
        case QUOTE_PYTHON:
                python_quote_buf(s, str);
@@ -741,9 +782,11 @@ static int append_atom(struct atom_value *v, struct ref_formatting_state *state,
         * encountered.
         */
        if (!state->stack->prev)
-               quote_formatting(&state->stack->output, v->s, state->quote_style);
-       else
+               quote_formatting(&state->stack->output, v->s, v->s_size, state->quote_style);
+       else if (v->s_size < 0)
                strbuf_addstr(&state->stack->output, v->s);
+       else
+               strbuf_add(&state->stack->output, v->s, v->s_size);
        return 0;
 }
 
@@ -843,21 +886,23 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state
        return 0;
 }
 
-static int is_empty(const char *s)
+static int is_empty(struct strbuf *buf)
 {
-       while (*s != '\0') {
-               if (!isspace(*s))
-                       return 0;
-               s++;
-       }
-       return 1;
-}
+       const char *cur = buf->buf;
+       const char *end = buf->buf + buf->len;
+
+       while (cur != end && (isspace(*cur)))
+               cur++;
+
+       return cur == end;
+ }
 
 static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,
                             struct strbuf *err)
 {
        struct ref_formatting_stack *cur = state->stack;
        struct if_then_else *if_then_else = NULL;
+       size_t str_len = 0;
 
        if (cur->at_end == if_then_else_handler)
                if_then_else = (struct if_then_else *)cur->at_end_data;
@@ -868,18 +913,22 @@ static int then_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
        if (if_then_else->else_atom_seen)
                return strbuf_addf_ret(err, -1, _("format: %%(then) atom used after %%(else)"));
        if_then_else->then_atom_seen = 1;
+       if (if_then_else->str)
+               str_len = strlen(if_then_else->str);
        /*
         * If the 'equals' or 'notequals' attribute is used then
         * perform the required comparison. If not, only non-empty
         * strings satisfy the 'if' condition.
         */
        if (if_then_else->cmp_status == COMPARE_EQUAL) {
-               if (!strcmp(if_then_else->str, cur->output.buf))
+               if (str_len == cur->output.len &&
+                   !memcmp(if_then_else->str, cur->output.buf, cur->output.len))
                        if_then_else->condition_satisfied = 1;
        } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
-               if (strcmp(if_then_else->str, cur->output.buf))
+               if (str_len != cur->output.len ||
+                   memcmp(if_then_else->str, cur->output.buf, cur->output.len))
                        if_then_else->condition_satisfied = 1;
-       } else if (cur->output.len && !is_empty(cur->output.buf))
+       } else if (cur->output.len && !is_empty(&cur->output))
                if_then_else->condition_satisfied = 1;
        strbuf_reset(&cur->output);
        return 0;
@@ -925,7 +974,7 @@ static int end_atom_handler(struct atom_value *atomv, struct ref_formatting_stat
         * only on the topmost supporting atom.
         */
        if (!current->prev->prev) {
-               quote_formatting(&s, current->output.buf, state->quote_style);
+               quote_formatting(&s, current->output.buf, current->output.len, state->quote_style);
                strbuf_swap(&current->output, &s);
        }
        strbuf_release(&s);
@@ -955,6 +1004,11 @@ static const char *find_next(const char *cp)
        return NULL;
 }
 
+static int reject_atom(enum atom_type atom_type)
+{
+       return atom_type == ATOM_REST;
+}
+
 /*
  * Make sure the format string is well formed, and parse out
  * the used atoms.
@@ -975,6 +1029,16 @@ int verify_ref_format(struct ref_format *format)
                at = parse_ref_filter_atom(format, sp + 2, ep, &err);
                if (at < 0)
                        die("%s", err.buf);
+               if (reject_atom(used_atom[at].atom_type))
+                       die(_("this command reject atom %%(%.*s)"), (int)(ep - sp - 2), sp + 2);
+
+               if ((format->quote_style == QUOTE_PYTHON ||
+                    format->quote_style == QUOTE_SHELL ||
+                    format->quote_style == QUOTE_TCL) &&
+                    used_atom[at].atom_type == ATOM_RAW &&
+                    used_atom[at].u.raw_data.option == RAW_BARE)
+                       die(_("--format=%.*s cannot be used with"
+                             "--python, --shell, --tcl"), (int)(ep - sp - 2), sp + 2);
                cp = ep + 1;
 
                if (skip_prefix(used_atom[at].name, "color:", &color))
@@ -1357,25 +1421,42 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
 }
 
 /* See grab_values */
-static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)
 {
        int i;
        const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
        size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
+       void *buf = data->content;
 
        for (i = 0; i < used_atom_cnt; i++) {
                struct used_atom *atom = &used_atom[i];
                const char *name = atom->name;
                struct atom_value *v = &val[i];
+               enum atom_type atom_type = atom->atom_type;
 
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
                        name++;
-               if (strcmp(name, "body") &&
-                   !starts_with(name, "subject") &&
-                   !starts_with(name, "trailers") &&
-                   !starts_with(name, "contents"))
+
+               if (atom_type == ATOM_RAW) {
+                       unsigned long buf_size = data->size;
+
+                       if (atom->u.raw_data.option == RAW_BARE) {
+                               v->s = xmemdupz(buf, buf_size);
+                               v->s_size = buf_size;
+                       } else if (atom->u.raw_data.option == RAW_LENGTH) {
+                               v->s = xstrfmt("%"PRIuMAX, (uintmax_t)buf_size);
+                       }
+                       continue;
+               }
+
+               if ((data->type != OBJ_TAG &&
+                    data->type != OBJ_COMMIT) ||
+                   (strcmp(name, "body") &&
+                    !starts_with(name, "subject") &&
+                    !starts_with(name, "trailers") &&
+                    !starts_with(name, "contents")))
                        continue;
                if (!subpos)
                        find_subpos(buf,
@@ -1439,25 +1520,29 @@ static void fill_missing_values(struct atom_value *val)
  * pointed at by the ref itself; otherwise it is the object the
  * ref (which is a tag) refers to.
  */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf)
+static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)
 {
+       void *buf = data->content;
+
        switch (obj->type) {
        case OBJ_TAG:
                grab_tag_values(val, deref, obj);
-               grab_sub_body_contents(val, deref, buf);
+               grab_sub_body_contents(val, deref, data);
                grab_person("tagger", val, deref, buf);
                break;
        case OBJ_COMMIT:
                grab_commit_values(val, deref, obj);
-               grab_sub_body_contents(val, deref, buf);
+               grab_sub_body_contents(val, deref, data);
                grab_person("author", val, deref, buf);
                grab_person("committer", val, deref, buf);
                break;
        case OBJ_TREE:
                /* grab_tree_values(val, deref, obj, buf, sz); */
+               grab_sub_body_contents(val, deref, data);
                break;
        case OBJ_BLOB:
                /* grab_blob_values(val, deref, obj, buf, sz); */
+               grab_sub_body_contents(val, deref, data);
                break;
        default:
                die("Eh?  Object of type %d?", obj->type);
@@ -1679,7 +1764,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj
                        return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"),
                                               oid_to_hex(&oi->oid), ref->refname);
                }
-               grab_values(ref->value, deref, *obj, oi->content);
+               grab_values(ref->value, deref, *obj, oi);
        }
 
        grab_common_values(ref->value, deref, oi);
@@ -1761,6 +1846,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
                const char *refname;
                struct branch *branch = NULL;
 
+               v->s_size = ATOM_SIZE_UNSPECIFIED;
                v->handler = append_atom;
                v->atom = atom;
 
@@ -1864,6 +1950,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
                        v->handler = else_atom_handler;
                        v->s = xstrdup("");
                        continue;
+               } else if (atom_type == ATOM_REST) {
+                       if (ref->rest)
+                               v->s = xstrdup(ref->rest);
+                       else
+                               v->s = xstrdup("");
+                       continue;
                } else
                        continue;
 
@@ -2008,8 +2100,7 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
  */
 static int for_each_fullref_in_pattern(struct ref_filter *filter,
                                       each_ref_fn cb,
-                                      void *cb_data,
-                                      int broken)
+                                      void *cb_data)
 {
        if (!filter->match_as_path) {
                /*
@@ -2017,7 +2108,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
                 * prefixes like "refs/heads/" etc. are stripped off,
                 * so we have to look at everything:
                 */
-               return for_each_fullref_in("", cb, cb_data, broken);
+               return for_each_fullref_in("", cb, cb_data);
        }
 
        if (filter->ignore_case) {
@@ -2026,16 +2117,16 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
                 * so just return everything and let the caller
                 * sort it out.
                 */
-               return for_each_fullref_in("", cb, cb_data, broken);
+               return for_each_fullref_in("", cb, cb_data);
        }
 
        if (!filter->name_patterns[0]) {
                /* no patterns; we have to look at everything */
-               return for_each_fullref_in("", cb, cb_data, broken);
+               return for_each_fullref_in("", cb, cb_data);
        }
 
        return for_each_fullref_in_prefixes(NULL, filter->name_patterns,
-                                           cb, cb_data, broken);
+                                           cb, cb_data);
 }
 
 /*
@@ -2081,6 +2172,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
 
        FLEX_ALLOC_STR(ref, refname, refname);
        oidcpy(&ref->objectname, oid);
+       ref->rest = NULL;
 
        return ref;
 }
@@ -2312,13 +2404,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
 {
        struct ref_filter_cbdata ref_cbdata;
        int ret = 0;
-       unsigned int broken = 0;
 
        ref_cbdata.array = array;
        ref_cbdata.filter = filter;
 
-       if (type & FILTER_REFS_INCLUDE_BROKEN)
-               broken = 1;
        filter->kind = type & FILTER_REFS_KIND_MASK;
 
        init_contains_cache(&ref_cbdata.contains_cache);
@@ -2335,13 +2424,13 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
                 * of filter_ref_kind().
                 */
                if (filter->kind == FILTER_REFS_BRANCHES)
-                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
+                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata);
                else if (filter->kind == FILTER_REFS_REMOTES)
-                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
+                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata);
                else if (filter->kind == FILTER_REFS_TAGS)
-                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
+                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata);
                else if (filter->kind & FILTER_REFS_ALL)
-                       ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken);
+                       ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata);
                if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
                        head_ref(ref_filter_handler, &ref_cbdata);
        }
@@ -2368,6 +2457,19 @@ static int compare_detached_head(struct ref_array_item *a, struct ref_array_item
        return 0;
 }
 
+static int memcasecmp(const void *vs1, const void *vs2, size_t n)
+{
+       const char *s1 = vs1, *s2 = vs2;
+       const char *end = s1 + n;
+
+       for (; s1 < end; s1++, s2++) {
+               int diff = tolower(*s1) - tolower(*s2);
+               if (diff)
+                       return diff;
+       }
+       return 0;
+}
+
 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
 {
        struct atom_value *va, *vb;
@@ -2388,10 +2490,29 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
        } else if (s->sort_flags & REF_SORTING_VERSION) {
                cmp = versioncmp(va->s, vb->s);
        } else if (cmp_type == FIELD_STR) {
-               int (*cmp_fn)(const char *, const char *);
-               cmp_fn = s->sort_flags & REF_SORTING_ICASE
-                       ? strcasecmp : strcmp;
-               cmp = cmp_fn(va->s, vb->s);
+               if (va->s_size < 0 && vb->s_size < 0) {
+                       int (*cmp_fn)(const char *, const char *);
+                       cmp_fn = s->sort_flags & REF_SORTING_ICASE
+                               ? strcasecmp : strcmp;
+                       cmp = cmp_fn(va->s, vb->s);
+               } else {
+                       size_t a_size = va->s_size < 0 ?
+                                       strlen(va->s) : va->s_size;
+                       size_t b_size = vb->s_size < 0 ?
+                                       strlen(vb->s) : vb->s_size;
+                       int (*cmp_fn)(const void *, const void *, size_t);
+                       cmp_fn = s->sort_flags & REF_SORTING_ICASE
+                               ? memcasecmp : memcmp;
+
+                       cmp = cmp_fn(va->s, vb->s, b_size > a_size ?
+                                    a_size : b_size);
+                       if (!cmp) {
+                               if (a_size > b_size)
+                                       cmp = 1;
+                               else if (a_size < b_size)
+                                       cmp = -1;
+                       }
+               }
        } else {
                if (va->value < vb->value)
                        cmp = -1;
@@ -2461,9 +2582,9 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
 }
 
 int format_ref_array_item(struct ref_array_item *info,
-                          const struct ref_format *format,
-                          struct strbuf *final_buf,
-                          struct strbuf *error_buf)
+                         struct ref_format *format,
+                         struct strbuf *final_buf,
+                         struct strbuf *error_buf)
 {
        const char *cp, *sp, *ep;
        struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
@@ -2490,7 +2611,7 @@ int format_ref_array_item(struct ref_array_item *info,
                append_literal(cp, sp, &state);
        }
        if (format->need_color_reset_at_eol) {
-               struct atom_value resetv;
+               struct atom_value resetv = ATOM_VALUE_INIT;
                resetv.s = GIT_COLOR_RESET;
                if (append_atom(&resetv, &state, error_buf)) {
                        pop_stack_element(&state.stack);
@@ -2507,7 +2628,7 @@ int format_ref_array_item(struct ref_array_item *info,
 }
 
 void pretty_print_ref(const char *name, const struct object_id *oid,
-                     const struct ref_format *format)
+                     struct ref_format *format)
 {
        struct ref_array_item *ref_item;
        struct strbuf output = STRBUF_INIT;
index baf72a718965278fe75ef18fb6d5fb9610d86b32..b636f4389d0507d870ece2bba0f0e6ceb5d951e7 100644 (file)
@@ -13,7 +13,6 @@
 #define QUOTE_PYTHON 4
 #define QUOTE_TCL 8
 
-#define FILTER_REFS_INCLUDE_BROKEN 0x0001
 #define FILTER_REFS_TAGS           0x0002
 #define FILTER_REFS_BRANCHES       0x0004
 #define FILTER_REFS_REMOTES        0x0008
@@ -38,6 +37,7 @@ struct ref_sorting {
 
 struct ref_array_item {
        struct object_id objectname;
+       const char *rest;
        int flag;
        unsigned int kind;
        const char *symref;
@@ -76,14 +76,16 @@ struct ref_format {
         * verify_ref_format() afterwards to finalize.
         */
        const char *format;
+       const char *rest;
        int quote_style;
+       int use_rest;
        int use_color;
 
        /* Internal state to ref-filter */
        int need_color_reset_at_eol;
 };
 
-#define REF_FORMAT_INIT { NULL, 0, -1 }
+#define REF_FORMAT_INIT { .use_color = -1 }
 
 /*  Macros for checking --merged and --no-merged options */
 #define _OPT_MERGED_NO_MERGED(option, filter, h) \
@@ -116,7 +118,7 @@ void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
 void ref_sorting_set_sort_flags_all(struct ref_sorting *sorting, unsigned int mask, int on);
 /*  Based on the given format and quote_style, fill the strbuf */
 int format_ref_array_item(struct ref_array_item *info,
-                         const struct ref_format *format,
+                         struct ref_format *format,
                          struct strbuf *final_buf,
                          struct strbuf *error_buf);
 /*  Parse a single sort specifier and add it to the list */
@@ -137,7 +139,7 @@ void setup_ref_filter_porcelain_msg(void);
  * name must be a fully qualified refname.
  */
 void pretty_print_ref(const char *name, const struct object_id *oid,
-                     const struct ref_format *format);
+                     struct ref_format *format);
 
 /*
  * Push a single ref onto the array; this can be used to construct your own
index e9cd3283694decbc6ea8e72c5681766129df793d..8ac4b284b6b6857a0a6d1f79eed563135771a291 100644 (file)
@@ -158,10 +158,9 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
                }
                reflogs = read_complete_reflog(branch);
                if (!reflogs || reflogs->nr == 0) {
-                       struct object_id oid;
                        char *b;
                        int ret = dwim_log(branch, strlen(branch),
-                                          &oid, &b);
+                                          NULL, &b);
                        if (ret > 1)
                                free(b);
                        else if (ret == 1) {
diff --git a/refs.c b/refs.c
index 8b9f7c3a80a0f615e33a7d14cd505c27c3304491..d7cc0a23a3b65502242650cce698179e3cca27d4 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -10,6 +10,7 @@
 #include "refs.h"
 #include "refs/refs-internal.h"
 #include "run-command.h"
+#include "hook.h"
 #include "object-store.h"
 #include "object.h"
 #include "tag.h"
@@ -33,11 +34,6 @@ static struct ref_storage_be *find_ref_storage_backend(const char *name)
        return NULL;
 }
 
-int ref_storage_backend_exists(const char *name)
-{
-       return find_ref_storage_backend(name) != NULL;
-}
-
 /*
  * How to handle various characters in refnames:
  * 0: An acceptable character for refs
@@ -255,12 +251,13 @@ int refname_is_safe(const char *refname)
  * does not exist, emit a warning and return false.
  */
 int ref_resolves_to_object(const char *refname,
+                          struct repository *repo,
                           const struct object_id *oid,
                           unsigned int flags)
 {
        if (flags & REF_ISBROKEN)
                return 0;
-       if (!has_object_file(oid)) {
+       if (!repo_has_object_file(repo, oid)) {
                error(_("%s does not point to a valid object!"), refname);
                return 0;
        }
@@ -698,7 +695,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
                strbuf_addf(&path, *p, len, str);
                ref = refs_resolve_ref_unsafe(refs, path.buf,
                                              RESOLVE_REF_READING,
-                                             &hash, NULL);
+                                             oid ? &hash : NULL, NULL);
                if (!ref)
                        continue;
                if (refs_reflog_exists(refs, path.buf))
@@ -710,7 +707,8 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
                        continue;
                if (!logs_found++) {
                        *log = xstrdup(it);
-                       oidcpy(oid, &hash);
+                       if (oid)
+                               oidcpy(oid, &hash);
                }
                if (!warn_ambiguous_refs)
                        break;
@@ -1413,14 +1411,21 @@ int head_ref(each_ref_fn fn, void *cb_data)
 
 struct ref_iterator *refs_ref_iterator_begin(
                struct ref_store *refs,
-               const char *prefix, int trim, int flags)
+               const char *prefix, int trim,
+               enum do_for_each_ref_flags flags)
 {
        struct ref_iterator *iter;
 
-       if (ref_paranoia < 0)
-               ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0);
-       if (ref_paranoia)
-               flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+       if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+               static int ref_paranoia = -1;
+
+               if (ref_paranoia < 0)
+                       ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
+               if (ref_paranoia) {
+                       flags |= DO_FOR_EACH_INCLUDE_BROKEN;
+                       flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+               }
+       }
 
        iter = refs->be->iterator_begin(refs, prefix, flags);
 
@@ -1479,7 +1484,8 @@ static int do_for_each_ref_helper(struct repository *r,
 }
 
 static int do_for_each_ref(struct ref_store *refs, const char *prefix,
-                          each_ref_fn fn, int trim, int flags, void *cb_data)
+                          each_ref_fn fn, int trim,
+                          enum do_for_each_ref_flags flags, void *cb_data)
 {
        struct ref_iterator *iter;
        struct do_for_each_ref_help hp = { fn, cb_data };
@@ -1514,25 +1520,16 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
        return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
 }
 
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data)
 {
-       unsigned int flag = 0;
-
-       if (broken)
-               flag = DO_FOR_EACH_INCLUDE_BROKEN;
        return do_for_each_ref(get_main_ref_store(the_repository),
-                              prefix, fn, 0, flag, cb_data);
+                              prefix, fn, 0, 0, cb_data);
 }
 
 int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
-                            each_ref_fn fn, void *cb_data,
-                            unsigned int broken)
+                            each_ref_fn fn, void *cb_data)
 {
-       unsigned int flag = 0;
-
-       if (broken)
-               flag = DO_FOR_EACH_INCLUDE_BROKEN;
-       return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
+       return do_for_each_ref(refs, prefix, fn, 0, 0, cb_data);
 }
 
 int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
@@ -1624,8 +1621,7 @@ static void find_longest_prefixes(struct string_list *out,
 
 int for_each_fullref_in_prefixes(const char *namespace,
                                 const char **patterns,
-                                each_ref_fn fn, void *cb_data,
-                                unsigned int broken)
+                                each_ref_fn fn, void *cb_data)
 {
        struct string_list prefixes = STRING_LIST_INIT_DUP;
        struct string_list_item *prefix;
@@ -1640,7 +1636,7 @@ int for_each_fullref_in_prefixes(const char *namespace,
 
        for_each_string_list_item(prefix, &prefixes) {
                strbuf_addstr(&buf, prefix->string);
-               ret = for_each_fullref_in(buf.buf, fn, cb_data, broken);
+               ret = for_each_fullref_in(buf.buf, fn, cb_data);
                if (ret)
                        break;
                strbuf_setlen(&buf, namespace_len);
@@ -1681,7 +1677,7 @@ int refs_read_raw_ref(struct ref_store *ref_store,
        }
 
        return ref_store->be->read_raw_ref(ref_store, refname, oid, referent,
-                                          type);
+                                          type, &errno);
 }
 
 /* This function needs to return a meaningful errno on failure */
@@ -1875,7 +1871,8 @@ static struct ref_store *lookup_ref_store_map(struct hashmap *map,
  * Create, record, and return a ref_store instance for the specified
  * gitdir.
  */
-static struct ref_store *ref_store_init(const char *gitdir,
+static struct ref_store *ref_store_init(struct repository *repo,
+                                       const char *gitdir,
                                        unsigned int flags)
 {
        const char *be_name = "files";
@@ -1885,7 +1882,7 @@ static struct ref_store *ref_store_init(const char *gitdir,
        if (!be)
                BUG("reference backend %s is unknown", be_name);
 
-       refs = be->init(gitdir, flags);
+       refs = be->init(repo, gitdir, flags);
        return refs;
 }
 
@@ -1897,7 +1894,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
        if (!r->gitdir)
                BUG("attempting to get main_ref_store outside of repository");
 
-       r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+       r->refs_private = ref_store_init(r, r->gitdir, REF_STORE_ALL_CAPS);
        r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
        return r->refs_private;
 }
@@ -1927,6 +1924,7 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
        struct ref_store *refs;
        char *to_free = NULL;
        size_t len;
+       struct repository *subrepo;
 
        if (!submodule)
                return NULL;
@@ -1952,8 +1950,19 @@ struct ref_store *get_submodule_ref_store(const char *submodule)
        if (submodule_to_gitdir(&submodule_sb, submodule))
                goto done;
 
-       /* assume that add_submodule_odb() has been called */
-       refs = ref_store_init(submodule_sb.buf,
+       subrepo = xmalloc(sizeof(*subrepo));
+       /*
+        * NEEDSWORK: Make get_submodule_ref_store() work with arbitrary
+        * superprojects other than the_repository. This probably should be
+        * done by making it take a struct repository * parameter instead of a
+        * submodule path.
+        */
+       if (repo_submodule_init(subrepo, the_repository, submodule,
+                               null_oid())) {
+               free(subrepo);
+               goto done;
+       }
+       refs = ref_store_init(subrepo, submodule_sb.buf,
                              REF_STORE_READ | REF_STORE_ODB);
        register_ref_store_map(&submodule_ref_stores, "submodule",
                               refs, submodule);
@@ -1979,10 +1988,12 @@ struct ref_store *get_worktree_ref_store(const struct worktree *wt)
                return refs;
 
        if (wt->id)
-               refs = ref_store_init(git_common_path("worktrees/%s", wt->id),
+               refs = ref_store_init(the_repository,
+                                     git_common_path("worktrees/%s", wt->id),
                                      REF_STORE_ALL_CAPS);
        else
-               refs = ref_store_init(get_git_common_dir(),
+               refs = ref_store_init(the_repository,
+                                     get_git_common_dir(),
                                      REF_STORE_ALL_CAPS);
 
        if (refs)
@@ -2370,19 +2381,19 @@ int delete_reflog(const char *refname)
 }
 
 int refs_reflog_expire(struct ref_store *refs,
-                      const char *refname, const struct object_id *oid,
+                      const char *refname,
                       unsigned int flags,
                       reflog_expiry_prepare_fn prepare_fn,
                       reflog_expiry_should_prune_fn should_prune_fn,
                       reflog_expiry_cleanup_fn cleanup_fn,
                       void *policy_cb_data)
 {
-       return refs->be->reflog_expire(refs, refname, oid, flags,
+       return refs->be->reflog_expire(refs, refname, flags,
                                       prepare_fn, should_prune_fn,
                                       cleanup_fn, policy_cb_data);
 }
 
-int reflog_expire(const char *refname, const struct object_id *oid,
+int reflog_expire(const char *refname,
                  unsigned int flags,
                  reflog_expiry_prepare_fn prepare_fn,
                  reflog_expiry_should_prune_fn should_prune_fn,
@@ -2390,7 +2401,7 @@ int reflog_expire(const char *refname, const struct object_id *oid,
                  void *policy_cb_data)
 {
        return refs_reflog_expire(get_main_ref_store(the_repository),
-                                 refname, oid, flags,
+                                 refname, flags,
                                  prepare_fn, should_prune_fn,
                                  cleanup_fn, policy_cb_data);
 }
diff --git a/refs.h b/refs.h
index 48970dfc7e0f0d6263a3faca9d92aa887a60d0e4..d5099d4984ef9c9fe248897528344ab9019eba65 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -342,10 +342,8 @@ int for_each_ref(each_ref_fn fn, void *cb_data);
 int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
 
 int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
-                            each_ref_fn fn, void *cb_data,
-                            unsigned int broken);
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
-                       unsigned int broken);
+                            each_ref_fn fn, void *cb_data);
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data);
 
 /**
  * iterate all refs in "patterns" by partitioning patterns into disjoint sets
@@ -354,8 +352,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
  * callers should be prepared to ignore references that they did not ask for.
  */
 int for_each_fullref_in_prefixes(const char *namespace, const char **patterns,
-                                each_ref_fn fn, void *cb_data,
-                                unsigned int broken);
+                                each_ref_fn fn, void *cb_data);
 /**
  * iterate refs from the respective area.
  */
@@ -796,7 +793,7 @@ enum expire_reflog_flags {
  * expiration policy that is desired.
  *
  * reflog_expiry_prepare_fn -- Called once after the reference is
- *     locked.
+ *     locked. Called with the OID of the locked reference.
  *
  * reflog_expiry_should_prune_fn -- Called once for each entry in the
  *     existing reflog. It should return true iff that entry should be
@@ -816,28 +813,25 @@ typedef int reflog_expiry_should_prune_fn(struct object_id *ooid,
 typedef void reflog_expiry_cleanup_fn(void *cb_data);
 
 /*
- * Expire reflog entries for the specified reference. oid is the old
- * value of the reference. flags is a combination of the constants in
+ * Expire reflog entries for the specified reference.
+ * flags is a combination of the constants in
  * enum expire_reflog_flags. The three function pointers are described
  * above. On success, return zero.
  */
 int refs_reflog_expire(struct ref_store *refs,
                       const char *refname,
-                      const struct object_id *oid,
                       unsigned int flags,
                       reflog_expiry_prepare_fn prepare_fn,
                       reflog_expiry_should_prune_fn should_prune_fn,
                       reflog_expiry_cleanup_fn cleanup_fn,
                       void *policy_cb_data);
-int reflog_expire(const char *refname, const struct object_id *oid,
+int reflog_expire(const char *refname,
                  unsigned int flags,
                  reflog_expiry_prepare_fn prepare_fn,
                  reflog_expiry_should_prune_fn should_prune_fn,
                  reflog_expiry_cleanup_fn cleanup_fn,
                  void *policy_cb_data);
 
-int ref_storage_backend_exists(const char *name);
-
 struct ref_store *get_main_ref_store(struct repository *r);
 
 /**
index 1a7a9e11cfac65eafb9b5e5cd997c5780703707f..8667c64023784051169451517d55f4055e3919aa 100644 (file)
@@ -239,15 +239,14 @@ debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
 
 static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
                              struct object_id *oid, struct strbuf *referent,
-                             unsigned int *type)
+                             unsigned int *type, int *failure_errno)
 {
        struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
        int res = 0;
 
        oidcpy(oid, null_oid());
-       errno = 0;
        res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent,
-                                           type);
+                                           type, failure_errno);
 
        if (res == 0) {
                trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n",
@@ -255,7 +254,7 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
        } else {
                trace_printf_key(&trace_refs,
                                 "read_raw_ref: %s: %d (errno %d)\n", refname,
-                                res, errno);
+                                res, *failure_errno);
        }
        return res;
 }
@@ -365,8 +364,8 @@ struct debug_reflog_expiry_should_prune {
 };
 
 static void debug_reflog_expiry_prepare(const char *refname,
-                                   const struct object_id *oid,
-                                   void *cb_data)
+                                       const struct object_id *oid,
+                                       void *cb_data)
 {
        struct debug_reflog_expiry_should_prune *prune = cb_data;
        trace_printf_key(&trace_refs, "reflog_expire_prepare: %s\n", refname);
@@ -392,7 +391,7 @@ static void debug_reflog_expiry_cleanup(void *cb_data)
 }
 
 static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
-                              const struct object_id *oid, unsigned int flags,
+                              unsigned int flags,
                               reflog_expiry_prepare_fn prepare_fn,
                               reflog_expiry_should_prune_fn should_prune_fn,
                               reflog_expiry_cleanup_fn cleanup_fn,
@@ -405,7 +404,7 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
                .should_prune = should_prune_fn,
                .cb_data = policy_cb_data,
        };
-       int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid,
+       int res = drefs->refs->be->reflog_expire(drefs->refs, refname,
                                                 flags, &debug_reflog_expiry_prepare,
                                                 &debug_reflog_expiry_should_prune_fn,
                                                 &debug_reflog_expiry_cleanup,
index 74c03858736cc212cad0edf7ab1be42cf577c363..151b0056fe57d41acdacdcf4e8dfc16a43d82b96 100644 (file)
@@ -79,13 +79,15 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
  * Create a new submodule ref cache and add it to the internal
  * set of caches.
  */
-static struct ref_store *files_ref_store_create(const char *gitdir,
+static struct ref_store *files_ref_store_create(struct repository *repo,
+                                               const char *gitdir,
                                                unsigned int flags)
 {
        struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
        struct strbuf sb = STRBUF_INIT;
 
+       ref_store->repo = repo;
        ref_store->gitdir = xstrdup(gitdir);
        base_ref_store_init(ref_store, &refs_be_files);
        refs->store_flags = flags;
@@ -93,7 +95,7 @@ static struct ref_store *files_ref_store_create(const char *gitdir,
        get_common_dir_noenv(&sb, gitdir);
        refs->gitcommondir = strbuf_detach(&sb, NULL);
        strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir);
-       refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
+       refs->packed_ref_store = packed_ref_store_create(repo, sb.buf, flags);
        strbuf_release(&sb);
 
        chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
@@ -227,7 +229,7 @@ static void add_per_worktree_entries_to_dir(struct ref_dir *dir, const char *dir
                pos = search_ref_dir(dir, prefix, prefix_len);
                if (pos >= 0)
                        continue;
-               child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1);
+               child_entry = create_dir_entry(dir->cache, prefix, prefix_len);
                add_entry_to_dir(dir, child_entry);
        }
 }
@@ -278,7 +280,7 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
                        strbuf_addch(&refname, '/');
                        add_entry_to_dir(dir,
                                         create_dir_entry(dir->cache, refname.buf,
-                                                         refname.len, 1));
+                                                         refname.len));
                } else {
                        if (!refs_resolve_ref_unsafe(&refs->base,
                                                     refname.buf,
@@ -336,14 +338,14 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
                 * lazily):
                 */
                add_entry_to_dir(get_ref_dir(refs->loose->root),
-                                create_dir_entry(refs->loose, "refs/", 5, 1));
+                                create_dir_entry(refs->loose, "refs/", 5));
        }
        return refs->loose;
 }
 
-static int files_read_raw_ref(struct ref_store *ref_store,
-                             const char *refname, struct object_id *oid,
-                             struct strbuf *referent, unsigned int *type)
+static int files_read_raw_ref(struct ref_store *ref_store, const char *refname,
+                             struct object_id *oid, struct strbuf *referent,
+                             unsigned int *type, int *failure_errno)
 {
        struct files_ref_store *refs =
                files_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
@@ -354,7 +356,6 @@ static int files_read_raw_ref(struct ref_store *ref_store,
        struct stat st;
        int fd;
        int ret = -1;
-       int save_errno;
        int remaining_retries = 3;
 
        *type = 0;
@@ -459,10 +460,9 @@ stat_ref:
        ret = parse_loose_ref_contents(buf, oid, referent, type);
 
 out:
-       save_errno = errno;
+       *failure_errno = errno;
        strbuf_release(&sb_path);
        strbuf_release(&sb_contents);
-       errno = save_errno;
        return ret;
 }
 
@@ -531,7 +531,6 @@ static void unlock_ref(struct ref_lock *lock)
 static int lock_raw_ref(struct files_ref_store *refs,
                        const char *refname, int mustexist,
                        const struct string_list *extras,
-                       const struct string_list *skip,
                        struct ref_lock **lock_p,
                        struct strbuf *referent,
                        unsigned int *type,
@@ -541,6 +540,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
        struct strbuf ref_file = STRBUF_INIT;
        int attempts_remaining = 3;
        int ret = TRANSACTION_GENERIC_ERROR;
+       int failure_errno;
 
        assert(err);
        files_assert_main_repository(refs, "lock_raw_ref");
@@ -568,7 +568,7 @@ retry:
                 * reason to expect this error to be transitory.
                 */
                if (refs_verify_refname_available(&refs->base, refname,
-                                                 extras, skip, err)) {
+                                                 extras, NULL, err)) {
                        if (mustexist) {
                                /*
                                 * To the user the relevant error is
@@ -611,7 +611,9 @@ retry:
        if (hold_lock_file_for_update_timeout(
                            &lock->lk, ref_file.buf, LOCK_NO_DEREF,
                            get_files_ref_lock_timeout_ms()) < 0) {
-               if (errno == ENOENT && --attempts_remaining > 0) {
+               int myerr = errno;
+               errno = 0;
+               if (myerr == ENOENT && --attempts_remaining > 0) {
                        /*
                         * Maybe somebody just deleted one of the
                         * directories leading to ref_file.  Try
@@ -619,7 +621,7 @@ retry:
                         */
                        goto retry;
                } else {
-                       unable_to_lock_message(ref_file.buf, errno, err);
+                       unable_to_lock_message(ref_file.buf, myerr, err);
                        goto error_return;
                }
        }
@@ -629,9 +631,9 @@ retry:
         * fear that its value will change.
         */
 
-       if (files_read_raw_ref(&refs->base, refname,
-                              &lock->old_oid, referent, type)) {
-               if (errno == ENOENT) {
+       if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
+                              type, &failure_errno)) {
+               if (failure_errno == ENOENT) {
                        if (mustexist) {
                                /* Garden variety missing reference. */
                                strbuf_addf(err, "unable to resolve reference '%s'",
@@ -655,7 +657,7 @@ retry:
                                 *   reference named "refs/foo/bar/baz".
                                 */
                        }
-               } else if (errno == EISDIR) {
+               } else if (failure_errno == EISDIR) {
                        /*
                         * There is a directory in the way. It might have
                         * contained references that have been deleted. If
@@ -673,7 +675,7 @@ retry:
                                                          REMOVE_DIR_EMPTY_ONLY)) {
                                if (refs_verify_refname_available(
                                                    &refs->base, refname,
-                                                   extras, skip, err)) {
+                                                   extras, NULL, err)) {
                                        /*
                                         * The error message set by
                                         * verify_refname_available() is OK.
@@ -693,13 +695,13 @@ retry:
                                        goto error_return;
                                }
                        }
-               } else if (errno == EINVAL && (*type & REF_ISBROKEN)) {
+               } else if (failure_errno == EINVAL && (*type & REF_ISBROKEN)) {
                        strbuf_addf(err, "unable to resolve reference '%s': "
                                    "reference broken", refname);
                        goto error_return;
                } else {
                        strbuf_addf(err, "unable to resolve reference '%s': %s",
-                                   refname, strerror(errno));
+                                   refname, strerror(failure_errno));
                        goto error_return;
                }
 
@@ -710,7 +712,7 @@ retry:
                 */
                if (refs_verify_refname_available(
                                    refs->packed_ref_store, refname,
-                                   extras, skip, err))
+                                   extras, NULL, err))
                        goto error_return;
        }
 
@@ -730,6 +732,7 @@ struct files_ref_iterator {
        struct ref_iterator base;
 
        struct ref_iterator *iter0;
+       struct repository *repo;
        unsigned int flags;
 };
 
@@ -744,8 +747,14 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
                    ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
                        continue;
 
+               if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&
+                   (iter->iter0->flags & REF_ISSYMREF) &&
+                   (iter->iter0->flags & REF_ISBROKEN))
+                       continue;
+
                if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
                    !ref_resolves_to_object(iter->iter0->refname,
+                                           iter->repo,
                                            iter->iter0->oid,
                                            iter->iter0->flags))
                        continue;
@@ -824,7 +833,7 @@ static struct ref_iterator *files_ref_iterator_begin(
         */
 
        loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
-                                             prefix, 1);
+                                             prefix, ref_store->repo, 1);
 
        /*
         * The packed-refs file might contain broken references, for
@@ -848,45 +857,119 @@ static struct ref_iterator *files_ref_iterator_begin(
        base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
                               overlay_iter->ordered);
        iter->iter0 = overlay_iter;
+       iter->repo = ref_store->repo;
        iter->flags = flags;
 
        return ref_iterator;
 }
 
 /*
- * Verify that the reference locked by lock has the value old_oid
- * (unless it is NULL).  Fail if the reference doesn't exist and
- * mustexist is set. Return 0 on success. On error, write an error
- * message to err, set errno, and return a negative value.
+ * Callback function for raceproof_create_file(). This function is
+ * expected to do something that makes dirname(path) permanent despite
+ * the fact that other processes might be cleaning up empty
+ * directories at the same time. Usually it will create a file named
+ * path, but alternatively it could create another file in that
+ * directory, or even chdir() into that directory. The function should
+ * return 0 if the action was completed successfully. On error, it
+ * should return a nonzero result and set errno.
+ * raceproof_create_file() treats two errno values specially:
+ *
+ * - ENOENT -- dirname(path) does not exist. In this case,
+ *             raceproof_create_file() tries creating dirname(path)
+ *             (and any parent directories, if necessary) and calls
+ *             the function again.
+ *
+ * - EISDIR -- the file already exists and is a directory. In this
+ *             case, raceproof_create_file() removes the directory if
+ *             it is empty (and recursively any empty directories that
+ *             it contains) and calls the function again.
+ *
+ * Any other errno causes raceproof_create_file() to fail with the
+ * callback's return value and errno.
+ *
+ * Obviously, this function should be OK with being called again if it
+ * fails with ENOENT or EISDIR. In other scenarios it will not be
+ * called again.
+ */
+typedef int create_file_fn(const char *path, void *cb);
+
+/*
+ * Create a file in dirname(path) by calling fn, creating leading
+ * directories if necessary. Retry a few times in case we are racing
+ * with another process that is trying to clean up the directory that
+ * contains path. See the documentation for create_file_fn for more
+ * details.
+ *
+ * Return the value and set the errno that resulted from the most
+ * recent call of fn. fn is always called at least once, and will be
+ * called more than once if it returns ENOENT or EISDIR.
  */
-static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
-                      const struct object_id *old_oid, int mustexist,
-                      struct strbuf *err)
+static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
 {
-       assert(err);
+       /*
+        * The number of times we will try to remove empty directories
+        * in the way of path. This is only 1 because if another
+        * process is racily creating directories that conflict with
+        * us, we don't want to fight against them.
+        */
+       int remove_directories_remaining = 1;
 
-       if (refs_read_ref_full(ref_store, lock->ref_name,
-                              mustexist ? RESOLVE_REF_READING : 0,
-                              &lock->old_oid, NULL)) {
-               if (old_oid) {
-                       int save_errno = errno;
-                       strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
-                       errno = save_errno;
-                       return -1;
-               } else {
-                       oidclr(&lock->old_oid);
-                       return 0;
-               }
-       }
-       if (old_oid && !oideq(&lock->old_oid, old_oid)) {
-               strbuf_addf(err, "ref '%s' is at %s but expected %s",
-                           lock->ref_name,
-                           oid_to_hex(&lock->old_oid),
-                           oid_to_hex(old_oid));
-               errno = EBUSY;
-               return -1;
+       /*
+        * The number of times that we will try to create the
+        * directories containing path. We are willing to attempt this
+        * more than once, because another process could be trying to
+        * clean up empty directories at the same time as we are
+        * trying to create them.
+        */
+       int create_directories_remaining = 3;
+
+       /* A scratch copy of path, filled lazily if we need it: */
+       struct strbuf path_copy = STRBUF_INIT;
+
+       int ret, save_errno;
+
+       /* Sanity check: */
+       assert(*path);
+
+retry_fn:
+       ret = fn(path, cb);
+       save_errno = errno;
+       if (!ret)
+               goto out;
+
+       if (errno == EISDIR && remove_directories_remaining-- > 0) {
+               /*
+                * A directory is in the way. Maybe it is empty; try
+                * to remove it:
+                */
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
+                       goto retry_fn;
+       } else if (errno == ENOENT && create_directories_remaining-- > 0) {
+               /*
+                * Maybe the containing directory didn't exist, or
+                * maybe it was just deleted by a process that is
+                * racing with us to clean up empty directories. Try
+                * to create it:
+                */
+               enum scld_error scld_result;
+
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               do {
+                       scld_result = safe_create_leading_directories(path_copy.buf);
+                       if (scld_result == SCLD_OK)
+                               goto retry_fn;
+               } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
        }
-       return 0;
+
+out:
+       strbuf_release(&path_copy);
+       errno = save_errno;
+       return ret;
 }
 
 static int remove_empty_directories(struct strbuf *path)
@@ -910,64 +993,27 @@ static int create_reflock(const char *path, void *cb)
 
 /*
  * Locks a ref returning the lock on success and NULL on failure.
- * On failure errno is set to something meaningful.
  */
 static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
-                                          const char *refname,
-                                          const struct object_id *old_oid,
-                                          const struct string_list *extras,
-                                          const struct string_list *skip,
-                                          unsigned int flags, int *type,
+                                          const char *refname, int *type,
                                           struct strbuf *err)
 {
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
-       int last_errno = 0;
-       int mustexist = (old_oid && !is_null_oid(old_oid));
-       int resolve_flags = RESOLVE_REF_NO_RECURSE;
-       int resolved;
 
        files_assert_main_repository(refs, "lock_ref_oid_basic");
        assert(err);
 
        CALLOC_ARRAY(lock, 1);
 
-       if (mustexist)
-               resolve_flags |= RESOLVE_REF_READING;
-       if (flags & REF_DELETING)
-               resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
-
        files_ref_path(refs, &ref_file, refname);
-       resolved = !!refs_resolve_ref_unsafe(&refs->base,
-                                            refname, resolve_flags,
-                                            &lock->old_oid, type);
-       if (!resolved && errno == EISDIR) {
-               /*
-                * we are trying to lock foo but we used to
-                * have foo/bar which now does not exist;
-                * it is normal for the empty directory 'foo'
-                * to remain.
-                */
-               if (remove_empty_directories(&ref_file)) {
-                       last_errno = errno;
-                       if (!refs_verify_refname_available(
-                                           &refs->base,
-                                           refname, extras, skip, err))
-                               strbuf_addf(err, "there are still refs under '%s'",
-                                           refname);
-                       goto error_return;
-               }
-               resolved = !!refs_resolve_ref_unsafe(&refs->base,
-                                                    refname, resolve_flags,
-                                                    &lock->old_oid, type);
-       }
-       if (!resolved) {
-               last_errno = errno;
-               if (last_errno != ENOTDIR ||
-                   !refs_verify_refname_available(&refs->base, refname,
-                                                  extras, skip, err))
+       if (!refs_resolve_ref_unsafe(&refs->base, refname,
+                                    RESOLVE_REF_NO_RECURSE,
+                                    &lock->old_oid, type)) {
+               if (!refs_verify_refname_available(&refs->base, refname,
+                                                  NULL, NULL, err))
                        strbuf_addf(err, "unable to resolve reference '%s': %s",
-                                   refname, strerror(last_errno));
+                                   refname, strerror(errno));
 
                goto error_return;
        }
@@ -980,23 +1026,20 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
         */
        if (is_null_oid(&lock->old_oid) &&
            refs_verify_refname_available(refs->packed_ref_store, refname,
-                                         extras, skip, err)) {
-               last_errno = ENOTDIR;
+                                         NULL, NULL, err))
                goto error_return;
-       }
 
        lock->ref_name = xstrdup(refname);
 
        if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
-               last_errno = errno;
                unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
        }
 
-       if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) {
-               last_errno = errno;
-               goto error_return;
-       }
+       if (refs_read_ref_full(&refs->base, lock->ref_name,
+                              0,
+                              &lock->old_oid, NULL))
+               oidclr(&lock->old_oid);
        goto out;
 
  error_return:
@@ -1005,7 +1048,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
 
  out:
        strbuf_release(&ref_file);
-       errno = last_errno;
        return lock;
 }
 
@@ -1132,7 +1174,7 @@ static int should_pack_ref(const char *refname,
                return 0;
 
        /* Do not pack broken refs: */
-       if (!ref_resolves_to_object(refname, oid, ref_flags))
+       if (!ref_resolves_to_object(refname, the_repository, oid, ref_flags))
                return 0;
 
        return 1;
@@ -1155,7 +1197,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
 
        packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
 
-       iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
+       iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL,
+                                       the_repository, 0);
        while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
                /*
                 * If the loose reference can be packed, add an entry
@@ -1415,8 +1458,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 
        logmoved = log;
 
-       lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL,
-                                 REF_NO_DEREF, NULL, &err);
+       lock = lock_ref_oid_basic(refs, newrefname, NULL, &err);
        if (!lock) {
                if (copy)
                        error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
@@ -1438,8 +1480,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
        goto out;
 
  rollback:
-       lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL,
-                                 REF_NO_DEREF, NULL, &err);
+       lock = lock_ref_oid_basic(refs, oldrefname, NULL, &err);
        if (!lock) {
                error("unable to lock %s for rollback: %s", oldrefname, err.buf);
                strbuf_release(&err);
@@ -1846,9 +1887,7 @@ static int files_create_symref(struct ref_store *ref_store,
        struct ref_lock *lock;
        int ret;
 
-       lock = lock_ref_oid_basic(refs, refname, NULL,
-                                 NULL, NULL, REF_NO_DEREF, NULL,
-                                 &err);
+       lock = lock_ref_oid_basic(refs, refname, NULL, &err);
        if (!lock) {
                error("%s", err.buf);
                strbuf_release(&err);
@@ -2416,7 +2455,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
        }
 
        ret = lock_raw_ref(refs, update->refname, mustexist,
-                          affected_refnames, NULL,
+                          affected_refnames,
                           &lock, &referent,
                           &update->type, err);
        if (ret) {
@@ -3037,7 +3076,7 @@ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
 }
 
 static int files_reflog_expire(struct ref_store *ref_store,
-                              const char *refname, const struct object_id *oid,
+                              const char *refname,
                               unsigned int flags,
                               reflog_expiry_prepare_fn prepare_fn,
                               reflog_expiry_should_prune_fn should_prune_fn,
@@ -3054,6 +3093,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
        int status = 0;
        int type;
        struct strbuf err = STRBUF_INIT;
+       const struct object_id *oid;
 
        memset(&cb, 0, sizeof(cb));
        cb.flags = flags;
@@ -3065,14 +3105,26 @@ static int files_reflog_expire(struct ref_store *ref_store,
         * reference itself, plus we might need to update the
         * reference if --updateref was specified:
         */
-       lock = lock_ref_oid_basic(refs, refname, oid,
-                                 NULL, NULL, REF_NO_DEREF,
-                                 &type, &err);
+       lock = lock_ref_oid_basic(refs, refname, &type, &err);
        if (!lock) {
                error("cannot lock ref '%s': %s", refname, err.buf);
                strbuf_release(&err);
                return -1;
        }
+       oid = &lock->old_oid;
+
+       /*
+        * When refs are deleted, their reflog is deleted before the
+        * ref itself is deleted. This is because there is no separate
+        * lock for reflog; instead we take a lock on the ref with
+        * lock_ref_oid_basic().
+        *
+        * If a race happens and the reflog doesn't exist after we've
+        * acquired the lock that's OK. We've got nothing more to do;
+        * We were asked to delete the reflog, but someone else
+        * deleted it! The caller doesn't care that we deleted it,
+        * just that it is deleted. So we can return successfully.
+        */
        if (!refs_reflog_exists(ref_store, refname)) {
                unlock_ref(lock);
                return 0;
index f8aa97d7998ecb2db1e677337b3d04eb052e86a8..1c5211b03e48cf23fd3a4d02e8aa6278b24ce26f 100644 (file)
@@ -193,13 +193,15 @@ static int release_snapshot(struct snapshot *snapshot)
        }
 }
 
-struct ref_store *packed_ref_store_create(const char *path,
+struct ref_store *packed_ref_store_create(struct repository *repo,
+                                         const char *path,
                                          unsigned int store_flags)
 {
        struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
 
        base_ref_store_init(ref_store, &refs_be_packed);
+       ref_store->repo = repo;
        ref_store->gitdir = xstrdup(path);
        refs->store_flags = store_flags;
 
@@ -724,9 +726,9 @@ static struct snapshot *get_snapshot(struct packed_ref_store *refs)
        return refs->snapshot;
 }
 
-static int packed_read_raw_ref(struct ref_store *ref_store,
-                              const char *refname, struct object_id *oid,
-                              struct strbuf *referent, unsigned int *type)
+static int packed_read_raw_ref(struct ref_store *ref_store, const char *refname,
+                              struct object_id *oid, struct strbuf *referent,
+                              unsigned int *type, int *failure_errno)
 {
        struct packed_ref_store *refs =
                packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
@@ -739,7 +741,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store,
 
        if (!rec) {
                /* refname is not a packed reference. */
-               errno = ENOENT;
+               *failure_errno = ENOENT;
                return -1;
        }
 
@@ -776,6 +778,7 @@ struct packed_ref_iterator {
        struct object_id oid, peeled;
        struct strbuf refname_buf;
 
+       struct repository *repo;
        unsigned int flags;
 };
 
@@ -864,8 +867,8 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
                        continue;
 
                if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
-                   !ref_resolves_to_object(iter->base.refname, &iter->oid,
-                                           iter->flags))
+                   !ref_resolves_to_object(iter->base.refname, iter->repo,
+                                           &iter->oid, iter->flags))
                        continue;
 
                return ITER_OK;
@@ -883,6 +886,9 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
        struct packed_ref_iterator *iter =
                (struct packed_ref_iterator *)ref_iterator;
 
+       if (iter->repo != the_repository)
+               BUG("peeling for non-the_repository is not supported");
+
        if ((iter->base.flags & REF_KNOWS_PEELED)) {
                oidcpy(peeled, &iter->peeled);
                return is_null_oid(&iter->peeled) ? -1 : 0;
@@ -954,6 +960,7 @@ static struct ref_iterator *packed_ref_iterator_begin(
 
        iter->base.oid = &iter->oid;
 
+       iter->repo = ref_store->repo;
        iter->flags = flags;
 
        if (prefix && *prefix)
@@ -1600,6 +1607,7 @@ static int packed_for_each_reflog_ent(struct ref_store *ref_store,
                                      const char *refname,
                                      each_reflog_ent_fn fn, void *cb_data)
 {
+       BUG("packed reference store does not support reflogs");
        return 0;
 }
 
@@ -1608,12 +1616,14 @@ static int packed_for_each_reflog_ent_reverse(struct ref_store *ref_store,
                                              each_reflog_ent_fn fn,
                                              void *cb_data)
 {
+       BUG("packed reference store does not support reflogs");
        return 0;
 }
 
 static int packed_reflog_exists(struct ref_store *ref_store,
                               const char *refname)
 {
+       BUG("packed reference store does not support reflogs");
        return 0;
 }
 
@@ -1627,17 +1637,19 @@ static int packed_create_reflog(struct ref_store *ref_store,
 static int packed_delete_reflog(struct ref_store *ref_store,
                               const char *refname)
 {
+       BUG("packed reference store does not support reflogs");
        return 0;
 }
 
 static int packed_reflog_expire(struct ref_store *ref_store,
-                               const char *refname, const struct object_id *oid,
+                               const char *refname,
                                unsigned int flags,
                                reflog_expiry_prepare_fn prepare_fn,
                                reflog_expiry_should_prune_fn should_prune_fn,
                                reflog_expiry_cleanup_fn cleanup_fn,
                                void *policy_cb_data)
 {
+       BUG("packed reference store does not support reflogs");
        return 0;
 }
 
index a01a0aff9c77280d7b3e605230af822fdab24ca9..f61a73ec25b4cb55d96bac23ada11a53e2781f95 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef REFS_PACKED_BACKEND_H
 #define REFS_PACKED_BACKEND_H
 
+struct repository;
 struct ref_transaction;
 
 /*
@@ -12,7 +13,8 @@ struct ref_transaction;
  * even among packed refs.
  */
 
-struct ref_store *packed_ref_store_create(const char *path,
+struct ref_store *packed_ref_store_create(struct repository *repo,
+                                         const char *path,
                                          unsigned int store_flags);
 
 /*
index 49d732f6db961c8daa1f0ff0f4589059420f636c..be4aa5e09818fad082a7c15303746edd8a5ddcaf 100644 (file)
@@ -49,7 +49,7 @@ struct ref_cache *create_ref_cache(struct ref_store *refs,
 
        ret->ref_store = refs;
        ret->fill_ref_dir = fill_ref_dir;
-       ret->root = create_dir_entry(ret, "", 0, 1);
+       ret->root = create_dir_entry(ret, "", 0);
        return ret;
 }
 
@@ -86,14 +86,13 @@ static void clear_ref_dir(struct ref_dir *dir)
 }
 
 struct ref_entry *create_dir_entry(struct ref_cache *cache,
-                                  const char *dirname, size_t len,
-                                  int incomplete)
+                                  const char *dirname, size_t len)
 {
        struct ref_entry *direntry;
 
        FLEX_ALLOC_MEM(direntry, name, dirname, len);
        direntry->u.subdir.cache = cache;
-       direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
+       direntry->flag = REF_DIR | REF_INCOMPLETE;
        return direntry;
 }
 
@@ -144,30 +143,19 @@ int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
 /*
  * Search for a directory entry directly within dir (without
  * recursing).  Sort dir if necessary.  subdirname must be a directory
- * name (i.e., end in '/').  If mkdir is set, then create the
- * directory if it is missing; otherwise, return NULL if the desired
+ * name (i.e., end in '/'). Returns NULL if the desired
  * directory cannot be found.  dir must already be complete.
  */
 static struct ref_dir *search_for_subdir(struct ref_dir *dir,
-                                        const char *subdirname, size_t len,
-                                        int mkdir)
+                                        const char *subdirname, size_t len)
 {
        int entry_index = search_ref_dir(dir, subdirname, len);
        struct ref_entry *entry;
-       if (entry_index == -1) {
-               if (!mkdir)
-                       return NULL;
-               /*
-                * Since dir is complete, the absence of a subdir
-                * means that the subdir really doesn't exist;
-                * therefore, create an empty record for it but mark
-                * the record complete.
-                */
-               entry = create_dir_entry(dir->cache, subdirname, len, 0);
-               add_entry_to_dir(dir, entry);
-       } else {
-               entry = dir->entries[entry_index];
-       }
+
+       if (entry_index == -1)
+               return NULL;
+
+       entry = dir->entries[entry_index];
        return get_ref_dir(entry);
 }
 
@@ -176,18 +164,17 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir,
  * tree that should hold refname. If refname is a directory name
  * (i.e., it ends in '/'), then return that ref_dir itself. dir must
  * represent the top-level directory and must already be complete.
- * Sort ref_dirs and recurse into subdirectories as necessary. If
- * mkdir is set, then create any missing directories; otherwise,
+ * Sort ref_dirs and recurse into subdirectories as necessary. Will
  * return NULL if the desired directory cannot be found.
  */
 static struct ref_dir *find_containing_dir(struct ref_dir *dir,
-                                          const char *refname, int mkdir)
+                                          const char *refname)
 {
        const char *slash;
        for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
                size_t dirnamelen = slash - refname + 1;
                struct ref_dir *subdir;
-               subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
+               subdir = search_for_subdir(dir, refname, dirnamelen);
                if (!subdir) {
                        dir = NULL;
                        break;
@@ -202,7 +189,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname)
 {
        int entry_index;
        struct ref_entry *entry;
-       dir = find_containing_dir(dir, refname, 0);
+       dir = find_containing_dir(dir, refname);
        if (!dir)
                return NULL;
        entry_index = search_ref_dir(dir, refname, strlen(refname));
@@ -212,50 +199,6 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname)
        return (entry->flag & REF_DIR) ? NULL : entry;
 }
 
-int remove_entry_from_dir(struct ref_dir *dir, const char *refname)
-{
-       int refname_len = strlen(refname);
-       int entry_index;
-       struct ref_entry *entry;
-       int is_dir = refname[refname_len - 1] == '/';
-       if (is_dir) {
-               /*
-                * refname represents a reference directory.  Remove
-                * the trailing slash; otherwise we will get the
-                * directory *representing* refname rather than the
-                * one *containing* it.
-                */
-               char *dirname = xmemdupz(refname, refname_len - 1);
-               dir = find_containing_dir(dir, dirname, 0);
-               free(dirname);
-       } else {
-               dir = find_containing_dir(dir, refname, 0);
-       }
-       if (!dir)
-               return -1;
-       entry_index = search_ref_dir(dir, refname, refname_len);
-       if (entry_index == -1)
-               return -1;
-       entry = dir->entries[entry_index];
-
-       MOVE_ARRAY(&dir->entries[entry_index],
-                  &dir->entries[entry_index + 1], dir->nr - entry_index - 1);
-       dir->nr--;
-       if (dir->sorted > entry_index)
-               dir->sorted--;
-       free_ref_entry(entry);
-       return dir->nr;
-}
-
-int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref)
-{
-       dir = find_containing_dir(dir, ref->name, 1);
-       if (!dir)
-               return -1;
-       add_entry_to_dir(dir, ref);
-       return 0;
-}
-
 /*
  * Emit a warning and return true iff ref1 and ref2 have the same name
  * and the same oid. Die if they have the same name but different
@@ -435,6 +378,8 @@ struct cache_ref_iterator {
         * on from there.)
         */
        struct cache_ref_iterator_level *levels;
+
+       struct repository *repo;
 };
 
 static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
@@ -491,6 +436,11 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
 static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
                                   struct object_id *peeled)
 {
+       struct cache_ref_iterator *iter =
+               (struct cache_ref_iterator *)ref_iterator;
+
+       if (iter->repo != the_repository)
+               BUG("peeling for non-the_repository is not supported");
        return peel_object(ref_iterator->oid, peeled) ? -1 : 0;
 }
 
@@ -513,6 +463,7 @@ static struct ref_iterator_vtable cache_ref_iterator_vtable = {
 
 struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
                                              const char *prefix,
+                                             struct repository *repo,
                                              int prime_dir)
 {
        struct ref_dir *dir;
@@ -522,7 +473,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
 
        dir = get_ref_dir(cache->root);
        if (prefix && *prefix)
-               dir = find_containing_dir(dir, prefix, 0);
+               dir = find_containing_dir(dir, prefix);
        if (!dir)
                /* There's nothing to iterate over. */
                return empty_ref_iterator_begin();
@@ -547,5 +498,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
                level->prefix_state = PREFIX_CONTAINS_DIR;
        }
 
+       iter->repo = repo;
+
        return ref_iterator;
 }
index 3bfb89d2b343e958ed094a7fd922f2fd6e0c4178..850d9d3744e94270fb5cd445b4f6b7b18aac8128 100644 (file)
@@ -169,8 +169,7 @@ struct ref_dir *get_ref_dir(struct ref_entry *entry);
  * "refs/heads/") or "" for the top-level directory.
  */
 struct ref_entry *create_dir_entry(struct ref_cache *cache,
-                                  const char *dirname, size_t len,
-                                  int incomplete);
+                                  const char *dirname, size_t len);
 
 struct ref_entry *create_ref_entry(const char *refname,
                                   const struct object_id *oid, int flag);
@@ -199,29 +198,6 @@ void free_ref_cache(struct ref_cache *cache);
  */
 void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
 
-/*
- * Remove the entry with the given name from dir, recursing into
- * subdirectories as necessary.  If refname is the name of a directory
- * (i.e., ends with '/'), then remove the directory and its contents.
- * If the removal was successful, return the number of entries
- * remaining in the directory entry that contained the deleted entry.
- * If the name was not found, return -1.  Please note that this
- * function only deletes the entry from the cache; it does not delete
- * it from the filesystem or ensure that other cache entries (which
- * might be symbolic references to the removed entry) are updated.
- * Nor does it remove any containing dir entries that might be made
- * empty by the removal.  dir must represent the top-level directory
- * and must already be complete.
- */
-int remove_entry_from_dir(struct ref_dir *dir, const char *refname);
-
-/*
- * Add a ref_entry to the ref_dir (unsorted), recursing into
- * subdirectories as necessary.  dir must represent the top-level
- * directory.  Return 0 on success.
- */
-int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref);
-
 /*
  * Find the value entry with the given name in dir, sorting ref_dirs
  * and recursing into subdirectories as necessary.  If the name is not
@@ -238,6 +214,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname);
  */
 struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
                                              const char *prefix,
+                                             struct repository *repo,
                                              int prime_dir);
 
 #endif /* REFS_REF_CACHE_H */
index 3155708345fcbccdd02f69dd4b155f9b6fd1d098..12224742ede8f01d4f8d88ceaaa2e326413bf674 100644 (file)
@@ -66,6 +66,7 @@ int refname_is_safe(const char *refname);
  * referred-to object does not exist, emit a warning and return false.
  */
 int ref_resolves_to_object(const char *refname,
+                          struct repository *repo,
                           const struct object_id *oid,
                           unsigned int flags);
 
@@ -245,8 +246,36 @@ int refs_rename_ref_available(struct ref_store *refs,
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define SYMREF_MAXDEPTH 5
 
-/* Include broken references in a do_for_each_ref*() iteration: */
-#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+/*
+ * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(),
+ * which feeds it).
+ */
+enum do_for_each_ref_flags {
+       /*
+        * Include broken references in a do_for_each_ref*() iteration, which
+        * would normally be omitted. This includes both refs that point to
+        * missing objects (a true repository corruption), ones with illegal
+        * names (which we prefer not to expose to callers), as well as
+        * dangling symbolic refs (i.e., those that point to a non-existent
+        * ref; this is not a corruption, but as they have no valid oid, we
+        * omit them from normal iteration results).
+        */
+       DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0),
+
+       /*
+        * Only include per-worktree refs in a do_for_each_ref*() iteration.
+        * Normally this will be used with a files ref_store, since that's
+        * where all reference backends will presumably store their
+        * per-worktree refs.
+        */
+       DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1),
+
+       /*
+        * Omit dangling symrefs from output; this only has an effect with
+        * INCLUDE_BROKEN, since they are otherwise not included at all.
+        */
+       DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),
+};
 
 /*
  * Reference iterators
@@ -349,16 +378,12 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
  * Return an iterator that goes over each reference in `refs` for
  * which the refname begins with prefix. If trim is non-zero, then
  * trim that many characters off the beginning of each refname.
- * The output is ordered by refname. The following flags are supported:
- *
- * DO_FOR_EACH_INCLUDE_BROKEN: include broken references in
- *         the iteration.
- *
- * DO_FOR_EACH_PER_WORKTREE_ONLY: only produce REF_TYPE_PER_WORKTREE refs.
+ * The output is ordered by refname.
  */
 struct ref_iterator *refs_ref_iterator_begin(
                struct ref_store *refs,
-               const char *prefix, int trim, int flags);
+               const char *prefix, int trim,
+               enum do_for_each_ref_flags flags);
 
 /*
  * A callback function used to instruct merge_ref_iterator how to
@@ -446,10 +471,8 @@ void base_ref_iterator_free(struct ref_iterator *iter);
 /*
  * backend-specific implementation of ref_iterator_advance. For symrefs, the
  * function should set REF_ISSYMREF, and it should also dereference the symref
- * to provide the OID referent. If DO_FOR_EACH_INCLUDE_BROKEN is set, symrefs
- * with non-existent referents and refs pointing to non-existent object names
- * should also be returned. If DO_FOR_EACH_PER_WORKTREE_ONLY, only
- * REF_TYPE_PER_WORKTREE refs should be returned.
+ * to provide the OID referent. It should respect do_for_each_ref_flags
+ * that were passed to refs_ref_iterator_begin().
  */
 typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
 
@@ -498,14 +521,6 @@ int do_for_each_repo_ref_iterator(struct repository *r,
                                  struct ref_iterator *iter,
                                  each_repo_ref_fn fn, void *cb_data);
 
-/*
- * Only include per-worktree refs in a do_for_each_ref*() iteration.
- * Normally this will be used with a files ref_store, since that's
- * where all reference backends will presumably store their
- * per-worktree refs.
- */
-#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02
-
 struct ref_store;
 
 /* refs backends */
@@ -525,7 +540,8 @@ struct ref_store;
  * should call base_ref_store_init() to initialize the shared part of
  * the ref_store and to record the ref_store for later lookup.
  */
-typedef struct ref_store *ref_store_init_fn(const char *gitdir,
+typedef struct ref_store *ref_store_init_fn(struct repository *repo,
+                                           const char *gitdir,
                                            unsigned int flags);
 
 typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err);
@@ -593,7 +609,7 @@ typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname,
                             int force_create, struct strbuf *err);
 typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname);
 typedef int reflog_expire_fn(struct ref_store *ref_store,
-                            const char *refname, const struct object_id *oid,
+                            const char *refname,
                             unsigned int flags,
                             reflog_expiry_prepare_fn prepare_fn,
                             reflog_expiry_should_prune_fn should_prune_fn,
@@ -620,11 +636,15 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
  * properly-formatted or even safe reference name. NEITHER INPUT NOR
  * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
  *
- * Return 0 on success. If the ref doesn't exist, set errno to ENOENT
- * and return -1. If the ref exists but is neither a symbolic ref nor
- * an object ID, it is broken; set REF_ISBROKEN in type, set errno to
- * EINVAL, and return -1. If there is another error reading the ref,
- * set errno appropriately and return -1.
+ * Return 0 on success, or -1 on failure. If the ref exists but is neither a
+ * symbolic ref nor an object ID, it is broken. In this case set REF_ISBROKEN in
+ * type, and return -1 (failure_errno should not be ENOENT)
+ *
+ * failure_errno provides errno codes that are interpreted beyond error
+ * reporting. The following error codes have special meaning:
+ *    * ENOENT: the ref doesn't exist
+ *    * EISDIR: ref name is a directory
+ *    * ENOTDIR: ref prefix is not a directory
  *
  * Backend-specific flags might be set in type as well, regardless of
  * outcome.
@@ -638,9 +658,9 @@ typedef int reflog_expire_fn(struct ref_store *ref_store,
  * - in all other cases, referent will be untouched, and therefore
  *   refname will still be valid and unchanged.
  */
-typedef int read_raw_ref_fn(struct ref_store *ref_store,
-                           const char *refname, struct object_id *oid,
-                           struct strbuf *referent, unsigned int *type);
+typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname,
+                           struct object_id *oid, struct strbuf *referent,
+                           unsigned int *type, int *failure_errno);
 
 struct ref_storage_be {
        struct ref_storage_be *next;
@@ -683,7 +703,12 @@ struct ref_store {
        /* The backend describing this ref_store's storage scheme: */
        const struct ref_storage_be *be;
 
-       /* The gitdir that this ref_store applies to: */
+       struct repository *repo;
+
+       /*
+        * The gitdir that this ref_store applies to. Note that this is not
+        * necessarily repo->gitdir if the repo has multiple worktrees.
+        */
        char *gitdir;
 };
 
index 6c320d5704598e4f0025af9c35dcc407c4cba980..5975103b96af34959713697f18de8ededf9272c5 100644 (file)
@@ -185,8 +185,6 @@ static int set_option(const char *name, const char *value)
                                                 strbuf_detach(&unquoted, NULL));
                }
                return 0;
-
-#if LIBCURL_VERSION_NUM >= 0x070a08
        } else if (!strcmp(name, "family")) {
                if (!strcmp(value, "ipv4"))
                        git_curl_ipresolve = CURL_IPRESOLVE_V4;
@@ -197,7 +195,6 @@ static int set_option(const char *name, const char *value)
                else
                        return -1;
                return 0;
-#endif /* LIBCURL_VERSION_NUM >= 0x070a08 */
        } else if (!strcmp(name, "from-promisor")) {
                options.from_promisor = 1;
                return 0;
@@ -502,6 +499,10 @@ static struct discovery *discover_refs(const char *service, int for_push)
                show_http_message(&type, &charset, &buffer);
                die(_("Authentication failed for '%s'"),
                    transport_anonymize_url(url.buf));
+       case HTTP_NOMATCHPUBLICKEY:
+               show_http_message(&type, &charset, &buffer);
+               die(_("unable to access '%s' with http.pinnedPubkey configuration: %s"),
+                   transport_anonymize_url(url.buf), curl_errorstr);
        default:
                show_http_message(&type, &charset, &buffer);
                die(_("unable to access '%s': %s"),
@@ -709,7 +710,6 @@ static size_t rpc_out(void *ptr, size_t eltsize,
        return avail;
 }
 
-#ifndef NO_CURL_IOCTL
 static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
 {
        struct rpc_state *rpc = clientp;
@@ -730,7 +730,6 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
                return CURLIOE_UNKNOWNCMD;
        }
 }
-#endif
 
 struct check_pktline_state {
        char len_buf[4];
@@ -858,7 +857,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
        curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, 4);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buf);
 
        err = run_slot(slot, results);
 
@@ -949,10 +948,8 @@ retry:
                rpc->initial_buffer = 1;
                curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
                curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
-#ifndef NO_CURL_IOCTL
                curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
                curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
-#endif
                if (options.verbosity > 1) {
                        fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
                        fflush(stderr);
@@ -1023,7 +1020,7 @@ retry:
        rpc_in_data.slot = slot;
        rpc_in_data.check_pktline = stateless_connect;
        memset(&rpc_in_data.pktline_state, 0, sizeof(rpc_in_data.pktline_state));
-       curl_easy_setopt(slot->curl, CURLOPT_FILE, &rpc_in_data);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &rpc_in_data);
        curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
 
 
@@ -1485,8 +1482,8 @@ int cmd_main(int argc, const char **argv)
        options.verbosity = 1;
        options.progress = !!isatty(2);
        options.thin = 1;
-       string_list_init(&options.deepen_not, 1);
-       string_list_init(&options.push_options, 1);
+       string_list_init_dup(&options.deepen_not);
+       string_list_init_dup(&options.push_options);
 
        /*
         * Just report "remote-curl" here (folding all the various aliases
index 40e785da38e0577a8d0c309e15e55f943743c7f8..f958543d707d0da5282b7fb1bbb70a53bc834647 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1111,7 +1111,7 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
                "Neither worked, so we gave up. You must fully qualify the ref."),
              dst_value, matched_src_name);
 
-       if (!advice_push_unqualified_ref_name)
+       if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
                return;
 
        if (get_oid(matched_src_name, &oid))
@@ -2118,7 +2118,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                strbuf_addf(sb,
                        _("Your branch is based on '%s', but the upstream is gone.\n"),
                        base);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git branch --unset-upstream\" to fixup)\n"));
        } else if (!sti) {
@@ -2129,7 +2129,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                strbuf_addf(sb,
                            _("Your branch and '%s' refer to different commits.\n"),
                            base);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
                                    "git status --ahead-behind");
        } else if (!theirs) {
@@ -2138,7 +2138,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                           "Your branch is ahead of '%s' by %d commits.\n",
                           ours),
                        base, ours);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git push\" to publish your local commits)\n"));
        } else if (!ours) {
@@ -2149,7 +2149,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                               "and can be fast-forwarded.\n",
                           theirs),
                        base, theirs);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git pull\" to update your local branch)\n"));
        } else {
@@ -2162,7 +2162,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
                               "respectively.\n",
                           ours + theirs),
                        base, ours, theirs);
-               if (advice_status_hints)
+               if (advice_enabled(ADVICE_STATUS_HINTS))
                        strbuf_addstr(sb,
                                _("  (use \"git pull\" to merge the remote branch into yours)\n"));
        }
@@ -2403,7 +2403,7 @@ struct reflog_commit_array {
        size_t nr, alloc;
 };
 
-#define REFLOG_COMMIT_ARRAY_INIT { NULL, 0, 0 }
+#define REFLOG_COMMIT_ARRAY_INIT { 0 }
 
 /* Append a commit to the array. */
 static void append_commit(struct reflog_commit_array *arr,
index 0cfe8b787db26d9eaf55c31e839fc291477b879e..b93e91a212eb98aae494acc544edcd71b77a4761 100644 (file)
@@ -3,40 +3,77 @@
 #include "repository.h"
 #include "midx.h"
 
-#define UPDATE_DEFAULT_BOOL(s,v) do { if (s == -1) { s = v; } } while(0)
+static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
+                         int def)
+{
+       if (repo_config_get_bool(r, key, dest))
+               *dest = def;
+}
 
 void prepare_repo_settings(struct repository *r)
 {
+       int experimental;
        int value;
        char *strval;
+       int manyfiles;
 
-       if (r->settings.initialized)
+       if (r->settings.initialized++)
                return;
 
        /* Defaults */
-       memset(&r->settings, -1, sizeof(r->settings));
+       r->settings.index_version = -1;
+       r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
+       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+
+       /* Booleans config or default, cascades to other settings */
+       repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
+       repo_cfg_bool(r, "feature.experimental", &experimental, 0);
+
+       /* Defaults modified by feature.* */
+       if (experimental) {
+               r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
+       }
+       if (manyfiles) {
+               r->settings.index_version = 4;
+               r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
+       }
 
-       if (!repo_config_get_bool(r, "core.commitgraph", &value))
-               r->settings.core_commit_graph = value;
-       if (!repo_config_get_bool(r, "commitgraph.readchangedpaths", &value))
-               r->settings.commit_graph_read_changed_paths = value;
-       if (!repo_config_get_bool(r, "gc.writecommitgraph", &value))
-               r->settings.gc_write_commit_graph = value;
-       UPDATE_DEFAULT_BOOL(r->settings.core_commit_graph, 1);
-       UPDATE_DEFAULT_BOOL(r->settings.commit_graph_read_changed_paths, 1);
-       UPDATE_DEFAULT_BOOL(r->settings.gc_write_commit_graph, 1);
+       /* Boolean config or default, does not cascade (simple)  */
+       repo_cfg_bool(r, "core.commitgraph", &r->settings.core_commit_graph, 1);
+       repo_cfg_bool(r, "commitgraph.readchangedpaths", &r->settings.commit_graph_read_changed_paths, 1);
+       repo_cfg_bool(r, "gc.writecommitgraph", &r->settings.gc_write_commit_graph, 1);
+       repo_cfg_bool(r, "fetch.writecommitgraph", &r->settings.fetch_write_commit_graph, 0);
+       repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1);
+       repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1);
+       repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0);
 
+       /*
+        * The GIT_TEST_MULTI_PACK_INDEX variable is special in that
+        * either it *or* the config sets
+        * r->settings.core_multi_pack_index if true. We don't take
+        * the environment variable if it exists (even if false) over
+        * any config, as in most other cases.
+        */
+       if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
+               r->settings.core_multi_pack_index = 1;
+
+       /*
+        * Non-boolean config
+        */
        if (!repo_config_get_int(r, "index.version", &value))
                r->settings.index_version = value;
-       if (!repo_config_get_maybe_bool(r, "core.untrackedcache", &value)) {
-               if (value == 0)
-                       r->settings.core_untracked_cache = UNTRACKED_CACHE_REMOVE;
-               else
-                       r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE;
-       } else if (!repo_config_get_string(r, "core.untrackedcache", &strval)) {
-               if (!strcasecmp(strval, "keep"))
-                       r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
 
+       if (!repo_config_get_string(r, "core.untrackedcache", &strval)) {
+               int v = git_parse_maybe_bool(strval);
+
+               /*
+                * If it's set to "keep", or some other non-boolean
+                * value then "v < 0". Then we do nothing and keep it
+                * at the default of UNTRACKED_CACHE_KEEP.
+                */
+               if (v >= 0)
+                       r->settings.core_untracked_cache = v ?
+                               UNTRACKED_CACHE_WRITE : UNTRACKED_CACHE_REMOVE;
                free(strval);
        }
 
@@ -45,39 +82,8 @@ void prepare_repo_settings(struct repository *r)
                        r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
                else if (!strcasecmp(strval, "noop"))
                        r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP;
-               else
-                       r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
        }
 
-       if (!repo_config_get_bool(r, "pack.usesparse", &value))
-               r->settings.pack_use_sparse = value;
-       UPDATE_DEFAULT_BOOL(r->settings.pack_use_sparse, 1);
-
-       value = git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0);
-       if (value || !repo_config_get_bool(r, "core.multipackindex", &value))
-               r->settings.core_multi_pack_index = value;
-       UPDATE_DEFAULT_BOOL(r->settings.core_multi_pack_index, 1);
-
-       if (!repo_config_get_bool(r, "feature.manyfiles", &value) && value) {
-               UPDATE_DEFAULT_BOOL(r->settings.index_version, 4);
-               UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_WRITE);
-       }
-
-       if (!repo_config_get_bool(r, "fetch.writecommitgraph", &value))
-               r->settings.fetch_write_commit_graph = value;
-       UPDATE_DEFAULT_BOOL(r->settings.fetch_write_commit_graph, 0);
-
-       if (!repo_config_get_bool(r, "feature.experimental", &value) && value)
-               UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_SKIPPING);
-
-       /* Hack for test programs like test-dump-untracked-cache */
-       if (ignore_untracked_cache_config)
-               r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
-       else
-               UPDATE_DEFAULT_BOOL(r->settings.core_untracked_cache, UNTRACKED_CACHE_KEEP);
-
-       UPDATE_DEFAULT_BOOL(r->settings.fetch_negotiation_algorithm, FETCH_NEGOTIATION_DEFAULT);
-
        /*
         * This setting guards all index reads to require a full index
         * over a sparse index. After suitable guards are placed in the
@@ -85,11 +91,4 @@ void prepare_repo_settings(struct repository *r)
         * removed.
         */
        r->settings.command_requires_full_index = 1;
-
-       /*
-        * Initialize this as off.
-        */
-       r->settings.sparse_index = 0;
-       if (!repo_config_get_bool(r, "index.sparse", &value) && value)
-               r->settings.sparse_index = 1;
 }
index b2bf44c6fafec83b6360354ae77081759096be6f..c5b90ba93ea816c62eeaf73433e65e2730827063 100644 (file)
@@ -190,19 +190,15 @@ error:
 
 int repo_submodule_init(struct repository *subrepo,
                        struct repository *superproject,
-                       const struct submodule *sub)
+                       const char *path,
+                       const struct object_id *treeish_name)
 {
        struct strbuf gitdir = STRBUF_INIT;
        struct strbuf worktree = STRBUF_INIT;
        int ret = 0;
 
-       if (!sub) {
-               ret = -1;
-               goto out;
-       }
-
-       strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", sub->path);
-       strbuf_repo_worktree_path(&worktree, superproject, "%s", sub->path);
+       strbuf_repo_worktree_path(&gitdir, superproject, "%s/.git", path);
+       strbuf_repo_worktree_path(&worktree, superproject, "%s", path);
 
        if (repo_init(subrepo, gitdir.buf, worktree.buf)) {
                /*
@@ -212,9 +208,15 @@ int repo_submodule_init(struct repository *subrepo,
                 * in the superproject's 'modules' directory.  In this case the
                 * submodule would not have a worktree.
                 */
+               const struct submodule *sub =
+                       submodule_from_path(superproject, treeish_name, path);
+               if (!sub) {
+                       ret = -1;
+                       goto out;
+               }
+
                strbuf_reset(&gitdir);
-               strbuf_repo_git_path(&gitdir, superproject,
-                                    "modules/%s", sub->name);
+               submodule_name_to_gitdir(&gitdir, superproject, sub->name);
 
                if (repo_init(subrepo, gitdir.buf, NULL)) {
                        ret = -1;
@@ -225,7 +227,7 @@ int repo_submodule_init(struct repository *subrepo,
        subrepo->submodule_prefix = xstrfmt("%s%s/",
                                            superproject->submodule_prefix ?
                                            superproject->submodule_prefix :
-                                           "", sub->path);
+                                           "", path);
 
 out:
        strbuf_release(&gitdir);
index 3740c93bc0fe273c975e76e5643507bff35ceb06..a057653981c7e8021a81ce1e5c59a872aaa9d51e 100644 (file)
@@ -13,18 +13,15 @@ struct submodule_cache;
 struct promisor_remote_config;
 
 enum untracked_cache_setting {
-       UNTRACKED_CACHE_UNSET = -1,
-       UNTRACKED_CACHE_REMOVE = 0,
-       UNTRACKED_CACHE_KEEP = 1,
-       UNTRACKED_CACHE_WRITE = 2
+       UNTRACKED_CACHE_KEEP,
+       UNTRACKED_CACHE_REMOVE,
+       UNTRACKED_CACHE_WRITE,
 };
 
 enum fetch_negotiation_setting {
-       FETCH_NEGOTIATION_UNSET = -1,
-       FETCH_NEGOTIATION_NONE = 0,
-       FETCH_NEGOTIATION_DEFAULT = 1,
-       FETCH_NEGOTIATION_SKIPPING = 2,
-       FETCH_NEGOTIATION_NOOP = 3,
+       FETCH_NEGOTIATION_DEFAULT,
+       FETCH_NEGOTIATION_SKIPPING,
+       FETCH_NEGOTIATION_NOOP,
 };
 
 struct repo_settings {
@@ -34,6 +31,8 @@ struct repo_settings {
        int commit_graph_read_changed_paths;
        int gc_write_commit_graph;
        int fetch_write_commit_graph;
+       int command_requires_full_index;
+       int sparse_index;
 
        int index_version;
        enum untracked_cache_setting core_untracked_cache;
@@ -42,9 +41,6 @@ struct repo_settings {
        enum fetch_negotiation_setting fetch_negotiation_algorithm;
 
        int core_multi_pack_index;
-
-       unsigned command_requires_full_index:1,
-                sparse_index:1;
 };
 
 struct repository {
@@ -172,15 +168,18 @@ void initialize_the_repository(void);
 int repo_init(struct repository *r, const char *gitdir, const char *worktree);
 
 /*
- * Initialize the repository 'subrepo' as the submodule given by the
- * struct submodule 'sub' in parent repository 'superproject'.
- * Return 0 upon success and a non-zero value upon failure, which may happen
- * if the submodule is not found, or 'sub' is NULL.
+ * Initialize the repository 'subrepo' as the submodule at the given path. If
+ * the submodule's gitdir cannot be found at <path>/.git, this function calls
+ * submodule_from_path() to try to find it. treeish_name is only used if
+ * submodule_from_path() needs to be called; see its documentation for more
+ * information.
+ * Return 0 upon success and a non-zero value upon failure.
  */
-struct submodule;
+struct object_id;
 int repo_submodule_init(struct repository *subrepo,
                        struct repository *superproject,
-                       const struct submodule *sub);
+                       const char *path,
+                       const struct object_id *treeish_name);
 void repo_clear(struct repository *repo);
 
 /*
diff --git a/reset.c b/reset.c
index 79310ae071b7e897e71d6778926b6e7d21869647..f214df3d96ca218a25c780b2dc79ca4ca76999de 100644 (file)
--- a/reset.c
+++ b/reset.c
@@ -56,9 +56,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
        unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
        unpack_tree_opts.update = 1;
        unpack_tree_opts.merge = 1;
+       unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
        init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
        if (!detach_head)
-               unpack_tree_opts.reset = 1;
+               unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
 
        if (repo_read_index_unmerged(r) < 0) {
                ret = error(_("could not read index"));
index cddd0542a657e10ef768d76a922e063286b9672e..ab7c1358042fa18a996c66505eb6694b950c18fa 100644 (file)
@@ -249,7 +249,7 @@ struct commit_stack {
        struct commit **items;
        size_t nr, alloc;
 };
-#define COMMIT_STACK_INIT { NULL, 0, 0 }
+#define COMMIT_STACK_INIT { 0 }
 
 static void commit_stack_push(struct commit_stack *stack, struct commit *commit)
 {
@@ -360,20 +360,18 @@ static struct object *get_reference(struct rev_info *revs, const char *name,
                                    unsigned int flags)
 {
        struct object *object;
+       struct commit *commit;
 
        /*
-        * If the repository has commit graphs, repo_parse_commit() avoids
-        * reading the object buffer, so use it whenever possible.
+        * If the repository has commit graphs, we try to opportunistically
+        * look up the object ID in those graphs. Like this, we can avoid
+        * parsing commit data from disk.
         */
-       if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) {
-               struct commit *c = lookup_commit(revs->repo, oid);
-               if (!repo_parse_commit(revs->repo, c))
-                       object = (struct object *) c;
-               else
-                       object = NULL;
-       } else {
+       commit = lookup_commit_in_graph(revs->repo, oid);
+       if (commit)
+               object = &commit->object;
+       else
                object = parse_object(revs->repo, oid);
-       }
 
        if (!object) {
                if (revs->ignore_missing)
@@ -1534,7 +1532,7 @@ static int handle_one_ref(const char *path, const struct object_id *oid,
 
        object = get_reference(cb->all_revs, path, oid, cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
-       add_pending_oid(cb->all_revs, path, oid, cb->all_flags);
+       add_pending_object(cb->all_revs, object, path);
        return 0;
 }
 
@@ -2256,6 +2254,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--author-date-order")) {
                revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
                revs->topo_order = 1;
+       } else if (!strcmp(arg, "--unsorted-input")) {
+               if (revs->no_walk)
+                       die(_("--unsorted-input is incompatible with --no-walk"));
+               revs->unsorted_input = 1;
        } else if (!strcmp(arg, "--early-output")) {
                revs->early_output = 100;
                revs->topo_order = 1;
@@ -2546,7 +2548,7 @@ static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn,
        struct strbuf bisect_refs = STRBUF_INIT;
        int status;
        strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
-       status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data, 0);
+       status = refs_for_each_fullref_in(refs, bisect_refs.buf, fn, cb_data);
        strbuf_release(&bisect_refs);
        return status;
 }
@@ -2561,8 +2563,7 @@ static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void
        return for_each_bisect_ref(refs, fn, cb_data, term_good);
 }
 
-static int handle_revision_pseudo_opt(const char *submodule,
-                                     struct rev_info *revs,
+static int handle_revision_pseudo_opt(struct rev_info *revs,
                                      const char **argv, int *flags)
 {
        const char *arg = argv[0];
@@ -2570,7 +2571,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
        struct ref_store *refs;
        int argcount;
 
-       if (submodule) {
+       if (revs->repo != the_repository) {
                /*
                 * We need some something like get_submodule_worktrees()
                 * before we can go through all worktrees of a submodule,
@@ -2579,9 +2580,8 @@ static int handle_revision_pseudo_opt(const char *submodule,
                 */
                if (!revs->single_worktree)
                        BUG("--single-worktree cannot be used together with submodule");
-               refs = get_submodule_ref_store(submodule);
-       } else
-               refs = get_main_ref_store(revs->repo);
+       }
+       refs = get_main_ref_store(revs->repo);
 
        /*
         * NOTE!
@@ -2651,16 +2651,22 @@ static int handle_revision_pseudo_opt(const char *submodule,
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
-               revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+               if (!revs->no_walk && revs->unsorted_input)
+                       die(_("--no-walk is incompatible with --unsorted-input"));
+               revs->no_walk = 1;
        } else if (skip_prefix(arg, "--no-walk=", &optarg)) {
+               if (!revs->no_walk && revs->unsorted_input)
+                       die(_("--no-walk is incompatible with --unsorted-input"));
+
                /*
                 * Detached form ("--no-walk X" as opposed to "--no-walk=X")
                 * not allowed, since the argument is optional.
                 */
+               revs->no_walk = 1;
                if (!strcmp(optarg, "sorted"))
-                       revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+                       revs->unsorted_input = 0;
                else if (!strcmp(optarg, "unsorted"))
-                       revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+                       revs->unsorted_input = 1;
                else
                        return error("invalid argument to --no-walk");
        } else if (!strcmp(arg, "--do-walk")) {
@@ -2699,12 +2705,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 {
        int i, flags, left, seen_dashdash, revarg_opt;
        struct strvec prune_data = STRVEC_INIT;
-       const char *submodule = NULL;
        int seen_end_of_options = 0;
 
-       if (opt)
-               submodule = opt->submodule;
-
        /* First, search for "--" */
        if (opt && opt->assume_dashdash) {
                seen_dashdash = 1;
@@ -2733,7 +2735,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                if (!seen_end_of_options && *arg == '-') {
                        int opts;
 
-                       opts = handle_revision_pseudo_opt(submodule,
+                       opts = handle_revision_pseudo_opt(
                                                revs, argv + i,
                                                &flags);
                        if (opts > 0) {
@@ -3584,7 +3586,7 @@ int prepare_revision_walk(struct rev_info *revs)
 
        if (!revs->reflog_info)
                prepare_to_use_bloom_filter(revs);
-       if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
+       if (!revs->unsorted_input)
                commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
                return 0;
index fbb068da9fb4d5ba8a89dde1d8e99c7b19f6aebe..5578bb4720ae37c2824a220a487dce0451bc7a85 100644 (file)
@@ -79,10 +79,6 @@ struct rev_cmdline_info {
        } *rev;
 };
 
-#define REVISION_WALK_WALK 0
-#define REVISION_WALK_NO_WALK_SORTED 1
-#define REVISION_WALK_NO_WALK_UNSORTED 2
-
 struct oidset;
 struct topo_walk_info;
 
@@ -129,7 +125,8 @@ struct rev_info {
        /* Traversal flags */
        unsigned int    dense:1,
                        prune:1,
-                       no_walk:2,
+                       no_walk:1,
+                       unsorted_input:1,
                        remove_empty_trees:1,
                        simplify_history:1,
                        show_pulls:1,
@@ -339,7 +336,6 @@ extern volatile show_early_output_fn_t show_early_output;
 struct setup_revision_opt {
        const char *def;
        void (*tweak)(struct rev_info *, struct setup_revision_opt *);
-       const char *submodule;  /* TODO: drop this and use rev_info->repo */
        unsigned int    assume_dashdash:1,
                        allow_exclude_promisor_objects:1;
        unsigned revarg_opt;
index 2961f7e55efdc1dc7b675fa8ff2da3e5b35d5442..7ef5cc712a924a48f8ab94defa8d48125de0be0c 100644 (file)
@@ -8,6 +8,8 @@
 #include "string-list.h"
 #include "quote.h"
 #include "config.h"
+#include "packfile.h"
+#include "hook.h"
 
 void child_process_init(struct child_process *child)
 {
@@ -210,9 +212,9 @@ static char *locate_in_PATH(const char *file)
        return NULL;
 }
 
-static int exists_in_PATH(const char *file)
+int exists_in_PATH(const char *command)
 {
-       char *r = locate_in_PATH(file);
+       char *r = locate_in_PATH(command);
        int found = r != NULL;
        free(r);
        return found;
@@ -740,6 +742,9 @@ fail_pipe:
 
        fflush(NULL);
 
+       if (cmd->close_object_store)
+               close_object_store(the_repository->objects);
+
 #ifndef GIT_WINDOWS_NATIVE
 {
        int notify_pipe[2];
@@ -1042,6 +1047,7 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
        cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0;
        cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
        cmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;
+       cmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;
        cmd.dir = dir;
        cmd.env = env;
        cmd.trace2_child_class = tr2_class;
@@ -1317,40 +1323,6 @@ int async_with_fork(void)
 #endif
 }
 
-const char *find_hook(const char *name)
-{
-       static struct strbuf path = STRBUF_INIT;
-
-       strbuf_reset(&path);
-       strbuf_git_path(&path, "hooks/%s", name);
-       if (access(path.buf, X_OK) < 0) {
-               int err = errno;
-
-#ifdef STRIP_EXTENSION
-               strbuf_addstr(&path, STRIP_EXTENSION);
-               if (access(path.buf, X_OK) >= 0)
-                       return path.buf;
-               if (errno == EACCES)
-                       err = errno;
-#endif
-
-               if (err == EACCES && advice_ignored_hook) {
-                       static struct string_list advise_given = STRING_LIST_INIT_DUP;
-
-                       if (!string_list_lookup(&advise_given, name)) {
-                               string_list_insert(&advise_given, name);
-                               advise(_("The '%s' hook was ignored because "
-                                        "it's not set as executable.\n"
-                                        "You can disable this warning with "
-                                        "`git config advice.ignoredHook false`."),
-                                      path.buf);
-                       }
-               }
-               return NULL;
-       }
-       return path.buf;
-}
-
 int run_hook_ve(const char *const *env, const char *name, va_list args)
 {
        struct child_process hook = CHILD_PROCESS_INIT;
@@ -1884,6 +1856,7 @@ int run_auto_maintenance(int quiet)
                return 0;
 
        maint.git_cmd = 1;
+       maint.close_object_store = 1;
        strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
        strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet");
 
@@ -1901,3 +1874,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
        }
        strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
 }
+
+enum start_bg_result start_bg_command(struct child_process *cmd,
+                                     start_bg_wait_cb *wait_cb,
+                                     void *cb_data,
+                                     unsigned int timeout_sec)
+{
+       enum start_bg_result sbgr = SBGR_ERROR;
+       int ret;
+       int wait_status;
+       pid_t pid_seen;
+       time_t time_limit;
+
+       /*
+        * We do not allow clean-on-exit because the child process
+        * should persist in the background and possibly/probably
+        * after this process exits.  So we don't want to kill the
+        * child during our atexit routine.
+        */
+       if (cmd->clean_on_exit)
+               BUG("start_bg_command() does not allow non-zero clean_on_exit");
+
+       if (!cmd->trace2_child_class)
+               cmd->trace2_child_class = "background";
+
+       ret = start_command(cmd);
+       if (ret) {
+               /*
+                * We assume that if `start_command()` fails, we
+                * either get a complete `trace2_child_start() /
+                * trace2_child_exit()` pair or it fails before the
+                * `trace2_child_start()` is emitted, so we do not
+                * need to worry about it here.
+                *
+                * We also assume that `start_command()` does not add
+                * us to the cleanup list.  And that it calls
+                * calls `child_process_clear()`.
+                */
+               sbgr = SBGR_ERROR;
+               goto done;
+       }
+
+       time(&time_limit);
+       time_limit += timeout_sec;
+
+wait:
+       pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG);
+
+       if (!pid_seen) {
+               /*
+                * The child is currently running.  Ask the callback
+                * if the child is ready to do work or whether we
+                * should keep waiting for it to boot up.
+                */
+               ret = (*wait_cb)(cmd, cb_data);
+               if (!ret) {
+                       /*
+                        * The child is running and "ready".
+                        */
+                       trace2_child_ready(cmd, "ready");
+                       sbgr = SBGR_READY;
+                       goto done;
+               } else if (ret > 0) {
+                       /*
+                        * The callback said to give it more time to boot up
+                        * (subject to our timeout limit).
+                        */
+                       time_t now;
+
+                       time(&now);
+                       if (now < time_limit)
+                               goto wait;
+
+                       /*
+                        * Our timeout has expired.  We don't try to
+                        * kill the child, but rather let it continue
+                        * (hopefully) trying to startup.
+                        */
+                       trace2_child_ready(cmd, "timeout");
+                       sbgr = SBGR_TIMEOUT;
+                       goto done;
+               } else {
+                       /*
+                        * The cb gave up on this child.  It is still running,
+                        * but our cb got an error trying to probe it.
+                        */
+                       trace2_child_ready(cmd, "error");
+                       sbgr = SBGR_CB_ERROR;
+                       goto done;
+               }
+       }
+
+       else if (pid_seen == cmd->pid) {
+               int child_code = -1;
+
+               /*
+                * The child started, but exited or was terminated
+                * before becoming "ready".
+                *
+                * We try to match the behavior of `wait_or_whine()`
+                * WRT the handling of WIFSIGNALED() and WIFEXITED()
+                * and convert the child's status to a return code for
+                * tracing purposes and emit the `trace2_child_exit()`
+                * event.
+                *
+                * We do not want the wait_or_whine() error message
+                * because we will be called by client-side library
+                * routines.
+                */
+               if (WIFEXITED(wait_status))
+                       child_code = WEXITSTATUS(wait_status);
+               else if (WIFSIGNALED(wait_status))
+                       child_code = WTERMSIG(wait_status) + 128;
+               trace2_child_exit(cmd, child_code);
+
+               sbgr = SBGR_DIED;
+               goto done;
+       }
+
+       else if (pid_seen < 0 && errno == EINTR)
+               goto wait;
+
+       trace2_child_exit(cmd, -1);
+       sbgr = SBGR_ERROR;
+
+done:
+       child_process_clear(cmd);
+       invalidate_lstat_cache();
+       return sbgr;
+}
index af1296769f986242cffe3dd9ac806e4dfffca507..4987826258490752d06f9e407b478fff23940ae3 100644 (file)
@@ -134,6 +134,14 @@ struct child_process {
         */
        unsigned use_shell:1;
 
+       /**
+        * Release any open file handles to the object store before running
+        * the command; This is necessary e.g. when the spawned process may
+        * want to repack because that would delete `.pack` files (and on
+        * Windows, you cannot delete files that are still in use).
+        */
+       unsigned close_object_store:1;
+
        unsigned stdout_to_stderr:1;
        unsigned clean_on_exit:1;
        unsigned wait_after_clean:1;
@@ -182,6 +190,18 @@ void child_process_clear(struct child_process *);
 
 int is_executable(const char *name);
 
+/**
+ * Check if the command exists on $PATH. This emulates the path search that
+ * execvp would perform, without actually executing the command so it
+ * can be used before fork() to prepare to run a command using
+ * execve() or after execvp() to diagnose why it failed.
+ *
+ * The caller should ensure that command contains no directory separators.
+ *
+ * Returns 1 if it is found in $PATH or 0 if the command could not be found.
+ */
+int exists_in_PATH(const char *command);
+
 /**
  * Start a sub-process. Takes a pointer to a `struct child_process`
  * that specifies the details and returns pipe FDs (if requested).
@@ -204,13 +224,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/*
- * Returns the path to the hook file, or NULL if the hook is missing
- * or disabled. Note that this points to static storage that will be
- * overwritten by further calls to find_hook and run_hook_*.
- */
-const char *find_hook(const char *name);
-
 /**
  * Run a hook.
  * The first argument is a pathname to an index file, or NULL
@@ -233,13 +246,14 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);
  */
 int run_auto_maintenance(int quiet);
 
-#define RUN_COMMAND_NO_STDIN 1
-#define RUN_GIT_CMD         2  /*If this is to be git sub-command */
-#define RUN_COMMAND_STDOUT_TO_STDERR 4
-#define RUN_SILENT_EXEC_FAILURE 8
-#define RUN_USING_SHELL 16
-#define RUN_CLEAN_ON_EXIT 32
-#define RUN_WAIT_AFTER_CLEAN 64
+#define RUN_COMMAND_NO_STDIN           (1<<0)
+#define RUN_GIT_CMD                    (1<<1)
+#define RUN_COMMAND_STDOUT_TO_STDERR   (1<<2)
+#define RUN_SILENT_EXEC_FAILURE                (1<<3)
+#define RUN_USING_SHELL                        (1<<4)
+#define RUN_CLEAN_ON_EXIT              (1<<5)
+#define RUN_WAIT_AFTER_CLEAN           (1<<6)
+#define RUN_CLOSE_OBJECT_STORE         (1<<7)
 
 /**
  * Convenience functions that encapsulate a sequence of
@@ -496,4 +510,61 @@ int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
  */
 void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir);
 
+/**
+ * Possible return values for start_bg_command().
+ */
+enum start_bg_result {
+       /* child process is "ready" */
+       SBGR_READY = 0,
+
+       /* child process could not be started */
+       SBGR_ERROR,
+
+       /* callback error when testing for "ready" */
+       SBGR_CB_ERROR,
+
+       /* timeout expired waiting for child to become "ready" */
+       SBGR_TIMEOUT,
+
+       /* child process exited or was signalled before becomming "ready" */
+       SBGR_DIED,
+};
+
+/**
+ * Callback used by start_bg_command() to ask whether the
+ * child process is ready or needs more time to become "ready".
+ *
+ * The callback will receive the cmd and cb_data arguments given to
+ * start_bg_command().
+ *
+ * Returns 1 is child needs more time (subject to the requested timeout).
+ * Returns 0 if child is "ready".
+ * Returns -1 on any error and cause start_bg_command() to also error out.
+ */
+typedef int(start_bg_wait_cb)(const struct child_process *cmd, void *cb_data);
+
+/**
+ * Start a command in the background.  Wait long enough for the child
+ * to become "ready" (as defined by the provided callback).  Capture
+ * immediate errors (like failure to start) and any immediate exit
+ * status (such as a shutdown/signal before the child became "ready")
+ * and return this like start_command().
+ *
+ * We run a custom wait loop using the provided callback to wait for
+ * the child to start and become "ready".  This is limited by the given
+ * timeout value.
+ *
+ * If the child does successfully start and become "ready", we orphan
+ * it into the background.
+ *
+ * The caller must not call finish_command().
+ *
+ * The opaque cb_data argument will be forwarded to the callback for
+ * any instance data that it might require.  This may be NULL.
+ */
+enum start_bg_result start_bg_command(struct child_process *cmd,
+                                     start_bg_wait_cb *wait_cb,
+                                     void *cb_data,
+                                     unsigned int timeout_sec);
+
 #endif
index b3a495b7b1998f992b69a4b6a37613a8a7a61dba..bc0fcdbb000769935e9f080164d8e02e81dbb8b3 100644 (file)
@@ -341,13 +341,13 @@ static int generate_push_cert(struct strbuf *req_buf,
 {
        const struct ref *ref;
        struct string_list_item *item;
-       char *signing_key = xstrdup(get_signing_key());
+       char *signing_key_id = xstrdup(get_signing_key_id());
        const char *cp, *np;
        struct strbuf cert = STRBUF_INIT;
        int update_seen = 0;
 
        strbuf_addstr(&cert, "certificate version 0.1\n");
-       strbuf_addf(&cert, "pusher %s ", signing_key);
+       strbuf_addf(&cert, "pusher %s ", signing_key_id);
        datestamp(&cert);
        strbuf_addch(&cert, '\n');
        if (args->url && *args->url) {
@@ -374,7 +374,7 @@ static int generate_push_cert(struct strbuf *req_buf,
        if (!update_seen)
                goto free_return;
 
-       if (sign_buffer(&cert, &cert, signing_key))
+       if (sign_buffer(&cert, &cert, get_signing_key()))
                die(_("failed to sign the push certificate"));
 
        packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
@@ -386,7 +386,7 @@ static int generate_push_cert(struct strbuf *req_buf,
        packet_buf_write(req_buf, "push-cert-end\n");
 
 free_return:
-       free(signing_key);
+       free(signing_key_id);
        strbuf_release(&cert);
        return update_seen;
 }
index e4627d91967e3b27c60a71ff1da61b5b3025c025..cd2aabf1f765508b13e748dcffda8073da6b9d55 100644 (file)
@@ -8,6 +8,7 @@
 #include "sequencer.h"
 #include "tag.h"
 #include "run-command.h"
+#include "hook.h"
 #include "exec-cmd.h"
 #include "utf8.h"
 #include "cache-tree.h"
@@ -403,7 +404,7 @@ static void print_advice(struct repository *r, int show_hint,
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
 
        if (msg) {
-               fprintf(stderr, "%s\n", msg);
+               advise("%s\n", msg);
                /*
                 * A conflict has occurred but the porcelain
                 * (typically rebase --interactive) wants to take care
@@ -418,10 +419,22 @@ static void print_advice(struct repository *r, int show_hint,
                if (opts->no_commit)
                        advise(_("after resolving the conflicts, mark the corrected paths\n"
                                 "with 'git add <paths>' or 'git rm <paths>'"));
+               else if (opts->action == REPLAY_PICK)
+                       advise(_("After resolving the conflicts, mark them with\n"
+                                "\"git add/rm <pathspec>\", then run\n"
+                                "\"git cherry-pick --continue\".\n"
+                                "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+                                "To abort and get back to the state before \"git cherry-pick\",\n"
+                                "run \"git cherry-pick --abort\"."));
+               else if (opts->action == REPLAY_REVERT)
+                       advise(_("After resolving the conflicts, mark them with\n"
+                                "\"git add/rm <pathspec>\", then run\n"
+                                "\"git revert --continue\".\n"
+                                "You can instead skip this commit with \"git revert --skip\".\n"
+                                "To abort and get back to the state before \"git revert\",\n"
+                                "run \"git revert --abort\"."));
                else
-                       advise(_("after resolving the conflicts, mark the corrected paths\n"
-                                "with 'git add <paths>' or 'git rm <paths>'\n"
-                                "and commit the result with 'git commit'"));
+                       BUG("unexpected pick action in print_advice()");
        }
 }
 
@@ -486,7 +499,7 @@ static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
        error(_("your local changes would be overwritten by %s."),
                _(action_name(opts)));
 
-       if (advice_commit_before_merge)
+       if (advice_enabled(ADVICE_COMMIT_BEFORE_MERGE))
                advise(_("commit your changes or stash them to proceed."));
        return -1;
 }
@@ -636,7 +649,7 @@ static int do_recursive_merge(struct repository *r,
        for (i = 0; i < opts->xopts_nr; i++)
                parse_merge_opt(&o, opts->xopts[i]);
 
-       if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+       if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
                memset(&result, 0, sizeof(result));
                merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,
                                            &result);
@@ -652,6 +665,7 @@ static int do_recursive_merge(struct repository *r,
                merge_switch_to_result(&o, head_tree, &result, 1, show_output);
                clean = result.clean;
        } else {
+               ensure_full_index(r->index);
                clean = merge_trees(&o, head_tree, next_tree, base_tree);
                if (is_rebase_i(opts) && clean <= 0)
                        fputs(o.obuf.buf, stdout);
@@ -1242,7 +1256,7 @@ N_("Your name and email address were configured automatically based\n"
 
 static const char *implicit_ident_advice(void)
 {
-       char *user_config = expand_user_path("~/.gitconfig", 0);
+       char *user_config = interpolate_path("~/.gitconfig", 0);
        char *xdg_config = xdg_config_home("config");
        int config_exists = file_exists(user_config) || file_exists(xdg_config);
 
@@ -1294,7 +1308,7 @@ void print_commit_summary(struct repository *r,
        if (!committer_ident_sufficiently_given()) {
                strbuf_addstr(&format, "\n Committer: ");
                strbuf_addbuf_percentquote(&format, &committer_ident);
-               if (advice_implicit_identity) {
+               if (advice_enabled(ADVICE_IMPLICIT_IDENTITY)) {
                        strbuf_addch(&format, '\n');
                        strbuf_addstr(&format, implicit_ident_advice());
                }
@@ -1446,7 +1460,7 @@ static int try_to_commit(struct repository *r,
                }
        }
 
-       if (find_hook("prepare-commit-msg")) {
+       if (hook_exists("prepare-commit-msg")) {
                res = run_prepare_commit_msg_hook(r, msg, hook_commit);
                if (res)
                        goto out;
@@ -2347,6 +2361,7 @@ static int read_and_refresh_cache(struct repository *r,
                        _(action_name(opts)));
        }
        refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+
        if (index_fd >= 0) {
                if (write_locked_index(r->index, &index_lock,
                                       COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
@@ -2354,6 +2369,13 @@ static int read_and_refresh_cache(struct repository *r,
                                _(action_name(opts)));
                }
        }
+
+       /*
+        * If we are resolving merges in any way other than "ort", then
+        * expand the sparse index.
+        */
+       if (opts->strategy && strcmp(opts->strategy, "ort"))
+               ensure_full_index(r->index);
        return 0;
 }
 
@@ -2672,7 +2694,6 @@ static int read_populate_todo(struct repository *r,
                              struct todo_list *todo_list,
                              struct replay_opts *opts)
 {
-       struct stat st;
        const char *todo_file = get_todo_path(opts);
        int res;
 
@@ -2680,11 +2701,6 @@ static int read_populate_todo(struct repository *r,
        if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
                return -1;
 
-       res = stat(todo_file, &st);
-       if (res)
-               return error(_("could not stat '%s'"), todo_file);
-       fill_stat_data(&todo_list->stat, &st);
-
        res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
        if (res) {
                if (is_rebase_i(opts))
@@ -3042,7 +3058,7 @@ static int create_seq_dir(struct repository *r)
        }
        if (in_progress_error) {
                error("%s", in_progress_error);
-               if (advice_sequencer_in_use)
+               if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
                        advise(in_progress_advice,
                                advise_skip ? "--skip | " : "");
                return -1;
@@ -3246,7 +3262,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
 give_advice:
        error(_("there is nothing to skip"));
 
-       if (advice_resolve_conflict) {
+       if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) {
                advise(_("have you committed already?\n"
                         "try \"git %s --continue\""),
                         action == REPLAY_REVERT ? "revert" : "cherry-pick");
@@ -3629,9 +3645,9 @@ static int do_reset(struct repository *r,
        struct strbuf ref_name = STRBUF_INIT;
        struct object_id oid;
        struct lock_file lock = LOCK_INIT;
-       struct tree_desc desc;
+       struct tree_desc desc = { 0 };
        struct tree *tree;
-       struct unpack_trees_options unpack_tree_opts;
+       struct unpack_trees_options unpack_tree_opts = { 0 };
        int ret = 0;
 
        if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0)
@@ -3663,14 +3679,11 @@ static int do_reset(struct repository *r,
                strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
                if (get_oid(ref_name.buf, &oid) &&
                    get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
-                       error(_("could not read '%s'"), ref_name.buf);
-                       rollback_lock_file(&lock);
-                       strbuf_release(&ref_name);
-                       return -1;
+                       ret = error(_("could not read '%s'"), ref_name.buf);
+                       goto cleanup;
                }
        }
 
-       memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
        setup_unpack_trees_porcelain(&unpack_tree_opts, "reset");
        unpack_tree_opts.head_idx = 1;
        unpack_tree_opts.src_index = r->index;
@@ -3678,27 +3691,22 @@ static int do_reset(struct repository *r,
        unpack_tree_opts.fn = oneway_merge;
        unpack_tree_opts.merge = 1;
        unpack_tree_opts.update = 1;
+       unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
        init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
        if (repo_read_index_unmerged(r)) {
-               rollback_lock_file(&lock);
-               strbuf_release(&ref_name);
-               return error_resolve_conflict(_(action_name(opts)));
+               ret = error_resolve_conflict(_(action_name(opts)));
+               goto cleanup;
        }
 
        if (!fill_tree_descriptor(r, &desc, &oid)) {
-               error(_("failed to find tree of %s"), oid_to_hex(&oid));
-               rollback_lock_file(&lock);
-               free((void *)desc.buffer);
-               strbuf_release(&ref_name);
-               return -1;
+               ret = error(_("failed to find tree of %s"), oid_to_hex(&oid));
+               goto cleanup;
        }
 
        if (unpack_trees(1, &desc, &unpack_tree_opts)) {
-               rollback_lock_file(&lock);
-               free((void *)desc.buffer);
-               strbuf_release(&ref_name);
-               return -1;
+               ret = -1;
+               goto cleanup;
        }
 
        tree = parse_tree_indirect(&oid);
@@ -3706,14 +3714,17 @@ static int do_reset(struct repository *r,
 
        if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)
                ret = error(_("could not write index"));
-       free((void *)desc.buffer);
 
        if (!ret)
                ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
                                                len, name), "HEAD", &oid,
                                 NULL, 0, UPDATE_REFS_MSG_ON_ERR);
-
+cleanup:
+       free((void *)desc.buffer);
+       if (ret < 0)
+               rollback_lock_file(&lock);
        strbuf_release(&ref_name);
+       clear_unpack_trees_porcelain(&unpack_tree_opts);
        return ret;
 }
 
@@ -3990,7 +4001,7 @@ static int do_merge(struct repository *r,
        o.branch2 = ref_name.buf;
        o.buffer_output = 2;
 
-       if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+       if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
                /*
                 * TODO: Should use merge_incore_recursive() and
                 * merge_switch_to_result(), skipping the call to
@@ -4263,6 +4274,30 @@ static int stopped_at_head(struct repository *r)
 
 }
 
+static int reread_todo_if_changed(struct repository *r,
+                                 struct todo_list *todo_list,
+                                 struct replay_opts *opts)
+{
+       int offset;
+       struct strbuf buf = STRBUF_INIT;
+
+       if (strbuf_read_file_or_whine(&buf, get_todo_path(opts)) < 0)
+               return -1;
+       offset = get_item_line_offset(todo_list, todo_list->current + 1);
+       if (buf.len != todo_list->buf.len - offset ||
+           memcmp(buf.buf, todo_list->buf.buf + offset, buf.len)) {
+               /* Reread the todo file if it has changed. */
+               todo_list_release(todo_list);
+               if (read_populate_todo(r, todo_list, opts))
+                       return -1; /* message was printed */
+               /* `current` will be incremented on return */
+               todo_list->current = -1;
+       }
+       strbuf_release(&buf);
+
+       return 0;
+}
+
 static const char rescheduled_advice[] =
 N_("Could not execute the todo command\n"
 "\n"
@@ -4441,20 +4476,9 @@ static int pick_commits(struct repository *r,
                                                        item->commit,
                                                        arg, item->arg_len,
                                                        opts, res, 0);
-               } else if (is_rebase_i(opts) && check_todo && !res) {
-                       struct stat st;
-
-                       if (stat(get_todo_path(opts), &st)) {
-                               res = error_errno(_("could not stat '%s'"),
-                                                 get_todo_path(opts));
-                       } else if (match_stat_data(&todo_list->stat, &st)) {
-                               /* Reread the todo file if it has changed. */
-                               todo_list_release(todo_list);
-                               if (read_populate_todo(r, todo_list, opts))
-                                       res = -1; /* message was printed */
-                               /* `current` will be incremented below */
-                               todo_list->current = -1;
-                       }
+               } else if (is_rebase_i(opts) && check_todo && !res &&
+                          reread_todo_if_changed(r, todo_list, opts)) {
+                       return -1;
                }
 
                todo_list->current++;
@@ -5110,6 +5134,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
        int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
+       int skipped_commit = 0;
        struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
        struct strbuf label = STRBUF_INIT;
        struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -5160,8 +5185,13 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                oidset_insert(&interesting, &commit->object.oid);
 
                is_empty = is_original_commit_empty(commit);
-               if (!is_empty && (commit->object.flags & PATCHSAME))
+               if (!is_empty && (commit->object.flags & PATCHSAME)) {
+                       if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+                               warning(_("skipped previously applied commit %s"),
+                                       short_commit_name(commit));
+                       skipped_commit = 1;
                        continue;
+               }
                if (is_empty && !keep_empty)
                        continue;
 
@@ -5225,6 +5255,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                oidcpy(&entry->entry.oid, &commit->object.oid);
                oidmap_put(&commit2todo, entry);
        }
+       if (skipped_commit)
+               advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+                                 _("use --reapply-cherry-picks to include skipped commits"));
 
        /*
         * Second phase:
@@ -5345,6 +5378,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
        int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
        int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
+       int skipped_commit = 0;
 
        repo_init_revisions(r, &revs, NULL);
        revs.verbose_header = 1;
@@ -5380,8 +5414,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        while ((commit = get_revision(&revs))) {
                int is_empty = is_original_commit_empty(commit);
 
-               if (!is_empty && (commit->object.flags & PATCHSAME))
+               if (!is_empty && (commit->object.flags & PATCHSAME)) {
+                       if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+                               warning(_("skipped previously applied commit %s"),
+                                       short_commit_name(commit));
+                       skipped_commit = 1;
                        continue;
+               }
                if (is_empty && !keep_empty)
                        continue;
                strbuf_addf(out, "%s %s ", insn,
@@ -5391,6 +5430,9 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                        strbuf_addf(out, " %c empty", comment_line_char);
                strbuf_addch(out, '\n');
        }
+       if (skipped_commit)
+               advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+                                 _("use --reapply-cherry-picks to include skipped commits"));
        return 0;
 }
 
@@ -5398,8 +5440,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
  * Add commands after pick and (series of) squash/fixup commands
  * in the todo list.
  */
-void todo_list_add_exec_commands(struct todo_list *todo_list,
-                                struct string_list *commands)
+static void todo_list_add_exec_commands(struct todo_list *todo_list,
+                                       struct string_list *commands)
 {
        struct strbuf *buf = &todo_list->buf;
        size_t base_offset = buf->len;
index d57d8ea23d7a225e10788859cab4bebf5cfc1346..05a7d2ba6b392c37b106bea6af5f0e1912dffd9e 100644 (file)
@@ -116,10 +116,11 @@ struct todo_list {
        struct todo_item *items;
        int nr, alloc, current;
        int done_nr, total_nr;
-       struct stat_data stat;
 };
 
-#define TODO_LIST_INIT { STRBUF_INIT }
+#define TODO_LIST_INIT { \
+       .buf = STRBUF_INIT, \
+}
 
 int todo_list_parse_insn_buffer(struct repository *r, char *buf,
                                struct todo_list *todo_list);
@@ -156,12 +157,11 @@ int sequencer_remove_state(struct replay_opts *opts);
  */
 #define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
 #define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
+#define TODO_LIST_WARN_SKIPPED_CHERRY_PICKS (1U << 8)
 
 int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
                          const char **argv, unsigned flags);
 
-void todo_list_add_exec_commands(struct todo_list *todo_list,
-                                struct string_list *commands);
 int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
                    const char *shortrevisions, const char *onto_name,
                    struct commit *onto, const struct object_id *orig_head,
diff --git a/serve.c b/serve.c
index f11c0e07c45fc2653c4204bafbf04473358444fb..b3fe9b5126a3347784d501e296102ab87cc4c5ab 100644 (file)
--- a/serve.c
+++ b/serve.c
@@ -9,7 +9,8 @@
 #include "serve.h"
 #include "upload-pack.h"
 
-static int advertise_sid;
+static int advertise_sid = -1;
+static int client_hash_algo = GIT_HASH_SHA1;
 
 static int always_advertise(struct repository *r,
                            struct strbuf *value)
@@ -33,8 +34,22 @@ static int object_format_advertise(struct repository *r,
        return 1;
 }
 
+static void object_format_receive(struct repository *r,
+                                 const char *algo_name)
+{
+       if (!algo_name)
+               die("object-format capability requires an argument");
+
+       client_hash_algo = hash_algo_by_name(algo_name);
+       if (client_hash_algo == GIT_HASH_UNKNOWN)
+               die("unknown object format '%s'", algo_name);
+}
+
 static int session_id_advertise(struct repository *r, struct strbuf *value)
 {
+       if (advertise_sid == -1 &&
+           git_config_get_bool("transfer.advertisesid", &advertise_sid))
+               advertise_sid = 0;
        if (!advertise_sid)
                return 0;
        if (value)
@@ -42,6 +57,14 @@ static int session_id_advertise(struct repository *r, struct strbuf *value)
        return 1;
 }
 
+static void session_id_receive(struct repository *r,
+                              const char *client_sid)
+{
+       if (!client_sid)
+               client_sid = "";
+       trace2_data_string("transfer", NULL, "client-sid", client_sid);
+}
+
 struct protocol_capability {
        /*
         * The name of the capability.  The server uses this name when
@@ -60,34 +83,70 @@ struct protocol_capability {
 
        /*
         * Function called when a client requests the capability as a command.
-        * The function will be provided the capabilities requested via 'keys'
-        * as well as a struct packet_reader 'request' which the command should
+        * Will be provided a struct packet_reader 'request' which it should
         * use to read the command specific part of the request.  Every command
         * MUST read until a flush packet is seen before sending a response.
         *
         * This field should be NULL for capabilities which are not commands.
         */
-       int (*command)(struct repository *r,
-                      struct strvec *keys,
-                      struct packet_reader *request);
+       int (*command)(struct repository *r, struct packet_reader *request);
+
+       /*
+        * Function called when a client requests the capability as a
+        * non-command. This may be NULL if the capability does nothing.
+        *
+        * For a capability of the form "foo=bar", the value string points to
+        * the content after the "=" (i.e., "bar"). For simple capabilities
+        * (just "foo"), it is NULL.
+        */
+       void (*receive)(struct repository *r, const char *value);
 };
 
 static struct protocol_capability capabilities[] = {
-       { "agent", agent_advertise, NULL },
-       { "ls-refs", ls_refs_advertise, ls_refs },
-       { "fetch", upload_pack_advertise, upload_pack_v2 },
-       { "server-option", always_advertise, NULL },
-       { "object-format", object_format_advertise, NULL },
-       { "session-id", session_id_advertise, NULL },
-       { "object-info", always_advertise, cap_object_info },
+       {
+               .name = "agent",
+               .advertise = agent_advertise,
+       },
+       {
+               .name = "ls-refs",
+               .advertise = ls_refs_advertise,
+               .command = ls_refs,
+       },
+       {
+               .name = "fetch",
+               .advertise = upload_pack_advertise,
+               .command = upload_pack_v2,
+       },
+       {
+               .name = "server-option",
+               .advertise = always_advertise,
+       },
+       {
+               .name = "object-format",
+               .advertise = object_format_advertise,
+               .receive = object_format_receive,
+       },
+       {
+               .name = "session-id",
+               .advertise = session_id_advertise,
+               .receive = session_id_receive,
+       },
+       {
+               .name = "object-info",
+               .advertise = always_advertise,
+               .command = cap_object_info,
+       },
 };
 
-static void advertise_capabilities(void)
+void protocol_v2_advertise_capabilities(void)
 {
        struct strbuf capability = STRBUF_INIT;
        struct strbuf value = STRBUF_INIT;
        int i;
 
+       /* serve by default supports v2 */
+       packet_write_fmt(1, "version 2\n");
+
        for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
                struct protocol_capability *c = &capabilities[i];
 
@@ -112,7 +171,7 @@ static void advertise_capabilities(void)
        strbuf_release(&value);
 }
 
-static struct protocol_capability *get_capability(const char *key)
+static struct protocol_capability *get_capability(const char *key, const char **value)
 {
        int i;
 
@@ -122,31 +181,46 @@ static struct protocol_capability *get_capability(const char *key)
        for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
                struct protocol_capability *c = &capabilities[i];
                const char *out;
-               if (skip_prefix(key, c->name, &out) && (!*out || *out == '='))
+               if (!skip_prefix(key, c->name, &out))
+                       continue;
+               if (!*out) {
+                       *value = NULL;
                        return c;
+               }
+               if (*out++ == '=') {
+                       *value = out;
+                       return c;
+               }
        }
 
        return NULL;
 }
 
-static int is_valid_capability(const char *key)
+static int receive_client_capability(const char *key)
 {
-       const struct protocol_capability *c = get_capability(key);
+       const char *value;
+       const struct protocol_capability *c = get_capability(key, &value);
 
-       return c && c->advertise(the_repository, NULL);
+       if (!c || c->command || !c->advertise(the_repository, NULL))
+               return 0;
+
+       if (c->receive)
+               c->receive(the_repository, value);
+       return 1;
 }
 
-static int is_command(const char *key, struct protocol_capability **command)
+static int parse_command(const char *key, struct protocol_capability **command)
 {
        const char *out;
 
        if (skip_prefix(key, "command=", &out)) {
-               struct protocol_capability *cmd = get_capability(out);
+               const char *value;
+               struct protocol_capability *cmd = get_capability(out, &value);
 
                if (*command)
                        die("command '%s' requested after already requesting command '%s'",
                            out, (*command)->name);
-               if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command)
+               if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command || value)
                        die("invalid command '%s'", out);
 
                *command = cmd;
@@ -156,42 +230,6 @@ static int is_command(const char *key, struct protocol_capability **command)
        return 0;
 }
 
-int has_capability(const struct strvec *keys, const char *capability,
-                  const char **value)
-{
-       int i;
-       for (i = 0; i < keys->nr; i++) {
-               const char *out;
-               if (skip_prefix(keys->v[i], capability, &out) &&
-                   (!*out || *out == '=')) {
-                       if (value) {
-                               if (*out == '=')
-                                       out++;
-                               *value = out;
-                       }
-                       return 1;
-               }
-       }
-
-       return 0;
-}
-
-static void check_algorithm(struct repository *r, struct strvec *keys)
-{
-       int client = GIT_HASH_SHA1, server = hash_algo_by_ptr(r->hash_algo);
-       const char *algo_name;
-
-       if (has_capability(keys, "object-format", &algo_name)) {
-               client = hash_algo_by_name(algo_name);
-               if (client == GIT_HASH_UNKNOWN)
-                       die("unknown object format '%s'", algo_name);
-       }
-
-       if (client != server)
-               die("mismatched object format: server %s; client %s\n",
-                   r->hash_algo->name, hash_algos[client].name);
-}
-
 enum request_state {
        PROCESS_REQUEST_KEYS,
        PROCESS_REQUEST_DONE,
@@ -201,9 +239,8 @@ static int process_request(void)
 {
        enum request_state state = PROCESS_REQUEST_KEYS;
        struct packet_reader reader;
-       struct strvec keys = STRVEC_INIT;
+       int seen_capability_or_command = 0;
        struct protocol_capability *command = NULL;
-       const char *client_sid;
 
        packet_reader_init(&reader, 0, NULL, 0,
                           PACKET_READ_CHOMP_NEWLINE |
@@ -223,10 +260,9 @@ static int process_request(void)
                case PACKET_READ_EOF:
                        BUG("Should have already died when seeing EOF");
                case PACKET_READ_NORMAL:
-                       /* collect request; a sequence of keys and values */
-                       if (is_command(reader.line, &command) ||
-                           is_valid_capability(reader.line))
-                               strvec_push(&keys, reader.line);
+                       if (parse_command(reader.line, &command) ||
+                           receive_client_capability(reader.line))
+                               seen_capability_or_command = 1;
                        else
                                die("unknown capability '%s'", reader.line);
 
@@ -238,7 +274,7 @@ static int process_request(void)
                         * If no command and no keys were given then the client
                         * wanted to terminate the connection.
                         */
-                       if (!keys.nr)
+                       if (!seen_capability_or_command)
                                return 1;
 
                        /*
@@ -265,40 +301,26 @@ static int process_request(void)
        if (!command)
                die("no command requested");
 
-       check_algorithm(the_repository, &keys);
-
-       if (has_capability(&keys, "session-id", &client_sid))
-               trace2_data_string("transfer", NULL, "client-sid", client_sid);
+       if (client_hash_algo != hash_algo_by_ptr(the_repository->hash_algo))
+               die("mismatched object format: server %s; client %s\n",
+                   the_repository->hash_algo->name,
+                   hash_algos[client_hash_algo].name);
 
-       command->command(the_repository, &keys, &reader);
+       command->command(the_repository, &reader);
 
-       strvec_clear(&keys);
        return 0;
 }
 
-/* Main serve loop for protocol version 2 */
-void serve(struct serve_options *options)
+void protocol_v2_serve_loop(int stateless_rpc)
 {
-       git_config_get_bool("transfer.advertisesid", &advertise_sid);
-
-       if (options->advertise_capabilities || !options->stateless_rpc) {
-               /* serve by default supports v2 */
-               packet_write_fmt(1, "version 2\n");
-
-               advertise_capabilities();
-               /*
-                * If only the list of capabilities was requested exit
-                * immediately after advertising capabilities
-                */
-               if (options->advertise_capabilities)
-                       return;
-       }
+       if (!stateless_rpc)
+               protocol_v2_advertise_capabilities();
 
        /*
         * If stateless-rpc was requested then exit after
         * a single request/response exchange
         */
-       if (options->stateless_rpc) {
+       if (stateless_rpc) {
                process_request();
        } else {
                for (;;)
diff --git a/serve.h b/serve.h
index fc2683e24d30577c41d4800239bf19b134316140..f946cf904a242db5106625e280d7daa671348516 100644 (file)
--- a/serve.h
+++ b/serve.h
@@ -1,15 +1,7 @@
 #ifndef SERVE_H
 #define SERVE_H
 
-struct strvec;
-int has_capability(const struct strvec *keys, const char *capability,
-                  const char **value);
-
-struct serve_options {
-       unsigned advertise_capabilities;
-       unsigned stateless_rpc;
-};
-#define SERVE_OPTIONS_INIT { 0 }
-void serve(struct serve_options *options);
+void protocol_v2_advertise_capabilities(void);
+void protocol_v2_serve_loop(int stateless_rpc);
 
 #endif /* SERVE_H */
index 5b4a96dcd69a2e94f4dcb2b3715e18e6373d5cac..aba6ff5829405647070c5b2a475318e2fd76282d 100644 (file)
--- a/shallow.h
+++ b/shallow.h
@@ -23,7 +23,9 @@ int is_repository_shallow(struct repository *r);
 struct shallow_lock {
        struct lock_file lock;
 };
-#define SHALLOW_LOCK_INIT { LOCK_INIT }
+#define SHALLOW_LOCK_INIT { \
+       .lock = LOCK_INIT, \
+}
 
 /* commit $GIT_DIR/shallow and reset stat-validity checks */
 int commit_shallow_file(struct repository *r, struct shallow_lock *lk);
index 2c48a5ee004732e7c33739befcf6efe4db2d4e27..a849d9f8411fdbb9855fbf7a502776244914a05c 100644 (file)
@@ -5,13 +5,6 @@
  * See Documentation/technical/api-simple-ipc.txt
  */
 
-#ifdef SUPPORTS_SIMPLE_IPC
-#include "pkt-line.h"
-
-/*
- * Simple IPC Client Side API.
- */
-
 enum ipc_active_state {
        /*
         * The pipe/socket exists and the daemon is waiting for connections.
@@ -43,6 +36,13 @@ enum ipc_active_state {
        IPC_STATE__OTHER_ERROR,
 };
 
+#ifdef SUPPORTS_SIMPLE_IPC
+#include "pkt-line.h"
+
+/*
+ * Simple IPC Client Side API.
+ */
+
 struct ipc_client_connect_options {
        /*
         * Spin under timeout if the server is running but can't
@@ -65,11 +65,7 @@ struct ipc_client_connect_options {
        unsigned int uds_disallow_chdir:1;
 };
 
-#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \
-       .wait_if_busy = 0, \
-       .wait_if_not_found = 0, \
-       .uds_disallow_chdir = 0, \
-}
+#define IPC_CLIENT_CONNECT_OPTIONS_INIT { 0 }
 
 /*
  * Determine if a server is listening on this named pipe or socket using
@@ -107,7 +103,8 @@ void ipc_client_close_connection(struct ipc_client_connection *connection);
  */
 int ipc_client_send_command_to_connection(
        struct ipc_client_connection *connection,
-       const char *message, struct strbuf *answer);
+       const char *message, size_t message_len,
+       struct strbuf *answer);
 
 /*
  * Used by the client to synchronously connect and send and receive a
@@ -119,7 +116,8 @@ int ipc_client_send_command_to_connection(
  */
 int ipc_client_send_command(const char *path,
                            const struct ipc_client_connect_options *options,
-                           const char *message, struct strbuf *answer);
+                           const char *message, size_t message_len,
+                           struct strbuf *answer);
 
 /*
  * Simple IPC Server Side API.
@@ -144,6 +142,7 @@ typedef int (ipc_server_reply_cb)(struct ipc_server_reply_data *,
  */
 typedef int (ipc_server_application_cb)(void *application_data,
                                        const char *request,
+                                       size_t request_len,
                                        ipc_server_reply_cb *reply_cb,
                                        struct ipc_server_reply_data *reply_data);
 
index 56eb65dc349a7aede46d17dae9c17ce86ce96f05..7b7ff79e0443a89d37409620a0fd6b04ef9cdc10 100644 (file)
@@ -33,19 +33,14 @@ static int convert_to_sparse_rec(struct index_state *istate,
 {
        int i, can_convert = 1;
        int start_converted = num_converted;
-       enum pattern_match_result match;
-       int dtype = DT_UNKNOWN;
        struct strbuf child_path = STRBUF_INIT;
-       struct pattern_list *pl = istate->sparse_checkout_patterns;
 
        /*
         * Is the current path outside of the sparse cone?
         * Then check if the region can be replaced by a sparse
         * directory entry (everything is sparse and merged).
         */
-       match = path_matches_pattern_list(ct_path, ct_pathlen,
-                                         NULL, &dtype, pl, istate);
-       if (match != NOT_MATCHED)
+       if (path_in_sparse_checkout(ct_path, istate))
                can_convert = 0;
 
        for (i = start; can_convert && i < end; i++) {
@@ -127,41 +122,51 @@ static int index_has_unmerged_entries(struct index_state *istate)
        return 0;
 }
 
-int convert_to_sparse(struct index_state *istate)
+int convert_to_sparse(struct index_state *istate, int flags)
 {
        int test_env;
-       if (istate->split_index || istate->sparse_index ||
+       if (istate->sparse_index || !istate->cache_nr ||
            !core_apply_sparse_checkout || !core_sparse_checkout_cone)
                return 0;
 
        if (!istate->repo)
                istate->repo = the_repository;
 
-       /*
-        * The GIT_TEST_SPARSE_INDEX environment variable triggers the
-        * index.sparse config variable to be on.
-        */
-       test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
-       if (test_env >= 0)
-               set_sparse_index_config(istate->repo, test_env);
-
-       /*
-        * Only convert to sparse if index.sparse is set.
-        */
-       prepare_repo_settings(istate->repo);
-       if (!istate->repo->settings.sparse_index)
-               return 0;
+       if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) {
+               /*
+                * The sparse index is not (yet) integrated with a split index.
+                */
+               if (istate->split_index)
+                       return 0;
+               /*
+                * The GIT_TEST_SPARSE_INDEX environment variable triggers the
+                * index.sparse config variable to be on.
+                */
+               test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
+               if (test_env >= 0)
+                       set_sparse_index_config(istate->repo, test_env);
 
-       if (!istate->sparse_checkout_patterns) {
-               istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));
-               if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)
+               /*
+                * Only convert to sparse if index.sparse is set.
+                */
+               prepare_repo_settings(istate->repo);
+               if (!istate->repo->settings.sparse_index)
                        return 0;
        }
 
-       if (!istate->sparse_checkout_patterns->use_cone_patterns) {
-               warning(_("attempting to use sparse-index without cone mode"));
-               return -1;
-       }
+       if (init_sparse_checkout_patterns(istate))
+               return 0;
+
+       /*
+        * We need cone-mode patterns to use sparse-index. If a user edits
+        * their sparse-checkout file manually, then we can detect during
+        * parsing that they are not actually using cone-mode patterns and
+        * hence we need to abort this conversion _without error_. Warnings
+        * already exist in the pattern parsing to inform the user of their
+        * bad patterns.
+        */
+       if (!istate->sparse_checkout_patterns->use_cone_patterns)
+               return 0;
 
        /*
         * NEEDSWORK: If we have unmerged entries, then stay full.
@@ -172,10 +177,15 @@ int convert_to_sparse(struct index_state *istate)
 
        /* Clear and recompute the cache-tree */
        cache_tree_free(&istate->cache_tree);
-       if (cache_tree_update(istate, 0)) {
-               warning(_("unable to update cache-tree, staying full"));
-               return -1;
-       }
+       /*
+        * Silently return if there is a problem with the cache tree update,
+        * which might just be due to a conflict state in some entry.
+        *
+        * This might create new tree objects, so be sure to use
+        * WRITE_TREE_MISSING_OK.
+        */
+       if (cache_tree_update(istate, WRITE_TREE_MISSING_OK))
+               return 0;
 
        remove_fsmonitor(istate);
 
index 1115a0d7dd984b142b0ed8c76db9f1c132e13f6d..9f3d7bc7fafce1968572c0f6b962a4d72f7a064c 100644 (file)
@@ -2,7 +2,8 @@
 #define SPARSE_INDEX_H__
 
 struct index_state;
-int convert_to_sparse(struct index_state *istate);
+#define SPARSE_INDEX_MEMORY_ONLY (1 << 0)
+int convert_to_sparse(struct index_state *istate, int flags);
 
 /*
  * Some places in the codebase expect to search for a specific path.
index c8a5789694cf805799a288b66e517bedf786e479..b22e9816559cabc4f3d94f7f42cde5a50f32495a 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1059,15 +1059,21 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm,
        strbuf_setlen(sb, sb->len + len);
 }
 
-void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid,
-                             int abbrev_len)
+void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo,
+                                  const struct object_id *oid, int abbrev_len)
 {
        int r;
        strbuf_grow(sb, GIT_MAX_HEXSZ + 1);
-       r = find_unique_abbrev_r(sb->buf + sb->len, oid, abbrev_len);
+       r = repo_find_unique_abbrev_r(repo, sb->buf + sb->len, oid, abbrev_len);
        strbuf_setlen(sb, sb->len + r);
 }
 
+void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid,
+                             int abbrev_len)
+{
+       strbuf_repo_add_unique_abbrev(sb, the_repository, oid, abbrev_len);
+}
+
 /*
  * Returns the length of a line, without trailing spaces.
  *
index 5b1113abf8fccd49c936ed3fb5fe5d9e5dc774aa..96512f85b316e7f3b56df94cd1e7912dbb6020f0 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -70,7 +70,7 @@ struct strbuf {
 };
 
 extern char strbuf_slopbuf[];
-#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }
+#define STRBUF_INIT  { .buf = strbuf_slopbuf }
 
 /*
  * Predeclare this here, since cache.h includes this file before it defines the
@@ -634,8 +634,10 @@ void strbuf_list_free(struct strbuf **list);
  * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
  * the strbuf `sb`.
  */
-void strbuf_add_unique_abbrev(struct strbuf *sb,
-                             const struct object_id *oid,
+struct repository;
+void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo,
+                                  const struct object_id *oid, int abbrev_len);
+void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid,
                              int abbrev_len);
 
 /**
index 5f480ad50c415cc48a591adb29b46f00b67468d7..fe54665d86e72a3400a6f60f610a5264bc0f0667 100644 (file)
@@ -223,19 +223,24 @@ static int open_istream_loose(struct git_istream *st, struct repository *r,
                              const struct object_id *oid,
                              enum object_type *type)
 {
+       struct object_info oi = OBJECT_INFO_INIT;
+       oi.sizep = &st->size;
+       oi.typep = type;
+
        st->u.loose.mapped = map_loose_object(r, oid, &st->u.loose.mapsize);
        if (!st->u.loose.mapped)
                return -1;
-       if ((unpack_loose_header(&st->z,
-                                st->u.loose.mapped,
-                                st->u.loose.mapsize,
-                                st->u.loose.hdr,
-                                sizeof(st->u.loose.hdr)) < 0) ||
-           (parse_loose_header(st->u.loose.hdr, &st->size) < 0)) {
-               git_inflate_end(&st->z);
-               munmap(st->u.loose.mapped, st->u.loose.mapsize);
-               return -1;
+       switch (unpack_loose_header(&st->z, st->u.loose.mapped,
+                                   st->u.loose.mapsize, st->u.loose.hdr,
+                                   sizeof(st->u.loose.hdr), NULL)) {
+       case ULHR_OK:
+               break;
+       case ULHR_BAD:
+       case ULHR_TOO_LONG:
+               goto error;
        }
+       if (parse_loose_header(st->u.loose.hdr, &oi) < 0 || *type < 0)
+               goto error;
 
        st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
        st->u.loose.hdr_avail = st->z.total_out;
@@ -244,6 +249,10 @@ static int open_istream_loose(struct git_istream *st, struct repository *r,
        st->read = read_istream_loose;
 
        return 0;
+error:
+       git_inflate_end(&st->z);
+       munmap(st->u.loose.mapped, st->u.loose.mapsize);
+       return -1;
 }
 
 
index 43576ad12653943ac436443086b6cbf5d72279c7..549fc416d68ea4de2d1eb6f513af7bf66f37ccb9 100644 (file)
@@ -13,14 +13,6 @@ void string_list_init_dup(struct string_list *list)
        memcpy(list, &blank, sizeof(*list));
 }
 
-void string_list_init(struct string_list *list, int strdup_strings)
-{
-       if (strdup_strings)
-               string_list_init_dup(list);
-       else
-               string_list_init_nodup(list);
-}
-
 /* if there is no exact match, point to the index where the entry could be
  * inserted */
 static int get_entry_index(const struct string_list *list, const char *string,
index 0d6b46923968e54179ab62eaf086f851eb6c5b41..267d6e5769d9bcea057d68bb46e685d59339d05d 100644 (file)
@@ -104,11 +104,6 @@ struct string_list {
 void string_list_init_nodup(struct string_list *list);
 void string_list_init_dup(struct string_list *list);
 
-/**
- * TODO remove: For compatibility with any in-flight older API users
- */
-void string_list_init(struct string_list *list, int strdup_strings);
-
 /** Callback function type for for_each_string_list */
 typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
 
index 6b3cbd675895116586407ee4214acfb9b042b33d..9f55c8766ba9de77437275b8796f8e348fe35e03 100644 (file)
--- a/strvec.h
+++ b/strvec.h
@@ -33,7 +33,9 @@ struct strvec {
        size_t alloc;
 };
 
-#define STRVEC_INIT { empty_strvec, 0, 0 }
+#define STRVEC_INIT { \
+       .v = empty_strvec, \
+}
 
 /**
  * Initialize an array. This is no different than assigning from
index 2026120fb3891ba53a8bb676e2da5a6248984cf7..f95344028b5208cc9808382b9c69975d8a692682 100644 (file)
@@ -649,9 +649,10 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void
                        config_source.file = file;
                } else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 ||
                           repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) {
+                       config_source.repo = repo;
                        config_source.blob = oidstr = xstrdup(oid_to_hex(&oid));
                        if (repo != the_repository)
-                               add_to_alternates_memory(repo->objects->odb->path);
+                               add_submodule_odb_by_path(repo->objects->odb->path);
                } else {
                        goto out;
                }
@@ -702,7 +703,7 @@ void gitmodules_config_oid(const struct object_id *commit_oid)
 
        if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {
                git_config_from_blob_oid(gitmodules_cb, rev.buf,
-                                        &oid, the_repository);
+                                        the_repository, &oid, the_repository);
        }
        strbuf_release(&rev);
 
index c11e22cf509ad1be7531c1740f4701578e6db723..65875b94ea503c4f13fa907ec58f8ae648a7d199 100644 (file)
@@ -45,10 +45,6 @@ struct submodule {
        struct object_id gitmodules_oid;
        int recommend_shallow;
 };
-
-#define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \
-       NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, { { 0 } }, -1 };
-
 struct submodule_cache;
 struct repository;
 
index 8e611fe1dbf1f7616040f8359ee5d9b9892ad191..c689070524171b8e6cfae3349cb317c18f693664 100644 (file)
@@ -165,6 +165,8 @@ void stage_updated_gitmodules(struct index_state *istate)
                die(_("staging updated .gitmodules failed"));
 }
 
+static struct string_list added_submodule_odb_paths = STRING_LIST_INIT_NODUP;
+
 /* TODO: remove this function, use repo_submodule_init instead. */
 int add_submodule_odb(const char *path)
 {
@@ -178,12 +180,35 @@ int add_submodule_odb(const char *path)
                ret = -1;
                goto done;
        }
-       add_to_alternates_memory(objects_directory.buf);
+       string_list_insert(&added_submodule_odb_paths,
+                          strbuf_detach(&objects_directory, NULL));
 done:
        strbuf_release(&objects_directory);
        return ret;
 }
 
+void add_submodule_odb_by_path(const char *path)
+{
+       string_list_insert(&added_submodule_odb_paths, xstrdup(path));
+}
+
+int register_all_submodule_odb_as_alternates(void)
+{
+       int i;
+       int ret = added_submodule_odb_paths.nr;
+
+       for (i = 0; i < added_submodule_odb_paths.nr; i++)
+               add_to_alternates_memory(added_submodule_odb_paths.items[i].string);
+       if (ret) {
+               string_list_clear(&added_submodule_odb_paths, 0);
+               trace2_data_intmax("submodule", the_repository,
+                                  "register_all_submodule_odb_as_alternates/registered", ret);
+               if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0))
+                       BUG("register_all_submodule_odb_as_alternates() called");
+       }
+       return ret;
+}
+
 void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                                             const char *path)
 {
@@ -237,6 +262,11 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
 /*
  * Determine if a submodule has been initialized at a given 'path'
  */
+/*
+ * NEEDSWORK: Emit a warning if submodule.active exists, but is valueless,
+ * ie, the config looks like: "[submodule] active\n".
+ * Since that is an invalid pathspec, we should inform the user.
+ */
 int is_submodule_active(struct repository *repo, const char *path)
 {
        int ret = 0;
@@ -497,9 +527,6 @@ static void prepare_submodule_repo_env_in_gitdir(struct strvec *out)
 /*
  * Initialize a repository struct for a submodule based on the provided 'path'.
  *
- * Unlike repo_submodule_init, this tolerates submodules not present
- * in .gitmodules. This function exists only to preserve historical behavior,
- *
  * Returns the repository struct on success,
  * NULL when the submodule is not present.
  */
@@ -697,8 +724,20 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
                strvec_push(&cp.args, oid_to_hex(new_oid));
 
        prepare_submodule_repo_env(&cp.env_array);
-       if (start_command(&cp))
+
+       if (!is_directory(path)) {
+               /* fall back to absorbed git dir, if any */
+               if (!sub)
+                       goto done;
+               cp.dir = sub->gitdir;
+               strvec_push(&cp.env_array, GIT_DIR_ENVIRONMENT "=.");
+               strvec_push(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT "=.");
+       }
+
+       if (start_command(&cp)) {
                diff_emit_submodule_error(o, "(diff failed)\n");
+               goto done;
+       }
 
        while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
                diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
@@ -891,23 +930,33 @@ struct has_commit_data {
 static int check_has_commit(const struct object_id *oid, void *data)
 {
        struct has_commit_data *cb = data;
+       struct repository subrepo;
+       enum object_type type;
+
+       if (repo_submodule_init(&subrepo, cb->repo, cb->path, null_oid())) {
+               cb->result = 0;
+               goto cleanup;
+       }
 
-       enum object_type type = oid_object_info(cb->repo, oid, NULL);
+       type = oid_object_info(&subrepo, oid, NULL);
 
        switch (type) {
        case OBJ_COMMIT:
-               return 0;
+               goto cleanup;
        case OBJ_BAD:
                /*
                 * Object is missing or invalid. If invalid, an error message
                 * has already been printed.
                 */
                cb->result = 0;
-               return 0;
+               goto cleanup;
        default:
                die(_("submodule entry '%s' (%s) is a %s, not a commit"),
                    cb->path, oid_to_hex(oid), type_name(type));
        }
+cleanup:
+       repo_clear(&subrepo);
+       return 0;
 }
 
 static int submodule_has_commits(struct repository *r,
@@ -1281,9 +1330,11 @@ struct submodule_parallel_fetch {
 
        struct strbuf submodules_with_errors;
 };
-#define SPF_INIT {0, STRVEC_INIT, NULL, NULL, 0, 0, 0, 0, \
-                 STRING_LIST_INIT_DUP, \
-                 NULL, 0, 0, STRBUF_INIT}
+#define SPF_INIT { \
+       .args = STRVEC_INIT, \
+       .changed_submodule_names = STRING_LIST_INIT_DUP, \
+       .submodules_with_errors = STRBUF_INIT, \
+}
 
 static int get_fetch_recurse_config(const struct submodule *submodule,
                                    struct submodule_parallel_fetch *spf)
@@ -1381,24 +1432,13 @@ static void fetch_task_release(struct fetch_task *p)
 }
 
 static struct repository *get_submodule_repo_for(struct repository *r,
-                                                const struct submodule *sub)
+                                                const char *path)
 {
        struct repository *ret = xmalloc(sizeof(*ret));
 
-       if (repo_submodule_init(ret, r, sub)) {
-               /*
-                * No entry in .gitmodules? Technically not a submodule,
-                * but historically we supported repositories that happen to be
-                * in-place where a gitlink is. Keep supporting them.
-                */
-               struct strbuf gitdir = STRBUF_INIT;
-               strbuf_repo_worktree_path(&gitdir, r, "%s/.git", sub->path);
-               if (repo_init(ret, gitdir.buf, NULL)) {
-                       strbuf_release(&gitdir);
-                       free(ret);
-                       return NULL;
-               }
-               strbuf_release(&gitdir);
+       if (repo_submodule_init(ret, r, path, null_oid())) {
+               free(ret);
+               return NULL;
        }
 
        return ret;
@@ -1440,7 +1480,7 @@ static int get_next_submodule(struct child_process *cp,
                        continue;
                }
 
-               task->repo = get_submodule_repo_for(spf->r, task->sub);
+               task->repo = get_submodule_repo_for(spf->r, task->sub->path);
                if (task->repo) {
                        struct strbuf submodule_prefix = STRBUF_INIT;
                        child_process_init(cp);
@@ -1819,14 +1859,16 @@ out:
 
 void submodule_unset_core_worktree(const struct submodule *sub)
 {
-       char *config_path = xstrfmt("%s/modules/%s/config",
-                                   get_git_dir(), sub->name);
+       struct strbuf config_path = STRBUF_INIT;
+
+       submodule_name_to_gitdir(&config_path, the_repository, sub->name);
+       strbuf_addstr(&config_path, "/config");
 
-       if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
+       if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL))
                warning(_("Could not unset core.worktree setting in submodule '%s'"),
                          sub->path);
 
-       free(config_path);
+       strbuf_release(&config_path);
 }
 
 static const char *get_super_prefix_or_empty(void)
@@ -1866,6 +1908,7 @@ static void submodule_reset_index(const char *path)
 
        strvec_pushf(&cp.args, "--super-prefix=%s%s/",
                     get_super_prefix_or_empty(), path);
+       /* TODO: determine if this might overwright untracked files */
        strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
 
        strvec_push(&cp.args, empty_tree_oid_hex());
@@ -1922,20 +1965,22 @@ int submodule_move_head(const char *path,
                                absorb_git_dir_into_superproject(path,
                                        ABSORB_GITDIR_RECURSE_SUBMODULES);
                } else {
-                       char *gitdir = xstrfmt("%s/modules/%s",
-                                   get_git_dir(), sub->name);
-                       connect_work_tree_and_git_dir(path, gitdir, 0);
-                       free(gitdir);
+                       struct strbuf gitdir = STRBUF_INIT;
+                       submodule_name_to_gitdir(&gitdir, the_repository,
+                                                sub->name);
+                       connect_work_tree_and_git_dir(path, gitdir.buf, 0);
+                       strbuf_release(&gitdir);
 
                        /* make sure the index is clean as well */
                        submodule_reset_index(path);
                }
 
                if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
-                       char *gitdir = xstrfmt("%s/modules/%s",
-                                   get_git_dir(), sub->name);
-                       connect_work_tree_and_git_dir(path, gitdir, 1);
-                       free(gitdir);
+                       struct strbuf gitdir = STRBUF_INIT;
+                       submodule_name_to_gitdir(&gitdir, the_repository,
+                                                sub->name);
+                       connect_work_tree_and_git_dir(path, gitdir.buf, 1);
+                       strbuf_release(&gitdir);
                }
        }
 
@@ -2050,7 +2095,7 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
 static void relocate_single_git_dir_into_superproject(const char *path)
 {
        char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
-       char *new_git_dir;
+       struct strbuf new_gitdir = STRBUF_INIT;
        const struct submodule *sub;
 
        if (submodule_uses_worktrees(path))
@@ -2068,14 +2113,13 @@ static void relocate_single_git_dir_into_superproject(const char *path)
        if (!sub)
                die(_("could not lookup name for submodule '%s'"), path);
 
-       new_git_dir = git_pathdup("modules/%s", sub->name);
-       if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
+       submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name);
+       if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0)
                die(_("refusing to move '%s' into an existing git dir"),
                    real_old_git_dir);
-       if (safe_create_leading_directories_const(new_git_dir) < 0)
-               die(_("could not create directory '%s'"), new_git_dir);
-       real_new_git_dir = real_pathdup(new_git_dir, 1);
-       free(new_git_dir);
+       if (safe_create_leading_directories_const(new_gitdir.buf) < 0)
+               die(_("could not create directory '%s'"), new_gitdir.buf);
+       real_new_git_dir = real_pathdup(new_gitdir.buf, 1);
 
        fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
                get_super_prefix_or_empty(), path,
@@ -2086,6 +2130,7 @@ static void relocate_single_git_dir_into_superproject(const char *path)
        free(old_git_dir);
        free(real_old_git_dir);
        free(real_new_git_dir);
+       strbuf_release(&new_gitdir);
 }
 
 /*
@@ -2105,6 +2150,7 @@ void absorb_git_dir_into_superproject(const char *path,
        /* Not populated? */
        if (!sub_git_dir) {
                const struct submodule *sub;
+               struct strbuf sub_gitdir = STRBUF_INIT;
 
                if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
                        /* unpopulated as expected */
@@ -2126,8 +2172,9 @@ void absorb_git_dir_into_superproject(const char *path,
                sub = submodule_from_path(the_repository, null_oid(), path);
                if (!sub)
                        die(_("could not lookup name for submodule '%s'"), path);
-               connect_work_tree_and_git_dir(path,
-                       git_path("modules/%s", sub->name), 0);
+               submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);
+               connect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
+               strbuf_release(&sub_gitdir);
        } else {
                /* Is it already absorbed into the superprojects git dir? */
                char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
@@ -2278,9 +2325,36 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
                        goto cleanup;
                }
                strbuf_reset(buf);
-               strbuf_git_path(buf, "%s/%s", "modules", sub->name);
+               submodule_name_to_gitdir(buf, the_repository, sub->name);
        }
 
 cleanup:
        return ret;
 }
+
+void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
+                             const char *submodule_name)
+{
+       /*
+        * NEEDSWORK: The current way of mapping a submodule's name to
+        * its location in .git/modules/ has problems with some naming
+        * schemes. For example, if a submodule is named "foo" and
+        * another is named "foo/bar" (whether present in the same
+        * superproject commit or not - the problem will arise if both
+        * superproject commits have been checked out at any point in
+        * time), or if two submodule names only have different cases in
+        * a case-insensitive filesystem.
+        *
+        * There are several solutions, including encoding the path in
+        * some way, introducing a submodule.<name>.gitdir config in
+        * .git/config (not .gitmodules) that allows overriding what the
+        * gitdir of a submodule would be (and teach Git, upon noticing
+        * a clash, to automatically determine a non-clashing name and
+        * to write such a config), or introducing a
+        * submodule.<name>.gitdir config in .gitmodules that repo
+        * administrators can explicitly set. Nothing has been decided,
+        * so for now, just append the name at the end of the path.
+        */
+       strbuf_repo_git_path(buf, r, "modules/");
+       strbuf_addstr(buf, submodule_name);
+}
index 84640c49c1149d665f88b75c38405a40e4157a8c..6bd2c99fd99d409abd89771d511b3a7db5f47252 100644 (file)
@@ -37,7 +37,9 @@ struct submodule_update_strategy {
        enum submodule_update_type type;
        const char *command;
 };
-#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
+#define SUBMODULE_UPDATE_STRATEGY_INIT { \
+       .type = SM_UPDATE_UNSPECIFIED, \
+}
 
 int is_gitmodules_unmerged(struct index_state *istate);
 int is_writing_gitmodules_ok(void);
@@ -97,7 +99,15 @@ int submodule_uses_gitfile(const char *path);
 #define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
 int bad_to_remove_submodule(const char *path, unsigned flags);
 
+/*
+ * Call add_submodule_odb() to add the submodule at the given path to a list.
+ * When register_all_submodule_odb_as_alternates() is called, the object stores
+ * of all submodules in that list will be added as alternates in
+ * the_repository.
+ */
 int add_submodule_odb(const char *path);
+void add_submodule_odb_by_path(const char *path);
+int register_all_submodule_odb_as_alternates(void);
 
 /*
  * Checks if there are submodule changes in a..b. If a is the null OID,
@@ -124,6 +134,13 @@ int push_unpushed_submodules(struct repository *r,
  */
 int submodule_to_gitdir(struct strbuf *buf, const char *submodule);
 
+/*
+ * Given a submodule name, create a path to where the submodule's gitdir lives
+ * inside of the provided repository's 'modules' directory.
+ */
+void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
+                             const char *submodule_name);
+
 /*
  * Make sure that no submodule's git dir is nested in a sibling submodule's.
  */
index 9e7012230203805da49deeba5f9a66af2199c792..29f72354bf17e959abcc2e14243816b1a0262e52 100644 (file)
--- a/t/README
+++ b/t/README
@@ -366,6 +366,13 @@ excluded as so much relies on it, but this might change in the future.
 GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
 test suite. Accept any boolean values that are accepted by git-config.
 
+GIT_TEST_PASSING_SANITIZE_LEAK=<boolean> when compiled with
+SANITIZE=leak will run only those tests that have whitelisted
+themselves as passing with no memory leaks. Tests can be whitelisted
+by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing
+"test-lib.sh" itself at the top of the test script. This test mode is
+used by the "linux-leaks" CI target.
+
 GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
 default to n.
 
@@ -425,6 +432,10 @@ GIT_TEST_MULTI_PACK_INDEX=<boolean>, when true, forces the multi-pack-
 index to be written after every 'git repack' command, and overrides the
 'core.multiPackIndex' setting to true.
 
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=<boolean>, when true, sets the
+'--bitmap' option on all invocations of 'git multi-pack-index write',
+and ignores pack-objects' '--write-bitmap-index'.
+
 GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the
 'uploadpack.allowSidebandAll' setting to true, and when false, forces
 fetch-pack to not request sideband-all (even if the server advertises
@@ -448,6 +459,13 @@ GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting
 to <n> and 'checkout.thresholdForParallelism' to 0, forcing the
 execution of the parallel-checkout code.
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<boolean>, when true, makes
+registering submodule ODBs as alternates a fatal action. Support for
+this environment variable can be removed once the migration to
+explicitly providing repositories when accessing submodule objects is
+complete or needs to be abandoned for whatever reason (in which case the
+migrated codepaths still retain their performance benefits).
+
 Naming Tests
 ------------
 
@@ -753,7 +771,8 @@ Test harness library
 --------------------
 
 There are a handful helper functions defined in the test harness
-library for your script to use.
+library for your script to use. Some of them are listed below;
+see test-lib-functions.sh for the full list and their options.
 
  - test_expect_success [<prereq>] <message> <script>
 
@@ -799,10 +818,12 @@ library for your script to use.
    argument.  This is primarily meant for use during the
    development of a new test script.
 
- - debug <git-command>
+ - debug [options] <git-command>
 
    Run a git command inside a debugger. This is primarily meant for
-   use when debugging a failing test script.
+   use when debugging a failing test script. With '-t', use your
+   original TERM instead of test-lib.sh's "dumb", so that your
+   debugger interface has colors.
 
  - test_done
 
@@ -989,7 +1010,7 @@ library for your script to use.
        EOF
 
 
- - test_pause
+ - test_pause [options]
 
        This command is useful for writing and debugging tests and must be
        removed before submitting. It halts the execution of the test and
index 134a1e9d762ced9955b42bafa9b0b8287ac68753..ff35f5999b367106f04743582ca9fb490b80501f 100644 (file)
@@ -7,6 +7,11 @@ static int bitmap_list_commits(void)
        return test_bitmap_commits(the_repository);
 }
 
+static int bitmap_dump_hashes(void)
+{
+       return test_bitmap_hashes(the_repository);
+}
+
 int cmd__bitmap(int argc, const char **argv)
 {
        setup_git_directory();
@@ -16,9 +21,12 @@ int cmd__bitmap(int argc, const char **argv)
 
        if (!strcmp(argv[1], "list-commits"))
                return bitmap_list_commits();
+       if (!strcmp(argv[1], "dump-hashes"))
+               return bitmap_dump_hashes();
 
 usage:
-       usage("\ttest-tool bitmap list-commits");
+       usage("\ttest-tool bitmap list-commits\n"
+             "\ttest-tool bitmap dump-hashes");
 
        return -1;
 }
index cf0f2c7228e8293c5982168f21b004bc7daa2ff0..99010614f6da9435e259e7e9a012e0154cbd9478 100644 (file)
@@ -45,8 +45,10 @@ int cmd__dump_untracked_cache(int ac, const char **av)
        struct untracked_cache *uc;
        struct strbuf base = STRBUF_INIT;
 
-       /* Hack to avoid modifying the untracked cache when we read it */
-       ignore_untracked_cache_config = 1;
+       /* Set core.untrackedCache=keep before setup_git_directory() */
+       xsetenv("GIT_CONFIG_COUNT", "1", 1);
+       xsetenv("GIT_CONFIG_KEY_0", "core.untrackedCache", 1);
+       xsetenv("GIT_CONFIG_VALUE_0", "keep", 1);
 
        setup_git_directory();
        if (read_cache() < 0)
index c5cffaa4b73ff52b2f166231ceb4a77b24ee8cef..ebf68f7de82465d2761bceedd47715badbf76dbb 100644 (file)
@@ -2,6 +2,12 @@
 #include "cache.h"
 #include "mergesort.h"
 
+static uint32_t minstd_rand(uint32_t *state)
+{
+       *state = (uint64_t)*state * 48271 % 2147483647;
+       return *state;
+}
+
 struct line {
        char *text;
        struct line *next;
@@ -23,14 +29,12 @@ static int compare_strings(const void *a, const void *b)
        return strcmp(x->text, y->text);
 }
 
-int cmd__mergesort(int argc, const char **argv)
+static int sort_stdin(void)
 {
        struct line *line, *p = NULL, *lines = NULL;
        struct strbuf sb = STRBUF_INIT;
 
-       for (;;) {
-               if (strbuf_getwholeline(&sb, stdin, '\n'))
-                       break;
+       while (!strbuf_getline(&sb, stdin)) {
                line = xmalloc(sizeof(struct line));
                line->text = strbuf_detach(&sb, NULL);
                if (p) {
@@ -46,8 +50,362 @@ int cmd__mergesort(int argc, const char **argv)
        lines = llist_mergesort(lines, get_next, set_next, compare_strings);
 
        while (lines) {
-               printf("%s", lines->text);
+               puts(lines->text);
                lines = lines->next;
        }
        return 0;
 }
+
+static void dist_sawtooth(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = i % m;
+}
+
+static void dist_rand(int *arr, int n, int m)
+{
+       int i;
+       uint32_t seed = 1;
+       for (i = 0; i < n; i++)
+               arr[i] = minstd_rand(&seed) % m;
+}
+
+static void dist_stagger(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = (i * m + i) % n;
+}
+
+static void dist_plateau(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = (i < m) ? i : m;
+}
+
+static void dist_shuffle(int *arr, int n, int m)
+{
+       int i, j, k;
+       uint32_t seed = 1;
+       for (i = j = 0, k = 1; i < n; i++)
+               arr[i] = minstd_rand(&seed) % m ? (j += 2) : (k += 2);
+}
+
+#define DIST(name) { #name, dist_##name }
+
+static struct dist {
+       const char *name;
+       void (*fn)(int *arr, int n, int m);
+} dist[] = {
+       DIST(sawtooth),
+       DIST(rand),
+       DIST(stagger),
+       DIST(plateau),
+       DIST(shuffle),
+};
+
+static const struct dist *get_dist_by_name(const char *name)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(dist); i++) {
+              if (!strcmp(dist[i].name, name))
+                      return &dist[i];
+       }
+       return NULL;
+}
+
+static void mode_copy(int *arr, int n)
+{
+       /* nothing */
+}
+
+static void mode_reverse(int *arr, int n)
+{
+       int i, j;
+       for (i = 0, j = n - 1; i < j; i++, j--)
+               SWAP(arr[i], arr[j]);
+}
+
+static void mode_reverse_1st_half(int *arr, int n)
+{
+       mode_reverse(arr, n / 2);
+}
+
+static void mode_reverse_2nd_half(int *arr, int n)
+{
+       int half = n / 2;
+       mode_reverse(arr + half, n - half);
+}
+
+static int compare_ints(const void *av, const void *bv)
+{
+       const int *ap = av, *bp = bv;
+       int a = *ap, b = *bp;
+       return (a > b) - (a < b);
+}
+
+static void mode_sort(int *arr, int n)
+{
+       QSORT(arr, n, compare_ints);
+}
+
+static void mode_dither(int *arr, int n)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] += i % 5;
+}
+
+static void unriffle(int *arr, int n, int *tmp)
+{
+       int i, j;
+       COPY_ARRAY(tmp, arr, n);
+       for (i = j = 0; i < n; i += 2)
+               arr[j++] = tmp[i];
+       for (i = 1; i < n; i += 2)
+               arr[j++] = tmp[i];
+}
+
+static void unriffle_recursively(int *arr, int n, int *tmp)
+{
+       if (n > 1) {
+               int half = n / 2;
+               unriffle(arr, n, tmp);
+               unriffle_recursively(arr, half, tmp);
+               unriffle_recursively(arr + half, n - half, tmp);
+       }
+}
+
+static void mode_unriffle(int *arr, int n)
+{
+       int *tmp;
+       ALLOC_ARRAY(tmp, n);
+       unriffle_recursively(arr, n, tmp);
+       free(tmp);
+}
+
+static unsigned int prev_pow2(unsigned int n)
+{
+       unsigned int pow2 = 1;
+       while (pow2 * 2 < n)
+               pow2 *= 2;
+       return pow2;
+}
+
+static void unriffle_recursively_skewed(int *arr, int n, int *tmp)
+{
+       if (n > 1) {
+               int pow2 = prev_pow2(n);
+               int rest = n - pow2;
+               unriffle(arr + pow2 - rest, rest * 2, tmp);
+               unriffle_recursively_skewed(arr, pow2, tmp);
+               unriffle_recursively_skewed(arr + pow2, rest, tmp);
+       }
+}
+
+static void mode_unriffle_skewed(int *arr, int n)
+{
+       int *tmp;
+       ALLOC_ARRAY(tmp, n);
+       unriffle_recursively_skewed(arr, n, tmp);
+       free(tmp);
+}
+
+#define MODE(name) { #name, mode_##name }
+
+static struct mode {
+       const char *name;
+       void (*fn)(int *arr, int n);
+} mode[] = {
+       MODE(copy),
+       MODE(reverse),
+       MODE(reverse_1st_half),
+       MODE(reverse_2nd_half),
+       MODE(sort),
+       MODE(dither),
+       MODE(unriffle),
+       MODE(unriffle_skewed),
+};
+
+static const struct mode *get_mode_by_name(const char *name)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(mode); i++) {
+              if (!strcmp(mode[i].name, name))
+                      return &mode[i];
+       }
+       return NULL;
+}
+
+static int generate(int argc, const char **argv)
+{
+       const struct dist *dist = NULL;
+       const struct mode *mode = NULL;
+       int i, n, m, *arr;
+
+       if (argc != 4)
+               return 1;
+
+       dist = get_dist_by_name(argv[0]);
+       mode = get_mode_by_name(argv[1]);
+       n = strtol(argv[2], NULL, 10);
+       m = strtol(argv[3], NULL, 10);
+       if (!dist || !mode)
+               return 1;
+
+       ALLOC_ARRAY(arr, n);
+       dist->fn(arr, n, m);
+       mode->fn(arr, n);
+       for (i = 0; i < n; i++)
+               printf("%08x\n", arr[i]);
+       free(arr);
+       return 0;
+}
+
+static struct stats {
+       int get_next, set_next, compare;
+} stats;
+
+struct number {
+       int value, rank;
+       struct number *next;
+};
+
+static void *get_next_number(const void *a)
+{
+       stats.get_next++;
+       return ((const struct number *)a)->next;
+}
+
+static void set_next_number(void *a, void *b)
+{
+       stats.set_next++;
+       ((struct number *)a)->next = b;
+}
+
+static int compare_numbers(const void *av, const void *bv)
+{
+       const struct number *an = av, *bn = bv;
+       int a = an->value, b = bn->value;
+       stats.compare++;
+       return (a > b) - (a < b);
+}
+
+static void clear_numbers(struct number *list)
+{
+       while (list) {
+               struct number *next = list->next;
+               free(list);
+               list = next;
+       }
+}
+
+static int test(const struct dist *dist, const struct mode *mode, int n, int m)
+{
+       int *arr;
+       size_t i;
+       struct number *curr, *list, **tail;
+       int is_sorted = 1;
+       int is_stable = 1;
+       const char *verdict;
+       int result = -1;
+
+       ALLOC_ARRAY(arr, n);
+       dist->fn(arr, n, m);
+       mode->fn(arr, n);
+       for (i = 0, tail = &list; i < n; i++) {
+               curr = xmalloc(sizeof(*curr));
+               curr->value = arr[i];
+               curr->rank = i;
+               *tail = curr;
+               tail = &curr->next;
+       }
+       *tail = NULL;
+
+       stats.get_next = stats.set_next = stats.compare = 0;
+       list = llist_mergesort(list, get_next_number, set_next_number,
+                              compare_numbers);
+
+       QSORT(arr, n, compare_ints);
+       for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) {
+               if (arr[i] != curr->value)
+                       is_sorted = 0;
+               if (curr->next && curr->value == curr->next->value &&
+                   curr->rank >= curr->next->rank)
+                       is_stable = 0;
+       }
+       if (i < n) {
+               verdict = "too short";
+       } else if (curr) {
+               verdict = "too long";
+       } else if (!is_sorted) {
+               verdict = "not sorted";
+       } else if (!is_stable) {
+               verdict = "unstable";
+       } else {
+               verdict = "OK";
+               result = 0;
+       }
+
+       printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n",
+              dist->name, mode->name, n, m, stats.get_next, stats.set_next,
+              stats.compare, verdict);
+
+       clear_numbers(list);
+       free(arr);
+
+       return result;
+}
+
+/*
+ * A version of the qsort certification program from "Engineering a Sort
+ * Function" by Bentley and McIlroy, Software—Practice and Experience,
+ * Volume 23, Issue 11, 1249–1265 (November 1993).
+ */
+static int run_tests(int argc, const char **argv)
+{
+       const char *argv_default[] = { "100", "1023", "1024", "1025" };
+       if (!argc)
+               return run_tests(ARRAY_SIZE(argv_default), argv_default);
+       printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n",
+              "distribut", "mode", "n", "m", "get_next", "set_next",
+              "compare", "verdict");
+       while (argc--) {
+               int i, j, m, n = strtol(*argv++, NULL, 10);
+               for (i = 0; i < ARRAY_SIZE(dist); i++) {
+                       for (j = 0; j < ARRAY_SIZE(mode); j++) {
+                               for (m = 1; m < 2 * n; m *= 2) {
+                                       if (test(&dist[i], &mode[j], n, m))
+                                               return 1;
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
+int cmd__mergesort(int argc, const char **argv)
+{
+       int i;
+       const char *sep;
+
+       if (argc == 6 && !strcmp(argv[1], "generate"))
+               return generate(argc - 2, argv + 2);
+       if (argc == 2 && !strcmp(argv[1], "sort"))
+               return sort_stdin();
+       if (argc > 1 && !strcmp(argv[1], "test"))
+               return run_tests(argc - 2, argv + 2);
+       fprintf(stderr, "usage: test-tool mergesort generate <distribution> <mode> <n> <m>\n");
+       fprintf(stderr, "   or: test-tool mergesort sort\n");
+       fprintf(stderr, "   or: test-tool mergesort test [<n>...]\n");
+       fprintf(stderr, "\n");
+       for (i = 0, sep = "distributions: "; i < ARRAY_SIZE(dist); i++, sep = ", ")
+               fprintf(stderr, "%s%s", sep, dist[i].name);
+       fprintf(stderr, "\n");
+       for (i = 0, sep = "modes: "; i < ARRAY_SIZE(mode); i++, sep = ", ")
+               fprintf(stderr, "%s%s", sep, mode[i].name);
+       fprintf(stderr, "\n");
+       return 129;
+}
index 2051ce57db735c9e5f353c9ae2a98ff6056246d6..a282b6ff13e56196a36b951b4a9f7540f5ba2666 100644 (file)
@@ -134,7 +134,6 @@ int cmd__parse_options(int argc, const char **argv)
                OPT_NOOP_NOARG(0, "obsolete"),
                OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
                OPT_GROUP("Magic arguments"),
-               OPT_ARGUMENT("quux", NULL, "means --quux"),
                OPT_NUMBER_CALLBACK(&integer, "set integer to NUM",
                        number_callback),
                { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b",
index 7c2eb11a8e70ffe1ff6d438efe0e0f0024caa546..9d6fa7a3773c102f7250bcb51c1aa18abe3487ac 100644 (file)
@@ -3,6 +3,7 @@
 #include "midx.h"
 #include "repository.h"
 #include "object-store.h"
+#include "pack-bitmap.h"
 
 static int read_midx_file(const char *object_dir, int show_objects)
 {
@@ -60,12 +61,52 @@ static int read_midx_file(const char *object_dir, int show_objects)
        return 0;
 }
 
+static int read_midx_checksum(const char *object_dir)
+{
+       struct multi_pack_index *m;
+
+       setup_git_directory();
+       m = load_multi_pack_index(object_dir, 1);
+       if (!m)
+               return 1;
+       printf("%s\n", hash_to_hex(get_midx_checksum(m)));
+       return 0;
+}
+
+static int read_midx_preferred_pack(const char *object_dir)
+{
+       struct multi_pack_index *midx = NULL;
+       struct bitmap_index *bitmap = NULL;
+
+       setup_git_directory();
+
+       midx = load_multi_pack_index(object_dir, 1);
+       if (!midx)
+               return 1;
+
+       bitmap = prepare_bitmap_git(the_repository);
+       if (!bitmap)
+               return 1;
+       if (!bitmap_is_midx(bitmap)) {
+               free_bitmap_index(bitmap);
+               return 1;
+       }
+
+       printf("%s\n", midx->pack_names[midx_preferred_pack(bitmap)]);
+       free_bitmap_index(bitmap);
+       return 0;
+}
+
 int cmd__read_midx(int argc, const char **argv)
 {
        if (!(argc == 2 || argc == 3))
-               usage("read-midx [--show-objects] <object-dir>");
+               usage("read-midx [--show-objects|--checksum|--preferred-pack] <object-dir>");
 
        if (!strcmp(argv[1], "--show-objects"))
                return read_midx_file(argv[2], 1);
+       else if (!strcmp(argv[1], "--checksum"))
+               return read_midx_checksum(argv[2]);
+       else if (!strcmp(argv[1], "--preferred-pack"))
+               return read_midx_preferred_pack(argv[2]);
        return read_midx_file(argv[1], 0);
 }
index 7ae03dc7123468463d02a28dce2b1c6175576b6f..3c4fb862234da8bd50bbe820d8e6510e0b4630e9 100644 (file)
@@ -60,8 +60,10 @@ struct testsuite {
        int next;
        int quiet, immediate, verbose, verbose_log, trace, write_junit_xml;
 };
-#define TESTSUITE_INIT \
-       { STRING_LIST_INIT_DUP, STRING_LIST_INIT_DUP, -1, 0, 0, 0, 0, 0, 0 }
+#define TESTSUITE_INIT { \
+       .tests = STRING_LIST_INIT_DUP, \
+       .failed = STRING_LIST_INIT_DUP, \
+}
 
 static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
                     void **task_cb)
@@ -142,9 +144,6 @@ static int testsuite(int argc, const char **argv)
                OPT_END()
        };
 
-       memset(&suite, 0, sizeof(suite));
-       suite.tests.strdup_strings = suite.failed.strdup_strings = 1;
-
        argc = parse_options(argc, argv, NULL, options,
                        testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
 
index aee35e5aef40431d5f572c71fa6aeebbe1bcf344..28e905afc36afdc3202ec68ac915b1ddc6a08781 100644 (file)
@@ -10,12 +10,12 @@ static char const * const serve_usage[] = {
 
 int cmd__serve_v2(int argc, const char **argv)
 {
-       struct serve_options opts = SERVE_OPTIONS_INIT;
-
+       int stateless_rpc = 0;
+       int advertise_capabilities = 0;
        struct option options[] = {
-               OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
+               OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
                         N_("quit after a single request/response exchange")),
-               OPT_BOOL(0, "advertise-capabilities", &opts.advertise_capabilities,
+               OPT_BOOL(0, "advertise-capabilities", &advertise_capabilities,
                         N_("exit immediately after advertising capabilities")),
                OPT_END()
        };
@@ -25,7 +25,11 @@ int cmd__serve_v2(int argc, const char **argv)
        argc = parse_options(argc, argv, prefix, options, serve_usage,
                             PARSE_OPT_KEEP_DASHDASH |
                             PARSE_OPT_KEEP_UNKNOWN);
-       serve(&opts);
+
+       if (advertise_capabilities)
+               protocol_v2_advertise_capabilities();
+       else
+               protocol_v2_serve_loop(stateless_rpc);
 
        return 0;
 }
index 42040ef81b1e8414b4c35d3f4cef566d7d9d01d6..28365ff85b69bbda4ca47b5284b2cbd7bebea9e9 100644 (file)
@@ -9,6 +9,7 @@
 #include "parse-options.h"
 #include "thread-utils.h"
 #include "strvec.h"
+#include "run-command.h"
 
 #ifndef SUPPORTS_SIMPLE_IPC
 int cmd__simple_ipc(int argc, const char **argv)
@@ -112,7 +113,7 @@ static int app__slow_command(ipc_server_reply_cb *reply_cb,
 /*
  * The client sent a command followed by a (possibly very) large buffer.
  */
-static int app__sendbytes_command(const char *received,
+static int app__sendbytes_command(const char *received, size_t received_len,
                                  ipc_server_reply_cb *reply_cb,
                                  struct ipc_server_reply_data *reply_data)
 {
@@ -123,6 +124,13 @@ static int app__sendbytes_command(const char *received,
        int errs = 0;
        int ret;
 
+       /*
+        * The test is setup to send:
+        *     "sendbytes" SP <n * char>
+        */
+       if (received_len < strlen("sendbytes "))
+               BUG("received_len is short in app__sendbytes_command");
+
        if (skip_prefix(received, "sendbytes ", &p))
                len_ballast = strlen(p);
 
@@ -160,7 +168,7 @@ static ipc_server_application_cb test_app_cb;
  * by this application.
  */
 static int test_app_cb(void *application_data,
-                      const char *command,
+                      const char *command, size_t command_len,
                       ipc_server_reply_cb *reply_cb,
                       struct ipc_server_reply_data *reply_data)
 {
@@ -173,7 +181,7 @@ static int test_app_cb(void *application_data,
        if (application_data != (void*)&my_app_data)
                BUG("application_cb: application_data pointer wrong");
 
-       if (!strcmp(command, "quit")) {
+       if (command_len == 4 && !strncmp(command, "quit", 4)) {
                /*
                 * The client sent a "quit" command.  This is an async
                 * request for the server to shutdown.
@@ -193,22 +201,23 @@ static int test_app_cb(void *application_data,
                return SIMPLE_IPC_QUIT;
        }
 
-       if (!strcmp(command, "ping")) {
+       if (command_len == 4 && !strncmp(command, "ping", 4)) {
                const char *answer = "pong";
                return reply_cb(reply_data, answer, strlen(answer));
        }
 
-       if (!strcmp(command, "big"))
+       if (command_len == 3 && !strncmp(command, "big", 3))
                return app__big_command(reply_cb, reply_data);
 
-       if (!strcmp(command, "chunk"))
+       if (command_len == 5 && !strncmp(command, "chunk", 5))
                return app__chunk_command(reply_cb, reply_data);
 
-       if (!strcmp(command, "slow"))
+       if (command_len == 4 && !strncmp(command, "slow", 4))
                return app__slow_command(reply_cb, reply_data);
 
-       if (starts_with(command, "sendbytes "))
-               return app__sendbytes_command(command, reply_cb, reply_data);
+       if (command_len >= 10 && starts_with(command, "sendbytes "))
+               return app__sendbytes_command(command, command_len,
+                                             reply_cb, reply_data);
 
        return app__unhandled_command(command, reply_cb, reply_data);
 }
@@ -259,185 +268,71 @@ static int daemon__run_server(void)
         */
        ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);
        if (ret == -2)
-               error(_("socket/pipe already in use: '%s'"), cl_args.path);
+               error("socket/pipe already in use: '%s'", cl_args.path);
        else if (ret == -1)
-               error_errno(_("could not start server on: '%s'"), cl_args.path);
+               error_errno("could not start server on: '%s'", cl_args.path);
 
        return ret;
 }
 
-#ifndef GIT_WINDOWS_NATIVE
-/*
- * This is adapted from `daemonize()`.  Use `fork()` to directly create and
- * run the daemon in a child process.
- */
-static int spawn_server(pid_t *pid)
-{
-       struct ipc_server_opts opts = {
-               .nr_threads = cl_args.nr_threads,
-       };
-
-       *pid = fork();
+static start_bg_wait_cb bg_wait_cb;
 
-       switch (*pid) {
-       case 0:
-               if (setsid() == -1)
-                       error_errno(_("setsid failed"));
-               close(0);
-               close(1);
-               close(2);
-               sanitize_stdfds();
+static int bg_wait_cb(const struct child_process *cp, void *cb_data)
+{
+       int s = ipc_get_active_state(cl_args.path);
 
-               return ipc_server_run(cl_args.path, &opts, test_app_cb,
-                                     (void*)&my_app_data);
+       switch (s) {
+       case IPC_STATE__LISTENING:
+               /* child is "ready" */
+               return 0;
 
-       case -1:
-               return error_errno(_("could not spawn daemon in the background"));
+       case IPC_STATE__NOT_LISTENING:
+       case IPC_STATE__PATH_NOT_FOUND:
+               /* give child more time */
+               return 1;
 
        default:
-               return 0;
+       case IPC_STATE__INVALID_PATH:
+       case IPC_STATE__OTHER_ERROR:
+               /* all the time in world won't help */
+               return -1;
        }
 }
-#else
-/*
- * Conceptually like `daemonize()` but different because Windows does not
- * have `fork(2)`.  Spawn a normal Windows child process but without the
- * limitations of `start_command()` and `finish_command()`.
- */
-static int spawn_server(pid_t *pid)
-{
-       char test_tool_exe[MAX_PATH];
-       struct strvec args = STRVEC_INIT;
-       int in, out;
-
-       GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);
-
-       in = open("/dev/null", O_RDONLY);
-       out = open("/dev/null", O_WRONLY);
-
-       strvec_push(&args, test_tool_exe);
-       strvec_push(&args, "simple-ipc");
-       strvec_push(&args, "run-daemon");
-       strvec_pushf(&args, "--name=%s", cl_args.path);
-       strvec_pushf(&args, "--threads=%d", cl_args.nr_threads);
 
-       *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
-       close(in);
-       close(out);
-
-       strvec_clear(&args);
-
-       if (*pid < 0)
-               return error(_("could not spawn daemon in the background"));
-
-       return 0;
-}
-#endif
-
-/*
- * This is adapted from `wait_or_whine()`.  Watch the child process and
- * let it get started and begin listening for requests on the socket
- * before reporting our success.
- */
-static int wait_for_server_startup(pid_t pid_child)
+static int daemon__start_server(void)
 {
-       int status;
-       pid_t pid_seen;
-       enum ipc_active_state s;
-       time_t time_limit, now;
+       struct child_process cp = CHILD_PROCESS_INIT;
+       enum start_bg_result sbgr;
 
-       time(&time_limit);
-       time_limit += cl_args.max_wait_sec;
+       strvec_push(&cp.args, "test-tool");
+       strvec_push(&cp.args, "simple-ipc");
+       strvec_push(&cp.args, "run-daemon");
+       strvec_pushf(&cp.args, "--name=%s", cl_args.path);
+       strvec_pushf(&cp.args, "--threads=%d", cl_args.nr_threads);
 
-       for (;;) {
-               pid_seen = waitpid(pid_child, &status, WNOHANG);
-
-               if (pid_seen == -1)
-                       return error_errno(_("waitpid failed"));
-
-               else if (pid_seen == 0) {
-                       /*
-                        * The child is still running (this should be
-                        * the normal case).  Try to connect to it on
-                        * the socket and see if it is ready for
-                        * business.
-                        *
-                        * If there is another daemon already running,
-                        * our child will fail to start (possibly
-                        * after a timeout on the lock), but we don't
-                        * care (who responds) if the socket is live.
-                        */
-                       s = ipc_get_active_state(cl_args.path);
-                       if (s == IPC_STATE__LISTENING)
-                               return 0;
+       cp.no_stdin = 1;
+       cp.no_stdout = 1;
+       cp.no_stderr = 1;
 
-                       time(&now);
-                       if (now > time_limit)
-                               return error(_("daemon not online yet"));
+       sbgr = start_bg_command(&cp, bg_wait_cb, NULL, cl_args.max_wait_sec);
 
-                       continue;
-               }
+       switch (sbgr) {
+       case SBGR_READY:
+               return 0;
 
-               else if (pid_seen == pid_child) {
-                       /*
-                        * The new child daemon process shutdown while
-                        * it was starting up, so it is not listening
-                        * on the socket.
-                        *
-                        * Try to ping the socket in the odd chance
-                        * that another daemon started (or was already
-                        * running) while our child was starting.
-                        *
-                        * Again, we don't care who services the socket.
-                        */
-                       s = ipc_get_active_state(cl_args.path);
-                       if (s == IPC_STATE__LISTENING)
-                               return 0;
+       default:
+       case SBGR_ERROR:
+       case SBGR_CB_ERROR:
+               return error("daemon failed to start");
 
-                       /*
-                        * We don't care about the WEXITSTATUS() nor
-                        * any of the WIF*(status) values because
-                        * `cmd__simple_ipc()` does the `!!result`
-                        * trick on all function return values.
-                        *
-                        * So it is sufficient to just report the
-                        * early shutdown as an error.
-                        */
-                       return error(_("daemon failed to start"));
-               }
+       case SBGR_TIMEOUT:
+               return error("daemon not online yet");
 
-               else
-                       return error(_("waitpid is confused"));
+       case SBGR_DIED:
+               return error("daemon terminated");
        }
 }
 
-/*
- * This process will start a simple-ipc server in a background process and
- * wait for it to become ready.  This is like `daemonize()` but gives us
- * more control and better error reporting (and makes it easier to write
- * unit tests).
- */
-static int daemon__start_server(void)
-{
-       pid_t pid_child;
-       int ret;
-
-       /*
-        * Run the actual daemon in a background process.
-        */
-       ret = spawn_server(&pid_child);
-       if (pid_child <= 0)
-               return ret;
-
-       /*
-        * Let the parent wait for the child process to get started
-        * and begin listening for requests on the socket.
-        */
-       ret = wait_for_server_startup(pid_child);
-
-       return ret;
-}
-
 /*
  * This process will run a quick probe to see if a simple-ipc server
  * is active on this path.
@@ -488,7 +383,9 @@ static int client__send_ipc(void)
        options.wait_if_busy = 1;
        options.wait_if_not_found = 0;
 
-       if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {
+       if (!ipc_client_send_command(cl_args.path, &options,
+                                    command, strlen(command),
+                                    &buf)) {
                if (buf.len) {
                        printf("%s\n", buf.buf);
                        fflush(stdout);
@@ -538,7 +435,7 @@ static int client__stop_server(void)
 
                time(&now);
                if (now > time_limit)
-                       return error(_("daemon has not shutdown yet"));
+                       return error("daemon has not shutdown yet");
        }
 }
 
@@ -556,7 +453,9 @@ static int do_sendbytes(int bytecount, char byte, const char *path,
        strbuf_addstr(&buf_send, "sendbytes ");
        strbuf_addchars(&buf_send, byte, bytecount);
 
-       if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {
+       if (!ipc_client_send_command(path, options,
+                                    buf_send.buf, buf_send.len,
+                                    &buf_resp)) {
                strbuf_rtrim(&buf_resp);
                printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf);
                fflush(stdout);
index e3f11ff5a78fc15b36b22efe05c90ebdac8920f0..dc1c14bde3741715f9dee0768f061e31ba2d7330 100644 (file)
@@ -11,15 +11,13 @@ static void die_usage(const char **argv, const char *msg)
 int cmd__submodule_nested_repo_config(int argc, const char **argv)
 {
        struct repository subrepo;
-       const struct submodule *sub;
 
        if (argc < 3)
                die_usage(argv, "Wrong number of arguments.");
 
        setup_git_directory();
 
-       sub = submodule_from_path(the_repository, null_oid(), argv[1]);
-       if (repo_submodule_init(&subrepo, the_repository, sub)) {
+       if (repo_submodule_init(&subrepo, the_repository, argv[1], null_oid())) {
                die_usage(argv, "Submodule not found.");
        }
 
index fe3f98be24f302aa8e5e9c0b5233aeaacda805d0..21d0392ddac5a2e21f8ce090d87efc6cda4e4d9a 100644 (file)
@@ -1,3 +1,6 @@
+# Helpers for scripts testing bitmap functionality; see t5310 for
+# example usage.
+
 # Compare a file containing rev-list bitmap traversal output to its non-bitmap
 # counterpart. You can't just use test_cmp for this, because the two produce
 # subtly different output:
@@ -24,3 +27,240 @@ test_bitmap_traversal () {
        test_cmp "$1.normalized" "$2.normalized" &&
        rm -f "$1.normalized" "$2.normalized"
 }
+
+# To ensure the logic for "maximal commits" is exercised, make
+# the repository a bit more complicated.
+#
+#    other                         second
+#      *                             *
+# (99 commits)                  (99 commits)
+#      *                             *
+#      |\                           /|
+#      | * octo-other  octo-second * |
+#      |/|\_________  ____________/|\|
+#      | \          \/  __________/  |
+#      |  | ________/\ /             |
+#      *  |/          * merge-right  *
+#      | _|__________/ \____________ |
+#      |/ |                         \|
+# (l1) *  * merge-left               * (r1)
+#      | / \________________________ |
+#      |/                           \|
+# (l2) *                             * (r2)
+#       \___________________________ |
+#                                   \|
+#                                    * (base)
+#
+# We only push bits down the first-parent history, which
+# makes some of these commits unimportant!
+#
+# The important part for the maximal commit algorithm is how
+# the bitmasks are extended. Assuming starting bit positions
+# for second (bit 0) and other (bit 1), the bitmasks at the
+# end should be:
+#
+#      second: 1       (maximal, selected)
+#       other: 01      (maximal, selected)
+#      (base): 11 (maximal)
+#
+# This complicated history was important for a previous
+# version of the walk that guarantees never walking a
+# commit multiple times. That goal might be important
+# again, so preserve this complicated case. For now, this
+# test will guarantee that the bitmaps are computed
+# correctly, even with the repeat calculations.
+setup_bitmap_history() {
+       test_expect_success 'setup repo with moderate-sized history' '
+               test_commit_bulk --id=file 10 &&
+               git branch -M second &&
+               git checkout -b other HEAD~5 &&
+               test_commit_bulk --id=side 10 &&
+
+               # add complicated history setup, including merges and
+               # ambiguous merge-bases
+
+               git checkout -b merge-left other~2 &&
+               git merge second~2 -m "merge-left" &&
+
+               git checkout -b merge-right second~1 &&
+               git merge other~1 -m "merge-right" &&
+
+               git checkout -b octo-second second &&
+               git merge merge-left merge-right -m "octopus-second" &&
+
+               git checkout -b octo-other other &&
+               git merge merge-left merge-right -m "octopus-other" &&
+
+               git checkout other &&
+               git merge octo-other -m "pull octopus" &&
+
+               git checkout second &&
+               git merge octo-second -m "pull octopus" &&
+
+               # Remove these branches so they are not selected
+               # as bitmap tips
+               git branch -D merge-left &&
+               git branch -D merge-right &&
+               git branch -D octo-other &&
+               git branch -D octo-second &&
+
+               # add padding to make these merges less interesting
+               # and avoid having them selected for bitmaps
+               test_commit_bulk --id=file 100 &&
+               git checkout other &&
+               test_commit_bulk --id=side 100 &&
+               git checkout second &&
+
+               bitmaptip=$(git rev-parse second) &&
+               blob=$(echo tagged-blob | git hash-object -w --stdin) &&
+               git tag tagged-blob $blob
+       '
+}
+
+rev_list_tests_head () {
+       test_expect_success "counting commits via bitmap ($state, $branch)" '
+               git rev-list --count $branch >expect &&
+               git rev-list --use-bitmap-index --count $branch >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "counting partial commits via bitmap ($state, $branch)" '
+               git rev-list --count $branch~5..$branch >expect &&
+               git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "counting commits with limit ($state, $branch)" '
+               git rev-list --count -n 1 $branch >expect &&
+               git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "counting non-linear history ($state, $branch)" '
+               git rev-list --count other...second >expect &&
+               git rev-list --use-bitmap-index --count other...second >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "counting commits with limiting ($state, $branch)" '
+               git rev-list --count $branch -- 1.t >expect &&
+               git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "counting objects via bitmap ($state, $branch)" '
+               git rev-list --count --objects $branch >expect &&
+               git rev-list --use-bitmap-index --count --objects $branch >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success "enumerate commits ($state, $branch)" '
+               git rev-list --use-bitmap-index $branch >actual &&
+               git rev-list $branch >expect &&
+               test_bitmap_traversal --no-confirm-bitmaps expect actual
+       '
+
+       test_expect_success "enumerate --objects ($state, $branch)" '
+               git rev-list --objects --use-bitmap-index $branch >actual &&
+               git rev-list --objects $branch >expect &&
+               test_bitmap_traversal expect actual
+       '
+
+       test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
+               git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
+               grep $blob actual
+       '
+}
+
+rev_list_tests () {
+       state=$1
+
+       for branch in "second" "other"
+       do
+               rev_list_tests_head
+       done
+}
+
+basic_bitmap_tests () {
+       tip="$1"
+       test_expect_success 'rev-list --test-bitmap verifies bitmaps' "
+               git rev-list --test-bitmap "${tip:-HEAD}"
+       "
+
+       rev_list_tests 'full bitmap'
+
+       test_expect_success 'clone from bitmapped repository' '
+               rm -fr clone.git &&
+               git clone --no-local --bare . clone.git &&
+               git rev-parse HEAD >expect &&
+               git --git-dir=clone.git rev-parse HEAD >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success 'partial clone from bitmapped repository' '
+               test_config uploadpack.allowfilter true &&
+               rm -fr partial-clone.git &&
+               git clone --no-local --bare --filter=blob:none . partial-clone.git &&
+               (
+                       cd partial-clone.git &&
+                       pack=$(echo objects/pack/*.pack) &&
+                       git verify-pack -v "$pack" >have &&
+                       awk "/blob/ { print \$1 }" <have >blobs &&
+                       # we expect this single blob because of the direct ref
+                       git rev-parse refs/tags/tagged-blob >expect &&
+                       test_cmp expect blobs
+               )
+       '
+
+       test_expect_success 'setup further non-bitmapped commits' '
+               test_commit_bulk --id=further 10
+       '
+
+       rev_list_tests 'partial bitmap'
+
+       test_expect_success 'fetch (partial bitmap)' '
+               git --git-dir=clone.git fetch origin second:second &&
+               git rev-parse HEAD >expect &&
+               git --git-dir=clone.git rev-parse HEAD >actual &&
+               test_cmp expect actual
+       '
+
+       test_expect_success 'enumerating progress counts pack-reused objects' '
+               count=$(git rev-list --objects --all --count) &&
+               git repack -adb &&
+
+               # check first with only reused objects; confirm that our
+               # progress showed the right number, and also that we did
+               # pack-reuse as expected.  Check only the final "done"
+               # line of the meter (there may be an arbitrary number of
+               # intermediate lines ending with CR).
+               GIT_PROGRESS_DELAY=0 \
+                       git pack-objects --all --stdout --progress \
+                       </dev/null >/dev/null 2>stderr &&
+               grep "Enumerating objects: $count, done" stderr &&
+               grep "pack-reused $count" stderr &&
+
+               # now the same but with one non-reused object
+               git commit --allow-empty -m "an extra commit object" &&
+               GIT_PROGRESS_DELAY=0 \
+                       git pack-objects --all --stdout --progress \
+                       </dev/null >/dev/null 2>stderr &&
+               grep "Enumerating objects: $((count+1)), done" stderr &&
+               grep "pack-reused $count" stderr
+       '
+}
+
+# have_delta <obj> <expected_base>
+#
+# Note that because this relies on cat-file, it might find _any_ copy of an
+# object in the repository. The caller is responsible for making sure
+# there's only one (e.g., via "repack -ad", or having just fetched a copy).
+have_delta () {
+       echo $2 >expect &&
+       echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
+       test_cmp expect actual
+}
+
+midx_checksum () {
+       test-tool read-midx --checksum "$1"
+}
index 9fc5241228e800bd237d48018989c6aed3ef942b..f99ef3e859dd433b720c789cc1f26aae1796bbb6 100644 (file)
@@ -87,6 +87,34 @@ test_lazy_prereq RFC1991 '
        echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
 '
 
+GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key"
+GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"
+GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key"
+GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key"
+GPGSSH_KEY_PASSPHRASE="super_secret"
+GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile"
+
+GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for'
+GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with'
+GPGSSH_KEY_NOT_TRUSTED="No principal matched"
+GPGSSH_BAD_SIGNATURE="Signature verification failed"
+
+test_lazy_prereq GPGSSH '
+       ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1)
+       test $? != 127 || exit 1
+       echo $ssh_version | grep -q "find-principals:missing signature file"
+       test $? = 0 || exit 1;
+       mkdir -p "${GNUPGHOME}" &&
+       chmod 0700 "${GNUPGHOME}" &&
+       ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null &&
+       echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
+       ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
+       echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
+       ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null &&
+       echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
+       ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null
+'
+
 sanitize_pgp() {
        perl -ne '
                /^-----END PGP/ and $in_pgp = 0;
index afa91e38b0e2130cc27de6a40c3498b88eb8d122..180a41fe9615c8023f063146a952f607c4d117d2 100644 (file)
@@ -81,8 +81,6 @@ PassEnv GIT_TRACE
 PassEnv GIT_CONFIG_NOSYSTEM
 PassEnv GIT_TEST_SIDEBAND_ALL
 
-SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0
-
 Alias /dumb/ www/
 Alias /auth/dumb/ www/auth/dumb/
 
@@ -117,6 +115,11 @@ Alias /auth/dumb/ www/auth/dumb/
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
        SetEnv GIT_HTTP_EXPORT_ALL
 </LocationMatch>
+<LocationMatch /smart_v0/>
+       SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+       SetEnv GIT_HTTP_EXPORT_ALL
+       SetEnv GIT_PROTOCOL
+</LocationMatch>
 ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
 ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
 ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/
diff --git a/t/lib-midx.sh b/t/lib-midx.sh
new file mode 100644 (file)
index 0000000..1261994
--- /dev/null
@@ -0,0 +1,8 @@
+# test_midx_consistent <objdir>
+test_midx_consistent () {
+       ls $1/pack/pack-*.idx | xargs -n 1 basename | sort >expect &&
+       test-tool read-midx $1 | grep ^pack-.*\.idx$ | sort >actual &&
+
+       test_cmp expect actual &&
+       git multi-pack-index --object-dir=$1 verify
+}
diff --git a/t/lib-subtest.sh b/t/lib-subtest.sh
new file mode 100644 (file)
index 0000000..56ee927
--- /dev/null
@@ -0,0 +1,95 @@
+write_sub_test_lib_test () {
+       name="$1" # stdin is the body of the test code
+       mkdir "$name" &&
+       write_script "$name/$name.sh" "$TEST_SHELL_PATH" <<-EOF &&
+       test_description='A test of test-lib.sh itself'
+
+       # Point to the t/test-lib.sh, which isn't in ../ as usual
+       . "\$TEST_DIRECTORY"/test-lib.sh
+       EOF
+       cat >>"$name/$name.sh"
+}
+
+_run_sub_test_lib_test_common () {
+       cmp_op="$1" want_code="$2" name="$3" # stdin is the body of the test code
+       shift 3
+
+       # intercept pseudo-options at the front of the argument list that we
+       # will not pass to child script
+       skip=
+       while test $# -gt 0
+       do
+               case "$1" in
+               --skip=*)
+                       skip=${1#--*=}
+                       shift
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+       done
+
+       (
+               cd "$name" &&
+
+               # Pretend we're not running under a test harness, whether we
+               # are or not. The test-lib output depends on the setting of
+               # this variable, so we need a stable setting under which to run
+               # the sub-test.
+               sane_unset HARNESS_ACTIVE &&
+
+               export TEST_DIRECTORY &&
+               # The child test re-sources GIT-BUILD-OPTIONS and may thus
+               # override the test output directory. We thus pass it as an
+               # explicit override to the child.
+               TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) &&
+               export TEST_OUTPUT_DIRECTORY_OVERRIDE &&
+               GIT_SKIP_TESTS=$skip &&
+               export GIT_SKIP_TESTS &&
+               sane_unset GIT_TEST_FAIL_PREREQS &&
+               ./"$name.sh" "$@" >out 2>err;
+               ret=$? &&
+               test "$ret" "$cmp_op" "$want_code"
+       )
+}
+
+write_and_run_sub_test_lib_test () {
+       name="$1" descr="$2" # stdin is the body of the test code
+       write_sub_test_lib_test "$@" || return 1
+       _run_sub_test_lib_test_common -eq 0 "$@"
+}
+
+write_and_run_sub_test_lib_test_err () {
+       name="$1" descr="$2" # stdin is the body of the test code
+       write_sub_test_lib_test "$@" || return 1
+       _run_sub_test_lib_test_common -eq 1 "$@"
+}
+
+run_sub_test_lib_test () {
+       _run_sub_test_lib_test_common -eq 0 "$@"
+}
+
+run_sub_test_lib_test_err () {
+       _run_sub_test_lib_test_common -eq 1 "$@"
+}
+
+_check_sub_test_lib_test_common () {
+       name="$1" &&
+       sed -e 's/^> //' -e 's/Z$//' >"$name"/expect.out &&
+       test_cmp "$name"/expect.out "$name"/out
+}
+
+check_sub_test_lib_test () {
+       name="$1" # stdin is the expected output from the test
+       _check_sub_test_lib_test_common "$name" &&
+       test_must_be_empty "$name"/err
+}
+
+check_sub_test_lib_test_err () {
+       name="$1" # stdin is the expected output from the test
+       _check_sub_test_lib_test_common "$name" &&
+       # expected error output is in descriptor 3
+       sed -e 's/^> //' -e 's/Z$//' <&3 >"$name"/expect.err &&
+       test_cmp "$name"/expect.err "$name"/err
+}
index a754970523ceaf4ff70dc9003d78a19e59d2268a..7547d2c790395a6cf67169ebfa832cc5ecfe2180 100644 (file)
@@ -27,3 +27,5 @@ numeric               sha1:0123456789012345678901234567890123456789
 numeric                sha256:0123456789012345678901234567890123456789012345678901234567890123
 deadbeef       sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef
 deadbeef       sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+deadbeef_short sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbee
+deadbeef_short sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbee
index 82c0df45533b2710d358bec3c0bfdc2f86181ca6..575d2000cc168426bea205b9ee5dc31b667ad935 100755 (executable)
@@ -17,8 +17,8 @@ sub get_times {
                my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3;
                return ($rt, $4, $5);
        # size
-       } elsif ($line =~ /^\d+$/) {
-               return $&;
+       } elsif ($line =~ /^\s*(\d+)$/) {
+               return $1;
        } else {
                die "bad input line: $line";
        }
diff --git a/t/perf/config b/t/perf/config
new file mode 100644 (file)
index 0000000..b92768b
--- /dev/null
@@ -0,0 +1,2 @@
+[gc]
+       auto = 0
diff --git a/t/perf/lib-bitmap.sh b/t/perf/lib-bitmap.sh
new file mode 100644 (file)
index 0000000..63d3bc7
--- /dev/null
@@ -0,0 +1,69 @@
+# Helper functions for testing bitmap performance; see p5310.
+
+test_full_bitmap () {
+       test_perf 'simulated clone' '
+               git pack-objects --stdout --all </dev/null >/dev/null
+       '
+
+       test_perf 'simulated fetch' '
+               have=$(git rev-list HEAD~100 -1) &&
+               {
+                       echo HEAD &&
+                       echo ^$have
+               } | git pack-objects --revs --stdout >/dev/null
+       '
+
+       test_perf 'pack to file (bitmap)' '
+               git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
+       '
+
+       test_perf 'rev-list (commits)' '
+               git rev-list --all --use-bitmap-index >/dev/null
+       '
+
+       test_perf 'rev-list (objects)' '
+               git rev-list --all --use-bitmap-index --objects >/dev/null
+       '
+
+       test_perf 'rev-list with tag negated via --not --all (objects)' '
+               git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null
+       '
+
+       test_perf 'rev-list with negative tag (objects)' '
+               git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null
+       '
+
+       test_perf 'rev-list count with blob:none' '
+               git rev-list --use-bitmap-index --count --objects --all \
+                       --filter=blob:none >/dev/null
+       '
+
+       test_perf 'rev-list count with blob:limit=1k' '
+               git rev-list --use-bitmap-index --count --objects --all \
+                       --filter=blob:limit=1k >/dev/null
+       '
+
+       test_perf 'rev-list count with tree:0' '
+               git rev-list --use-bitmap-index --count --objects --all \
+                       --filter=tree:0 >/dev/null
+       '
+
+       test_perf 'simulated partial clone' '
+               git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
+       '
+}
+
+test_partial_bitmap () {
+       test_perf 'clone (partial bitmap)' '
+               git pack-objects --stdout --all </dev/null >/dev/null
+       '
+
+       test_perf 'pack to file (partial bitmap)' '
+               git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
+       '
+
+       test_perf 'rev-list with tree filter (partial bitmap)' '
+               git rev-list --use-bitmap-index --count --objects --all \
+                       --filter=tree:0 >/dev/null
+       '
+}
index 6e924f5fa3f900b4b82a55f282e810572a62f20b..ed366e2e1295254d176941a60e1fa5128d24f02e 100755 (executable)
@@ -11,16 +11,42 @@ test_expect_success 'setup' '
        git cat-file --batch >unsorted
 '
 
-test_perf 'sort(1)' '
-       sort <unsorted >expect
+test_perf 'sort(1) unsorted' '
+       sort <unsorted >sorted
 '
 
-test_perf 'string_list_sort()' '
-       test-tool string-list sort <unsorted >actual
+test_expect_success 'reverse' '
+       sort -r <unsorted >reversed
 '
 
-test_expect_success 'string_list_sort() sorts like sort(1)' '
-       test_cmp_bin expect actual
-'
+for file in sorted reversed
+do
+       test_perf "sort(1) $file" "
+               sort <$file >actual
+       "
+done
+
+for file in unsorted sorted reversed
+do
+
+       test_perf "string_list_sort() $file" "
+               test-tool string-list sort <$file >actual
+       "
+
+       test_expect_success "string_list_sort() $file sorts like sort(1)" "
+               test_cmp_bin sorted actual
+       "
+done
+
+for file in unsorted sorted reversed
+do
+       test_perf "llist_mergesort() $file" "
+               test-tool mergesort sort <$file >actual
+       "
+
+       test_expect_success "llist_mergesort() $file sorts like sort(1)" "
+               test_cmp_bin sorted actual
+       "
+done
 
 test_done
index 7a0bb294480a6d98ec3db506dff3eafa57cb30f0..43d5a34e8cadc8c9284e6586f426de5fa9857a9f 100755 (executable)
@@ -18,7 +18,7 @@ test_expect_success 'setup rebasing on top of a lot of changes' '
                test_tick &&
                git commit -m commit$i unrelated-file$i &&
                echo change$i >unrelated-file$i &&
-               test_seq 1000 | tac >>unrelated-file$i &&
+               test_seq 1000 | sort -nr >>unrelated-file$i &&
                git add unrelated-file$i &&
                test_tick &&
                git commit -m commit$i-reverse unrelated-file$i ||
index 452be01056c6b4797958809036811984634ace7d..7ad4f237bc37ff0547bcd036cbbfc160cf723a4f 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Tests pack performance using bitmaps'
 . ./perf-lib.sh
+. "${TEST_DIRECTORY}/perf/lib-bitmap.sh"
 
 test_perf_large_repo
 
@@ -25,56 +26,7 @@ test_perf 'repack to disk' '
        git repack -ad
 '
 
-test_perf 'simulated clone' '
-       git pack-objects --stdout --all </dev/null >/dev/null
-'
-
-test_perf 'simulated fetch' '
-       have=$(git rev-list HEAD~100 -1) &&
-       {
-               echo HEAD &&
-               echo ^$have
-       } | git pack-objects --revs --stdout >/dev/null
-'
-
-test_perf 'pack to file (bitmap)' '
-       git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
-'
-
-test_perf 'rev-list (commits)' '
-       git rev-list --all --use-bitmap-index >/dev/null
-'
-
-test_perf 'rev-list (objects)' '
-       git rev-list --all --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list with tag negated via --not --all (objects)' '
-       git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list with negative tag (objects)' '
-       git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list count with blob:none' '
-       git rev-list --use-bitmap-index --count --objects --all \
-               --filter=blob:none >/dev/null
-'
-
-test_perf 'rev-list count with blob:limit=1k' '
-       git rev-list --use-bitmap-index --count --objects --all \
-               --filter=blob:limit=1k >/dev/null
-'
-
-test_perf 'rev-list count with tree:0' '
-       git rev-list --use-bitmap-index --count --objects --all \
-               --filter=tree:0 >/dev/null
-'
-
-test_perf 'simulated partial clone' '
-       git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
-'
+test_full_bitmap
 
 test_expect_success 'create partial bitmap state' '
        # pick a commit to represent the repo tip in the past
@@ -97,17 +49,6 @@ test_expect_success 'create partial bitmap state' '
        git update-ref HEAD $orig_tip
 '
 
-test_perf 'clone (partial bitmap)' '
-       git pack-objects --stdout --all </dev/null >/dev/null
-'
-
-test_perf 'pack to file (partial bitmap)' '
-       git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
-'
-
-test_perf 'rev-list with tree filter (partial bitmap)' '
-       git rev-list --use-bitmap-index --count --objects --all \
-               --filter=tree:0 >/dev/null
-'
+test_partial_bitmap
 
 test_done
diff --git a/t/perf/p5326-multi-pack-bitmaps.sh b/t/perf/p5326-multi-pack-bitmaps.sh
new file mode 100755 (executable)
index 0000000..f2fa228
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='Tests performance using midx bitmaps'
+. ./perf-lib.sh
+. "${TEST_DIRECTORY}/perf/lib-bitmap.sh"
+
+test_perf_large_repo
+
+# we need to create the tag up front such that it is covered by the repack and
+# thus by generated bitmaps.
+test_expect_success 'create tags' '
+       git tag --message="tag pointing to HEAD" perf-tag HEAD
+'
+
+test_expect_success 'start with bitmapped pack' '
+       git repack -adb
+'
+
+test_perf 'setup multi-pack index' '
+       git multi-pack-index write --bitmap
+'
+
+test_expect_success 'drop pack bitmap' '
+       rm -f .git/objects/pack/pack-*.bitmap
+'
+
+test_full_bitmap
+
+test_expect_success 'create partial bitmap state' '
+       # pick a commit to represent the repo tip in the past
+       cutoff=$(git rev-list HEAD~100 -1) &&
+       orig_tip=$(git rev-parse HEAD) &&
+
+       # now pretend we have just one tip
+       rm -rf .git/logs .git/refs/* .git/packed-refs &&
+       git update-ref HEAD $cutoff &&
+
+       # and then repack, which will leave us with a nice
+       # big bitmap pack of the "old" history, and all of
+       # the new history will be loose, as if it had been pushed
+       # up incrementally and exploded via unpack-objects
+       git repack -Ad &&
+       git multi-pack-index write --bitmap &&
+
+       # and now restore our original tip, as if the pushes
+       # had happened
+       git update-ref HEAD $orig_tip
+'
+
+test_partial_bitmap
+
+test_done
index f5ed092ee591ca3c350e274495acf1e37ce868c6..780a7402d5191f72f7b50a48da4adf665090349f 100644 (file)
@@ -27,6 +27,10 @@ TEST_NO_MALLOC_CHECK=t
 
 . ../test-lib.sh
 
+unset GIT_CONFIG_NOSYSTEM
+GIT_CONFIG_SYSTEM="$TEST_DIRECTORY/perf/config"
+export GIT_CONFIG_SYSTEM
+
 if test -n "$GIT_TEST_INSTALLED" -a -z "$PERF_SET_GIT_TEST_INSTALLED"
 then
        error "Do not use GIT_TEST_INSTALLED with the perf tests.
@@ -230,6 +234,7 @@ test_perf_ () {
                test_ok_ "$1"
        fi
        "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".result
+       rm test_time.*
 }
 
 test_perf () {
index d19dec258a28a85c8a54f16ece6b5e73fc247fc5..55219aa405638304411bc319cc29bb5eb2a39d0b 100755 (executable)
@@ -74,7 +74,7 @@ set_git_test_installed () {
        mydir=$1
 
        mydir_abs=$(cd $mydir && pwd)
-       mydir_abs_wrappers="$mydir_abs_wrappers/bin-wrappers"
+       mydir_abs_wrappers="$mydir_abs/bin-wrappers"
        if test -d "$mydir_abs_wrappers"
        then
                GIT_TEST_INSTALLED=$mydir_abs_wrappers
index 5c342de713a99b1c971760aee561f6ec5518ae63..b007f0efef26294d2aa0a0665d37d522eb60f103 100755 (executable)
@@ -19,6 +19,7 @@ modification *should* take notice and update the test vectors here.
 '
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-subtest.sh
 
 try_local_xy () {
        local x="local" y="alsolocal" &&
@@ -66,95 +67,8 @@ test_expect_success 'success is reported like this' '
        :
 '
 
-_run_sub_test_lib_test_common () {
-       neg="$1" name="$2" descr="$3" # stdin is the body of the test code
-       shift 3
-
-       # intercept pseudo-options at the front of the argument list that we
-       # will not pass to child script
-       skip=
-       while test $# -gt 0
-       do
-               case "$1" in
-               --skip=*)
-                       skip=${1#--*=}
-                       shift
-                       ;;
-               *)
-                       break
-                       ;;
-               esac
-       done
-
-       mkdir "$name" &&
-       (
-               # Pretend we're not running under a test harness, whether we
-               # are or not. The test-lib output depends on the setting of
-               # this variable, so we need a stable setting under which to run
-               # the sub-test.
-               sane_unset HARNESS_ACTIVE &&
-               cd "$name" &&
-               write_script "$name.sh" "$TEST_SHELL_PATH" <<-EOF &&
-               test_description='$descr (run in sub test-lib)
-
-               This is run in a sub test-lib so that we do not get incorrect
-               passing metrics
-               '
-
-               # Point to the t/test-lib.sh, which isn't in ../ as usual
-               . "\$TEST_DIRECTORY"/test-lib.sh
-               EOF
-               cat >>"$name.sh" &&
-               export TEST_DIRECTORY &&
-               # The child test re-sources GIT-BUILD-OPTIONS and may thus
-               # override the test output directory. We thus pass it as an
-               # explicit override to the child.
-               TEST_OUTPUT_DIRECTORY_OVERRIDE=$(pwd) &&
-               export TEST_OUTPUT_DIRECTORY_OVERRIDE &&
-               GIT_SKIP_TESTS=$skip &&
-               export GIT_SKIP_TESTS &&
-               sane_unset GIT_TEST_FAIL_PREREQS &&
-               if test -z "$neg"
-               then
-                       ./"$name.sh" "$@" >out 2>err
-               else
-                       ! ./"$name.sh" "$@" >out 2>err
-               fi
-       )
-}
-
-run_sub_test_lib_test () {
-       _run_sub_test_lib_test_common '' "$@"
-}
-
-run_sub_test_lib_test_err () {
-       _run_sub_test_lib_test_common '!' "$@"
-}
-
-check_sub_test_lib_test () {
-       name="$1" # stdin is the expected output from the test
-       (
-               cd "$name" &&
-               test_must_be_empty err &&
-               sed -e 's/^> //' -e 's/Z$//' >expect &&
-               test_cmp expect out
-       )
-}
-
-check_sub_test_lib_test_err () {
-       name="$1" # stdin is the expected output from the test
-       # expected error output is in descriptor 3
-       (
-               cd "$name" &&
-               sed -e 's/^> //' -e 's/Z$//' >expect.out &&
-               test_cmp expect.out out &&
-               sed -e 's/^> //' -e 's/Z$//' <&3 >expect.err &&
-               test_cmp expect.err err
-       )
-}
-
-test_expect_success 'pretend we have a fully passing test suite' '
-       run_sub_test_lib_test full-pass "3 passing tests" <<-\EOF &&
+test_expect_success 'subtest: 3 passing tests' '
+       write_and_run_sub_test_lib_test full-pass <<-\EOF &&
        for i in 1 2 3
        do
                test_expect_success "passing test #$i" "true"
@@ -170,9 +84,8 @@ test_expect_success 'pretend we have a fully passing test suite' '
        EOF
 '
 
-test_expect_success 'pretend we have a partially passing test suite' '
-       run_sub_test_lib_test_err \
-               partial-pass "2/3 tests passing" <<-\EOF &&
+test_expect_success 'subtest: 2/3 tests passing' '
+       write_and_run_sub_test_lib_test_err partial-pass <<-\EOF &&
        test_expect_success "passing test #1" "true"
        test_expect_success "failing test #2" "false"
        test_expect_success "passing test #3" "true"
@@ -188,8 +101,8 @@ test_expect_success 'pretend we have a partially passing test suite' '
        EOF
 '
 
-test_expect_success 'pretend we have a known breakage' '
-       run_sub_test_lib_test failing-todo "A failing TODO test" <<-\EOF &&
+test_expect_success 'subtest: a failing TODO test' '
+       write_and_run_sub_test_lib_test failing-todo <<-\EOF &&
        test_expect_success "passing test" "true"
        test_expect_failure "pretend we have a known breakage" "false"
        test_done
@@ -203,8 +116,8 @@ test_expect_success 'pretend we have a known breakage' '
        EOF
 '
 
-test_expect_success 'pretend we have fixed a known breakage' '
-       run_sub_test_lib_test passing-todo "A passing TODO test" <<-\EOF &&
+test_expect_success 'subtest: a passing TODO test' '
+       write_and_run_sub_test_lib_test passing-todo <<-\EOF &&
        test_expect_failure "pretend we have fixed a known breakage" "true"
        test_done
        EOF
@@ -215,9 +128,8 @@ test_expect_success 'pretend we have fixed a known breakage' '
        EOF
 '
 
-test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' '
-       run_sub_test_lib_test partially-passing-todos \
-               "2 TODO tests, one passing" <<-\EOF &&
+test_expect_success 'subtest: 2 TODO tests, one passin' '
+       write_and_run_sub_test_lib_test partially-passing-todos <<-\EOF &&
        test_expect_failure "pretend we have a known breakage" "false"
        test_expect_success "pretend we have a passing test" "true"
        test_expect_failure "pretend we have fixed another known breakage" "true"
@@ -234,9 +146,8 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su
        EOF
 '
 
-test_expect_success 'pretend we have a pass, fail, and known breakage' '
-       run_sub_test_lib_test_err \
-               mixed-results1 "mixed results #1" <<-\EOF &&
+test_expect_success 'subtest: mixed results: pass, failure and a TODO test' '
+       write_and_run_sub_test_lib_test_err mixed-results1 <<-\EOF &&
        test_expect_success "passing test" "true"
        test_expect_success "failing test" "false"
        test_expect_failure "pretend we have a known breakage" "false"
@@ -253,9 +164,8 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' '
        EOF
 '
 
-test_expect_success 'pretend we have a mix of all possible results' '
-       run_sub_test_lib_test_err \
-               mixed-results2 "mixed results #2" <<-\EOF &&
+test_expect_success 'subtest: mixed results: a mixture of all possible results' '
+       write_and_run_sub_test_lib_test_err mixed-results2 <<-\EOF &&
        test_expect_success "passing test" "true"
        test_expect_success "passing test" "true"
        test_expect_success "passing test" "true"
@@ -289,9 +199,8 @@ test_expect_success 'pretend we have a mix of all possible results' '
        EOF
 '
 
-test_expect_success 'test --verbose' '
-       run_sub_test_lib_test_err \
-               t1234-verbose "test verbose" --verbose <<-\EOF &&
+test_expect_success 'subtest: --verbose option' '
+       write_and_run_sub_test_lib_test_err t1234-verbose --verbose <<-\EOF &&
        test_expect_success "passing test" true
        test_expect_success "test with output" "echo foo"
        test_expect_success "failing test" false
@@ -316,19 +225,14 @@ test_expect_success 'test --verbose' '
        EOF
 '
 
-test_expect_success 'test --verbose-only' '
+test_expect_success 'subtest: --verbose-only option' '
        run_sub_test_lib_test_err \
-               t2345-verbose-only-2 "test verbose-only=2" \
-               --verbose-only=2 <<-\EOF &&
-       test_expect_success "passing test" true
-       test_expect_success "test with output" "echo foo"
-       test_expect_success "failing test" false
-       test_done
-       EOF
-       check_sub_test_lib_test t2345-verbose-only-2 <<-\EOF
+               t1234-verbose \
+               --verbose-only=2 &&
+       check_sub_test_lib_test t1234-verbose <<-\EOF
        > ok 1 - passing test
        > Z
-       > expecting success of 2345.2 '\''test with output'\'': echo foo
+       > expecting success of 1234.2 '\''test with output'\'': echo foo
        > foo
        > ok 2 - test with output
        > Z
@@ -339,18 +243,11 @@ test_expect_success 'test --verbose-only' '
        EOF
 '
 
-test_expect_success 'GIT_SKIP_TESTS' '
+test_expect_success 'subtest: skip one with GIT_SKIP_TESTS' '
        (
-               run_sub_test_lib_test git-skip-tests-basic \
-                       "GIT_SKIP_TESTS" \
-                       --skip="git.2" <<-\EOF &&
-               for i in 1 2 3
-               do
-                       test_expect_success "passing test #$i" "true"
-               done
-               test_done
-               EOF
-               check_sub_test_lib_test git-skip-tests-basic <<-\EOF
+               run_sub_test_lib_test full-pass \
+                       --skip="full.2" &&
+               check_sub_test_lib_test full-pass <<-\EOF
                > ok 1 - passing test #1
                > ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
                > ok 3 - passing test #3
@@ -360,10 +257,9 @@ test_expect_success 'GIT_SKIP_TESTS' '
        )
 '
 
-test_expect_success 'GIT_SKIP_TESTS several tests' '
+test_expect_success 'subtest: skip several with GIT_SKIP_TESTS' '
        (
-               run_sub_test_lib_test git-skip-tests-several \
-                       "GIT_SKIP_TESTS several tests" \
+               write_and_run_sub_test_lib_test git-skip-tests-several \
                        --skip="git.2 git.5" <<-\EOF &&
                for i in 1 2 3 4 5 6
                do
@@ -384,18 +280,11 @@ test_expect_success 'GIT_SKIP_TESTS several tests' '
        )
 '
 
-test_expect_success 'GIT_SKIP_TESTS sh pattern' '
+test_expect_success 'subtest: sh pattern skipping with GIT_SKIP_TESTS' '
        (
-               run_sub_test_lib_test git-skip-tests-sh-pattern \
-                       "GIT_SKIP_TESTS sh pattern" \
-                       --skip="git.[2-5]" <<-\EOF &&
-               for i in 1 2 3 4 5 6
-               do
-                       test_expect_success "passing test #$i" "true"
-               done
-               test_done
-               EOF
-               check_sub_test_lib_test git-skip-tests-sh-pattern <<-\EOF
+               run_sub_test_lib_test git-skip-tests-several \
+                       --skip="git.[2-5]" &&
+               check_sub_test_lib_test git-skip-tests-several <<-\EOF
                > ok 1 - passing test #1
                > ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
                > ok 3 # skip passing test #3 (GIT_SKIP_TESTS)
@@ -408,35 +297,23 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' '
        )
 '
 
-test_expect_success 'GIT_SKIP_TESTS entire suite' '
+test_expect_success 'subtest: skip entire test suite with GIT_SKIP_TESTS' '
        (
-               run_sub_test_lib_test git-skip-tests-entire-suite \
-                       "GIT_SKIP_TESTS entire suite" \
-                       --skip="git" <<-\EOF &&
-               for i in 1 2 3
-               do
-                       test_expect_success "passing test #$i" "true"
-               done
-               test_done
-               EOF
-               check_sub_test_lib_test git-skip-tests-entire-suite <<-\EOF
+               GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS &&
+               run_sub_test_lib_test git-skip-tests-several \
+                       --skip="git" &&
+               check_sub_test_lib_test git-skip-tests-several <<-\EOF
                > 1..0 # SKIP skip all tests in git
                EOF
        )
 '
 
-test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' '
+test_expect_success 'subtest: GIT_SKIP_TESTS does not skip unmatched suite' '
        (
-               run_sub_test_lib_test git-skip-tests-unmatched-suite \
-                       "GIT_SKIP_TESTS does not skip unmatched suite" \
-                       --skip="notgit" <<-\EOF &&
-               for i in 1 2 3
-               do
-                       test_expect_success "passing test #$i" "true"
-               done
-               test_done
-               EOF
-               check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\EOF
+               GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS &&
+               run_sub_test_lib_test full-pass \
+                       --skip="notfull" &&
+               check_sub_test_lib_test full-pass <<-\EOF
                > ok 1 - passing test #1
                > ok 2 - passing test #2
                > ok 3 - passing test #3
@@ -446,16 +323,9 @@ test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' '
        )
 '
 
-test_expect_success '--run basic' '
-       run_sub_test_lib_test run-basic \
-               "--run basic" --run="1,3,5" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-basic <<-\EOF
+test_expect_success 'subtest: --run basic' '
+       run_sub_test_lib_test git-skip-tests-several --run="1,3,5" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 # skip passing test #2 (--run)
        > ok 3 - passing test #3
@@ -467,16 +337,10 @@ test_expect_success '--run basic' '
        EOF
 '
 
-test_expect_success '--run with a range' '
-       run_sub_test_lib_test run-range \
-               "--run with a range" --run="1-3" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-range <<-\EOF
+test_expect_success 'subtest: --run with a range' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="1-3" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 - passing test #3
@@ -488,16 +352,10 @@ test_expect_success '--run with a range' '
        EOF
 '
 
-test_expect_success '--run with two ranges' '
-       run_sub_test_lib_test run-two-ranges \
-               "--run with two ranges" --run="1-2,5-6" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-two-ranges <<-\EOF
+test_expect_success 'subtest: --run with two ranges' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="1-2,5-6" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -509,16 +367,10 @@ test_expect_success '--run with two ranges' '
        EOF
 '
 
-test_expect_success '--run with a left open range' '
-       run_sub_test_lib_test run-left-open-range \
-               "--run with a left open range" --run="-3" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-left-open-range <<-\EOF
+test_expect_success 'subtest: --run with a left open range' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="-3" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 - passing test #3
@@ -530,16 +382,10 @@ test_expect_success '--run with a left open range' '
        EOF
 '
 
-test_expect_success '--run with a right open range' '
-       run_sub_test_lib_test run-right-open-range \
-               "--run with a right open range" --run="4-" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-right-open-range <<-\EOF
+test_expect_success 'subtest: --run with a right open range' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="4-" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 # skip passing test #1 (--run)
        > ok 2 # skip passing test #2 (--run)
        > ok 3 # skip passing test #3 (--run)
@@ -551,16 +397,10 @@ test_expect_success '--run with a right open range' '
        EOF
 '
 
-test_expect_success '--run with basic negation' '
-       run_sub_test_lib_test run-basic-neg \
-               "--run with basic negation" --run="!3" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-basic-neg <<-\EOF
+test_expect_success 'subtest: --run with basic negation' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="!3" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -572,16 +412,10 @@ test_expect_success '--run with basic negation' '
        EOF
 '
 
-test_expect_success '--run with two negations' '
-       run_sub_test_lib_test run-two-neg \
-               "--run with two negations" --run="!3,!6" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-two-neg <<-\EOF
+test_expect_success 'subtest: --run with two negations' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="!3,!6" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -593,16 +427,10 @@ test_expect_success '--run with two negations' '
        EOF
 '
 
-test_expect_success '--run a range and negation' '
-       run_sub_test_lib_test run-range-and-neg \
-               "--run a range and negation" --run="-4,!2" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-range-and-neg <<-\EOF
+test_expect_success 'subtest: --run a range and negation' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="-4,!2" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 # skip passing test #2 (--run)
        > ok 3 - passing test #3
@@ -614,16 +442,10 @@ test_expect_success '--run a range and negation' '
        EOF
 '
 
-test_expect_success '--run range negation' '
-       run_sub_test_lib_test run-range-neg \
-               "--run range negation" --run="!1-3" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-range-neg <<-\EOF
+test_expect_success 'subtest: --run range negation' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="!1-3" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 # skip passing test #1 (--run)
        > ok 2 # skip passing test #2 (--run)
        > ok 3 # skip passing test #3 (--run)
@@ -635,17 +457,10 @@ test_expect_success '--run range negation' '
        EOF
 '
 
-test_expect_success '--run include, exclude and include' '
-       run_sub_test_lib_test run-inc-neg-inc \
-               "--run include, exclude and include" \
-               --run="1-5,!1-3,2" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-inc-neg-inc <<-\EOF
+test_expect_success 'subtest: --run include, exclude and include' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="1-5,!1-3,2" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 # skip passing test #1 (--run)
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -657,17 +472,10 @@ test_expect_success '--run include, exclude and include' '
        EOF
 '
 
-test_expect_success '--run include, exclude and include, comma separated' '
-       run_sub_test_lib_test run-inc-neg-inc-comma \
-               "--run include, exclude and include, comma separated" \
-               --run=1-5,!1-3,2 <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-inc-neg-inc-comma <<-\EOF
+test_expect_success 'subtest: --run include, exclude and include, comma separated' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run=1-5,!1-3,2 &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 # skip passing test #1 (--run)
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -679,17 +487,10 @@ test_expect_success '--run include, exclude and include, comma separated' '
        EOF
 '
 
-test_expect_success '--run exclude and include' '
-       run_sub_test_lib_test run-neg-inc \
-               "--run exclude and include" \
-               --run="!3-,5" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-neg-inc <<-\EOF
+test_expect_success 'subtest: --run exclude and include' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="!3-,5" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 - passing test #2
        > ok 3 # skip passing test #3 (--run)
@@ -701,17 +502,10 @@ test_expect_success '--run exclude and include' '
        EOF
 '
 
-test_expect_success '--run empty selectors' '
-       run_sub_test_lib_test run-empty-sel \
-               "--run empty selectors" \
-               --run="1,,3,,,5" <<-\EOF &&
-       for i in 1 2 3 4 5 6
-       do
-               test_expect_success "passing test #$i" "true"
-       done
-       test_done
-       EOF
-       check_sub_test_lib_test run-empty-sel <<-\EOF
+test_expect_success 'subtest: --run empty selectors' '
+       run_sub_test_lib_test git-skip-tests-several \
+               --run="1,,3,,,5" &&
+       check_sub_test_lib_test git-skip-tests-several <<-\EOF
        > ok 1 - passing test #1
        > ok 2 # skip passing test #2 (--run)
        > ok 3 - passing test #3
@@ -723,9 +517,8 @@ test_expect_success '--run empty selectors' '
        EOF
 '
 
-test_expect_success '--run substring selector' '
-       run_sub_test_lib_test run-substring-selector \
-               "--run empty selectors" \
+test_expect_success 'subtest: --run substring selector' '
+       write_and_run_sub_test_lib_test run-substring-selector \
                --run="relevant" <<-\EOF &&
        test_expect_success "relevant test" "true"
        for i in 1 2 3 4 5 6
@@ -747,9 +540,8 @@ test_expect_success '--run substring selector' '
        EOF
 '
 
-test_expect_success '--run keyword selection' '
-       run_sub_test_lib_test_err run-inv-range-start \
-               "--run invalid range start" \
+test_expect_success 'subtest: --run keyword selection' '
+       write_and_run_sub_test_lib_test_err run-inv-range-start \
                --run="a-5" <<-\EOF &&
        test_expect_success "passing test #1" "true"
        test_done
@@ -762,14 +554,10 @@ test_expect_success '--run keyword selection' '
        EOF_ERR
 '
 
-test_expect_success '--run invalid range end' '
-       run_sub_test_lib_test_err run-inv-range-end \
-               "--run invalid range end" \
-               --run="1-z" <<-\EOF &&
-       test_expect_success "passing test #1" "true"
-       test_done
-       EOF
-       check_sub_test_lib_test_err run-inv-range-end \
+test_expect_success 'subtest: --run invalid range end' '
+       run_sub_test_lib_test_err run-inv-range-start \
+               --run="1-z" &&
+       check_sub_test_lib_test_err run-inv-range-start \
                <<-\EOF_OUT 3<<-EOF_ERR
        > FATAL: Unexpected exit with code 1
        EOF_OUT
@@ -777,8 +565,8 @@ test_expect_success '--run invalid range end' '
        EOF_ERR
 '
 
-test_expect_success 'tests respect prerequisites' '
-       run_sub_test_lib_test prereqs "tests respect prereqs" <<-\EOF &&
+test_expect_success 'subtest: tests respect prerequisites' '
+       write_and_run_sub_test_lib_test prereqs <<-\EOF &&
 
        test_set_prereq HAVEIT
        test_expect_success HAVEIT "prereq is satisfied" "true"
@@ -807,8 +595,8 @@ test_expect_success 'tests respect prerequisites' '
        EOF
 '
 
-test_expect_success 'tests respect lazy prerequisites' '
-       run_sub_test_lib_test lazy-prereqs "respect lazy prereqs" <<-\EOF &&
+test_expect_success 'subtest: tests respect lazy prerequisites' '
+       write_and_run_sub_test_lib_test lazy-prereqs <<-\EOF &&
 
        test_lazy_prereq LAZY_TRUE true
        test_expect_success LAZY_TRUE "lazy prereq is satisifed" "true"
@@ -831,8 +619,8 @@ test_expect_success 'tests respect lazy prerequisites' '
        EOF
 '
 
-test_expect_success 'nested lazy prerequisites' '
-       run_sub_test_lib_test nested-lazy "nested lazy prereqs" <<-\EOF &&
+test_expect_success 'subtest: nested lazy prerequisites' '
+       write_and_run_sub_test_lib_test nested-lazy <<-\EOF &&
 
        test_lazy_prereq NESTED_INNER "
                >inner &&
@@ -857,9 +645,9 @@ test_expect_success 'nested lazy prerequisites' '
        EOF
 '
 
-test_expect_success 'lazy prereqs do not turn off tracing' '
-       run_sub_test_lib_test lazy-prereq-and-tracing \
-               "lazy prereqs and -x" -v -x <<-\EOF &&
+test_expect_success 'subtest: lazy prereqs do not turn off tracing' '
+       write_and_run_sub_test_lib_test lazy-prereq-and-tracing \
+               -v -x <<-\EOF &&
        test_lazy_prereq LAZY true
 
        test_expect_success lazy "test_have_prereq LAZY && echo trace"
@@ -870,8 +658,8 @@ test_expect_success 'lazy prereqs do not turn off tracing' '
        grep "echo trace" lazy-prereq-and-tracing/err
 '
 
-test_expect_success 'tests clean up after themselves' '
-       run_sub_test_lib_test cleanup "test with cleanup" <<-\EOF &&
+test_expect_success 'subtest: tests clean up after themselves' '
+       write_and_run_sub_test_lib_test cleanup <<-\EOF &&
        clean=no
        test_expect_success "do cleanup" "
                test_when_finished clean=yes
@@ -890,9 +678,9 @@ test_expect_success 'tests clean up after themselves' '
        EOF
 '
 
-test_expect_success 'tests clean up even on failures' '
-       run_sub_test_lib_test_err \
-               failing-cleanup "Failing tests with cleanup commands" <<-\EOF &&
+test_expect_success 'subtest: tests clean up even on failures' '
+       write_and_run_sub_test_lib_test_err \
+               failing-cleanup <<-\EOF &&
        test_expect_success "tests clean up even after a failure" "
                touch clean-after-failure &&
                test_when_finished rm clean-after-failure &&
@@ -919,9 +707,9 @@ test_expect_success 'tests clean up even on failures' '
        EOF
 '
 
-test_expect_success 'test_atexit is run' '
-       run_sub_test_lib_test_err \
-               atexit-cleanup "Run atexit commands" -i <<-\EOF &&
+test_expect_success 'subtest: test_atexit is run' '
+       write_and_run_sub_test_lib_test_err \
+               atexit-cleanup -i <<-\EOF &&
        test_expect_success "tests clean up even after a failure" "
                > ../../clean-atexit &&
                test_atexit rm ../../clean-atexit &&
index e3137d638ee5bb07b9278c1a9b90a207ad024a08..2e9d652d826af230fc90166c9ef53da3f6e7aa7e 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='detect unwritable repository and fail correctly'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success setup '
@@ -18,27 +19,66 @@ test_expect_success setup '
 test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        chmod a-w .git/objects .git/objects/?? &&
-       test_must_fail git write-tree
+       test_must_fail git write-tree 2>out.write-tree
 '
 
-test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
+test_lazy_prereq WRITE_TREE_OUT 'test -e "$TRASH_DIRECTORY"/out.write-tree'
+test_expect_success WRITE_TREE_OUT 'write-tree output on unwritable repository' '
+       cat >expect <<-\EOF &&
+       error: insufficient permission for adding an object to repository database .git/objects
+       fatal: git-write-tree: error building trees
+       EOF
+       test_cmp expect out.write-tree
+'
+
+test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        chmod a-w .git/objects .git/objects/?? &&
-       test_must_fail git commit -m second
+       test_must_fail git commit -m second 2>out.commit
+'
+
+test_lazy_prereq COMMIT_OUT 'test -e "$TRASH_DIRECTORY"/out.commit'
+test_expect_success COMMIT_OUT 'commit output on unwritable repository' '
+       cat >expect <<-\EOF &&
+       error: insufficient permission for adding an object to repository database .git/objects
+       error: Error building trees
+       EOF
+       test_cmp expect out.commit
 '
 
 test_expect_success POSIXPERM,SANITY 'update-index should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        echo 6O >file &&
        chmod a-w .git/objects .git/objects/?? &&
-       test_must_fail git update-index file
+       test_must_fail git update-index file 2>out.update-index
+'
+
+test_lazy_prereq UPDATE_INDEX_OUT 'test -e "$TRASH_DIRECTORY"/out.update-index'
+test_expect_success UPDATE_INDEX_OUT 'update-index output on unwritable repository' '
+       cat >expect <<-\EOF &&
+       error: insufficient permission for adding an object to repository database .git/objects
+       error: file: failed to insert into database
+       fatal: Unable to process path file
+       EOF
+       test_cmp expect out.update-index
 '
 
 test_expect_success POSIXPERM,SANITY 'add should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        echo b >file &&
        chmod a-w .git/objects .git/objects/?? &&
-       test_must_fail git add file
+       test_must_fail git add file 2>out.add
+'
+
+test_lazy_prereq ADD_OUT 'test -e "$TRASH_DIRECTORY"/out.add'
+test_expect_success ADD_OUT 'add output on unwritable repository' '
+       cat >expect <<-\EOF &&
+       error: insufficient permission for adding an object to repository database .git/objects
+       error: file: failed to insert into database
+       error: unable to index file '\''file'\''
+       fatal: updating files failed
+       EOF
+       test_cmp expect out.add
 '
 
 test_done
index 5343ffd3f92c1637bb60719d812a01e0ab6d4064..e094975b13bf270992d9cac8999bb0972a633198 100755 (executable)
@@ -1,6 +1,8 @@
 #!/bin/sh
 
 test_description='test hashmap and string hash functions'
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_hashmap() {
index 5679e29c62479c663446e76fbc587a2648efe601..91b68c74a154a97c50f1011ebf5ec70c2ce66a33 100755 (executable)
@@ -34,6 +34,18 @@ test_expect_success 'basic help commands' '
        git help -a >/dev/null
 '
 
+test_expect_success 'invalid usage' '
+       test_expect_code 129 git help -g add &&
+       test_expect_code 129 git help -a -c &&
+
+       test_expect_code 129 git help -g add &&
+       test_expect_code 129 git help -a -g &&
+
+       test_expect_code 129 git help -g -c &&
+       test_expect_code 129 git help --config-for-completion add &&
+       test_expect_code 129 git help --config-sections-for-completion add
+'
+
 test_expect_success "works for commands and guides by default" '
        configure_help &&
        git help status &&
@@ -73,6 +85,59 @@ test_expect_success 'git help -g' '
        test_i18ngrep "^   tutorial   " help.output
 '
 
+test_expect_success 'git help fails for non-existing html pages' '
+       configure_help &&
+       mkdir html-empty &&
+       test_must_fail git -c help.htmlpath=html-empty help status &&
+       test_must_be_empty test-browser.log
+'
+
+test_expect_success 'git help succeeds without git.html' '
+       configure_help &&
+       mkdir html-with-docs &&
+       touch html-with-docs/git-status.html &&
+       git -c help.htmlpath=html-with-docs help status &&
+       echo "html-with-docs/git-status.html" >expect &&
+       test_cmp expect test-browser.log
+'
+
+test_expect_success 'git help -c' '
+       git help -c >help.output &&
+       cat >expect <<-\EOF &&
+
+       '\''git help config'\'' for more information
+       EOF
+       grep -v -E \
+               -e "^[^.]+\.[^.]+$" \
+               -e "^[^.]+\.[^.]+\.[^.]+$" \
+               help.output >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git help --config-for-completion' '
+       git help -c >human &&
+       grep -E \
+            -e "^[^.]+\.[^.]+$" \
+            -e "^[^.]+\.[^.]+\.[^.]+$" human |
+            sed -e "s/\*.*//" -e "s/<.*//" |
+            sort -u >human.munged &&
+
+       git help --config-for-completion >vars &&
+       test_cmp human.munged vars
+'
+
+test_expect_success 'git help --config-sections-for-completion' '
+       git help -c >human &&
+       grep -E \
+            -e "^[^.]+\.[^.]+$" \
+            -e "^[^.]+\.[^.]+\.[^.]+$" human |
+            sed -e "s/\..*//" |
+            sort -u >human.munged &&
+
+       git help --config-sections-for-completion >sections &&
+       test_cmp human.munged sections
+'
+
 test_expect_success 'generate builtin list' '
        git --list-cmds=builtins >builtins
 '
index 31f8276ba82bad3129c4a3cfa92f2d0ba798073c..0faef1f4f110dc0ce5006bcc9195d2a6216cd76a 100755 (executable)
@@ -1,6 +1,8 @@
 #!/bin/sh
 
 test_description='test oidmap'
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # This purposefully is very similar to t0011-hashmap.sh
index 4a159f99e44342d04f2810e2f136637c51f5e400..2e42fba9567d5b35bf72eda214d336a7c3564566 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='test env--helper'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 
index 39e5e4b34f8729e361f7cc7801dadb4db6d6c76b..c13057a4ca3be22e431771fd60d5ab0325349bdc 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Test advise_if_enabled functionality'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'advice should be printed when config variable is unset' '
index 0c24a0f9a377103f49fa7cb749a5657058da3490..ae1ca380c1aa54709a557374d08a700337759b75 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='git stripspace'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 t40='A quick brown fox jumps over the lazy do'
index ad4746d899a48b6eef46f7690d291c0ec6411d5b..da310ed29b15b320916a0eea8181e3b4c5211f64 100755 (executable)
@@ -37,7 +37,6 @@ String options
     --list <str>          add str to list
 
 Magic arguments
-    --quux                means --quux
     -NUM                  set integer to NUM
     +                     same as -b
     --ambiguous           positive ambiguity
@@ -263,10 +262,6 @@ test_expect_success 'detect possible typos' '
        test_cmp typo.err output.err
 '
 
-test_expect_success 'keep some options as arguments' '
-       test-tool parse-options --expect="arg 00: --quux" --quux
-'
-
 cat >expect <<\EOF
 Callback: "four", 0
 boolean: 5
index de4960783f071a48fd136b14f7ac1051fa3c4b77..34d1061f321fb406a5e3d6058860da957307dfab 100755 (executable)
@@ -525,4 +525,30 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
                "PRN./abc"
 '
 
+test_lazy_prereq RUNTIME_PREFIX '
+       test true = "$RUNTIME_PREFIX"
+'
+
+test_lazy_prereq CAN_EXEC_IN_PWD '
+       cp "$GIT_EXEC_PATH"/git$X ./ &&
+       ./git rev-parse
+'
+
+test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX works' '
+       mkdir -p pretend/bin pretend/libexec/git-core &&
+       echo "echo HERE" | write_script pretend/libexec/git-core/git-here &&
+       cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
+       GIT_EXEC_PATH= ./pretend/bin/git here >actual &&
+       echo HERE >expect &&
+       test_cmp expect actual'
+
+test_expect_success RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' '
+       mkdir -p pretend/bin &&
+       cp "$GIT_EXEC_PATH"/git$X pretend/bin/ &&
+       git config yes.path "%(prefix)/yes" &&
+       GIT_EXEC_PATH= ./pretend/bin/git config --path yes.path >actual &&
+       echo "$(pwd)/pretend/yes" >expect &&
+       test_cmp expect actual
+'
+
 test_done
index c6ee9f66b11d55f312ee673106ddf295c39bb50d..46d4839194bb2322b2a8cd9b80618a855e0fb070 100755 (executable)
@@ -5,6 +5,7 @@
 
 test_description='Test string list functionality'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_split () {
diff --git a/t/t0071-sort.sh b/t/t0071-sort.sh
new file mode 100755 (executable)
index 0000000..a8ab174
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+test_description='verify sort functions'
+
+. ./test-lib.sh
+
+test_expect_success 'llist_mergesort()' '
+       test-tool mergesort test
+'
+
+test_done
index 9bf66c9e68dd7b99434dfeaa19a0cc8a7618f039..906757264844b67b0c72914b3aa2a6d02e17101c 100755 (executable)
@@ -195,6 +195,7 @@ test_expect_success 'reset --hard gives cache-tree' '
 
 test_expect_success 'reset --hard without index gives cache-tree' '
        rm -f .git/index &&
+       git clean -fd &&
        git reset --hard &&
        test_cache_tree
 '
index 526304ff95b3e91e7db69f823bfc0e173b4e0764..eeedbfa919327b206a1d730069edb8cd1ed59930 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git bugreport'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 # Headers "[System Info]" will be followed by a non-empty line if we put some
index c65d1a815ea3d88fc09e88971e4126f24ce25c43..7cc4de392a051e68d438c19c0a179b2b47b3c4ae 100644 (file)
@@ -42,6 +42,12 @@ while (<>) {
        # so just omit it for testing purposes.
        # print "cmd_path _EXE_\n";
     }
+    elsif ($line =~ m/^cmd_ancestry/) {
+       # 'cmd_ancestry' is not implemented everywhere, so for portability's
+       # sake, skip it when parsing normal.
+       #
+       # print "$line";
+    }
     else {
        print "$line";
     }
index 351af7844ed95d0823bbb8c54cee06fe340e29fb..d164b750ff702c32e449d6e844f403c657871b2d 100644 (file)
@@ -44,6 +44,11 @@ while (<>) {
        # $tokens[$col_rest] = "_EXE_";
        goto SKIP_LINE;
     }
+    elsif ($tokens[$col_event] =~ m/cmd_ancestry/) {
+       # 'cmd_ancestry' is platform-specific and not implemented everywhere,
+       # so skip it.
+       goto SKIP_LINE;
+    }
     elsif ($tokens[$col_event] =~ m/child_exit/) {
        $tokens[$col_rest] =~ s/ pid:\d* / pid:_PID_ /;
     }
index 6584bb5634276e85c44beff001f8d72f09264df5..b6408560c0ca934601cda3fed8091510a3918745 100644 (file)
@@ -132,7 +132,10 @@ while (<>) {
        # just omit it for testing purposes.
        # $processes->{$sid}->{'path'} = "_EXE_";
     }
-    
+    elsif ($event eq 'cmd_ancestry') {
+       # 'cmd_ancestry' is platform-specific and not implemented everywhere, so
+       # just skip it for testing purposes.
+    }
     elsif ($event eq 'cmd_name') {
        $processes->{$sid}->{'name'} = $line->{'name'};
        $processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};
index ebd5fa5249ca276b739977739e8dea27aa6728aa..698b7159f030905fb63ce4f6856d7ecf49f50b02 100755 (executable)
@@ -9,6 +9,21 @@ test -z "$NO_UNIX_SOCKETS" || {
        test_done
 }
 
+uname_s=$(uname -s)
+case $uname_s in
+*MINGW*)
+       test_path_is_socket () {
+               # `test -S` cannot detect Win10's Unix sockets
+               test_path_exists "$1"
+       }
+       ;;
+*)
+       test_path_is_socket () {
+               test -S "$1"
+       }
+       ;;
+esac
+
 # don't leave a stale daemon running
 test_atexit 'git credential-cache exit'
 
@@ -21,7 +36,7 @@ test_expect_success 'socket defaults to ~/.cache/git/credential/socket' '
                rmdir -p .cache/git/credential/
        " &&
        test_path_is_missing "$HOME/.git-credential-cache" &&
-       test -S "$HOME/.cache/git/credential/socket"
+       test_path_is_socket "$HOME/.cache/git/credential/socket"
 '
 
 XDG_CACHE_HOME="$HOME/xdg"
@@ -31,7 +46,7 @@ helper_test cache
 
 test_expect_success "use custom XDG_CACHE_HOME if set and default sockets are not created" '
        test_when_finished "git credential-cache exit" &&
-       test -S "$XDG_CACHE_HOME/git/credential/socket" &&
+       test_path_is_socket "$XDG_CACHE_HOME/git/credential/socket" &&
        test_path_is_missing "$HOME/.git-credential-cache/socket" &&
        test_path_is_missing "$HOME/.cache/git/credential/socket"
 '
@@ -48,7 +63,7 @@ test_expect_success 'credential-cache --socket option overrides default location
        username=store-user
        password=store-pass
        EOF
-       test -S "$HOME/dir/socket"
+       test_path_is_socket "$HOME/dir/socket"
 '
 
 test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
@@ -62,7 +77,7 @@ test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
        username=store-user
        password=store-pass
        EOF
-       test -S "$HOME/.cache/git/credential/socket" &&
+       test_path_is_socket "$HOME/.cache/git/credential/socket" &&
        XDG_CACHE_HOME="$HOME/xdg" &&
        export XDG_CACHE_HOME &&
        check approve cache <<-\EOF &&
@@ -71,7 +86,7 @@ test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" '
        username=store-user
        password=store-pass
        EOF
-       test -S "$XDG_CACHE_HOME/git/credential/socket"
+       test_path_is_socket "$XDG_CACHE_HOME/git/credential/socket"
 '
 
 test_expect_success 'use user socket if user directory exists' '
@@ -79,14 +94,15 @@ test_expect_success 'use user socket if user directory exists' '
                git credential-cache exit &&
                rmdir \"\$HOME/.git-credential-cache/\"
        " &&
-       mkdir -p -m 700 "$HOME/.git-credential-cache/" &&
+       mkdir -p "$HOME/.git-credential-cache/" &&
+       chmod 700 "$HOME/.git-credential-cache/" &&
        check approve cache <<-\EOF &&
        protocol=https
        host=example.com
        username=store-user
        password=store-pass
        EOF
-       test -S "$HOME/.git-credential-cache/socket"
+       test_path_is_socket "$HOME/.git-credential-cache/socket"
 '
 
 test_expect_success SYMLINKS 'use user socket if user directory is a symlink to a directory' '
@@ -103,7 +119,7 @@ test_expect_success SYMLINKS 'use user socket if user directory is a symlink to
        username=store-user
        password=store-pass
        EOF
-       test -S "$HOME/.git-credential-cache/socket"
+       test_path_is_socket "$HOME/.git-credential-cache/socket"
 '
 
 helper_test_timeout cache --timeout=1
index a211a66c67d8e6c9c73f3feb1d40b2d75b0bf420..bba679685f68cfcd9f75ab41f48603f07315e1f9 100755 (executable)
@@ -4,6 +4,9 @@ test_description='partial clone'
 
 . ./test-lib.sh
 
+# missing promisor objects cause repacks which write bitmaps to fail
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
 delete_object () {
        rm $1/.git/objects/$(echo $2 | sed -e 's|^..|&/|')
 }
@@ -536,7 +539,13 @@ test_expect_success 'gc does not repack promisor objects if there are none' '
 repack_and_check () {
        rm -rf repo2 &&
        cp -r repo repo2 &&
-       git -C repo2 repack $1 -d &&
+       if test x"$1" = "x--must-fail"
+       then
+               shift
+               test_must_fail git -C repo2 repack $1 -d
+       else
+               git -C repo2 repack $1 -d
+       fi &&
        git -C repo2 fsck &&
 
        git -C repo2 cat-file -e $2 &&
@@ -561,6 +570,7 @@ test_expect_success 'repack -d does not irreversibly delete promisor objects' '
        printf "$THREE\n" | pack_as_from_promisor &&
        delete_object repo "$ONE" &&
 
+       repack_and_check --must-fail -ab "$TWO" "$THREE" &&
        repack_and_check -a "$TWO" "$THREE" &&
        repack_and_check -A "$TWO" "$THREE" &&
        repack_and_check -l "$TWO" "$THREE"
index 1057a96b2498d1a8abf87f21f90d108eee2c2f96..d1115528cb9461495f3ad1e934beb1cd3682f97c 100755 (executable)
@@ -20,6 +20,8 @@ In the test, these paths are used:
        rezrov  - in H, deleted in M
        yomin   - not in H or M
 '
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-read-tree.sh
 
index 18b3779ccb6014b5c268d0c29e6616f6728395af..658628375c85b17e44aa501c7624daddff4053f6 100755 (executable)
@@ -315,39 +315,238 @@ test_expect_success '%(deltabase) reports packed delta bases' '
        }
 '
 
-bogus_type="bogus"
-bogus_content="bogus"
-bogus_size=$(strlen "$bogus_content")
-bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+test_expect_success 'setup bogus data' '
+       bogus_short_type="bogus" &&
+       bogus_short_content="bogus" &&
+       bogus_short_size=$(strlen "$bogus_short_content") &&
+       bogus_short_sha1=$(echo_without_newline "$bogus_short_content" | git hash-object -t $bogus_short_type --literally -w --stdin) &&
+
+       bogus_long_type="abcdefghijklmnopqrstuvwxyz1234679" &&
+       bogus_long_content="bogus" &&
+       bogus_long_size=$(strlen "$bogus_long_content") &&
+       bogus_long_sha1=$(echo_without_newline "$bogus_long_content" | git hash-object -t $bogus_long_type --literally -w --stdin)
+'
+
+for arg1 in '' --allow-unknown-type
+do
+       for arg2 in -s -t -p
+       do
+               if test "$arg1" = "--allow-unknown-type" && test "$arg2" = "-p"
+               then
+                       continue
+               fi
+
+
+               test_expect_success "cat-file $arg1 $arg2 error on bogus short OID" '
+                       cat >expect <<-\EOF &&
+                       fatal: invalid object type
+                       EOF
+
+                       if test "$arg1" = "--allow-unknown-type"
+                       then
+                               git cat-file $arg1 $arg2 $bogus_short_sha1
+                       else
+                               test_must_fail git cat-file $arg1 $arg2 $bogus_short_sha1 >out 2>actual &&
+                               test_must_be_empty out &&
+                               test_cmp expect actual
+                       fi
+               '
+
+               test_expect_success "cat-file $arg1 $arg2 error on bogus full OID" '
+                       if test "$arg2" = "-p"
+                       then
+                               cat >expect <<-EOF
+                               error: header for $bogus_long_sha1 too long, exceeds 32 bytes
+                               fatal: Not a valid object name $bogus_long_sha1
+                               EOF
+                       else
+                               cat >expect <<-EOF
+                               error: header for $bogus_long_sha1 too long, exceeds 32 bytes
+                               fatal: git cat-file: could not get object info
+                               EOF
+                       fi &&
+
+                       if test "$arg1" = "--allow-unknown-type"
+                       then
+                               git cat-file $arg1 $arg2 $bogus_short_sha1
+                       else
+                               test_must_fail git cat-file $arg1 $arg2 $bogus_long_sha1 >out 2>actual &&
+                               test_must_be_empty out &&
+                               test_cmp expect actual
+                       fi
+               '
+
+               test_expect_success "cat-file $arg1 $arg2 error on missing short OID" '
+                       cat >expect.err <<-EOF &&
+                       fatal: Not a valid object name $(test_oid deadbeef_short)
+                       EOF
+                       test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef_short) >out 2>err.actual &&
+                       test_must_be_empty out
+               '
+
+               test_expect_success "cat-file $arg1 $arg2 error on missing full OID" '
+                       if test "$arg2" = "-p"
+                       then
+                               cat >expect.err <<-EOF
+                               fatal: Not a valid object name $(test_oid deadbeef)
+                               EOF
+                       else
+                               cat >expect.err <<-\EOF
+                               fatal: git cat-file: could not get object info
+                               EOF
+                       fi &&
+                       test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef) >out 2>err.actual &&
+                       test_must_be_empty out &&
+                       test_cmp expect.err err.actual
+               '
+       done
+done
+
+test_expect_success '-e is OK with a broken object without --allow-unknown-type' '
+       git cat-file -e $bogus_short_sha1
+'
+
+test_expect_success '-e can not be combined with --allow-unknown-type' '
+       test_expect_code 128 git cat-file -e --allow-unknown-type $bogus_short_sha1
+'
+
+test_expect_success '-p cannot print a broken object even with --allow-unknown-type' '
+       test_must_fail git cat-file -p $bogus_short_sha1 &&
+       test_expect_code 128 git cat-file -p --allow-unknown-type $bogus_short_sha1
+'
+
+test_expect_success '<type> <hash> does not work with objects of broken types' '
+       cat >err.expect <<-\EOF &&
+       fatal: invalid object type "bogus"
+       EOF
+       test_must_fail git cat-file $bogus_short_type $bogus_short_sha1 2>err.actual &&
+       test_cmp err.expect err.actual
+'
+
+test_expect_success 'broken types combined with --batch and --batch-check' '
+       echo $bogus_short_sha1 >bogus-oid &&
+
+       cat >err.expect <<-\EOF &&
+       fatal: invalid object type
+       EOF
+
+       test_must_fail git cat-file --batch <bogus-oid 2>err.actual &&
+       test_cmp err.expect err.actual &&
+
+       test_must_fail git cat-file --batch-check <bogus-oid 2>err.actual &&
+       test_cmp err.expect err.actual
+'
+
+test_expect_success 'the --batch and --batch-check options do not combine with --allow-unknown-type' '
+       test_expect_code 128 git cat-file --batch --allow-unknown-type <bogus-oid &&
+       test_expect_code 128 git cat-file --batch-check --allow-unknown-type <bogus-oid
+'
+
+test_expect_success 'the --allow-unknown-type option does not consider replacement refs' '
+       cat >expect <<-EOF &&
+       $bogus_short_type
+       EOF
+       git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual &&
+       test_cmp expect actual &&
+
+       # Create it manually, as "git replace" will die on bogus
+       # types.
+       head=$(git rev-parse --verify HEAD) &&
+       test_when_finished "rm -rf .git/refs/replace" &&
+       mkdir -p .git/refs/replace &&
+       echo $head >.git/refs/replace/$bogus_short_sha1 &&
+
+       cat >expect <<-EOF &&
+       commit
+       EOF
+       git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual &&
+       test_cmp expect actual
+'
 
 test_expect_success "Type of broken object is correct" '
-       echo $bogus_type >expect &&
-       git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+       echo $bogus_short_type >expect &&
+       git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual &&
        test_cmp expect actual
 '
 
 test_expect_success "Size of broken object is correct" '
-       echo $bogus_size >expect &&
-       git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+       echo $bogus_short_size >expect &&
+       git cat-file -s --allow-unknown-type $bogus_short_sha1 >actual &&
        test_cmp expect actual
 '
-bogus_type="abcdefghijklmnopqrstuvwxyz1234679"
-bogus_content="bogus"
-bogus_size=$(strlen "$bogus_content")
-bogus_sha1=$(echo_without_newline "$bogus_content" | git hash-object -t $bogus_type --literally -w --stdin)
+
+test_expect_success 'clean up broken object' '
+       rm .git/objects/$(test_oid_to_path $bogus_short_sha1)
+'
 
 test_expect_success "Type of broken object is correct when type is large" '
-       echo $bogus_type >expect &&
-       git cat-file -t --allow-unknown-type $bogus_sha1 >actual &&
+       echo $bogus_long_type >expect &&
+       git cat-file -t --allow-unknown-type $bogus_long_sha1 >actual &&
        test_cmp expect actual
 '
 
 test_expect_success "Size of large broken object is correct when type is large" '
-       echo $bogus_size >expect &&
-       git cat-file -s --allow-unknown-type $bogus_sha1 >actual &&
+       echo $bogus_long_size >expect &&
+       git cat-file -s --allow-unknown-type $bogus_long_sha1 >actual &&
        test_cmp expect actual
 '
 
+test_expect_success 'clean up broken object' '
+       rm .git/objects/$(test_oid_to_path $bogus_long_sha1)
+'
+
+test_expect_success 'cat-file -t and -s on corrupt loose object' '
+       git init --bare corrupt-loose.git &&
+       (
+               cd corrupt-loose.git &&
+
+               # Setup and create the empty blob and its path
+               empty_path=$(git rev-parse --git-path objects/$(test_oid_to_path "$EMPTY_BLOB")) &&
+               git hash-object -w --stdin </dev/null &&
+
+               # Create another blob and its path
+               echo other >other.blob &&
+               other_blob=$(git hash-object -w --stdin <other.blob) &&
+               other_path=$(git rev-parse --git-path objects/$(test_oid_to_path "$other_blob")) &&
+
+               # Before the swap the size is 0
+               cat >out.expect <<-EOF &&
+               0
+               EOF
+               git cat-file -s "$EMPTY_BLOB" >out.actual 2>err.actual &&
+               test_must_be_empty err.actual &&
+               test_cmp out.expect out.actual &&
+
+               # Swap the two to corrupt the repository
+               mv -f "$other_path" "$empty_path" &&
+               test_must_fail git fsck 2>err.fsck &&
+               grep "hash-path mismatch" err.fsck &&
+
+               # confirm that cat-file is reading the new swapped-in
+               # blob...
+               cat >out.expect <<-EOF &&
+               blob
+               EOF
+               git cat-file -t "$EMPTY_BLOB" >out.actual 2>err.actual &&
+               test_must_be_empty err.actual &&
+               test_cmp out.expect out.actual &&
+
+               # ... since it has a different size now.
+               cat >out.expect <<-EOF &&
+               6
+               EOF
+               git cat-file -s "$EMPTY_BLOB" >out.actual 2>err.actual &&
+               test_must_be_empty err.actual &&
+               test_cmp out.expect out.actual &&
+
+               # So far "cat-file" has been happy to spew the found
+               # content out as-is. Try to make it zlib-invalid.
+               mv -f other.blob "$empty_path" &&
+               test_must_fail git fsck 2>err.fsck &&
+               grep "^error: inflate: data stream error (" err.fsck
+       )
+'
+
 # Tests for git cat-file --follow-symlinks
 test_expect_success 'prep for symlink tests' '
        echo_without_newline "$hello_content" >morx &&
@@ -608,4 +807,70 @@ test_expect_success 'cat-file --batch="batman" with --batch-all-objects will wor
        cmp expect actual
 '
 
+test_expect_success 'set up replacement object' '
+       orig=$(git rev-parse HEAD) &&
+       git cat-file commit $orig >orig &&
+       {
+               cat orig &&
+               echo extra
+       } >fake &&
+       fake=$(git hash-object -t commit -w fake) &&
+       orig_size=$(git cat-file -s $orig) &&
+       fake_size=$(git cat-file -s $fake) &&
+       git replace $orig $fake
+'
+
+test_expect_success 'cat-file --batch respects replace objects' '
+       git cat-file --batch >actual <<-EOF &&
+       $orig
+       EOF
+       {
+               echo "$orig commit $fake_size" &&
+               cat fake &&
+               echo
+       } >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cat-file --batch-check respects replace objects' '
+       git cat-file --batch-check >actual <<-EOF &&
+       $orig
+       EOF
+       echo "$orig commit $fake_size" >expect &&
+       test_cmp expect actual
+'
+
+# Pull the entry for object with oid "$1" out of the output of
+# "cat-file --batch", including its object content (which requires
+# parsing and reading a set amount of bytes, hence perl).
+extract_batch_output () {
+    perl -ne '
+       BEGIN { $oid = shift }
+       if (/^$oid \S+ (\d+)$/) {
+           print;
+           read STDIN, my $buf, $1;
+           print $buf;
+           print "\n";
+       }
+    ' "$@"
+}
+
+test_expect_success 'cat-file --batch-all-objects --batch ignores replace' '
+       git cat-file --batch-all-objects --batch >actual.raw &&
+       extract_batch_output $orig <actual.raw >actual &&
+       {
+               echo "$orig commit $orig_size" &&
+               cat orig &&
+               echo
+       } >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'cat-file --batch-all-objects --batch-check ignores replace' '
+       git cat-file --batch-all-objects --batch-check >actual.raw &&
+       grep ^$orig actual.raw >actual &&
+       echo "$orig commit $orig_size" >expect &&
+       test_cmp expect actual
+'
+
 test_done
index b6df7444c05e06caf3eb6fa491932f1e837112f7..bfc90d4cf272e8a8d6b6d517787bed0f074f4e82 100755 (executable)
@@ -6,7 +6,6 @@ test_description='read-tree can handle submodules'
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
-KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
 test_submodule_switch_recursing_with_args "read-tree -u -m"
 
index 38fc8340f5c9b7bfd2ede379545af809c2e9e636..272ba1b566b3eaf43798f9ecfb1a77c26db3f59b 100755 (executable)
@@ -206,16 +206,21 @@ test_expect_success 'sparse-checkout disable' '
 '
 
 test_expect_success 'sparse-index enabled and disabled' '
-       git -C repo sparse-checkout init --cone --sparse-index &&
-       test_cmp_config -C repo true index.sparse &&
-       test-tool -C repo read-cache --table >cache &&
-       grep " tree " cache &&
-
-       git -C repo sparse-checkout disable &&
-       test-tool -C repo read-cache --table >cache &&
-       ! grep " tree " cache &&
-       git -C repo config --list >config &&
-       ! grep index.sparse config
+       (
+               sane_unset GIT_TEST_SPLIT_INDEX &&
+               git -C repo update-index --no-split-index &&
+
+               git -C repo sparse-checkout init --cone --sparse-index &&
+               test_cmp_config -C repo true index.sparse &&
+               test-tool -C repo read-cache --table >cache &&
+               grep " tree " cache &&
+
+               git -C repo sparse-checkout disable &&
+               test-tool -C repo read-cache --table >cache &&
+               ! grep " tree " cache &&
+               git -C repo config --list >config &&
+               ! grep index.sparse config
+       )
 '
 
 test_expect_success 'cone mode: init and set' '
@@ -406,7 +411,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat
        git -C unmerged sparse-checkout disable
 '
 
-test_expect_success 'sparse-checkout reapply' '
+test_expect_failure 'sparse-checkout reapply' '
        git clone repo tweak &&
 
        echo dirty >tweak/deep/deeper2/a &&
@@ -438,6 +443,8 @@ test_expect_success 'sparse-checkout reapply' '
        test_i18ngrep "warning.*The following paths are unmerged" err &&
        test_path_is_file tweak/folder1/a &&
 
+       # NEEDSWORK: We are asking to update a file outside of the
+       # sparse-checkout cone, but this is no longer allowed.
        git -C tweak add folder1/a &&
        git -C tweak sparse-checkout reapply 2>err &&
        test_must_be_empty err &&
@@ -642,4 +649,63 @@ test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
        check_files repo/deep a deeper1
 '
 
+test_expect_success 'cone mode clears ignored subdirectories' '
+       rm repo/.git/info/sparse-checkout &&
+
+       git -C repo sparse-checkout init --cone &&
+       git -C repo sparse-checkout set deep/deeper1 &&
+
+       cat >repo/.gitignore <<-\EOF &&
+       obj/
+       *.o
+       EOF
+
+       git -C repo add .gitignore &&
+       git -C repo commit -m ".gitignore" &&
+
+       mkdir -p repo/obj repo/folder1/obj repo/deep/deeper2/obj &&
+       for file in folder1/obj/a obj/a folder1/file.o folder1.o \
+                   deep/deeper2/obj/a deep/deeper2/file.o file.o
+       do
+               echo ignored >repo/$file || return 1
+       done &&
+
+       git -C repo status --porcelain=v2 >out &&
+       test_must_be_empty out &&
+
+       git -C repo sparse-checkout reapply &&
+       test_path_is_missing repo/folder1 &&
+       test_path_is_missing repo/deep/deeper2 &&
+       test_path_is_dir repo/obj &&
+       test_path_is_file repo/file.o &&
+
+       git -C repo status --porcelain=v2 >out &&
+       test_must_be_empty out &&
+
+       git -C repo sparse-checkout set deep/deeper2 &&
+       test_path_is_missing repo/deep/deeper1 &&
+       test_path_is_dir repo/deep/deeper2 &&
+       test_path_is_dir repo/obj &&
+       test_path_is_file repo/file.o &&
+
+       >repo/deep/deeper2/ignored.o &&
+       >repo/deep/deeper2/untracked &&
+
+       # When an untracked file is in the way, all untracked files
+       # (even ignored files) are preserved.
+       git -C repo sparse-checkout set folder1 2>err &&
+       grep "contains untracked files" err &&
+       test_path_is_file repo/deep/deeper2/ignored.o &&
+       test_path_is_file repo/deep/deeper2/untracked &&
+
+       # The rest of the cone matches expectation
+       test_path_is_missing repo/deep/deeper1 &&
+       test_path_is_dir repo/obj &&
+       test_path_is_file repo/file.o &&
+
+       git -C repo status --porcelain=v2 >out &&
+       echo "? deep/deeper2/untracked" >expect &&
+       test_cmp expect out
+'
+
 test_done
index 91e30d6ec2248a88b58752d30cb2f72f79ee92c9..16fbd2c6db9db18456b6a02d443b2b518aafc9e0 100755 (executable)
@@ -47,7 +47,7 @@ test_expect_success 'setup' '
                git checkout -b base &&
                for dir in folder1 folder2 deep
                do
-                       git checkout -b update-$dir &&
+                       git checkout -b update-$dir base &&
                        echo "updated $dir" >$dir/a &&
                        git commit -a -m "update $dir" || return 1
                done &&
@@ -114,6 +114,16 @@ test_expect_success 'setup' '
                git add . &&
                git commit -m "file to dir" &&
 
+               for side in left right
+               do
+                       git checkout -b merge-$side base &&
+                       echo $side >>deep/deeper2/a &&
+                       echo $side >>folder1/a &&
+                       echo $side >>folder2/a &&
+                       git add . &&
+                       git commit -m "$side" || return 1
+               done &&
+
                git checkout -b deepest base &&
                echo "updated deepest" >deep/deeper1/deepest/a &&
                git commit -a -m "update deepest" &&
@@ -177,6 +187,16 @@ test_sparse_match () {
        test_cmp sparse-checkout-err sparse-index-err
 }
 
+test_sparse_unstaged () {
+       file=$1 &&
+       for repo in sparse-checkout sparse-index
+       do
+               # Skip "unmerged" paths
+               git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
+               test_must_be_empty diff || return 1
+       done
+}
+
 test_expect_success 'sparse-index contents' '
        init_repos &&
 
@@ -281,6 +301,20 @@ test_expect_success 'add, commit, checkout' '
        test_all_match git checkout -
 '
 
+test_expect_success 'add outside sparse cone' '
+       init_repos &&
+
+       run_on_sparse mkdir folder1 &&
+       run_on_sparse ../edit-contents folder1/a &&
+       run_on_sparse ../edit-contents folder1/newfile &&
+       test_sparse_match test_must_fail git add folder1/a &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/a &&
+       test_sparse_match test_must_fail git add folder1/newfile &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/newfile
+'
+
 test_expect_success 'commit including unstaged changes' '
        init_repos &&
 
@@ -312,9 +346,6 @@ test_expect_success 'commit including unstaged changes' '
 test_expect_success 'status/add: outside sparse cone' '
        init_repos &&
 
-       # adding a "missing" file outside the cone should fail
-       test_sparse_match test_must_fail git add folder1/a &&
-
        # folder1 is at HEAD, but outside the sparse cone
        run_on_sparse mkdir folder1 &&
        cp initial-repo/folder1/a sparse-checkout/folder1/a &&
@@ -330,21 +361,29 @@ test_expect_success 'status/add: outside sparse cone' '
 
        test_sparse_match git status --porcelain=v2 &&
 
-       # This "git add folder1/a" fails with a warning
-       # in the sparse repos, differing from the full
-       # repo. This is intentional.
+       # Adding the path outside of the sparse-checkout cone should fail.
        test_sparse_match test_must_fail git add folder1/a &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/a &&
        test_sparse_match test_must_fail git add --refresh folder1/a &&
-       test_all_match git status --porcelain=v2 &&
-
-       test_all_match git add . &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/a &&
+       test_sparse_match test_must_fail git add folder1/new &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/new &&
+       test_sparse_match git add --sparse folder1/a &&
+       test_sparse_match git add --sparse folder1/new &&
+
+       test_all_match git add --sparse . &&
        test_all_match git status --porcelain=v2 &&
        test_all_match git commit -m folder1/new &&
+       test_all_match git rev-parse HEAD^{tree} &&
 
        run_on_all ../edit-contents folder1/newer &&
-       test_all_match git add folder1/ &&
+       test_all_match git add --sparse folder1/ &&
        test_all_match git status --porcelain=v2 &&
-       test_all_match git commit -m folder1/newer
+       test_all_match git commit -m folder1/newer &&
+       test_all_match git rev-parse HEAD^{tree}
 '
 
 test_expect_success 'checkout and reset --hard' '
@@ -472,16 +511,97 @@ test_expect_success 'checkout and reset (mixed) [sparse]' '
        test_sparse_match git reset update-folder2
 '
 
-test_expect_success 'merge' '
+test_expect_success 'merge, cherry-pick, and rebase' '
        init_repos &&
 
-       test_all_match git checkout -b merge update-deep &&
-       test_all_match git merge -m "folder1" update-folder1 &&
-       test_all_match git rev-parse HEAD^{tree} &&
-       test_all_match git merge -m "folder2" update-folder2 &&
+       for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
+       do
+               test_all_match git checkout -B temp update-deep &&
+               test_all_match git $OPERATION update-folder1 &&
+               test_all_match git rev-parse HEAD^{tree} &&
+               test_all_match git $OPERATION update-folder2 &&
+               test_all_match git rev-parse HEAD^{tree} || return 1
+       done
+'
+
+test_expect_success 'merge with conflict outside cone' '
+       init_repos &&
+
+       test_all_match git checkout -b merge-tip merge-left &&
+       test_all_match git status --porcelain=v2 &&
+       test_all_match test_must_fail git merge -m merge merge-right &&
+       test_all_match git status --porcelain=v2 &&
+
+       # Resolve the conflict in different ways:
+       # 1. Revert to the base
+       test_all_match git checkout base -- deep/deeper2/a &&
+       test_all_match git status --porcelain=v2 &&
+
+       # 2. Add the file with conflict markers
+       test_sparse_match test_must_fail git add folder1/a &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder1/a &&
+       test_all_match git add --sparse folder1/a &&
+       test_all_match git status --porcelain=v2 &&
+
+       # 3. Rename the file to another sparse filename and
+       #    accept conflict markers as resolved content.
+       run_on_all mv folder2/a folder2/z &&
+       test_sparse_match test_must_fail git add folder2 &&
+       grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+       test_sparse_unstaged folder2/z &&
+       test_all_match git add --sparse folder2 &&
+       test_all_match git status --porcelain=v2 &&
+
+       test_all_match git merge --continue &&
+       test_all_match git status --porcelain=v2 &&
        test_all_match git rev-parse HEAD^{tree}
 '
 
+test_expect_success 'cherry-pick/rebase with conflict outside cone' '
+       init_repos &&
+
+       for OPERATION in cherry-pick rebase
+       do
+               test_all_match git checkout -B tip &&
+               test_all_match git reset --hard merge-left &&
+               test_all_match git status --porcelain=v2 &&
+               test_all_match test_must_fail git $OPERATION merge-right &&
+               test_all_match git status --porcelain=v2 &&
+
+               # Resolve the conflict in different ways:
+               # 1. Revert to the base
+               test_all_match git checkout base -- deep/deeper2/a &&
+               test_all_match git status --porcelain=v2 &&
+
+               # 2. Add the file with conflict markers
+               # NEEDSWORK: Even though the merge conflict removed the
+               # SKIP_WORKTREE bit from the index entry for folder1/a, we should
+               # warn that this is a problematic add.
+               test_sparse_match test_must_fail git add folder1/a &&
+               grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+               test_sparse_unstaged folder1/a &&
+               test_all_match git add --sparse folder1/a &&
+               test_all_match git status --porcelain=v2 &&
+
+               # 3. Rename the file to another sparse filename and
+               #    accept conflict markers as resolved content.
+               # NEEDSWORK: This mode now fails, because folder2/z is
+               # outside of the sparse-checkout cone and does not match an
+               # existing index entry with the SKIP_WORKTREE bit cleared.
+               run_on_all mv folder2/a folder2/z &&
+               test_sparse_match test_must_fail git add folder2 &&
+               grep "Disable or modify the sparsity rules" sparse-checkout-err &&
+               test_sparse_unstaged folder2/z &&
+               test_all_match git add --sparse folder2 &&
+               test_all_match git status --porcelain=v2 &&
+
+               test_all_match git $OPERATION --continue &&
+               test_all_match git status --porcelain=v2 &&
+               test_all_match git rev-parse HEAD^{tree} || return 1
+       done
+'
+
 test_expect_success 'merge with outside renames' '
        init_repos &&
 
@@ -549,6 +669,7 @@ test_expect_success 'clean' '
 test_expect_success 'submodule handling' '
        init_repos &&
 
+       test_sparse_match git sparse-checkout add modules &&
        test_all_match mkdir modules &&
        test_all_match touch modules/a &&
        test_all_match git add modules &&
@@ -558,6 +679,7 @@ test_expect_success 'submodule handling' '
        test_all_match git commit -m "add submodule" &&
 
        # having a submodule prevents "modules" from collapse
+       test_sparse_match git sparse-checkout set deep/deeper1 &&
        test-tool -C sparse-index read-cache --table >cache &&
        grep "100644 blob .*    modules/a" cache &&
        grep "160000 commit $(git -C initial-repo rev-parse HEAD)       modules/sub" cache
@@ -575,8 +697,17 @@ test_expect_success 'sparse-index is expanded and converted back' '
 ensure_not_expanded () {
        rm -f trace2.txt &&
        echo >>sparse-index/untracked.txt &&
-       GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
-               git -C sparse-index "$@" &&
+
+       if test "$1" = "!"
+       then
+               shift &&
+               test_must_fail env \
+                       GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+                       git -C sparse-index "$@" || return 1
+       else
+               GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
+                       git -C sparse-index "$@" || return 1
+       fi &&
        test_region ! index ensure_full_index trace2.txt
 }
 
@@ -598,7 +729,42 @@ test_expect_success 'sparse-index is not expanded' '
        git -C sparse-index reset --hard &&
        ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
        git -C sparse-index reset --hard &&
-       ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1
+       ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
+
+       echo >>sparse-index/README.md &&
+       ensure_not_expanded add -A &&
+       echo >>sparse-index/extra.txt &&
+       ensure_not_expanded add extra.txt &&
+       echo >>sparse-index/untracked.txt &&
+       ensure_not_expanded add . &&
+
+       ensure_not_expanded checkout -f update-deep &&
+       test_config -C sparse-index pull.twohead ort &&
+       (
+               sane_unset GIT_TEST_MERGE_ALGORITHM &&
+               for OPERATION in "merge -m merge" cherry-pick rebase
+               do
+                       ensure_not_expanded merge -m merge update-folder1 &&
+                       ensure_not_expanded merge -m merge update-folder2 || return 1
+               done
+       )
+'
+
+test_expect_success 'sparse-index is not expanded: merge conflict in cone' '
+       init_repos &&
+
+       for side in right left
+       do
+               git -C sparse-index checkout -b expand-$side base &&
+               echo $side >sparse-index/deep/a &&
+               git -C sparse-index commit -a -m "$side" || return 1
+       done &&
+
+       (
+               sane_unset GIT_TEST_MERGE_ALGORITHM &&
+               git -C sparse-index config pull.twohead ort &&
+               ensure_not_expanded ! merge -m merged expand-right
+       )
 '
 
 # NEEDSWORK: a sparse-checkout behaves differently from a full checkout
index a237d9880ead84fac4e36c1a34082452d06f0fff..49718b7ea7fe7df85b6e2bd4c9833ee443825632 100755 (executable)
@@ -9,12 +9,18 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 RUN="test-tool ref-store main"
 
-test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
-       test_commit one &&
+
+test_expect_success 'setup' '
+       test_commit one
+'
+
+test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
        N=`find .git/refs -type f | wc -l` &&
        test "$N" != 0 &&
-       $RUN pack-refs 3 &&
-       N=`find .git/refs -type f | wc -l`
+       ALL_OR_PRUNE_FLAG=3 &&
+       $RUN pack-refs ${ALL_OR_PRUNE_FLAG} &&
+       N=`find .git/refs -type f` &&
+       test -z "$N"
 '
 
 test_expect_success 'create_symref(FOO, refs/heads/main)' '
@@ -98,12 +104,12 @@ test_expect_success 'reflog_exists(HEAD)' '
 
 test_expect_success 'delete_reflog(HEAD)' '
        $RUN delete-reflog HEAD &&
-       ! test -f .git/logs/HEAD
+       test_must_fail git reflog exists HEAD
 '
 
 test_expect_success 'create-reflog(HEAD)' '
        $RUN create-reflog HEAD 1 &&
-       test -f .git/logs/HEAD
+       git reflog exists HEAD
 '
 
 test_expect_success 'delete_ref(refs/heads/foo)' '
index 27b9080251a91b2f7031f3ccb6f9bafcbb9c5d22..d42f067ff8ca0feefcb7cc28a6fc549bd31827bd 100755 (executable)
@@ -374,7 +374,9 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
        test_line_count = 3 actual
 '
 
-test_expect_success 'reflog expire operates on symref not referrent' '
+# This test takes a lock on an individual ref; this is not supported in
+# reftable.
+test_expect_success REFFILES 'reflog expire operates on symref not referrent' '
        git branch --create-reflog the_symref &&
        git branch --create-reflog referrent &&
        git update-ref referrent HEAD &&
index b1839e08771d4162db11f2b6b6eabd33da10b275..fa3aeb80f2c8146b7df23daaf5d81d3015267935 100755 (executable)
@@ -170,7 +170,7 @@ test_expect_success 'for-each-ref emits warnings for broken names' '
        ! grep -e "badname" output &&
        ! grep -e "broken\.\.\.symref" output &&
        test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
-       test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
+       test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
        test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error
 '
 
index 5071ac63a5b51b89c973456211ce3aaac3587553..6337236fd8226b1902f11ebf54ff9e6d02f537e2 100755 (executable)
@@ -48,24 +48,70 @@ remove_object () {
        rm "$(sha1_file "$1")"
 }
 
-test_expect_success 'object with bad sha1' '
-       sha=$(echo blob | git hash-object -w --stdin) &&
-       old=$(test_oid_to_path "$sha") &&
-       new=$(dirname $old)/$(test_oid ff_2) &&
-       sha="$(dirname $new)$(basename $new)" &&
-       mv .git/objects/$old .git/objects/$new &&
-       test_when_finished "remove_object $sha" &&
-       git update-index --add --cacheinfo 100644 $sha foo &&
-       test_when_finished "git read-tree -u --reset HEAD" &&
-       tree=$(git write-tree) &&
-       test_when_finished "remove_object $tree" &&
-       cmt=$(echo bogus | git commit-tree $tree) &&
-       test_when_finished "remove_object $cmt" &&
-       git update-ref refs/heads/bogus $cmt &&
-       test_when_finished "git update-ref -d refs/heads/bogus" &&
+test_expect_success 'object with hash mismatch' '
+       git init --bare hash-mismatch &&
+       (
+               cd hash-mismatch &&
 
-       test_must_fail git fsck 2>out &&
-       test_i18ngrep "$sha.*corrupt" out
+               oid=$(echo blob | git hash-object -w --stdin) &&
+               oldoid=$oid &&
+               old=$(test_oid_to_path "$oid") &&
+               new=$(dirname $old)/$(test_oid ff_2) &&
+               oid="$(dirname $new)$(basename $new)" &&
+
+               mv objects/$old objects/$new &&
+               git update-index --add --cacheinfo 100644 $oid foo &&
+               tree=$(git write-tree) &&
+               cmt=$(echo bogus | git commit-tree $tree) &&
+               git update-ref refs/heads/bogus $cmt &&
+
+               test_must_fail git fsck 2>out &&
+               grep "$oldoid: hash-path mismatch, found at: .*$new" out
+       )
+'
+
+test_expect_success 'object with hash and type mismatch' '
+       git init --bare hash-type-mismatch &&
+       (
+               cd hash-type-mismatch &&
+
+               oid=$(echo blob | git hash-object -w --stdin -t garbage --literally) &&
+               oldoid=$oid &&
+               old=$(test_oid_to_path "$oid") &&
+               new=$(dirname $old)/$(test_oid ff_2) &&
+               oid="$(dirname $new)$(basename $new)" &&
+
+               mv objects/$old objects/$new &&
+               git update-index --add --cacheinfo 100644 $oid foo &&
+               tree=$(git write-tree) &&
+               cmt=$(echo bogus | git commit-tree $tree) &&
+               git update-ref refs/heads/bogus $cmt &&
+
+
+               test_must_fail git fsck 2>out &&
+               grep "^error: $oldoid: hash-path mismatch, found at: .*$new" out &&
+               grep "^error: $oldoid: object is of unknown type '"'"'garbage'"'"'" out
+       )
+'
+
+test_expect_success POSIXPERM 'zlib corrupt loose object output ' '
+       git init --bare corrupt-loose-output &&
+       (
+               cd corrupt-loose-output &&
+               oid=$(git hash-object -w --stdin --literally </dev/null) &&
+               oidf=objects/$(test_oid_to_path "$oid") &&
+               chmod 755 $oidf &&
+               echo extra garbage >>$oidf &&
+
+               cat >expect.error <<-EOF &&
+               error: garbage at end of loose object '\''$oid'\''
+               error: unable to unpack contents of ./$oidf
+               error: $oid: object corrupt or missing: ./$oidf
+               EOF
+               test_must_fail git fsck 2>actual &&
+               grep ^error: actual >error &&
+               test_cmp expect.error error
+       )
 '
 
 test_expect_success 'branch pointing to non-commit' '
@@ -865,4 +911,21 @@ test_expect_success 'detect corrupt index file in fsck' '
        test_i18ngrep "bad index file" errors
 '
 
+test_expect_success 'fsck error and recovery on invalid object type' '
+       git init --bare garbage-type &&
+       (
+               cd garbage-type &&
+
+               garbage_blob=$(git hash-object --stdin -w -t garbage --literally </dev/null) &&
+
+               cat >err.expect <<-\EOF &&
+               fatal: invalid object type
+               EOF
+               test_must_fail git fsck >out 2>err &&
+               grep -e "^error" -e "^fatal" err >errors &&
+               test_line_count = 1 errors &&
+               grep "$garbage_blob: object is of unknown type '"'"'garbage'"'"':" err
+       )
+'
+
 test_done
index b29563fc9973aad63a313cbb94f239d2365ee0d8..284fe18e7262ae9198d61a34142aa2e82696ce2a 100755 (executable)
@@ -282,4 +282,58 @@ test_expect_success 'test --parseopt --stuck-long and short option with unset op
        test_cmp expect output
 '
 
+test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" lines' '
+       sed -e "s/^|//" >spec <<-\EOF &&
+       |cmd [--some-option]
+       |    [--another-option]
+       |cmd [--yet-another-option]
+       |--
+       |h,help    show the help
+       EOF
+
+       sed -e "s/^|//" >expect <<-\END_EXPECT &&
+       |cat <<\EOF
+       |usage: cmd [--some-option]
+       |   or:     [--another-option]
+       |   or: cmd [--yet-another-option]
+       |
+       |    -h, --help            show the help
+       |
+       |EOF
+       END_EXPECT
+
+       test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'test --parseopt help output: multi-line blurb after empty line' '
+       sed -e "s/^|//" >spec <<-\EOF &&
+       |cmd [--some-option]
+       |    [--another-option]
+       |
+       |multi
+       |line
+       |blurb
+       |--
+       |h,help    show the help
+       EOF
+
+       sed -e "s/^|//" >expect <<-\END_EXPECT &&
+       |cat <<\EOF
+       |usage: cmd [--some-option]
+       |   or:     [--another-option]
+       |
+       |    multi
+       |    line
+       |    blurb
+       |
+       |    -h, --help            show the help
+       |
+       |EOF
+       END_EXPECT
+
+       test_must_fail git rev-parse --parseopt -- -h >out <spec >actual &&
+       test_cmp expect actual
+'
+
 test_done
index bf08102391710a9fe792b9d0a676305c1a0a13bf..40958615ebb9c16af55ab81c08584ef784e0574c 100755 (executable)
@@ -142,7 +142,7 @@ test_expect_success 'main@{n} for various n' '
        test_must_fail git rev-parse --verify main@{$Np1}
 '
 
-test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' '
+test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' '
        ln -s does-not-exist .git/refs/heads/broken &&
        test_must_fail git rev-parse --verify broken
 '
index c9b9e718b89403012b8ce555b474698b8d567140..46329c488b19cd41759d78c6d89a4411d193402e 100755 (executable)
@@ -4,6 +4,8 @@ test_description='index file specific tests'
 
 . ./test-lib.sh
 
+sane_unset GIT_TEST_SPLIT_INDEX
+
 test_expect_success 'setup' '
        echo 1 >a
 '
@@ -13,7 +15,8 @@ test_expect_success 'bogus GIT_INDEX_VERSION issues warning' '
                rm -f .git/index &&
                GIT_INDEX_VERSION=2bogus &&
                export GIT_INDEX_VERSION &&
-               git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+               git add a 2>err &&
+               sed "s/[0-9]//" err >actual.err &&
                sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
                        warning: GIT_INDEX_VERSION set, but the value is invalid.
                        Using version Z
@@ -27,7 +30,8 @@ test_expect_success 'out of bounds GIT_INDEX_VERSION issues warning' '
                rm -f .git/index &&
                GIT_INDEX_VERSION=1 &&
                export GIT_INDEX_VERSION &&
-               git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+               git add a 2>err &&
+               sed "s/[0-9]//" err >actual.err &&
                sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
                        warning: GIT_INDEX_VERSION set, but the value is invalid.
                        Using version Z
@@ -50,7 +54,8 @@ test_expect_success 'out of bounds index.version issues warning' '
                sane_unset GIT_INDEX_VERSION &&
                rm -f .git/index &&
                git config --add index.version 1 &&
-               git add a 2>&1 | sed "s/[0-9]//" >actual.err &&
+               git add a 2>err &&
+               sed "s/[0-9]//" err >actual.err &&
                sed -e "s/ Z$/ /" <<-\EOF >expect.err &&
                        warning: index.version set, but the value is invalid.
                        Using version Z
@@ -79,7 +84,7 @@ test_index_version () {
                else
                        unset GIT_INDEX_VERSION
                fi &&
-               git add a 2>&1 &&
+               git add a &&
                echo $EXPECTED_OUTPUT_VERSION >expect &&
                test-tool index-version <.git/index >actual &&
                test_cmp expect actual
index 986baa612eaaea965a11c250338157b1b8f3eee2..decd2527ed6427fe760d082cedc8a37d0df4f312 100755 (executable)
@@ -510,4 +510,38 @@ test_expect_success 'do not refresh null base index' '
        )
 '
 
+test_expect_success 'reading split index at alternate location' '
+       git init reading-alternate-location &&
+       (
+               cd reading-alternate-location &&
+               >file-in-alternate &&
+               git update-index --split-index --add file-in-alternate
+       ) &&
+       echo file-in-alternate >expect &&
+
+       # Should be able to find the shared index both right next to
+       # the specified split index file ...
+       GIT_INDEX_FILE=./reading-alternate-location/.git/index \
+       git ls-files --cached >actual &&
+       test_cmp expect actual &&
+
+       # ... and, for backwards compatibility, in the current GIT_DIR
+       # as well.
+       mv -v ./reading-alternate-location/.git/sharedindex.* .git &&
+       GIT_INDEX_FILE=./reading-alternate-location/.git/index \
+       git ls-files --cached >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'GIT_TEST_SPLIT_INDEX works' '
+       git init git-test-split-index &&
+       (
+               cd git-test-split-index &&
+               >file &&
+               GIT_TEST_SPLIT_INDEX=1 git update-index --add file &&
+               ls -l .git/sharedindex.* >actual &&
+               test_line_count = 1 actual
+       )
+'
+
 test_done
index 70d69263e6821514ada6ace0ba7846360585f32d..660132ff8d5919b1af69010ff4122b1f18d6a742 100755 (executable)
@@ -48,6 +48,7 @@ test_expect_success 'checkout commit with dir must not remove untracked a/b' '
 
 test_expect_success SYMLINKS 'the symlink remained' '
 
+       test_when_finished "rm a/b" &&
        test -h a/b
 '
 
index 45ca35d60ac5b750a635ef42f96275c4bb197692..94c4cb0672126c6ae0440598a68e99182a4cffbf 100755 (executable)
@@ -129,12 +129,15 @@ test_expect_success 'add -n -u should not add but just report' '
                echo "remove '\''top'\''"
        ) >expect &&
        before=$(git ls-files -s check top) &&
+       git count-objects -v >objects_before &&
        echo changed >>check &&
        rm -f top &&
        git add -n -u >actual &&
        after=$(git ls-files -s check top) &&
+       git count-objects -v >objects_after &&
 
        test "$before" = "$after" &&
+       test_cmp objects_before objects_after &&
        test_cmp expect actual
 
 '
index fedcefe8de334f2925f0536b16fd405fecc52836..4012bd67b04445dd7012e75bcb9bee4057c58ec5 100755 (executable)
@@ -230,7 +230,7 @@ test_expect_success 'broken main worktree still at the top' '
                EOF
                cd linked &&
                echo "worktree $(pwd)" >expected &&
-               echo "ref: .broken" >../.git/HEAD &&
+               (cd ../ && test-tool ref-store main create-symref HEAD .broken ) &&
                git worktree list --porcelain >out &&
                head -n 3 out >actual &&
                test_cmp ../expected actual &&
diff --git a/t/t2500-untracked-overwriting.sh b/t/t2500-untracked-overwriting.sh
new file mode 100755 (executable)
index 0000000..5c0bf4d
--- /dev/null
@@ -0,0 +1,244 @@
+#!/bin/sh
+
+test_description='Test handling of overwriting untracked files'
+
+. ./test-lib.sh
+
+test_setup_reset () {
+       git init reset_$1 &&
+       (
+               cd reset_$1 &&
+               test_commit init &&
+
+               git branch stable &&
+               git branch work &&
+
+               git checkout work &&
+               test_commit foo &&
+
+               git checkout stable
+       )
+}
+
+test_expect_success 'reset --hard will nuke untracked files/dirs' '
+       test_setup_reset hard &&
+       (
+               cd reset_hard &&
+               git ls-tree -r stable &&
+               git log --all --name-status --oneline &&
+               git ls-tree -r work &&
+
+               mkdir foo.t &&
+               echo precious >foo.t/file &&
+               echo foo >expect &&
+
+               git reset --hard work &&
+
+               # check that untracked directory foo.t/ was nuked
+               test_path_is_file foo.t &&
+               test_cmp expect foo.t
+       )
+'
+
+test_expect_success 'reset --merge will preserve untracked files/dirs' '
+       test_setup_reset merge &&
+       (
+               cd reset_merge &&
+
+               mkdir foo.t &&
+               echo precious >foo.t/file &&
+               cp foo.t/file expect &&
+
+               test_must_fail git reset --merge work 2>error &&
+               test_cmp expect foo.t/file &&
+               grep "Updating .foo.t. would lose untracked files" error
+       )
+'
+
+test_expect_success 'reset --keep will preserve untracked files/dirs' '
+       test_setup_reset keep &&
+       (
+               cd reset_keep &&
+
+               mkdir foo.t &&
+               echo precious >foo.t/file &&
+               cp foo.t/file expect &&
+
+               test_must_fail git reset --merge work 2>error &&
+               test_cmp expect foo.t/file &&
+               grep "Updating.*foo.t.*would lose untracked files" error
+       )
+'
+
+test_setup_checkout_m () {
+       git init checkout &&
+       (
+               cd checkout &&
+               test_commit init &&
+
+               test_write_lines file has some >filler &&
+               git add filler &&
+               git commit -m filler &&
+
+               git branch stable &&
+
+               git switch -c work &&
+               echo stuff >notes.txt &&
+               test_write_lines file has some words >filler &&
+               git add notes.txt filler &&
+               git commit -m filler &&
+
+               git checkout stable
+       )
+}
+
+test_expect_success 'checkout -m does not nuke untracked file' '
+       test_setup_checkout_m &&
+       (
+               cd checkout &&
+
+               # Tweak filler
+               test_write_lines this file has some >filler &&
+               # Make an untracked file, save its contents in "expect"
+               echo precious >notes.txt &&
+               cp notes.txt expect &&
+
+               test_must_fail git checkout -m work &&
+               test_cmp expect notes.txt
+       )
+'
+
+test_setup_sequencing () {
+       git init sequencing_$1 &&
+       (
+               cd sequencing_$1 &&
+               test_commit init &&
+
+               test_write_lines this file has some words >filler &&
+               git add filler &&
+               git commit -m filler &&
+
+               mkdir -p foo/bar &&
+               test_commit foo/bar/baz &&
+
+               git branch simple &&
+               git branch fooey &&
+
+               git checkout fooey &&
+               git rm foo/bar/baz.t &&
+               echo stuff >>filler &&
+               git add -u &&
+               git commit -m "changes" &&
+
+               git checkout simple &&
+               echo items >>filler &&
+               echo newstuff >>newfile &&
+               git add filler newfile &&
+               git commit -m another
+       )
+}
+
+test_expect_success 'git rebase --abort and untracked files' '
+       test_setup_sequencing rebase_abort_and_untracked &&
+       (
+               cd sequencing_rebase_abort_and_untracked &&
+               git checkout fooey &&
+               test_must_fail git rebase simple &&
+
+               cat init.t &&
+               git rm init.t &&
+               echo precious >init.t &&
+               cp init.t expect &&
+               git status --porcelain &&
+               test_must_fail git rebase --abort &&
+               test_cmp expect init.t
+       )
+'
+
+test_expect_success 'git rebase fast forwarding and untracked files' '
+       test_setup_sequencing rebase_fast_forward_and_untracked &&
+       (
+               cd sequencing_rebase_fast_forward_and_untracked &&
+               git checkout init &&
+               echo precious >filler &&
+               cp filler expect &&
+               test_must_fail git rebase init simple &&
+               test_cmp expect filler
+       )
+'
+
+test_expect_failure 'git rebase --autostash and untracked files' '
+       test_setup_sequencing rebase_autostash_and_untracked &&
+       (
+               cd sequencing_rebase_autostash_and_untracked &&
+               git checkout simple &&
+               git rm filler &&
+               mkdir filler &&
+               echo precious >filler/file &&
+               cp filler/file expect &&
+               git rebase --autostash init &&
+               test_path_is_file filler/file
+       )
+'
+
+test_expect_failure 'git stash and untracked files' '
+       test_setup_sequencing stash_and_untracked_files &&
+       (
+               cd sequencing_stash_and_untracked_files &&
+               git checkout simple &&
+               git rm filler &&
+               mkdir filler &&
+               echo precious >filler/file &&
+               cp filler/file expect &&
+               git status --porcelain &&
+               git stash push &&
+               git status --porcelain &&
+               test_path_is_file filler/file
+       )
+'
+
+test_expect_success 'git am --abort and untracked dir vs. unmerged file' '
+       test_setup_sequencing am_abort_and_untracked &&
+       (
+               cd sequencing_am_abort_and_untracked &&
+               git format-patch -1 --stdout fooey >changes.mbox &&
+               test_must_fail git am --3way changes.mbox &&
+
+               # Delete the conflicted file; we will stage and commit it later
+               rm filler &&
+
+               # Put an unrelated untracked directory there
+               mkdir filler &&
+               echo foo >filler/file1 &&
+               echo bar >filler/file2 &&
+
+               test_must_fail git am --abort 2>errors &&
+               test_path_is_dir filler &&
+               grep "Updating .filler. would lose untracked files in it" errors
+       )
+'
+
+test_expect_success 'git am --skip and untracked dir vs deleted file' '
+       test_setup_sequencing am_skip_and_untracked &&
+       (
+               cd sequencing_am_skip_and_untracked &&
+               git checkout fooey &&
+               git format-patch -1 --stdout simple >changes.mbox &&
+               test_must_fail git am --3way changes.mbox &&
+
+               # Delete newfile
+               rm newfile &&
+
+               # Put an unrelated untracked directory there
+               mkdir newfile &&
+               echo foo >newfile/file1 &&
+               echo bar >newfile/file2 &&
+
+               # Change our mind about resolutions, just skip this patch
+               test_must_fail git am --skip 2>errors &&
+               test_path_is_dir newfile &&
+               grep "Updating .newfile. would lose untracked files in it" errors
+       )
+'
+
+test_done
index 5325b9f67a00783974c34b4ac88431b903b3ccec..6e94c6db7b5aa987c29364f32fee8c0d52538adf 100755 (executable)
@@ -340,6 +340,10 @@ test_expect_success 'git branch --format option' '
        test_cmp expect actual
 '
 
+test_expect_success 'git branch with --format=%(rest) must fail' '
+       test_must_fail git branch --format="%(rest)" >actual
+'
+
 test_expect_success 'worktree colors correct' '
        cat >expect <<-EOF &&
        * <GREEN>(HEAD detached from fromtag)<RESET>
index 052516e6c6ab9d6f78e289d0339f8192379bad98..6b2d507f3e7f0ef9ed9db25a6129259627ecacfc 100755 (executable)
@@ -46,8 +46,9 @@ test_expect_success 'create some new worktrees' '
 test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' '
        git config core.notesRef refs/notes/y &&
        test_must_fail git notes merge z &&
-       echo "ref: refs/notes/y" >expect &&
-       test_cmp expect .git/NOTES_MERGE_REF
+       echo "refs/notes/y" >expect &&
+       git symbolic-ref NOTES_MERGE_REF >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'merge z into y while mid-merge in another workdir fails' '
@@ -57,7 +58,7 @@ test_expect_success 'merge z into y while mid-merge in another workdir fails' '
                test_must_fail git notes merge z 2>err &&
                test_i18ngrep "a notes merge into refs/notes/y is already in-progress at" err
        ) &&
-       test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF
+       test_must_fail git -C worktree symbolic-ref NOTES_MERGE_REF
 '
 
 test_expect_success 'merge z into x while mid-merge on y succeeds' '
@@ -68,8 +69,9 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
                test_i18ngrep "Automatic notes merge failed" out &&
                grep -v "A notes merge into refs/notes/x is already in-progress in" out
        ) &&
-       echo "ref: refs/notes/x" >expect &&
-       test_cmp expect .git/worktrees/worktree2/NOTES_MERGE_REF
+       echo "refs/notes/x" >expect &&
+       git -C worktree2 symbolic-ref NOTES_MERGE_REF >actual &&
+       test_cmp expect actual
 '
 
 test_done
index d877872e8f4ad15f3af4ad8e2bf22948fae34a6c..12eb226957676e7a1de02c0c22f629c3dc78ba74 100755 (executable)
@@ -297,6 +297,7 @@ test_expect_success 'abort with error when new base cannot be checked out' '
                output &&
        test_i18ngrep "file1" output &&
        test_path_is_missing .git/rebase-merge &&
+       rm file1 &&
        git reset --hard HEAD^
 '
 
@@ -351,82 +352,6 @@ test_expect_success 'retain authorship when squashing' '
        git show HEAD | grep "^Author: Twerp Snog"
 '
 
-test_expect_success REBASE_P '-p handles "no changes" gracefully' '
-       HEAD=$(git rev-parse HEAD) &&
-       git rebase -i -p HEAD^ &&
-       git update-index --refresh &&
-       git diff-files --quiet &&
-       git diff-index --quiet --cached HEAD -- &&
-       test $HEAD = $(git rev-parse HEAD)
-'
-
-test_expect_failure REBASE_P 'exchange two commits with -p' '
-       git checkout H &&
-       (
-               set_fake_editor &&
-               FAKE_LINES="2 1" git rebase -i -p HEAD~2
-       ) &&
-       test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
-       test G = $(git cat-file commit HEAD | sed -ne \$p)
-'
-
-test_expect_success REBASE_P 'preserve merges with -p' '
-       git checkout -b to-be-preserved primary^ &&
-       : > unrelated-file &&
-       git add unrelated-file &&
-       test_tick &&
-       git commit -m "unrelated" &&
-       git checkout -b another-branch primary &&
-       echo B > file1 &&
-       test_tick &&
-       git commit -m J file1 &&
-       test_tick &&
-       git merge to-be-preserved &&
-       echo C > file1 &&
-       test_tick &&
-       git commit -m K file1 &&
-       echo D > file1 &&
-       test_tick &&
-       git commit -m L1 file1 &&
-       git checkout HEAD^ &&
-       echo 1 > unrelated-file &&
-       test_tick &&
-       git commit -m L2 unrelated-file &&
-       test_tick &&
-       git merge another-branch &&
-       echo E > file1 &&
-       test_tick &&
-       git commit -m M file1 &&
-       git checkout -b to-be-rebased &&
-       test_tick &&
-       git rebase -i -p --onto branch1 primary &&
-       git update-index --refresh &&
-       git diff-files --quiet &&
-       git diff-index --quiet --cached HEAD -- &&
-       test_cmp_rev HEAD~6 branch1 &&
-       test_cmp_rev HEAD~4^2 to-be-preserved &&
-       test_cmp_rev HEAD^^2^ HEAD^^^ &&
-       test $(git show HEAD~5:file1) = B &&
-       test $(git show HEAD~3:file1) = C &&
-       test $(git show HEAD:file1) = E &&
-       test $(git show HEAD:unrelated-file) = 1
-'
-
-test_expect_success REBASE_P 'edit ancestor with -p' '
-       (
-               set_fake_editor &&
-               FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3
-       ) &&
-       echo 2 > unrelated-file &&
-       test_tick &&
-       git commit -m L2-modified --amend unrelated-file &&
-       git rebase --continue &&
-       git update-index --refresh &&
-       git diff-files --quiet &&
-       git diff-index --quiet --cached HEAD -- &&
-       test $(git show HEAD:unrelated-file) = 2
-'
-
 test_expect_success '--continue tries to commit' '
        git reset --hard D &&
        test_tick &&
index 7c381fbc89a824cad85f32d7905c7ace3efa6b29..ebbaed147a6ce2156472f45d88a0d300ec6612ac 100755 (executable)
@@ -7,77 +7,77 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
 
-### Test that we handle space characters properly
-work_dir="$(pwd)/test dir"
-
 test_expect_success setup '
-       mkdir -p "$work_dir" &&
-       cd "$work_dir" &&
-       git init &&
-       echo a > a &&
-       git add a &&
-       git commit -m a &&
+       test_commit a a a &&
        git branch to-rebase &&
 
-       echo b > a &&
-       git commit -a -m b &&
-       echo c > a &&
-       git commit -a -m c &&
+       test_commit --annotate b a b &&
+       test_commit --annotate c a c &&
 
        git checkout to-rebase &&
-       echo d > a &&
-       git commit -a -m "merge should fail on this" &&
-       echo e > a &&
-       git commit -a -m "merge should fail on this, too" &&
-       git branch pre-rebase
+       test_commit "merge should fail on this" a d d &&
+       test_commit --annotate "merge should fail on this, too" a e pre-rebase
 '
 
+# Check that HEAD is equal to "pre-rebase" and the current branch is
+# "to-rebase"
+check_head() {
+       test_cmp_rev HEAD pre-rebase^{commit} &&
+       test "$(git symbolic-ref HEAD)" = refs/heads/to-rebase
+}
+
 testrebase() {
        type=$1
-       dotest=$2
+       state_dir=$2
 
        test_expect_success "rebase$type --abort" '
-               cd "$work_dir" &&
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type main &&
-               test_path_is_dir "$dotest" &&
+               test_path_is_dir "$state_dir" &&
                git rebase --abort &&
-               test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
-               test ! -d "$dotest"
+               check_head &&
+               test_path_is_missing "$state_dir"
        '
 
        test_expect_success "rebase$type --abort after --skip" '
-               cd "$work_dir" &&
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type main &&
-               test_path_is_dir "$dotest" &&
+               test_path_is_dir "$state_dir" &&
                test_must_fail git rebase --skip &&
-               test $(git rev-parse HEAD) = $(git rev-parse main) &&
+               test_cmp_rev HEAD main &&
                git rebase --abort &&
-               test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
-               test ! -d "$dotest"
+               check_head &&
+               test_path_is_missing "$state_dir"
        '
 
        test_expect_success "rebase$type --abort after --continue" '
-               cd "$work_dir" &&
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type main &&
-               test_path_is_dir "$dotest" &&
+               test_path_is_dir "$state_dir" &&
                echo c > a &&
                echo d >> a &&
                git add a &&
                test_must_fail git rebase --continue &&
-               test $(git rev-parse HEAD) != $(git rev-parse main) &&
+               test_cmp_rev ! HEAD main &&
+               git rebase --abort &&
+               check_head &&
+               test_path_is_missing "$state_dir"
+       '
+
+       test_expect_success "rebase$type --abort when checking out a tag" '
+               test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" &&
+               git reset --hard a -- &&
+               test_must_fail git rebase$type --onto b c pre-rebase &&
+               test_cmp_rev HEAD b^{commit} &&
                git rebase --abort &&
-               test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
-               test ! -d "$dotest"
+               test_cmp_rev HEAD pre-rebase^{commit} &&
+               ! git symbolic-ref HEAD
        '
 
        test_expect_success "rebase$type --abort does not update reflog" '
-               cd "$work_dir" &&
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                git reflog show to-rebase > reflog_before &&
@@ -89,7 +89,6 @@ testrebase() {
        '
 
        test_expect_success 'rebase --abort can not be used with other options' '
-               cd "$work_dir" &&
                # Clean up the state from the previous one
                git reset --hard pre-rebase &&
                test_must_fail git rebase$type main &&
@@ -97,33 +96,21 @@ testrebase() {
                test_must_fail git rebase --abort -v &&
                git rebase --abort
        '
+
+       test_expect_success "rebase$type --quit" '
+               test_when_finished "git symbolic-ref HEAD refs/heads/to-rebase" &&
+               # Clean up the state from the previous one
+               git reset --hard pre-rebase &&
+               test_must_fail git rebase$type main &&
+               test_path_is_dir $state_dir &&
+               head_before=$(git rev-parse HEAD) &&
+               git rebase --quit &&
+               test_cmp_rev HEAD $head_before &&
+               test_path_is_missing .git/rebase-apply
+       '
 }
 
 testrebase " --apply" .git/rebase-apply
 testrebase " --merge" .git/rebase-merge
 
-test_expect_success 'rebase --apply --quit' '
-       cd "$work_dir" &&
-       # Clean up the state from the previous one
-       git reset --hard pre-rebase &&
-       test_must_fail git rebase --apply main &&
-       test_path_is_dir .git/rebase-apply &&
-       head_before=$(git rev-parse HEAD) &&
-       git rebase --quit &&
-       test $(git rev-parse HEAD) = $head_before &&
-       test ! -d .git/rebase-apply
-'
-
-test_expect_success 'rebase --merge --quit' '
-       cd "$work_dir" &&
-       # Clean up the state from the previous one
-       git reset --hard pre-rebase &&
-       test_must_fail git rebase --merge main &&
-       test_path_is_dir .git/rebase-merge &&
-       head_before=$(git rev-parse HEAD) &&
-       git rebase --quit &&
-       test $(git rev-parse HEAD) = $head_before &&
-       test ! -d .git/rebase-merge
-'
-
 test_done
index ab0960e6d9963e27dfc70a7bbe3a72ce1734109b..cde3562e3a64f756544763107d9c607eaa4f38ca 100755 (executable)
@@ -55,14 +55,4 @@ test_expect_success rebase '
        test_cmp expect actual
 
 '
-test_expect_success REBASE_P rebasep '
-
-       git checkout side-merge &&
-       git rebase -p side &&
-       git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
-       git cat-file commit side-merge-original | sed -e "1,/^\$/d" >expect &&
-       test_cmp expect actual
-
-'
-
 test_done
diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh
deleted file mode 100755 (executable)
index ec8062a..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/sh
-#
-# Copyright(C) 2008 Stephen Habermann & Andreas Ericsson
-#
-test_description='git rebase -p should preserve merges
-
-Run "git rebase -p" and check that merges are properly carried along
-'
-GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
-export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
-       skip_all='skipping git rebase -p tests, as asked for'
-       test_done
-fi
-
-GIT_AUTHOR_EMAIL=bogus_email_address
-export GIT_AUTHOR_EMAIL
-
-# Clone 2 (conflicting merge):
-#
-# A1--A2--B3   <-- origin/main
-#  \       \
-#   B1------M  <-- topic
-#    \
-#     B2       <-- origin/topic
-#
-# Clone 3 (no-ff merge):
-#
-# A1--A2--B3   <-- origin/main
-#  \
-#   B1------M  <-- topic
-#    \     /
-#     \--A3    <-- topic2
-#      \
-#       B2     <-- origin/topic
-#
-# Clone 4 (same as Clone 3)
-
-test_expect_success 'setup for merge-preserving rebase' \
-       'echo First > A &&
-       git add A &&
-       git commit -m "Add A1" &&
-       git checkout -b topic &&
-       echo Second > B &&
-       git add B &&
-       git commit -m "Add B1" &&
-       git checkout -f main &&
-       echo Third >> A &&
-       git commit -a -m "Modify A2" &&
-       echo Fifth > B &&
-       git add B &&
-       git commit -m "Add different B" &&
-
-       git clone ./. clone2 &&
-       (
-               cd clone2 &&
-               git checkout -b topic origin/topic &&
-               test_must_fail git merge origin/main &&
-               echo Resolved >B &&
-               git add B &&
-               git commit -m "Merge origin/main into topic"
-       ) &&
-
-       git clone ./. clone3 &&
-       (
-               cd clone3 &&
-               git checkout -b topic2 origin/topic &&
-               echo Sixth > A &&
-               git commit -a -m "Modify A3" &&
-               git checkout -b topic origin/topic &&
-               git merge --no-ff topic2
-       ) &&
-
-       git clone ./. clone4 &&
-       (
-               cd clone4 &&
-               git checkout -b topic2 origin/topic &&
-               echo Sixth > A &&
-               git commit -a -m "Modify A3" &&
-               git checkout -b topic origin/topic &&
-               git merge --no-ff topic2
-       ) &&
-
-       git checkout topic &&
-       echo Fourth >> B &&
-       git commit -a -m "Modify B2"
-'
-
-test_expect_success '--continue works after a conflict' '
-       (
-       cd clone2 &&
-       git fetch &&
-       test_must_fail git rebase -p origin/topic &&
-       test 2 = $(git ls-files B | wc -l) &&
-       echo Resolved again > B &&
-       test_must_fail git rebase --continue &&
-       grep "^@@@ " .git/rebase-merge/patch &&
-       git add B &&
-       git rebase --continue &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Add different" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge origin" | wc -l)
-       )
-'
-
-test_expect_success 'rebase -p preserves no-ff merges' '
-       (
-       cd clone3 &&
-       git fetch &&
-       git rebase -p origin/topic &&
-       test 3 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) &&
-       test 1 = $(git rev-list --all --pretty=oneline | grep "Merge branch" | wc -l)
-       )
-'
-
-test_expect_success 'rebase -p ignores merge.log config' '
-       (
-       cd clone4 &&
-       git fetch &&
-       git -c merge.log=1 rebase -p origin/topic &&
-       echo >expected &&
-       git log --format="%b" -1 >current &&
-       test_cmp expected current
-       )
-'
-
-test_done
diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh
deleted file mode 100755 (executable)
index 2e29866..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2008 Stephen Haberman
-#
-
-test_description='git rebase preserve merges
-
-This test runs git rebase with preserve merges and ensures commits
-dropped by the --cherry-pick flag have their childrens parents
-rewritten.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
-       skip_all='skipping git rebase -p tests, as asked for'
-       test_done
-fi
-
-# set up two branches like this:
-#
-# A - B - C - D - E
-#   \
-#     F - G - H
-#       \
-#         I
-#
-# where B, D and G touch the same file.
-
-test_expect_success 'setup' '
-       test_commit A file1 &&
-       test_commit B file1 1 &&
-       test_commit C file2 &&
-       test_commit D file1 2 &&
-       test_commit E file3 &&
-       git checkout A &&
-       test_commit F file4 &&
-       test_commit G file1 3 &&
-       test_commit H file5 &&
-       git checkout F &&
-       test_commit I file6
-'
-
-# A - B - C - D - E
-#   \             \ \
-#     F - G - H -- L \        -->   L
-#       \            |               \
-#         I -- G2 -- J -- K           I -- K
-# G2 = same changes as G
-test_expect_success 'skip same-resolution merges with -p' '
-       git checkout H &&
-       test_must_fail git merge E &&
-       test_commit L file1 23 &&
-       git checkout I &&
-       test_commit G2 file1 3 &&
-       test_must_fail git merge E &&
-       test_commit J file1 23 &&
-       test_commit K file7 file7 &&
-       git rebase -i -p L &&
-       test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
-       test "23" = "$(cat file1)" &&
-       test "I" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)"
-'
-
-# A - B - C - D - E
-#   \             \ \
-#     F - G - H -- L2 \        -->   L2
-#       \             |                \
-#         I -- G3 --- J2 -- K2           I -- G3 -- K2
-# G2 = different changes as G
-test_expect_success 'keep different-resolution merges with -p' '
-       git checkout H &&
-       test_must_fail git merge E &&
-       test_commit L2 file1 23 &&
-       git checkout I &&
-       test_commit G3 file1 4 &&
-       test_must_fail git merge E &&
-       test_commit J2 file1 24 &&
-       test_commit K2 file7 file7 &&
-       test_must_fail git rebase -i -p L2 &&
-       echo 234 > file1 &&
-       git add file1 &&
-       git rebase --continue &&
-       test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
-       test "234" = "$(cat file1)" &&
-       test "I" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)"
-'
-
-test_done
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
deleted file mode 100755 (executable)
index fb45e7b..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2008 Stephen Haberman
-#
-
-test_description='git rebase preserve merges
-
-This test runs git rebase with -p and tries to squash a commit from after
-a merge to before the merge.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
-       skip_all='skipping git rebase -p tests, as asked for'
-       test_done
-fi
-
-. "$TEST_DIRECTORY"/lib-rebase.sh
-
-set_fake_editor
-
-# set up two branches like this:
-#
-# A1 - B1 - D1 - E1 - F1
-#       \        /
-#        -- C1 --
-
-test_expect_success 'setup' '
-       test_commit A1 &&
-       test_commit B1 &&
-       test_commit C1 &&
-       git reset --hard B1 &&
-       test_commit D1 &&
-       test_merge E1 C1 &&
-       test_commit F1
-'
-
-# Should result in:
-#
-# A1 - B1 - D2 - E2
-#       \        /
-#        -- C1 --
-#
-test_expect_success 'squash F1 into D1' '
-       FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
-       test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
-       test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
-       git tag E2
-'
-
-# Start with:
-#
-# A1 - B1 - D2 - E2
-#  \
-#   G1 ---- L1 ---- M1
-#    \             /
-#     H1 -- J1 -- K1
-#      \         /
-#        -- I1 --
-#
-# And rebase G1..M1 onto E2
-
-test_expect_success 'rebase two levels of merge' '
-       git checkout A1 &&
-       test_commit G1 &&
-       test_commit H1 &&
-       test_commit I1 &&
-       git checkout -b branch3 H1 &&
-       test_commit J1 &&
-       test_merge K1 I1 &&
-       git checkout -b branch2 G1 &&
-       test_commit L1 &&
-       test_merge M1 K1 &&
-       GIT_EDITOR=: git rebase -i -p E2 &&
-       test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
-       test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
-       test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)"
-'
-
-test_done
index fda62c65bd5781b81314af9a9ac4e34fd83a3541..19c6f4acbf6c8a2872da776e586b327639258901 100755 (executable)
@@ -89,17 +89,6 @@ test_expect_success 'pre-rebase got correct input (4)' '
        test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
 '
 
-test_expect_success REBASE_P 'rebase -i -p with linear history' '
-       git checkout -b work5 other &&
-       git rebase -i -p --root --onto main &&
-       git log --pretty=tformat:"%s" > rebased5 &&
-       test_cmp expect rebased5
-'
-
-test_expect_success REBASE_P 'pre-rebase got correct input (5)' '
-       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
-'
-
 test_expect_success 'set up merge history' '
        git checkout other^ &&
        git checkout -b side &&
@@ -123,13 +112,6 @@ commit work6~4
 1
 EOF
 
-test_expect_success REBASE_P 'rebase -i -p with merge' '
-       git checkout -b work6 other &&
-       git rebase -i -p --root --onto main &&
-       log_with_names work6 > rebased6 &&
-       test_cmp expect-side rebased6
-'
-
 test_expect_success 'set up second root and merge' '
        git symbolic-ref HEAD refs/heads/third &&
        rm .git/index &&
@@ -158,13 +140,6 @@ commit work7~5
 1
 EOF
 
-test_expect_success REBASE_P 'rebase -i -p with two roots' '
-       git checkout -b work7 other &&
-       git rebase -i -p --root --onto main &&
-       log_with_names work7 > rebased7 &&
-       test_cmp expect-third rebased7
-'
-
 test_expect_success 'setup pre-rebase hook that fails' '
        mkdir -p .git/hooks &&
        cat >.git/hooks/pre-rebase <<EOF &&
@@ -264,21 +239,9 @@ commit conflict3~6
 1
 EOF
 
-test_expect_success REBASE_P 'rebase -i -p --root with conflict (first part)' '
-       git checkout -b conflict3 other &&
-       test_must_fail git rebase -i -p --root --onto main &&
-       git ls-files -u | grep "B$"
-'
-
 test_expect_success 'fix the conflict' '
        echo 3 > B &&
        git add B
 '
 
-test_expect_success REBASE_P 'rebase -i -p --root with conflict (second part)' '
-       git rebase --continue &&
-       log_with_names conflict3 >out &&
-       test_cmp expect-conflict-p out
-'
-
 test_done
diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh
deleted file mode 100755 (executable)
index 72e04b5..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2009 Greg Price
-#
-
-test_description='git rebase -p should respect --onto
-
-In a rebase with --onto, we should rewrite all the commits that
-aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM.
-'
-. ./test-lib.sh
-
-if ! test_have_prereq REBASE_P; then
-       skip_all='skipping git rebase -p tests, as asked for'
-       test_done
-fi
-
-. "$TEST_DIRECTORY"/lib-rebase.sh
-
-# Set up branches like this:
-# A1---B1---E1---F1---G1
-#  \    \             /
-#   \    \--C1---D1--/
-#    H1
-
-test_expect_success 'setup' '
-       test_commit A1 &&
-       test_commit B1 &&
-       test_commit C1 &&
-       test_commit D1 &&
-       git reset --hard B1 &&
-       test_commit E1 &&
-       test_commit F1 &&
-       test_merge G1 D1 &&
-       git reset --hard A1 &&
-       test_commit H1
-'
-
-# Now rebase merge G1 from both branches' base B1, both should move:
-# A1---B1---E1---F1---G1
-#  \    \             /
-#   \    \--C1---D1--/
-#    \
-#     H1---E2---F2---G2
-#      \             /
-#       \--C2---D2--/
-
-test_expect_success 'rebase from B1 onto H1' '
-       git checkout G1 &&
-       git rebase -p --onto H1 B1 &&
-       test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" &&
-       test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)"
-'
-
-# On the other hand if rebase from E1 which is within one branch,
-# then the other branch stays:
-# A1---B1---E1---F1---G1
-#  \    \             /
-#   \    \--C1---D1--/
-#    \             \
-#     H1-----F3-----G3
-
-test_expect_success 'rebase from E1 onto H1' '
-       git checkout G1 &&
-       git rebase -p --onto H1 E1 &&
-       test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" &&
-       test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)"
-'
-
-# And the same if we rebase from a commit in the second-parent branch.
-# A1---B1---E1---F1----G1
-#  \    \          \   /
-#   \    \--C1---D1-\-/
-#    \               \
-#     H1------D3------G4
-
-test_expect_success 'rebase from C1 onto H1' '
-       git checkout G1 &&
-       git rev-list --first-parent --pretty=oneline C1..G1 &&
-       git rebase -p --onto H1 C1 &&
-       test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" &&
-       test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)"
-'
-
-test_done
index 738fbae9b291b38f7129ec6321819c70cdc5aeae..22eca73aa3e926cf58d0c723f97c1d0394021371 100755 (executable)
@@ -119,20 +119,6 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' '
        test -f funny.was.run
 '
 
-test_expect_success REBASE_P 'rebase passes merge strategy options correctly' '
-       rm -fr .git/rebase-* &&
-       git reset --hard commit-new-file-F3-on-topic-branch &&
-       test_commit theirs-to-merge &&
-       git reset --hard HEAD^ &&
-       test_commit some-commit &&
-       test_tick &&
-       git merge --no-ff theirs-to-merge &&
-       FAKE_LINES="1 edit 2 3" git rebase -i -f -p -m \
-               -s recursive --strategy-option=theirs HEAD~2 &&
-       test_commit force-change &&
-       git rebase --continue
-'
-
 test_expect_success 'rebase -r passes merge strategy options correctly' '
        rm -fr .git/rebase-* &&
        git reset --hard commit-new-file-F3-on-topic-branch &&
@@ -268,7 +254,6 @@ test_rerere_autoupdate --apply
 test_rerere_autoupdate -m
 GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
 test_rerere_autoupdate -i
-test_have_prereq !REBASE_P || test_rerere_autoupdate --preserve-merges
 unset GIT_SEQUENCE_EDITOR
 
 test_expect_success 'the todo command "break" works' '
index 4a9204b4b6491e4bb734897268a2154ff407e62c..62d86d557dafa13fca52176e67dbe374a0e73210 100755 (executable)
@@ -29,7 +29,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_expect_success 'setup branches and remote tracking' '
        git tag -l >tags &&
@@ -53,7 +52,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -70,7 +68,6 @@ test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -87,7 +84,6 @@ test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -102,7 +98,6 @@ test_run_rebase success --apply
 test_run_rebase success --fork-point
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 #       f
 #      /
@@ -142,7 +137,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -157,7 +151,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -172,7 +165,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -187,7 +179,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 # a---b---c---j!
 #      \
@@ -215,7 +206,6 @@ test_run_rebase () {
 test_run_rebase failure --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -229,7 +219,6 @@ test_run_rebase () {
 }
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -243,7 +232,6 @@ test_run_rebase () {
 }
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 test_run_rebase success --rebase-merges
 
 #       m
@@ -283,7 +271,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -298,7 +285,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -313,7 +299,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_run_rebase () {
        result=$1
@@ -329,7 +314,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -344,7 +328,6 @@ test_run_rebase () {
 test_run_rebase success --apply
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -358,7 +341,6 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase failure -p
 
 test_run_rebase () {
        result=$1
@@ -373,6 +355,5 @@ test_run_rebase () {
 test_run_rebase success ''
 test_run_rebase success -m
 test_run_rebase success -i
-test_have_prereq !REBASE_P || test_run_rebase success -p
 
 test_done
index c8234062c6c8322d92f5054cf76b164446227cfc..eb0a3d9d48738375bfe74c199aa5e8116d30dd3d 100755 (executable)
@@ -63,15 +63,4 @@ test_rebase_am_only () {
 test_rebase_am_only --whitespace=fix
 test_rebase_am_only -C4
 
-test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' '
-       git checkout B^0 &&
-       test_must_fail git rebase --preserve-merges --signoff A
-'
-
-test_expect_success REBASE_P \
-       '--preserve-merges incompatible with --rebase-merges' '
-       git checkout B^0 &&
-       test_must_fail git rebase --preserve-merges --rebase-merges A
-'
-
 test_done
index e42faa44e74f1abe247aa1c16da9d5582123d42c..63acc1ea4dab43e5fc1f9eb26b031f49e3d9a432 100755 (executable)
@@ -106,155 +106,4 @@ test_run_rebase success 'd n o e' --apply
 test_run_rebase success 'd n o e' -m
 test_run_rebase success 'd n o e' -i
 
-if ! test_have_prereq REBASE_P; then
-       skip_all='skipping git rebase -p tests, as asked for'
-       test_done
-fi
-
-test_expect_success "rebase -p is no-op in non-linear history" "
-       reset_rebase &&
-       git rebase -p d w &&
-       test_cmp_rev w HEAD
-"
-
-test_expect_success "rebase -p is no-op when base inside second parent" "
-       reset_rebase &&
-       git rebase -p e w &&
-       test_cmp_rev w HEAD
-"
-
-test_expect_failure "rebase -p --root on non-linear history is a no-op" "
-       reset_rebase &&
-       git rebase -p --root w &&
-       test_cmp_rev w HEAD
-"
-
-test_expect_success "rebase -p re-creates merge from side branch" "
-       reset_rebase &&
-       git rebase -p z w &&
-       test_cmp_rev z HEAD^ &&
-       test_cmp_rev w^2 HEAD^2
-"
-
-test_expect_success "rebase -p re-creates internal merge" "
-       reset_rebase &&
-       git rebase -p c w &&
-       test_cmp_rev c HEAD~4 &&
-       test_cmp_rev HEAD^2^ HEAD~3 &&
-       test_revision_subjects 'd n e o w' HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p can re-create two branches on onto" "
-       reset_rebase &&
-       git rebase -p --onto c d w &&
-       test_cmp_rev c HEAD~3 &&
-       test_cmp_rev c HEAD^2^ &&
-       test_revision_subjects 'n e o w' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-#       f
-#      /
-# a---b---c---g---h
-#      \
-#       d---gp--i
-#        \       \
-#         e-------u
-#
-# gp = cherry-picked g
-# h = reverted g
-test_expect_success 'setup of non-linear-history for patch-equivalence tests' '
-       git checkout e &&
-       test_merge u i
-'
-
-test_expect_success "rebase -p re-creates history around dropped commit matching upstream" "
-       reset_rebase &&
-       git rebase -p h u &&
-       test_cmp_rev h HEAD~3 &&
-       test_cmp_rev HEAD^2^ HEAD~2 &&
-       test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p --onto in merged history drops patches in upstream" "
-       reset_rebase &&
-       git rebase -p --onto f h u &&
-       test_cmp_rev f HEAD~3 &&
-       test_cmp_rev HEAD^2^ HEAD~2 &&
-       test_revision_subjects 'd i e u' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p --onto in merged history does not drop patches in onto" "
-       reset_rebase &&
-       git rebase -p --onto h f u &&
-       test_cmp_rev h HEAD~3 &&
-       test_cmp_rev HEAD^2~2 HEAD~2 &&
-       test_revision_subjects 'd gp i e u' HEAD~2 HEAD^2^ HEAD^2 HEAD^ HEAD
-"
-
-# a---b---c---g---h
-#      \
-#       d---gp--s
-#        \   \ /
-#         \   X
-#          \ / \
-#           e---t
-#
-# gp = cherry-picked g
-# h = reverted g
-test_expect_success 'setup of non-linear-history for dropping whole side' '
-       git checkout gp &&
-       test_merge s e &&
-       git checkout e &&
-       test_merge t gp
-'
-
-test_expect_failure "rebase -p drops merge commit when entire first-parent side is dropped" "
-       reset_rebase &&
-       git rebase -p h s &&
-       test_cmp_rev h HEAD~2 &&
-       test_linear_range 'd e' h..
-"
-
-test_expect_success "rebase -p drops merge commit when entire second-parent side is dropped" "
-       reset_rebase &&
-       git rebase -p h t &&
-       test_cmp_rev h HEAD~2 &&
-       test_linear_range 'd e' h..
-"
-
-# a---b---c
-#      \
-#       d---e
-#        \   \
-#         n---r
-#          \
-#           o
-#
-# r = tree-same with n
-test_expect_success 'setup of non-linear-history for empty commits' '
-       git checkout n &&
-       git merge --no-commit e &&
-       git reset n . &&
-       git commit -m r &&
-       git reset --hard &&
-       git clean -f &&
-       git tag r
-'
-
-test_expect_success "rebase -p re-creates empty internal merge commit" "
-       reset_rebase &&
-       git rebase -p c r &&
-       test_cmp_rev c HEAD~3 &&
-       test_cmp_rev HEAD^2^ HEAD~2 &&
-       test_revision_subjects 'd e n r' HEAD~2 HEAD^2 HEAD^ HEAD
-"
-
-test_expect_success "rebase -p re-creates empty merge commit" "
-       reset_rebase &&
-       git rebase -p o r &&
-       test_cmp_rev e HEAD^2 &&
-       test_cmp_rev o HEAD^ &&
-       test_revision_subjects 'r' HEAD
-"
-
 test_done
index e78c7e37969ab3d7caabcd3f39131e134670cf06..48b76f82325da49d521bf4603566ebcbc3653ddc 100755 (executable)
@@ -36,11 +36,10 @@ commit_message() {
 # where the root commit adds three files: topic_1.t, topic_2.t and topic_3.t.
 #
 # This commit history is then rebased onto `topic_3` with the
-# `-Xsubtree=files_subtree` option in three different ways:
+# `-Xsubtree=files_subtree` option in two different ways:
 #
-# 1. using `--preserve-merges`
-# 2. using `--preserve-merges` and --keep-empty
-# 3. without specifying a rebase backend
+# 1. without specifying a rebase backend
+# 2. using the `--rebase-merges` backend
 
 test_expect_success 'setup' '
        test_commit README &&
@@ -69,25 +68,6 @@ test_expect_success 'setup' '
        git commit -m "Empty commit" --allow-empty
 '
 
-# FAILURE: Does not preserve topic_4.
-test_expect_failure REBASE_P 'Rebase -Xsubtree --preserve-merges --onto commit' '
-       reset_rebase &&
-       git checkout -b rebase-preserve-merges to-rebase &&
-       git rebase -Xsubtree=files_subtree --preserve-merges --onto files-main main &&
-       verbose test "$(commit_message HEAD~)" = "topic_4" &&
-       verbose test "$(commit_message HEAD)" = "files_subtree/topic_5"
-'
-
-# FAILURE: Does not preserve topic_4.
-test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit' '
-       reset_rebase &&
-       git checkout -b rebase-keep-empty to-rebase &&
-       git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-main main &&
-       verbose test "$(commit_message HEAD~2)" = "topic_4" &&
-       verbose test "$(commit_message HEAD~)" = "files_subtree/topic_5" &&
-       verbose test "$(commit_message HEAD)" = "Empty commit"
-'
-
 test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' '
        reset_rebase &&
        git checkout -b rebase-onto to-rebase &&
index ec1076685802221f4ba89c75aa09b1283c48e2a9..5f8ba2c7399dc3d1cb5661097a3a3f0990624e77 100755 (executable)
@@ -65,6 +65,7 @@ test_rebase_gpg_sign ! true  -i --gpg-sign --no-gpg-sign
 test_rebase_gpg_sign   false -i --no-gpg-sign --gpg-sign
 
 test_expect_failure 'rebase -p --no-gpg-sign override commit.gpgsign' '
+       test_when_finished "git clean -f" &&
        git reset --hard merged &&
        git config commit.gpgsign true &&
        git rebase -p --no-gpg-sign --onto=one fork-point main &&
index 9d100cd1884e39a55589c6bf556273ea64dedfec..4b5b607673329d30ef71c26f673c36ae0058cc67 100755 (executable)
@@ -158,4 +158,20 @@ test_expect_success 'cherry-pick works with dirty renamed file' '
        grep -q "^modified$" renamed
 '
 
+test_expect_success 'advice from failed revert' '
+       test_commit --no-tag "add dream" dream dream &&
+       dream_oid=$(git rev-parse --short HEAD) &&
+       cat <<-EOF >expected &&
+       error: could not revert $dream_oid... add dream
+       hint: After resolving the conflicts, mark them with
+       hint: "git add/rm <pathspec>", then run
+       hint: "git revert --continue".
+       hint: You can instead skip this commit with "git revert --skip".
+       hint: To abort and get back to the state before "git revert",
+       hint: run "git revert --abort".
+       EOF
+       test_commit --append --no-tag "double-add dream" dream dream &&
+       test_must_fail git revert HEAD^ 2>actual &&
+       test_cmp expected actual
+'
 test_done
index 014001b8f325c0f1d8b140e7828332bb15610574..979e843c65a97cf914e9f8f966893d2c965a9052 100755 (executable)
@@ -47,20 +47,23 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
        test "$head" = "$newhead"
 '
 
-test_expect_success 'advice from failed cherry-pick' "
+test_expect_success 'advice from failed cherry-pick' '
        pristine_detach initial &&
 
-       picked=\$(git rev-parse --short picked) &&
+       picked=$(git rev-parse --short picked) &&
        cat <<-EOF >expected &&
-       error: could not apply \$picked... picked
-       hint: after resolving the conflicts, mark the corrected paths
-       hint: with 'git add <paths>' or 'git rm <paths>'
-       hint: and commit the result with 'git commit'
+       error: could not apply $picked... picked
+       hint: After resolving the conflicts, mark them with
+       hint: "git add/rm <pathspec>", then run
+       hint: "git cherry-pick --continue".
+       hint: You can instead skip this commit with "git cherry-pick --skip".
+       hint: To abort and get back to the state before "git cherry-pick",
+       hint: run "git cherry-pick --abort".
        EOF
        test_must_fail git cherry-pick picked 2>actual &&
 
        test_cmp expected actual
-"
+'
 
 test_expect_success 'advice from failed cherry-pick --no-commit' "
        pristine_detach initial &&
index 49010aa9469d9dc48637e80fae79248a8cf826fb..3b0fa66c33da5857012b56d465c5ffad77f61be4 100755 (executable)
@@ -238,6 +238,7 @@ test_expect_success 'allow skipping commit but not abort for a new history' '
 '
 
 test_expect_success 'allow skipping stopped cherry-pick because of untracked file modifications' '
+       test_when_finished "rm unrelated" &&
        pristine_detach initial &&
        git rm --cached unrelated &&
        git commit -m "untrack unrelated" &&
index e9e9a15c74cd339d592f64e31c3bb1e1adfde845..ecce497a9ca177594ea6d444371c415c3a7f2b21 100755 (executable)
@@ -11,12 +11,15 @@ test_expect_success 'setup' "
        git commit -m files &&
 
        cat >sparse_error_header <<-EOF &&
-       The following pathspecs didn't match any eligible path, but they do match index
-       entries outside the current sparse checkout:
+       The following paths and/or pathspecs matched paths that exist
+       outside of your sparse-checkout definition, so will not be
+       updated in the index:
        EOF
 
        cat >sparse_hint <<-EOF &&
-       hint: Disable or modify the sparsity rules if you intend to update such entries.
+       hint: If you intend to update such entries, try one of the following:
+       hint: * Use the --sparse option.
+       hint: * Disable or modify the sparsity rules.
        hint: Disable this message with \"git config advice.updateSparsePath false\"
        EOF
 
@@ -37,9 +40,25 @@ done
 test_expect_success 'recursive rm does not remove sparse entries' '
        git reset --hard &&
        git sparse-checkout set sub/dir &&
-       git rm -r sub &&
+       test_must_fail git rm -r sub &&
+       git rm --sparse -r sub &&
        git status --porcelain -uno >actual &&
-       echo "D  sub/dir/e" >expected &&
+       cat >expected <<-\EOF &&
+       D  sub/d
+       D  sub/dir/e
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'recursive rm --sparse removes sparse entries' '
+       git reset --hard &&
+       git sparse-checkout set "sub/dir" &&
+       git rm --sparse -r sub &&
+       git status --porcelain -uno >actual &&
+       cat >expected <<-\EOF &&
+       D  sub/d
+       D  sub/dir/e
+       EOF
        test_cmp expected actual
 '
 
@@ -75,4 +94,15 @@ test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
        git ls-files --error-unmatch b
 '
 
+test_expect_success 'refuse to rm a non-skip-worktree path outside sparse cone' '
+       git reset --hard &&
+       git sparse-checkout set a &&
+       git update-index --no-skip-worktree b &&
+       test_must_fail git rm b 2>stderr &&
+       test_cmp b_error_and_hint stderr &&
+       git rm --sparse b 2>stderr &&
+       test_must_be_empty stderr &&
+       test_path_is_missing b
+'
+
 test_done
index 2b1fd0d0eef004a5518111936dc8271545a190d7..5b904988d499e50d91554d6a67c6ad6a5584f961 100755 (executable)
@@ -19,6 +19,7 @@ setup_sparse_entry () {
        fi &&
        git add sparse_entry &&
        git update-index --skip-worktree sparse_entry &&
+       git commit --allow-empty -m "ensure sparse_entry exists at HEAD" &&
        SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
 }
 
@@ -36,14 +37,22 @@ setup_gitignore () {
        EOF
 }
 
+test_sparse_entry_unstaged () {
+       git diff --staged -- sparse_entry >diff &&
+       test_must_be_empty diff
+}
+
 test_expect_success 'setup' "
        cat >sparse_error_header <<-EOF &&
-       The following pathspecs didn't match any eligible path, but they do match index
-       entries outside the current sparse checkout:
+       The following paths and/or pathspecs matched paths that exist
+       outside of your sparse-checkout definition, so will not be
+       updated in the index:
        EOF
 
        cat >sparse_hint <<-EOF &&
-       hint: Disable or modify the sparsity rules if you intend to update such entries.
+       hint: If you intend to update such entries, try one of the following:
+       hint: * Use the --sparse option.
+       hint: * Disable or modify the sparsity rules.
        hint: Disable this message with \"git config advice.updateSparsePath false\"
        EOF
 
@@ -55,6 +64,7 @@ test_expect_success 'git add does not remove sparse entries' '
        setup_sparse_entry &&
        rm sparse_entry &&
        test_must_fail git add sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp error_and_hint stderr &&
        test_sparse_entry_unchanged
 '
@@ -73,6 +83,7 @@ test_expect_success 'git add . does not remove sparse entries' '
        rm sparse_entry &&
        setup_gitignore &&
        test_must_fail git add . 2>stderr &&
+       test_sparse_entry_unstaged &&
 
        cat sparse_error_header >expect &&
        echo . >>expect &&
@@ -88,6 +99,7 @@ do
                setup_sparse_entry &&
                echo modified >sparse_entry &&
                test_must_fail git add $opt sparse_entry 2>stderr &&
+               test_sparse_entry_unstaged &&
                test_cmp error_and_hint stderr &&
                test_sparse_entry_unchanged
        '
@@ -98,6 +110,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
        git ls-files --debug sparse_entry | grep mtime >before &&
        test-tool chmtime -60 sparse_entry &&
        test_must_fail git add --refresh sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp error_and_hint stderr &&
        git ls-files --debug sparse_entry | grep mtime >after &&
        test_cmp before after
@@ -106,6 +119,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
 test_expect_success 'git add --chmod does not update sparse entries' '
        setup_sparse_entry &&
        test_must_fail git add --chmod=+x sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp error_and_hint stderr &&
        test_sparse_entry_unchanged &&
        ! test -x sparse_entry
@@ -116,6 +130,7 @@ test_expect_success 'git add --renormalize does not update sparse entries' '
        setup_sparse_entry "LINEONE\r\nLINETWO\r\n" &&
        echo "sparse_entry text=auto" >.gitattributes &&
        test_must_fail git add --renormalize sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp error_and_hint stderr &&
        test_sparse_entry_unchanged
 '
@@ -124,6 +139,7 @@ test_expect_success 'git add --dry-run --ignore-missing warn on sparse path' '
        setup_sparse_entry &&
        rm sparse_entry &&
        test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp error_and_hint stderr &&
        test_sparse_entry_unchanged
 '
@@ -145,11 +161,57 @@ test_expect_success 'do not warn when pathspec matches dense entries' '
        git ls-files --error-unmatch dense_entry
 '
 
+test_expect_success 'git add fails outside of sparse-checkout definition' '
+       test_when_finished git sparse-checkout disable &&
+       test_commit a &&
+       git sparse-checkout init &&
+       git sparse-checkout set a &&
+       echo >>sparse_entry &&
+
+       git update-index --no-skip-worktree sparse_entry &&
+       test_must_fail git add sparse_entry &&
+       test_sparse_entry_unstaged &&
+
+       test_must_fail git add --chmod=+x sparse_entry &&
+       test_sparse_entry_unstaged &&
+
+       test_must_fail git add --renormalize sparse_entry &&
+       test_sparse_entry_unstaged &&
+
+       # Avoid munging CRLFs to avoid an error message
+       git -c core.autocrlf=input add --sparse sparse_entry 2>stderr &&
+       test_must_be_empty stderr &&
+       test-tool read-cache --table >actual &&
+       grep "^100644 blob.*sparse_entry\$" actual &&
+
+       git add --sparse --chmod=+x sparse_entry 2>stderr &&
+       test_must_be_empty stderr &&
+       test-tool read-cache --table >actual &&
+       grep "^100755 blob.*sparse_entry\$" actual &&
+
+       git reset &&
+
+       # This will print a message over stderr on Windows.
+       git add --sparse --renormalize sparse_entry &&
+       git status --porcelain >actual &&
+       grep "^M  sparse_entry\$" actual
+'
+
 test_expect_success 'add obeys advice.updateSparsePath' '
        setup_sparse_entry &&
        test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
+       test_sparse_entry_unstaged &&
        test_cmp sparse_entry_error stderr
 
 '
 
+test_expect_success 'add allows sparse entries with --sparse' '
+       git sparse-checkout set a &&
+       echo modified >sparse_entry &&
+       test_must_fail git add sparse_entry &&
+       test_sparse_entry_unchanged &&
+       git add --sparse sparse_entry 2>stderr &&
+       test_must_be_empty stderr
+'
+
 test_done
index dd2cdcc114867ed0810190417c0f2a98ee24a641..5390eec4e3b57eefca8971d86fa08b8f09c93747 100755 (executable)
@@ -422,4 +422,10 @@ test_expect_success 'stash show --{include,only}-untracked on stashes without un
        test_must_be_empty actual
 '
 
+test_expect_success 'stash -u ignores sub-repository' '
+       test_when_finished "rm -rf sub-repo" &&
+       git init sub-repo &&
+       git stash -u
+'
+
 test_done
index 298bc7a71b29d169c66f1589ca6fa79034609656..3b95f68b3b0c8252f083e549332244175ece6d0a 100644 (file)
@@ -3,6 +3,10 @@ public class Beer
        int special;
        public static void main(String RIGHT[])
        {
+               someMethodCall();
+               someOtherMethod("17")
+                       .doThat();
+               // Whatever
                System.out.print("ChangeMe");
        }
 }
diff --git a/t/t4018/java-enum-constant b/t/t4018/java-enum-constant
new file mode 100644 (file)
index 0000000..a1931c8
--- /dev/null
@@ -0,0 +1,6 @@
+private enum RIGHT {
+    ONE,
+    TWO,
+    THREE,
+    ChangeMe
+}
diff --git a/t/t4018/java-method-return-generic-bounded b/t/t4018/java-method-return-generic-bounded
new file mode 100644 (file)
index 0000000..66dd78c
--- /dev/null
@@ -0,0 +1,9 @@
+class MyExample {
+    public <T extends Bar & Foo<T>, R> Map<T, R[]> foo(String[] RIGHT) {
+        someMethodCall();
+        someOtherMethod()
+            .doThat();
+        // Whatever...
+        return (List<T>) Arrays.asList("ChangeMe");
+    }
+}
diff --git a/t/t4018/java-method-return-generic-wildcard b/t/t4018/java-method-return-generic-wildcard
new file mode 100644 (file)
index 0000000..96e9e5f
--- /dev/null
@@ -0,0 +1,9 @@
+class MyExample {
+    public List<? extends Comparable> foo(String[] RIGHT) {
+        someMethodCall();
+        someOtherMethod()
+            .doThat();
+        // Whatever...
+        return Arrays.asList("ChangeMe");
+    }
+}
diff --git a/t/t4018/java-nested-field b/t/t4018/java-nested-field
new file mode 100644 (file)
index 0000000..d92d3ec
--- /dev/null
@@ -0,0 +1,6 @@
+class MyExample {
+    private static class RIGHT {
+        // change an inner class field
+        String inner = "ChangeMe";
+    }
+}
diff --git a/t/t4018/php-enum b/t/t4018/php-enum
new file mode 100644 (file)
index 0000000..91a69c1
--- /dev/null
@@ -0,0 +1,4 @@
+enum RIGHT: string
+{
+    case Foo = 'ChangeMe';
+}
index dc7b242697d7760e5cff1fd5e166ba895ce605c6..d86e38abd882220ec60d45b2a351d2947f7dcfcb 100755 (executable)
@@ -361,7 +361,6 @@ test_expect_success 'typechanged submodule(submodule->blob)' '
 rm -f sm1 &&
 test_create_repo sm1 &&
 head6=$(add_file sm1 foo6 foo7)
-fullhead6=$(cd sm1; git rev-parse --verify HEAD)
 test_expect_success 'nonexistent commit' '
        git diff-index -p --submodule=diff HEAD >actual &&
        cat >expected <<-EOF &&
@@ -704,10 +703,26 @@ test_expect_success 'path filter' '
        diff_cmp expected actual
 '
 
-commit_file sm2
+cat >.gitmodules <<-EOF
+[submodule "sm2"]
+       path = sm2
+       url = bogus_url
+EOF
+git add .gitmodules
+commit_file sm2 .gitmodules
+
 test_expect_success 'given commit' '
        git diff-index -p --submodule=diff HEAD^ >actual &&
        cat >expected <<-EOF &&
+       diff --git a/.gitmodules b/.gitmodules
+       new file mode 100644
+       index 1234567..89abcde
+       --- /dev/null
+       +++ b/.gitmodules
+       @@ -0,0 +1,3 @@
+       +[submodule "sm2"]
+       +path = sm2
+       +url = bogus_url
        Submodule sm1 $head7...0000000 (submodule deleted)
        Submodule sm2 0000000...$head9 (new submodule)
        diff --git a/sm2/foo8 b/sm2/foo8
@@ -729,15 +744,21 @@ test_expect_success 'given commit' '
 '
 
 test_expect_success 'setup .git file for sm2' '
-       (cd sm2 &&
-        REAL="$(pwd)/../.real" &&
-        mv .git "$REAL" &&
-        echo "gitdir: $REAL" >.git)
+       git submodule absorbgitdirs sm2
 '
 
 test_expect_success 'diff --submodule=diff with .git file' '
        git diff --submodule=diff HEAD^ >actual &&
        cat >expected <<-EOF &&
+       diff --git a/.gitmodules b/.gitmodules
+       new file mode 100644
+       index 1234567..89abcde
+       --- /dev/null
+       +++ b/.gitmodules
+       @@ -0,0 +1,3 @@
+       +[submodule "sm2"]
+       +path = sm2
+       +url = bogus_url
        Submodule sm1 $head7...0000000 (submodule deleted)
        Submodule sm2 0000000...$head9 (new submodule)
        diff --git a/sm2/foo8 b/sm2/foo8
@@ -758,9 +779,67 @@ test_expect_success 'diff --submodule=diff with .git file' '
        diff_cmp expected actual
 '
 
+mv sm2 sm2-bak
+
+test_expect_success 'deleted submodule with .git file' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head7...0000000 (submodule deleted)
+       Submodule sm2 $head9...0000000 (submodule deleted)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       deleted file mode 100644
+       index 1234567..89abcde
+       --- a/sm2/foo8
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       deleted file mode 100644
+       index 1234567..89abcde
+       --- a/sm2/foo9
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo9
+       EOF
+       diff_cmp expected actual
+'
+
+echo submodule-to-blob>sm2
+
+test_expect_success 'typechanged(submodule->blob) submodule with .git file' '
+       git diff-index -p --submodule=diff HEAD >actual &&
+       cat >expected <<-EOF &&
+       Submodule sm1 $head7...0000000 (submodule deleted)
+       Submodule sm2 $head9...0000000 (submodule deleted)
+       diff --git a/sm2/foo8 b/sm2/foo8
+       deleted file mode 100644
+       index 1234567..89abcde
+       --- a/sm2/foo8
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       deleted file mode 100644
+       index 1234567..89abcde
+       --- a/sm2/foo9
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo9
+       diff --git a/sm2 b/sm2
+       new file mode 100644
+       index 1234567..89abcde
+       --- /dev/null
+       +++ b/sm2
+       @@ -0,0 +1 @@
+       +submodule-to-blob
+       EOF
+       diff_cmp expected actual
+'
+
+rm sm2
+mv sm2-bak sm2
+
 test_expect_success 'setup nested submodule' '
-       git submodule add -f ./sm2 &&
-       git commit -a -m "add sm2" &&
        git -C sm2 submodule add ../sm2 nested &&
        git -C sm2 commit -a -m "nested sub" &&
        head10=$(git -C sm2 rev-parse --short --verify HEAD)
@@ -791,6 +870,7 @@ test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' '
 
 test_expect_success 'diff --submodule=diff recurses into nested submodules' '
        cat >expected <<-EOF &&
+       Submodule sm1 $head7...0000000 (submodule deleted)
        Submodule sm2 contains modified content
        Submodule sm2 $head9..$head10:
        diff --git a/sm2/.gitmodules b/sm2/.gitmodules
@@ -830,4 +910,67 @@ test_expect_success 'diff --submodule=diff recurses into nested submodules' '
        diff_cmp expected actual
 '
 
+(cd sm2; commit_file nested)
+commit_file sm2
+head12=$(cd sm2; git rev-parse --short --verify HEAD)
+
+mv sm2 sm2-bak
+
+test_expect_success 'diff --submodule=diff recurses into deleted nested submodules' '
+       cat >expected <<-EOF &&
+       Submodule sm1 $head7...0000000 (submodule deleted)
+       Submodule sm2 $head12...0000000 (submodule deleted)
+       diff --git a/sm2/.gitmodules b/sm2/.gitmodules
+       deleted file mode 100644
+       index 3a816b8..0000000
+       --- a/sm2/.gitmodules
+       +++ /dev/null
+       @@ -1,3 +0,0 @@
+       -[submodule "nested"]
+       -       path = nested
+       -       url = ../sm2
+       diff --git a/sm2/foo8 b/sm2/foo8
+       deleted file mode 100644
+       index db9916b..0000000
+       --- a/sm2/foo8
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo8
+       diff --git a/sm2/foo9 b/sm2/foo9
+       deleted file mode 100644
+       index 9c3b4f6..0000000
+       --- a/sm2/foo9
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo9
+       Submodule nested $head11...0000000 (submodule deleted)
+       diff --git a/sm2/nested/file b/sm2/nested/file
+       deleted file mode 100644
+       index ca281f5..0000000
+       --- a/sm2/nested/file
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -nested content
+       diff --git a/sm2/nested/foo8 b/sm2/nested/foo8
+       deleted file mode 100644
+       index db9916b..0000000
+       --- a/sm2/nested/foo8
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo8
+       diff --git a/sm2/nested/foo9 b/sm2/nested/foo9
+       deleted file mode 100644
+       index 9c3b4f6..0000000
+       --- a/sm2/nested/foo9
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -foo9
+       EOF
+       git diff --submodule=diff >actual 2>err &&
+       test_must_be_empty err &&
+       diff_cmp expected actual
+'
+
+mv sm2-bak sm2
+
 test_done
index 9dfead936b7b5be7e0c5a16c2deb90837d7c96d4..6a650dacd6e70b29a32919328a098eb885a3ec5d 100755 (executable)
@@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' '
        git commit -S -m signed_commit
 '
 
+test_expect_success GPGSSH 'setup sshkey signed branch' '
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       test_when_finished "git reset --hard && git checkout main" &&
+       git checkout -b signed-ssh main &&
+       echo foo >foo &&
+       git add foo &&
+       git commit -S -m signed_commit
+'
+
 test_expect_success GPGSM 'log x509 fingerprint' '
        echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
        git log -n1 --format="%GF | %GP" signed-x509 >actual &&
@@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' '
        test_cmp expect actual
 '
 
+test_expect_success GPGSSH 'log ssh key fingerprint' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       ssh-keygen -lf  "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect &&
+       git log -n1 --format="%GF | %GP" signed-ssh >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success GPG 'log --graph --show-signature' '
        git log --graph --show-signature -n1 signed >actual &&
        grep "^| gpg: Signature made" actual &&
@@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' '
        grep "^| gpgsm: Good signature" actual
 '
 
+test_expect_success GPGSSH 'log --graph --show-signature ssh' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git log --graph --show-signature -n1 signed-ssh >actual &&
+       grep "${GOOD_SIGNATURE_TRUSTED}" actual
+'
+
 test_expect_success GPG 'log --graph --show-signature for merged tag' '
        test_when_finished "git reset --hard && git checkout main" &&
        git checkout -b plain main &&
index 7cabb85ca6e128add7c86bc9940e6a7c66c3886c..8ae314af585482ec240fed353613faf6b23ebb67 100755 (executable)
@@ -291,6 +291,7 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' '
                cat ../expected >blob &&
                git add blob &&
                git commit -m "second commit in third" &&
+               git clean -f && # Remove untracked left behind by deleting index
                git reset --hard HEAD^
        ) &&
        git prune --expire=now &&
index b02838750e49a3d782bf7195e26b71dba6ad1902..673baa5c3ccc8a44948280337d55fa67fd28f324 100755 (executable)
@@ -8,6 +8,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . "$TEST_DIRECTORY"/lib-bundle.sh
 . "$TEST_DIRECTORY"/lib-bitmap.sh
 
+# t5310 deals only with single-pack bitmaps, so don't write MIDX bitmaps in
+# their place.
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
 objpath () {
        echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')"
 }
@@ -25,93 +29,10 @@ has_any () {
        grep -Ff "$1" "$2"
 }
 
-# To ensure the logic for "maximal commits" is exercised, make
-# the repository a bit more complicated.
-#
-#    other                         second
-#      *                             *
-# (99 commits)                  (99 commits)
-#      *                             *
-#      |\                           /|
-#      | * octo-other  octo-second * |
-#      |/|\_________  ____________/|\|
-#      | \          \/  __________/  |
-#      |  | ________/\ /             |
-#      *  |/          * merge-right  *
-#      | _|__________/ \____________ |
-#      |/ |                         \|
-# (l1) *  * merge-left               * (r1)
-#      | / \________________________ |
-#      |/                           \|
-# (l2) *                             * (r2)
-#       \___________________________ |
-#                                   \|
-#                                    * (base)
-#
-# We only push bits down the first-parent history, which
-# makes some of these commits unimportant!
-#
-# The important part for the maximal commit algorithm is how
-# the bitmasks are extended. Assuming starting bit positions
-# for second (bit 0) and other (bit 1), the bitmasks at the
-# end should be:
-#
-#      second: 1       (maximal, selected)
-#       other: 01      (maximal, selected)
-#      (base): 11 (maximal)
-#
-# This complicated history was important for a previous
-# version of the walk that guarantees never walking a
-# commit multiple times. That goal might be important
-# again, so preserve this complicated case. For now, this
-# test will guarantee that the bitmaps are computed
-# correctly, even with the repeat calculations.
-
-test_expect_success 'setup repo with moderate-sized history' '
-       test_commit_bulk --id=file 10 &&
-       git branch -M second &&
-       git checkout -b other HEAD~5 &&
-       test_commit_bulk --id=side 10 &&
-
-       # add complicated history setup, including merges and
-       # ambiguous merge-bases
-
-       git checkout -b merge-left other~2 &&
-       git merge second~2 -m "merge-left" &&
-
-       git checkout -b merge-right second~1 &&
-       git merge other~1 -m "merge-right" &&
-
-       git checkout -b octo-second second &&
-       git merge merge-left merge-right -m "octopus-second" &&
-
-       git checkout -b octo-other other &&
-       git merge merge-left merge-right -m "octopus-other" &&
-
-       git checkout other &&
-       git merge octo-other -m "pull octopus" &&
-
-       git checkout second &&
-       git merge octo-second -m "pull octopus" &&
-
-       # Remove these branches so they are not selected
-       # as bitmap tips
-       git branch -D merge-left &&
-       git branch -D merge-right &&
-       git branch -D octo-other &&
-       git branch -D octo-second &&
-
-       # add padding to make these merges less interesting
-       # and avoid having them selected for bitmaps
-       test_commit_bulk --id=file 100 &&
-       git checkout other &&
-       test_commit_bulk --id=side 100 &&
-       git checkout second &&
-
-       bitmaptip=$(git rev-parse second) &&
-       blob=$(echo tagged-blob | git hash-object -w --stdin) &&
-       git tag tagged-blob $blob &&
-       git config repack.writebitmaps true
+setup_bitmap_history
+
+test_expect_success 'setup writing bitmaps during repack' '
+       git config repack.writeBitmaps true
 '
 
 test_expect_success 'full repack creates bitmaps' '
@@ -123,109 +44,7 @@ test_expect_success 'full repack creates bitmaps' '
        grep "\"key\":\"num_maximal_commits\",\"value\":\"107\"" trace
 '
 
-test_expect_success 'rev-list --test-bitmap verifies bitmaps' '
-       git rev-list --test-bitmap HEAD
-'
-
-rev_list_tests_head () {
-       test_expect_success "counting commits via bitmap ($state, $branch)" '
-               git rev-list --count $branch >expect &&
-               git rev-list --use-bitmap-index --count $branch >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "counting partial commits via bitmap ($state, $branch)" '
-               git rev-list --count $branch~5..$branch >expect &&
-               git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "counting commits with limit ($state, $branch)" '
-               git rev-list --count -n 1 $branch >expect &&
-               git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "counting non-linear history ($state, $branch)" '
-               git rev-list --count other...second >expect &&
-               git rev-list --use-bitmap-index --count other...second >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "counting commits with limiting ($state, $branch)" '
-               git rev-list --count $branch -- 1.t >expect &&
-               git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "counting objects via bitmap ($state, $branch)" '
-               git rev-list --count --objects $branch >expect &&
-               git rev-list --use-bitmap-index --count --objects $branch >actual &&
-               test_cmp expect actual
-       '
-
-       test_expect_success "enumerate commits ($state, $branch)" '
-               git rev-list --use-bitmap-index $branch >actual &&
-               git rev-list $branch >expect &&
-               test_bitmap_traversal --no-confirm-bitmaps expect actual
-       '
-
-       test_expect_success "enumerate --objects ($state, $branch)" '
-               git rev-list --objects --use-bitmap-index $branch >actual &&
-               git rev-list --objects $branch >expect &&
-               test_bitmap_traversal expect actual
-       '
-
-       test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
-               git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
-               grep $blob actual
-       '
-}
-
-rev_list_tests () {
-       state=$1
-
-       for branch in "second" "other"
-       do
-               rev_list_tests_head
-       done
-}
-
-rev_list_tests 'full bitmap'
-
-test_expect_success 'clone from bitmapped repository' '
-       git clone --no-local --bare . clone.git &&
-       git rev-parse HEAD >expect &&
-       git --git-dir=clone.git rev-parse HEAD >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success 'partial clone from bitmapped repository' '
-       test_config uploadpack.allowfilter true &&
-       git clone --no-local --bare --filter=blob:none . partial-clone.git &&
-       (
-               cd partial-clone.git &&
-               pack=$(echo objects/pack/*.pack) &&
-               git verify-pack -v "$pack" >have &&
-               awk "/blob/ { print \$1 }" <have >blobs &&
-               # we expect this single blob because of the direct ref
-               git rev-parse refs/tags/tagged-blob >expect &&
-               test_cmp expect blobs
-       )
-'
-
-test_expect_success 'setup further non-bitmapped commits' '
-       test_commit_bulk --id=further 10
-'
-
-rev_list_tests 'partial bitmap'
-
-test_expect_success 'fetch (partial bitmap)' '
-       git --git-dir=clone.git fetch origin second:second &&
-       git rev-parse HEAD >expect &&
-       git --git-dir=clone.git rev-parse HEAD >actual &&
-       test_cmp expect actual
-'
+basic_bitmap_tests
 
 test_expect_success 'incremental repack fails when bitmaps are requested' '
        test_commit more-1 &&
@@ -461,40 +280,6 @@ test_expect_success 'truncated bitmap fails gracefully (cache)' '
        test_i18ngrep corrupted.bitmap.index stderr
 '
 
-test_expect_success 'enumerating progress counts pack-reused objects' '
-       count=$(git rev-list --objects --all --count) &&
-       git repack -adb &&
-
-       # check first with only reused objects; confirm that our progress
-       # showed the right number, and also that we did pack-reuse as expected.
-       # Check only the final "done" line of the meter (there may be an
-       # arbitrary number of intermediate lines ending with CR).
-       GIT_PROGRESS_DELAY=0 \
-               git pack-objects --all --stdout --progress \
-               </dev/null >/dev/null 2>stderr &&
-       grep "Enumerating objects: $count, done" stderr &&
-       grep "pack-reused $count" stderr &&
-
-       # now the same but with one non-reused object
-       git commit --allow-empty -m "an extra commit object" &&
-       GIT_PROGRESS_DELAY=0 \
-               git pack-objects --all --stdout --progress \
-               </dev/null >/dev/null 2>stderr &&
-       grep "Enumerating objects: $((count+1)), done" stderr &&
-       grep "pack-reused $count" stderr
-'
-
-# have_delta <obj> <expected_base>
-#
-# Note that because this relies on cat-file, it might find _any_ copy of an
-# object in the repository. The caller is responsible for making sure
-# there's only one (e.g., via "repack -ad", or having just fetched a copy).
-have_delta () {
-       echo $2 >expect &&
-       echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
-       test_cmp expect actual
-}
-
 # Create a state of history with these properties:
 #
 #  - refs that allow a client to fetch some new history, while sharing some old
index 11423b3cb2fee3d628c350e2856d760069153fde..ea889c088a51f635e1e111f1e55d02cd21b53d0f 100755 (executable)
@@ -7,6 +7,9 @@ if we see, for example, a ref with a bogus name, it is OK either to
 bail out or to proceed using it as a reachable tip, but it is _not_
 OK to proceed as if it did not exist. Otherwise we might silently
 delete objects that cannot be recovered.
+
+Note that we do assert command failure in these cases, because that is
+what currently happens. If that changes, these tests should be revisited.
 '
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@@ -18,39 +21,58 @@ test_expect_success 'disable reflogs' '
        git reflog expire --expire=all --all
 '
 
+create_bogus_ref () {
+       test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
+       echo $bogus >.git/refs/heads/bogus..name
+}
+
 test_expect_success 'create history reachable only from a bogus-named ref' '
        test_tick && git commit --allow-empty -m main &&
        base=$(git rev-parse HEAD) &&
        test_tick && git commit --allow-empty -m bogus &&
        bogus=$(git rev-parse HEAD) &&
        git cat-file commit $bogus >saved &&
-       echo $bogus >.git/refs/heads/bogus..name &&
        git reset --hard HEAD^
 '
 
 test_expect_success 'pruning does not drop bogus object' '
        test_when_finished "git hash-object -w -t commit saved" &&
-       test_might_fail git prune --expire=now &&
-       verbose git cat-file -e $bogus
+       create_bogus_ref &&
+       test_must_fail git prune --expire=now &&
+       git cat-file -e $bogus
 '
 
 test_expect_success 'put bogus object into pack' '
        git tag reachable $bogus &&
        git repack -ad &&
        git tag -d reachable &&
-       verbose git cat-file -e $bogus
+       git cat-file -e $bogus
+'
+
+test_expect_success 'non-destructive repack bails on bogus ref' '
+       create_bogus_ref &&
+       test_must_fail git repack -adk
 '
 
+test_expect_success 'GIT_REF_PARANOIA=0 overrides safety' '
+       create_bogus_ref &&
+       GIT_REF_PARANOIA=0 git repack -adk
+'
+
+
 test_expect_success 'destructive repack keeps packed object' '
-       test_might_fail git repack -Ad --unpack-unreachable=now &&
-       verbose git cat-file -e $bogus &&
-       test_might_fail git repack -ad &&
-       verbose git cat-file -e $bogus
+       create_bogus_ref &&
+       test_must_fail git repack -Ad --unpack-unreachable=now &&
+       git cat-file -e $bogus &&
+       test_must_fail git repack -ad &&
+       git cat-file -e $bogus
 '
 
-# subsequent tests will have different corruptions
-test_expect_success 'clean up bogus ref' '
-       rm .git/refs/heads/bogus..name
+test_expect_success 'destructive repack not confused by dangling symref' '
+       test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+       git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+       git repack -ad &&
+       test_must_fail git cat-file -e $bogus
 '
 
 # We create two new objects here, "one" and "two". Our
@@ -77,8 +99,8 @@ test_expect_success 'create history with missing tip commit' '
 
 test_expect_success 'pruning with a corrupted tip does not drop history' '
        test_when_finished "git hash-object -w -t commit saved" &&
-       test_might_fail git prune --expire=now &&
-       verbose git cat-file -e $recoverable
+       test_must_fail git prune --expire=now &&
+       git cat-file -e $recoverable
 '
 
 test_expect_success 'pack-refs does not silently delete broken loose ref' '
index af88f805aa275b6cd7a0ef4c50819a8538d7b0d1..295c5bd94d23fdc8e9783caa74602216f649a3f6 100755 (executable)
@@ -5,6 +5,25 @@ test_description='commit graph'
 
 GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
 
+test_expect_success 'usage' '
+       test_expect_code 129 git commit-graph write blah 2>err &&
+       test_expect_code 129 git commit-graph write verify
+'
+
+test_expect_success 'usage shown without sub-command' '
+       test_expect_code 129 git commit-graph 2>err &&
+       ! grep error: err
+'
+
+test_expect_success 'usage shown with an error on unknown sub-command' '
+       cat >expect <<-\EOF &&
+       error: unrecognized subcommand: unknown
+       EOF
+       test_expect_code 129 git commit-graph unknown 2>stderr &&
+       grep error stderr >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'setup full repo' '
        mkdir full &&
        cd "$TRASH_DIRECTORY/full" &&
index 3d4d9f10c31b2ff1a0c5bb21458a3ef54113802f..a3c72b68f7cbb3fdc6004eb9d4a925fa500a0325 100755 (executable)
@@ -168,18 +168,33 @@ test_expect_success 'write midx with two packs' '
 
 compare_results_with_midx "two packs"
 
+test_expect_success 'write midx with --stdin-packs' '
+       rm -fr $objdir/pack/multi-pack-index &&
+
+       idx="$(find $objdir/pack -name "test-2-*.idx")" &&
+       basename "$idx" >in &&
+
+       git multi-pack-index write --stdin-packs <in &&
+
+       test-tool read-midx $objdir | grep "\.idx$" >packs &&
+
+       test_cmp packs in
+'
+
+compare_results_with_midx "mixed mode (one pack + extra)"
+
 test_expect_success 'write progress off for redirected stderr' '
        git multi-pack-index --object-dir=$objdir write 2>err &&
        test_line_count = 0 err
 '
 
 test_expect_success 'write force progress on for stderr' '
-       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress write 2>err &&
+       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --progress 2>err &&
        test_file_not_empty err
 '
 
 test_expect_success 'write with the --no-progress option' '
-       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress write 2>err &&
+       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir write --no-progress 2>err &&
        test_line_count = 0 err
 '
 
@@ -201,6 +216,34 @@ test_expect_success 'write midx with twelve packs' '
 
 compare_results_with_midx "twelve packs"
 
+test_expect_success 'multi-pack-index *.rev cleanup with --object-dir' '
+       git init repo &&
+       git clone -s repo alternate &&
+
+       test_when_finished "rm -rf repo alternate" &&
+
+       (
+               cd repo &&
+               test_commit base &&
+               git repack -d
+       ) &&
+
+       ours="alternate/.git/objects/pack/multi-pack-index-123.rev" &&
+       theirs="repo/.git/objects/pack/multi-pack-index-abc.rev" &&
+       touch "$ours" "$theirs" &&
+
+       (
+               cd alternate &&
+               git multi-pack-index --object-dir ../repo/.git/objects write
+       ) &&
+
+       # writing a midx in "repo" should not remove the .rev file in the
+       # alternate
+       test_path_is_file repo/.git/objects/pack/multi-pack-index &&
+       test_path_is_file $ours &&
+       test_path_is_missing $theirs
+'
+
 test_expect_success 'warn on improper hash version' '
        git init --object-format=sha1 sha1 &&
        (
@@ -277,6 +320,23 @@ test_expect_success 'midx picks objects from preferred pack' '
        )
 '
 
+test_expect_success 'preferred packs must be non-empty' '
+       test_when_finished rm -rf preferred.git &&
+       git init preferred.git &&
+       (
+               cd preferred.git &&
+
+               test_commit base &&
+               git repack -ad &&
+
+               empty="$(git pack-objects $objdir/pack/pack </dev/null)" &&
+
+               test_must_fail git multi-pack-index write \
+                       --preferred-pack=pack-$empty.pack 2>err &&
+               grep "with no objects" err
+       )
+'
+
 test_expect_success 'verify multi-pack-index success' '
        git multi-pack-index verify --object-dir=$objdir
 '
@@ -429,12 +489,12 @@ test_expect_success 'repack progress off for redirected stderr' '
 '
 
 test_expect_success 'repack force progress on for stderr' '
-       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --progress repack 2>err &&
+       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --progress 2>err &&
        test_file_not_empty err
 '
 
 test_expect_success 'repack with the --no-progress option' '
-       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir --no-progress repack 2>err &&
+       GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack --no-progress 2>err &&
        test_line_count = 0 err
 '
 
@@ -487,7 +547,8 @@ test_expect_success 'repack preserves multi-pack-index when creating packs' '
 compare_results_with_midx "after repack"
 
 test_expect_success 'multi-pack-index and pack-bitmap' '
-       git -c repack.writeBitmaps=true repack -ad &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -c repack.writeBitmaps=true repack -ad &&
        git multi-pack-index write &&
        git rev-list --test-bitmap HEAD
 '
@@ -537,7 +598,15 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '
        idx64=objects64/pack/test-64-$pack64.idx &&
        chmod u+w $idx64 &&
        corrupt_data $idx64 $(test_oid idxoff) "\02" &&
-       midx64=$(git multi-pack-index --object-dir=objects64 write) &&
+       # objects64 is not a real repository, but can serve as an alternate
+       # anyway so we can write a MIDX into it
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+               ( cd ../objects64 && pwd ) >.git/objects/info/alternates &&
+               midx64=$(git multi-pack-index --object-dir=../objects64 write)
+       ) &&
        midx_read_expect 1 63 5 objects64 " large-offsets"
 '
 
@@ -618,7 +687,7 @@ test_expect_success 'expire progress off for redirected stderr' '
 test_expect_success 'expire force progress on for stderr' '
        (
                cd dup &&
-               GIT_PROGRESS_DELAY=0 git multi-pack-index --progress expire 2>err &&
+               GIT_PROGRESS_DELAY=0 git multi-pack-index expire --progress 2>err &&
                test_file_not_empty err
        )
 '
@@ -626,7 +695,7 @@ test_expect_success 'expire force progress on for stderr' '
 test_expect_success 'expire with the --no-progress option' '
        (
                cd dup &&
-               GIT_PROGRESS_DELAY=0 git multi-pack-index --no-progress expire 2>err &&
+               GIT_PROGRESS_DELAY=0 git multi-pack-index expire --no-progress 2>err &&
                test_line_count = 0 err
        )
 '
@@ -842,4 +911,9 @@ test_expect_success 'usage shown without sub-command' '
        ! test_i18ngrep "unrecognized subcommand" err
 '
 
+test_expect_success 'complains when run outside of a repository' '
+       nongit test_must_fail git multi-pack-index write 2>err &&
+       grep "not a git repository" err
+'
+
 test_done
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
new file mode 100755 (executable)
index 0000000..e187f90
--- /dev/null
@@ -0,0 +1,398 @@
+#!/bin/sh
+
+test_description='exercise basic multi-pack bitmap functionality'
+. ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+
+# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# automatic ones.
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
+# midx_pack_source <obj>
+midx_pack_source () {
+       test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
+}
+
+setup_bitmap_history
+
+test_expect_success 'enable core.multiPackIndex' '
+       git config core.multiPackIndex true
+'
+
+test_expect_success 'create single-pack midx with bitmaps' '
+       git repack -ad &&
+       git multi-pack-index write --bitmap &&
+       test_path_is_file $midx &&
+       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+       test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests
+
+test_expect_success 'create new additional packs' '
+       for i in $(test_seq 1 16)
+       do
+               test_commit "$i" &&
+               git repack -d || return 1
+       done &&
+
+       git checkout -b other2 HEAD~8 &&
+       for i in $(test_seq 1 8)
+       do
+               test_commit "side-$i" &&
+               git repack -d || return 1
+       done &&
+       git checkout second
+'
+
+test_expect_success 'create multi-pack midx with bitmaps' '
+       git multi-pack-index write --bitmap &&
+
+       ls $objdir/pack/pack-*.pack >packs &&
+       test_line_count = 25 packs &&
+
+       test_path_is_file $midx &&
+       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+       test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests
+
+test_expect_success '--no-bitmap is respected when bitmaps exist' '
+       git multi-pack-index write --bitmap &&
+
+       test_commit respect--no-bitmap &&
+       git repack -d &&
+
+       test_path_is_file $midx &&
+       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+       test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+
+       git multi-pack-index write --no-bitmap &&
+
+       test_path_is_file $midx &&
+       test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
+       test_path_is_missing $midx-$(midx_checksum $objdir).rev
+'
+
+test_expect_success 'setup midx with base from later pack' '
+       # Write a and b so that "a" is a delta on top of base "b", since Git
+       # prefers to delete contents out of a base rather than add to a shorter
+       # object.
+       test_seq 1 128 >a &&
+       test_seq 1 130 >b &&
+
+       git add a b &&
+       git commit -m "initial commit" &&
+
+       a=$(git rev-parse HEAD:a) &&
+       b=$(git rev-parse HEAD:b) &&
+
+       # In the first pack, "a" is stored as a delta to "b".
+       p1=$(git pack-objects .git/objects/pack/pack <<-EOF
+       $a
+       $b
+       EOF
+       ) &&
+
+       # In the second pack, "a" is missing, and "b" is not a delta nor base to
+       # any other object.
+       p2=$(git pack-objects .git/objects/pack/pack <<-EOF
+       $b
+       $(git rev-parse HEAD)
+       $(git rev-parse HEAD^{tree})
+       EOF
+       ) &&
+
+       git prune-packed &&
+       # Use the second pack as the preferred source, so that "b" occurs
+       # earlier in the MIDX object order, rendering "a" unusable for pack
+       # reuse.
+       git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
+
+       have_delta $a $b &&
+       test $(midx_pack_source $a) != $(midx_pack_source $b)
+'
+
+rev_list_tests 'full bitmap with backwards delta'
+
+test_expect_success 'clone with bitmaps enabled' '
+       git clone --no-local --bare . clone-reverse-delta.git &&
+       test_when_finished "rm -fr clone-reverse-delta.git" &&
+
+       git rev-parse HEAD >expect &&
+       git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
+       test_cmp expect actual
+'
+
+bitmap_reuse_tests() {
+       from=$1
+       to=$2
+
+       test_expect_success "setup pack reuse tests ($from -> $to)" '
+               rm -fr repo &&
+               git init repo &&
+               (
+                       cd repo &&
+                       test_commit_bulk 16 &&
+                       git tag old-tip &&
+
+                       git config core.multiPackIndex true &&
+                       if test "MIDX" = "$from"
+                       then
+                               git repack -Ad &&
+                               git multi-pack-index write --bitmap
+                       else
+                               git repack -Adb
+                       fi
+               )
+       '
+
+       test_expect_success "build bitmap from existing ($from -> $to)" '
+               (
+                       cd repo &&
+                       test_commit_bulk --id=further 16 &&
+                       git tag new-tip &&
+
+                       if test "MIDX" = "$to"
+                       then
+                               git repack -d &&
+                               git multi-pack-index write --bitmap
+                       else
+                               git repack -Adb
+                       fi
+               )
+       '
+
+       test_expect_success "verify resulting bitmaps ($from -> $to)" '
+               (
+                       cd repo &&
+                       git for-each-ref &&
+                       git rev-list --test-bitmap refs/tags/old-tip &&
+                       git rev-list --test-bitmap refs/tags/new-tip
+               )
+       '
+}
+
+bitmap_reuse_tests 'pack' 'MIDX'
+bitmap_reuse_tests 'MIDX' 'pack'
+bitmap_reuse_tests 'MIDX' 'MIDX'
+
+test_expect_success 'missing object closure fails gracefully' '
+       rm -fr repo &&
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit loose &&
+               test_commit packed &&
+
+               # Do not pass "--revs"; we want a pack without the "loose"
+               # commit.
+               git pack-objects $objdir/pack/pack <<-EOF &&
+               $(git rev-parse packed)
+               EOF
+
+               test_must_fail git multi-pack-index write --bitmap 2>err &&
+               grep "doesn.t have full closure" err &&
+               test_path_is_missing $midx
+       )
+'
+
+test_expect_success 'setup partial bitmaps' '
+       test_commit packed &&
+       git repack &&
+       test_commit loose &&
+       git multi-pack-index write --bitmap 2>err &&
+       test_path_is_file $midx &&
+       test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+       test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests HEAD~
+
+test_expect_success 'removing a MIDX clears stale bitmaps' '
+       rm -fr repo &&
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+               test_commit base &&
+               git repack &&
+               git multi-pack-index write --bitmap &&
+
+               # Write a MIDX and bitmap; remove the MIDX but leave the bitmap.
+               stale_bitmap=$midx-$(midx_checksum $objdir).bitmap &&
+               stale_rev=$midx-$(midx_checksum $objdir).rev &&
+               rm $midx &&
+
+               # Then write a new MIDX.
+               test_commit new &&
+               git repack &&
+               git multi-pack-index write --bitmap &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+               test_path_is_missing $stale_bitmap &&
+               test_path_is_missing $stale_rev
+       )
+'
+
+test_expect_success 'pack.preferBitmapTips' '
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit_bulk --message="%s" 103 &&
+
+               git log --format="%H" >commits.raw &&
+               sort <commits.raw >commits &&
+
+               git log --format="create refs/tags/%s %H" HEAD >refs &&
+               git update-ref --stdin <refs &&
+
+               git multi-pack-index write --bitmap &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >before &&
+               test_line_count = 1 before &&
+
+               perl -ne "printf(\"create refs/tags/include/%d \", $.); print" \
+                       <before | git update-ref --stdin &&
+
+               rm -fr $midx-$(midx_checksum $objdir).bitmap &&
+               rm -fr $midx-$(midx_checksum $objdir).rev &&
+               rm -fr $midx &&
+
+               git -c pack.preferBitmapTips=refs/tags/include \
+                       multi-pack-index write --bitmap &&
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >after &&
+
+               ! test_cmp before after
+       )
+'
+
+test_expect_success 'writing a bitmap with --refs-snapshot' '
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit one &&
+               test_commit two &&
+
+               git rev-parse one >snapshot &&
+
+               git repack -ad &&
+
+               # First, write a MIDX which see both refs/tags/one and
+               # refs/tags/two (causing both of those commits to receive
+               # bitmaps).
+               git multi-pack-index write --bitmap &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               grep "$(git rev-parse one)" bitmaps &&
+               grep "$(git rev-parse two)" bitmaps &&
+
+               rm -fr $midx-$(midx_checksum $objdir).bitmap &&
+               rm -fr $midx-$(midx_checksum $objdir).rev &&
+               rm -fr $midx &&
+
+               # Then again, but with a refs snapshot which only sees
+               # refs/tags/one.
+               git multi-pack-index write --bitmap --refs-snapshot=snapshot &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               grep "$(git rev-parse one)" bitmaps &&
+               ! grep "$(git rev-parse two)" bitmaps
+       )
+'
+
+test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' '
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit_bulk --message="%s" 103 &&
+
+               git log --format="%H" >commits.raw &&
+               sort <commits.raw >commits &&
+
+               git log --format="create refs/tags/%s %H" HEAD >refs &&
+               git update-ref --stdin <refs &&
+
+               git multi-pack-index write --bitmap &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >before &&
+               test_line_count = 1 before &&
+
+               (
+                       grep -vf before commits.raw &&
+                       # mark missing commits as preferred
+                       sed "s/^/+/" before
+               ) >snapshot &&
+
+               rm -fr $midx-$(midx_checksum $objdir).bitmap &&
+               rm -fr $midx-$(midx_checksum $objdir).rev &&
+               rm -fr $midx &&
+
+               git multi-pack-index write --bitmap --refs-snapshot=snapshot &&
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >after &&
+
+               ! test_cmp before after
+       )
+'
+
+test_expect_success 'hash-cache values are propagated from pack bitmaps' '
+       rm -fr repo &&
+       git init repo &&
+       test_when_finished "rm -fr repo" &&
+       (
+               cd repo &&
+
+               test_commit base &&
+               test_commit base2 &&
+               git repack -adb &&
+
+               test-tool bitmap dump-hashes >pack.raw &&
+               test_file_not_empty pack.raw &&
+               sort pack.raw >pack.hashes &&
+
+               test_commit new &&
+               git repack &&
+               git multi-pack-index write --bitmap &&
+
+               test-tool bitmap dump-hashes >midx.raw &&
+               sort midx.raw >midx.hashes &&
+
+               # ensure that every namehash in the pack bitmap can be found in
+               # the midx bitmap (i.e., that there are no oid-namehash pairs
+               # unique to the pack bitmap).
+               comm -23 pack.hashes midx.hashes >dropped.hashes &&
+               test_must_be_empty dropped.hashes
+       )
+'
+
+test_done
index 4db8edd9c85ab1c4b6606f3548e82df3953ca04b..8212ca56dc5b132edb552fba714657eed3579681 100755 (executable)
@@ -662,10 +662,10 @@ test_expect_success 'push does not update local refs on failure' '
 
 test_expect_success 'allow deleting an invalid remote ref' '
 
-       mk_test testrepo heads/main &&
+       mk_test testrepo heads/branch &&
        rm -f testrepo/.git/objects/??/* &&
-       git push testrepo :refs/heads/main &&
-       (cd testrepo && test_must_fail git rev-parse --verify refs/heads/main)
+       git push testrepo :refs/heads/branch &&
+       (cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch)
 
 '
 
@@ -706,25 +706,26 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho
 '
 
 test_expect_success 'deleting dangling ref triggers hooks with correct args' '
-       mk_test_with_hooks testrepo heads/main &&
+       mk_test_with_hooks testrepo heads/branch &&
+       orig=$(git -C testrepo rev-parse refs/heads/branch) &&
        rm -f testrepo/.git/objects/??/* &&
-       git push testrepo :refs/heads/main &&
+       git push testrepo :refs/heads/branch &&
        (
                cd testrepo/.git &&
                cat >pre-receive.expect <<-EOF &&
-               $ZERO_OID $ZERO_OID refs/heads/main
+               $orig $ZERO_OID refs/heads/branch
                EOF
 
                cat >update.expect <<-EOF &&
-               refs/heads/main $ZERO_OID $ZERO_OID
+               refs/heads/branch $orig $ZERO_OID
                EOF
 
                cat >post-receive.expect <<-EOF &&
-               $ZERO_OID $ZERO_OID refs/heads/main
+               $orig $ZERO_OID refs/heads/branch
                EOF
 
                cat >post-update.expect <<-EOF &&
-               refs/heads/main
+               refs/heads/branch
                EOF
 
                test_cmp pre-receive.expect pre-receive.actual &&
index 672001a18bd10bc2b7a0c4eaec01ec9e353788db..93ecfcdd245b4ed86dd71fd6d3bc69fbf2505934 100755 (executable)
@@ -546,15 +546,6 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
        test_cmp expect actual
 '
 
-test_expect_success REBASE_P \
-       'pull.rebase=preserve rebases and merges keep-merge' '
-       git reset --hard before-preserve-rebase &&
-       test_config pull.rebase preserve &&
-       git pull . copy &&
-       test_cmp_rev HEAD^^ copy &&
-       test_cmp_rev HEAD^2 keep-merge
-'
-
 test_expect_success 'pull.rebase=interactive' '
        write_script "$TRASH_DIRECTORY/fake-editor" <<-\EOF &&
        echo I was here >fake.out &&
@@ -598,7 +589,7 @@ test_expect_success '--rebase=false create a new merge commit' '
 
 test_expect_success '--rebase=true rebases and flattens keep-merge' '
        git reset --hard before-preserve-rebase &&
-       test_config pull.rebase preserve &&
+       test_config pull.rebase merges &&
        git pull --rebase=true . copy &&
        test_cmp_rev HEAD^^ copy &&
        echo file3 >expect &&
@@ -606,23 +597,14 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' '
        test_cmp expect actual
 '
 
-test_expect_success REBASE_P \
-       '--rebase=preserve rebases and merges keep-merge' '
-       git reset --hard before-preserve-rebase &&
-       test_config pull.rebase true &&
-       git pull --rebase=preserve . copy &&
-       test_cmp_rev HEAD^^ copy &&
-       test_cmp_rev HEAD^2 keep-merge
-'
-
 test_expect_success '--rebase=invalid fails' '
        git reset --hard before-preserve-rebase &&
        test_must_fail git pull --rebase=invalid . copy
 '
 
-test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' '
+test_expect_success '--rebase overrides pull.rebase=merges and flattens keep-merge' '
        git reset --hard before-preserve-rebase &&
-       test_config pull.rebase preserve &&
+       test_config pull.rebase merges &&
        git pull --rebase . copy &&
        test_cmp_rev HEAD^^ copy &&
        echo file3 >expect &&
index ed11569d8d7ec605198773351e75baeaa232cb1a..2dc75b80db80964a6bcdbc3977be98f815b40dd0 100755 (executable)
@@ -6,6 +6,9 @@ test_description='Recursive "git fetch" for submodules'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 
 pwd=$(pwd)
index d573ca496ab2234e1c5b7a228b9291320bf358c2..3f58b515cee5d8f8ec72d22e363512b5a5a40c1f 100755 (executable)
@@ -5,6 +5,9 @@ test_description='test push with submodules'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 
 test_expect_success setup '
index bba768f5ded1fc9b74a6920db74436335d254559..24d374adbae8846de060ae24db9f92b1e8e5e207 100755 (executable)
@@ -137,6 +137,53 @@ test_expect_success GPG 'signed push sends push certificate' '
        test_cmp expect dst/push-cert-status
 '
 
+test_expect_success GPGSSH 'ssh signed push sends push certificate' '
+       prepare_dst &&
+       mkdir -p dst/.git/hooks &&
+       git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git -C dst config receive.certnonceseed sekrit &&
+       write_script dst/.git/hooks/post-receive <<-\EOF &&
+       # discard the update list
+       cat >/dev/null
+       # record the push certificate
+       if test -n "${GIT_PUSH_CERT-}"
+       then
+               git cat-file blob $GIT_PUSH_CERT >../push-cert
+       fi &&
+
+       cat >../push-cert-status <<E_O_F
+       SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
+       KEY=${GIT_PUSH_CERT_KEY-nokey}
+       STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+       NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
+       NONCE=${GIT_PUSH_CERT_NONCE-nononce}
+       E_O_F
+
+       EOF
+
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+       git push --signed dst noop ff +noff &&
+
+       (
+               cat <<-\EOF &&
+               SIGNER=principal with number 1
+               KEY=FINGERPRINT
+               STATUS=G
+               NONCE_STATUS=OK
+               EOF
+               sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
+       ) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >expect &&
+
+       noop=$(git rev-parse noop) &&
+       ff=$(git rev-parse ff) &&
+       noff=$(git rev-parse noff) &&
+       grep "$noop $ff refs/heads/ff" dst/push-cert &&
+       grep "$noop $noff refs/heads/noff" dst/push-cert &&
+       test_cmp expect dst/push-cert-status
+'
+
 test_expect_success GPG 'inconsistent push options in signed push not allowed' '
        # First, invoke receive-pack with dummy input to obtain its preamble.
        prepare_dst &&
@@ -276,6 +323,60 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
        test_cmp expect dst/push-cert-status
 '
 
+test_expect_success GPGSSH 'fail without key and heed user.signingkey ssh' '
+       test_config gpg.format ssh &&
+       prepare_dst &&
+       mkdir -p dst/.git/hooks &&
+       git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git -C dst config receive.certnonceseed sekrit &&
+       write_script dst/.git/hooks/post-receive <<-\EOF &&
+       # discard the update list
+       cat >/dev/null
+       # record the push certificate
+       if test -n "${GIT_PUSH_CERT-}"
+       then
+               git cat-file blob $GIT_PUSH_CERT >../push-cert
+       fi &&
+
+       cat >../push-cert-status <<E_O_F
+       SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
+       KEY=${GIT_PUSH_CERT_KEY-nokey}
+       STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+       NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
+       NONCE=${GIT_PUSH_CERT_NONCE-nononce}
+       E_O_F
+
+       EOF
+
+       test_config user.email hasnokey@nowhere.com &&
+       test_config gpg.format ssh &&
+       test_config user.signingkey "" &&
+       (
+               sane_unset GIT_COMMITTER_EMAIL &&
+               test_must_fail git push --signed dst noop ff +noff
+       ) &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+       git push --signed dst noop ff +noff &&
+
+       (
+               cat <<-\EOF &&
+               SIGNER=principal with number 1
+               KEY=FINGERPRINT
+               STATUS=G
+               NONCE_STATUS=OK
+               EOF
+               sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
+       ) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >expect &&
+
+       noop=$(git rev-parse noop) &&
+       ff=$(git rev-parse ff) &&
+       noff=$(git rev-parse noff) &&
+       grep "$noop $ff refs/heads/ff" dst/push-cert &&
+       grep "$noop $noff refs/heads/noff" dst/push-cert &&
+       test_cmp expect dst/push-cert-status
+'
+
 test_expect_success GPG 'failed atomic push does not execute GPG' '
        prepare_dst &&
        git -C dst config receive.certnonceseed sekrit &&
index 58c7add7eefad8dc8280590975b71a0b04296633..214228349ad3995e49815850412e9c24bfc58065 100755 (executable)
@@ -5,6 +5,9 @@ test_description='pushing to a repository using push options'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 
 mk_repo_pair () {
index 4e542261621aafbfe26909aa51eed75c0fc571db..f92c79c13266526b52160fb5c1e8eeebed5d8dd6 100755 (executable)
@@ -558,4 +558,13 @@ test_expect_success 'http auth forgets bogus credentials' '
        expect_askpass both user@host
 '
 
+test_expect_success 'client falls back from v2 to v0 to match server' '
+       GIT_TRACE_PACKET=$PWD/trace \
+       GIT_TEST_PROTOCOL_VERSION=2 \
+       git clone $HTTPD_URL/smart_v0/repo.git repo-v0 &&
+       # check for v0; there the HEAD symref is communicated in the capability
+       # line; v2 uses a different syntax on each ref advertisement line
+       grep symref=HEAD:refs/heads/ trace
+'
+
 test_done
diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh
new file mode 100755 (executable)
index 0000000..49faf5e
--- /dev/null
@@ -0,0 +1,161 @@
+#!/bin/sh
+
+test_description='test functionality common to smart fetch & push'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit --no-tag initial
+'
+
+test_expect_success 'git upload-pack --http-backend-info-refs and --advertise-refs are aliased' '
+       git upload-pack --http-backend-info-refs . >expected 2>err.expected &&
+       git upload-pack --advertise-refs . >actual 2>err.actual &&
+       test_cmp err.expected err.actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'git receive-pack --http-backend-info-refs and --advertise-refs are aliased' '
+       git receive-pack --http-backend-info-refs . >expected 2>err.expected &&
+       git receive-pack --advertise-refs . >actual 2>err.actual &&
+       test_cmp err.expected err.actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'git upload-pack --advertise-refs' '
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) HEAD
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       # We only care about GIT_PROTOCOL, not GIT_TEST_PROTOCOL_VERSION
+       sane_unset GIT_PROTOCOL &&
+       GIT_TEST_PROTOCOL_VERSION=2 \
+       git upload-pack --advertise-refs . >out 2>err &&
+
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect &&
+
+       # The --advertise-refs alias works
+       git upload-pack --advertise-refs . >out 2>err &&
+
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v0' '
+       # With no specified protocol
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) HEAD
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       git upload-pack --advertise-refs . >out 2>err &&
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect &&
+
+       # With explicit v0
+       GIT_PROTOCOL=version=0 \
+       git upload-pack --advertise-refs . >out 2>err &&
+       test-tool pkt-line unpack <out >actual 2>err &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v0' '
+       # With no specified protocol
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       git receive-pack --advertise-refs . >out 2>err &&
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect &&
+
+       # With explicit v0
+       GIT_PROTOCOL=version=0 \
+       git receive-pack --advertise-refs . >out 2>err &&
+       test-tool pkt-line unpack <out >actual 2>err &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v1' '
+       # With no specified protocol
+       cat >expect <<-EOF &&
+       version 1
+       $(git rev-parse HEAD) HEAD
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       GIT_PROTOCOL=version=1 \
+       git upload-pack --advertise-refs . >out &&
+
+       test-tool pkt-line unpack <out >actual 2>err &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v1' '
+       # With no specified protocol
+       cat >expect <<-EOF &&
+       version 1
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       GIT_PROTOCOL=version=1 \
+       git receive-pack --advertise-refs . >out &&
+
+       test-tool pkt-line unpack <out >actual 2>err &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+'
+
+test_expect_success 'git upload-pack --advertise-refs: v2' '
+       cat >expect <<-EOF &&
+       version 2
+       agent=FAKE
+       ls-refs=unborn
+       fetch=shallow wait-for-done
+       server-option
+       object-format=$(test_oid algo)
+       object-info
+       0000
+       EOF
+
+       GIT_PROTOCOL=version=2 \
+       GIT_USER_AGENT=FAKE \
+       git upload-pack --advertise-refs . >out 2>err &&
+
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+'
+
+test_expect_success 'git receive-pack --advertise-refs: v2' '
+       # There is no v2 yet for receive-pack, implicit v0
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) $(git symbolic-ref HEAD)
+       0000
+       EOF
+
+       GIT_PROTOCOL=version=2 \
+       git receive-pack --advertise-refs . >out 2>err &&
+
+       test-tool pkt-line unpack <out >actual &&
+       test_must_be_empty err &&
+       test_cmp actual expect
+'
+
+test_done
index 4f92a116e1f0ec48bdf79f1963da32c8ecdcd8dd..fa6b4cca65c8c687bf83f9eaf0ecb5568c9373f6 100755 (executable)
@@ -2,6 +2,9 @@
 
 test_description='pull can handle submodules'
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
index 5bf10261d363ae1b37a106bdbf4f5e14e22813f4..34b3df4027593b58582b4742d36a211aec57a526 100755 (executable)
@@ -35,7 +35,9 @@ test_expect_success 'create a repo to clone' '
 '
 
 test_expect_success 'create objects in repo for later corruption' '
-       test_commit -C foo file
+       test_commit -C foo file &&
+       git -C foo checkout --detach &&
+       test_commit -C foo detached
 '
 
 # source repository given to git clone should be relative to the
index 3a595c0f82c7043a44887bdd8499b490355d675d..d822153e4d29240babbd2904af9527e6f93e6528 100755 (executable)
@@ -16,6 +16,18 @@ test_expect_success 'setup' '
 
 '
 
+test_expect_success 'submodule.stickyRecursiveClone flag manipulates submodule.recurse value' '
+
+       test_config_global submodule.stickyRecursiveClone true &&
+       git clone --recurse-submodules parent clone_recurse_true &&
+       test_cmp_config -C clone_recurse_true true submodule.recurse &&
+
+       test_config_global submodule.stickyRecursiveClone false &&
+       git clone --recurse-submodules parent clone_recurse_false &&
+       test_expect_code 1 git -C clone_recurse_false config --get submodule.recurse
+
+'
+
 test_expect_success 'clone -o' '
 
        git clone -o foo parent clone-o &&
index ed0d911e953e7536f8fb2ce7cc885a3a7dbce167..51705aa86a172edfcdf9f41d9640f93c8667ebb9 100755 (executable)
@@ -91,7 +91,8 @@ test_expect_success 'ridiculously long subject in boundary' '
 
        git fetch long-subject-bundle.bdl &&
 
-       if ! test_have_prereq SHA1
+       algo=$(test_oid algo) &&
+       if test "$algo" != sha1
        then
                echo "@object-format=sha256"
        fi >expect &&
@@ -100,7 +101,7 @@ test_expect_success 'ridiculously long subject in boundary' '
        $(git rev-parse HEAD) HEAD
        EOF
 
-       if test_have_prereq SHA1
+       if test "$algo" = sha1
        then
                head -n 3 long-subject-bundle.bdl
        else
index 930721f053f1d1bb418fec18e547e51d3ae64719..aa1827d841d47779ef3bb081aba8205802395fb2 100755 (executable)
@@ -72,6 +72,37 @@ test_expect_success 'request invalid command' '
        test_i18ngrep "invalid command" err
 '
 
+test_expect_success 'request capability as command' '
+       test-tool pkt-line pack >in <<-EOF &&
+       command=agent
+       object-format=$(test_oid algo)
+       0000
+       EOF
+       test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+       grep invalid.command.*agent err
+'
+
+test_expect_success 'request command as capability' '
+       test-tool pkt-line pack >in <<-EOF &&
+       command=ls-refs
+       object-format=$(test_oid algo)
+       fetch
+       0000
+       EOF
+       test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+       grep unknown.capability err
+'
+
+test_expect_success 'requested command is command=value' '
+       test-tool pkt-line pack >in <<-EOF &&
+       command=ls-refs=whatever
+       object-format=$(test_oid algo)
+       0000
+       EOF
+       test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+       grep invalid.command.*ls-refs=whatever err
+'
+
 test_expect_success 'wrong object-format' '
        test-tool pkt-line pack >in <<-EOF &&
        command=fetch
@@ -116,6 +147,19 @@ test_expect_success 'basics of ls-refs' '
        test_cmp expect actual
 '
 
+test_expect_success 'ls-refs complains about unknown options' '
+       test-tool pkt-line pack >in <<-EOF &&
+       command=ls-refs
+       object-format=$(test_oid algo)
+       0001
+       no-such-arg
+       0000
+       EOF
+
+       test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in &&
+       grep unexpected.line.*no-such-arg err
+'
+
 test_expect_success 'basic ref-prefixes' '
        test-tool pkt-line pack >in <<-EOF &&
        command=ls-refs
@@ -158,6 +202,37 @@ test_expect_success 'refs/heads prefix' '
        test_cmp expect actual
 '
 
+test_expect_success 'ignore very large set of prefixes' '
+       # generate a large number of ref-prefixes that we expect
+       # to match nothing; the value here exceeds TOO_MANY_PREFIXES
+       # from ls-refs.c.
+       {
+               echo command=ls-refs &&
+               echo object-format=$(test_oid algo) &&
+               echo 0001 &&
+               perl -le "print \"ref-prefix refs/heads/\$_\" for (1..65536)" &&
+               echo 0000
+       } |
+       test-tool pkt-line pack >in &&
+
+       # and then confirm that we see unmatched prefixes anyway (i.e.,
+       # that the prefix was not applied).
+       cat >expect <<-EOF &&
+       $(git rev-parse HEAD) HEAD
+       $(git rev-parse refs/heads/dev) refs/heads/dev
+       $(git rev-parse refs/heads/main) refs/heads/main
+       $(git rev-parse refs/heads/release) refs/heads/release
+       $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
+       $(git rev-parse refs/tags/one) refs/tags/one
+       $(git rev-parse refs/tags/two) refs/tags/two
+       0000
+       EOF
+
+       test-tool serve-v2 --stateless-rpc <in >out &&
+       test-tool pkt-line unpack <out >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'peel parameter' '
        test-tool pkt-line pack >in <<-EOF &&
        command=ls-refs
index 12def7bcbf91949a90066b04cb0b9fddeb3f656d..ef849e5bc88b88a9630671ea2d90a03436dc6c55 100755 (executable)
@@ -169,4 +169,35 @@ test_expect_success 'rev-list --count --objects' '
        test_line_count = $count actual
 '
 
+test_expect_success 'rev-list --unsorted-input results in different sorting' '
+       git rev-list --unsorted-input HEAD HEAD~ >first &&
+       git rev-list --unsorted-input HEAD~ HEAD >second &&
+       ! test_cmp first second &&
+       sort first >first.sorted &&
+       sort second >second.sorted &&
+       test_cmp first.sorted second.sorted
+'
+
+test_expect_success 'rev-list --unsorted-input incompatible with --no-walk' '
+       cat >expect <<-EOF &&
+               fatal: --no-walk is incompatible with --unsorted-input
+       EOF
+       test_must_fail git rev-list --unsorted-input --no-walk HEAD 2>error &&
+       test_cmp expect error &&
+       test_must_fail git rev-list --unsorted-input --no-walk=sorted HEAD 2>error &&
+       test_cmp expect error &&
+       test_must_fail git rev-list --unsorted-input --no-walk=unsorted HEAD 2>error &&
+       test_cmp expect error &&
+
+       cat >expect <<-EOF &&
+               fatal: --unsorted-input is incompatible with --no-walk
+       EOF
+       test_must_fail git rev-list --no-walk --unsorted-input HEAD 2>error &&
+       test_cmp expect error &&
+       test_must_fail git rev-list --no-walk=sorted --unsorted-input HEAD 2>error &&
+       test_cmp expect error &&
+       test_must_fail git rev-list --no-walk=unsorted --unsorted-input HEAD 2>error &&
+       test_cmp expect error
+'
+
 test_done
index 90d93f77fa79c2694ebd9b2229c8620859e6a36a..7294147334a92e6be082ad57177adb6e0857facc 100755 (executable)
@@ -23,7 +23,8 @@ test_expect_success setup '
        git commit -a -m "Third in one history." &&
        A2=$(git rev-parse --verify HEAD) &&
 
-       rm -f .git/refs/heads/main .git/index &&
+       git update-ref -d refs/heads/main &&
+       rm -f .git/index &&
 
        echo >fileA fileA again &&
        echo >subdir/fileB fileB again &&
index a1baf4e4511d33e5519a5e4feb5423342203fe36..1be85d064e7612526a66f0f642a424ed1da12f89 100755 (executable)
@@ -962,4 +962,22 @@ test_expect_success 'bisect handles annotated tags' '
        grep "$bad is the first bad commit" output
 '
 
+test_expect_success 'bisect run fails with exit code equals or greater than 128' '
+       write_script test_script.sh <<-\EOF &&
+       exit 128
+       EOF
+       test_must_fail git bisect run ./test_script.sh &&
+       write_script test_script.sh <<-\EOF &&
+       exit 255
+       EOF
+       test_must_fail git bisect run ./test_script.sh
+'
+
+test_expect_success 'bisect visualize with a filename with dash and space' '
+       echo "My test line" >>"./-hello 2" &&
+       git add -- "./-hello 2" &&
+       git commit --quiet -m "Add test line" -- "./-hello 2" &&
+       git bisect visualize -p -- "-hello 2"
+'
+
 test_done
index e33d512ec11e844147380cabbbada45e31e4d65b..2500acc2ef80f9679105adfa73a805d06e14180c 100755 (executable)
@@ -132,7 +132,7 @@ tagger T A Gger <> 0 +0000
 EOF
 
 test_expect_success 'tag replaced commit' '
-     git mktag <tag.sig >.git/refs/tags/mytag
+     git update-ref refs/tags/mytag $(git mktag <tag.sig)
 '
 
 test_expect_success '"git fsck" works' '
index 1a501ee09e1b43dd30a19e47434cc6b88d21a9b3..bae2419150b8b52244dda726a4d2eaf10c1405ce 100755 (executable)
@@ -107,7 +107,8 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' '
 check_describe tags/A --all A^0
 
 test_expect_success 'renaming tag A to Q locally produces a warning' "
-       mv .git/refs/tags/A .git/refs/tags/Q &&
+       git update-ref refs/tags/Q $(git rev-parse refs/tags/A) &&
+       git update-ref -d refs/tags/A &&
        git describe HEAD 2>err >out &&
        cat >expected <<-\EOF &&
        warning: tag 'Q' is externally known as 'A'
@@ -135,7 +136,8 @@ test_expect_success 'abbrev=0 will not break misplaced tag (2)' '
 '
 
 test_expect_success 'rename tag Q back to A' '
-       mv .git/refs/tags/Q .git/refs/tags/A
+       git update-ref refs/tags/A $(git rev-parse refs/tags/Q) &&
+       git update-ref -d refs/tags/Q
 '
 
 test_expect_success 'pack tag refs' 'git pack-refs'
index 44f55d93fefbc766feca7e264df9319f7591feb9..06c5fb56157f28359b47c04818e2a51f9098ee36 100755 (executable)
@@ -81,6 +81,16 @@ test_expect_success GPG 'set up a signed tag' '
        git tag -s -m signed-tag-msg signed-good-tag left
 '
 
+test_expect_success GPGSSH 'created ssh signed commit and tag' '
+       test_config gpg.format ssh &&
+       git checkout -b signed-ssh &&
+       touch file &&
+       git add file &&
+       git commit -m "ssh signed" -S"${GPGSSH_KEY_PRIMARY}" &&
+       git tag -s -u"${GPGSSH_KEY_PRIMARY}" -m signed-ssh-tag-msg signed-good-ssh-tag left &&
+       git tag -s -u"${GPGSSH_KEY_UNTRUSTED}" -m signed-ssh-tag-msg-untrusted signed-untrusted-ssh-tag left
+'
+
 test_expect_success 'message for merging local branch' '
        echo "Merge branch ${apos}left${apos}" >expected &&
 
@@ -109,6 +119,24 @@ test_expect_success GPG 'message for merging local tag signed by unknown key' '
        grep -E "^# gpg: Can${apos}t check signature: (public key not found|No public key)" actual
 '
 
+test_expect_success GPGSSH 'message for merging local tag signed by good ssh key' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git checkout main &&
+       git fetch . signed-good-ssh-tag &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+       ! grep "${GPGSSH_BAD_SIGNATURE}" actual
+'
+
+test_expect_success GPGSSH 'message for merging local tag signed by unknown ssh key' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git checkout main &&
+       git fetch . signed-untrusted-ssh-tag &&
+       git fmt-merge-msg <.git/FETCH_HEAD >actual 2>&1 &&
+       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+       grep "${GPGSSH_KEY_NOT_TRUSTED}" actual
+'
 test_expect_success 'message for merging external branch' '
        echo "Merge branch ${apos}left${apos} of $(pwd)" >expected &&
 
index 05a15a933a9a6d48bd99a2503b8e03fe9c6c646a..80679d5e12d49328fea6bd4d8e0b7929b68d9bea 100755 (executable)
@@ -137,6 +137,8 @@ test_atom head parent:short=10 ''
 test_atom head numparent 0
 test_atom head object ''
 test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
 test_atom head '*objectname' ''
 test_atom head '*objecttype' ''
 test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
@@ -228,6 +230,15 @@ test_atom tag contents 'Tagging at 1151968727
 '
 test_atom tag HEAD ' '
 
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+       git cat-file commit refs/tags/testtag^{} >expected &&
+       git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
+       sanitize_pgp <expected >expected.clean &&
+       echo >>expected.clean &&
+       sanitize_pgp <actual >actual.clean &&
+       test_cmp expected.clean actual.clean
+'
+
 test_expect_success 'Check invalid atoms names are errors' '
        test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
 '
@@ -693,6 +704,15 @@ test_atom refs/tags/signed-empty contents:body ''
 test_atom refs/tags/signed-empty contents:signature "$sig"
 test_atom refs/tags/signed-empty contents "$sig"
 
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+       git cat-file tag refs/tags/signed-empty >expected &&
+       git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
+       sanitize_pgp <expected >expected.clean &&
+       echo >>expected.clean &&
+       sanitize_pgp <actual >actual.clean &&
+       test_cmp expected.clean actual.clean
+'
+
 test_atom refs/tags/signed-short subject 'subject line'
 test_atom refs/tags/signed-short subject:sanitize 'subject-line'
 test_atom refs/tags/signed-short contents:subject 'subject line'
@@ -702,6 +722,15 @@ test_atom refs/tags/signed-short contents:signature "$sig"
 test_atom refs/tags/signed-short contents "subject line
 $sig"
 
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+       git cat-file tag refs/tags/signed-short >expected &&
+       git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
+       sanitize_pgp <expected >expected.clean &&
+       echo >>expected.clean &&
+       sanitize_pgp <actual >actual.clean &&
+       test_cmp expected.clean actual.clean
+'
+
 test_atom refs/tags/signed-long subject 'subject line'
 test_atom refs/tags/signed-long subject:sanitize 'subject-line'
 test_atom refs/tags/signed-long contents:subject 'subject line'
@@ -715,6 +744,15 @@ test_atom refs/tags/signed-long contents "subject line
 body contents
 $sig"
 
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+       git cat-file tag refs/tags/signed-long >expected &&
+       git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
+       sanitize_pgp <expected >expected.clean &&
+       echo >>expected.clean &&
+       sanitize_pgp <actual >actual.clean &&
+       test_cmp expected.clean actual.clean
+'
+
 test_expect_success 'set up refs pointing to tree and blob' '
        git update-ref refs/mytrees/first refs/heads/main^{tree} &&
        git update-ref refs/myblobs/first refs/heads/main:one
@@ -727,6 +765,16 @@ test_atom refs/mytrees/first contents:body ""
 test_atom refs/mytrees/first contents:signature ""
 test_atom refs/mytrees/first contents ""
 
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+       git cat-file tree refs/mytrees/first >expected &&
+       echo >>expected &&
+       git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
+       test_cmp expected actual &&
+       git cat-file -s refs/mytrees/first >expected &&
+       git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
+       test_cmp expected actual
+'
+
 test_atom refs/myblobs/first subject ""
 test_atom refs/myblobs/first contents:subject ""
 test_atom refs/myblobs/first body ""
@@ -734,6 +782,189 @@ test_atom refs/myblobs/first contents:body ""
 test_atom refs/myblobs/first contents:signature ""
 test_atom refs/myblobs/first contents ""
 
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+       git cat-file blob refs/myblobs/first >expected &&
+       echo >>expected &&
+       git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
+       test_cmp expected actual &&
+       git cat-file -s refs/myblobs/first >expected &&
+       git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+       printf "a\0b\0c" >blob1 &&
+       printf "a\0c\0b" >blob2 &&
+       printf "\0a\0b\0c" >blob3 &&
+       printf "abc" >blob4 &&
+       printf "\0 \0 \0 " >blob5 &&
+       printf "\0 \0a\0 " >blob6 &&
+       printf "  " >blob7 &&
+       >blob8 &&
+       obj=$(git hash-object -w blob1) &&
+       git update-ref refs/myblobs/blob1 "$obj" &&
+       obj=$(git hash-object -w blob2) &&
+       git update-ref refs/myblobs/blob2 "$obj" &&
+       obj=$(git hash-object -w blob3) &&
+       git update-ref refs/myblobs/blob3 "$obj" &&
+       obj=$(git hash-object -w blob4) &&
+       git update-ref refs/myblobs/blob4 "$obj" &&
+       obj=$(git hash-object -w blob5) &&
+       git update-ref refs/myblobs/blob5 "$obj" &&
+       obj=$(git hash-object -w blob6) &&
+       git update-ref refs/myblobs/blob6 "$obj" &&
+       obj=$(git hash-object -w blob7) &&
+       git update-ref refs/myblobs/blob7 "$obj" &&
+       obj=$(git hash-object -w blob8) &&
+       git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+       cat >expected <<-EOF &&
+       refs/myblobs/blob8
+       refs/myblobs/blob5
+       refs/myblobs/blob6
+       refs/myblobs/blob3
+       refs/myblobs/blob7
+       refs/mytrees/first
+       refs/myblobs/first
+       refs/myblobs/blob1
+       refs/myblobs/blob2
+       refs/myblobs/blob4
+       refs/heads/main
+       EOF
+       git for-each-ref --format="%(refname)" --sort=raw \
+               refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+       cat >expected <<-EOF &&
+       refs/myblobs/blob8
+       refs/myblobs/first
+       refs/myblobs/blob7
+       refs/heads/main
+       refs/myblobs/blob4
+       refs/myblobs/blob1
+       refs/myblobs/blob2
+       refs/myblobs/blob3
+       refs/myblobs/blob5
+       refs/myblobs/blob6
+       refs/mytrees/first
+       EOF
+       git for-each-ref --format="%(refname)" --sort=raw:size \
+               refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+       cat >expected <<-EOF &&
+       not equals
+       not equals
+       not equals
+       not equals
+       not equals
+       not equals
+       refs/myblobs/blob4
+       not equals
+       not equals
+       not equals
+       not equals
+       not equals
+       EOF
+       git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+               refs/myblobs/ refs/heads/ >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+       cat >expected <<-EOF &&
+       refs/heads/ambiguous
+       refs/heads/main
+       refs/heads/newtag
+       refs/myblobs/blob1
+       refs/myblobs/blob2
+       refs/myblobs/blob3
+       equals
+       refs/myblobs/blob5
+       refs/myblobs/blob6
+       refs/myblobs/blob7
+       refs/myblobs/blob8
+       refs/myblobs/first
+       EOF
+       git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+               refs/myblobs/ refs/heads/ >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+       cat >expected <<-EOF &&
+       refs/myblobs/blob1 not empty
+       refs/myblobs/blob2 not empty
+       refs/myblobs/blob3 not empty
+       refs/myblobs/blob4 not empty
+       refs/myblobs/blob5 not empty
+       refs/myblobs/blob6 not empty
+       refs/myblobs/blob7 empty
+       refs/myblobs/blob8 empty
+       refs/myblobs/first not empty
+       EOF
+       git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+               refs/myblobs/ >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+       test_must_fail git for-each-ref --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+       test_must_fail git for-each-ref --format="%(raw)" --tcl
+'
+
+test_expect_success '%(raw) with --perl' '
+       git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+       cmp blob1 actual &&
+       git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+       cmp blob3 actual &&
+       git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+       cmp blob8 actual &&
+       git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+       cmp one actual &&
+       git cat-file tree refs/mytrees/first > expected &&
+       git for-each-ref --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+       cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+       test_must_fail git for-each-ref --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+       test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+       git for-each-ref --format="%(raw:size)" | while read line
+       do
+               echo "'\''$line'\''" >>expect
+       done &&
+       git for-each-ref --format="%(raw:size)" --shell >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'for-each-ref --format compare with cat-file --batch' '
+       git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+       git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'set up multiple-sort tags' '
        for when in 100000 200000
        do
@@ -987,6 +1218,10 @@ test_expect_success 'basic atom: head contents:trailers' '
        test_cmp expect actual.clean
 '
 
+test_expect_success 'basic atom: rest must fail' '
+       test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
+'
+
 test_expect_success 'trailer parsing not fooled by --- line' '
        git commit --allow-empty -F - <<-\EOF &&
        this is the subject
index 2ce104aca7cc2923aa88d191efec2894d12eb86e..2655e295f5ae4536eaf86dcf95e8062f61cc2948 100755 (executable)
@@ -25,7 +25,8 @@ test_expect_success 'checkout does not clobber untracked symlink' '
        git reset --hard main &&
        git rm --cached a/b &&
        git commit -m "untracked symlink remains" &&
-       test_must_fail git checkout start^0
+       test_must_fail git checkout start^0 &&
+       git clean -fd    # Do not leave the untracked symlink in the way
 '
 
 test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
@@ -34,7 +35,8 @@ test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' '
        git rm --cached a/b &&
        git commit -m "untracked symlink remains" &&
        git checkout -f start^0 &&
-       test_path_is_file a/b-2/c/d
+       test_path_is_file a/b-2/c/d &&
+       git clean -fd    # Do not leave the untracked symlink in the way
 '
 
 test_expect_success 'checkout should not have deleted a/b-2/c/d' '
index 5e3779ebc9310bfb25d0c6579f234ecf4fabd12e..89dd544f3880c46b4207220427da86e1580c5789 100755 (executable)
@@ -132,6 +132,7 @@ test_expect_success 'merge-recursive, when index==head but head!=HEAD' '
 
        # Make index match B
        git diff C B -- | git apply --cached &&
+       test_when_finished "git clean -fd" &&  # Do not leave untracked around
        # Merge B & F, with B as "head"
        git merge-recursive A -- B F > out &&
        test_i18ngrep "Already up to date" out
index ffcc01fe65d017caa3fad22968a7fd6d35dd4a37..a0efe7cb6dbe77215f0b2bd220f6974ad0c17ec0 100755 (executable)
@@ -718,7 +718,9 @@ test_expect_success 'merge-recursive remembers the names of all base trees' '
        # merge-recursive prints in reverse order, but we do not care
        sort <trees >expect &&
        sed -n "s/^virtual //p" out | sort >actual &&
-       test_cmp expect actual
+       test_cmp expect actual &&
+
+       git clean -fd
 '
 
 test_expect_success 'merge-recursive internal merge resolves to the sameness' '
index 84b4aacf496d1df73c6adf83860db430bc1c1c4c..c0b7bd7c3fe55303d62caed5f4c02368d2e3fd3c 100755 (executable)
@@ -68,7 +68,8 @@ test_expect_success 'will not overwrite removed file' '
        git commit -m "rm c1.c" &&
        cp important c1.c &&
        test_must_fail git merge c1a &&
-       test_cmp important c1.c
+       test_cmp important c1.c &&
+       rm c1.c  # Do not leave untracked file in way of future tests
 '
 
 test_expect_success 'will not overwrite re-added file' '
index e5e89c2045e714aca5e52d7612ba6bed67fa3c5e..178413c22f0dc1dd03152ec1d84843e070d1d843 100755 (executable)
@@ -5,6 +5,9 @@ test_description='merging with submodules'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-merge.sh
 
index 10c7ae7f09ce6d617aaf51b273c6d276dbf1e948..c2021267f2c558151567f274251844dd4abafb5c 100755 (executable)
@@ -241,7 +241,7 @@ test_expect_success 'background auto gc respects lock for all operations' '
 
        # create a ref whose loose presence we can use to detect a pack-refs run
        git update-ref refs/heads/should-be-loose HEAD &&
-       test_path_is_file .git/refs/heads/should-be-loose &&
+       (ls -1 .git/refs/heads .git/reftable >expect || true) &&
 
        # now fake a concurrent gc that holds the lock; we can use our
        # shell pid so that it looks valid.
@@ -258,7 +258,8 @@ test_expect_success 'background auto gc respects lock for all operations' '
 
        # our gc should exit zero without doing anything
        run_and_wait_for_auto_gc &&
-       test_path_is_file .git/refs/heads/should-be-loose
+       (ls -1 .git/refs/heads .git/reftable >actual || true) &&
+       test_cmp expect actual
 '
 
 # DO NOT leave a detached auto gc process running near the end of the
diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh
new file mode 100755 (executable)
index 0000000..5457489
--- /dev/null
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+test_description='git mv in sparse working trees'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' "
+       mkdir -p sub/dir sub/dir2 &&
+       touch a b c sub/d sub/dir/e sub/dir2/e &&
+       git add -A &&
+       git commit -m files &&
+
+       cat >sparse_error_header <<-EOF &&
+       The following paths and/or pathspecs matched paths that exist
+       outside of your sparse-checkout definition, so will not be
+       updated in the index:
+       EOF
+
+       cat >sparse_hint <<-EOF
+       hint: If you intend to update such entries, try one of the following:
+       hint: * Use the --sparse option.
+       hint: * Disable or modify the sparsity rules.
+       hint: Disable this message with \"git config advice.updateSparsePath false\"
+       EOF
+"
+
+test_expect_success 'mv refuses to move sparse-to-sparse' '
+       test_when_finished rm -f e &&
+       git reset --hard &&
+       git sparse-checkout set a &&
+       touch b &&
+       test_must_fail git mv b e 2>stderr &&
+       cat sparse_error_header >expect &&
+       echo b >>expect &&
+       echo e >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse b e 2>stderr &&
+       test_must_be_empty stderr
+'
+
+test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' '
+       test_when_finished rm -f b c e &&
+       git reset --hard &&
+       git sparse-checkout set a &&
+
+       # tracked-to-untracked
+       touch b &&
+       git mv -k b e 2>stderr &&
+       test_path_exists b &&
+       test_path_is_missing e &&
+       cat sparse_error_header >expect &&
+       echo b >>expect &&
+       echo e >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+
+       git mv --sparse b e 2>stderr &&
+       test_must_be_empty stderr &&
+       test_path_is_missing b &&
+       test_path_exists e &&
+
+       # tracked-to-tracked
+       git reset --hard &&
+       touch b &&
+       git mv -k b c 2>stderr &&
+       test_path_exists b &&
+       test_path_is_missing c &&
+       cat sparse_error_header >expect &&
+       echo b >>expect &&
+       echo c >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+
+       git mv --sparse b c 2>stderr &&
+       test_must_be_empty stderr &&
+       test_path_is_missing b &&
+       test_path_exists c
+'
+
+test_expect_success 'mv refuses to move non-sparse-to-sparse' '
+       test_when_finished rm -f b c e &&
+       git reset --hard &&
+       git sparse-checkout set a &&
+
+       # tracked-to-untracked
+       test_must_fail git mv a e 2>stderr &&
+       test_path_exists a &&
+       test_path_is_missing e &&
+       cat sparse_error_header >expect &&
+       echo e >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse a e 2>stderr &&
+       test_must_be_empty stderr &&
+       test_path_is_missing a &&
+       test_path_exists e &&
+
+       # tracked-to-tracked
+       rm e &&
+       git reset --hard &&
+       test_must_fail git mv a c 2>stderr &&
+       test_path_exists a &&
+       test_path_is_missing c &&
+       cat sparse_error_header >expect &&
+       echo c >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse a c 2>stderr &&
+       test_must_be_empty stderr &&
+       test_path_is_missing a &&
+       test_path_exists c
+'
+
+test_expect_success 'mv refuses to move sparse-to-non-sparse' '
+       test_when_finished rm -f b c e &&
+       git reset --hard &&
+       git sparse-checkout set a e &&
+
+       # tracked-to-untracked
+       touch b &&
+       test_must_fail git mv b e 2>stderr &&
+       cat sparse_error_header >expect &&
+       echo b >>expect &&
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse b e 2>stderr &&
+       test_must_be_empty stderr
+'
+
+test_expect_success 'recursive mv refuses to move (possible) sparse' '
+       test_when_finished rm -rf b c e sub2 &&
+       git reset --hard &&
+       # Without cone mode, "sub" and "sub2" do not match
+       git sparse-checkout set sub/dir sub2/dir &&
+
+       # Add contained contents to ensure we avoid non-existence errors
+       mkdir sub/dir2 &&
+       touch sub/d sub/dir2/e &&
+
+       test_must_fail git mv sub sub2 2>stderr &&
+       cat sparse_error_header >expect &&
+       cat >>expect <<-\EOF &&
+       sub/d
+       sub2/d
+       sub/dir/e
+       sub2/dir/e
+       sub/dir2/e
+       sub2/dir2/e
+       EOF
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse sub sub2 2>stderr &&
+       test_must_be_empty stderr &&
+       git commit -m "moved sub to sub2" &&
+       git rev-parse HEAD~1:sub >expect &&
+       git rev-parse HEAD:sub2 >actual &&
+       test_cmp expect actual &&
+       git reset --hard HEAD~1
+'
+
+test_expect_success 'recursive mv refuses to move sparse' '
+       git reset --hard &&
+       # Use cone mode so "sub/" matches the sparse-checkout patterns
+       git sparse-checkout init --cone &&
+       git sparse-checkout set sub/dir sub2/dir &&
+
+       # Add contained contents to ensure we avoid non-existence errors
+       mkdir sub/dir2 &&
+       touch sub/dir2/e &&
+
+       test_must_fail git mv sub sub2 2>stderr &&
+       cat sparse_error_header >expect &&
+       cat >>expect <<-\EOF &&
+       sub/dir2/e
+       sub2/dir2/e
+       EOF
+       cat sparse_hint >>expect &&
+       test_cmp expect stderr &&
+       git mv --sparse sub sub2 2>stderr &&
+       test_must_be_empty stderr &&
+       git commit -m "moved sub to sub2" &&
+       git rev-parse HEAD~1:sub >expect &&
+       git rev-parse HEAD:sub2 >actual &&
+       test_cmp expect actual &&
+       git reset --hard HEAD~1
+'
+
+test_done
index 2f72c5c6883e3e45bbc9602f3e85e1dd9ae3fd01..082be85dffc7b1765471e11d2c580c853cf0965b 100755 (executable)
@@ -1998,6 +1998,10 @@ test_expect_success '--format should list tags as per format given' '
        test_cmp expect actual
 '
 
+test_expect_success 'git tag -l with --format="%(rest)" must fail' '
+       test_must_fail git tag -l --format="%(rest)" "v1*"
+'
+
 test_expect_success "set up color tests" '
        echo "<RED>v1.0<RESET>" >expect.color &&
        echo "v1.0" >expect.bare &&
index 3cefde9602bf73b7be1a457556d0d11713a47e4f..10faa645157ea4521e370abdfd470c84a24f6051 100755 (executable)
@@ -194,6 +194,10 @@ test_expect_success GPG 'verifying tag with --format' '
        test_cmp expect actual
 '
 
+test_expect_success GPG 'verifying tag with --format="%(rest)" must fail' '
+       test_must_fail git verify-tag --format="%(rest)" "fourth-signed"
+'
+
 test_expect_success GPG 'verifying a forged tag with --format should fail silently' '
        test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
        test_must_be_empty actual-forged
diff --git a/t/t7031-verify-tag-signed-ssh.sh b/t/t7031-verify-tag-signed-ssh.sh
new file mode 100755 (executable)
index 0000000..06c9dd6
--- /dev/null
@@ -0,0 +1,161 @@
+#!/bin/sh
+
+test_description='signed tag tests'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPGSSH 'create signed tags ssh' '
+       test_when_finished "test_unconfig commit.gpgsign" &&
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+       echo 1 >file && git add file &&
+       test_tick && git commit -m initial &&
+       git tag -s -m initial initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -m second &&
+       git tag -s -m second second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout main &&
+       test_tick && git merge -S side &&
+       git tag -s -m merge merge &&
+
+       echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" &&
+       git tag -a -m fourth-unsigned fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag -s -m fourth fourth-signed &&
+
+       echo 5 >file && test_tick && git commit -a -m "fifth" &&
+       git tag fifth-unsigned &&
+
+       git config commit.gpgsign true &&
+       echo 6 >file && test_tick && git commit -a -m "sixth" &&
+       git tag -a -m sixth sixth-unsigned &&
+
+       test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ &&
+       git tag -m seventh -s seventh-signed &&
+
+       echo 8 >file && test_tick && git commit -a -m eighth &&
+       git tag -u"${GPGSSH_KEY_UNTRUSTED}" -m eighth eighth-signed-alt
+'
+
+test_expect_success GPGSSH 'verify and show ssh signatures' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag $tag 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag $tag 2>actual &&
+                       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       test_must_fail git verify-tag $tag 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPGSSH 'detect fudged ssh signature' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git cat-file tag seventh-signed >raw &&
+       sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
+       git hash-object -w -t tag forged1 >forged1.tag &&
+       test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
+       grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
+       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
+       ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
+'
+
+test_expect_success GPGSSH 'verify ssh signatures with --raw' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag --raw $tag 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag --raw $tag 2>actual &&
+                       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       test_must_fail git verify-tag --raw $tag 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPGSSH 'verify signatures with --raw ssh' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git verify-tag --raw sixth-signed 2>actual &&
+       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+       echo sixth-signed OK
+'
+
+test_expect_success GPGSSH 'verify multiple tags ssh' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       tags="seventh-signed sixth-signed" &&
+       for i in $tags
+       do
+               git verify-tag -v --raw $i || return 1
+       done >expect.stdout 2>expect.stderr.1 &&
+       grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <expect.stderr.1 >expect.stderr &&
+       git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 &&
+       grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <actual.stderr.1 >actual.stderr &&
+       test_cmp expect.stdout actual.stdout &&
+       test_cmp expect.stderr actual.stderr
+'
+
+test_expect_success GPGSSH 'verifying tag with --format - ssh' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       cat >expect <<-\EOF &&
+       tagname : fourth-signed
+       EOF
+       git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'verifying a forged tag with --format should fail silently - ssh' '
+       test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
+       test_must_be_empty actual-forged
+'
+
+test_done
index 4613882caffc3f1fe91d65426fddee825fc09e46..eeb0534163db176442d38d19d0afb186cd985666 100755 (executable)
@@ -373,10 +373,7 @@ test_expect_success 'verify upstream fields in branch header' '
 
                ## Test upstream-gone case. Fake this by pointing
                ## origin/initial-branch at a non-existing commit.
-               OLD=$(git rev-parse origin/initial-branch) &&
-               NEW=$ZERO_OID &&
-               mv .git/packed-refs .git/old-packed-refs &&
-               sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs &&
+               git update-ref -d refs/remotes/origin/initial-branch &&
 
                HUF=$(git rev-parse HEAD) &&
 
index 19830d90365661505a047e2376026a1ada023bb0..a3e2413bc339533ef09a9092b2918b7265645e68 100755 (executable)
@@ -6,7 +6,6 @@ test_description='reset can handle submodules'
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
-KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
 
 test_submodule_switch_recursing_with_args "reset --keep"
 
index 7f6e23a4bb98afe1725b54f7e312f48b80c773b7..b7ba1c3268e32935ca62f0e3f562a80c99f84481 100755 (executable)
@@ -585,6 +585,7 @@ test_expect_success 'checkout --conflict=diff3' '
 '
 
 test_expect_success 'failing checkout -b should not break working tree' '
+       git clean -fd &&  # Remove untracked files in the way
        git reset --hard main &&
        git symbolic-ref HEAD refs/heads/main &&
        test_must_fail git checkout -b renamer side^ &&
index 3f7f27188313fb97e9e69e5be8d6c0b005b59e59..f87e524d6d467fd723c083c105f092850051f566 100755 (executable)
@@ -12,6 +12,9 @@ The test setup uses a sparse checkout, however the same scenario can be set up
 also by committing .gitmodules and then just removing it from the filesystem.
 '
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 . ./test-lib.sh
 
 test_expect_success 'sparse checkout setup which hides .gitmodules' '
index 7a8194ce720ed5d9244caf2bbd882a56f491476c..2a07c70867966ccfa901d91667358a7d86f34e5e 100755 (executable)
@@ -250,7 +250,6 @@ test_rebase () {
 }
 
 test_rebase success
-test_have_prereq !REBASE_P || test_rebase success -p
 
 test_expect_success 'with hook (cherry-pick)' '
        test_when_finished "git checkout -f main" &&
index 8df5a74f1db4ecd846c9365c0474f6628cbbd443..d65a0171f29c85b437721d823737189a9d72df81 100755 (executable)
@@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' '
        git tag eleventh-signed $(cat oid) &&
        echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid &&
        test_line_count = 1 oid &&
-       git tag twelfth-signed-alt $(cat oid)
+       git tag twelfth-signed-alt $(cat oid) &&
+
+       cat >keydetails <<-\EOF &&
+       Key-Type: RSA
+       Key-Length: 2048
+       Subkey-Type: RSA
+       Subkey-Length: 2048
+       Name-Real: Unknown User
+       Name-Email: unknown@git.com
+       Expire-Date: 0
+       %no-ask-passphrase
+       %no-protection
+       EOF
+       gpg --batch --gen-key keydetails &&
+       echo 13 >file && git commit -a -S"unknown@git.com" -m thirteenth &&
+       git tag thirteenth-signed &&
+       DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch unknown@git.com | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") &&
+       gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT &&
+       gpg --batch --yes --delete-keys unknown@git.com
 '
 
 test_expect_success GPG 'verify and show signatures' '
@@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' '
        )
 '
 
+test_expect_success GPG 'verify-commit exits failure on unknown signature' '
+       test_must_fail git verify-commit thirteenth-signed 2>actual &&
+       ! grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       grep -q -F -e "No public key" -e "public key not found" actual
+'
+
 test_expect_success GPG 'verify-commit exits success on untrusted signature' '
        git verify-commit eighth-signed-alt 2>actual &&
        grep "Good signature from" actual &&
@@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' '
 '
 
 
+# NEEDSWORK: This test relies on the test_tick commit/author dates from the first
+# 'create signed commits' test even though it creates its own
 test_expect_success GPG 'verify-commit verifies multiply signed commits' '
        git init multiply-signed &&
        cd multiply-signed &&
index 405420ae4d823c3103a3baf6b7f68eab4acc2df0..163ae8046850e729ef5e329f6589faf5381e9cdc 100755 (executable)
@@ -75,19 +75,6 @@ test_expect_success 'noop interactive rebase does not care about ident' '
        git rebase -i HEAD^
 '
 
-test_expect_success REBASE_P \
-       'fast-forward rebase does not care about ident (preserve)' '
-       git checkout -B tmp side-without-commit &&
-       git rebase -p main
-'
-
-test_expect_success REBASE_P \
-       'non-fast-forward rebase refuses to write commits (preserve)' '
-       test_when_finished "git rebase --abort || true" &&
-       git checkout -B tmp side-with-commit &&
-       test_must_fail git rebase -p main
-'
-
 test_expect_success 'author.name overrides user.name' '
        test_config user.name user &&
        test_config user.email user@example.com &&
index deea88d4431d230971f139196740218992f9dc54..f488d930dfd73f37b1c01b52d4ce5732b38e1d8a 100755 (executable)
@@ -389,47 +389,55 @@ test_expect_success 'status succeeds after staging/unstaging' '
 # If "!" is supplied, then we verify that we do not call ensure_full_index
 # during a call to 'git status'. Otherwise, we verify that we _do_ call it.
 check_sparse_index_behavior () {
-       git status --porcelain=v2 >expect &&
-       git sparse-checkout init --cone --sparse-index &&
-       git sparse-checkout set dir1 dir2 &&
+       git -C full status --porcelain=v2 >expect &&
        GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
-               git status --porcelain=v2 >actual &&
+               git -C sparse status --porcelain=v2 >actual &&
        test_region $1 index ensure_full_index trace2.txt &&
        test_region fsm_hook query trace2.txt &&
        test_cmp expect actual &&
-       rm trace2.txt &&
-       git sparse-checkout disable
+       rm trace2.txt
 }
 
 test_expect_success 'status succeeds with sparse index' '
-       git reset --hard &&
-
-       test_config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-all" &&
-       check_sparse_index_behavior ! &&
-
-       write_script .git/hooks/fsmonitor-test<<-\EOF &&
-               printf "last_update_token\0"
-       EOF
-       git config core.fsmonitor .git/hooks/fsmonitor-test &&
-       check_sparse_index_behavior ! &&
+       (
+               sane_unset GIT_TEST_SPLIT_INDEX &&
 
-       write_script .git/hooks/fsmonitor-test<<-\EOF &&
-               printf "last_update_token\0"
-               printf "dir1/modified\0"
-       EOF
-       check_sparse_index_behavior ! &&
+               git clone . full &&
+               git clone --sparse . sparse &&
+               git -C sparse sparse-checkout init --cone --sparse-index &&
+               git -C sparse sparse-checkout set dir1 dir2 &&
 
-       cp -r dir1 dir1a &&
-       git add dir1a &&
-       git commit -m "add dir1a" &&
+               write_script .git/hooks/fsmonitor-test <<-\EOF &&
+                       printf "last_update_token\0"
+               EOF
+               git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test &&
+               git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test &&
+               check_sparse_index_behavior ! &&
 
-       # This one modifies outside the sparse-checkout definition
-       # and hence we expect to expand the sparse-index.
-       write_script .git/hooks/fsmonitor-test<<-\EOF &&
-               printf "last_update_token\0"
-               printf "dir1a/modified\0"
-       EOF
-       check_sparse_index_behavior
+               write_script .git/hooks/fsmonitor-test <<-\EOF &&
+                       printf "last_update_token\0"
+                       printf "dir1/modified\0"
+               EOF
+               check_sparse_index_behavior ! &&
+
+               git -C sparse sparse-checkout add dir1a &&
+
+               for repo in full sparse
+               do
+                       cp -r $repo/dir1 $repo/dir1a &&
+                       git -C $repo add dir1a &&
+                       git -C $repo commit -m "add dir1a" || return 1
+               done &&
+               git -C sparse sparse-checkout set dir1 dir2 &&
+
+               # This one modifies outside the sparse-checkout definition
+               # and hence we expect to expand the sparse-index.
+               write_script .git/hooks/fsmonitor-test <<-\EOF &&
+                       printf "last_update_token\0"
+                       printf "dir1a/modified\0"
+               EOF
+               check_sparse_index_behavior
+       )
 '
 
 test_done
diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh
new file mode 100755 (executable)
index 0000000..badf3ed
--- /dev/null
@@ -0,0 +1,398 @@
+#!/bin/sh
+
+test_description='ssh signed commit tests'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPGSSH 'create signed commits' '
+       test_oid_cache <<-\EOF &&
+       header sha1:gpgsig
+       header sha256:gpgsig-sha256
+       EOF
+
+       test_when_finished "test_unconfig commit.gpgsign" &&
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+       echo 1 >file && git add file &&
+       test_tick && git commit -S -m initial &&
+       git tag initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -S -m second &&
+       git tag second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout main &&
+       test_tick && git merge -S side &&
+       git tag merge &&
+
+       echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
+       git tag fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag fourth-signed &&
+
+       git config commit.gpgsign true &&
+       echo 5 >file && test_tick && git commit -a -m "fifth signed" &&
+       git tag fifth-signed &&
+
+       git config commit.gpgsign false &&
+       echo 6 >file && test_tick && git commit -a -m "sixth" &&
+       git tag sixth-unsigned &&
+
+       git config commit.gpgsign true &&
+       echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign &&
+       git tag seventh-unsigned &&
+
+       test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ &&
+       git tag seventh-signed &&
+
+       echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" &&
+       git tag eighth-signed-alt &&
+
+       # commit.gpgsign is still on but this must not be signed
+       echo 9 | git commit-tree HEAD^{tree} >oid &&
+       test_line_count = 1 oid &&
+       git tag ninth-unsigned $(cat oid) &&
+       # explicit -S of course must sign.
+       echo 10 | git commit-tree -S HEAD^{tree} >oid &&
+       test_line_count = 1 oid &&
+       git tag tenth-signed $(cat oid) &&
+
+       # --gpg-sign[=<key-id>] must sign.
+       echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid &&
+       test_line_count = 1 oid &&
+       git tag eleventh-signed $(cat oid) &&
+       echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid &&
+       test_line_count = 1 oid &&
+       git tag twelfth-signed-alt $(cat oid)
+'
+
+test_expect_success GPGSSH 'verify and show signatures' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       test_config gpg.mintrustlevel UNDEFINED &&
+       (
+               for commit in initial second merge fourth-signed \
+                       fifth-signed sixth-signed seventh-signed tenth-signed \
+                       eleventh-signed
+               do
+                       git verify-commit $commit &&
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned sixth-unsigned \
+                       seventh-unsigned ninth-unsigned
+               do
+                       test_must_fail git verify-commit $commit &&
+                       git show --pretty=short --show-signature $commit >actual &&
+                       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in eighth-signed-alt twelfth-signed-alt
+               do
+                       git show --pretty=short --show-signature $commit >actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
+                       echo $commit OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       test_must_fail git verify-commit eighth-signed-alt 2>actual &&
+       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+       grep "${GPGSSH_KEY_NOT_TRUSTED}" actual
+'
+
+test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       test_config gpg.minTrustLevel fully &&
+       git verify-commit sixth-signed
+'
+
+test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       test_config gpg.minTrustLevel marginal &&
+       git verify-commit sixth-signed
+'
+
+test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' '
+       test_config gpg.minTrustLevel ultimate &&
+       test_must_fail git verify-commit eighth-signed-alt
+'
+
+test_expect_success GPGSSH 'verify signatures with --raw' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       (
+               for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
+               do
+                       git verify-commit --raw $commit 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
+               do
+                       test_must_fail git verify-commit --raw $commit 2>actual &&
+                       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in eighth-signed-alt
+               do
+                       test_must_fail git verify-commit --raw $commit 2>actual &&
+                       grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+                       ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+                       echo $commit OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPGSSH 'proper header is used for hash algorithm' '
+       git cat-file commit fourth-signed >output &&
+       grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output
+'
+
+test_expect_success GPGSSH 'show signed commit with signature' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git show -s initial >commit &&
+       git show -s --show-signature initial >show &&
+       git verify-commit -v initial >verify.1 2>verify.2 &&
+       git cat-file commit initial >cat &&
+       grep -v -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit &&
+       grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg &&
+       grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit &&
+       test_cmp show.commit commit &&
+       test_cmp show.gpg verify.2 &&
+       test_cmp cat.commit verify.1
+'
+
+test_expect_success GPGSSH 'detect fudged signature' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git cat-file commit seventh-signed >raw &&
+       sed -e "s/^seventh/7th forged/" raw >forged1 &&
+       git hash-object -w -t commit forged1 >forged1.commit &&
+       test_must_fail git verify-commit $(cat forged1.commit) &&
+       git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
+       grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
+       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
+       ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
+'
+
+test_expect_success GPGSSH 'detect fudged signature with NUL' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git cat-file commit seventh-signed >raw &&
+       cat raw >forged2 &&
+       echo Qwik | tr "Q" "\000" >>forged2 &&
+       git hash-object -w -t commit forged2 >forged2.commit &&
+       test_must_fail git verify-commit $(cat forged2.commit) &&
+       git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
+       grep "${GPGSSH_BAD_SIGNATURE}" actual2 &&
+       ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2
+'
+
+test_expect_success GPGSSH 'amending already signed commit' '
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       git checkout fourth-signed^0 &&
+       git commit --amend -S --no-edit &&
+       git verify-commit HEAD &&
+       git show -s --show-signature HEAD >actual &&
+       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
+       ! grep "${GPGSSH_BAD_SIGNATURE}" actual
+'
+
+test_expect_success GPGSSH 'show good signature with custom format' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+       cat >expect.tmpl <<-\EOF &&
+       G
+       FINGERPRINT
+       principal with number 1
+       FINGERPRINT
+
+       EOF
+       sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+       git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show bad signature with custom format' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       cat >expect <<-\EOF &&
+       B
+
+
+
+
+       EOF
+       git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show untrusted signature with custom format' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       cat >expect.tmpl <<-\EOF &&
+       U
+       FINGERPRINT
+
+       FINGERPRINT
+
+       EOF
+       git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
+       sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show untrusted signature with undefined trust level' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       cat >expect.tmpl <<-\EOF &&
+       undefined
+       FINGERPRINT
+
+       FINGERPRINT
+
+       EOF
+       git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
+       sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       cat >expect.tmpl <<-\EOF &&
+       fully
+       FINGERPRINT
+       principal with number 1
+       FINGERPRINT
+
+       EOF
+       git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
+       FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+       sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show lack of signature with custom format' '
+       cat >expect <<-\EOF &&
+       N
+
+
+
+
+       EOF
+       git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' '
+       test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+       test_config log.showsignature true &&
+       git show initial >actual &&
+       grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual
+'
+
+test_expect_success GPGSSH 'check config gpg.format values' '
+       test_config gpg.format ssh &&
+       test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+       test_config gpg.format ssh &&
+       git commit -S --amend -m "success" &&
+       test_config gpg.format OpEnPgP &&
+       test_must_fail git commit -S --amend -m "fail"
+'
+
+test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' '
+       sed -e "/gpgsig/,/END PGP/d" forged1 >double-base &&
+       sed -n -e "/gpgsig/,/END PGP/p" forged1 | \
+               sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig &&
+       gpg -o double-sig2.sig -u 29472784 --detach-sign double-base &&
+       cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc &&
+       sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \
+               double-combined.asc > double-gpgsig &&
+       sed -e "/committer/r double-gpgsig" double-base >double-commit &&
+       git hash-object -w -t commit double-commit >double-commit.commit &&
+       test_must_fail git verify-commit $(cat double-commit.commit) &&
+       git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual &&
+       grep "BAD signature from" double-actual &&
+       grep "Good signature from" double-actual
+'
+
+test_expect_failure GPGSSH 'show double signature with custom format (TODO)' '
+       cat >expect <<-\EOF &&
+       E
+
+
+
+
+       EOF
+       git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual &&
+       test_cmp expect actual
+'
+
+
+test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' '
+       git init multiply-signed &&
+       cd multiply-signed &&
+       test_commit first &&
+       echo 1 >second &&
+       git add second &&
+       tree=$(git write-tree) &&
+       parent=$(git rev-parse HEAD^{commit}) &&
+       git commit --gpg-sign -m second &&
+       git cat-file commit HEAD &&
+       # Avoid trailing whitespace.
+       sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF &&
+       Qtree $tree
+       Qparent $parent
+       Qauthor A U Thor <author@example.com> 1112912653 -0700
+       Qcommitter C O Mitter <committer@example.com> 1112912653 -0700
+       Qgpgsig -----BEGIN PGP SIGNATURE-----
+       QZ
+       Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy
+       Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC
+       Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ==
+       Q =tQ0N
+       Q -----END PGP SIGNATURE-----
+       Qgpgsig-sha256 -----BEGIN PGP SIGNATURE-----
+       QZ
+       Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy
+       Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO
+       Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw==
+       Q =pIwP
+       Q -----END PGP SIGNATURE-----
+       Q
+       Qsecond
+       EOF
+       head=$(git hash-object -t commit -w commit) &&
+       git reset --hard $head &&
+       git verify-commit $head 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual
+'
+
+test_done
index 2ef39d3088e80690781e43c671ab1de11beed55f..c773e30b3fa17bc129ca78afe3aef9194d77cb1d 100755 (executable)
@@ -717,6 +717,7 @@ test_expect_success 'failed fast-forward merge with --autostash' '
        git reset --hard c0 &&
        git merge-file file file.orig file.5 &&
        cp file.5 other &&
+       test_when_finished "rm other" &&
        test_must_fail git merge --autostash c1 2>err &&
        test_i18ngrep "Applied autostash." err &&
        test_cmp file.5 file
index 25b235c06303850e17b90f5fc6c0f84bb962c4c8..0260ad6f0e06ec3cbd4d6515a4f893e219b42b0a 100755 (executable)
@@ -3,6 +3,8 @@
 test_description='git repack works correctly'
 
 . ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+. "${TEST_DIRECTORY}/lib-midx.sh"
 
 commit_and_pack () {
        test_commit "$@" 1>&2 &&
@@ -63,13 +65,14 @@ test_expect_success 'objects in packs marked .keep are not repacked' '
 
 test_expect_success 'writing bitmaps via command-line can duplicate .keep objects' '
        # build on $oid, $packid, and .keep state from previous
-       git repack -Adbl &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 git repack -Adbl &&
        test_has_duplicate_object true
 '
 
 test_expect_success 'writing bitmaps via config can duplicate .keep objects' '
        # build on $oid, $packid, and .keep state from previous
-       git -c repack.writebitmaps=true repack -Adl &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -c repack.writebitmaps=true repack -Adl &&
        test_has_duplicate_object true
 '
 
@@ -189,7 +192,9 @@ test_expect_success 'repack --keep-pack' '
 
 test_expect_success 'bitmaps are created by default in bare repos' '
        git clone --bare .git bare.git &&
-       git -C bare.git repack -ad &&
+       rm -f bare.git/objects/pack/*.bitmap &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -C bare.git repack -ad &&
        bitmap=$(ls bare.git/objects/pack/*.bitmap) &&
        test_path_is_file "$bitmap"
 '
@@ -200,7 +205,8 @@ test_expect_success 'incremental repack does not complain' '
 '
 
 test_expect_success 'bitmaps can be disabled on bare repos' '
-       git -c repack.writeBitmaps=false -C bare.git repack -ad &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -c repack.writeBitmaps=false -C bare.git repack -ad &&
        bitmap=$(ls bare.git/objects/pack/*.bitmap || :) &&
        test -z "$bitmap"
 '
@@ -211,7 +217,8 @@ test_expect_success 'no bitmaps created if .keep files present' '
        keep=${pack%.pack}.keep &&
        test_when_finished "rm -f \"\$keep\"" &&
        >"$keep" &&
-       git -C bare.git repack -ad 2>stderr &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -C bare.git repack -ad 2>stderr &&
        test_must_be_empty stderr &&
        find bare.git/objects/pack/ -type f -name "*.bitmap" >actual &&
        test_must_be_empty actual
@@ -222,10 +229,147 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' '
        blob=$(test-tool genrandom big $((1024*1024)) |
               git -C bare.git hash-object -w --stdin) &&
        git -C bare.git update-ref refs/tags/big $blob &&
-       git -C bare.git repack -ad 2>stderr &&
+       GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+               git -C bare.git repack -ad 2>stderr &&
        test_must_be_empty stderr &&
        find bare.git/objects/pack -type f -name "*.bitmap" >actual &&
        test_must_be_empty actual
 '
 
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
+test_expect_success 'setup for --write-midx tests' '
+       git init midx &&
+       (
+               cd midx &&
+               git config core.multiPackIndex true &&
+
+               test_commit base
+       )
+'
+
+test_expect_success '--write-midx unchanged' '
+       (
+               cd midx &&
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack &&
+               test_path_is_missing $midx &&
+               test_path_is_missing $midx-*.bitmap &&
+
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx &&
+
+               test_path_is_file $midx &&
+               test_path_is_missing $midx-*.bitmap &&
+               test_midx_consistent $objdir
+       )
+'
+
+test_expect_success '--write-midx with a new pack' '
+       (
+               cd midx &&
+               test_commit loose &&
+
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack --write-midx &&
+
+               test_path_is_file $midx &&
+               test_path_is_missing $midx-*.bitmap &&
+               test_midx_consistent $objdir
+       )
+'
+
+test_expect_success '--write-midx with -b' '
+       (
+               cd midx &&
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack -mb &&
+
+               test_path_is_file $midx &&
+               test_path_is_file $midx-*.bitmap &&
+               test_midx_consistent $objdir
+       )
+'
+
+test_expect_success '--write-midx with -d' '
+       (
+               cd midx &&
+               test_commit repack &&
+
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack -Ad --write-midx &&
+
+               test_path_is_file $midx &&
+               test_path_is_missing $midx-*.bitmap &&
+               test_midx_consistent $objdir
+       )
+'
+
+test_expect_success 'cleans up MIDX when appropriate' '
+       (
+               cd midx &&
+
+               test_commit repack-2 &&
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
+
+               checksum=$(midx_checksum $objdir) &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$checksum.bitmap &&
+               test_path_is_file $midx-$checksum.rev &&
+
+               test_commit repack-3 &&
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
+
+               test_path_is_file $midx &&
+               test_path_is_missing $midx-$checksum.bitmap &&
+               test_path_is_missing $midx-$checksum.rev &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+               test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+
+               test_commit repack-4 &&
+               GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb &&
+
+               find $objdir/pack -type f -name "multi-pack-index*" >files &&
+               test_must_be_empty files
+       )
+'
+
+test_expect_success '--write-midx with preferred bitmap tips' '
+       git init midx-preferred-tips &&
+       test_when_finished "rm -fr midx-preferred-tips" &&
+       (
+               cd midx-preferred-tips &&
+
+               test_commit_bulk --message="%s" 103 &&
+
+               git log --format="%H" >commits.raw &&
+               sort <commits.raw >commits &&
+
+               git log --format="create refs/tags/%s/%s %H" HEAD >refs &&
+               git update-ref --stdin <refs &&
+
+               git repack --write-midx --write-bitmap-index &&
+               test_path_is_file $midx &&
+               test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >before &&
+               test_line_count = 1 before &&
+
+               rm -fr $midx-$(midx_checksum $objdir).bitmap &&
+               rm -fr $midx-$(midx_checksum $objdir).rev &&
+               rm -fr $midx &&
+
+               # instead of constructing the snapshot ourselves (c.f., the test
+               # "write a bitmap with --refs-snapshot (preferred tips)" in
+               # t5326), mark the missing commit as preferred by adding it to
+               # the pack.preferBitmapTips configuration.
+               git for-each-ref --format="%(refname:rstrip=1)" \
+                       --points-at="$(cat before)" >missing &&
+               git config pack.preferBitmapTips "$(cat missing)" &&
+               git repack --write-midx --write-bitmap-index &&
+
+               test-tool bitmap list-commits | sort >bitmaps &&
+               comm -13 bitmaps commits >after &&
+
+               ! test_cmp before after
+       )
+'
+
 test_done
index 5ccaa440e0c140e8ea34112c49c62248539d9678..bdbbcbf1eca88fef1c2dcd6632d880d3848c842d 100755 (executable)
@@ -15,7 +15,7 @@ test_expect_success '--geometric with no packs' '
        (
                cd geometric &&
 
-               git repack --geometric 2 >out &&
+               git repack --write-midx --geometric 2 >out &&
                test_i18ngrep "Nothing new to pack" out
        )
 '
@@ -180,4 +180,26 @@ test_expect_success '--geometric ignores kept packs' '
        )
 '
 
+test_expect_success '--geometric chooses largest MIDX preferred pack' '
+       git init geometric &&
+       test_when_finished "rm -fr geometric" &&
+       (
+               cd geometric &&
+
+               # These packs already form a geometric progression.
+               test_commit_bulk --start=1 1 && # 3 objects
+               test_commit_bulk --start=2 2 && # 6 objects
+               ls $objdir/pack/pack-*.idx >before &&
+               test_commit_bulk --start=4 4 && # 12 objects
+               ls $objdir/pack/pack-*.idx >after &&
+
+               git repack --geometric 2 -dbm &&
+
+               comm -3 before after | xargs -n 1 basename >expect &&
+               test-tool read-midx --preferred-pack $objdir >actual &&
+
+               test_cmp expect actual
+       )
+'
+
 test_done
index 528e0dabf08653b73f2bc9ced333d4c79fa70daf..096456292c0ad604d90c1d4d2aebb8aacf883d99 100755 (executable)
@@ -453,6 +453,13 @@ run_dir_diff_test 'difftool --dir-diff' '
        grep "^file$" output
 '
 
+run_dir_diff_test 'difftool --dir-diff avoids repeated slashes in TMPDIR' '
+       TMPDIR="${TMPDIR:-/tmp}////" \
+               git difftool --dir-diff $symlinks --extcmd echo branch >output &&
+       grep -v // output >actual &&
+       test_line_count = 1 actual
+'
+
 run_dir_diff_test 'difftool --dir-diff ignores --prompt' '
        git difftool --dir-diff $symlinks --prompt --extcmd ls branch >output &&
        grep "^sub$" output &&
index 828cb3ba5818fd47b6466fd52235e1d5a54cb333..058e5d0c966dfd8fe816de8fdc6bcff7f93a70ac 100755 (executable)
@@ -8,6 +8,9 @@ submodules.
 
 . ./test-lib.sh
 
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
 test_expect_success 'setup directory structure and submodule' '
        echo "(1|2)d(3|4)" >a &&
        mkdir b &&
@@ -438,4 +441,107 @@ test_expect_success 'grep --recurse-submodules with --cached ignores worktree mo
        test_must_fail git grep --recurse-submodules --cached "A modified line in submodule" >actual 2>&1 &&
        test_must_be_empty actual
 '
+
+test_expect_failure 'grep --textconv: superproject .gitattributes does not affect submodules' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+       echo "a diff=d2x" >.gitattributes &&
+
+       cat >expect <<-\EOF &&
+       a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv: superproject .gitattributes (from index) does not affect submodules' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+       echo "a diff=d2x" >.gitattributes &&
+       git add .gitattributes &&
+       rm .gitattributes &&
+
+       cat >expect <<-\EOF &&
+       a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv: superproject .git/info/attributes does not affect submodules' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+       super_attr="$(git rev-parse --git-path info/attributes)" &&
+       test_when_finished "rm -f \"$super_attr\"" &&
+       echo "a diff=d2x" >"$super_attr" &&
+
+       cat >expect <<-\EOF &&
+       a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+# Note: what currently prevents this test from passing is not that the
+# .gitattributes file from "./submodule" is being ignored, but that it is being
+# propagated to the nested "./submodule/sub" files.
+#
+test_expect_failure 'grep --textconv correctly reads submodule .gitattributes' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+       echo "a diff=d2x" >submodule/.gitattributes &&
+
+       cat >expect <<-\EOF &&
+       submodule/a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv correctly reads submodule .gitattributes (from index)' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+       echo "a diff=d2x" >submodule/.gitattributes &&
+       git -C submodule add .gitattributes &&
+       rm submodule/.gitattributes &&
+
+       cat >expect <<-\EOF &&
+       submodule/a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'grep --textconv correctly reads submodule .git/info/attributes' '
+       reset_and_clean &&
+       test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" &&
+
+       submodule_attr="$(git -C submodule rev-parse --path-format=absolute --git-path info/attributes)" &&
+       test_when_finished "rm -f \"$submodule_attr\"" &&
+       echo "a diff=d2x" >"$submodule_attr" &&
+
+       cat >expect <<-\EOF &&
+       submodule/a:(1|2)x(3|4)
+       EOF
+       git grep --textconv --recurse-submodules x >actual &&
+       test_cmp expect actual
+'
+
+test_expect_failure 'grep saves textconv cache in the appropriate repository' '
+       reset_and_clean &&
+       test_config_global diff.d2x_cached.textconv "sed -e \"s/d/x/\"" &&
+       test_config_global diff.d2x_cached.cachetextconv true &&
+       echo "a diff=d2x_cached" >submodule/.gitattributes &&
+
+       # We only read/write to the textconv cache when grepping from an OID,
+       # as the working tree file might have modifications.
+       git grep --textconv --cached --recurse-submodules x &&
+
+       super_textconv_cache="$(git rev-parse --git-path refs/notes/textconv/d2x_cached)" &&
+       sub_textconv_cache="$(git -C submodule rev-parse \
+                       --path-format=absolute --git-path refs/notes/textconv/d2x_cached)" &&
+       test_path_is_missing "$super_textconv_cache" &&
+       test_path_is_file "$sub_textconv_cache"
+'
+
 test_done
index fc16ac22585b90911e9ec6e6a62fdad407a99ba1..9b9f11a8e70ed23f2eca7d000f7192e060209047 100755 (executable)
@@ -20,6 +20,17 @@ test_xmllint () {
        fi
 }
 
+test_lazy_prereq SYSTEMD_ANALYZE '
+       systemd-analyze verify /lib/systemd/system/basic.target
+'
+
+test_systemd_analyze_verify () {
+       if test_have_prereq SYSTEMD_ANALYZE
+       then
+               systemd-analyze verify "$@"
+       fi
+}
+
 test_expect_success 'help text' '
        test_expect_code 129 git maintenance -h 2>err &&
        test_i18ngrep "usage: git maintenance <subcommand>" err &&
@@ -265,7 +276,7 @@ test_expect_success 'incremental-repack task' '
 
        # Delete refs that have not been repacked in these packs.
        git for-each-ref --format="delete %(refname)" \
-               refs/prefetch refs/tags >refs &&
+               refs/prefetch refs/tags refs/remotes >refs &&
        git update-ref --stdin <refs &&
 
        # Replace the object directory with this pack layout.
@@ -274,6 +285,10 @@ test_expect_success 'incremental-repack task' '
        ls $packDir/*.pack >packs-before &&
        test_line_count = 3 packs-before &&
 
+       # make sure we do not have any broken refs that were
+       # missed in the deletion above
+       git for-each-ref &&
+
        # the job repacks the two into a new pack, but does not
        # delete the old ones.
        git maintenance run --task=incremental-repack &&
@@ -492,8 +507,21 @@ test_expect_success !MINGW 'register and unregister with regex metacharacters' '
                maintenance.repo "$(pwd)/$META"
 '
 
+test_expect_success 'start --scheduler=<scheduler>' '
+       test_expect_code 129 git maintenance start --scheduler=foo 2>err &&
+       test_i18ngrep "unrecognized --scheduler argument" err &&
+
+       test_expect_code 129 git maintenance start --no-scheduler 2>err &&
+       test_i18ngrep "unknown option" err &&
+
+       test_expect_code 128 \
+               env GIT_TEST_MAINT_SCHEDULER="launchctl:true,schtasks:true" \
+               git maintenance start --scheduler=crontab 2>err &&
+       test_i18ngrep "fatal: crontab scheduler is not available" err
+'
+
 test_expect_success 'start from empty cron table' '
-       GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
 
        # start registers the repo
        git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -516,7 +544,7 @@ test_expect_success 'stop from existing schedule' '
 
 test_expect_success 'start preserves existing schedule' '
        echo "Important information!" >cron.txt &&
-       GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
        grep "Important information!" cron.txt
 '
 
@@ -545,7 +573,7 @@ test_expect_success 'start and stop macOS maintenance' '
        EOF
 
        rm -f args &&
-       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
 
        # start registers the repo
        git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -584,11 +612,11 @@ test_expect_success 'start and stop macOS maintenance' '
 
 test_expect_success 'use launchctl list to prevent extra work' '
        # ensure we are registered
-       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
 
        # do it again on a fresh args file
        rm -f args &&
-       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
 
        ls "$HOME/Library/LaunchAgents" >actual &&
        cat >expect <<-\EOF &&
@@ -613,7 +641,7 @@ test_expect_success 'start and stop Windows maintenance' '
        EOF
 
        rm -f args &&
-       GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start &&
+       GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start --scheduler=schtasks &&
 
        # start registers the repo
        git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -636,6 +664,83 @@ test_expect_success 'start and stop Windows maintenance' '
        test_cmp expect args
 '
 
+test_expect_success 'start and stop Linux/systemd maintenance' '
+       write_script print-args <<-\EOF &&
+       printf "%s\n" "$*" >>args
+       EOF
+
+       XDG_CONFIG_HOME="$PWD" &&
+       export XDG_CONFIG_HOME &&
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance start --scheduler=systemd-timer &&
+
+       # start registers the repo
+       git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+       test_systemd_analyze_verify "systemd/user/git-maintenance@.service" &&
+
+       printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+       test_cmp expect args &&
+
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance stop &&
+
+       # stop does not unregister the repo
+       git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+       test_path_is_missing "systemd/user/git-maintenance@.timer" &&
+       test_path_is_missing "systemd/user/git-maintenance@.service" &&
+
+       printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+       test_cmp expect args
+'
+
+test_expect_success 'start and stop when several schedulers are available' '
+       write_script print-args <<-\EOF &&
+       printf "%s\n" "$*" | sed "s:gui/[0-9][0-9]*:gui/[UID]:; s:\(schtasks /create .* /xml\).*:\1:;" >>args
+       EOF
+
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=systemd-timer &&
+       printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+               hourly daily weekly >expect &&
+       printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+               hourly daily weekly >>expect &&
+       printf -- "systemctl --user enable --now git-maintenance@%s.timer\n" hourly daily weekly >>expect &&
+       test_cmp expect args &&
+
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=launchctl &&
+       printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+       printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+               hourly daily weekly >>expect &&
+       for frequency in hourly daily weekly
+       do
+               PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
+               echo "launchctl bootout gui/[UID] $PLIST" >>expect &&
+               echo "launchctl bootstrap gui/[UID] $PLIST" >>expect || return 1
+       done &&
+       test_cmp expect args &&
+
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=schtasks &&
+       printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+       printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+               hourly daily weekly >>expect &&
+       printf "schtasks /create /tn Git Maintenance (%s) /f /xml\n" \
+               hourly daily weekly >>expect &&
+       test_cmp expect args &&
+
+       rm -f args &&
+       GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance stop &&
+       printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+       printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+               hourly daily weekly >>expect &&
+       printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+               hourly daily weekly >>expect &&
+       test_cmp expect args
+'
+
 test_expect_success 'register preserves existing strategy' '
        git config maintenance.strategy none &&
        git maintenance register &&
index e28411bb75aa512ce30a1ff5d2213884610b7806..eef2262a3608aaf44786f0481b1c1739ae17ea6f 100644 (file)
@@ -137,33 +137,110 @@ test_tick () {
 # Stop execution and start a shell. This is useful for debugging tests.
 #
 # Be sure to remove all invocations of this command before submitting.
+# WARNING: the shell invoked by this helper does not have the same environment
+# as the one running the tests (shell variables and functions are not
+# available, and the options below further modify the environment). As such,
+# commands copied from a test script might behave differently than when
+# running the test.
+#
+# Usage: test_pause [options]
+#   -t
+#      Use your original TERM instead of test-lib.sh's "dumb".
+#      This usually restores color output in the invoked shell.
+#   -s
+#      Invoke $SHELL instead of $TEST_SHELL_PATH.
+#   -h
+#      Use your original HOME instead of test-lib.sh's "$TRASH_DIRECTORY".
+#      This allows you to use your regular shell environment and Git aliases.
+#      CAUTION: running commands copied from a test script into the paused shell
+#      might result in files in your HOME being overwritten.
+#   -a
+#      Shortcut for -t -s -h
 
 test_pause () {
-       "$SHELL_PATH" <&6 >&5 2>&7
+       PAUSE_TERM=$TERM &&
+       PAUSE_SHELL=$TEST_SHELL_PATH &&
+       PAUSE_HOME=$HOME &&
+       while test $# != 0
+       do
+               case "$1" in
+               -t)
+                       PAUSE_TERM="$USER_TERM"
+                       ;;
+               -s)
+                       PAUSE_SHELL="$SHELL"
+                       ;;
+               -h)
+                       PAUSE_HOME="$USER_HOME"
+                       ;;
+               -a)
+                       PAUSE_TERM="$USER_TERM"
+                       PAUSE_SHELL="$SHELL"
+                       PAUSE_HOME="$USER_HOME"
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done &&
+       TERM="$PAUSE_TERM" HOME="$PAUSE_HOME" "$PAUSE_SHELL" <&6 >&5 2>&7
 }
 
 # Wrap git with a debugger. Adding this to a command can make it easier
 # to understand what is going on in a failing test.
 #
+# Usage: debug [options] <git command>
+#   -d <debugger>
+#   --debugger=<debugger>
+#      Use <debugger> instead of GDB
+#   -t
+#      Use your original TERM instead of test-lib.sh's "dumb".
+#      This usually restores color output in the debugger.
+#      WARNING: the command being debugged might behave differently than when
+#      running the test.
+#
 # Examples:
 #     debug git checkout master
 #     debug --debugger=nemiver git $ARGS
 #     debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS
 debug () {
-       case "$1" in
-       -d)
-               GIT_DEBUGGER="$2" &&
-               shift 2
-               ;;
-       --debugger=*)
-               GIT_DEBUGGER="${1#*=}" &&
-               shift 1
-               ;;
-       *)
-               GIT_DEBUGGER=1
-               ;;
-       esac &&
-       GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
+       GIT_DEBUGGER=1 &&
+       DEBUG_TERM=$TERM &&
+       while test $# != 0
+       do
+               case "$1" in
+               -t)
+                       DEBUG_TERM="$USER_TERM"
+                       ;;
+               -d)
+                       GIT_DEBUGGER="$2" &&
+                       shift
+                       ;;
+               --debugger=*)
+                       GIT_DEBUGGER="${1#*=}"
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done &&
+
+       dotfiles=".gdbinit .lldbinit"
+
+       for dotfile in $dotfiles
+       do
+               dotfile="$USER_HOME/$dotfile" &&
+               test -f "$dotfile" && cp "$dotfile" "$HOME" || :
+       done &&
+
+       TERM="$DEBUG_TERM" GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7 &&
+
+       for dotfile in $dotfiles
+       do
+               rm -f "$HOME/$dotfile"
+       done
 }
 
 # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
index fc1e521519833ce0bd06077bfe682847188578eb..151da80c56132ba0c705499e88fb611ec1c5ddec 100644 (file)
@@ -534,7 +534,7 @@ SQ=\'
 # when case-folding filenames
 u200c=$(printf '\342\200\214')
 
-export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX
+export _x05 _x35 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX
 
 # Each test should start with something like this, after copyright notices:
 #
@@ -585,8 +585,9 @@ else
        }
 fi
 
+USER_TERM="$TERM"
 TERM=dumb
-export TERM
+export TERM USER_TERM
 
 error () {
        say_color error "error: $*"
@@ -1380,10 +1381,31 @@ then
        test_done
 fi
 
+# skip non-whitelisted tests when compiled with SANITIZE=leak
+if test -n "$SANITIZE_LEAK"
+then
+       if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+       then
+               # We need to see it in "git env--helper" (via
+               # test_bool_env)
+               export TEST_PASSES_SANITIZE_LEAK
+
+               if ! test_bool_env TEST_PASSES_SANITIZE_LEAK false
+               then
+                       skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true"
+                       test_done
+               fi
+       fi
+elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
+then
+       error "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak"
+fi
+
 # Last-minute variable setup
+USER_HOME="$HOME"
 HOME="$TRASH_DIRECTORY"
 GNUPGHOME="$HOME/gnupg-home-not-used"
-export HOME GNUPGHOME
+export HOME GNUPGHOME USER_HOME
 
 # Test repository
 rm -fr "$TRASH_DIRECTORY" || {
@@ -1423,10 +1445,9 @@ then
 fi
 
 # Convenience
-# A regexp to match 5, 35 and 40 hexdigits
+# A regexp to match 5 and 35 hexdigits
 _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
-_x40="$_x35$_x05"
 
 test_oid_init
 
@@ -1435,7 +1456,6 @@ OID_REGEX=$(echo $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
 OIDPATH_REGEX=$(test_oid_to_path $ZERO_OID | sed -e 's/0/[0-9a-f]/g')
 EMPTY_TREE=$(test_oid empty_tree)
 EMPTY_BLOB=$(test_oid empty_blob)
-_z40=$ZERO_OID
 
 # Provide an implementation of the 'yes' utility; the upper bound
 # limit is there to help Windows that cannot stop this loop from
@@ -1534,6 +1554,7 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE2" && test_set_prereq PCRE
 test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2
 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
+test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK
 
 if test -z "$GIT_TEST_CHECK_CACHE_TREE"
 then
@@ -1709,10 +1730,6 @@ test_lazy_prereq SHA1 '
        esac
 '
 
-test_lazy_prereq REBASE_P '
-       test -z "$GIT_TEST_SKIP_REBASE_P"
-'
-
 # Ensure that no test accidentally triggers a Git command
 # that runs the actual maintenance scheduler, affecting a user's
 # system permanently.
diff --git a/trace.h b/trace.h
index 0dbbad0e41cb074e669f58fc3ed347e16faeb45c..e25984051aa041eb7557766a29a8c8b4743c62fd 100644 (file)
--- a/trace.h
+++ b/trace.h
@@ -89,7 +89,7 @@ struct trace_key {
 
 extern struct trace_key trace_default_key;
 
-#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 }
+#define TRACE_KEY_INIT(name) { .key = "GIT_TRACE_" #name }
 extern struct trace_key trace_perf_key;
 extern struct trace_key trace_setup_key;
 
index 256120c7fd553e81d82a1a6e1c883af6213e46b6..b2d471526fd64cf323ae7ef6facc730cbd133107 100644 (file)
--- a/trace2.c
+++ b/trace2.c
@@ -260,6 +260,19 @@ void trace2_cmd_path_fl(const char *file, int line, const char *pathname)
                        tgt_j->pfn_command_path_fl(file, line, pathname);
 }
 
+void trace2_cmd_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+
+       if (!trace2_enabled)
+               return;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_command_ancestry_fl)
+                       tgt_j->pfn_command_ancestry_fl(file, line, parent_names);
+}
+
 void trace2_cmd_name_fl(const char *file, int line, const char *name)
 {
        struct tr2_tgt *tgt_j;
@@ -381,6 +394,37 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
                                                 us_elapsed_child);
 }
 
+void trace2_child_ready_fl(const char *file, int line,
+                          struct child_process *cmd,
+                          const char *ready)
+{
+       struct tr2_tgt *tgt_j;
+       int j;
+       uint64_t us_now;
+       uint64_t us_elapsed_absolute;
+       uint64_t us_elapsed_child;
+
+       if (!trace2_enabled)
+               return;
+
+       us_now = getnanotime() / 1000;
+       us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
+
+       if (cmd->trace2_child_us_start)
+               us_elapsed_child = us_now - cmd->trace2_child_us_start;
+       else
+               us_elapsed_child = 0;
+
+       for_each_wanted_builtin (j, tgt_j)
+               if (tgt_j->pfn_child_ready_fl)
+                       tgt_j->pfn_child_ready_fl(file, line,
+                                                 us_elapsed_absolute,
+                                                 cmd->trace2_child_id,
+                                                 cmd->pid,
+                                                 ready,
+                                                 us_elapsed_child);
+}
+
 int trace2_exec_fl(const char *file, int line, const char *exe,
                   const char **argv)
 {
index 49c96ec26b10e9fd5e0b388cbbf0b951a5041dea..0cc7b5f53127e25c50021776bc5b505454ff5b5d 100644 (file)
--- a/trace2.h
+++ b/trace2.h
@@ -133,6 +133,16 @@ void trace2_cmd_path_fl(const char *file, int line, const char *pathname);
 
 #define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))
 
+/*
+ * Emit an 'ancestry' event with the process name of the current process's
+ * parent process.
+ * This gives post-processors a way to determine what invoked the command and
+ * learn more about usage patterns.
+ */
+void trace2_cmd_ancestry_fl(const char *file, int line, const char **parent_names);
+
+#define trace2_cmd_ancestry(v) trace2_cmd_ancestry_fl(__FILE__, __LINE__, (v))
+
 /*
  * Emit a 'cmd_name' event with the canonical name of the command.
  * This gives post-processors a simple field to identify the command
@@ -243,6 +253,31 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
 #define trace2_child_exit(cmd, code) \
        trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))
 
+/**
+ * Emits a "child_ready" message containing the "child-id" and a flag
+ * indicating whether the child was considered "ready" when we
+ * released it.
+ *
+ * This function should be called after starting a daemon process in
+ * the background (and after giving it sufficient time to boot
+ * up) to indicate that we no longer control or own it.
+ *
+ * The "ready" argument should contain one of { "ready", "timeout",
+ * "error" } to indicate the state of the running daemon when we
+ * released it.
+ *
+ * If the daemon process fails to start or it exits or is terminated
+ * while we are still waiting for it, the caller should emit a
+ * regular "child_exit" to report the normal process exit information.
+ *
+ */
+void trace2_child_ready_fl(const char *file, int line,
+                          struct child_process *cmd,
+                          const char *ready);
+
+#define trace2_child_ready(cmd, ready) \
+       trace2_child_ready_fl(__FILE__, __LINE__, (cmd), (ready))
+
 /**
  * Emit an 'exec' event prior to calling one of exec(), execv(),
  * execvp(), and etc.  On Unix-derived systems, this will be the
@@ -492,13 +527,7 @@ enum trace2_process_info_reason {
        TRACE2_PROCESS_INFO_EXIT,
 };
 
-#if defined(GIT_WINDOWS_NATIVE)
 void trace2_collect_process_info(enum trace2_process_info_reason reason);
-#else
-#define trace2_collect_process_info(reason) \
-       do {                                \
-       } while (0)
-#endif
 
 const char *trace2_session_id(void);
 
index 7b904692123e28074458fce0c0afe06922caa607..65f94e15748aa2497856a19afd6cc0de7f7736dc 100644 (file)
@@ -27,6 +27,8 @@ typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,
 
 typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,
                                            const char *command_path);
+typedef void(tr2_tgt_evt_command_ancestry_fl_t)(const char *file, int line,
+                                               const char **parent_names);
 typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,
                                            const char *name,
                                            const char *hierarchy);
@@ -43,6 +45,10 @@ typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,
                                          uint64_t us_elapsed_absolute, int cid,
                                          int pid, int code,
                                          uint64_t us_elapsed_child);
+typedef void(tr2_tgt_evt_child_ready_fl_t)(const char *file, int line,
+                                          uint64_t us_elapsed_absolute,
+                                          int cid, int pid, const char *ready,
+                                          uint64_t us_elapsed_child);
 
 typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,
                                            uint64_t us_elapsed_absolute);
@@ -108,11 +114,13 @@ struct tr2_tgt {
        tr2_tgt_evt_atexit_t                    *pfn_atexit;
        tr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;
        tr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;
+       tr2_tgt_evt_command_ancestry_fl_t       *pfn_command_ancestry_fl;
        tr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;
        tr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;
        tr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;
        tr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;
        tr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;
+       tr2_tgt_evt_child_ready_fl_t            *pfn_child_ready_fl;
        tr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;
        tr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;
        tr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;
index 6353e8ad915610bc93147c5291bc6f31020ac03f..70cfc2f77cc332e167ede6f94995ceacb0a72136 100644 (file)
@@ -261,6 +261,26 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
        jw_release(&jw);
 }
 
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+       const char *event_name = "cmd_ancestry";
+       const char *parent_name = NULL;
+       struct json_writer jw = JSON_WRITER_INIT;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_inline_begin_array(&jw, "ancestry");
+
+       while ((parent_name = *parent_names++))
+               jw_array_string(&jw, parent_name);
+
+       jw_end(&jw); /* 'ancestry' array */
+       jw_end(&jw); /* event object */
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+       jw_release(&jw);
+}
+
 static void fn_command_name_fl(const char *file, int line, const char *name,
                               const char *hierarchy)
 {
@@ -363,6 +383,27 @@ static void fn_child_exit_fl(const char *file, int line,
        jw_release(&jw);
 }
 
+static void fn_child_ready_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int cid, int pid,
+                             const char *ready, uint64_t us_elapsed_child)
+{
+       const char *event_name = "child_ready";
+       struct json_writer jw = JSON_WRITER_INIT;
+       double t_rel = (double)us_elapsed_child / 1000000.0;
+
+       jw_object_begin(&jw, 0);
+       event_fmt_prepare(event_name, file, line, NULL, &jw);
+       jw_object_intmax(&jw, "child_id", cid);
+       jw_object_intmax(&jw, "pid", pid);
+       jw_object_string(&jw, "ready", ready);
+       jw_object_double(&jw, "t_rel", 6, t_rel);
+       jw_end(&jw);
+
+       tr2_dst_write_line(&tr2dst_event, &jw.json);
+
+       jw_release(&jw);
+}
+
 static void fn_thread_start_fl(const char *file, int line,
                               uint64_t us_elapsed_absolute)
 {
@@ -584,11 +625,13 @@ struct tr2_tgt tr2_tgt_event = {
        fn_atexit,
        fn_error_va_fl,
        fn_command_path_fl,
+       fn_command_ancestry_fl,
        fn_command_name_fl,
        fn_command_mode_fl,
        fn_alias_fl,
        fn_child_start_fl,
        fn_child_exit_fl,
+       fn_child_ready_fl,
        fn_thread_start_fl,
        fn_thread_exit_fl,
        fn_exec_fl,
index 31b602c171fc69177a75c62135ed2629b62ec892..58d9e430f05c96cf8886e09b93e92403e1ad1ef1 100644 (file)
@@ -160,6 +160,24 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
        strbuf_release(&buf_payload);
 }
 
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+       const char *parent_name = NULL;
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       /* cmd_ancestry parent <- grandparent <- great-grandparent */
+       strbuf_addstr(&buf_payload, "cmd_ancestry ");
+       while ((parent_name = *parent_names++)) {
+               strbuf_addstr(&buf_payload, parent_name);
+               /* if we'll write another one after this, add a delimiter */
+               if (parent_names && *parent_names)
+                       strbuf_addstr(&buf_payload, " <- ");
+       }
+
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
 static void fn_command_name_fl(const char *file, int line, const char *name,
                               const char *hierarchy)
 {
@@ -233,6 +251,19 @@ static void fn_child_exit_fl(const char *file, int line,
        strbuf_release(&buf_payload);
 }
 
+static void fn_child_ready_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int cid, int pid,
+                             const char *ready, uint64_t us_elapsed_child)
+{
+       struct strbuf buf_payload = STRBUF_INIT;
+       double elapsed = (double)us_elapsed_child / 1000000.0;
+
+       strbuf_addf(&buf_payload, "child_ready[%d] pid:%d ready:%s elapsed:%.6f",
+                   cid, pid, ready, elapsed);
+       normal_io_write_fl(file, line, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
 static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
                       int exec_id, const char *exe, const char **argv)
 {
@@ -306,11 +337,13 @@ struct tr2_tgt tr2_tgt_normal = {
        fn_atexit,
        fn_error_va_fl,
        fn_command_path_fl,
+       fn_command_ancestry_fl,
        fn_command_name_fl,
        fn_command_mode_fl,
        fn_alias_fl,
        fn_child_start_fl,
        fn_child_exit_fl,
+       fn_child_ready_fl,
        NULL, /* thread_start */
        NULL, /* thread_exit */
        fn_exec_fl,
index a8018f18cc87e869d63bc92abf0d4434c320134e..e4acca13d64b7e1a8f2c25c15f61dc35384ce9e1 100644 (file)
@@ -253,6 +253,21 @@ static void fn_command_path_fl(const char *file, int line, const char *pathname)
        strbuf_release(&buf_payload);
 }
 
+static void fn_command_ancestry_fl(const char *file, int line, const char **parent_names)
+{
+       const char *event_name = "cmd_ancestry";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addstr(&buf_payload, "ancestry:[");
+       /* It's not an argv but the rules are basically the same. */
+       sq_append_quote_argv_pretty(&buf_payload, parent_names);
+       strbuf_addch(&buf_payload, ']');
+
+       perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
+                        &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
 static void fn_command_name_fl(const char *file, int line, const char *name,
                               const char *hierarchy)
 {
@@ -345,6 +360,20 @@ static void fn_child_exit_fl(const char *file, int line,
        strbuf_release(&buf_payload);
 }
 
+static void fn_child_ready_fl(const char *file, int line,
+                             uint64_t us_elapsed_absolute, int cid, int pid,
+                             const char *ready, uint64_t us_elapsed_child)
+{
+       const char *event_name = "child_ready";
+       struct strbuf buf_payload = STRBUF_INIT;
+
+       strbuf_addf(&buf_payload, "[ch%d] pid:%d ready:%s", cid, pid, ready);
+
+       perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
+                        &us_elapsed_child, NULL, &buf_payload);
+       strbuf_release(&buf_payload);
+}
+
 static void fn_thread_start_fl(const char *file, int line,
                               uint64_t us_elapsed_absolute)
 {
@@ -532,11 +561,13 @@ struct tr2_tgt tr2_tgt_perf = {
        fn_atexit,
        fn_error_va_fl,
        fn_command_path_fl,
+       fn_command_ancestry_fl,
        fn_command_name_fl,
        fn_command_mode_fl,
        fn_alias_fl,
        fn_child_start_fl,
        fn_child_exit_fl,
+       fn_child_ready_fl,
        fn_thread_start_fl,
        fn_thread_exit_fl,
        fn_exec_fl,
index 067c23755fb557895bf8cd12d497130dd55041fe..7da94aba522f5435138f5aff51d530a083a867f3 100644 (file)
@@ -95,6 +95,7 @@ void tr2tls_unset_self(void)
 
        pthread_setspecific(tr2tls_key, NULL);
 
+       strbuf_release(&ctx->thread_name);
        free(ctx->array_us_start);
        free(ctx);
 }
index 4be035edb8b7c6d618b4dc78ea5de203a2831391..e8dbdd115309c860d8c458379fef07ac0af37c3b 100644 (file)
@@ -671,8 +671,8 @@ static int connect_helper(struct transport *transport, const char *name,
 static struct ref *get_refs_list_using_list(struct transport *transport,
                                            int for_push);
 
-static int fetch(struct transport *transport,
-                int nr_heads, struct ref **to_fetch)
+static int fetch_refs(struct transport *transport,
+                     int nr_heads, struct ref **to_fetch)
 {
        struct helper_data *data = transport->data;
        int i, count;
@@ -681,7 +681,7 @@ static int fetch(struct transport *transport,
 
        if (process_connect(transport, 0)) {
                do_take_over(transport);
-               return transport->vtable->fetch(transport, nr_heads, to_fetch);
+               return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);
        }
 
        /*
@@ -1261,12 +1261,12 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
 }
 
 static struct transport_vtable vtable = {
-       set_helper_option,
-       get_refs_list,
-       fetch,
-       push_refs,
-       connect_helper,
-       release_helper
+       .set_option     = set_helper_option,
+       .get_refs_list  = get_refs_list,
+       .fetch_refs     = fetch_refs,
+       .push_refs      = push_refs,
+       .connect        = connect_helper,
+       .disconnect     = release_helper
 };
 
 int transport_helper_init(struct transport *transport, const char *name)
index b60f1ba9077d09c5f0c3259fe5bc86c5facdbc78..c4ca0b733acbdbe7fc7a5906490e396240ec7b06 100644 (file)
@@ -34,7 +34,7 @@ struct transport_vtable {
         * get_refs_list(), it should set the old_sha1 fields in the
         * provided refs now.
         **/
-       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+       int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);
 
        /**
         * Push the objects and refs. Send the necessary objects, and
index 17e9629710a2f8053c2657be91425b1aab840991..e4f1decae2063ce8981344c19626141c8bcd866c 100644 (file)
@@ -1,7 +1,7 @@
 #include "cache.h"
 #include "config.h"
 #include "transport.h"
-#include "run-command.h"
+#include "hook.h"
 #include "pkt-line.h"
 #include "fetch-pack.h"
 #include "remote.h"
@@ -162,12 +162,16 @@ static int fetch_refs_from_bundle(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
+       struct strvec extra_index_pack_args = STRVEC_INIT;
        int ret;
 
+       if (transport->progress)
+               strvec_push(&extra_index_pack_args, "-v");
+
        if (!data->get_refs_from_bundle_called)
                get_refs_from_bundle(transport, 0, NULL);
        ret = unbundle(the_repository, &data->header, data->fd,
-                          transport->progress ? BUNDLE_VERBOSE : 0);
+                      &extra_index_pack_args);
        transport->hash_algo = data->header.hash_algo;
        return ret;
 }
@@ -883,12 +887,10 @@ static int disconnect_git(struct transport *transport)
 }
 
 static struct transport_vtable taken_over_vtable = {
-       NULL,
-       get_refs_via_connect,
-       fetch_refs_via_pack,
-       git_transport_push,
-       NULL,
-       disconnect_git
+       .get_refs_list  = get_refs_via_connect,
+       .fetch_refs     = fetch_refs_via_pack,
+       .push_refs      = git_transport_push,
+       .disconnect     = disconnect_git
 };
 
 void transport_take_over(struct transport *transport,
@@ -1032,21 +1034,17 @@ void transport_check_allowed(const char *type)
 }
 
 static struct transport_vtable bundle_vtable = {
-       NULL,
-       get_refs_from_bundle,
-       fetch_refs_from_bundle,
-       NULL,
-       NULL,
-       close_bundle
+       .get_refs_list  = get_refs_from_bundle,
+       .fetch_refs     = fetch_refs_from_bundle,
+       .disconnect     = close_bundle
 };
 
 static struct transport_vtable builtin_smart_vtable = {
-       NULL,
-       get_refs_via_connect,
-       fetch_refs_via_pack,
-       git_transport_push,
-       connect_git,
-       disconnect_git
+       .get_refs_list  = get_refs_via_connect,
+       .fetch_refs     = fetch_refs_via_pack,
+       .push_refs      = git_transport_push,
+       .connect        = connect_git,
+       .disconnect     = disconnect_git
 };
 
 struct transport *transport_get(struct remote *remote, const char *url)
@@ -1453,7 +1451,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
                        heads[nr_heads++] = rm;
        }
 
-       rc = transport->vtable->fetch(transport, nr_heads, heads);
+       rc = transport->vtable->fetch_refs(transport, nr_heads, heads);
 
        free(heads);
        return rc;
index 1cbab11373080f73df22fdd510537aa8f66bf164..8bb4c8bbc8cae2059060ae25044a160192a4354e 100644 (file)
@@ -262,7 +262,9 @@ struct transport_ls_refs_options {
         */
        char *unborn_head_target;
 };
-#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }
+#define TRANSPORT_LS_REFS_OPTIONS_INIT { \
+       .ref_prefixes = STRVEC_INIT, \
+}
 
 /*
  * Retrieve refs from a remote.
index f07304f1b714cfb5cbd4eb8c9b2bc5255807514e..89ca95ce90b369bc521fc52fc9b071c467a74022 100644 (file)
@@ -111,17 +111,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
        strvec_init(&opts->msgs_to_free);
 
        if (!strcmp(cmd, "checkout"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("Your local changes to the following files would be overwritten by checkout:\n%%s"
                          "Please commit your changes or stash them before you switch branches.")
                      : _("Your local changes to the following files would be overwritten by checkout:\n%%s");
        else if (!strcmp(cmd, "merge"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("Your local changes to the following files would be overwritten by merge:\n%%s"
                          "Please commit your changes or stash them before you merge.")
                      : _("Your local changes to the following files would be overwritten by merge:\n%%s");
        else
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("Your local changes to the following files would be overwritten by %s:\n%%s"
                          "Please commit your changes or stash them before you %s.")
                      : _("Your local changes to the following files would be overwritten by %s:\n%%s");
@@ -132,17 +132,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                _("Updating the following directories would lose untracked files in them:\n%s");
 
        if (!strcmp(cmd, "checkout"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be removed by checkout:\n%%s"
                          "Please move or remove them before you switch branches.")
                      : _("The following untracked working tree files would be removed by checkout:\n%%s");
        else if (!strcmp(cmd, "merge"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be removed by merge:\n%%s"
                          "Please move or remove them before you merge.")
                      : _("The following untracked working tree files would be removed by merge:\n%%s");
        else
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be removed by %s:\n%%s"
                          "Please move or remove them before you %s.")
                      : _("The following untracked working tree files would be removed by %s:\n%%s");
@@ -150,17 +150,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                strvec_pushf(&opts->msgs_to_free, msg, cmd, cmd);
 
        if (!strcmp(cmd, "checkout"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be overwritten by checkout:\n%%s"
                          "Please move or remove them before you switch branches.")
                      : _("The following untracked working tree files would be overwritten by checkout:\n%%s");
        else if (!strcmp(cmd, "merge"))
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be overwritten by merge:\n%%s"
                          "Please move or remove them before you merge.")
                      : _("The following untracked working tree files would be overwritten by merge:\n%%s");
        else
-               msg = advice_commit_before_merge
+               msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
                      ? _("The following untracked working tree files would be overwritten by %s:\n%%s"
                          "Please move or remove them before you %s.")
                      : _("The following untracked working tree files would be overwritten by %s:\n%%s");
@@ -1255,7 +1255,7 @@ static int sparse_dir_matches_path(const struct cache_entry *ce,
 static struct cache_entry *find_cache_entry(struct traverse_info *info,
                                            const struct name_entry *p)
 {
-       struct cache_entry *ce;
+       const char *path;
        int pos = find_cache_pos(info, p->path, p->pathlen);
        struct unpack_trees_options *o = info->data;
 
@@ -1281,9 +1281,11 @@ static struct cache_entry *find_cache_entry(struct traverse_info *info,
         * paths (e.g. "subdir-").
         */
        while (pos >= 0) {
-               ce = o->src_index->cache[pos];
+               struct cache_entry *ce = o->src_index->cache[pos];
 
-               if (strncmp(ce->name, p->path, p->pathlen))
+               if (!skip_prefix(ce->name, info->traverse_path, &path) ||
+                   strncmp(path, p->path, p->pathlen) ||
+                   path[p->pathlen] != '/')
                        return NULL;
 
                if (S_ISSPARSEDIR(ce->ce_mode) &&
@@ -1692,9 +1694,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        static struct cache_entry *dfc;
        struct pattern_list pl;
        int free_pattern_list = 0;
+       struct dir_struct dir = DIR_INIT;
+
+       if (o->reset == UNPACK_RESET_INVALID)
+               BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED");
 
        if (len > MAX_UNPACK_TREES)
                die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
+       if (o->dir)
+               BUG("o->dir is for internal use only");
 
        trace_performance_enter();
        trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
@@ -1705,6 +1713,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                ensure_full_index(o->dst_index);
        }
 
+       if (o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED &&
+           o->preserve_ignored)
+               BUG("UNPACK_RESET_OVERWRITE_UNTRACKED incompatible with preserved ignored files");
+
+       if (!o->preserve_ignored) {
+               o->dir = &dir;
+               o->dir->flags |= DIR_SHOW_IGNORED;
+               setup_standard_excludes(o->dir);
+       }
+
        if (!core_apply_sparse_checkout || !o->update)
                o->skip_sparse_checkout = 1;
        if (!o->skip_sparse_checkout && !o->pl) {
@@ -1866,6 +1884,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 done:
        if (free_pattern_list)
                clear_pattern_list(&pl);
+       if (o->dir) {
+               dir_clear(o->dir);
+               o->dir = NULL;
+       }
        trace2_region_leave("unpack_trees", "unpack_trees", the_repository);
        trace_performance_leave("unpack_trees");
        return ret;
@@ -2134,9 +2156,10 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
        if (o->dir)
                d.exclude_per_dir = o->dir->exclude_per_dir;
        i = read_directory(&d, o->src_index, pathbuf, namelen+1, NULL);
+       dir_clear(&d);
+       free(pathbuf);
        if (i)
                return add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name);
-       free(pathbuf);
        return cnt;
 }
 
@@ -2156,9 +2179,15 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le
        return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 }
 
+enum absent_checking_type {
+       COMPLETELY_ABSENT,
+       ABSENT_ANY_DIRECTORY
+};
+
 static int check_ok_to_remove(const char *name, int len, int dtype,
                              const struct cache_entry *ce, struct stat *st,
                              enum unpack_trees_error_types error_type,
+                             enum absent_checking_type absent_type,
                              struct unpack_trees_options *o)
 {
        const struct cache_entry *result;
@@ -2193,6 +2222,10 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
                return 0;
        }
 
+       /* If we only care about directories, then we can remove */
+       if (absent_type == ABSENT_ANY_DIRECTORY)
+               return 0;
+
        /*
         * The previous round may already have decided to
         * delete this path, which is in a subdirectory that
@@ -2213,12 +2246,14 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
  */
 static int verify_absent_1(const struct cache_entry *ce,
                           enum unpack_trees_error_types error_type,
+                          enum absent_checking_type absent_type,
                           struct unpack_trees_options *o)
 {
        int len;
        struct stat st;
 
-       if (o->index_only || o->reset || !o->update)
+       if (o->index_only || !o->update ||
+           o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED)
                return 0;
 
        len = check_leading_path(ce->name, ce_namelen(ce), 0);
@@ -2238,7 +2273,8 @@ static int verify_absent_1(const struct cache_entry *ce,
                                                                NULL, o);
                        else
                                ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
-                                                        &st, error_type, o);
+                                                        &st, error_type,
+                                                        absent_type, o);
                }
                free(path);
                return ret;
@@ -2253,7 +2289,7 @@ static int verify_absent_1(const struct cache_entry *ce,
 
                return check_ok_to_remove(ce->name, ce_namelen(ce),
                                          ce_to_dtype(ce), ce, &st,
-                                         error_type, o);
+                                         error_type, absent_type, o);
        }
 }
 
@@ -2263,14 +2299,23 @@ static int verify_absent(const struct cache_entry *ce,
 {
        if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
                return 0;
-       return verify_absent_1(ce, error_type, o);
+       return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
+}
+
+static int verify_absent_if_directory(const struct cache_entry *ce,
+                                     enum unpack_trees_error_types error_type,
+                                     struct unpack_trees_options *o)
+{
+       if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
+               return 0;
+       return verify_absent_1(ce, error_type, ABSENT_ANY_DIRECTORY, o);
 }
 
 static int verify_absent_sparse(const struct cache_entry *ce,
                                enum unpack_trees_error_types error_type,
                                struct unpack_trees_options *o)
 {
-       return verify_absent_1(ce, error_type, o);
+       return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
 }
 
 static int merged_entry(const struct cache_entry *ce,
@@ -2344,6 +2389,12 @@ static int merged_entry(const struct cache_entry *ce,
                 * Previously unmerged entry left as an existence
                 * marker by read_index_unmerged();
                 */
+               if (verify_absent_if_directory(merge,
+                                 ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+                       discard_cache_entry(merge);
+                       return -1;
+               }
+
                invalidate_ce_path(old, o);
        }
 
@@ -2361,7 +2412,10 @@ static int deleted_entry(const struct cache_entry *ce,
                if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
                        return -1;
                return 0;
+       } else if (verify_absent_if_directory(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) {
+               return -1;
        }
+
        if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
                return -1;
        add_entry(o, ce, CE_REMOVE, 0);
index 2d88b19dca7eb8ea3cadd0905575b36876c189e3..71ffb7eeb0c0d1df8539cdff4d6383116d4ff1b4 100644 (file)
@@ -45,10 +45,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
  */
 void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);
 
+enum unpack_trees_reset_type {
+       UNPACK_RESET_NONE = 0,    /* traditional "false" value; still valid */
+       UNPACK_RESET_INVALID = 1, /* "true" no longer valid; use below values */
+       UNPACK_RESET_PROTECT_UNTRACKED,
+       UNPACK_RESET_OVERWRITE_UNTRACKED
+};
+
 struct unpack_trees_options {
-       unsigned int reset,
-                    merge,
+       unsigned int merge,
                     update,
+                    preserve_ignored,
                     clone,
                     index_only,
                     nontrivial_merge,
@@ -64,9 +71,9 @@ struct unpack_trees_options {
                     exiting_early,
                     show_all_errors,
                     dry_run;
+       enum unpack_trees_reset_type reset;
        const char *prefix;
        int cache_bottom;
-       struct dir_struct *dir;
        struct pathspec *pathspec;
        merge_fn_t fn;
        const char *msgs[NB_UNPACK_TREES_WARNING_TYPES];
@@ -88,6 +95,7 @@ struct unpack_trees_options {
        struct index_state result;
 
        struct pattern_list *pl; /* for internal use */
+       struct dir_struct *dir; /* for internal use only */
        struct checkout_metadata meta;
 };
 
index 6ce07231d3dc349260c1a60308f1fc25554c74a8..c78d55bc674ed2b22b7f4417173242ad4e53bfd0 100644 (file)
@@ -1207,14 +1207,14 @@ static int send_ref(const char *refname, const struct object_id *oid,
 
                format_symref_info(&symref_info, &data->symref);
                format_session_id(&session_id, data);
-               packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
+               packet_fwrite_fmt(stdout, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
                             oid_to_hex(oid), refname_nons,
                             0, capabilities,
                             (data->allow_uor & ALLOW_TIP_SHA1) ?
                                     " allow-tip-sha1-in-want" : "",
                             (data->allow_uor & ALLOW_REACHABLE_SHA1) ?
                                     " allow-reachable-sha1-in-want" : "",
-                            data->stateless_rpc ? " no-done" : "",
+                            data->no_done ? " no-done" : "",
                             symref_info.buf,
                             data->allow_filter ? " filter" : "",
                             session_id.buf,
@@ -1223,11 +1223,11 @@ static int send_ref(const char *refname, const struct object_id *oid,
                strbuf_release(&symref_info);
                strbuf_release(&session_id);
        } else {
-               packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
+               packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(oid), refname_nons);
        }
        capabilities = NULL;
        if (!peel_iterated_oid(oid, &peeled))
-               packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
+               packet_fwrite_fmt(stdout, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
        return 0;
 }
 
@@ -1329,7 +1329,8 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
        return parse_hide_refs_config(var, value, "uploadpack");
 }
 
-void upload_pack(struct upload_pack_options *options)
+void upload_pack(const int advertise_refs, const int stateless_rpc,
+                const int timeout)
 {
        struct packet_reader reader;
        struct upload_pack_data data;
@@ -1338,16 +1339,24 @@ void upload_pack(struct upload_pack_options *options)
 
        git_config(upload_pack_config, &data);
 
-       data.stateless_rpc = options->stateless_rpc;
-       data.daemon_mode = options->daemon_mode;
-       data.timeout = options->timeout;
+       data.stateless_rpc = stateless_rpc;
+       data.timeout = timeout;
+       if (data.timeout)
+               data.daemon_mode = 1;
 
        head_ref_namespaced(find_symref, &data.symref);
 
-       if (options->advertise_refs || !data.stateless_rpc) {
+       if (advertise_refs || !data.stateless_rpc) {
                reset_timeout(data.timeout);
+               if (advertise_refs)
+                       data.no_done = 1;
                head_ref_namespaced(send_ref, &data);
                for_each_namespaced_ref(send_ref, &data);
+               /*
+                * fflush stdout before calling advertise_shallow_grafts because send_ref
+                * uses stdio.
+                */
+               fflush_or_die(stdout);
                advertise_shallow_grafts(1);
                packet_flush(1);
        } else {
@@ -1355,7 +1364,7 @@ void upload_pack(struct upload_pack_options *options)
                for_each_namespaced_ref(check_ref, NULL);
        }
 
-       if (!options->advertise_refs) {
+       if (!advertise_refs) {
                packet_reader_init(&reader, 0, NULL, 0,
                                   PACKET_READ_CHOMP_NEWLINE |
                                   PACKET_READ_DIE_ON_ERR_PACKET);
@@ -1659,8 +1668,7 @@ enum fetch_state {
        FETCH_DONE,
 };
 
-int upload_pack_v2(struct repository *r, struct strvec *keys,
-                  struct packet_reader *request)
+int upload_pack_v2(struct repository *r, struct packet_reader *request)
 {
        enum fetch_state state = FETCH_PROCESS_ARGS;
        struct upload_pack_data data;
index 27ddcdc6cb071fd916ad48219a520d8973e961e0..d6ee25ea98e1b350377a3b9fb6e3750e516517f8 100644 (file)
@@ -1,20 +1,12 @@
 #ifndef UPLOAD_PACK_H
 #define UPLOAD_PACK_H
 
-struct upload_pack_options {
-       int stateless_rpc;
-       int advertise_refs;
-       unsigned int timeout;
-       int daemon_mode;
-};
-
-void upload_pack(struct upload_pack_options *options);
+void upload_pack(const int advertise_refs, const int stateless_rpc,
+                const int timeout);
 
 struct repository;
-struct strvec;
 struct packet_reader;
-int upload_pack_v2(struct repository *r, struct strvec *keys,
-                  struct packet_reader *request);
+int upload_pack_v2(struct repository *r, struct packet_reader *request);
 
 struct strbuf;
 int upload_pack_advertise(struct repository *r,
index 6ff42f81b0c1e0fdcdd75511f8b7746da0dccbf7..34a3ba6d1973b4aa8d82a2af7fab8b6672433cc5 100644 (file)
@@ -66,6 +66,10 @@ struct urlmatch_config {
        int (*fallback_match_fn)(const char *url, void *cb);
 };
 
+#define URLMATCH_CONFIG_INIT { \
+       .vars = STRING_LIST_INIT_DUP, \
+}
+
 int urlmatch_config_entry(const char *var, const char *value, void *cb);
 
 #endif /* URL_MATCH_H */
index d9b2ba752f0885240ea1089ea90525de2cedf40c..af02b1878c7d0128a2b650d03c050168e9ac7a3e 100644 (file)
@@ -13,6 +13,16 @@ static int drivers_alloc;
 #define IPATTERN(name, pattern, word_regex)                    \
        { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, \
          word_regex "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" }
+
+/*
+ * Built-in drivers for various languages, sorted by their names
+ * (except that the "default" is left at the end).
+ *
+ * When writing or updating patterns, assume that the contents these
+ * patterns are applied to are syntactically correct.  The patterns
+ * can be simple without implementing all syntactical corner cases, as
+ * long as they are sufficiently permissive.
+ */
 static struct userdiff_driver builtin_drivers[] = {
 IPATTERN("ada",
         "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n"
@@ -142,7 +152,11 @@ PATTERNS("html",
         "[^<>= \t]+"),
 PATTERNS("java",
         "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
-        "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
+        /* Class, enum, and interface declarations */
+        "^[ \t]*(([a-z]+[ \t]+)*(class|enum|interface)[ \t]+[A-Za-z][A-Za-z0-9_$]*[ \t]+.*)$\n"
+        /* Method definitions; note that constructor signatures are not */
+        /* matched because they are indistinguishable from method calls. */
+        "^[ \t]*(([A-Za-z_<>&][][?&<>.,A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
         /* -- */
         "[a-zA-Z_][a-zA-Z0-9_]*"
         "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
@@ -214,7 +228,7 @@ PATTERNS("perl",
         "|<<|<>|<=>|>>"),
 PATTERNS("php",
         "^[\t ]*(((public|protected|private|static|abstract|final)[\t ]+)*function.*)$\n"
-        "^[\t ]*((((final|abstract)[\t ]+)?class|interface|trait).*)$",
+        "^[\t ]*((((final|abstract)[\t ]+)?class|enum|interface|trait).*)$",
         /* -- */
         "[a-zA-Z_][a-zA-Z0-9_]*"
         "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
index 7c6586af3210009e991ccd06756ce94ce8de5962..1460d4e27b03cc976e21581942158d16d144fc18 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -145,6 +145,18 @@ void *xcalloc(size_t nmemb, size_t size)
        return ret;
 }
 
+void xsetenv(const char *name, const char *value, int overwrite)
+{
+       if (setenv(name, value, overwrite))
+               die_errno(_("could not setenv '%s'"), name ? name : "(null)");
+}
+
+void xunsetenv(const char *name)
+{
+       if (!unsetenv(name))
+               die_errno(_("could not unsetenv '%s'"), name ? name : "(null)");
+}
+
 /*
  * Limit size of IO chunks, because huge chunks only cause pain.  OS X
  * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
index d33e68f6abb30aed23d53909c1b855ad352a7baa..0b1ec8190b622d18751aef3d0e640bf3d0e63b97 100644 (file)
@@ -70,3 +70,15 @@ void write_or_die(int fd, const void *buf, size_t count)
                die_errno("write error");
        }
 }
+
+void fwrite_or_die(FILE *f, const void *buf, size_t count)
+{
+       if (fwrite(buf, 1, count, f) != count)
+               die_errno("fwrite error");
+}
+
+void fflush_or_die(FILE *f)
+{
+       if (fflush(f))
+               die_errno("fflush error");
+}
index eaed30eafba8005994894fac2e4046ee00987486..e4f29b2b4c9f7493076ee904f4c5e2b476841d6c 100644 (file)
@@ -787,7 +787,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
 
        dir_clear(&dir);
 
-       if (advice_status_u_option)
+       if (advice_enabled(ADVICE_STATUS_U_OPTION))
                s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
 }
 
@@ -1158,7 +1158,7 @@ static void wt_longstatus_print_tracking(struct wt_status *s)
        if (!format_tracking_info(branch, &sb, s->ahead_behind_flags))
                return;
 
-       if (advice_status_ahead_behind_warning &&
+       if (advice_enabled(ADVICE_STATUS_AHEAD_BEHIND_WARNING) &&
            s->ahead_behind_flags == AHEAD_BEHIND_FULL) {
                uint64_t t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
                if (t_delta_in_ms > AB_DELAY_WARNING_IN_MS) {
@@ -1845,7 +1845,7 @@ static void wt_longstatus_print(struct wt_status *s)
                wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
                if (s->show_ignored_mode)
                        wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
-               if (advice_status_u_option && 2000 < s->untracked_in_ms) {
+               if (advice_enabled(ADVICE_STATUS_U_OPTION) && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                                         _("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"