]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/retire-preserve-merges'
authorJunio C Hamano <gitster@pobox.com>
Mon, 18 Oct 2021 22:47:56 +0000 (15:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 18 Oct 2021 22:47:56 +0000 (15:47 -0700)
The "--preserve-merges" option of "git rebase" has been removed.

* js/retire-preserve-merges:
  sequencer: restrict scope of a formerly public function
  rebase: remove a no-longer-used function
  rebase: stop mentioning the -p option in comments
  rebase: remove obsolete code comment
  rebase: drop the internal `rebase--interactive` command
  git-svn: drop support for `--preserve-merges`
  rebase: drop support for `--preserve-merges`
  pull: remove support for `--rebase=preserve`
  tests: stop testing `git rebase --preserve-merges`
  remote: warn about unhandled branch.<name>.rebase values
  t5520: do not use `pull.rebase=preserve`

455 files changed:
.cirrus.yml
.github/workflows/l10n.yml [new file with mode: 0644]
.github/workflows/main.yml
.gitignore
Documentation/MyFirstContribution.txt
Documentation/MyFirstObjectWalk.txt
Documentation/RelNotes/2.33.1.txt [new file with mode: 0644]
Documentation/RelNotes/2.34.0.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/config/advice.txt
Documentation/config/gui.txt
Documentation/config/help.txt
Documentation/config/pack.txt
Documentation/config/transfer.txt
Documentation/diff-format.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-bugreport.txt
Documentation/git-bundle.txt
Documentation/git-checkout.txt
Documentation/git-column.txt
Documentation/git-config.txt
Documentation/git-cvsserver.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-merge.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-reset.txt
Documentation/git-rm.txt
Documentation/git-send-pack.txt
Documentation/git-sparse-checkout.txt
Documentation/git-status.txt
Documentation/git-upload-pack.txt
Documentation/git-version.txt [new file with mode: 0644]
Documentation/git.txt
Documentation/gitfaq.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/api-trace2.txt
Documentation/technical/bitmap-format.txt
Documentation/technical/directory-rename-detection.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
apply.c
archive.c
attr.c
bisect.c
branch.c
builtin.h
builtin/add.c
builtin/am.c
builtin/archive.c
builtin/bisect--helper.c
builtin/branch.c
builtin/bugreport.c
builtin/bundle.c
builtin/checkout.c
builtin/clone.c
builtin/column.c
builtin/commit-graph.c
builtin/commit-tree.c
builtin/commit.c
builtin/credential-cache.c
builtin/credential-store.c
builtin/diff-index.c
builtin/difftool.c
builtin/fast-export.c
builtin/fetch.c
builtin/for-each-ref.c
builtin/gc.c
builtin/grep.c
builtin/hash-object.c
builtin/help.c
builtin/index-pack.c
builtin/log.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/mailsplit.c
builtin/merge.c
builtin/multi-pack-index.c
builtin/mv.c
builtin/notes.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/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/update-index.c
builtin/update-ref.c
builtin/upload-pack.c
builtin/worktree.c
bulk-checkin.c
bundle.c
bundle.h
cache.h
cbtree.h
check_bindir [deleted file]
checkout.c
ci/install-dependencies.sh
ci/install-docker-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/mmap.c
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/vcbuild/README
compat/win32/lazyload.h
config.c
config.h
config.mak.dev
config.mak.uname
connect.c
connected.c
connected.h
contrib/buildsystems/CMakeLists.txt
contrib/coccinelle/xopen.cocci [new file with mode: 0644]
contrib/completion/git-completion.bash
contrib/completion/git-completion.tcsh
contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
contrib/credential/libsecret/git-credential-libsecret.c
contrib/rerere-train.sh
credential.c
csum-file.c
detect-compiler
diff-lib.c
diff-merges.c
diff-merges.h
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
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-cvsserver.perl
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git.c
gitweb/gitweb.perl
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.h
ls-refs.c
ls-refs.h
mailmap.c
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.h
oid-array.h
oidset.c
oidset.h
pack-bitmap-write.c
pack-bitmap.c
pack-bitmap.h
pack-revindex.h
pack-write.c
pack.h
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
protocol.c
quote.c
quote.h
range-diff.c
read-cache.c
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/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
setup.c
shallow.h
simple-ipc.h
sparse-index.c
sparse-index.h
strbuf.h
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-httpd/apache.conf
t/lib-rebase.sh
t/lib-subtest.sh [new file with mode: 0644]
t/perf/aggregate.perl
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/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/t0021-conversion.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/t1013-read-tree-submodule.sh
t/t1091-sparse-checkout-builtin.sh
t/t1092-sparse-checkout-compatibility.sh
t/t1400-update-ref.sh
t/t1405-main-ref-store.sh
t/t1410-reflog.sh
t/t1430-bad-ref-name.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/t2402-worktree-list.sh
t/t2500-untracked-overwriting.sh [new file with mode: 0755]
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3320-notes-merge-worktrees.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh
t/t3407-rebase-abort.sh
t/t3418-rebase-continue.sh
t/t3430-rebase-merges.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/t3903-stash.sh
t/t4013-diff-various.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/t4045-diff-relative.sh
t/t4060-diff-submodule-option-diff-format.sh
t/t4103-apply-binary.sh
t/t4108-apply-threeway.sh
t/t4151-am-abort.sh
t/t4210-log-i18n.sh
t/t5300-pack-object.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/t5323-pack-redundant.sh
t/t5326-multi-pack-bitmaps.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t5521-pull-options.sh
t/t5524-pull-msg.sh
t/t5549-fetch-push-http.sh [new file with mode: 0755]
t/t5551-http-fetch-smart.sh
t/t5553-set-upstream.sh
t/t5555-http-smart-common.sh [new file with mode: 0755]
t/t5562/invoke-with-content-length.pl
t/t5582-fetch-negative-refspec.sh
t/t5600-clone-fail-cleanup.sh
t/t5604-clone-reference.sh
t/t5606-clone-options.sh
t/t5607-clone-bundle.sh
t/t5701-git-serve.sh
t/t5702-protocol-v2.sh
t/t5703-upload-pack-ref-in-want.sh
t/t5704-protocol-violations.sh
t/t5705-session-id-in-capabilities.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/t6300-for-each-ref.sh
t/t6402-merge-rename.sh
t/t6409-merge-subtree.sh
t/t6415-merge-dir-to-symlink.sh
t/t6417-merge-ours-theirs.sh
t/t6424-merge-unrelated-index-changes.sh
t/t6430-merge-recursive.sh
t/t6436-merge-overwrite.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/t7064-wtstatus-pv2.sh
t/t7112-reset-submodule.sh
t/t7201-co.sh
t/t7500-commit-template-squash-signoff.sh
t/t7519-status-fsmonitor.sh
t/t7600-merge.sh
t/t7601-merge-pull-config.sh
t/t7603-merge-reduce-heads.sh
t/t7700-repack.sh
t/t7800-difftool.sh
t/t7814-grep-recurse-submodules.sh
t/t7900-maintenance.sh
t/t9001-send-email.sh
t/t9002-column.sh
t/t9351-fast-export-anonymize.sh
t/t9400-git-cvsserver-server.sh
t/t9902-completion.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
tree-diff.c
unicode-width.h
unpack-trees.c
unpack-trees.h
upload-pack.c
upload-pack.h
userdiff.c
wrapper.c
write-or-die.c
wt-status.c

index c2f5fe385af1bbc161f6c010bdcf0048ab6671ed..e114ffee1a7fb28bf73b92230a5d70b06e5629a1 100644 (file)
@@ -2,8 +2,15 @@ env:
   CIRRUS_CLONE_DEPTH: 1
 
 freebsd_12_task:
+  env:
+    GIT_PROVE_OPTS: "--timer --jobs 10"
+    GIT_TEST_OPTS: "--no-chain-lint --no-bin-wrappers"
+    MAKEFLAGS: "-j4"
+    DEFAULT_TEST_TARGET: prove
+    DEVELOPER: 1
   freebsd_instance:
-    image: freebsd-12-1-release-amd64
+    image_family: freebsd-12-2
+    memory: 2G
   install_script:
     pkg install -y gettext gmake perl5
   create_user_script:
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 6115755c4dff9a6b7abf4554f345df5bebe7b826..59acc35d37654aded4f43c6cf0f2d540c2b3b19e 100644 (file)
@@ -231,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}}
@@ -258,6 +261,8 @@ jobs:
           image: alpine
         - jobname: Linux32
           image: daald/ubuntu32:xenial
+        - jobname: pedantic
+          image: fedora
     env:
       jobname: ${{matrix.vector.jobname}}
     runs-on: ubuntu-latest
@@ -270,7 +275,7 @@ jobs:
       if: failure()
     - name: Upload failed tests' directories
       if: failure() && env.FAILED_TEST_ARTIFACTS != ''
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v1
       with:
         name: failed-tests-${{matrix.vector.jobname}}
         path: ${{env.FAILED_TEST_ARTIFACTS}}
index 98d6275b20d01601e0ef15aee011f54779166fa7..054249b20a8d91d77a569294f8104e53bdd12f51 100644 (file)
 /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 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]]
index 2d10eea7a9f65f23f766dc3b730495018e8f1234..45eb84d8b489e4f1859fb7f2301ceff05953029a 100644 (file)
@@ -691,7 +691,7 @@ help understand. In our case, that means we omit trees and blobs not directly
 referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only
 `HEAD` in the `pending` list.)
 
-First, we'll need to `#include "list-objects-filter-options.h`" and set up the
+First, we'll need to `#include "list-objects-filter-options.h"` and set up the
 `struct list_objects_filter_options` at the top of the function.
 
 ----
@@ -779,7 +779,7 @@ Count all the objects within and modify the print statement:
        while ((oid = oidset_iter_next(&oit)))
                omitted_count++;
 
-       printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n",
+       printf("commits %d\nblobs %d\ntags %d\ntrees %d\nomitted %d\n",
                commit_count, blob_count, tag_count, tree_count, omitted_count);
 ----
 
diff --git a/Documentation/RelNotes/2.33.1.txt b/Documentation/RelNotes/2.33.1.txt
new file mode 100644 (file)
index 0000000..b71738e
--- /dev/null
@@ -0,0 +1,138 @@
+Git 2.33.1 Release Notes
+========================
+
+This primarily is to backport various fixes accumulated during the
+development towards Git 2.34, the next feature release.
+
+
+Fixes since v2.33
+-----------------
+
+ * The unicode character width table (used for output alignment) has
+   been updated.
+
+ * Input validation of "git pack-objects --stdin-packs" has been
+   corrected.
+
+ * Bugfix for common ancestor negotiation recently introduced in "git
+   push" codepath.
+
+ * "git pull" had various corner cases that were not well thought out
+   around its --rebase backend, e.g. "git pull --ff-only" did not stop
+   but went ahead and rebased when the history on other side is not a
+   descendant of our history.  The series tries to fix them up.
+
+ * "git apply" miscounted the bytes and failed to read to the end of
+   binary hunks.
+
+ * "git range-diff" code clean-up.
+
+ * "git commit --fixup" now works with "--edit" again, after it was
+   broken in v2.32.
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+   new version has a blocker bug for that architecture.
+
+ * Checking out all the paths from HEAD during the last conflicted
+   step in "git rebase" and continuing would cause the step to be
+   skipped (which is expected), but leaves MERGE_MSG file behind in
+   $GIT_DIR and confuses the next "git commit", which has been
+   corrected.
+
+ * Various bugs in "git rebase -r" have been fixed.
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+   failure, which has been corrected to just return an error to the
+   caller to be handled.
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+   when there are unmerged paths.
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+   even when --quiet and/or --no-progress options were given.
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+   ref that points at a missing commit, which has been corrected.
+
+ * Build update for Apple clang.
+
+ * The parser for the "--nl" option of "git column" has been
+   corrected.
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+   forgot to take the ref namespaces into account when handling
+   want-ref requests.
+
+ * The sparse-index support can corrupt the index structure by storing
+   a stale and/or uninitialized data, which has been corrected.
+
+ * Buggy tests could damage repositories outside the throw-away test
+   area we created.  We now by default export GIT_CEILING_DIRECTORIES
+   to limit the damage from such a stray test.
+
+ * Even when running "git send-email" without its own threaded
+   discussion support, a threading related header in one message is
+   carried over to the subsequent message to result in an unwanted
+   threading, which has been corrected.
+
+ * The output from "git fast-export", when its anonymization feature
+   is in use, showed an annotated tag incorrectly.
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+
+ * "git maintenance" scheduler fix for macOS.
+
+ * A pathname in an advice message has been made cut-and-paste ready.
+
+ * The "git apply -3" code path learned not to bother the lower level
+   merge machinery when the three-way merge can be trivially resolved
+   without the content level merge.
+
+ * The code that optionally creates the *.rev reverse index file has
+   been optimized to avoid needless computation when it is not writing
+   the file out.
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+   corrected.
+
+ * The order in which various files that make up a single (conceptual)
+   packfile has been reevaluated and straightened up.  This matters in
+   correctness, as an incomplete set of files must not be shown to a
+   running Git.
+
+ * The "mode" word is useless in a call to open(2) that does not
+   create a new file.  Such a call in the files backend of the ref
+   subsystem has been cleaned up.
+
+ * "git update-ref --stdin" failed to flush its output as needed,
+   which potentially led the conversation to a deadlock.
+
+ * When "git am --abort" fails to abort correctly, it still exited
+   with exit status of 0, which has been corrected.
+
+ * Correct nr and alloc members of strvec struct to be of type size_t.
+
+ * "git stash", where the tentative change involves changing a
+   directory to a file (or vice versa), was confused, which has been
+   corrected.
+
+ * "git clone" from a repository whose HEAD is unborn into a bare
+   repository didn't follow the branch name the other side used, which
+   is corrected.
+
+ * "git cvsserver" had a long-standing bug in its authentication code,
+   which has finally been corrected (it is unclear and is a separate
+   question if anybody is seriously using it, though).
+
+ * "git difftool --dir-diff" mishandled symbolic links.
+
+ * Sensitive data in the HTTP trace were supposed to be redacted, but
+   we failed to do so in HTTP/2 requests.
+
+ * "make clean" has been updated to remove leftover .depend/
+   directories, even when it is not told to use them to compute header
+   dependencies.
+
+ * Protocol v0 clients can get stuck parsing a malformed feature line.
+
+Also contains various documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.34.0.txt b/Documentation/RelNotes/2.34.0.txt
new file mode 100644 (file)
index 0000000..0bfeaea
--- /dev/null
@@ -0,0 +1,307 @@
+Git 2.34 Release Notes
+======================
+
+Updates since Git 2.33
+----------------------
+
+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.
+
+
+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).
+
+ * 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).
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 d30831a130800ac12a1fd8296ea6cff530d518b3..0c087fd8c9313e5e68dcd7cd7a91b6900936febc 100644 (file)
@@ -11,7 +11,7 @@ gui.displayUntracked::
        in the file list. The default is "true".
 
 gui.encoding::
-       Specifies the default encoding to use for displaying of
+       Specifies the default character encoding to use for displaying of
        file contents in linkgit:git-gui[1] and linkgit:gitk[1].
        It can be overridden by setting the 'encoding' attribute
        for relevant files (see linkgit:gitattributes[5]).
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 505126a7802319de3d31cf4426e2e75407b7baec..b49429eb4db8e07c7d8753f7843936a7fc059955 100644 (file)
@@ -52,13 +52,17 @@ If you have multiple hideRefs values, later entries override earlier ones
 (and entries in more-specific config files override less-specific ones).
 +
 If a namespace is in use, the namespace prefix is stripped from each
-reference before it is matched against `transfer.hiderefs` patterns.
+reference before it is matched against `transfer.hiderefs` patterns. In
+order to match refs before stripping, add a `^` in front of the ref name. If
+you combine `!` and `^`, `!` must be specified first.
++
 For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
 the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
-is omitted from the advertisements but `refs/heads/master` and
-`refs/namespaces/bar/refs/heads/master` are still advertised as so-called
-"have" lines. In order to match refs before stripping, add a `^` in front of
-the ref name. If you combine `!` and `^`, `!` must be specified first.
+is omitted from the advertisements. If `uploadpack.allowRefInWant` is set,
+`upload-pack` will treat `want-ref refs/heads/master` in a protocol v2
+`fetch` command as if `refs/namespaces/foo/refs/heads/master` did not exist.
+`receive-pack`, on the other hand, will still advertise the object id the
+ref is pointing to without mentioning its name (a so-called ".have" line).
 +
 Even if you hide refs, a client may still be able to steal the target
 objects via the techniques described in the "SECURITY" section of the
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 8714dfcb76220e8c409f3a15042b1d05debd9d9c..0a4a984dfdecda8ef5d252e2a2883f6497f2cb1b 100644 (file)
@@ -178,6 +178,8 @@ default.   You can use `--no-utf8` to override this.
 
 --abort::
        Restore the original branch and abort the patching operation.
+       Revert contents of files involved in the am operation to their
+       pre-am state.
 
 --quit::
        Abort the patching operation but keep HEAD and the index
index 94dc9a54f2d7269bb9b8a4de718b8da54c62c02e..5449767121103299e442560775a54bce5b371a9f 100644 (file)
@@ -118,7 +118,8 @@ OPTIONS
        Reset <branchname> to <startpoint>, even if <branchname> exists
        already. Without `-f`, 'git branch' refuses to change an existing branch.
        In combination with `-d` (or `--delete`), allow deleting the
-       branch irrespective of its merged status. In combination with
+       branch irrespective of its merged status, or whether it even
+       points to a valid commit. In combination with
        `-m` (or `--move`), allow renaming the branch even if the new
        branch name already exists, the same applies for `-c` (or `--copy`).
 
index 66e88c2e312b10afeb44d3f4a861ed92c21e925e..d8817bf3cec3995cc7f3fa4a87c8f8ff3f9db865 100644 (file)
@@ -40,8 +40,8 @@ OPTIONS
 -------
 -o <path>::
 --output-directory <path>::
-       Place the resulting bug report file in `<path>` instead of the root of
-       the Git repository.
+       Place the resulting bug report file in `<path>` instead of the current
+       directory.
 
 -s <format>::
 --suffix <format>::
index 53804cad4b67fdc3898ff008d737517c4928e7da..71b5ecabd1f08386e50063d249ecb7e71fb2b5bb 100644 (file)
@@ -13,26 +13,53 @@ 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
 -----------
 
-Some workflows require that one or more branches of development on one
-machine be replicated on another machine, but the two machines cannot
-be directly connected, and therefore the interactive Git protocols (git,
-ssh, http) cannot be used.
+Create, unpack, and manipulate "bundle" files. Bundles are used for
+the "offline" transfer of Git objects without an active "server"
+sitting on the other side of the network connection.
 
-The 'git bundle' command packages objects and references in an archive
-at the originating machine, which can then be imported into another
-repository using 'git fetch', 'git pull', or 'git clone',
-after moving the archive by some means (e.g., by sneakernet).
+They can be used to create both incremental and full backups of a
+repository, and to relay the state of the references in one repository
+to another.
 
-As no
-direct connection between the repositories exists, the user must specify a
-basis for the bundle that is held by the destination repository: the
-bundle assumes that all objects in the basis are already in the
-destination repository.
+Git commands that fetch or otherwise "read" via protocols such as
+`ssh://` and `https://` can also operate on bundle files. It is
+possible linkgit:git-clone[1] a new repository from a bundle, to use
+linkgit:git-fetch[1] to fetch from one, and to list the references
+contained within it with linkgit:git-ls-remote[1]. There's no
+corresponding "write" support, i.e.a 'git push' into a bundle is not
+supported.
+
+See the "EXAMPLES" section below for examples of how to use bundles.
+
+BUNDLE FORMAT
+-------------
+
+Bundles are `.pack` files (see linkgit:git-pack-objects[1]) with a
+header indicating what references are contained within the bundle.
+
+Like the the packed archive format itself bundles can either be
+self-contained, or be created using exclusions.
+See the "OBJECT PREREQUISITES" section below.
+
+Bundles created using revision exclusions are "thin packs" created
+using the `--thin` option to linkgit:git-pack-objects[1], and
+unbundled using the `--fix-thin` option to linkgit:git-index-pack[1].
+
+There is no option to create a "thick pack" when using revision
+exclusions, users should not be concerned about the difference. By
+using "thin packs" bundles created using exclusions are smaller in
+size. That they're "thin" under the hood is merely noted here as a
+curiosity, and as a reference to other documentation
+
+See link:technical/bundle-format.html[the `bundle-format`
+documentation] for more details and the discussion of "thin pack" in
+link:technical/pack-format.html[the pack format documentation] for
+further details.
 
 OPTIONS
 -------
@@ -117,28 +144,88 @@ unbundle <file>::
 SPECIFYING REFERENCES
 ---------------------
 
-'git bundle' will only package references that are shown by
-'git show-ref': this includes heads, tags, and remote heads.  References
-such as `master~1` cannot be packaged, but are perfectly suitable for
-defining the basis.  More than one reference may be packaged, and more
-than one basis can be specified.  The objects packaged are those not
-contained in the union of the given bases.  Each basis can be
-specified explicitly (e.g. `^master~10`), or implicitly (e.g.
-`master~10..master`, `--since=10.days.ago master`).
+Revisions must accompanied by reference names to be packaged in a
+bundle.
+
+More than one reference may be packaged, and more than one set of prerequisite objects can
+be specified.  The objects packaged are those not contained in the
+union of the prerequisites.
+
+The 'git bundle create' command resolves the reference names for you
+using the same rules as `git rev-parse --abbrev-ref=loose`. Each
+prerequisite can be specified explicitly (e.g. `^master~10`), or implicitly
+(e.g. `master~10..master`, `--since=10.days.ago master`).
+
+All of these simple cases are OK (assuming we have a "master" and
+"next" branch):
+
+----------------
+$ git bundle create master.bundle master
+$ echo master | git bundle create master.bundle --stdin
+$ git bundle create master-and-next.bundle master next
+$ (echo master; echo next) | git bundle create master-and-next.bundle --stdin
+----------------
+
+And so are these (and the same but omitted `--stdin` examples):
+
+----------------
+$ git bundle create recent-master.bundle master~10..master
+$ git bundle create recent-updates.bundle master~10..master next~5..next
+----------------
+
+A revision name or a range whose right-hand-side cannot be resolved to
+a reference is not accepted:
+
+----------------
+$ git bundle create HEAD.bundle $(git rev-parse HEAD)
+fatal: Refusing to create empty bundle.
+$ git bundle create master-yesterday.bundle master~10..master~5
+fatal: Refusing to create empty bundle.
+----------------
+
+OBJECT PREREQUISITES
+--------------------
+
+When creating bundles it is possible to create a self-contained bundle
+that can be unbundled in a repository with no common history, as well
+as providing negative revisions to exclude objects needed in the
+earlier parts of the history.
+
+Feeding a revision such as `new` to `git bundle create` will create a
+bundle file that contains all the objects reachable from the revision
+`new`. That bundle can be unbundled in any repository to obtain a full
+history that leads to the revision `new`:
+
+----------------
+$ git bundle create full.bundle new
+----------------
+
+A revision range such as `old..new` will produce a bundle file that
+will require the revision `old` (and any objects reachable from it)
+to exist for the bundle to be "unbundle"-able:
+
+----------------
+$ git bundle create full.bundle old..new
+----------------
+
+A self-contained bundle without any prerequisites can be extracted
+into anywhere, even into an empty repository, or be cloned from
+(i.e., `new`, but not `old..new`).
 
-It is very important that the basis used be held by the destination.
 It is okay to err on the side of caution, causing the bundle file
 to contain objects already in the destination, as these are ignored
 when unpacking at the destination.
 
-`git clone` can use any bundle created without negative refspecs
-(e.g., `new`, but not `old..new`).
 If you want to match `git clone --mirror`, which would include your
 refs such as `refs/remotes/*`, use `--all`.
 If you want to provide the same set of refs that a clone directly
 from the source repository would get, use `--branches --tags` for
 the `<git-rev-list-args>`.
 
+The 'git bundle verify' command can be used to check whether your
+recipient repository has the required prerequisite commits for a
+bundle.
+
 EXAMPLES
 --------
 
@@ -149,7 +236,7 @@ but we can move data from A to B via some mechanism (CD, email, etc.).
 We want to update R2 with development made on the branch master in R1.
 
 To bootstrap the process, you can first create a bundle that does not have
-any basis. You can use a tag to remember up to what commit you last
+any prerequisites. You can use a tag to remember up to what commit you last
 processed, in order to make it easy to later update the other repository
 with an incremental bundle:
 
@@ -200,7 +287,7 @@ machineB$ git pull
 
 If you know up to what commit the intended recipient repository should
 have the necessary objects, you can use that knowledge to specify the
-basis, giving a cut-off point to limit the revisions and objects that go
+prerequisites, giving a cut-off point to limit the revisions and objects that go
 in the resulting bundle. The previous example used the lastR2bundle tag
 for this purpose, but you can use any other options that you would give to
 the linkgit:git-log[1] command. Here are more examples:
@@ -211,7 +298,7 @@ You can use a tag that is present in both:
 $ git bundle create mybundle v1.0.0..master
 ----------------
 
-You can use a basis based on time:
+You can use a prerequisite based on time:
 
 ----------------
 $ git bundle create mybundle --since=10.days master
@@ -224,7 +311,7 @@ $ git bundle create mybundle -10 master
 ----------------
 
 You can run `git-bundle verify` to see if you can extract from a bundle
-that was created with a basis:
+that was created with a prerequisite:
 
 ----------------
 $ git bundle verify mybundle
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 f58e9c43e60cec3b56fb84f54859f3d01d1a963a..6cea9ab4638be5d1cada5e3350ddf41696280112 100644 (file)
@@ -39,7 +39,7 @@ OPTIONS
 --indent=<string>::
        String to be printed at the beginning of each line.
 
---nl=<N>::
+--nl=<string>::
        String to be printed at the end of each line,
        including newline character.
 
index 2dc4bae6da9a5c3fbc6925e896af8411935f9893..992225f61295d1f24329ec1a25c57dd2a18e538c 100644 (file)
@@ -71,6 +71,9 @@ codes are:
 
 On success, the command returns the exit code 0.
 
+A list of all available configuration variables can be obtained using the
+`git help --config` command.
+
 [[OPTIONS]]
 OPTIONS
 -------
index f2e4a47ebe84c321d765aac7bdfd660bf8befc37..4dc57ed254721aebcb544f21582789f9089681b5 100644 (file)
@@ -99,7 +99,7 @@ looks like
 
 ------
 
-Only anonymous access is provided by pserve by default. To commit you
+Only anonymous access is provided by pserver by default. To commit you
 will have to create pserver accounts, simply add a gitcvs.authdb
 setting in the config file of the repositories you want the cvsserver
 to allow writes to, for example:
@@ -114,21 +114,20 @@ The format of these files is username followed by the encrypted password,
 for example:
 
 ------
-   myuser:$1Oyx5r9mdGZ2
-   myuser:$1$BA)@$vbnMJMDym7tA32AamXrm./
+   myuser:sqkNi8zPf01HI
+   myuser:$1$9K7FzU28$VfF6EoPYCJEYcVQwATgOP/
+   myuser:$5$.NqmNH1vwfzGpV8B$znZIcumu1tNLATgV2l6e1/mY8RzhUDHMOaVOeL1cxV3
 ------
 You can use the 'htpasswd' facility that comes with Apache to make these
-files, but Apache's MD5 crypt method differs from the one used by most C
-library's crypt() function, so don't use the -m option.
+files, but only with the -d option (or -B if your system suports it).
 
-Alternatively you can produce the password with perl's crypt() operator:
------
-   perl -e 'my ($user, $pass) = @ARGV; printf "%s:%s\n", $user, crypt($user, $pass)' $USER password
------
+Preferably use the system specific utility that manages password hash
+creation in your platform (e.g. mkpasswd in Linux, encrypt in OpenBSD or
+pwhash in NetBSD) and paste it in the right location.
 
 Then provide your password via the pserver method, for example:
 ------
-   cvs -d:pserver:someuser:somepassword <at> server/path/repo.git co <HEAD_name>
+   cvs -d:pserver:someuser:somepassword@server:/path/repo.git co <HEAD_name>
 ------
 No special setup is needed for SSH access, other than having Git tools
 in the PATH. If you have clients that do not accept the CVS_SERVER
@@ -138,7 +137,7 @@ Note: Newer CVS versions (>= 1.12.11) also support specifying
 CVS_SERVER directly in CVSROOT like
 
 ------
-cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
+   cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name>
 ------
 This has the advantage that it will be saved in your 'CVS/Root' files and
 you don't need to worry about always setting the correct environment
@@ -186,8 +185,8 @@ allowing access over SSH.
 +
 --
 ------
-     export CVSROOT=:ext:user@server:/var/git/project.git
-     export CVS_SERVER="git cvsserver"
+   export CVSROOT=:ext:user@server:/var/git/project.git
+   export CVS_SERVER="git cvsserver"
 ------
 --
 4. For SSH clients that will make commits, make sure their server-side
@@ -203,7 +202,7 @@ allowing access over SSH.
    `project-master` directory:
 +
 ------
-     cvs co -d project-master master
+   cvs co -d project-master master
 ------
 
 [[dbbackend]]
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 3819fadac1f1e4daa3bc44beed5602c413830851..e4f3352eb584539b75235fc304431646df9415f4 100644 (file)
@@ -61,6 +61,8 @@ merge has resulted in conflicts.
 
 OPTIONS
 -------
+:git-merge: 1
+
 include::merge-options.txt[]
 
 -m <msg>::
index ffd601bc17b4dc8f453e6a5b28fb4d6239176d96..3b0b55cd7555e0e4c418dc361d4697665b1ff99b 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,12 @@ 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.
 --
 
 verify::
@@ -81,6 +86,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 7144690a0cbbae25bef34302947a9d591504aee6..0e14f8b5b25924c98c4bda92729787f2361ada9c 100644 (file)
@@ -15,14 +15,17 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-Incorporates changes from a remote repository into the current
-branch.  In its default mode, `git pull` is shorthand for
-`git fetch` followed by `git merge FETCH_HEAD`.
-
-More precisely, 'git pull' runs 'git fetch' with the given
-parameters and calls 'git merge' to merge the retrieved branch
-heads into the current branch.
-With `--rebase`, it runs 'git rebase' instead of 'git merge'.
+Incorporates changes from a remote repository into the current branch.
+If the current branch is behind the remote, then by default it will
+fast-forward the current branch to match the remote.  If the current
+branch and the remote have diverged, the user needs to specify how to
+reconcile the divergent branches with `--rebase` or `--no-rebase` (or
+the corresponding configuration option in `pull.rebase`).
+
+More precisely, `git pull` runs `git fetch` with the given parameters
+and then depending on configuration options or command line flags,
+will call either `git rebase` or `git merge` to reconcile diverging
+branches.
 
 <repository> should be the name of a remote repository as
 passed to linkgit:git-fetch[1].  <refspec> can name an
@@ -128,7 +131,7 @@ published that history already.  Do *not* use this option
 unless you have read linkgit:git-rebase[1] carefully.
 
 --no-rebase::
-       Override earlier --rebase.
+       This is shorthand for --rebase=false.
 
 Options related to fetching
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 1382dc6f00565771ed1a698a95599ba2a1681141..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.
@@ -340,9 +344,7 @@ See also INCOMPATIBLE OPTIONS below.
 
 -m::
 --merge::
-       Use merging strategies to rebase.  When the recursive (default) merge
-       strategy is used, this allows rebase to be aware of renames on the
-       upstream side.  This is the default.
+       Using merging strategies to rebase (default).
 +
 Note that a rebase merge works by replaying each commit from the working
 branch on top of the <upstream> branch.  Because of this, when a merge
@@ -354,9 +356,8 @@ See also INCOMPATIBLE OPTIONS below.
 
 -s <strategy>::
 --strategy=<strategy>::
-       Use the given merge strategy.
-       If there is no `-s` option 'git merge-recursive' is used
-       instead.  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
@@ -369,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.
@@ -445,7 +446,8 @@ When --fork-point is active, 'fork_point' will be used instead of
 ends up being empty, the <upstream> will be used as a fallback.
 +
 If <upstream> is given on the command line, then the default is
-`--no-fork-point`, otherwise the default is `--fork-point`.
+`--no-fork-point`, otherwise the default is `--fork-point`. See also
+`rebase.forkpoint` in linkgit:git-config[1].
 +
 If your branch was based on <upstream> but <upstream> was rewound and
 your branch contains commits which were dropped, this option can be used
@@ -526,7 +528,7 @@ the `rebase-cousins` mode is turned on, such commits are instead rebased
 onto `<upstream>` (or `<onto>`, if specified).
 +
 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.
@@ -1191,12 +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.
 
-At this time, the `merge` command will *always* use the `recursive`
-merge strategy for regular merges, and `octopus` for octopus merges,
-with no way to choose a different one. To work around
-this, an `exec` command can be used to call `git merge` explicitly,
-using the fact that the labels are worktree-local refs (the ref
-`refs/rewritten/onto` would correspond to the label `onto`, for example).
+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
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 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 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]
diff --git a/Documentation/git-version.txt b/Documentation/git-version.txt
new file mode 100644 (file)
index 0000000..80fa775
--- /dev/null
@@ -0,0 +1,28 @@
+git-version(1)
+==============
+
+NAME
+----
+git-version - Display version information about Git
+
+SYNOPSIS
+--------
+[verse]
+'git version' [--build-options]
+
+DESCRIPTION
+-----------
+With no options given, the version of 'git' is printed on the standard output.
+
+Note that `git --version` is identical to `git version` because the
+former is internally converted into the latter.
+
+OPTIONS
+-------
+--build-options::
+       Include additional information about how git was built for diagnostic
+       purposes.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 6dd241ef83881d37e7b9db20ba6e1492d49ac36a..d63c65e67d825d6aac6181de79689557bd76a019 100644 (file)
@@ -41,6 +41,10 @@ OPTIONS
 -------
 --version::
        Prints the Git suite version that the 'git' program came from.
++
+This option is internaly converted to `git version ...` and accepts
+the same options as the linkgit:git-version[1] command. If `--help` is
+also given, it takes precedence over `--version`.
 
 --help::
        Prints the synopsis and a list of the most commonly used
@@ -863,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
@@ -894,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 52565014c15a51f30ea910a69687ea1d93a90358..61ec157c2f38b4b89039403c334edce10f8303d1 100644 (file)
@@ -2,6 +2,9 @@
 --no-commit::
        Perform the merge and commit the result. This option can
        be used to override --no-commit.
+ifdef::git-pull[]
+       Only useful when merging.
+endif::git-pull[]
 +
 With --no-commit perform the merge and stop just before creating
 a merge commit, to give the user a chance to inspect and further
@@ -39,6 +42,7 @@ set to `no` at the beginning of them.
        to `MERGE_MSG` before being passed on to the commit machinery in the
        case of a merge conflict.
 
+ifdef::git-merge[]
 --ff::
 --no-ff::
 --ff-only::
@@ -47,6 +51,22 @@ set to `no` at the beginning of them.
        default unless merging an annotated (and possibly signed) tag
        that is not stored in its natural place in the `refs/tags/`
        hierarchy, in which case `--no-ff` is assumed.
+endif::git-merge[]
+ifdef::git-pull[]
+--ff-only::
+       Only update to the new history if there is no divergent local
+       history.  This is the default when no method for reconciling
+       divergent histories is provided (via the --rebase=* flags).
+
+--ff::
+--no-ff::
+       When merging rather than rebasing, specifies how a merge is
+       handled when the merged-in history is already a descendant of
+       the current history.  If merging is requested, `--ff` is the
+       default unless merging an annotated (and possibly signed) tag
+       that is not stored in its natural place in the `refs/tags/`
+       hierarchy, in which case `--no-ff` is assumed.
+endif::git-pull[]
 +
 With `--ff`, when possible resolve the merge as a fast-forward (only
 update the branch pointer to match the merged branch; do not create a
@@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.
 +
 With `--no-ff`, create a merge commit in all cases, even when the merge
 could instead be resolved as a fast-forward.
+ifdef::git-merge[]
 +
 With `--ff-only`, resolve the merge as a fast-forward when possible.
 When not possible, refuse to merge and exit with a non-zero status.
+endif::git-merge[]
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
@@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.
        In addition to branch names, populate the log message with
        one-line descriptions from at most <n> actual commits that are being
        merged. See also linkgit:git-fmt-merge-msg[1].
+ifdef::git-pull[]
+       Only useful when merging.
+endif::git-pull[]
 +
 With --no-log do not list one-line descriptions from the
 actual commits being merged.
@@ -102,18 +127,25 @@ With --no-squash perform the merge and commit the result. This
 option can be used to override --squash.
 +
 With --squash, --commit is not allowed, and will fail.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
 
 --no-verify::
        This option bypasses the pre-merge and commit-msg hooks.
        See also linkgit:githooks[5].
+ifdef::git-pull[]
+       Only useful when merging.
+endif::git-pull[]
 
 -s <strategy>::
 --strategy=<strategy>::
        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 ('git merge-recursive' when merging a single
-       head, 'git merge-octopus' otherwise).
+       is used instead (`ort` when merging a single head,
+       `octopus` otherwise).
 
 -X <option>::
 --strategy-option=<option>::
@@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.
        default trust model, this means the signing key has been signed by
        a trusted key.  If the tip commit of the side branch is not signed
        with a valid key, the merge is aborted.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
 
 --summary::
 --no-summary::
@@ -167,3 +203,7 @@ endif::git-pull[]
        projects that started their lives independently. As that is
        a very rare occasion, no configuration variable to enable
        this by default exists and will not be added.
+ifdef::git-pull[]
++
+Only useful when merging.
+endif::git-pull[]
index 2912de706bf3921f817c9d18c66e526fd0a0fa8e..5fc54ec060b9638e11c66231eed6304de1772313 100644 (file)
@@ -6,28 +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`.
 
-resolve::
-       This can only resolve two heads (i.e. the current branch
-       and another branch you pulled from) using a 3-way merge
-       algorithm.  It tries to carefully detect criss-cross
-       merge ambiguities and is considered generally safe and
-       fast.
-
-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, but currently cannot 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
@@ -43,19 +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;;
-       With this option, 'merge-recursive' spends a little extra time
-       to avoid mismerges that sometimes occur due to unimportant
-       matching lines (e.g., braces from distinct functions).  Use
-       this when the branches to be merged have diverged wildly.
-       See also linkgit:git-diff[1] `--patience`.
-
-diff-algorithm=[patience|minimal|histogram|myers];;
-       Tells 'merge-recursive' to use a different diff algorithm, 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`.
-
 ignore-space-change;;
 ignore-all-space;;
 ignore-space-at-eol;;
@@ -84,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
@@ -105,6 +82,46 @@ subtree[=<path>];;
        is prefixed (or stripped from the beginning) to make the shape of
        two trees to match.
 
+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 '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
+       and another branch you pulled from) using a 3-way merge
+       algorithm.  It tries to carefully detect criss-cross
+       merge ambiguities.  It does not handle renames.
+
 octopus::
        This resolves cases with more than two heads, but refuses to do
        a complex merge that needs manual resolution.  It is
@@ -121,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 27ddaf84a195f46edc7b3f102eb1caf1adf68765..b3af8506086a83216abc5e5791dfe6c36eaf978c 100644 (file)
@@ -33,14 +33,16 @@ people using 80-column terminals.
        used together.
 
 --encoding=<encoding>::
-       The commit objects record the encoding used for the log message
+       Commit objects record the character encoding used for the log message
        in their encoding header; this option can be used to tell the
        command to re-code the commit log message in the encoding
        preferred by the user.  For non plumbing commands this
        defaults to UTF-8. Note that if an object claims to be encoded
        in `X` and we are outputting in `X`, we will output the object
        verbatim; this means that invalid sequences in the original
-       commit may be copied to the output.
+       commit may be copied to the output. Likewise, if iconv(3) fails
+       to convert the commit, we will output the original object
+       verbatim, along with a warning.
 
 --expand-tabs=<n>::
 --expand-tabs::
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 49b83ef3cc4be5dfed9e80f3edc11fd10c64fda6..029ee2cedc5d38d6b6f0b08de6bd6951455f0e2e 100644 (file)
@@ -2,9 +2,9 @@ Directory rename detection
 ==========================
 
 Rename detection logic in diffcore-rename that checks for renames of
-individual files is aggregated and analyzed in merge-recursive for cases
-where combinations of renames indicate that a full directory has been
-renamed.
+individual files is also aggregated there and then analyzed in either
+merge-ort or merge-recursive for cases where combinations of renames
+indicate that a full directory has been renamed.
 
 Scope of abilities
 ------------------
@@ -88,9 +88,11 @@ directory rename detection support in:
     Folks have requested in the past that `git diff` detect directory
     renames and somehow simplify its output.  It is not clear whether this
     would be desirable or how the output should be simplified, so this was
-    simply not implemented.  Further, to implement this, directory rename
-    detection logic would need to move from merge-recursive to
-    diffcore-rename.
+    simply not implemented.  Also, while diffcore-rename has most of the
+    logic for detecting directory renames, some of the logic is still found
+    within merge-ort and merge-recursive.  Fully supporting directory
+    rename detection in diffs would require copying or moving the remaining
+    bits of logic to the diff machinery.
 
   * am
 
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 43f93f6166047b94ae2e7e791b9031f17dee9188..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
@@ -823,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) . \
@@ -908,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
@@ -1220,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.
@@ -1264,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
@@ -1284,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 $$?)
@@ -1309,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 $$?)
@@ -1346,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)
@@ -1435,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"
@@ -1916,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
@@ -2222,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)"' \
@@ -2246,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):\
@@ -2476,7 +2472,6 @@ dep_args = -MF $(dep_file) -MQ $@ -MMD -MP
 endif
 
 ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
-dep_dirs =
 missing_dep_dirs =
 dep_args =
 endif
@@ -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
 
@@ -2602,10 +2590,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
                $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 export DEFAULT_EDITOR DEFAULT_PAGER
 
@@ -2744,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):\
@@ -2806,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)))'\' >>$@+
@@ -2846,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
 
@@ -2901,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
@@ -2930,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"; \
@@ -2940,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)
@@ -2993,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
@@ -3002,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.
@@ -3088,8 +3097,7 @@ endif
                  ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
                  ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
                  cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
-       done && \
-       ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
+       done
 
 .PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf
 .PHONY: quick-install-doc quick-install-man quick-install-html
@@ -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 f071367dc30cd9c12cc010539f4ca151d1282757..6c94469548115b2f3265611116bdc1f13f64a382 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.33.0.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 0b9c89c48ab996da0e7938ac0667a3ecad925efc..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,11 +207,16 @@ 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."));
 }
 
+void NORETURN die_ff_impossible(void)
+{
+       die(_("Not possible to fast-forward, aborting."));
+}
+
 void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
 {
        struct string_list_item *item;
@@ -293,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 9f8ffc73546b3939ad5ca9496b187c7aeb60242e..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);
@@ -96,6 +68,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);
 int error_resolve_conflict(const char *me);
 void NORETURN die_resolve_conflict(const char *me);
 void NORETURN die_conclude_merge(void);
+void NORETURN die_ff_impossible(void);
 void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
 void detach_advice(const char *new_name);
 
diff --git a/apply.c b/apply.c
index 44bc31d6eb5b42d4077eff458246cde376cb6785..43a0aebf4eec85fa6b0a69f655c0224aa14f29e5 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -1917,6 +1917,7 @@ static struct fragment *parse_binary_hunk(struct apply_state *state,
 
        state->linenr++;
        buffer += llen;
+       size -= llen;
        while (1) {
                int byte_length, max_byte_length, newsize;
                llen = linelen(buffer, size);
@@ -3467,6 +3468,21 @@ static int load_preimage(struct apply_state *state,
        return 0;
 }
 
+static int resolve_to(struct image *image, const struct object_id *result_id)
+{
+       unsigned long size;
+       enum object_type type;
+
+       clear_image(image);
+
+       image->buf = read_object_file(result_id, &type, &size);
+       if (!image->buf || type != OBJ_BLOB)
+               die("unable to read blob object %s", oid_to_hex(result_id));
+       image->len = size;
+
+       return 0;
+}
+
 static int three_way_merge(struct apply_state *state,
                           struct image *image,
                           char *path,
@@ -3478,6 +3494,12 @@ static int three_way_merge(struct apply_state *state,
        mmbuffer_t result = { NULL };
        int status;
 
+       /* resolve trivial cases first */
+       if (oideq(base, ours))
+               return resolve_to(image, theirs);
+       else if (oideq(base, theirs) || oideq(ours, theirs))
+               return resolve_to(image, ours);
+
        read_mmblob(&base_file, base);
        read_mmblob(&our_file, ours);
        read_mmblob(&their_file, theirs);
index 3c266d1d7bf4bdd2413f66e5338ca0c82604a512..a3bbb091256dd7077e257f20f532d47516e4a960 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -191,7 +191,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
        return err;
 }
 
-static void queue_directory(const unsigned char *sha1,
+static void queue_directory(const struct object_id *oid,
                struct strbuf *base, const char *filename,
                unsigned mode, struct archiver_context *c)
 {
@@ -203,7 +203,7 @@ static void queue_directory(const unsigned char *sha1,
        d->mode    = mode;
        c->bottom  = d;
        d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
-       oidread(&d->oid, sha1);
+       oidcpy(&d->oid, oid);
 }
 
 static int write_directory(struct archiver_context *c)
@@ -250,8 +250,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
 
                if (check_attr_export_ignore(check))
                        return 0;
-               queue_directory(oid->hash, base, filename,
-                               mode, c);
+               queue_directory(oid, base, filename, mode, c);
                return READ_TREE_RECURSIVE;
        }
 
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 09e684585d9ff25e708434000e09f807b1d30a45..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 {
@@ -313,9 +323,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
        rev.diffopt.use_color = 0;
        rev.diffopt.flags.ignore_dirty_submodules = 1;
-       out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
-       if (out < 0)
-               die(_("Could not open '%s' for writing."), file);
+       out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
        rev.diffopt.file = xfdopen(out, "w");
        rev.diffopt.close_file = 1;
        if (run_diff_files(&rev, 0))
@@ -378,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,
@@ -419,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;
@@ -430,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);
@@ -442,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\""));
@@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags)
        }
 
        for (i = 0; i < dir->nr; i++) {
+               if (!include_sparse &&
+                   !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) {
+                       string_list_append(&matched_sparse_paths,
+                                          dir->entries[i]->name);
+                       continue;
+               }
                if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
                        if (!ignore_add_errors)
                                die(_("adding files failed"));
@@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags)
                        check_embedded_repo(dir->entries[i]->name);
                }
        }
+
+       if (matched_sparse_paths.nr) {
+               advise_on_updating_sparse_paths(&matched_sparse_paths);
+               exit_status = 1;
+       }
+
+       string_list_clear(&matched_sparse_paths, 0);
+
        return exit_status;
 }
 
@@ -528,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);
 
        /*
@@ -553,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\""));
@@ -624,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 0c2ad96b70eef408492987a8b358887cc56dcb9c..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);
@@ -2106,7 +2107,8 @@ static void am_abort(struct am_state *state)
        if (!has_orig_head)
                oidcpy(&orig_head, the_hash_algo->empty_tree);
 
-       clean_index(&curr_head, &orig_head);
+       if (clean_index(&curr_head, &orig_head))
+               die(_("failed to clean index"));
 
        if (has_orig_head)
                update_ref("am --abort", "HEAD", &orig_head,
index 45d11669aae459caf95bb6b4737558297debae89..7176b041b6d85b5760c91f94fcdde551a38d147f 100644 (file)
@@ -12,9 +12,7 @@
 
 static void create_output_file(const char *output_file)
 {
-       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
-       if (output_fd < 0)
-               die_errno(_("could not create archive file '%s'"), output_file);
+       int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
        if (output_fd != 1) {
                if (dup2(output_fd, 1) < 0)
                        die_errno(_("could not redirect output"));
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 b23b1d1752afd724d76b263ef8253036d331a078..0b7ed82654af1fd52170e7a27fcc3f69fd2f4825 100644 (file)
@@ -168,7 +168,7 @@ static int check_branch_commit(const char *branchname, const char *refname,
                               int kinds, int force)
 {
        struct commit *rev = lookup_commit_reference(the_repository, oid);
-       if (!rev) {
+       if (!force && !rev) {
                error(_("Couldn't look up commit object for '%s'"), refname);
                return -1;
        }
@@ -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 9915a5841def8c5c86a844efe9960cebbef31717..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[] = {
@@ -171,10 +143,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
        get_populated_hooks(&buffer, !startup_info->have_repository);
 
        /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
-       report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
-
-       if (report < 0)
-               die(_("couldn't create a new file at '%s'"), report_path.buf);
+       report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
 
        if (write_in_full(report, buffer.buf, buffer.len) < 0)
                die_errno(_("unable to write to %s"), report_path.buf);
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 b5d477919a743885ab536608e86d28662ac3b615..cbf73b8c9f65ae2fdd1d96265bd4972aeaa9bd68 100644 (file)
@@ -404,7 +404,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
        mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
        remove_marked_cache_entries(&the_index, 1);
        remove_scheduled_dirs();
-       errs |= finish_delayed_checkout(&state, &nr_checkouts);
+       errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
 
        if (opts->count_checkout_paths) {
                if (nr_unmerged)
@@ -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 66fe66679c8498f8b2ef2729059b153f24ad8f6a..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 "
@@ -1340,6 +1230,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        our_head_points_at = remote_head_points_at;
        }
        else {
+               const char *branch;
+               char *ref;
+
                if (option_branch)
                        die(_("Remote branch %s not found in upstream %s"),
                                        option_branch, remote_name);
@@ -1350,24 +1243,22 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                remote_head_points_at = NULL;
                remote_head = NULL;
                option_no_checkout = 1;
-               if (!option_bare) {
-                       const char *branch;
-                       char *ref;
-
-                       if (transport_ls_refs_options.unborn_head_target &&
-                           skip_prefix(transport_ls_refs_options.unborn_head_target,
-                                       "refs/heads/", &branch)) {
-                               ref = transport_ls_refs_options.unborn_head_target;
-                               transport_ls_refs_options.unborn_head_target = NULL;
-                               create_symref("HEAD", ref, reflog_msg.buf);
-                       } else {
-                               branch = git_default_branch_name(0);
-                               ref = xstrfmt("refs/heads/%s", branch);
-                       }
 
-                       install_branch_config(0, branch, remote_name, ref);
-                       free(ref);
+               if (transport_ls_refs_options.unborn_head_target &&
+                   skip_prefix(transport_ls_refs_options.unborn_head_target,
+                               "refs/heads/", &branch)) {
+                       ref = transport_ls_refs_options.unborn_head_target;
+                       transport_ls_refs_options.unborn_head_target = NULL;
+                       create_symref("HEAD", ref, reflog_msg.buf);
+               } else {
+                       branch = git_default_branch_name(0);
+                       ref = xstrfmt("refs/heads/%s", branch);
                }
+
+               if (!option_bare)
+                       install_branch_config(0, branch, remote_name, ref);
+
+               free(ref);
        }
 
        write_refspec_config(src_ref_prefix, our_head_points_at,
index 40d4b3bee2dd8e7e20b2d0ecc289837a0458c771..158fdf53d9fb9ce3694502fe6edbc609d7839282 100644 (file)
@@ -29,7 +29,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
                OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
                OPT_INTEGER(0, "width", &copts.width, N_("maximum width")),
                OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")),
-               OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")),
+               OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")),
                OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
                OPT_END()
        };
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 1031b9a491c5cec1411ff822402e1fd39b9fca4f..63ea3229333c8766d51c34be9aec3f4d1bab3cd9 100644 (file)
@@ -88,9 +88,7 @@ static int parse_file_arg_callback(const struct option *opt,
        if (!strcmp(arg, "-"))
                fd = 0;
        else {
-               fd = open(arg, O_RDONLY);
-               if (fd < 0)
-                       die_errno(_("git commit-tree: failed to open '%s'"), arg);
+               fd = xopen(arg, O_RDONLY);
        }
        if (strbuf_read(buf, fd, 0) < 0)
                die_errno(_("git commit-tree: failed to read '%s'"), arg);
index 243c626307c644050806d465b6933177b3171ac2..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
@@ -1253,8 +1254,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
        if (logfile || have_option_m || use_message)
                use_editor = 0;
-       if (0 <= edit_flag)
-               use_editor = edit_flag;
 
        /* Sanity check options */
        if (amend && !current_head)
@@ -1344,6 +1343,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
                }
        }
 
+       if (0 <= edit_flag)
+               use_editor = edit_flag;
+
        cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
 
        handle_untracked_files_arg(s);
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 cf09559e422d66a06936eef050203498f00f4559..5fd23ab5b6c5b634be82e91339d4920fe9e15b99 100644 (file)
@@ -29,10 +29,10 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
        prefix = precompose_argv_prefix(argc, argv, prefix);
 
        /*
-        * We need no diff for merges options, and we need to avoid conflict
-        * with our own meaning of "-m".
+        * We need (some of) diff for merges options (e.g., --cc), and we need
+        * to avoid conflict with our own meaning of "-m".
         */
-       diff_merges_suppress_options_parsing();
+       diff_merges_suppress_m_parsing();
 
        argc = setup_revisions(argc, argv, &rev, NULL);
        for (i = 1; i < argc; i++) {
index 6a9242a80329559eda808b169b4522cae7f95469..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,38 +554,19 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
         */
        hashmap_for_each_entry(&submodules, &iter, entry,
                                entry /* member name */) {
-               if (*entry->left) {
-                       add_path(&ldir, ldir_len, entry->path);
-                       ensure_leading_directories(ldir.buf);
-                       write_file(ldir.buf, "%s", entry->left);
-               }
-               if (*entry->right) {
-                       add_path(&rdir, rdir_len, entry->path);
-                       ensure_leading_directories(rdir.buf);
-                       write_file(rdir.buf, "%s", entry->right);
-               }
+               write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
        }
 
        /*
-        * Symbolic links require special treatment.The standard "git diff"
+        * Symbolic links require special treatment. The standard "git diff"
         * shows only the link itself, not the contents of the link target.
         * This loop replicates that behavior.
         */
        hashmap_for_each_entry(&symlinks2, &iter, entry,
                                entry /* member name */) {
-               if (*entry->left) {
-                       add_path(&ldir, ldir_len, entry->path);
-                       ensure_leading_directories(ldir.buf);
-                       write_file(ldir.buf, "%s", entry->left);
-               }
-               if (*entry->right) {
-                       add_path(&rdir, rdir_len, entry->path);
-                       ensure_leading_directories(rdir.buf);
-                       write_file(rdir.buf, "%s", entry->right);
-               }
-       }
 
-       strbuf_release(&buf);
+               write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len);
+       }
 
        strbuf_setlen(&ldir, ldir_len);
        helper_argv[1] = ldir.buf;
@@ -583,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);
@@ -617,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);
@@ -647,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)
@@ -663,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)
@@ -713,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;
@@ -768,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 3c20f164f0f05178cb12d66102939fa8cc607457..95e8e89e81f0ec5ac2993ea5dc0cd8d4ce095e9d 100644 (file)
@@ -821,6 +821,7 @@ static void handle_tag(const char *name, struct tag *tag)
                        static struct hashmap tags;
                        message = anonymize_str(&tags, anonymize_tag,
                                                message, message_size, NULL);
+                       message_size = strlen(message);
                }
        }
 
index 25740c13df1bf8d569e341fe717ff322af25207c..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;
 }
 
@@ -1428,7 +1442,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
                if (!has_glob_specials(s)) {
                        struct object_id oid;
                        if (get_oid(s, &oid))
-                               die("%s is not a valid object", s);
+                               die(_("%s is not a valid object"), s);
+                       if (!has_object(the_repository, &oid, 0))
+                               die(_("the object %s does not exist"), s);
                        oid_array_append(oids, &oid);
                        continue;
                }
@@ -1499,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);
@@ -1591,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;
@@ -2133,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 f05d2f0a1ac9cd5637237d7da987452523a8ced3..6b3de3dd51438e2553e82d734206c3828f9c96aa 100644 (file)
@@ -502,7 +502,7 @@ static int report_last_gc_error(void)
                 */
                warning(_("The last gc run reported the following. "
                               "Please correct the root cause\n"
-                              "and remove %s.\n"
+                              "and remove %s\n"
                               "Automatic cleanup will not be performed "
                               "until the file is removed.\n\n"
                               "%s"),
@@ -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,38 +1660,56 @@ 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_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int launchctl_list_contains_plist(const char *name, const char *cmd)
 {
-       FILE *plist;
-       int i;
+       struct child_process child = CHILD_PROCESS_INIT;
+
+       strvec_split(&child.args, cmd);
+       strvec_pushl(&child.args, "list", name, NULL);
+
+       child.no_stderr = 1;
+       child.no_stdout = 1;
+
+       if (start_command(&child))
+               die(_("failed to start launchctl"));
+
+       /* Returns failure if 'name' doesn't exist. */
+       return !finish_command(&child);
+}
+
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
+{
+       int i, fd;
        const char *preamble, *repeat;
        const char *frequency = get_frequency(schedule);
        char *name = launchctl_service_name(frequency);
        char *filename = launchctl_service_filename(name);
+       struct lock_file lk = LOCK_INIT;
+       static unsigned long lock_file_timeout_ms = ULONG_MAX;
+       struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
+       struct stat st;
+       const char *cmd = "launchctl";
 
-       if (safe_create_leading_directories(filename))
-               die(_("failed to create directories for '%s'"), filename);
-       plist = xfopen(filename, "w");
-
+       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\">"
@@ -1630,7 +1727,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
                   "</array>\n"
                   "<key>StartCalendarInterval</key>\n"
                   "<array>\n";
-       fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+       strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
 
        switch (schedule) {
        case SCHEDULE_HOURLY:
@@ -1639,7 +1736,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
                         "<key>Minute</key><integer>0</integer>\n"
                         "</dict>\n";
                for (i = 1; i <= 23; i++)
-                       fprintf(plist, repeat, i);
+                       strbuf_addf(&plist, repeat, i);
                break;
 
        case SCHEDULE_DAILY:
@@ -1649,50 +1746,91 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
                         "<key>Minute</key><integer>0</integer>\n"
                         "</dict>\n";
                for (i = 1; i <= 6; i++)
-                       fprintf(plist, repeat, i);
+                       strbuf_addf(&plist, repeat, i);
                break;
 
        case SCHEDULE_WEEKLY:
-               fprintf(plist,
-                       "<dict>\n"
-                       "<key>Day</key><integer>0</integer>\n"
-                       "<key>Hour</key><integer>0</integer>\n"
-                       "<key>Minute</key><integer>0</integer>\n"
-                       "</dict>\n");
+               strbuf_addstr(&plist,
+                             "<dict>\n"
+                             "<key>Day</key><integer>0</integer>\n"
+                             "<key>Hour</key><integer>0</integer>\n"
+                             "<key>Minute</key><integer>0</integer>\n"
+                             "</dict>\n");
                break;
 
        default:
                /* unreachable */
                break;
        }
-       fprintf(plist, "</array>\n</dict>\n</plist>\n");
-       fclose(plist);
+       strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
+
+       if (safe_create_leading_directories(filename))
+               die(_("failed to create directories for '%s'"), filename);
 
-       /* bootout might fail if not already running, so ignore */
-       launchctl_boot_plist(0, filename, cmd);
-       if (launchctl_boot_plist(1, filename, cmd))
-               die(_("failed to bootstrap service %s"), filename);
+       if ((long)lock_file_timeout_ms < 0 &&
+           git_config_get_ulong("gc.launchctlplistlocktimeoutms",
+                                &lock_file_timeout_ms))
+               lock_file_timeout_ms = 150;
+
+       fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
+                                              lock_file_timeout_ms);
+
+       /*
+        * Does this file already exist? With the intended contents? Is it
+        * registered already? Then it does not need to be re-registered.
+        */
+       if (!stat(filename, &st) && st.st_size == plist.len &&
+           strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
+           !strbuf_cmp(&plist, &plist2) &&
+           launchctl_list_contains_plist(name, cmd))
+               rollback_lock_file(&lk);
+       else {
+               if (write_in_full(fd, plist.buf, plist.len) < 0 ||
+                   commit_lock_file(&lk))
+                       die_errno(_("could not write '%s'"), filename);
+
+               /* bootout might fail if not already running, so ignore */
+               launchctl_boot_plist(0, filename);
+               if (launchctl_boot_plist(1, filename))
+                       die(_("failed to bootstrap service %s"), filename);
+       }
 
        free(filename);
        free(name);
+       strbuf_release(&plist);
+       strbuf_release(&plist2);
        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)
@@ -1702,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);
 
@@ -1719,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;
@@ -1736,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);
@@ -1840,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;
@@ -1869,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;
@@ -1945,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>]");
@@ -2018,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 640ef4ded595a3a21579a5630e04c3e6a412420d..c7b3ad74c60cb914aa480b24ab7fc768903159bc 100644 (file)
@@ -53,9 +53,7 @@ static void hash_object(const char *path, const char *type, const char *vpath,
                        unsigned flags, int literally)
 {
        int fd;
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               die_errno("Cannot open '%s'", path);
+       fd = xopen(path, O_RDONLY);
        hash_fd(fd, type, vpath, flags, literally);
 }
 
@@ -117,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
                prefix = setup_git_directory_gently(&nongit);
 
        if (vpath && prefix)
-               vpath = xstrdup(prefix_filename(prefix, vpath));
+               vpath = prefix_filename(prefix, vpath);
 
        git_config(git_default_config, NULL);
 
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 8336466865cbef122d552da29611196a1164bcbc..7ce69c087ecd1cc925780b6668a18d0ab1cf00d8 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;
@@ -338,15 +337,11 @@ static const char *open_pack_file(const char *pack_name)
                                                "pack/tmp_pack_XXXXXX");
                        pack_name = strbuf_detach(&tmp_file, NULL);
                } else {
-                       output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
-                       if (output_fd < 0)
-                               die_errno(_("unable to create '%s'"), pack_name);
+                       output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                }
                nothread_data.pack_fd = output_fd;
        } else {
-               input_fd = open(pack_name, O_RDONLY);
-               if (input_fd < 0)
-                       die_errno(_("cannot open packfile '%s'"), pack_name);
+               input_fd = xopen(pack_name, O_RDONLY);
                output_fd = -1;
                nothread_data.pack_fd = input_fd;
        }
@@ -1157,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++) {
@@ -1481,6 +1477,22 @@ static void write_special_file(const char *suffix, const char *msg,
        strbuf_release(&name_buf);
 }
 
+static void rename_tmp_packfile(const char **final_name,
+                               const char *curr_name,
+                               struct strbuf *name, unsigned char *hash,
+                               const char *ext, int make_read_only_if_same)
+{
+       if (*final_name != curr_name) {
+               if (!*final_name)
+                       *final_name = odb_pack_name(name, hash, ext);
+               if (finalize_object_file(curr_name, *final_name))
+                       die(_("unable to rename temporary '*.%s' file to '%s"),
+                           ext, *final_name);
+       } else if (make_read_only_if_same) {
+               chmod(*final_name, 0444);
+       }
+}
+
 static void final(const char *final_pack_name, const char *curr_pack_name,
                  const char *final_index_name, const char *curr_index_name,
                  const char *final_rev_index_name, const char *curr_rev_index_name,
@@ -1509,31 +1521,13 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                write_special_file("promisor", promisor_msg, final_pack_name,
                                   hash, NULL);
 
-       if (final_pack_name != curr_pack_name) {
-               if (!final_pack_name)
-                       final_pack_name = odb_pack_name(&pack_name, hash, "pack");
-               if (finalize_object_file(curr_pack_name, final_pack_name))
-                       die(_("cannot store pack file"));
-       } else if (from_stdin)
-               chmod(final_pack_name, 0444);
-
-       if (final_index_name != curr_index_name) {
-               if (!final_index_name)
-                       final_index_name = odb_pack_name(&index_name, hash, "idx");
-               if (finalize_object_file(curr_index_name, final_index_name))
-                       die(_("cannot store index file"));
-       } else
-               chmod(final_index_name, 0444);
-
-       if (curr_rev_index_name) {
-               if (final_rev_index_name != curr_rev_index_name) {
-                       if (!final_rev_index_name)
-                               final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev");
-                       if (finalize_object_file(curr_rev_index_name, final_rev_index_name))
-                               die(_("cannot store reverse index file"));
-               } else
-                       chmod(final_rev_index_name, 0444);
-       }
+       rename_tmp_packfile(&final_pack_name, curr_pack_name, &pack_name,
+                           hash, "pack", from_stdin);
+       if (curr_rev_index_name)
+               rename_tmp_packfile(&final_rev_index_name, curr_rev_index_name,
+                                   &rev_index_name, hash, "rev", 1);
+       rename_tmp_packfile(&final_index_name, curr_index_name, &index_name,
+                           hash, "idx", 1);
 
        if (do_fsck_object) {
                struct packed_git *p;
@@ -1806,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 1794548c71179afb68c74a231749be5f5fdae3ee..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
 };
 
@@ -84,6 +84,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
                             PARSE_OPT_STOP_AT_NON_OPTION);
        dest = argv[0];
 
+       packet_trace_identity("ls-remote");
+
        UNLEAK(sorting);
 
        if (argc > 1) {
index 664400b8169b673f735e77f7dd86474f1665a3ae..7baef30569fce8e6b4a7353067bb4aba00561557 100644 (file)
@@ -75,9 +75,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
                fprintf(stderr, "corrupt mailbox\n");
                exit(1);
        }
-       fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       if (fd < 0)
-               die_errno("cannot open output file '%s'", name);
+       fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
        output = xfdopen(fd, "w");
 
        /* Copy it out, while searching for a line that begins with
index 22f23990b37b6af8756daa5bcf7a1a7bbef2049c..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;
@@ -739,7 +740,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
 
                for (x = 0; x < xopts_nr; x++)
                        if (parse_merge_opt(&o, xopts[x]))
-                               die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
+                               die(_("unknown strategy option: -X%s"), xopts[x]);
 
                o.branch1 = head_arg;
                o.branch2 = merge_remote_util(remoteheads->item)->name;
@@ -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);
@@ -862,9 +863,11 @@ static void prepare_to_commit(struct commit_list *remoteheads)
                        strbuf_commented_addf(&msg, "\n");
                }
                strbuf_commented_addf(&msg, _(merge_editor_comment));
-               strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
-                       scissors_editor_comment :
-                       no_scissors_editor_comment), comment_line_char);
+               if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+                       strbuf_commented_addf(&msg, _(scissors_editor_comment));
+               else
+                       strbuf_commented_addf(&msg,
+                               _(no_scissors_editor_comment), comment_line_char);
        }
        if (signoff)
                append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
@@ -1136,9 +1139,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
                merge_names = &fetch_head_file;
 
        filename = git_path_fetch_head(the_repository);
-       fd = open(filename, O_RDONLY);
-       if (fd < 0)
-               die_errno(_("could not open '%s' for reading"), filename);
+       fd = xopen(filename, O_RDONLY);
 
        if (strbuf_read(merge_names, fd, 0) < 0)
                die_errno(_("could not read '%s'"), filename);
@@ -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 */
@@ -1622,7 +1632,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
 
        if (fast_forward == FF_ONLY)
-               die(_("Not possible to fast-forward, aborting."));
+               die_ff_impossible();
 
        if (autostash)
                create_autostash(the_repository,
index 8ff0dee2ecbb86881f77bc58ecf86f714a1d8622..6426bbdaceab63a6664f492c34eeaf910ff97abd 100644 (file)
@@ -52,7 +52,6 @@ static struct opts_multi_pack_index {
 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 +60,23 @@ 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 int cmd_multi_pack_index_write(int argc, const char **argv)
 {
        struct option *options;
@@ -68,13 +84,23 @@ 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_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);
@@ -90,10 +116,18 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
 
 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 +140,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 +168,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 +177,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 +200,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 +208,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 +219,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 74bba39ca8293208d3d577a47e2617a11d856c20..71c59583a17f8da6eb948248e19a262781091f28 100644 (file)
@@ -172,9 +172,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
 
                /* write the template message before editing: */
                d->edit_path = git_pathdup("NOTES_EDITMSG");
-               fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
-               if (fd < 0)
-                       die_errno(_("could not create file '%s'"), d->edit_path);
+               fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 
                if (d->given)
                        write_or_die(fd, d->buf.buf, d->buf.len);
index de00adbb9e0d517bf44d6318ec8df26b9f82c503..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);
                }
@@ -1217,6 +1222,7 @@ static void write_pack_file(void)
                if (!pack_to_stdout) {
                        struct stat st;
                        struct strbuf tmpname = STRBUF_INIT;
+                       char *idx_tmp_name = NULL;
 
                        /*
                         * Packs are runtime accessed in their mtime
@@ -1237,7 +1243,8 @@ static void write_pack_file(void)
                                        warning_errno(_("failed utime() on %s"), pack_tmp_name);
                        }
 
-                       strbuf_addf(&tmpname, "%s-", base_name);
+                       strbuf_addf(&tmpname, "%s-%s.", base_name,
+                                   hash_to_hex(hash));
 
                        if (write_bitmap_index) {
                                bitmap_writer_set_checksum(hash);
@@ -1245,23 +1252,29 @@ static void write_pack_file(void)
                                        &to_pack, written_list, nr_written);
                        }
 
-                       finish_tmp_packfile(&tmpname, pack_tmp_name,
+                       stage_tmp_packfiles(&tmpname, pack_tmp_name,
                                            written_list, nr_written,
-                                           &pack_idx_opts, hash);
+                                           &pack_idx_opts, hash, &idx_tmp_name);
 
                        if (write_bitmap_index) {
-                               strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash));
+                               size_t tmpname_len = tmpname.len;
 
+                               strbuf_addstr(&tmpname, "bitmap");
                                stop_progress(&progress_state);
 
                                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;
+                               strbuf_setlen(&tmpname, tmpname_len);
                        }
 
+                       rename_tmp_packfile_idx(&tmpname, &idx_tmp_name);
+
+                       free(idx_tmp_name);
                        strbuf_release(&tmpname);
                        free(pack_tmp_name);
                        puts(hash_to_hex(hash));
@@ -3311,9 +3324,26 @@ static void read_packs_list_from_stdin(void)
        }
 
        /*
-        * First handle all of the excluded packs, marking them as kept in-core
-        * so that later calls to add_object_entry() discards any objects that
-        * are also found in excluded packs.
+        * Arguments we got on stdin may not even be packs. First
+        * check that to avoid segfaulting later on in
+        * e.g. pack_mtime_cmp(), excluded packs are handled below.
+        *
+        * Since we first parsed our STDIN and then sorted the input
+        * lines the pack we error on will be whatever line happens to
+        * sort first. This is lazy, it's enough that we report one
+        * bad case here, we don't need to report the first/last one,
+        * or all of them.
+        */
+       for_each_string_list_item(item, &include_packs) {
+               struct packed_git *p = item->util;
+               if (!p)
+                       die(_("could not find pack '%s'"), item->string);
+       }
+
+       /*
+        * Then, handle all of the excluded packs, marking them as
+        * kept in-core so that later calls to add_object_entry()
+        * discards any objects that are also found in excluded packs.
         */
        for_each_string_list_item(item, &exclude_packs) {
                struct packed_git *p = item->util;
@@ -3388,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);
@@ -3407,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;
@@ -3488,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)
-{
-       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_)
+static int add_object_in_unpacked_pack(const struct object_id *oid,
+                                      struct packed_git *pack,
+                                      uint32_t pos,
+                                      void *_data)
 {
-       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 bda5c32ab6fbfa0f88dbefbe3bdc50ecdb75f959..ae9f5bd7ccde012abb9412f99f5e96cb98253150 100644 (file)
@@ -26,6 +26,7 @@
 #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
@@ -576,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;
 }
@@ -890,6 +891,8 @@ static int run_rebase(const struct object_id *newbase,
        strvec_pushv(&args, opt_strategy_opts.v);
        if (opt_gpg_sign)
                strvec_push(&args, opt_gpg_sign);
+       if (opt_signoff)
+               strvec_push(&args, opt_signoff);
        if (opt_autostash == 0)
                strvec_push(&args, "--no-autostash");
        else if (opt_autostash == 1)
@@ -908,12 +911,18 @@ static int run_rebase(const struct object_id *newbase,
        return ret;
 }
 
-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
+static int get_can_ff(struct object_id *orig_head,
+                     struct oid_array *merge_heads)
 {
        int ret;
        struct commit_list *list = NULL;
        struct commit *merge_head, *head;
+       struct object_id *orig_merge_head;
 
+       if (merge_heads->nr > 1)
+               return 0;
+
+       orig_merge_head = &merge_heads->oid[0];
        head = lookup_commit_reference(the_repository, orig_head);
        commit_list_insert(head, &list);
        merge_head = lookup_commit_reference(the_repository, orig_merge_head);
@@ -924,9 +933,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_
 
 static void show_advice_pull_non_ff(void)
 {
-       advise(_("Pulling without specifying how to reconcile divergent branches is\n"
-                "discouraged. You can squelch this message by running one of the following\n"
-                "commands sometime before your next pull:\n"
+       advise(_("You have divergent branches and need to specify how to reconcile them.\n"
+                "You can do so by running one of the following commands sometime before\n"
+                "your next pull:\n"
                 "\n"
                 "  git config pull.rebase false  # merge (the default strategy)\n"
                 "  git config pull.rebase true   # rebase\n"
@@ -963,8 +972,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
 
        parse_repo_refspecs(argc, argv, &repo, &refspecs);
 
-       if (!opt_ff)
+       if (!opt_ff) {
                opt_ff = xstrdup_or_null(config_get_ff());
+               /*
+                * A subtle point: opt_ff was set on the line above via
+                * reading from config.  opt_rebase, in contrast, is set
+                * before this point via command line options.  The setting
+                * of opt_rebase via reading from config (using
+                * config_get_rebase()) does not happen until later.  We
+                * are relying on the next if-condition happening before
+                * the config_get_rebase() call so that an explicit
+                * "--rebase" can override a config setting of
+                * pull.ff=only.
+                */
+               if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only"))
+                       opt_ff = "--ff";
+       }
 
        if (opt_rebase < 0)
                opt_rebase = config_get_rebase(&rebase_unspecified);
@@ -1038,14 +1061,25 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
                        die(_("Cannot merge multiple branches into empty head."));
                return pull_into_void(merge_heads.oid, &curr_head);
        }
-       if (opt_rebase && merge_heads.nr > 1)
-               die(_("Cannot rebase onto multiple branches."));
+       if (merge_heads.nr > 1) {
+               if (opt_rebase)
+                       die(_("Cannot rebase onto multiple branches."));
+               if (opt_ff && !strcmp(opt_ff, "--ff-only"))
+                       die(_("Cannot fast-forward to multiple branches."));
+       }
 
-       can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
+       can_ff = get_can_ff(&orig_head, &merge_heads);
 
-       if (rebase_unspecified && !opt_ff && !can_ff) {
-               if (opt_verbosity >= 0)
-                       show_advice_pull_non_ff();
+       /* ff-only takes precedence over rebase */
+       if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
+               if (!can_ff)
+                       die_ff_impossible();
+               opt_rebase = REBASE_FALSE;
+       }
+       /* If no action specified and we can't fast forward, then warn. */
+       if (!opt_ff && rebase_unspecified && !can_ff) {
+               show_advice_pull_non_ff();
+               die(_("Need to specify how to reconcile divergent branches."));
        }
 
        if (opt_rebase) {
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 c4d4cf25028848dabeaec31373ef7c175dbc9bdf..34b4744e5f3b7ccd5c4a215f477a622cfa06e4a0 100644 (file)
@@ -323,6 +323,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
        flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
        flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 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: {
@@ -546,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.
@@ -568,17 +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 int move_to_original_branch(struct rebase_options *opts)
 {
        struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -1145,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 */
@@ -1287,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: {
@@ -1296,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);
@@ -1426,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++)
@@ -1538,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;
@@ -1581,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);
@@ -1606,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(_("fatal: 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 =
@@ -1652,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;
        }
 
@@ -1687,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;
@@ -1702,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 */
@@ -1780,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;
        }
 
@@ -1794,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);
@@ -1805,5 +1801,5 @@ cleanup:
        free(options.strategy);
        strbuf_release(&options.git_format_patch_opt);
        free(squash_onto_name);
-       return ret;
+       return !!ret;
 }
index 2d1f97e1ca7b5346d9cae3239165d6824df309af..25cc0c907e133650ed55d173353155d35df33f89 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"
@@ -1306,7 +1307,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 +1464,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w
 
        strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
 
-       if (!find_hook(push_to_checkout_hook))
+       if (!hook_exists(push_to_checkout_hook))
                retval = push_to_deploy(sha1, &env, work_tree);
        else
                retval = push_to_checkout(sha1, &env, work_tree);
@@ -1731,16 +1732,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 +1770,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 +1781,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 +2475,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 +2579,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 5f9bc74adc05aff9a0dc9cf738dc82c733115eb9..cb9f4bfed33cdcfd3ca3d6d5490248387977ba82 100644 (file)
@@ -208,10 +208,10 @@ static struct {
        unsigned optional:1;
 } exts[] = {
        {".pack"},
-       {".idx"},
        {".rev", 1},
        {".bitmap", 1},
        {".promisor", 1},
+       {".idx"},
 };
 
 static unsigned populate_pack_exts(char *name)
@@ -515,6 +515,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                if (!(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;
@@ -582,15 +586,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) {
@@ -725,8 +726,12 @@ 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, flags);
+       }
 
        string_list_clear(&names, 0);
        string_list_clear(&rollback, 0);
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 a7e01667b089feed68a8219ae44e721bbcf8ae0b..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,
 };
 
@@ -230,6 +230,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        args.atomic = atomic;
        args.stateless_rpc = stateless_rpc;
        args.push_options = push_options.nr ? &push_options : NULL;
+       args.url = dest;
 
        if (from_stdin) {
                if (args.stateless_rpc) {
index d77ce7aeb3824bb00bc068063672d60712c7827b..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
 };
@@ -482,10 +482,9 @@ static void snarf_refs(int head, int remotes)
        }
 }
 
-static int rev_is_head(const char *head, const char *name,
-                      unsigned char *head_sha1, unsigned char *sha1)
+static int rev_is_head(const char *head, const char *name)
 {
-       if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
+       if (!head)
                return 0;
        skip_prefix(head, "refs/heads/", &head);
        if (!skip_prefix(name, "refs/heads/", &name))
@@ -806,9 +805,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        /* We are only interested in adding the branch
                         * HEAD points at.
                         */
-                       if (rev_is_head(head,
-                                       ref_name[i],
-                                       head_oid.hash, NULL))
+                       if (rev_is_head(head, ref_name[i]))
                                has_head++;
                }
                if (!has_head) {
@@ -867,10 +864,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        if (1 < num_rev || extra < 0) {
                for (i = 0; i < num_rev; i++) {
                        int j;
-                       int is_head = rev_is_head(head,
-                                                 ref_name[i],
-                                                 head_oid.hash,
-                                                 rev[i]->object.oid.hash);
+                       int is_head = rev_is_head(head, ref_name[i]) &&
+                                     oideq(&head_oid, &rev[i]->object.oid);
                        if (extra < 0)
                                printf("%c [%s] ",
                                       is_head ? '*' : ' ', ref_name[i]);
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 8f42360ca91385387a3da84c948023e0a3b06574..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))
@@ -313,6 +315,17 @@ static int reset_head(void)
        return run_command(&cp);
 }
 
+static int is_path_a_directory(const char *path)
+{
+       /*
+        * This function differs from abspath.c:is_directory() in that
+        * here we use lstat() instead of stat(); we do not want to
+        * follow symbolic links here.
+        */
+       struct stat st;
+       return (!lstat(path, &st) && S_ISDIR(st.st_mode));
+}
+
 static void add_diff_to_buf(struct diff_queue_struct *q,
                            struct diff_options *options,
                            void *data)
@@ -320,6 +333,9 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
        int i;
 
        for (i = 0; i < q->nr; i++) {
+               if (is_path_a_directory(q->queue[i]->one->path))
+                       continue;
+
                strbuf_addstr(data, q->queue[i]->one->path);
 
                /* NUL-terminate: will be fed to update-index -z */
@@ -521,9 +537,6 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
                }
        }
 
-       if (info->has_u && restore_untracked(&info->u_tree))
-               return error(_("could not restore untracked files from stash"));
-
        init_merge_options(&o, the_repository);
 
        o.branch1 = "Updated upstream";
@@ -558,6 +571,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
                unstage_changes_unless_new(&c_tree);
        }
 
+       if (info->has_u && restore_untracked(&info->u_tree))
+               return error(_("could not restore untracked files from stash"));
+
        if (!quiet) {
                struct child_process cp = CHILD_PROCESS_INIT;
 
@@ -1519,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
                } else {
                        struct child_process cp = CHILD_PROCESS_INIT;
                        cp.git_cmd = 1;
+                       /* BUG: this nukes untracked files in the way */
                        strvec_pushl(&cp.args, "reset", "--hard", "-q",
                                     "--no-recurse-submodules", NULL);
                        if (run_command(&cp)) {
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 82fcfc0982423f3c7ee90fe43d30295023cc7873..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)
@@ -293,9 +293,7 @@ static void create_tag(const struct object_id *object, const char *object_ref,
 
                /* write the template message before editing: */
                path = git_pathdup("TAG_EDITMSG");
-               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
-               if (fd < 0)
-                       die_errno(_("could not create file '%s'"), path);
+               fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 
                if (opt->message_given) {
                        write_or_die(fd, buf->buf, buf->len);
index f1f16f2de526d907cc8c5b41da64a7eef3d22a7d..187203e8bb53cbeaf80d2f4b89c04134a2a5c95f 100644 (file)
@@ -95,9 +95,7 @@ static int create_file(const char *path)
 {
        int fd;
        path = get_mtime_path(path);
-       fd = open(path, O_CREAT | O_RDWR, 0644);
-       if (fd < 0)
-               die_errno(_("failed to create file %s"), path);
+       fd = xopen(path, O_CREAT | O_RDWR, 0644);
        return fd;
 }
 
index 6029a80544e560ccc97e77163782f7c432099296..a84e7b47a20620b421b9ab3782cdad4e52bc9e90 100644 (file)
@@ -302,6 +302,12 @@ static void parse_cmd_verify(struct ref_transaction *transaction,
        strbuf_release(&err);
 }
 
+static void report_ok(const char *command)
+{
+       fprintf(stdout, "%s: ok\n", command);
+       fflush(stdout);
+}
+
 static void parse_cmd_option(struct ref_transaction *transaction,
                             const char *next, const char *end)
 {
@@ -317,7 +323,7 @@ static void parse_cmd_start(struct ref_transaction *transaction,
 {
        if (*next != line_termination)
                die("start: extra input: %s", next);
-       puts("start: ok");
+       report_ok("start");
 }
 
 static void parse_cmd_prepare(struct ref_transaction *transaction,
@@ -328,7 +334,7 @@ static void parse_cmd_prepare(struct ref_transaction *transaction,
                die("prepare: extra input: %s", next);
        if (ref_transaction_prepare(transaction, &error))
                die("prepare: %s", error.buf);
-       puts("prepare: ok");
+       report_ok("prepare");
 }
 
 static void parse_cmd_abort(struct ref_transaction *transaction,
@@ -339,7 +345,7 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
                die("abort: extra input: %s", next);
        if (ref_transaction_abort(transaction, &error))
                die("abort: %s", error.buf);
-       puts("abort: ok");
+       report_ok("abort");
 }
 
 static void parse_cmd_commit(struct ref_transaction *transaction,
@@ -350,7 +356,7 @@ static void parse_cmd_commit(struct ref_transaction *transaction,
                die("commit: extra input: %s", next);
        if (ref_transaction_commit(transaction, &error))
                die("commit: %s", error.buf);
-       puts("commit: ok");
+       report_ok("commit");
        ref_transaction_free(transaction);
 }
 
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 b023d9959aae4360cd42414a7f33ee11c550c61a..8785b2ac80699255ae8abd0801922ba025235309 100644 (file)
@@ -23,9 +23,25 @@ static struct bulk_checkin_state {
        uint32_t nr_written;
 } state;
 
+static void finish_tmp_packfile(struct strbuf *basename,
+                               const char *pack_tmp_name,
+                               struct pack_idx_entry **written_list,
+                               uint32_t nr_written,
+                               struct pack_idx_option *pack_idx_opts,
+                               unsigned char hash[])
+{
+       char *idx_tmp_name = NULL;
+
+       stage_tmp_packfiles(basename, pack_tmp_name, written_list, nr_written,
+                           pack_idx_opts, hash, &idx_tmp_name);
+       rename_tmp_packfile_idx(basename, &idx_tmp_name);
+
+       free(idx_tmp_name);
+}
+
 static void finish_bulk_checkin(struct bulk_checkin_state *state)
 {
-       struct object_id oid;
+       unsigned char hash[GIT_MAX_RAWSZ];
        struct strbuf packname = STRBUF_INIT;
        int i;
 
@@ -37,19 +53,20 @@ static void finish_bulk_checkin(struct bulk_checkin_state *state)
                unlink(state->pack_tmp_name);
                goto clear_exit;
        } else if (state->nr_written == 1) {
-               finalize_hashfile(state->f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+               finalize_hashfile(state->f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
        } else {
-               int fd = finalize_hashfile(state->f, oid.hash, 0);
-               fixup_pack_header_footer(fd, oid.hash, state->pack_tmp_name,
-                                        state->nr_written, oid.hash,
+               int fd = finalize_hashfile(state->f, hash, 0);
+               fixup_pack_header_footer(fd, hash, state->pack_tmp_name,
+                                        state->nr_written, hash,
                                         state->offset);
                close(fd);
        }
 
-       strbuf_addf(&packname, "%s/pack/pack-", get_object_directory());
+       strbuf_addf(&packname, "%s/pack/pack-%s.", get_object_directory(),
+                   hash_to_hex(hash));
        finish_tmp_packfile(&packname, state->pack_tmp_name,
                            state->written, state->nr_written,
-                           &state->pack_idx_opts, oid.hash);
+                           &state->pack_idx_opts, hash);
        for (i = 0; i < state->nr_written; i++)
                free(state->written[i]);
 
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);
 
diff --git a/cache.h b/cache.h
index bd4869beee4bb06412e50fe70f2b64fc255fecad..d092820c94337064b49de930e01995b14b767c11 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -958,7 +958,6 @@ extern char *apply_default_ignorewhitespace;
 extern const char *git_attributes_file;
 extern const char *git_hooks_path;
 extern int zlib_compression_level;
-extern int core_compression_level;
 extern int pack_compression_level;
 extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
@@ -995,14 +994,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).
  */
@@ -1211,51 +1202,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)
 {
@@ -1295,6 +1245,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
@@ -1660,7 +1617,9 @@ struct cache_def {
        int track_flags;
        int prefix_len_stat_func;
 };
-#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
+#define CACHE_DEF_INIT { \
+       .path = STRBUF_INIT, \
+}
 static inline void cache_def_clear(struct cache_def *cache)
 {
        strbuf_release(&cache->path);
@@ -1717,13 +1676,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);
 
@@ -1736,6 +1688,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);
diff --git a/check_bindir b/check_bindir
deleted file mode 100755 (executable)
index 623eadc..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-bindir="$1"
-gitexecdir="$2"
-gitcmd="$3"
-if test "$bindir" != "$gitexecdir" && test -x "$gitcmd"
-then
-       echo
-       echo "!! You have installed git-* commands to new gitexecdir."
-       echo "!! Old version git-* commands still remain in bindir."
-       echo "!! Mixing two versions of Git will lead to problems."
-       echo "!! Please remove old version commands in bindir now."
-       echo
-fi
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 26a6689766d7f17b3d38e01de2937838430a5770..07a8c6b199d39cc1df0e1a2bd739c49820741fd0 100755 (executable)
@@ -15,4 +15,8 @@ linux-musl)
        apk add --update build-base curl-dev openssl-dev expat-dev gettext \
                pcre2-dev python3 musl-libintl perl-utils ncurses >/dev/null
        ;;
+pedantic)
+       dnf -yq update >/dev/null &&
+       dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+       ;;
 esac
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 3ce81ffee941b2efb2d129d6b6df15e779894958..cc62616d8063cb5eadc18636d561b52a71253d90 100755 (executable)
@@ -10,6 +10,11 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
 *) ln -s "$cache_dir/.prove" t/.prove;;
 esac
 
+if test "$jobname" = "pedantic"
+then
+       export DEVOPTS=pedantic
+fi
+
 make
 case "$jobname" in
 linux-gcc)
@@ -23,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
@@ -35,10 +41,9 @@ linux-clang)
        export GIT_TEST_DEFAULT_HASH=sha256
        make test
        ;;
-linux-gcc-4.8)
+linux-gcc-4.8|pedantic)
        # Don't run the tests; we only care about whether Git can be
-       # built with GCC 4.8, as it errors out on some undesired (C99)
-       # constructs that newer compilers seem to quietly accept.
+       # built with GCC 4.8 or with pedantic
        ;;
 *)
        make test
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 14d31010dfe57e520c6864df4f84ea732018785f..8d6c02d4bccc0160e69d3c0892946bfc79f10d00 100644 (file)
@@ -7,7 +7,12 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
        if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
                die("Invalid usage of mmap when built with NO_MMAP");
 
-       start = xmalloc(length);
+       if (length == 0) {
+               errno = EINVAL;
+               return MAP_FAILED;
+       }
+
+       start = malloc(length);
        if (start == NULL) {
                errno = ENOMEM;
                return MAP_FAILED;
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 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 f33abeab851542d9df1c540f8b42031add5ea120..2dcbe901b6b7a05f56a66056ac79d9b729454382 100644 (file)
--- a/config.c
+++ b/config.c
@@ -76,7 +76,6 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
-static int core_compression_seen;
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
@@ -137,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;
@@ -185,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);
@@ -426,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;
@@ -438,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;
        }
 
@@ -456,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++) {
@@ -468,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)
 {
@@ -1270,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;
@@ -1400,8 +1381,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
                        die(_("bad zlib compression level %d"), level);
-               core_compression_level = level;
-               core_compression_seen = 1;
                if (!zlib_compression_seen)
                        zlib_compression_level = level;
                if (!pack_compression_seen)
@@ -1796,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)
 {
@@ -1804,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) {
@@ -1820,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)
@@ -1845,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");
        }
 
@@ -1958,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 70b13389ba5f706045b2b77114fa6f4797b58d28..eaf7d6d26187f7265e507bca6ba8d6bf5110f26b 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -164,6 +164,8 @@ enum protocol_version discover_version(struct packet_reader *reader)
                BUG("unknown protocol version");
        }
 
+       trace2_data_intmax("transfer", NULL, "negotiated-version", version);
+
        return version;
 }
 
@@ -555,6 +557,8 @@ const char *parse_feature_value(const char *feature_list, const char *feature, i
                        if (!*value || isspace(*value)) {
                                if (lenp)
                                        *lenp = 0;
+                               if (offset)
+                                       *offset = found + len - feature_list;
                                return value;
                        }
                        /* feature with a value (e.g., "agent=git/1.2.3") */
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
diff --git a/contrib/coccinelle/xopen.cocci b/contrib/coccinelle/xopen.cocci
new file mode 100644 (file)
index 0000000..b71db67
--- /dev/null
@@ -0,0 +1,19 @@
+@@
+identifier fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+  int fd =
+- open
++ xopen
+  (...);
+- 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 5dab8bd579e6ac52bc23b8e6d9f9b6675239a9e6..eb5fd4783d4b3a3efa2369ae21d270caea395358 100644 (file)
@@ -356,7 +356,7 @@ __gitcomp ()
        local cur_="${3-$cur}"
 
        case "$cur_" in
-       --*=)
+       *=)
                ;;
        --no-*)
                local c i=0 IFS=$' \t\n'
@@ -421,7 +421,7 @@ __gitcomp_builtin ()
        local incl="${2-}"
        local excl="${3-}"
 
-       local var=__gitcomp_builtin_"${cmd/-/_}"
+       local var=__gitcomp_builtin_"${cmd//-/_}"
        local options
        eval "options=\${$var-}"
 
@@ -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.
@@ -2650,10 +2657,10 @@ __git_complete_config_variable_name ()
                return
                ;;
        branch.*)
-               local pfx="${cur%.*}."
-               cur_="${cur#*.}"
+               local pfx="${cur_%.*}."
+               cur_="${cur_#*.}"
                __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
-               __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
+               __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
                return
                ;;
        guitool.*.*)
@@ -2687,7 +2694,7 @@ __git_complete_config_variable_name ()
                local pfx="${cur_%.*}."
                cur_="${cur_#*.}"
                __git_compute_all_commands
-               __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
+               __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
                return
                ;;
        remote.*.*)
@@ -2703,7 +2710,7 @@ __git_complete_config_variable_name ()
                local pfx="${cur_%.*}."
                cur_="${cur_#*.}"
                __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
-               __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
+               __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
                return
                ;;
        url.*.*)
@@ -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 4a790d8f4ec18e0bfedbc7f8ce9f2d4291c284e4..ba797e5b3cd59783c0e743fd2df8962918b8a40a 100644 (file)
@@ -80,8 +80,9 @@ else
        COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
 fi
 
-# Call _git() or _gitk() of the bash script, based on the first argument
-_\${1}
+# Call __git_wrap__git_main() or __git_wrap__gitk_main() of the bash script,
+# based on the first argument
+__git_wrap__\${1}_main
 
 IFS=\$'\n'
 if [ \${#COMPREPLY[*]} -eq 0 ]; then
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 3c05c7c669166f3ececfaa7fb9ffaac8dbe6ffbe..000ac7a8d430e4d14472c068b9817e1fc8ee69bd 100644 (file)
@@ -128,6 +128,7 @@ static void credential_apply_config(struct credential *c)
        normalized_url = url_normalize(url.buf, &config.url);
 
        git_config(urlmatch_config_entry, &config);
+       string_list_clear(&config.vars, 1);
        free(normalized_url);
        strbuf_release(&url);
 
index c951cf82774a8a1f4ea67b3a2eb49ea686d79da6..26e8a6df44e9415ffe02fc612ec045c6fa32b032 100644 (file)
@@ -131,12 +131,8 @@ struct hashfile *hashfd_check(const char *name)
        int sink, check;
        struct hashfile *f;
 
-       sink = open("/dev/null", O_WRONLY);
-       if (sink < 0)
-               die_errno("unable to open /dev/null");
-       check = open(name, O_RDONLY);
-       if (check < 0)
-               die_errno("unable to open '%s'", name);
+       sink = xopen("/dev/null", O_WRONLY);
+       check = xopen(name, O_RDONLY);
        f = hashfd(sink, name);
        f->check_fd = check;
        f->check_buffer = xmalloc(f->buffer_len);
index 70b754481c877cab3d7f87cf08b7403846925860..11d60da5b72512332185c703658cee6d14f551bc 100755 (executable)
@@ -13,11 +13,11 @@ get_version_line() {
 }
 
 get_family() {
-       get_version_line | sed 's/^\(.*\) version [0-9][^ ]* .*/\1/'
+       get_version_line | sed 's/^\(.*\) version [0-9].*/\1/'
 }
 
 get_version() {
-       get_version_line | sed 's/^.* version \([0-9][^ ]*\) .*/\1/'
+       get_version_line | sed 's/^.* version \([0-9][^ ]*\).*/\1/'
 }
 
 print_flags() {
@@ -38,10 +38,7 @@ case "$(get_family)" in
 gcc)
        print_flags gcc
        ;;
-clang)
-       print_flags clang
-       ;;
-"FreeBSD clang")
+clang | *" clang")
        print_flags clang
        ;;
 "Apple LLVM")
index f9eadc4fc1a64b57fd0b506822c6396219763c92..ca085a03efc280deecd7cefce7ce00f5492a0d4c 100644 (file)
@@ -117,6 +117,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
                        continue;
 
+               if (revs->diffopt.prefix &&
+                   strncmp(ce->name, revs->diffopt.prefix, revs->diffopt.prefix_length))
+                       continue;
+
                if (ce_stage(ce)) {
                        struct combine_diff_path *dpath;
                        struct diff_filepair *pair;
index d897fd8a293319b34172c5c816d28ffb37ad7ce9..5060ccd890bd307b1276e367e3b70872718e3af7 100644 (file)
@@ -6,7 +6,7 @@ typedef void (*diff_merges_setup_func_t)(struct rev_info *);
 static void set_separate(struct rev_info *revs);
 
 static diff_merges_setup_func_t set_to_default = set_separate;
-static int suppress_parsing;
+static int suppress_m_parsing;
 
 static void suppress(struct rev_info *revs)
 {
@@ -91,9 +91,9 @@ int diff_merges_config(const char *value)
        return 0;
 }
 
-void diff_merges_suppress_options_parsing(void)
+void diff_merges_suppress_m_parsing(void)
 {
-       suppress_parsing = 1;
+       suppress_m_parsing = 1;
 }
 
 int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
@@ -102,10 +102,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
        const char *optarg;
        const char *arg = argv[0];
 
-       if (suppress_parsing)
-               return 0;
-
-       if (!strcmp(arg, "-m")) {
+       if (!suppress_m_parsing && !strcmp(arg, "-m")) {
                set_to_default(revs);
        } else if (!strcmp(arg, "-c")) {
                set_combined(revs);
@@ -153,9 +150,6 @@ void diff_merges_set_dense_combined_if_unset(struct rev_info *revs)
 
 void diff_merges_setup_revs(struct rev_info *revs)
 {
-       if (suppress_parsing)
-               return;
-
        if (revs->combine_merges == 0)
                revs->dense_combined_merges = 0;
        if (revs->separate_merges == 0)
index b5d57f6563e3ab8323e4091201aeb62b4a5530d1..19639689bb05f7b241f37a22ad16e83d1d0be8db 100644 (file)
@@ -11,7 +11,7 @@ struct rev_info;
 
 int diff_merges_config(const char *value);
 
-void diff_merges_suppress_options_parsing(void);
+void diff_merges_suppress_m_parsing(void);
 
 int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
 
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..fdd3eeafa94791791aefc976988f4cd0b5a5f58f 100644 (file)
--- a/editor.c
+++ b/editor.c
@@ -58,7 +58,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) {
                        /*
diff --git a/entry.c b/entry.c
index 125fabdbd52c4d042e40ad66ef9c12fff846e25a..9b0f968a70c9cbeba7c00ec09d0e5dbf42110533 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -159,25 +159,25 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
        return !available;
 }
 
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
+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 = start_delayed_progress(_("Filtering content"), delayed_object_count);
+       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 */
@@ -224,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 b8c0e170dc791a7bebde1e6e71c194f3301a9cff..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
 /*
@@ -43,7 +43,8 @@ static inline int checkout_entry(struct cache_entry *ce,
 }
 
 void enable_delayed_checkout(struct checkout *state);
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
+                           int show_progress);
 
 /*
  * Unlink the last component and schedule the leading directories for
index d6b22ede7ea2885d5649e59b00590c2b2a3fdf65..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;
@@ -41,7 +40,6 @@ char *apply_default_ignorewhitespace;
 const char *git_attributes_file;
 const char *git_hooks_path;
 int zlib_compression_level = Z_BEST_SPEED;
-int core_compression_level;
 int pack_compression_level = Z_DEFAULT_COMPRESSION;
 int fsync_object_files;
 size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
@@ -96,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;
 
@@ -330,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,
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
index ed035f32c29d516164622994095122afe9f49e8e..64319bed43f2b4916910480223f1089ce90990ef 100755 (executable)
@@ -222,10 +222,11 @@ if ($state->{method} eq 'pserver') {
         open my $passwd, "<", $authdb or die $!;
         while (<$passwd>) {
             if (m{^\Q$user\E:(.*)}) {
-                if (crypt($user, descramble($password)) eq $1) {
+                my $hash = crypt(descramble($password), $1);
+                if (defined $hash and $hash eq $1) {
                     $auth_ok = 1;
                 }
-            };
+            }
         }
         close $passwd;
 
index e65d969d0bb257f5c95ba7fe955a3788d4b98a32..5262d88ee32073f83bbb0c18c3386b6a3db12cd8 100755 (executable)
@@ -376,7 +376,7 @@ sub read_config {
                        @$target = @values;
                }
                else {
-                       my $v = $known_keys->{$key}->[0];
+                       my $v = $known_keys->{$key}->[-1];
                        next unless defined $v;
                        next if $configured->{$setting}++;
                        $$target = $v;
@@ -1697,7 +1697,6 @@ EOF
 
 $in_reply_to = $initial_in_reply_to;
 $references = $initial_in_reply_to || '';
-$subject = $initial_subject;
 $message_num = 0;
 
 # Prepares the email, prompts the user, sends it out
@@ -1720,6 +1719,7 @@ sub process_file {
        @xh = ();
        my $input_format = undef;
        my @header = ();
+       $subject = $initial_subject;
        $message = "";
        $message_num++;
        # First unfold multiline header fields
@@ -1926,15 +1926,23 @@ sub process_file {
        }
 
        # set up for the next message
-       if ($thread && $message_was_sent &&
-               ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
-               $message_num == 1)) {
-               $in_reply_to = $message_id;
-               if (length $references > 0) {
-                       $references .= "\n $message_id";
-               } else {
-                       $references = "$message_id";
+       if ($thread) {
+               if ($message_was_sent &&
+                 ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
+                 $message_num == 1)) {
+                       $in_reply_to = $message_id;
+                       if (length $references > 0) {
+                               $references .= "\n $message_id";
+                       } else {
+                               $references = "$message_id";
+                       }
                }
+       } elsif (!defined $initial_in_reply_to) {
+               # --thread and --in-reply-to manage the "In-Reply-To" header and by
+               # extension the "References" header. If these commands are not used, reset
+               # the header values to their defaults.
+               $in_reply_to = undef;
+               $references = '';
        }
        $message_id = undef;
        $num_sent++;
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
diff --git a/git.c b/git.c
index 268cdd82cfc630c610902225c6152a820b75dace..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 },
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;
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 8119247149af70b461c51077e99007a2d2a96cc7..53cf7ad57093a0c8d6327f5033b4732347f7cf94 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)
@@ -643,8 +551,8 @@ static void redact_sensitive_header(struct strbuf *header)
        const char *sensitive_header;
 
        if (trace_curl_redact &&
-           (skip_prefix(header->buf, "Authorization:", &sensitive_header) ||
-            skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
+           (skip_iprefix(header->buf, "Authorization:", &sensitive_header) ||
+            skip_iprefix(header->buf, "Proxy-Authorization:", &sensitive_header))) {
                /* The first token is the type, which is OK to log */
                while (isspace(*sensitive_header))
                        sensitive_header++;
@@ -654,7 +562,7 @@ static void redact_sensitive_header(struct strbuf *header)
                strbuf_setlen(header,  sensitive_header - header->buf);
                strbuf_addstr(header, " <redacted>");
        } else if (trace_curl_redact &&
-                  skip_prefix(header->buf, "Cookie:", &sensitive_header)) {
+                  skip_iprefix(header->buf, "Cookie:", &sensitive_header)) {
                struct strbuf redacted_header = STRBUF_INIT;
                const char *cookie;
 
@@ -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);
 
@@ -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 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 462b3956340be0bb917f7bda2cf82bdc9d489b11..40ce152024d7dc3cf6d387dc5d0c1a47542129bd 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -37,6 +37,7 @@ static void free_mailmap_info(void *p, const char *s)
                 s, debug_str(mi->name), debug_str(mi->email));
        free(mi->name);
        free(mi->email);
+       free(mi);
 }
 
 static void free_mailmap_entry(void *p, const char *s)
@@ -52,6 +53,7 @@ static void free_mailmap_entry(void *p, const char *s)
 
        me->namemap.strdup_strings = 1;
        string_list_clear_func(&me->namemap, free_mailmap_info);
+       free(me);
 }
 
 /*
index 6eb910d6f0cf829f6dd028d32f89b3aa628ce12e..e5456f4722894a20f09c6255d921306ab5884ccd 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;
@@ -665,6 +647,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 +805,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 +904,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 +1099,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 +1354,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 +1512,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 +1521,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 +1536,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 +1557,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,6 +1578,10 @@ static int merge_submodule(struct merge_options *opt,
        if (is_null_oid(b))
                return 0;
 
+       /*
+        * NEEDSWORK: Remove this when all submodule object accesses are
+        * through explicitly specified repositores.
+        */
        if (add_submodule_odb(path)) {
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s (not checked out)"),
@@ -1571,39 +1589,48 @@ static int merge_submodule(struct merge_options *opt,
                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 (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
+               path_msg(opt, path, 0,
+                               _("Failed to merge submodule %s (not checked out)"),
+                               path);
+               return 0;
+       }
+
+       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 +1642,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:
@@ -1652,7 +1679,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 +2322,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 +2341,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 +2367,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 +2400,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 +2827,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 +2951,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 +3002,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 +3015,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 +3133,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 +3146,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 +3621,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 +3714,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 +3784,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 +4062,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 +4070,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 +4085,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;
@@ -4065,7 +4107,7 @@ static int record_conflicted_index_entries(struct merge_options *opt)
        state.istate = index;
        original_cache_nr = index->cache_nr;
 
-       /* Put every entry from paths into plist, then sort */
+       /* Append every entry from conflicted into index, then sort */
        strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
                const char *path = e->key;
                struct conflict_info *ci = e->value;
@@ -4293,6 +4335,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 +4401,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 +4419,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 +4429,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 +4439,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..c5537518899684b2fa4809211fee4309b9b57290 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);
 }
 
 /*
@@ -411,8 +409,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 +1114,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 +1123,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 +1138,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;
                        }
@@ -1174,6 +1174,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,27 +1199,36 @@ static int merge_submodule(struct merge_options *opt,
        if (is_null_oid(b))
                return 0;
 
+       /*
+        * NEEDSWORK: Remove this when all submodule object accesses are
+        * through explicitly specified repositores.
+        */
        if (add_submodule_odb(path)) {
                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))) {
-               output(opt, 1, _("Failed to merge submodule %s (commits not present)"), 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(&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);
+               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);
@@ -1227,9 +1238,10 @@ static int merge_submodule(struct merge_options *opt,
                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);
@@ -1239,7 +1251,8 @@ static int merge_submodule(struct merge_options *opt,
                else
                        ; /* no output */
 
-               return 1;
+               ret = 1;
+               goto cleanup;
        }
 
        /*
@@ -1251,10 +1264,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:
@@ -1281,7 +1294,9 @@ static int merge_submodule(struct merge_options *opt,
        }
 
        object_array_clear(&merges);
-       return 0;
+cleanup:
+       repo_clear(&subrepo);
+       return ret;
 }
 
 static int merge_mode_and_contents(struct merge_options *opt,
@@ -3750,6 +3765,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..4a01583fe81546920d4dcf990c8bafc85b82758a 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)
@@ -882,7 +874,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,7 +882,171 @@ 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 struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p,
+                                                   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);
+       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,
+                            unsigned flags)
+{
+       struct packing_data pdata;
+       struct pack_idx_entry **index;
+       struct commit **commits = NULL;
+       uint32_t i, commits_nr;
+       uint16_t options = 0;
+       char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
+       int ret;
+
+       if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
+               options |= BITMAP_OPT_HASH_CACHE;
+
+       prepare_midx_packing_data(&pdata, ctx);
+
+       commits = find_commits_for_midx_bitmap(&commits_nr, ctx);
+
+       /*
+        * 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_drop,
                               const char *preferred_pack_name,
                               unsigned flags)
@@ -901,20 +1057,26 @@ 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);
+       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 +1094,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++;
                }
        }
@@ -947,18 +1128,89 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
        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_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 +1281,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 +1296,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 +1326,27 @@ 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, 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,6 +1361,7 @@ cleanup:
        free(ctx.pack_perm);
        free(ctx.pack_order);
        free(midx_name);
+
        return result;
 }
 
@@ -1111,8 +1369,7 @@ int write_midx_file(const char *object_dir,
                    const char *preferred_pack_name,
                    unsigned flags)
 {
-       return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name,
-                                  flags);
+       return write_midx_internal(object_dir, NULL, preferred_pack_name, flags);
 }
 
 struct clear_midx_data {
@@ -1135,7 +1392,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 +1403,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 +1422,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 +1648,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, &packs_to_drop, NULL, flags);
+               m = NULL;
+       }
 
        string_list_clear(&packs_to_drop, 0);
        return result;
@@ -1580,7 +1840,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, flags);
        m = NULL;
 
 cleanup:
diff --git a/midx.h b/midx.h
index 8684cf0fefe81dbc5aa32b6c48203c12ae2c12da..541d9ac728d59a6eeaa17df9d92e743a43bbd3fc 100644 (file)
--- a/midx.h
+++ b/midx.h
@@ -8,6 +8,8 @@ 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 +43,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);
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..112d9b4badcfdeb2667aea1f2ea07c3adb6ea8e7 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)
 {
@@ -1592,6 +1546,10 @@ static int do_oid_object_info_extended(struct repository *r,
                                break;
                }
 
+               if (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 +1574,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 +1683,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();
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..1e647a5be3033ab34239545674cc668b4b0969aa 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;
@@ -370,7 +371,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 +456,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 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..33a3732992c38be50f3858ec3bd8de0bd9a58412 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;
+}
+
+static 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..ed46d27077f7fb3dfd4f698da1c29f9e5748d665 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,6 +52,7 @@ 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);
@@ -87,12 +89,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 479b8f2f9c916880ee692ea812b689d0237c18b1..74f4eae668d5555f99fa4d6af0f486ddc94b5447 100644 (file)
@@ -109,7 +109,7 @@ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos);
  * If the reverse index has not yet been loaded, or the position is out of
  * bounds, this function aborts.
  *
- * This function runs in time O(log N) with the number of objects in the MIDX.
+ * This function runs in constant time.
  */
 uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
 
@@ -120,7 +120,7 @@ uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
  * If the reverse index has not yet been loaded, or the position is out of
  * bounds, this function aborts.
  *
- * This function runs in constant time.
+ * This function runs in time O(log N) with the number of objects in the MIDX.
  */
 int midx_to_pack_pos(struct multi_pack_index *midx, uint32_t at, uint32_t *pos);
 
index f1fc3ecafadef49d73e6efe7451c683d6e3ccd39..a5846f3a346dd032c11cf047af8fc65d57c726e0 100644 (file)
@@ -75,9 +75,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
                        index_name = strbuf_detach(&tmp_file, NULL);
                } else {
                        unlink(index_name);
-                       fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
-                       if (fd < 0)
-                               die_errno("unable to create '%s'", index_name);
+                       fd = xopen(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
                }
                f = hashfd(fd, index_name);
        }
@@ -224,6 +222,9 @@ const char *write_rev_file(const char *rev_name,
        uint32_t i;
        const char *ret;
 
+       if (!(flags & WRITE_REV) && !(flags & WRITE_REV_VERIFY))
+               return NULL;
+
        ALLOC_ARRAY(pack_order, nr_objects);
        for (i = 0; i < nr_objects; i++)
                pack_order[i] = i;
@@ -256,9 +257,7 @@ const char *write_rev_file_order(const char *rev_name,
                        rev_name = strbuf_detach(&tmp_file, NULL);
                } else {
                        unlink(rev_name);
-                       fd = open(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
-                       if (fd < 0)
-                               die_errno("unable to create '%s'", rev_name);
+                       fd = xopen(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
                }
                f = hashfd(fd, rev_name);
        } else if (flags & WRITE_REV_VERIFY) {
@@ -462,49 +461,48 @@ struct hashfile *create_tmp_packfile(char **pack_tmp_name)
        return hashfd(fd, *pack_tmp_name);
 }
 
-void finish_tmp_packfile(struct strbuf *name_buffer,
+static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source,
+                               const char *ext)
+{
+       size_t name_prefix_len = name_prefix->len;
+
+       strbuf_addstr(name_prefix, ext);
+       if (rename(source, name_prefix->buf))
+               die_errno("unable to rename temporary file to '%s'",
+                         name_prefix->buf);
+       strbuf_setlen(name_prefix, name_prefix_len);
+}
+
+void rename_tmp_packfile_idx(struct strbuf *name_buffer,
+                            char **idx_tmp_name)
+{
+       rename_tmp_packfile(name_buffer, *idx_tmp_name, "idx");
+}
+
+void stage_tmp_packfiles(struct strbuf *name_buffer,
                         const char *pack_tmp_name,
                         struct pack_idx_entry **written_list,
                         uint32_t nr_written,
                         struct pack_idx_option *pack_idx_opts,
-                        unsigned char hash[])
+                        unsigned char hash[],
+                        char **idx_tmp_name)
 {
-       const char *idx_tmp_name, *rev_tmp_name = NULL;
-       int basename_len = name_buffer->len;
+       const char *rev_tmp_name = NULL;
 
        if (adjust_shared_perm(pack_tmp_name))
                die_errno("unable to make temporary pack file readable");
 
-       idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
-                                     pack_idx_opts, hash);
-       if (adjust_shared_perm(idx_tmp_name))
+       *idx_tmp_name = (char *)write_idx_file(NULL, written_list, nr_written,
+                                              pack_idx_opts, hash);
+       if (adjust_shared_perm(*idx_tmp_name))
                die_errno("unable to make temporary index file readable");
 
        rev_tmp_name = write_rev_file(NULL, written_list, nr_written, hash,
                                      pack_idx_opts->flags);
 
-       strbuf_addf(name_buffer, "%s.pack", hash_to_hex(hash));
-
-       if (rename(pack_tmp_name, name_buffer->buf))
-               die_errno("unable to rename temporary pack file");
-
-       strbuf_setlen(name_buffer, basename_len);
-
-       strbuf_addf(name_buffer, "%s.idx", hash_to_hex(hash));
-       if (rename(idx_tmp_name, name_buffer->buf))
-               die_errno("unable to rename temporary index file");
-
-       strbuf_setlen(name_buffer, basename_len);
-
-       if (rev_tmp_name) {
-               strbuf_addf(name_buffer, "%s.rev", hash_to_hex(hash));
-               if (rename(rev_tmp_name, name_buffer->buf))
-                       die_errno("unable to rename temporary reverse-index file");
-       }
-
-       strbuf_setlen(name_buffer, basename_len);
-
-       free((void *)idx_tmp_name);
+       rename_tmp_packfile(name_buffer, pack_tmp_name, "pack");
+       if (rev_tmp_name)
+               rename_tmp_packfile(name_buffer, rev_tmp_name, "rev");
 }
 
 void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought)
diff --git a/pack.h b/pack.h
index fa1395452624bfaf93bf86e88a4d12580ca77bee..b22bfc4a18e8543be54d6328c4e3cae6270c0aa7 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -110,6 +110,14 @@ int encode_in_pack_object_header(unsigned char *hdr, int hdr_len,
 int read_pack_header(int fd, struct pack_header *);
 
 struct hashfile *create_tmp_packfile(char **pack_tmp_name);
-void finish_tmp_packfile(struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, uint32_t nr_written, struct pack_idx_option *pack_idx_opts, unsigned char sha1[]);
+void stage_tmp_packfiles(struct strbuf *name_buffer,
+                        const char *pack_tmp_name,
+                        struct pack_idx_entry **written_list,
+                        uint32_t nr_written,
+                        struct pack_idx_option *pack_idx_opts,
+                        unsigned char hash[],
+                        char **idx_tmp_name);
+void rename_tmp_packfile_idx(struct strbuf *basename,
+                            char **idx_tmp_name);
 
 #endif
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 9631529c10a072a21acb76d65545d61a80b337d4..73b5ead5099d3efdb3e1999865ab868da5053ea6 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -671,7 +671,11 @@ const char *repo_logmsg_reencode(struct repository *r,
         * If the re-encoding failed, out might be NULL here; in that
         * case we just return the commit message verbatim.
         */
-       return out ? out : msg;
+       if (!out) {
+               warning("unable to reencode commit to '%s'", output_encoding);
+               return msg;
+       }
+       return out;
 }
 
 static int mailmap_name(const char **email, size_t *email_len,
index 13a9e63a04afba22065cbb260ee5ed57123bdc91..bbde91810ac6667a1ee624d112ad26cf23776b1e 100644 (file)
@@ -69,13 +69,13 @@ static void send_info(struct repository *r, struct packet_writer *writer,
                        }
                }
 
-               packet_writer_write(writer, "%s",
-                                   strbuf_detach(&send_buffer, NULL));
+               packet_writer_write(writer, "%s", send_buffer.buf);
+               strbuf_reset(&send_buffer);
        }
+       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 */
index 052d7edbb9a332c40a4bde7db85bed14564df51c..c53f7df5be4abae7a4f5792f42e4c4ecac006e9a 100644 (file)
@@ -73,6 +73,8 @@ enum protocol_version determine_protocol_version_server(void)
                string_list_clear(&list, 0);
        }
 
+       trace2_data_intmax("transfer", NULL, "negotiated-version", version);
+
        return version;
 }
 
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 e9479794b46a51fd08b8ed287a50f35b7c92a4d5..cac89a2f4f2c8d4c6471f0a55bc725cf17a2d9b9 100644 (file)
@@ -26,17 +26,6 @@ struct patch_util {
        struct object_id oid;
 };
 
-static size_t find_end_of_line(char *buffer, unsigned long size)
-{
-       char *eol = memchr(buffer, '\n', size);
-
-       if (!eol)
-               return size;
-
-       *eol = '\0';
-       return eol + 1 - buffer;
-}
-
 /*
  * Reads the patches into a string list, with the `util` field being populated
  * as struct object_id (will need to be free()d).
@@ -49,7 +38,7 @@ static int read_patches(const char *range, struct string_list *list,
        struct patch_util *util = NULL;
        int in_header = 1;
        char *line, *current_filename = NULL;
-       int offset, len;
+       ssize_t len;
        size_t size;
 
        strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
@@ -86,11 +75,18 @@ static int read_patches(const char *range, struct string_list *list,
 
        line = contents.buf;
        size = contents.len;
-       for (offset = 0; size > 0; offset += len, size -= len, line += len) {
+       for (; size > 0; size -= len, line += len) {
                const char *p;
+               char *eol;
+
+               eol = memchr(line, '\n', size);
+               if (eol) {
+                       *eol = '\0';
+                       len = eol + 1 - line;
+               } else {
+                       len = size;
+               }
 
-               len = find_end_of_line(line, size);
-               line[len - 1] = '\0';
                if (skip_prefix(line, "commit ", &p)) {
                        if (util) {
                                string_list_append(list, buf.buf)->util = util;
@@ -132,7 +128,8 @@ static int read_patches(const char *range, struct string_list *list,
                        strbuf_addch(&buf, '\n');
                        if (!util->diff_offset)
                                util->diff_offset = buf.len;
-                       line[len - 1] = '\n';
+                       if (eol)
+                               *eol = '\n';
                        orig_len = len;
                        len = parse_git_diff_header(&root, &linenr, 0, line,
                                                    len, size, &patch);
@@ -485,6 +482,7 @@ static void output(struct string_list *a, struct string_list *b,
        else
                diff_setup(&opts);
 
+       opts.no_free = 1;
        if (!opts.output_format)
                opts.output_format = DIFF_FORMAT_PATCH;
        opts.flags.suppress_diff_headers = 1;
@@ -545,6 +543,8 @@ static void output(struct string_list *a, struct string_list *b,
        strbuf_release(&buf);
        strbuf_release(&dashes);
        strbuf_release(&indent);
+       opts.no_free = 0;
+       diff_free(&opts);
 }
 
 int show_range_diff(const char *range1, const char *range2,
index 9048ef9e905251bacb516be6afdb474b4e59648f..a78b88a41bf01c5fa131b2f3d6d683bff465690b 100644 (file)
@@ -1944,13 +1944,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 +2400,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 +2833,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 +3087,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 +3200,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 +3261,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 +3276,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 +3287,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 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..7f019c2377effa49a90427ffec0451c400941e0c 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
@@ -698,7 +694,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 +706,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 +1410,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 +1483,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 +1519,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 +1620,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 +1635,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 +1676,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 */
@@ -2370,19 +2365,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 +2385,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 677b7e4cdd2d056f48f700a0bc6301dd91c50beb..6a6ead0b99bbd8b0326081516e3b6cac45366012 100644 (file)
@@ -227,7 +227,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 +278,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 +336,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 +354,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 +458,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 +529,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 +538,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 +566,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 +609,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 +619,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 +629,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 +655,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 +673,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 +693,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 +710,7 @@ retry:
                 */
                if (refs_verify_refname_available(
                                    refs->packed_ref_store, refname,
-                                   extras, skip, err))
+                                   extras, NULL, err))
                        goto error_return;
        }
 
@@ -744,6 +744,11 @@ 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->iter0->oid,
@@ -854,39 +859,112 @@ static struct ref_iterator *files_ref_iterator_begin(
 }
 
 /*
- * Verify that the reference locked by lock has the value old_oid
- * (unless it is NULL).  Fail if the reference doesn't exist and
- * mustexist is set. Return 0 on success. On error, write an error
- * message to err, set errno, and return a negative value.
+ * Callback function for raceproof_create_file(). This function is
+ * expected to do something that makes dirname(path) permanent despite
+ * the fact that other processes might be cleaning up empty
+ * directories at the same time. Usually it will create a file named
+ * path, but alternatively it could create another file in that
+ * directory, or even chdir() into that directory. The function should
+ * return 0 if the action was completed successfully. On error, it
+ * should return a nonzero result and set errno.
+ * raceproof_create_file() treats two errno values specially:
+ *
+ * - ENOENT -- dirname(path) does not exist. In this case,
+ *             raceproof_create_file() tries creating dirname(path)
+ *             (and any parent directories, if necessary) and calls
+ *             the function again.
+ *
+ * - EISDIR -- the file already exists and is a directory. In this
+ *             case, raceproof_create_file() removes the directory if
+ *             it is empty (and recursively any empty directories that
+ *             it contains) and calls the function again.
+ *
+ * Any other errno causes raceproof_create_file() to fail with the
+ * callback's return value and errno.
+ *
+ * Obviously, this function should be OK with being called again if it
+ * fails with ENOENT or EISDIR. In other scenarios it will not be
+ * called again.
+ */
+typedef int create_file_fn(const char *path, void *cb);
+
+/*
+ * Create a file in dirname(path) by calling fn, creating leading
+ * directories if necessary. Retry a few times in case we are racing
+ * with another process that is trying to clean up the directory that
+ * contains path. See the documentation for create_file_fn for more
+ * details.
+ *
+ * Return the value and set the errno that resulted from the most
+ * recent call of fn. fn is always called at least once, and will be
+ * called more than once if it returns ENOENT or EISDIR.
  */
-static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
-                      const struct object_id *old_oid, int mustexist,
-                      struct strbuf *err)
+static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
 {
-       assert(err);
+       /*
+        * The number of times we will try to remove empty directories
+        * in the way of path. This is only 1 because if another
+        * process is racily creating directories that conflict with
+        * us, we don't want to fight against them.
+        */
+       int remove_directories_remaining = 1;
 
-       if (refs_read_ref_full(ref_store, lock->ref_name,
-                              mustexist ? RESOLVE_REF_READING : 0,
-                              &lock->old_oid, NULL)) {
-               if (old_oid) {
-                       int save_errno = errno;
-                       strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
-                       errno = save_errno;
-                       return -1;
-               } else {
-                       oidclr(&lock->old_oid);
-                       return 0;
-               }
-       }
-       if (old_oid && !oideq(&lock->old_oid, old_oid)) {
-               strbuf_addf(err, "ref '%s' is at %s but expected %s",
-                           lock->ref_name,
-                           oid_to_hex(&lock->old_oid),
-                           oid_to_hex(old_oid));
-               errno = EBUSY;
-               return -1;
+       /*
+        * The number of times that we will try to create the
+        * directories containing path. We are willing to attempt this
+        * more than once, because another process could be trying to
+        * clean up empty directories at the same time as we are
+        * trying to create them.
+        */
+       int create_directories_remaining = 3;
+
+       /* A scratch copy of path, filled lazily if we need it: */
+       struct strbuf path_copy = STRBUF_INIT;
+
+       int ret, save_errno;
+
+       /* Sanity check: */
+       assert(*path);
+
+retry_fn:
+       ret = fn(path, cb);
+       save_errno = errno;
+       if (!ret)
+               goto out;
+
+       if (errno == EISDIR && remove_directories_remaining-- > 0) {
+               /*
+                * A directory is in the way. Maybe it is empty; try
+                * to remove it:
+                */
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
+                       goto retry_fn;
+       } else if (errno == ENOENT && create_directories_remaining-- > 0) {
+               /*
+                * Maybe the containing directory didn't exist, or
+                * maybe it was just deleted by a process that is
+                * racing with us to clean up empty directories. Try
+                * to create it:
+                */
+               enum scld_error scld_result;
+
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+
+               do {
+                       scld_result = safe_create_leading_directories(path_copy.buf);
+                       if (scld_result == SCLD_OK)
+                               goto retry_fn;
+               } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
        }
-       return 0;
+
+out:
+       strbuf_release(&path_copy);
+       errno = save_errno;
+       return ret;
 }
 
 static int remove_empty_directories(struct strbuf *path)
@@ -910,64 +988,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 +1021,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 +1043,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
 
  out:
        strbuf_release(&ref_file);
-       errno = last_errno;
        return lock;
 }
 
@@ -1415,8 +1452,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 +1474,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);
@@ -1569,7 +1604,7 @@ static int log_ref_setup(struct files_ref_store *refs,
                        goto error;
                }
        } else {
-               *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
+               *logfd = open(logfile, O_APPEND | O_WRONLY);
                if (*logfd < 0) {
                        if (errno == ENOENT || errno == EISDIR) {
                                /*
@@ -1846,9 +1881,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 +2449,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 +3070,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 +3087,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 +3099,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..47247a149178f364423f84572c5b2912772d10be 100644 (file)
@@ -724,9 +724,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 +739,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;
        }
 
@@ -1600,6 +1600,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 +1609,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 +1630,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 49d732f6db961c8daa1f0ff0f4589059420f636c..a5ad8a39fb405430f7d5f9ffd18af6472c3b215f 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
@@ -522,7 +465,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();
index 3bfb89d2b343e958ed094a7fd922f2fd6e0c4178..5c042ae718cb370ddba39e27f9f972636384bace 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
index 3155708345fcbccdd02f69dd4b155f9b6fd1d098..72746407fc3dd4df57d17894967cece6804bf0d5 100644 (file)
@@ -245,8 +245,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 +377,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 +470,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 +520,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 */
@@ -593,7 +607,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 +634,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 +656,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;
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 dfb863d80835254ce363f22160606e0aa85c21e8..f958543d707d0da5282b7fb1bbb70a53bc834647 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -135,7 +135,7 @@ static inline void init_remotes_hash(void)
 
 static struct remote *make_remote(const char *name, int len)
 {
-       struct remote *ret, *replaced;
+       struct remote *ret;
        struct remotes_hash_key lookup;
        struct hashmap_entry lookup_entry, *e;
 
@@ -162,8 +162,8 @@ static struct remote *make_remote(const char *name, int len)
        remotes[remotes_nr++] = ret;
 
        hashmap_entry_init(&ret->ent, lookup_entry.hash);
-       replaced = hashmap_put_entry(&remotes_hash, ret, ent);
-       assert(replaced == NULL);  /* no previous entry overwritten */
+       if (hashmap_put_entry(&remotes_hash, ret, ent))
+               BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
        return ret;
 }
 
@@ -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 f72e72cce73f1a1decbdd99b098f050f37bea41f..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];
@@ -761,9 +766,7 @@ fail_pipe:
                notify_pipe[0] = notify_pipe[1] = -1;
 
        if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) {
-               null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
-               if (null_fd < 0)
-                       die_errno(_("open /dev/null failed"));
+               null_fd = xopen("/dev/null", O_RDWR | O_CLOEXEC);
                set_cloexec(null_fd);
        }
 
@@ -1044,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;
@@ -1319,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;
@@ -1886,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");
 
@@ -1903,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 5a79e0e711031907bcec04acc18a64c2b9d40adc..b3a495b7b1998f992b69a4b6a37613a8a7a61dba 100644 (file)
@@ -425,8 +425,10 @@ static void get_commons_through_negotiation(const char *url,
        child.no_stdin = 1;
        child.out = -1;
        strvec_pushl(&child.args, "fetch", "--negotiate-only", NULL);
-       for (ref = remote_refs; ref; ref = ref->next)
-               strvec_pushf(&child.args, "--negotiation-tip=%s", oid_to_hex(&ref->new_oid));
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!is_null_oid(&ref->new_oid))
+                       strvec_pushf(&child.args, "--negotiation-tip=%s", oid_to_hex(&ref->new_oid));
+       }
        strvec_push(&child.args, url);
 
        if (start_command(&child))
index 3cdb20b07432b4dcb9640c12ab72866fef83a166..74ecb23d20e730b2716ba0f9b17d7c8d494c1779 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);
@@ -983,7 +997,8 @@ static int run_git_commit(const char *defmsg,
 
        cmd.git_cmd = 1;
 
-       if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+       if (is_rebase_i(opts) && !(!defmsg && (flags & AMEND_MSG)) &&
+           read_env_script(&cmd.env_array)) {
                const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                return error(_(staged_changes_advice),
@@ -1241,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);
 
@@ -1293,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());
                }
@@ -1445,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;
@@ -2065,7 +2080,7 @@ static int do_pick_commit(struct repository *r,
                /*
                 * We do not intend to commit immediately.  We just want to
                 * merge the differences in, so let's compute the tree
-                * that represents the "current" state for merge-recursive
+                * that represents the "current" state for the merge machinery
                 * to work on.
                 */
                if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
@@ -2346,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)) {
@@ -2353,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;
 }
 
@@ -2671,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;
 
@@ -2679,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))
@@ -3041,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;
@@ -3245,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");
@@ -3677,6 +3694,7 @@ 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)) {
@@ -3739,10 +3757,9 @@ static struct commit *lookup_label(const char *label, int len,
 static int do_merge(struct repository *r,
                    struct commit *commit,
                    const char *arg, int arg_len,
-                   int flags, struct replay_opts *opts)
+                   int flags, int *check_todo, struct replay_opts *opts)
 {
-       int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
-               EDIT_MSG | VERIFY_MSG : 0;
+       int run_commit_flags = 0;
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
@@ -3816,6 +3833,45 @@ static int do_merge(struct repository *r,
                goto leave_merge;
        }
 
+       /*
+        * If HEAD is not identical to the first parent of the original merge
+        * commit, we cannot fast-forward.
+        */
+       can_fast_forward = opts->allow_ff && commit && commit->parents &&
+               oideq(&commit->parents->item->object.oid,
+                     &head_commit->object.oid);
+
+       /*
+        * If any merge head is different from the original one, we cannot
+        * fast-forward.
+        */
+       if (can_fast_forward) {
+               struct commit_list *p = commit->parents->next;
+
+               for (j = to_merge; j && p; j = j->next, p = p->next)
+                       if (!oideq(&j->item->object.oid,
+                                  &p->item->object.oid)) {
+                               can_fast_forward = 0;
+                               break;
+                       }
+               /*
+                * If the number of merge heads differs from the original merge
+                * commit, we cannot fast-forward.
+                */
+               if (j || p)
+                       can_fast_forward = 0;
+       }
+
+       if (can_fast_forward) {
+               rollback_lock_file(&lock);
+               ret = fast_forward_to(r, &commit->object.oid,
+                                     &head_commit->object.oid, 0, opts);
+               if (flags & TODO_EDIT_MERGE_MSG)
+                       goto fast_forward_edit;
+
+               goto leave_merge;
+       }
+
        if (commit) {
                const char *encoding = get_commit_output_encoding();
                const char *message = logmsg_reencode(commit, NULL, encoding);
@@ -3865,46 +3921,6 @@ static int do_merge(struct repository *r,
                }
        }
 
-       /*
-        * If HEAD is not identical to the first parent of the original merge
-        * commit, we cannot fast-forward.
-        */
-       can_fast_forward = opts->allow_ff && commit && commit->parents &&
-               oideq(&commit->parents->item->object.oid,
-                     &head_commit->object.oid);
-
-       /*
-        * If any merge head is different from the original one, we cannot
-        * fast-forward.
-        */
-       if (can_fast_forward) {
-               struct commit_list *p = commit->parents->next;
-
-               for (j = to_merge; j && p; j = j->next, p = p->next)
-                       if (!oideq(&j->item->object.oid,
-                                  &p->item->object.oid)) {
-                               can_fast_forward = 0;
-                               break;
-                       }
-               /*
-                * If the number of merge heads differs from the original merge
-                * commit, we cannot fast-forward.
-                */
-               if (j || p)
-                       can_fast_forward = 0;
-       }
-
-       if (can_fast_forward) {
-               rollback_lock_file(&lock);
-               ret = fast_forward_to(r, &commit->object.oid,
-                                     &head_commit->object.oid, 0, opts);
-               if (flags & TODO_EDIT_MERGE_MSG) {
-                       run_commit_flags |= AMEND_MSG;
-                       goto fast_forward_edit;
-               }
-               goto leave_merge;
-       }
-
        if (strategy || to_merge->next) {
                /* Octopus merge */
                struct child_process cmd = CHILD_PROCESS_INIT;
@@ -3935,7 +3951,10 @@ static int do_merge(struct repository *r,
                                strvec_pushf(&cmd.args,
                                             "-X%s", opts->xopts[k]);
                }
-               strvec_push(&cmd.args, "--no-edit");
+               if (!(flags & TODO_EDIT_MERGE_MSG))
+                       strvec_push(&cmd.args, "--no-edit");
+               else
+                       strvec_push(&cmd.args, "--edit");
                strvec_push(&cmd.args, "--no-ff");
                strvec_push(&cmd.args, "--no-log");
                strvec_push(&cmd.args, "--no-stat");
@@ -3988,7 +4007,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
@@ -4035,10 +4054,17 @@ static int do_merge(struct repository *r,
                 * value (a negative one would indicate that the `merge`
                 * command needs to be rescheduled).
                 */
-       fast_forward_edit:
                ret = !!run_git_commit(git_path_merge_msg(r), opts,
                                       run_commit_flags);
 
+       if (!ret && flags & TODO_EDIT_MERGE_MSG) {
+       fast_forward_edit:
+               *check_todo = 1;
+               run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
+               ret = !!run_git_commit(NULL, opts, run_commit_flags);
+       }
+
+
 leave_merge:
        strbuf_release(&ref_name);
        rollback_lock_file(&lock);
@@ -4254,6 +4280,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"
@@ -4405,9 +4455,8 @@ static int pick_commits(struct repository *r,
                        if ((res = do_reset(r, arg, item->arg_len, opts)))
                                reschedule = 1;
                } else if (item->command == TODO_MERGE) {
-                       if ((res = do_merge(r, item->commit,
-                                           arg, item->arg_len,
-                                           item->flags, opts)) < 0)
+                       if ((res = do_merge(r, item->commit, arg, item->arg_len,
+                                           item->flags, &check_todo, opts)) < 0)
                                reschedule = 1;
                        else if (item->commit)
                                record_in_rewritten(&item->commit->object.oid,
@@ -4433,20 +4482,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++;
@@ -4716,6 +4754,9 @@ static int commit_staged_changes(struct repository *r,
                    refs_delete_ref(get_main_ref_store(r), "",
                                    "CHERRY_PICK_HEAD", NULL, 0))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
+               if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
+                       return error_errno(_("could not remove '%s'"),
+                                          git_path_merge_msg(r));
                if (!final_fixup)
                        return 0;
        }
@@ -5099,6 +5140,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;
@@ -5149,8 +5191,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;
 
@@ -5214,6 +5261,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:
@@ -5334,6 +5384,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;
@@ -5369,8 +5420,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,
@@ -5380,6 +5436,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;
 }
 
index 60a156ea906273b2bf7ba5a833a55e68dc2dafa2..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,6 +157,7 @@ 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);
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 */
diff --git a/setup.c b/setup.c
index eb9367ca5cbebf6855cd271407fd9533ffced795..347d7181ae907c027f01cbc8ec42a2b64de06ebc 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1423,11 +1423,9 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code)
 /* if any standard file descriptor is missing open it to /dev/null */
 void sanitize_stdfds(void)
 {
-       int fd = open("/dev/null", O_RDWR, 0);
-       while (fd != -1 && fd < 2)
-               fd = dup(fd);
-       if (fd == -1)
-               die_errno(_("open /dev/null or dup failed"));
+       int fd = xopen("/dev/null", O_RDWR);
+       while (fd < 2)
+               fd = xdup(fd);
        if (fd > 2)
                close(fd);
 }
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 c6b4feec413a8ffeda31cadee1a85020abca466b..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);
 
@@ -283,6 +293,7 @@ void ensure_full_index(struct index_state *istate)
 
        /* Copy back into original index. */
        memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
+       memcpy(&istate->dir_hash, &full->dir_hash, sizeof(full->dir_hash));
        istate->sparse_index = 0;
        free(istate->cache);
        istate->cache = full->cache;
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 5b1113abf8fccd49c936ed3fb5fe5d9e5dc774aa..3b36bbc49f08b2fa38fb97994053ed74b44ef766 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
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 fdcad75b45b33fdc6974fd35c2102ecbcaba31e4..9f55c8766ba9de77437275b8796f8e348fe35e03 100644 (file)
--- a/strvec.h
+++ b/strvec.h
@@ -29,11 +29,13 @@ extern const char *empty_strvec[];
  */
 struct strvec {
        const char **v;
-       int nr;
-       int alloc;
+       size_t nr;
+       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..f3c99634a974a5e3f2f4b68836782ad74a2fbd1d 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,33 @@ 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);
+               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 +260,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 +525,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 +722,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);
@@ -1281,9 +1318,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 +1420,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 +1468,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 +1847,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 +1896,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 +1953,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 +2083,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 +2101,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 +2118,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 +2138,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 +2160,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 +2313,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..b92155a822ea962cc4d7c899e89681884f25914d 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,16 @@ 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 (in which case we might want to replace this with a trace2
+call so that users can make it visible if accessing submodule objects
+without an explicit repository still happens) or needs to be abandoned
+for whatever reason (in which case the migrated codepaths still retain
+their performance benefits).
+
 Naming Tests
 ------------
 
@@ -753,7 +774,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 +821,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 +1013,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..cb0d27049a07e1dcd886bbe6aaede26176a54ee2 100644 (file)
@@ -60,12 +60,26 @@ 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;
+}
+
 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] <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]);
        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 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/
index dc75b834518c8f2d2e1cc9e54fdc8d6af2729125..ec6b9b107da4eaab70e221b07c25c9f949f09b14 100644 (file)
@@ -151,3 +151,59 @@ test_editor_unchanged () {
        EOF
        test_cmp expect actual
 }
+
+# Set up an editor for testing reword commands
+# Checks that there are no uncommitted changes when rewording and that the
+# todo-list is reread after each
+set_reword_editor () {
+       >reword-actual &&
+       >reword-oid &&
+
+       # Check rewording keeps the original authorship
+       GIT_AUTHOR_NAME="Reword Author"
+       GIT_AUTHOR_EMAIL="reword.author@example.com"
+       GIT_AUTHOR_DATE=@123456
+
+       write_script reword-sequence-editor.sh <<-\EOF &&
+       todo="$(cat "$1")" &&
+       echo "exec git log -1 --pretty=format:'%an <%ae> %at%n%B%n' \
+               >>reword-actual" >"$1" &&
+       printf "%s\n" "$todo" >>"$1"
+       EOF
+
+       write_script reword-editor.sh <<-EOF &&
+       # Save the oid of the first reworded commit so we can check rebase
+       # fast-forwards to it. Also check that we do not write .git/MERGE_MSG
+       # when fast-forwarding
+       if ! test -s reword-oid
+       then
+               git rev-parse HEAD >reword-oid &&
+               if test -f .git/MERGE_MSG
+               then
+                       echo 1>&2 "error: .git/MERGE_MSG exists"
+                       exit 1
+               fi
+       fi &&
+       # There should be no uncommited changes
+       git diff --exit-code HEAD &&
+       # The todo-list should be re-read after a reword
+       GIT_SEQUENCE_EDITOR="\"$PWD/reword-sequence-editor.sh\"" \
+               git rebase --edit-todo &&
+       echo edited >>"\$1"
+       EOF
+
+       test_set_editor "$PWD/reword-editor.sh"
+}
+
+# Check the results of a rebase after calling set_reword_editor
+# Pass the commits that were reworded in the order that they were picked
+# Expects the first pick to be a fast-forward
+check_reworded_commits () {
+       test_cmp_rev "$(cat reword-oid)" "$1^{commit}" &&
+       git log --format="%an <%ae> %at%n%B%nedited%n" --no-walk=unsorted "$@" \
+               >reword-expected &&
+       test_cmp reword-expected reword-actual &&
+       git log --format="%an <%ae> %at%n%B" -n $# --first-parent --reverse \
+               >reword-log &&
+       test_cmp reword-expected reword-log
+}
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 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/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 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 cb87768513c8035cb377e245df1628de23aec0ad..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 &&
@@ -1271,28 +1059,29 @@ P=$(test_oid root)
 
 test_expect_success 'git commit-tree records the correct tree in a commit' '
        commit0=$(echo NO | git commit-tree $P) &&
-       tree=$(git show --pretty=raw $commit0 |
-                sed -n -e "s/^tree //p" -e "/^author /q") &&
+       git show --pretty=raw $commit0 >out &&
+       tree=$(sed -n -e "s/^tree //p" -e "/^author /q" out) &&
        test "z$tree" = "z$P"
 '
 
 test_expect_success 'git commit-tree records the correct parent in a commit' '
        commit1=$(echo NO | git commit-tree $P -p $commit0) &&
-       parent=$(git show --pretty=raw $commit1 |
-               sed -n -e "s/^parent //p" -e "/^author /q") &&
+       git show --pretty=raw $commit1 >out &&
+       parent=$(sed -n -e "s/^parent //p" -e "/^author /q" out) &&
        test "z$commit0" = "z$parent"
 '
 
 test_expect_success 'git commit-tree omits duplicated parent in a commit' '
        commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
-            parent=$(git show --pretty=raw $commit2 |
-               sed -n -e "s/^parent //p" -e "/^author /q" |
-               sort -u) &&
+       git show --pretty=raw $commit2 >out &&
+       cat >match.sed <<-\EOF &&
+       s/^parent //p
+       /^author /q
+       EOF
+       parent=$(sed -n -f match.sed out | sort -u) &&
        test "z$commit0" = "z$parent" &&
-       numparent=$(git show --pretty=raw $commit2 |
-               sed -n -e "s/^parent //p" -e "/^author /q" |
-               wc -l) &&
-       test $numparent = 1
+       git show --pretty=raw $commit2 >out &&
+       test_stdout_line_count = 1 sed -n -f match.sed out
 '
 
 test_expect_success 'update-index D/F conflict' '
index e3137d638ee5bb07b9278c1a9b90a207ad024a08..37d68ef03be4a468a4926d37f3cfab40f78ac9bb 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 '
@@ -21,7 +22,7 @@ test_expect_success POSIXPERM,SANITY 'write-tree should notice unwritable reposi
        test_must_fail git write-tree
 '
 
-test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' '
+test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' '
        test_when_finished "chmod 775 .git/objects .git/objects/??" &&
        chmod a-w .git/objects .git/objects/?? &&
        test_must_fail git commit -m second
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 b5749f327dd17bd4577fc3764e811020117cd9ef..33dfc9cd562327b725a3338ac0e9b6787f58c9fe 100755 (executable)
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
 
 TEST_ROOT="$PWD"
 PATH=$TEST_ROOT:$PATH
@@ -1061,4 +1062,74 @@ test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
        )
 '
 
+test_expect_success PERL 'setup for progress tests' '
+       git init progress &&
+       (
+               cd progress &&
+               git config filter.delay.process "rot13-filter.pl delay-progress.log clean smudge delay" &&
+               git config filter.delay.required true &&
+
+               echo "*.a filter=delay" >.gitattributes &&
+               touch test-delay10.a &&
+               git add . &&
+               git commit -m files
+       )
+'
+
+test_delayed_checkout_progress () {
+       if test "$1" = "!"
+       then
+               local expect_progress=N &&
+               shift
+       else
+               local expect_progress=
+       fi &&
+
+       if test $# -lt 1
+       then
+               BUG "no command given to test_delayed_checkout_progress"
+       fi &&
+
+       (
+               cd progress &&
+               GIT_PROGRESS_DELAY=0 &&
+               export GIT_PROGRESS_DELAY &&
+               rm -f *.a delay-progress.log &&
+
+               "$@" 2>err &&
+               grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delay-progress.log &&
+               if test "$expect_progress" = N
+               then
+                       ! grep "Filtering content" err
+               else
+                       grep "Filtering content" err
+               fi
+       )
+}
+
+for mode in pathspec branch
+do
+       case "$mode" in
+       pathspec) opt='.' ;;
+       branch) opt='-f HEAD' ;;
+       esac
+
+       test_expect_success PERL,TTY "delayed checkout shows progress by default on tty ($mode checkout)" '
+               test_delayed_checkout_progress test_terminal git checkout $opt
+       '
+
+       test_expect_success PERL "delayed checkout ommits progress on non-tty ($mode checkout)" '
+               test_delayed_checkout_progress ! git checkout $opt
+       '
+
+       test_expect_success PERL,TTY "delayed checkout ommits progress with --quiet ($mode checkout)" '
+               test_delayed_checkout_progress ! test_terminal git checkout --quiet $opt
+       '
+
+       test_expect_success PERL,TTY "delayed checkout honors --[no]-progress ($mode checkout)" '
+               test_delayed_checkout_progress ! test_terminal git checkout --no-progress $opt &&
+               test_delayed_checkout_progress test_terminal git checkout --quiet --progress $opt
+       '
+done
+
 test_done
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 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..ca91c6a67f8fb7223b05870be0c40c013a51f5dc 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
+       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 4506cd435b050b1f8395824cfc161bdeb1703030..0d4f73acaa87e5f29a6346abb8d1dc6736d41794 100755 (executable)
@@ -1598,6 +1598,40 @@ test_expect_success 'transaction cannot restart ongoing transaction' '
        test_must_fail git show-ref --verify refs/heads/restart
 '
 
+test_expect_success PIPE 'transaction flushes status updates' '
+       mkfifo in out &&
+       (git update-ref --stdin <in >out &) &&
+
+       exec 9>in &&
+       exec 8<out &&
+       test_when_finished "exec 9>&-" &&
+       test_when_finished "exec 8<&-" &&
+
+       echo "start" >&9 &&
+       echo "start: ok" >expected &&
+       read line <&8 &&
+       echo "$line" >actual &&
+       test_cmp expected actual &&
+
+       echo "create refs/heads/flush $A" >&9 &&
+
+       echo prepare >&9 &&
+       echo "prepare: ok" >expected &&
+       read line <&8 &&
+       echo "$line" >actual &&
+       test_cmp expected actual &&
+
+       # This must now fail given that we have locked the ref.
+       test_must_fail git update-ref refs/heads/flush $B 2>stderr &&
+       grep "fatal: update_ref failed for ref ${SQ}refs/heads/flush${SQ}: cannot lock ref" stderr &&
+
+       echo commit >&9 &&
+       echo "commit: ok" >expected &&
+       read line <&8 &&
+       echo "$line" >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'directory not created deleting packed ref' '
        git branch d1/d2/r1 HEAD &&
        git pack-refs --all &&
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 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 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 cc4b10236e2c8335465185cff84105208b807aa9..e575ffb4ffb4c2d9c69da9b83706a7eecd01a6ef 100755 (executable)
@@ -1272,6 +1272,19 @@ test_expect_success 'attempt to delete a branch merged to its base' '
        test_must_fail git branch -d my10
 '
 
+test_expect_success 'branch --delete --force removes dangling branch' '
+       git checkout main &&
+       test_commit unstable &&
+       hash=$(git rev-parse HEAD) &&
+       objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
+       git branch --no-track dangling &&
+       mv $objpath $objpath.x &&
+       test_when_finished "mv $objpath.x $objpath" &&
+       git branch --delete --force dangling &&
+       git for-each-ref refs/heads/dangling >actual &&
+       test_must_be_empty actual
+'
+
 test_expect_success 'use --edit-description' '
        write_script editor <<-\EOF &&
                echo "New contents" >"$1"
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 e26762d0b29400f687df390d0b8e4c52e6f3bba0..f6e48644978b63ab00ccf40cca379e0770aef0b9 100755 (executable)
@@ -20,6 +20,7 @@ test_expect_success setup '
        git add hello &&
        git commit -m "hello" &&
        git branch skip-reference &&
+       git tag hello &&
 
        echo world >> hello &&
        git commit -a -m "hello world" &&
@@ -36,7 +37,8 @@ test_expect_success setup '
        test_tick &&
        GIT_AUTHOR_NAME="Another Author" \
                GIT_AUTHOR_EMAIL="another.author@example.com" \
-               git commit --amend --no-edit -m amended-goodbye &&
+               git commit --amend --no-edit -m amended-goodbye \
+                       --reset-author &&
        test_tick &&
        git tag amended-goodbye &&
 
@@ -51,7 +53,7 @@ test_expect_success setup '
        '
 
 test_expect_success 'rebase with git am -3 (default)' '
-       test_must_fail git rebase main
+       test_must_fail git rebase --apply main
 '
 
 test_expect_success 'rebase --skip can not be used with other options' '
@@ -95,6 +97,13 @@ test_expect_success 'moved back to branch correctly' '
 
 test_debug 'gitk --all & sleep 1'
 
+test_expect_success 'skipping final pick removes .git/MERGE_MSG' '
+       test_must_fail git rebase --onto hello reverted-goodbye^ \
+               reverted-goodbye &&
+       git rebase --skip &&
+       test_path_is_missing .git/MERGE_MSG
+'
+
 test_expect_success 'correct advice upon picking empty commit' '
        test_when_finished "git rebase --abort" &&
        test_must_fail git rebase -i --onto goodbye \
index 827b0450b9f83a33624e4ab7d2b8ecbf6ed6b38c..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^
 '
 
@@ -763,6 +764,19 @@ test_expect_success 'reword' '
        git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'no uncommited changes when rewording the todo list is reloaded' '
+       git checkout E &&
+       test_when_finished "git checkout @{-1}" &&
+       (
+               set_fake_editor &&
+               GIT_SEQUENCE_EDITOR="\"$PWD/fake-editor.sh\"" &&
+               export GIT_SEQUENCE_EDITOR &&
+               set_reword_editor &&
+               FAKE_LINES="reword 1 reword 2" git rebase -i C
+       ) &&
+       check_reworded_commits D E
+'
+
 test_expect_success 'rebase -i can copy notes' '
        git config notes.rewrite.rebase true &&
        git config notes.rewriteRef "refs/notes/*" &&
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 b62c16026fc3f391a5f1bc92f7bbdb3e42e63669..22eca73aa3e926cf58d0c723f97c1d0394021371 100755 (executable)
@@ -21,7 +21,7 @@ test_expect_success 'setup' '
        git checkout main
 '
 
-test_expect_success 'interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue with works with touched file' '
        rm -fr .git/rebase-* &&
        git reset --hard &&
        git checkout main &&
@@ -31,12 +31,22 @@ test_expect_success 'interactive rebase --continue works with touched file' '
        git rebase --continue
 '
 
-test_expect_success 'non-interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue removes .git/MERGE_MSG' '
+       git checkout -f --detach topic &&
+
+       test_must_fail git rebase --onto main HEAD^ &&
+       git read-tree --reset -u HEAD &&
+       test_path_is_file .git/MERGE_MSG &&
+       git rebase --continue &&
+       test_path_is_missing .git/MERGE_MSG
+'
+
+test_expect_success 'apply based rebase --continue works with touched file' '
        rm -fr .git/rebase-* &&
        git reset --hard &&
        git checkout main &&
 
-       test_must_fail git rebase --onto main main topic &&
+       test_must_fail git rebase --apply --onto main main topic &&
        echo "Resolved" >F2 &&
        git add F2 &&
        test-tool chmtime =-60 F1 &&
@@ -240,7 +250,7 @@ test_rerere_autoupdate () {
        '
 }
 
-test_rerere_autoupdate
+test_rerere_autoupdate --apply
 test_rerere_autoupdate -m
 GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
 test_rerere_autoupdate -i
index 6748070df523020eb4c933c1d0176a6f1fae7c22..43c82d9a33b003d194e1ec7284e312e4e84337d7 100755 (executable)
@@ -172,19 +172,39 @@ test_expect_success 'failed `merge <branch>` does not crash' '
        grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
 '
 
-test_expect_success 'fast-forward merge -c still rewords' '
-       git checkout -b fast-forward-merge-c H &&
+test_expect_success 'merge -c commits before rewording and reloads todo-list' '
+       cat >script-from-scratch <<-\EOF &&
+       merge -c E B
+       merge -c H G
+       EOF
+
+       git checkout -b merge-c H &&
        (
-               set_fake_editor &&
-               FAKE_COMMIT_MESSAGE=edited \
-                       GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
-                       git rebase -ir @^
+               set_reword_editor &&
+               GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
+                       git rebase -i -r D
        ) &&
-       echo edited >expected &&
-       git log --pretty=format:%B -1 >actual &&
-       test_cmp expected actual
+       check_reworded_commits E H
 '
 
+test_expect_success 'merge -c rewords when a strategy is given' '
+       git checkout -b merge-c-with-strategy H &&
+       write_script git-merge-override <<-\EOF &&
+       echo overridden$1 >G.t
+       git add G.t
+       EOF
+
+       PATH="$PWD:$PATH" \
+       GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
+       GIT_EDITOR="echo edited >>" \
+               git rebase --no-ff -ir -s override -Xxopt E &&
+       test_write_lines overridden--xopt >expect &&
+       test_cmp expect G.t &&
+       test_write_lines H "" edited "" >expect &&
+       git log --format=%B -1 >actual &&
+       test_cmp expect actual
+
+'
 test_expect_success 'with a branch tip that was cherry-picked already' '
        git checkout -b already-upstream main &&
        base="$(git rev-parse --verify HEAD)" &&
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 873aa56e359d6401b6cab262cfc70fcf9f94a45b..f0a82be9de7654a0956b1d9a4f24e161869e021e 100755 (executable)
@@ -1307,4 +1307,62 @@ test_expect_success 'stash -c stash.useBuiltin=false warning ' '
        test_must_be_empty err
 '
 
+test_expect_success 'git stash succeeds despite directory/file change' '
+       test_create_repo directory_file_switch_v1 &&
+       (
+               cd directory_file_switch_v1 &&
+               test_commit init &&
+
+               test_write_lines this file has some words >filler &&
+               git add filler &&
+               git commit -m filler &&
+
+               git rm filler &&
+               mkdir filler &&
+               echo contents >filler/file &&
+               git stash push
+       )
+'
+
+test_expect_success 'git stash can pop file -> directory saved changes' '
+       test_create_repo directory_file_switch_v2 &&
+       (
+               cd directory_file_switch_v2 &&
+               test_commit init &&
+
+               test_write_lines this file has some words >filler &&
+               git add filler &&
+               git commit -m filler &&
+
+               git rm filler &&
+               mkdir filler &&
+               echo contents >filler/file &&
+               cp filler/file expect &&
+               git stash push --include-untracked &&
+               git stash apply --index &&
+               test_cmp expect filler/file
+       )
+'
+
+test_expect_success 'git stash can pop directory -> file saved changes' '
+       test_create_repo directory_file_switch_v3 &&
+       (
+               cd directory_file_switch_v3 &&
+               test_commit init &&
+
+               mkdir filler &&
+               test_write_lines some words >filler/file1 &&
+               test_write_lines and stuff >filler/file2 &&
+               git add filler &&
+               git commit -m filler &&
+
+               git rm -rf filler &&
+               echo contents >filler &&
+               cp filler expect &&
+               git stash push --include-untracked &&
+               git stash apply --index &&
+               test_cmp expect filler
+       )
+'
+
 test_done
index e561a8e48521a022ea90891f96d1054cf1ecc68d..28683d059d3bdcc79b7d0ac0a1add9c164e957c5 100755 (executable)
@@ -65,7 +65,7 @@ test_expect_success setup '
        export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
 
        git checkout master &&
-       git pull -s ours . side &&
+       git pull -s ours --no-rebase . side &&
 
        GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
        GIT_COMMITTER_DATE="2006-06-26 00:05:00 +0000" &&
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 61ba5f707fbd419aa3625eeedc39210f9d6d59ac..fab351b48a1f52b450f3911aa6edafae70015613 100755 (executable)
@@ -162,4 +162,57 @@ check_diff_relative_option subdir file2 true --no-relative --relative
 check_diff_relative_option . file2 false --no-relative --relative=subdir
 check_diff_relative_option . file2 true --no-relative --relative=subdir
 
+test_expect_success 'setup diff --relative unmerged' '
+       test_commit zero file0 &&
+       test_commit base subdir/file0 &&
+       git switch -c br1 &&
+       test_commit one file0 &&
+       test_commit sub1 subdir/file0 &&
+       git switch -c br2 base &&
+       test_commit two file0 &&
+       git switch -c br3 &&
+       test_commit sub3 subdir/file0
+'
+
+test_expect_success 'diff --relative without change in subdir' '
+       git switch br2 &&
+       test_when_finished "git merge --abort" &&
+       test_must_fail git merge one &&
+       git -C subdir diff --relative >out &&
+       test_must_be_empty out &&
+       git -C subdir diff --relative --name-only >out &&
+       test_must_be_empty out
+'
+
+test_expect_success 'diff --relative --name-only with change in subdir' '
+       git switch br3 &&
+       test_when_finished "git merge --abort" &&
+       test_must_fail git merge sub1 &&
+       test_write_lines file0 file0 >expected &&
+       git -C subdir diff --relative --name-only >out &&
+       test_cmp expected out
+'
+
+test_expect_failure 'diff --relative with change in subdir' '
+       git switch br3 &&
+       br1_blob=$(git rev-parse --short --verify br1:subdir/file0) &&
+       br3_blob=$(git rev-parse --short --verify br3:subdir/file0) &&
+       test_when_finished "git merge --abort" &&
+       test_must_fail git merge br1 &&
+       cat >expected <<-EOF &&
+       diff --cc file0
+       index $br3_blob,$br1_blob..0000000
+       --- a/file0
+       +++ b/file0
+       @@@ -1,1 -1,1 +1,5 @@@
+       ++<<<<<<< HEAD
+        +sub3
+       ++=======
+       + sub1
+       ++>>>>>>> br1
+       EOF
+       git -C subdir diff --relative >out &&
+       test_cmp expected out
+'
+
 test_done
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 fad6d3f5424a34a2bdea53a0ff9563b86eb19ce9..d370ecfe0d9eea752d81d02f50f5739bfc923b04 100755 (executable)
@@ -158,4 +158,27 @@ test_expect_success 'apply binary -p0 diff' '
        test -z "$(git diff --name-status binary -- file3)"
 '
 
+test_expect_success 'reject truncated binary diff' '
+       do_reset &&
+
+       # this length is calculated to get us very close to
+       # the 8192-byte strbuf we will use to read in the patch.
+       test-tool genrandom foo 6205 >file1 &&
+       git diff --binary >patch &&
+
+       # truncate the patch at the second "literal" line,
+       # but exclude the trailing newline. We must use perl
+       # for this, since tools like "sed" cannot reliably
+       # produce output without the trailing newline.
+       perl -pe "
+               if (/^literal/ && \$count++ >= 1) {
+                       chomp;
+                       print;
+                       exit 0;
+               }
+       " <patch >patch.trunc &&
+
+       do_reset &&
+       test_must_fail git apply patch.trunc
+'
 test_done
index 65147efdea9a00e30d156e6f4d5d72a3987f230d..cc3aa3314a3448cd1d5c845ce9803f0877db83ce 100755 (executable)
@@ -230,4 +230,49 @@ test_expect_success 'apply with --3way --cached and conflicts' '
        test_cmp expect.diff actual.diff
 '
 
+test_expect_success 'apply binary file patch' '
+       git reset --hard main &&
+       cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+       git add bin.png &&
+       git commit -m "add binary file" &&
+
+       cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+       git diff --binary >bin.diff &&
+       git reset --hard &&
+
+       # Apply must succeed.
+       git apply bin.diff
+'
+
+test_expect_success 'apply binary file patch with 3way' '
+       git reset --hard main &&
+       cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+       git add bin.png &&
+       git commit -m "add binary file" &&
+
+       cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+       git diff --binary >bin.diff &&
+       git reset --hard &&
+
+       # Apply must succeed.
+       git apply --3way --index bin.diff
+'
+
+test_expect_success 'apply full-index patch with 3way' '
+       git reset --hard main &&
+       cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+       git add bin.png &&
+       git commit -m "add binary file" &&
+
+       cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+       git diff --full-index >bin.diff &&
+       git reset --hard &&
+
+       # Apply must succeed.
+       git apply --3way --index bin.diff
+'
+
 test_done
index 9d8d3c72e7efe68b8e0011c1ec9b77d9a0820b30..2374151662b88ceec17777e01b823612fd8edfd4 100755 (executable)
@@ -23,7 +23,13 @@ test_expect_success setup '
                test_tick &&
                git commit -a -m $i || return 1
        done &&
+       git branch changes &&
        git format-patch --no-numbered initial &&
+       git checkout -b conflicting initial &&
+       echo different >>file-1 &&
+       echo whatever >new-file &&
+       git add file-1 new-file &&
+       git commit -m different &&
        git checkout -b side initial &&
        echo local change >file-2-expect
 '
@@ -191,4 +197,37 @@ test_expect_success 'am --abort leaves index stat info alone' '
        git diff-files --exit-code --quiet
 '
 
+test_expect_success 'git am --abort return failed exit status when it fails' '
+       test_when_finished "rm -rf file-2/ && git reset --hard && git am --abort" &&
+       git checkout changes &&
+       git format-patch -1 --stdout conflicting >changes.mbox &&
+       test_must_fail git am --3way changes.mbox &&
+
+       git rm file-2 &&
+       mkdir file-2 &&
+       echo precious >file-2/somefile &&
+       test_must_fail git am --abort &&
+       test_path_is_dir file-2/
+'
+
+test_expect_success 'git am --abort cleans relevant files' '
+       git checkout changes &&
+       git format-patch -1 --stdout conflicting >changes.mbox &&
+       test_must_fail git am --3way changes.mbox &&
+
+       test_path_is_file new-file &&
+       echo further changes >>file-1 &&
+       echo change other file >>file-2 &&
+
+       # Abort, and expect the files touched by am to be reverted
+       git am --abort &&
+
+       test_path_is_missing new-file &&
+
+       # Files not involved in am operation are left modified
+       git diff --name-only changes >actual &&
+       test_write_lines file-2 >expect &&
+       test_cmp expect actual
+'
+
 test_done
index d2dfcf164e25b8771dd852d2aefc4aee4bd3f5ea..0141f36e338188c87ca6765871e67f091b7240f8 100755 (executable)
@@ -131,4 +131,11 @@ do
        fi
 done
 
+test_expect_success 'log shows warning when conversion fails' '
+       enc=this-encoding-does-not-exist &&
+       git log -1 --encoding=$enc 2>err &&
+       echo "warning: unable to reencode commit to ${SQ}${enc}${SQ}" >expect &&
+       test_cmp expect err
+'
+
 test_done
index 5c5e53f0be9e2073a1e81ffe4c3323c1c784e4da..e13a884207589d3c804745acb9c1f91e731a6f1d 100755 (executable)
@@ -34,6 +34,110 @@ test_expect_success 'setup' '
        } >expect
 '
 
+test_expect_success 'setup pack-object <stdin' '
+       git init pack-object-stdin &&
+       test_commit -C pack-object-stdin one &&
+       test_commit -C pack-object-stdin two
+
+'
+
+test_expect_success 'pack-object <stdin parsing: basic [|--revs]' '
+       cat >in <<-EOF &&
+       $(git -C pack-object-stdin rev-parse one)
+       EOF
+
+       git -C pack-object-stdin pack-objects basic-stdin <in &&
+       idx=$(echo pack-object-stdin/basic-stdin-*.idx) &&
+       git show-index <"$idx" >actual &&
+       test_line_count = 1 actual &&
+
+       git -C pack-object-stdin pack-objects --revs basic-stdin-revs <in &&
+       idx=$(echo pack-object-stdin/basic-stdin-revs-*.idx) &&
+       git show-index <"$idx" >actual &&
+       test_line_count = 3 actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] bad line' '
+       cat >in <<-EOF &&
+       $(git -C pack-object-stdin rev-parse one)
+       garbage
+       $(git -C pack-object-stdin rev-parse two)
+       EOF
+
+       sed "s/^> //g" >err.expect <<-EOF &&
+       fatal: expected object ID, got garbage:
+       >  garbage
+
+       EOF
+       test_must_fail git -C pack-object-stdin pack-objects bad-line-stdin <in 2>err.actual &&
+       test_cmp err.expect err.actual &&
+
+       cat >err.expect <<-EOF &&
+       fatal: bad revision '"'"'garbage'"'"'
+       EOF
+       test_must_fail git -C pack-object-stdin pack-objects --revs bad-line-stdin-revs <in 2>err.actual &&
+       test_cmp err.expect err.actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] empty line' '
+       cat >in <<-EOF &&
+       $(git -C pack-object-stdin rev-parse one)
+
+       $(git -C pack-object-stdin rev-parse two)
+       EOF
+
+       sed -e "s/^> //g" -e "s/Z$//g" >err.expect <<-EOF &&
+       fatal: expected object ID, got garbage:
+       >  Z
+
+       EOF
+       test_must_fail git -C pack-object-stdin pack-objects empty-line-stdin <in 2>err.actual &&
+       test_cmp err.expect err.actual &&
+
+       git -C pack-object-stdin pack-objects --revs empty-line-stdin-revs <in &&
+       idx=$(echo pack-object-stdin/empty-line-stdin-revs-*.idx) &&
+       git show-index <"$idx" >actual &&
+       test_line_count = 3 actual
+'
+
+test_expect_success 'pack-object <stdin parsing: [|--revs] with --stdin' '
+       cat >in <<-EOF &&
+       $(git -C pack-object-stdin rev-parse one)
+       $(git -C pack-object-stdin rev-parse two)
+       EOF
+
+       # There is the "--stdin-packs is incompatible with --revs"
+       # test below, but we should make sure that the revision.c
+       # --stdin is not picked up
+       cat >err.expect <<-EOF &&
+       fatal: disallowed abbreviated or ambiguous option '"'"'stdin'"'"'
+       EOF
+       test_must_fail git -C pack-object-stdin pack-objects stdin-with-stdin-option --stdin <in 2>err.actual &&
+       test_cmp err.expect err.actual &&
+
+       test_must_fail git -C pack-object-stdin pack-objects --stdin --revs stdin-with-stdin-option-revs 2>err.actual <in &&
+       test_cmp err.expect err.actual
+'
+
+test_expect_success 'pack-object <stdin parsing: --stdin-packs handles garbage' '
+       cat >in <<-EOF &&
+       $(git -C pack-object-stdin rev-parse one)
+       $(git -C pack-object-stdin rev-parse two)
+       EOF
+
+       # That we get "two" and not "one" has to do with OID
+       # ordering. It happens to be the same here under SHA-1 and
+       # SHA-256. See commentary in pack-objects.c
+       cat >err.expect <<-EOF &&
+       fatal: could not find pack '"'"'$(git -C pack-object-stdin rev-parse two)'"'"'
+       EOF
+       test_must_fail git \
+               -C pack-object-stdin \
+               pack-objects stdin-with-stdin-option --stdin-packs \
+               <in 2>err.actual &&
+       test_cmp err.expect err.actual
+'
+
 # usage: check_deltas <stderr_from_pack_objects> <cmp_op> <nr_deltas>
 # e.g.: check_deltas stderr -gt 0
 check_deltas() {
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..bd17f308b3832897747bc7ee9f2cf47943cb2e88 100755 (executable)
@@ -174,12 +174,12 @@ test_expect_success 'write progress off for redirected stderr' '
 '
 
 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 +201,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 +305,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 +474,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 +532,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 +583,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 +672,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 +680,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 +896,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
index 8b01793845ff65b7e3244259842af3d3de63e0a1..8dbbcc5e51c06d7c5f56fcb3107860fcb66a5106 100755 (executable)
@@ -114,9 +114,9 @@ test_expect_success 'setup main repo' '
        create_commits_in "$main_repo" A B C D E F G H I J K L M N O P Q R
 '
 
-test_expect_success 'master: pack-redundant works with no packfile' '
+test_expect_success 'main: pack-redundant works with no packfile' '
        (
-               cd "$master_repo" &&
+               cd "$main_repo" &&
                cat >expect <<-EOF &&
                        fatal: Zero packs found!
                        EOF
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
new file mode 100755 (executable)
index 0000000..ec4aa89
--- /dev/null
@@ -0,0 +1,316 @@
+#!/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 '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 e83b2a65069f6a1ddf78da5a38efd3f5f85c65e8..a0faf0dd94909392d7fa3b263b30b2bb41d7ca35 100755 (executable)
@@ -1214,6 +1214,19 @@ test_expect_success '--negotiation-tip understands abbreviated SHA-1' '
        check_negotiation_tip
 '
 
+test_expect_success '--negotiation-tip rejects missing OIDs' '
+       setup_negotiation_tip server server 0 &&
+       test_must_fail git -C client fetch \
+               --negotiation-tip=alpha_1 \
+               --negotiation-tip=$(test_oid zero) \
+               origin alpha_s beta_s 2>err &&
+       cat >fatal-expect <<-EOF &&
+       fatal: the object $(test_oid zero) does not exist
+EOF
+       grep fatal: err >fatal-actual &&
+       test_cmp fatal-expect fatal-actual
+'
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
index 0916f7630207be03ce2af25368146c1302375e27..8212ca56dc5b132edb552fba714657eed3579681 100755 (executable)
@@ -201,6 +201,7 @@ test_expect_success 'push with negotiation' '
        # Without negotiation
        mk_empty testrepo &&
        git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+       test_commit -C testrepo unrelated_commit &&
        git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
        echo now pushing without negotiation &&
        GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 push testrepo refs/heads/main:refs/remotes/origin/main &&
@@ -210,6 +211,7 @@ test_expect_success 'push with negotiation' '
        rm event &&
        mk_empty testrepo &&
        git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+       test_commit -C testrepo unrelated_commit &&
        git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
        GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main &&
        grep_wrote 2 event # 1 commit, 1 tree
@@ -219,6 +221,7 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f
        rm event &&
        mk_empty testrepo &&
        git push testrepo $the_first_commit:refs/remotes/origin/first_commit &&
+       test_commit -C testrepo unrelated_commit &&
        git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit &&
        GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" \
                git -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err &&
@@ -659,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)
 
 '
 
@@ -703,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 &&
@@ -1767,5 +1771,4 @@ test_expect_success 'denyCurrentBranch and worktrees' '
        git -C cloned push origin HEAD:new-wt &&
        test_must_fail git -C cloned push --delete origin new-wt
 '
-
 test_done
index a2924036ad498d06be77311fdb4f8548df9437ff..93ecfcdd245b4ed86dd71fd6d3bc69fbf2505934 100755 (executable)
@@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '
        git reset --hard HEAD^ &&
        echo file >expect &&
        test_cmp expect file &&
-       git pull . second &&
+       git pull --no-rebase . second &&
        echo modified >expect &&
        test_cmp expect file &&
        git reflog -1 >reflog.actual &&
        sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
-       echo "OBJID HEAD@{0}: pull . second: Fast-forward" >reflog.expected &&
+       echo "OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward" >reflog.expected &&
        test_cmp reflog.expected reflog.fuzzy
 '
 
@@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '
        test_commit modified2 file &&
        git ls-files -u >unmerged &&
        test_must_be_empty unmerged &&
-       test_must_fail git pull . second &&
+       test_must_fail git pull --no-rebase . second &&
        git ls-files -u >unmerged &&
        test_file_not_empty unmerged &&
        cp file expected &&
@@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '
 
 test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
        test_config merge.autostash true &&
-       test_pull_autostash 2
+       test_pull_autostash 2 --no-rebase
 '
 
 test_expect_success 'pull --autostash & merge.autostash=true' '
        test_config merge.autostash true &&
-       test_pull_autostash 2 --autostash
+       test_pull_autostash 2 --autostash --no-rebase
 '
 
 test_expect_success 'pull --autostash & merge.autostash=false' '
        test_config merge.autostash false &&
-       test_pull_autostash 2 --autostash
+       test_pull_autostash 2 --autostash --no-rebase
 '
 
 test_expect_success 'pull --autostash & merge.autostash unset' '
        test_unconfig merge.autostash &&
-       test_pull_autostash 2 --autostash
+       test_pull_autostash 2 --autostash --no-rebase
 '
 
 test_expect_success 'pull --no-autostash & merge.autostash=true' '
        test_config merge.autostash true &&
-       test_pull_autostash_fail --no-autostash
+       test_pull_autostash_fail --no-autostash --no-rebase
 '
 
 test_expect_success 'pull --no-autostash & merge.autostash=false' '
        test_config merge.autostash false &&
-       test_pull_autostash_fail --no-autostash
+       test_pull_autostash_fail --no-autostash --no-rebase
 '
 
 test_expect_success 'pull --no-autostash & merge.autostash unset' '
        test_unconfig merge.autostash &&
-       test_pull_autostash_fail --no-autostash
+       test_pull_autostash_fail --no-autostash --no-rebase
 '
 
 test_expect_success 'pull.rebase' '
index 63a688bdbf51e6096ab387293d13c81eaeb72f74..7601c919fdc0c1fd991632adb61604b8fd16df24 100755 (executable)
@@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '
        git pull two &&
        test_commit A &&
        git branch -f origin &&
-       git pull --all --force
+       git pull --no-rebase --all --force
        )
 '
 
@@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '
        (
                cd dst &&
                test_must_fail git pull ../src side &&
-               git pull --allow-unrelated-histories ../src side
+               git pull --no-rebase --allow-unrelated-histories ../src side
        )
 '
 
index c278adaa5a2556327a820cdeb98943b58d1e429f..b2be3605f5a3f0649879a31803b5fc7cfe40d8a1 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success setup '
 test_expect_success pull '
 (
        cd cloned &&
-       git pull --log &&
+       git pull --no-rebase --log &&
        git log -2 &&
        git cat-file commit HEAD >result &&
        grep Dollar result
@@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '
        git reset --hard HEAD^ &&
        test "$(cat afile)" = original &&
        test "$(cat bfile)" = added &&
-       git pull --log=1 &&
+       git pull --no-rebase --log=1 &&
        git log -3 &&
        git cat-file commit HEAD >result &&
        grep Dollar result &&
diff --git a/t/t5549-fetch-push-http.sh b/t/t5549-fetch-push-http.sh
new file mode 100755 (executable)
index 0000000..2cdebcb
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+test_description='fetch/push functionality using the HTTP protocol'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server"
+URI="$HTTPD_URL/smart/server"
+
+grep_wrote () {
+       object_count=$1
+       file_name=$2
+       grep 'write_pack_file/wrote.*"value":"'$1'"' $2
+}
+
+setup_client_and_server () {
+       git init client &&
+       test_when_finished 'rm -rf client' &&
+       test_commit -C client first_commit &&
+       test_commit -C client second_commit &&
+
+       git init "$SERVER" &&
+       test_when_finished 'rm -rf "$SERVER"' &&
+       test_config -C "$SERVER" http.receivepack true &&
+       test_commit -C "$SERVER" unrelated_commit &&
+       git -C client push "$URI" first_commit:refs/remotes/origin/first_commit &&
+       git -C "$SERVER" config receive.hideRefs refs/remotes/origin/first_commit
+}
+
+test_expect_success 'push without negotiation (for comparing object counts with the next test)' '
+       setup_client_and_server &&
+
+       GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c protocol.version=2 \
+               push "$URI" refs/heads/main:refs/remotes/origin/main &&
+       test_when_finished "rm -f event" &&
+       grep_wrote 6 event # 2 commits, 2 trees, 2 blobs
+'
+
+test_expect_success 'push with negotiation' '
+       setup_client_and_server &&
+
+       GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c protocol.version=2 -c push.negotiate=1 \
+               push "$URI" refs/heads/main:refs/remotes/origin/main &&
+       test_when_finished "rm -f event" &&
+       grep_wrote 3 event # 1 commit, 1 tree, 1 blob
+'
+
+test_expect_success 'push with negotiation proceeds anyway even if negotiation fails' '
+       setup_client_and_server &&
+
+       # Use protocol v0 to make negotiation fail (because protocol v0 does
+       # not support the "wait-for-done" capability, which is required for
+       # push negotiation)
+       GIT_TEST_PROTOCOL_VERSION=0 GIT_TRACE2_EVENT="$(pwd)/event" git -C client -c push.negotiate=1 \
+               push "$URI" refs/heads/main:refs/remotes/origin/main 2>err &&
+       test_when_finished "rm -f event" &&
+       grep_wrote 6 event && # 2 commits, 2 trees, 2 blobs
+
+       cat >warning-expect <<-EOF &&
+       warning: --negotiate-only requires protocol v2
+       warning: push negotiation failed; proceeding anyway with push
+EOF
+       grep warning: err >warning-actual &&
+       test_cmp warning-expect warning-actual
+'
+
+test_done
index 4f87d90c5bd9e0c796cb236c78fbf9b74ca6f3b7..f92c79c13266526b52160fb5c1e8eeebed5d8dd6 100755 (executable)
@@ -196,8 +196,8 @@ test_expect_success 'GIT_TRACE_CURL redacts auth details' '
 
        # Ensure that there is no "Basic" followed by a base64 string, but that
        # the auth details are redacted
-       ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
-       grep "Authorization: Basic <redacted>" trace
+       ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+       grep -i "Authorization: Basic <redacted>" trace
 '
 
 test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
@@ -208,8 +208,8 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
 
        # Ensure that there is no "Basic" followed by a base64 string, but that
        # the auth details are redacted
-       ! grep "Authorization: Basic [0-9a-zA-Z+/]" trace &&
-       grep "Authorization: Basic <redacted>" trace
+       ! grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace &&
+       grep -i "Authorization: Basic <redacted>" trace
 '
 
 test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' '
@@ -219,7 +219,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE
                git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
        expect_askpass both user@host &&
 
-       grep "Authorization: Basic [0-9a-zA-Z+/]" trace
+       grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
 '
 
 test_expect_success 'disable dumb http on server' '
@@ -474,10 +474,10 @@ test_expect_success 'cookies are redacted by default' '
        GIT_TRACE_CURL=true \
                git -c "http.cookieFile=$(pwd)/cookies" clone \
                $HTTPD_URL/smart/repo.git clone 2>err &&
-       grep "Cookie:.*Foo=<redacted>" err &&
-       grep "Cookie:.*Bar=<redacted>" err &&
-       ! grep "Cookie:.*Foo=1" err &&
-       ! grep "Cookie:.*Bar=2" err
+       grep -i "Cookie:.*Foo=<redacted>" err &&
+       grep -i "Cookie:.*Bar=<redacted>" err &&
+       ! grep -i "Cookie:.*Foo=1" err &&
+       ! grep -i "Cookie:.*Bar=2" err
 '
 
 test_expect_success 'empty values of cookies are also redacted' '
@@ -486,7 +486,7 @@ test_expect_success 'empty values of cookies are also redacted' '
        GIT_TRACE_CURL=true \
                git -c "http.cookieFile=$(pwd)/cookies" clone \
                $HTTPD_URL/smart/repo.git clone 2>err &&
-       grep "Cookie:.*Foo=<redacted>" err
+       grep -i "Cookie:.*Foo=<redacted>" err
 '
 
 test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
@@ -496,8 +496,8 @@ test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' '
        GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \
                git -c "http.cookieFile=$(pwd)/cookies" clone \
                $HTTPD_URL/smart/repo.git clone 2>err &&
-       grep "Cookie:.*Foo=1" err &&
-       grep "Cookie:.*Bar=2" err
+       grep -i "Cookie:.*Foo=1" err &&
+       grep -i "Cookie:.*Bar=2" err
 '
 
 test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' '
@@ -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
index b1d614ce18c2869467fe1812fed5ed62597328d4..9c12c0f8c321def9e39d0887d463b5d227d3157d 100755 (executable)
@@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '
 
 test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '
        clear_config main other &&
-       git pull --set-upstream upstream main &&
+       git pull --no-rebase --set-upstream upstream main &&
        check_config main upstream refs/heads/main &&
        check_config_missing other
 '
 
 test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '
        clear_config other2 &&
-       git pull --set-upstream upstream main:other2 &&
+       git pull --no-rebase --set-upstream upstream main:other2 &&
        check_config_missing other2
 '
 
 test_expect_success 'pull --set-upstream upstream other sets branch main' '
        clear_config main other &&
-       git pull --set-upstream upstream other &&
+       git pull --no-rebase --set-upstream upstream other &&
        check_config main upstream refs/heads/other &&
        check_config_missing other
 '
 
 test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
        clear_config three &&
-       git pull --tags --set-upstream upstream three &&
+       git pull --no-rebase --tags --set-upstream upstream three &&
        check_config_missing three
 '
 
@@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w
 
 test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
        clear_config main other &&
-       git pull --set-upstream upstream HEAD &&
+       git pull --no-rebase --set-upstream upstream HEAD &&
        check_config main upstream HEAD &&
        git checkout other &&
-       git pull --set-upstream upstream HEAD &&
+       git pull --no-rebase --set-upstream upstream HEAD &&
        check_config other upstream HEAD
 '
 
 test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
        clear_config main three &&
-       git pull --set-upstream upstream main three &&
+       git pull --no-rebase --set-upstream upstream main three &&
        check_config_missing main &&
        check_config_missing three
 '
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 0943474af20d345e58205bace2b0c7d8984e740c..718dd9b49d493ec55e2503dcb14aa5ddcc26cc6f 100644 (file)
@@ -13,11 +13,6 @@ my $body_data;
 defined read($body_fh, $body_data, $body_size) or die "Cannot read $body_filename: $!";
 close($body_fh);
 
-my $exited = 0;
-$SIG{"CHLD"} = sub {
-        $exited = 1;
-};
-
 # write data
 my $pid = open(my $out, "|-", @command);
 {
@@ -29,8 +24,13 @@ my $pid = open(my $out, "|-", @command);
 }
 print $out $body_data or die "Cannot write data: $!";
 
-sleep 60; # is interrupted by SIGCHLD
-if (!$exited) {
-        close($out);
+$SIG{ALRM} = sub {
+        kill 'KILL', $pid;
         die "Command did not exit after reading whole body";
+};
+alarm 60;
+
+my $ret = waitpid($pid, 0);
+if ($ret != $pid) {
+        die "confusing return from waitpid: $ret";
 }
index e5d2e79ad382a0caeec098e773cc922af21a824b..7a80e47c2b70a8cf64c71fa00289b5abfc158151 100755 (executable)
@@ -105,7 +105,6 @@ test_expect_success "fetch with negative pattern refspec does not expand prefix"
 '
 
 test_expect_success "fetch with negative refspec avoids duplicate conflict" '
-       cd "$D" &&
        (
                cd one &&
                git branch dups/a &&
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 e845d621f618ba83962842b766c076a788877936..24340e6d56e18d6a2a9082654c95ef58ac7ed196 100755 (executable)
@@ -87,7 +87,7 @@ test_expect_success 'updating origin' '
 '
 
 test_expect_success 'pulling changes from origin' '
-       git -C C pull origin
+       git -C C pull --no-rebase origin
 '
 
 # the 2 local objects are commit and tree from the merge
@@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '
 '
 
 test_expect_success 'pulling changes from origin' '
-       git -C D pull origin
+       git -C D pull --no-rebase origin
 '
 
 # the 5 local objects are expected; file3 blob, commit in A to add it
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 78de1ff2ad5900ddc81c4096b10bce0ad3e48e13..d527cf6c49f9d864ca7c296515e7509caf5a0c34 100755 (executable)
@@ -27,9 +27,9 @@ test_expect_success 'list refs with git:// using protocol v2' '
                ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
 
        # Client requested to use protocol v2
-       grep "git> .*\\\0\\\0version=2\\\0$" log &&
+       grep "ls-remote> .*\\\0\\\0version=2\\\0$" log &&
        # Server responded using protocol v2
-       grep "git< version 2" log &&
+       grep "ls-remote< version 2" log &&
 
        git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
        test_cmp expect actual
@@ -151,7 +151,7 @@ test_expect_success 'list refs with file:// using protocol v2' '
                ls-remote --symref "file://$(pwd)/file_parent" >actual &&
 
        # Server responded using protocol v2
-       grep "git< version 2" log &&
+       grep "ls-remote< version 2" log &&
 
        git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
        test_cmp expect actual
@@ -237,6 +237,19 @@ test_expect_success '...but not if explicitly forbidden by config' '
        ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD
 '
 
+test_expect_success 'bare clone propagates empty default branch' '
+       test_when_finished "rm -rf file_empty_parent file_empty_child.git" &&
+
+       GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+       git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&
+
+       GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \
+       git -c init.defaultBranch=main -c protocol.version=2 \
+               clone --bare \
+               "file://$(pwd)/file_empty_parent" file_empty_child.git &&
+       grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD
+'
+
 test_expect_success 'fetch with file:// using protocol v2' '
        test_when_finished "rm -f log" &&
 
index e9e471621d59a6a321699c1c59138b95c05b2525..220098523a699451cbb3abaaa2850416561b981b 100755 (executable)
@@ -40,6 +40,30 @@ write_command () {
        fi
 }
 
+# Write a complete fetch command to stdout, suitable for use with `test-tool
+# pkt-line`. "want-ref", "want", and "have" lines are read from stdin.
+#
+# Examples:
+#
+# write_fetch_command <<-EOF
+# want-ref refs/heads/main
+# have $(git rev-parse a)
+# EOF
+#
+# write_fetch_command <<-EOF
+# want $(git rev-parse b)
+# have $(git rev-parse a)
+# EOF
+#
+write_fetch_command () {
+       write_command fetch &&
+       echo "0001" &&
+       echo "no-progress" &&
+       cat &&
+       echo "done" &&
+       echo "0000"
+}
+
 # c(o/foo) d(o/bar)
 #        \ /
 #         b   e(baz)  f(main)
@@ -77,15 +101,11 @@ test_expect_success 'config controls ref-in-want advertisement' '
 '
 
 test_expect_success 'invalid want-ref line' '
-       test-tool pkt-line pack >in <<-EOF &&
-       $(write_command fetch)
-       0001
-       no-progress
+       write_fetch_command >pkt <<-EOF &&
        want-ref refs/heads/non-existent
-       done
-       0000
        EOF
 
+       test-tool pkt-line pack <pkt >in &&
        test_must_fail test-tool serve-v2 --stateless-rpc 2>out <in &&
        grep "unknown ref" out
 '
@@ -97,16 +117,11 @@ test_expect_success 'basic want-ref' '
        EOF
        git rev-parse f >expected_commits &&
 
-       oid=$(git rev-parse a) &&
-       test-tool pkt-line pack >in <<-EOF &&
-       $(write_command fetch)
-       0001
-       no-progress
+       write_fetch_command >pkt <<-EOF &&
        want-ref refs/heads/main
-       have $oid
-       done
-       0000
+       have $(git rev-parse a)
        EOF
+       test-tool pkt-line pack <pkt >in &&
 
        test-tool serve-v2 --stateless-rpc >out <in &&
        check_output
@@ -121,17 +136,12 @@ test_expect_success 'multiple want-ref lines' '
        EOF
        git rev-parse c d >expected_commits &&
 
-       oid=$(git rev-parse b) &&
-       test-tool pkt-line pack >in <<-EOF &&
-       $(write_command fetch)
-       0001
-       no-progress
+       write_fetch_command >pkt <<-EOF &&
        want-ref refs/heads/o/foo
        want-ref refs/heads/o/bar
-       have $oid
-       done
-       0000
+       have $(git rev-parse b)
        EOF
+       test-tool pkt-line pack <pkt >in &&
 
        test-tool serve-v2 --stateless-rpc >out <in &&
        check_output
@@ -144,16 +154,12 @@ test_expect_success 'mix want and want-ref' '
        EOF
        git rev-parse e f >expected_commits &&
 
-       test-tool pkt-line pack >in <<-EOF &&
-       $(write_command fetch)
-       0001
-       no-progress
+       write_fetch_command >pkt <<-EOF &&
        want-ref refs/heads/main
        want $(git rev-parse e)
        have $(git rev-parse a)
-       done
-       0000
        EOF
+       test-tool pkt-line pack <pkt >in &&
 
        test-tool serve-v2 --stateless-rpc >out <in &&
        check_output
@@ -166,16 +172,11 @@ test_expect_success 'want-ref with ref we already have commit for' '
        EOF
        >expected_commits &&
 
-       oid=$(git rev-parse c) &&
-       test-tool pkt-line pack >in <<-EOF &&
-       $(write_command fetch)
-       0001
-       no-progress
+       write_fetch_command >pkt <<-EOF &&
        want-ref refs/heads/o/foo
-       have $oid
-       done
-       0000
+       have $(git rev-parse c)
        EOF
+       test-tool pkt-line pack <pkt >in &&
 
        test-tool serve-v2 --stateless-rpc >out <in &&
        check_output
@@ -298,6 +299,141 @@ test_expect_success 'fetching with wildcard that matches multiple refs' '
        grep "want-ref refs/heads/o/bar" log
 '
 
+REPO="$(pwd)/repo-ns"
+
+test_expect_success 'setup namespaced repo' '
+       (
+               git init -b main "$REPO" &&
+               cd "$REPO" &&
+               test_commit a &&
+               test_commit b &&
+               git checkout a &&
+               test_commit c &&
+               git checkout a &&
+               test_commit d &&
+               git update-ref refs/heads/ns-no b &&
+               git update-ref refs/namespaces/ns/refs/heads/ns-yes c &&
+               git update-ref refs/namespaces/ns/refs/heads/hidden d
+       ) &&
+       git -C "$REPO" config uploadpack.allowRefInWant true
+'
+
+test_expect_success 'with namespace: want-ref is considered relative to namespace' '
+       wanted_ref=refs/heads/ns-yes &&
+
+       oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+       cat >expected_refs <<-EOF &&
+       $oid $wanted_ref
+       EOF
+       cat >expected_commits <<-EOF &&
+       $oid
+       $(git -C "$REPO" rev-parse a)
+       EOF
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       check_output
+'
+
+test_expect_success 'with namespace: want-ref outside namespace is unknown' '
+       wanted_ref=refs/heads/ns-no &&
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       test_must_fail env GIT_NAMESPACE=ns \
+               test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       grep "unknown ref" out
+'
+
+# Cross-check refs/heads/ns-no indeed exists
+test_expect_success 'without namespace: want-ref outside namespace succeeds' '
+       wanted_ref=refs/heads/ns-no &&
+
+       oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+       cat >expected_refs <<-EOF &&
+       $oid $wanted_ref
+       EOF
+       cat >expected_commits <<-EOF &&
+       $oid
+       $(git -C "$REPO" rev-parse a)
+       EOF
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       check_output
+'
+
+test_expect_success 'with namespace: hideRefs is matched, relative to namespace' '
+       wanted_ref=refs/heads/hidden &&
+       git -C "$REPO" config transfer.hideRefs $wanted_ref &&
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       test_must_fail env GIT_NAMESPACE=ns \
+               test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       grep "unknown ref" out
+'
+
+# Cross-check refs/heads/hidden indeed exists
+test_expect_success 'with namespace: want-ref succeeds if hideRefs is removed' '
+       wanted_ref=refs/heads/hidden &&
+       git -C "$REPO" config --unset transfer.hideRefs $wanted_ref &&
+
+       oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+       cat >expected_refs <<-EOF &&
+       $oid $wanted_ref
+       EOF
+       cat >expected_commits <<-EOF &&
+       $oid
+       $(git -C "$REPO" rev-parse a)
+       EOF
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       check_output
+'
+
+test_expect_success 'without namespace: relative hideRefs does not match' '
+       wanted_ref=refs/namespaces/ns/refs/heads/hidden &&
+       git -C "$REPO" config transfer.hideRefs refs/heads/hidden &&
+
+       oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+       cat >expected_refs <<-EOF &&
+       $oid $wanted_ref
+       EOF
+       cat >expected_commits <<-EOF &&
+       $oid
+       $(git -C "$REPO" rev-parse a)
+       EOF
+
+       write_fetch_command >pkt <<-EOF &&
+       want-ref $wanted_ref
+       EOF
+       test-tool pkt-line pack <pkt >in &&
+
+       test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+       check_output
+'
+
+
 . "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
index 5c941949b980b49607fb9b3876487110cf0acabc..bc393d7c31939f6e592815614521741c3e8dc087 100755 (executable)
@@ -32,4 +32,19 @@ test_expect_success 'extra delim packet in v2 fetch args' '
        test_i18ngrep "expected flush after fetch arguments" err
 '
 
+test_expect_success 'bogus symref in v0 capabilities' '
+       test_commit foo &&
+       oid=$(git rev-parse HEAD) &&
+       dst=refs/heads/foo &&
+       {
+               printf "%s HEAD\0symref object-format=%s symref=HEAD:%s\n" \
+                       "$oid" "$GIT_DEFAULT_HASH" "$dst" |
+                       test-tool pkt-line pack-raw-stdin &&
+               printf "0000"
+       } >input &&
+       git ls-remote --symref --upload-pack="cat input; read junk;:" . >actual &&
+       printf "ref: %s\tHEAD\n%s\tHEAD\n" "$dst" "$oid" >expect &&
+       test_cmp expect actual
+'
+
 test_done
index f1d189d5bcc443c3abf8a62939b04ff3006ccf1a..eb8c79aafdd6aa4d203ff6d8ab85a191c37a5d9b 100755 (executable)
@@ -73,6 +73,17 @@ do
                grep \"key\":\"server-sid\" tr2-client-events &&
                grep \"key\":\"client-sid\" tr2-server-events
        '
+
+       test_expect_success "client & server log negotiated version (v${PROTO})" '
+               test_when_finished "rm -rf local tr2-client-events tr2-server-events" &&
+               cp -r "$LOCAL_PRISTINE" local &&
+               GIT_TRACE2_EVENT="$(pwd)/tr2-client-events" \
+               git -c protocol.version=$PROTO -C local fetch \
+                       --upload-pack "GIT_TRACE2_EVENT=\"$(pwd)/tr2-server-events\" git-upload-pack" \
+                       origin &&
+               grep \"key\":\"negotiated-version\",\"value\":\"$PROTO\" tr2-client-events &&
+               grep \"key\":\"negotiated-version\",\"value\":\"$PROTO\" tr2-server-events
+       '
 done
 
 test_done
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 9e0214076b4d7efd3522a8b7d68f6fe297666cd1..80679d5e12d49328fea6bd4d8e0b7929b68d9bea 100755 (executable)
@@ -59,18 +59,25 @@ test_atom() {
        # Automatically test "contents:size" atom after testing "contents"
        if test "$2" = "contents"
        then
-               case $(git cat-file -t "$ref") in
-               tag)
-                       # We cannot use $3 as it expects sanitize_pgp to run
-                       expect=$(git cat-file tag $ref | tail -n +6 | wc -c) ;;
-               tree | blob)
-                       expect='' ;;
-               commit)
-                       expect=$(printf '%s' "$3" | wc -c) ;;
-               esac
-               # Leave $expect unquoted to lose possible leading whitespaces
-               echo $expect >expected
+               # for commit leg, $3 is changed there
+               expect=$(printf '%s' "$3" | wc -c)
                test_expect_${4:-success} $PREREQ "basic atom: $1 contents:size" '
+                       type=$(git cat-file -t "$ref") &&
+                       case $type in
+                       tag)
+                               # We cannot use $3 as it expects sanitize_pgp to run
+                               git cat-file tag $ref >out &&
+                               expect=$(tail -n +6 out | wc -c) &&
+                               rm -f out ;;
+                       tree | blob)
+                               expect="" ;;
+                       commit)
+                               : "use the calculated expect" ;;
+                       *)
+                               BUG "unknown object type" ;;
+                       esac &&
+                       # Leave $expect unquoted to lose possible leading whitespaces
+                       echo $expect >expected &&
                        git for-each-ref --format="%(contents:size)" "$ref" >actual &&
                        test_cmp expected actual
                '
@@ -130,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'
@@ -221,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
 '
@@ -686,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'
@@ -695,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'
@@ -708,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
@@ -720,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 ""
@@ -727,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
@@ -980,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 3da2896e3bc8dc3a6fe63dd48edce292118ee7e4..3a32b1a45cf8e4bf2a7b42eeec98923262b3e470 100755 (executable)
@@ -103,7 +103,7 @@ test_expect_success 'setup' '
 test_expect_success 'pull renaming branch into unrenaming one' \
 '
        git show-branch &&
-       test_expect_code 1 git pull . white &&
+       test_expect_code 1 git pull --no-rebase . white &&
        git ls-files -s &&
        test_stdout_line_count = 3 git ls-files -u B &&
        test_stdout_line_count = 1 git ls-files -s N &&
@@ -119,7 +119,7 @@ test_expect_success 'pull renaming branch into another renaming one' \
        rm -f B &&
        git reset --hard &&
        git checkout red &&
-       test_expect_code 1 git pull . white &&
+       test_expect_code 1 git pull --no-rebase . white &&
        test_stdout_line_count = 3 git ls-files -u B &&
        test_stdout_line_count = 1 git ls-files -s N &&
        sed -ne "/^g/{
@@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \
 '
        git reset --hard &&
        git show-branch &&
-       test_expect_code 1 git pull . main &&
+       test_expect_code 1 git pull --no-rebase . main &&
        test_stdout_line_count = 3 git ls-files -u B &&
        test_stdout_line_count = 1 git ls-files -s N &&
        sed -ne "/^g/{
@@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \
 '
        git reset --hard &&
        git show-branch &&
-       test_expect_code 1 git pull . blue &&
+       test_expect_code 1 git pull --no-rebase . blue &&
        test_stdout_line_count = 1 git ls-files -u A &&
        test_stdout_line_count = 1 git ls-files -u B &&
        test_stdout_line_count = 1 git ls-files -u C &&
@@ -163,7 +163,7 @@ test_expect_success 'interference with untracked working tree file' '
        git reset --hard &&
        git show-branch &&
        echo >A this file should not matter &&
-       test_expect_code 1 git pull . white &&
+       test_expect_code 1 git pull --no-rebase . white &&
        test_path_is_file A
 '
 
@@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '
        git show-branch &&
        rm -f A &&
        echo >A this file should not matter &&
-       test_expect_code 1 git pull . red &&
+       test_expect_code 1 git pull --no-rebase . red &&
        test_path_is_file A
 '
 
@@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '
        git checkout -f main &&
        git tag -f anchor &&
        git show-branch &&
-       git pull . yellow &&
+       git pull --no-rebase . yellow &&
        test_path_is_missing M &&
        git reset --hard anchor
 '
@@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
        echo >>M one line addition &&
        cat M >M.saved &&
        git update-index M &&
-       test_expect_code 128 git pull . yellow &&
+       test_expect_code 128 git pull --no-rebase . yellow &&
        test_cmp M M.saved &&
        rm -f M.saved
 '
@@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '
        git tag -f anchor &&
        git show-branch &&
        echo >M this file should not matter &&
-       git pull . main &&
+       git pull --no-rebase . main &&
        test_path_is_file M &&
        ! {
                git ls-files -s |
index d406b2343cb3260a02e9941148dd2a83ec3abeac..ba7890ec521f510cf9d389331bf12d7cf11be4fb 100755 (executable)
@@ -100,7 +100,7 @@ test_expect_success 'merge update' '
        git checkout -b topic_2 &&
        git commit -m "update git-gui" &&
        cd ../git &&
-       git pull -s subtree gui topic_2 &&
+       git pull --no-rebase -s subtree gui topic_2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o3 0      git-gui/git-gui.sh" &&
@@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '
 test_expect_success 'merge using explicit' '
        cd ../git &&
        git reset --hard topic_2 &&
-       git pull -Xsubtree=git-gui gui topic_2 &&
+       git pull --no-rebase -Xsubtree=git-gui gui topic_2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o3 0      git-gui/git-gui.sh" &&
@@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '
 test_expect_success 'merge2 using explicit' '
        cd ../git &&
        git reset --hard topic_2 &&
-       git pull -Xsubtree=git-gui2 gui topic_2 &&
+       git pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&
        git ls-files -s >actual &&
        (
                echo "100644 $o1 0      git-gui/git-gui.sh" &&
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 ac9aee9a6625530f385b038f59776073044e691f..ec065d6a6581dc368fa9f03edfa9cfdcb0483eb2 100755 (executable)
@@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '
 '
 
 test_expect_success 'pull passes -X to underlying merge' '
-       git reset --hard main && git pull -s recursive -Xours . side &&
-       git reset --hard main && git pull -s recursive -X ours . side &&
-       git reset --hard main && git pull -s recursive -Xtheirs . side &&
-       git reset --hard main && git pull -s recursive -X theirs . side &&
-       git reset --hard main && test_must_fail git pull -s recursive -X bork . side
+       git reset --hard main && git pull --no-rebase -s recursive -Xours . side &&
+       git reset --hard main && git pull --no-rebase -s recursive -X ours . side &&
+       git reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&
+       git reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&
+       git reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side
 '
 
 test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '
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 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
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 54c2082acb8d19338aed25277467b6a9760c7bda..8dd0f988129d5f35c3b0aa93ba2f441c20d5afe9 100755 (executable)
@@ -270,7 +270,7 @@ EOF
 
 test_expect_success 'commit --fixup provides correct one-line commit message' '
        commit_for_rebase_autosquash_setup &&
-       git commit --fixup HEAD~1 &&
+       EDITOR="echo ignored >>" git commit --fixup HEAD~1 &&
        commit_msg_is "fixup! target message subject line"
 '
 
@@ -281,6 +281,13 @@ test_expect_success 'commit --fixup -m"something" -m"extra"' '
 
 extra"
 '
+test_expect_success 'commit --fixup --edit' '
+       commit_for_rebase_autosquash_setup &&
+       EDITOR="printf \"something\nextra\" >>" git commit --fixup HEAD~1 --edit &&
+       commit_msg_is "fixup! target message subject linesomething
+extra"
+'
+
 get_commit_msg () {
        rev="$1" &&
        git log -1 --pretty=format:"%B" "$rev"
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
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 52e8ccc933ad1269d62fc91348700558dc9b95ef..1f652f433ee276b1420324f6609ae51c5b02863c 100755 (executable)
@@ -27,120 +27,324 @@ test_expect_success 'setup' '
        git tag c3
 '
 
-test_expect_success 'pull.rebase not set' '
+test_expect_success 'pull.rebase not set, ff possible' '
        git reset --hard c0 &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=true' '
        git reset --hard c0 &&
        test_config pull.ff true &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=false' '
        git reset --hard c0 &&
        test_config pull.ff false &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=only' '
        git reset --hard c0 &&
        test_config pull.ff only &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --rebase given' '
        git reset --hard c0 &&
        git pull --rebase . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --no-rebase given' '
        git reset --hard c0 &&
        git pull --no-rebase . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --ff given' '
        git reset --hard c0 &&
        git pull --ff . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --no-ff given' '
        git reset --hard c0 &&
        git pull --no-ff . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --ff-only given' '
        git reset --hard c0 &&
        git pull --ff-only . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set (not-fast-forward)' '
        git reset --hard c2 &&
-       git -c color.advice=always pull . c1 2>err &&
+       test_must_fail git -c color.advice=always pull . c1 2>err &&
        test_decode_color <err >decoded &&
        test_i18ngrep "<YELLOW>hint: " decoded &&
-       test_i18ngrep "Pulling without specifying how to reconcile" decoded
+       test_i18ngrep "You have divergent branches" decoded
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '
        git reset --hard c2 &&
        test_config pull.ff true &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '
        git reset --hard c2 &&
        test_config pull.ff false &&
        git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '
        git reset --hard c2 &&
        test_config pull.ff only &&
        test_must_fail git pull . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '
        git reset --hard c2 &&
        git pull --rebase . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '
        git reset --hard c2 &&
        git pull --no-rebase . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '
        git reset --hard c2 &&
        git pull --ff . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '
        git reset --hard c2 &&
        git pull --no-ff . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
 '
 
 test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '
        git reset --hard c2 &&
        test_must_fail git pull --ff-only . c1 2>err &&
-       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+       test_i18ngrep ! "You have divergent branches" err
+'
+
+test_does_rebase () {
+       git reset --hard c2 &&
+       git "$@" . c1 &&
+       # Check that we actually did a rebase
+       git rev-list --count HEAD >actual &&
+       git rev-list --merges --count HEAD >>actual &&
+       test_write_lines 3 0 >expect &&
+       test_cmp expect actual &&
+       rm actual expect
+}
+
+# Prefers merge over fast-forward
+test_does_merge_when_ff_possible () {
+       git reset --hard c0 &&
+       git "$@" . c1 &&
+       # Check that we actually did a merge
+       git rev-list --count HEAD >actual &&
+       git rev-list --merges --count HEAD >>actual &&
+       test_write_lines 3 1 >expect &&
+       test_cmp expect actual &&
+       rm actual expect
+}
+
+# Prefers fast-forward over merge or rebase
+test_does_fast_forward () {
+       git reset --hard c0 &&
+       git "$@" . c1 &&
+
+       # Check that we did not get any merges
+       git rev-list --count HEAD >actual &&
+       git rev-list --merges --count HEAD >>actual &&
+       test_write_lines 2 0 >expect &&
+       test_cmp expect actual &&
+
+       # Check that we ended up at c1
+       git rev-parse HEAD >actual &&
+       git rev-parse c1^{commit} >expect &&
+       test_cmp actual expect &&
+
+       # Remove temporary files
+       rm actual expect
+}
+
+# Doesn't fail when fast-forward not possible; does a merge
+test_falls_back_to_full_merge () {
+       git reset --hard c2 &&
+       git "$@" . c1 &&
+       # Check that we actually did a merge
+       git rev-list --count HEAD >actual &&
+       git rev-list --merges --count HEAD >>actual &&
+       test_write_lines 4 1 >expect &&
+       test_cmp expect actual &&
+       rm actual expect
+}
+
+# Attempts fast forward, which is impossible, and bails
+test_attempts_fast_forward () {
+       git reset --hard c2 &&
+       test_must_fail git "$@" . c1 2>err &&
+       test_i18ngrep "Not possible to fast-forward, aborting" err
+}
+
+#
+# Group 1: Interaction of --ff-only with --[no-]rebase
+# (And related interaction of pull.ff=only with pull.rebase)
+#
+test_expect_success '--ff-only overrides --rebase' '
+       test_attempts_fast_forward pull --rebase --ff-only
+'
+
+test_expect_success '--ff-only overrides --rebase even if first' '
+       test_attempts_fast_forward pull --ff-only --rebase
+'
+
+test_expect_success '--ff-only overrides --no-rebase' '
+       test_attempts_fast_forward pull --ff-only --no-rebase
+'
+
+test_expect_success 'pull.ff=only overrides pull.rebase=true' '
+       test_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull
+'
+
+test_expect_success 'pull.ff=only overrides pull.rebase=false' '
+       test_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull
+'
+
+# Group 2: --rebase=[!false] overrides --no-ff and --ff
+# (And related interaction of pull.rebase=!false and pull.ff=!only)
+test_expect_success '--rebase overrides --no-ff' '
+       test_does_rebase pull --rebase --no-ff
+'
+
+test_expect_success '--rebase overrides --ff' '
+       test_does_rebase pull --rebase --ff
+'
+
+test_expect_success '--rebase fast-forwards when possible' '
+       test_does_fast_forward pull --rebase --ff
+'
+
+test_expect_success 'pull.rebase=true overrides pull.ff=false' '
+       test_does_rebase -c pull.rebase=true -c pull.ff=false pull
+'
+
+test_expect_success 'pull.rebase=true overrides pull.ff=true' '
+       test_does_rebase -c pull.rebase=true -c pull.ff=true pull
+'
+
+# Group 3: command line flags take precedence over config
+test_expect_success '--ff-only takes precedence over pull.rebase=true' '
+       test_attempts_fast_forward -c pull.rebase=true pull --ff-only
+'
+
+test_expect_success '--ff-only takes precedence over pull.rebase=false' '
+       test_attempts_fast_forward -c pull.rebase=false pull --ff-only
+'
+
+test_expect_success '--no-rebase takes precedence over pull.ff=only' '
+       test_falls_back_to_full_merge -c pull.ff=only pull --no-rebase
+'
+
+test_expect_success '--rebase takes precedence over pull.ff=only' '
+       test_does_rebase -c pull.ff=only pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff=true' '
+       test_does_rebase -c pull.ff=true pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff=false' '
+       test_does_rebase -c pull.ff=false pull --rebase
+'
+
+test_expect_success '--rebase overrides pull.ff unset' '
+       test_does_rebase pull --rebase
+'
+
+# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff
+
+test_expect_success '--no-rebase works with --no-ff' '
+       test_does_merge_when_ff_possible pull --no-rebase --no-ff
+'
+
+test_expect_success '--no-rebase works with --ff' '
+       test_does_fast_forward pull --no-rebase --ff
+'
+
+test_expect_success '--no-rebase does ff if pull.ff unset' '
+       test_does_fast_forward pull --no-rebase
+'
+
+test_expect_success '--no-rebase heeds pull.ff=true' '
+       test_does_fast_forward -c pull.ff=true pull --no-rebase
+'
+
+test_expect_success '--no-rebase heeds pull.ff=false' '
+       test_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase
+'
+
+# Group 5: pull.rebase=!false in combination with --no-ff or --ff
+test_expect_success 'pull.rebase=true and --no-ff' '
+       test_does_rebase -c pull.rebase=true pull --no-ff
+'
+
+test_expect_success 'pull.rebase=true and --ff' '
+       test_does_rebase -c pull.rebase=true pull --ff
+'
+
+test_expect_success 'pull.rebase=false and --no-ff' '
+       test_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff
+'
+
+test_expect_success 'pull.rebase=false and --ff, ff possible' '
+       test_does_fast_forward -c pull.rebase=false pull --ff
+'
+
+test_expect_success 'pull.rebase=false and --ff, ff not possible' '
+       test_falls_back_to_full_merge -c pull.rebase=false pull --ff
+'
+
+# End of groupings for conflicting merge vs. rebase flags/options
+
+test_expect_success 'Multiple heads warns about inability to fast forward' '
+       git reset --hard c1 &&
+       test_must_fail git pull . c2 c3 2>err &&
+       test_i18ngrep "You have divergent branches" err
+'
+
+test_expect_success 'Multiple can never be fast forwarded' '
+       git reset --hard c0 &&
+       test_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&
+       test_i18ngrep ! "You have divergent branches" err &&
+       # In addition to calling out "cannot fast-forward", we very much
+       # want the "multiple branches" piece to be called out to users.
+       test_i18ngrep "Cannot fast-forward to multiple branches" err
+'
+
+test_expect_success 'Cannot rebase with multiple heads' '
+       git reset --hard c0 &&
+       test_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&
+       test_i18ngrep ! "You have divergent branches" err &&
+       test_i18ngrep "Cannot rebase onto multiple branches." err
 '
 
 test_expect_success 'merge c1 with c2' '
index 98948955ae507ef007dc541f7e981d06bf7b49ab..27cd94ad6f7770f93c84bad8a0a966e6d026ea78 100755 (executable)
@@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
 
 test_expect_success 'pull c2, c3, c4, c5 into c1' '
        git reset --hard c1 &&
-       git pull . c2 c3 c4 c5 &&
+       git pull --no-rebase . c2 c3 c4 c5 &&
        test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
        test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
        test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
index 25b235c06303850e17b90f5fc6c0f84bb962c4c8..98eda3bfeb56c4cd685ed352ee0fe2a885061c31 100755 (executable)
@@ -63,13 +63,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 +190,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 +203,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 +215,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,7 +227,8 @@ 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
index a173f564bcfeb53152c47cf79ce124b903db5f83..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 &&
@@ -674,7 +681,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
        rm c &&
        ln -s d c &&
        cat >expect <<-EOF &&
-               b
                c
 
                c
@@ -710,7 +716,6 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
        # Deleted symlinks
        rm -f c &&
        cat >expect <<-EOF &&
-               b
                c
 
        EOF
@@ -723,6 +728,71 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
        test_cmp expect actual
 '
 
+test_expect_success SYMLINKS 'difftool --dir-diff writes symlinks as raw text' '
+       # Start out on a branch called "branch-init".
+       git init -b branch-init symlink-files &&
+       (
+               cd symlink-files &&
+               # This test ensures that symlinks are written as raw text.
+               # The "cat" tools output link and file contents.
+               git config difftool.cat-left-link.cmd "cat \"\$LOCAL/link\"" &&
+               git config difftool.cat-left-a.cmd "cat \"\$LOCAL/file-a\"" &&
+               git config difftool.cat-right-link.cmd "cat \"\$REMOTE/link\"" &&
+               git config difftool.cat-right-b.cmd "cat \"\$REMOTE/file-b\"" &&
+
+               # Record the empty initial state so that we can come back here
+               # later and not have to consider the any cases where difftool
+               # will create symlinks back into the worktree.
+               test_tick &&
+               git commit --allow-empty -m init &&
+
+               # Create a file called "file-a" with a symlink pointing to it.
+               git switch -c branch-a &&
+               echo a >file-a &&
+               ln -s file-a link &&
+               git add file-a link &&
+               test_tick &&
+               git commit -m link-to-file-a &&
+
+               # Create a file called "file-b" and point the symlink to it.
+               git switch -c branch-b &&
+               echo b >file-b &&
+               rm link &&
+               ln -s file-b link &&
+               git add file-b link &&
+               git rm file-a &&
+               test_tick &&
+               git commit -m link-to-file-b &&
+
+               # Checkout the initial branch so that the --symlinks behavior is
+               # not activated. The two directories should be completely
+               # independent with no symlinks pointing back here.
+               git switch branch-init &&
+
+               # The left link must be "file-a" and "file-a" must contain "a".
+               echo file-a >expect &&
+               git difftool --symlinks --dir-diff --tool cat-left-link \
+                       branch-a branch-b >actual &&
+               test_cmp expect actual &&
+
+               echo a >expect &&
+               git difftool --symlinks --dir-diff --tool cat-left-a \
+                       branch-a branch-b >actual &&
+               test_cmp expect actual &&
+
+               # The right link must be "file-b" and "file-b" must contain "b".
+               echo file-b >expect &&
+               git difftool --symlinks --dir-diff --tool cat-right-link \
+                       branch-a branch-b >actual &&
+               test_cmp expect actual &&
+
+               echo b >expect &&
+               git difftool --symlinks --dir-diff --tool cat-right-b \
+                       branch-a branch-b >actual &&
+               test_cmp expect actual
+       )
+'
+
 test_expect_success 'add -N and difftool -d' '
        test_when_finished git reset --hard &&
 
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 58f46c77e66604020b15806453719374cfddd18e..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)" &&
@@ -582,6 +610,23 @@ test_expect_success 'start and stop macOS maintenance' '
        test_line_count = 0 actual
 '
 
+test_expect_success 'use launchctl list to prevent extra work' '
+       # ensure we are registered
+       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 --scheduler=launchctl &&
+
+       ls "$HOME/Library/LaunchAgents" >actual &&
+       cat >expect <<-\EOF &&
+       list org.git-scm.git.hourly
+       list org.git-scm.git.daily
+       list org.git-scm.git.weekly
+       EOF
+       test_cmp expect args
+'
+
 test_expect_success 'start and stop Windows maintenance' '
        write_script print-args <<-\EOF &&
        echo $* >>args
@@ -596,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)" &&
@@ -619,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 57fc10e7f82186e6a9af93f50c503e4c6bf6448b..aa0c20499ba3e9620739dee08fbdb118b62446e4 100755 (executable)
@@ -1533,6 +1533,21 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding works' '
        test_cmp content-type-decl actual
 '
 
+test_expect_success $PREREQ 'sendemail.8bitEncoding in .git/config overrides --global .gitconfig' '
+       clean_fake_sendmail &&
+       git config sendemail.assume8bitEncoding UTF-8 &&
+       test_when_finished "rm -rf home" &&
+       mkdir home &&
+       git config -f home/.gitconfig sendemail.assume8bitEncoding "bogus too" &&
+       echo bogus |
+       env HOME="$(pwd)/home" DEBUG=1 \
+       git send-email --from=author@example.com --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       email-using-8bit >stdout &&
+       egrep "Content|MIME" msgtxt1 >actual &&
+       test_cmp content-type-decl actual
+'
+
 test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' '
        clean_fake_sendmail &&
        git config sendemail.assume8bitEncoding "bogus too" &&
@@ -2198,7 +2213,7 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
 
 test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
        clean_fake_sendmail &&
-       PATH="$(pwd):$PATH" \
+       PATH="$PWD:$PATH" \
        git send-email \
                --from="Example <nobody@example.com>" \
                --to=nobody@example.com \
@@ -2227,6 +2242,51 @@ test_expect_success $PREREQ 'test shell expression with --sendmail-cmd' '
        test_path_is_file commandline1
 '
 
+test_expect_success $PREREQ 'set up in-reply-to/references patches' '
+       cat >has-reply.patch <<-\EOF &&
+       From: A U Thor <author@example.com>
+       Subject: patch with in-reply-to
+       Message-ID: <patch.with.in.reply.to@example.com>
+       In-Reply-To: <replied.to@example.com>
+       References: <replied.to@example.com>
+
+       This is the body.
+       EOF
+       cat >no-reply.patch <<-\EOF
+       From: A U Thor <author@example.com>
+       Subject: patch without in-reply-to
+       Message-ID: <patch.without.in.reply.to@example.com>
+
+       This is the body.
+       EOF
+'
+
+test_expect_success $PREREQ 'patch reply headers correct with --no-thread' '
+       clean_fake_sendmail &&
+       git send-email \
+               --no-thread \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               has-reply.patch no-reply.patch &&
+       grep "In-Reply-To: <replied.to@example.com>" msgtxt1 &&
+       grep "References: <replied.to@example.com>" msgtxt1 &&
+       ! grep replied.to@example.com msgtxt2
+'
+
+test_expect_success $PREREQ 'cmdline in-reply-to used with --no-thread' '
+       clean_fake_sendmail &&
+       git send-email \
+               --no-thread \
+               --in-reply-to="<cmdline.reply@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               has-reply.patch no-reply.patch &&
+       grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt1 &&
+       grep "References: <cmdline.reply@example.com>" msgtxt1 &&
+       grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt2 &&
+       grep "References: <cmdline.reply@example.com>" msgtxt2
+'
+
 test_expect_success $PREREQ 'invoke hook' '
        mkdir -p .git/hooks &&
 
index 89983527b62f1d6c330edaf2f50c70ce735b0c1e..6d3dbde3feb9a41dc59a42f75ef578b1ebc5b183 100755 (executable)
@@ -42,6 +42,24 @@ EOF
        test_cmp expected actual
 '
 
+test_expect_success '--nl' '
+       cat >expected <<\EOF &&
+oneZ
+twoZ
+threeZ
+fourZ
+fiveZ
+sixZ
+sevenZ
+eightZ
+nineZ
+tenZ
+elevenZ
+EOF
+       git column --nl="Z$LF" --mode=plain <lista >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success '80 columns' '
        cat >expected <<\EOF &&
 one    two    three  four   five   six    seven  eight  nine   ten    eleven
index 1c6e6fcdaf3c5a258dd9a98647b38a2cd82631b6..77047e250dc2c862182619f717b6b96206a38ec7 100755 (executable)
@@ -18,7 +18,8 @@ test_expect_success 'setup simple repo' '
        git update-index --add --cacheinfo 160000,$fake_commit,link1 &&
        git update-index --add --cacheinfo 160000,$fake_commit,link2 &&
        git commit -m "add gitlink" &&
-       git tag -m "annotated tag" mytag
+       git tag -m "annotated tag" mytag &&
+       git tag -m "annotated tag with long message" longtag
 '
 
 test_expect_success 'export anonymized stream' '
@@ -55,7 +56,8 @@ test_expect_success 'stream retains other as refname' '
 
 test_expect_success 'stream omits other refnames' '
        ! grep main stream &&
-       ! grep mytag stream
+       ! grep mytag stream &&
+       ! grep longtag stream
 '
 
 test_expect_success 'stream omits identities' '
@@ -118,9 +120,9 @@ test_expect_success 'identical gitlinks got identical oid' '
        test_line_count = 1 commits
 '
 
-test_expect_success 'tag points to branch tip' '
+test_expect_success 'all tags point to branch tip' '
        git rev-parse $other_branch >expect &&
-       git for-each-ref --format="%(*objectname)" | grep . >actual &&
+       git for-each-ref --format="%(*objectname)" | grep . | uniq >actual &&
        test_cmp expect actual
 '
 
index 2d29d486eeef171e13e8896b58a3ccc937e108ec..17f988edd268d60e4b4a7dd2074c5566b290554f 100755 (executable)
@@ -36,6 +36,13 @@ CVSWORK="$PWD/cvswork"
 CVS_SERVER=git-cvsserver
 export CVSROOT CVS_SERVER
 
+if perl -e 'exit(1) if not defined crypt("", "cv")'
+then
+       PWDHASH='lac2ItudM3.KM'
+else
+       PWDHASH='$2b$10$t8fGvE/a9eLmfOLzsZme2uOa2QtoMYwIxq9wZA6aBKtF1Yb7FJIzi'
+fi
+
 rm -rf "$CVSWORK" "$SERVERDIR"
 test_expect_success 'setup' '
   git config push.default matching &&
@@ -54,7 +61,7 @@ test_expect_success 'setup' '
   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
   GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
   GIT_DIR="$SERVERDIR" git config gitcvs.authdb "$SERVERDIR/auth.db" &&
-  echo cvsuser:cvGVEarMLnhlA > "$SERVERDIR/auth.db"
+  echo "cvsuser:$PWDHASH" >"$SERVERDIR/auth.db"
 '
 
 # note that cvs doesn't accept absolute pathnames
index 11573936d5802ee5bcb1f0d5edee3cc77f8fbee9..5decc3b269e53a75c5e431fc5fe41333c7425470 100755 (executable)
@@ -540,6 +540,15 @@ test_expect_success '__gitcomp - expand/narrow all negative options' '
        EOF
 '
 
+test_expect_success '__gitcomp - equal skip' '
+       test_gitcomp "--option=" "--option=" <<-\EOF &&
+
+       EOF
+       test_gitcomp "option=" "option=" <<-\EOF
+
+       EOF
+'
+
 test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
        __gitcomp "$invalid_variable_name"
 '
@@ -2380,6 +2389,12 @@ test_expect_success 'git clone --config= - value' '
        EOF
 '
 
+test_expect_success 'options with value' '
+       test_completion "git merge -X diff-algorithm=" <<-\EOF
+
+       EOF
+'
+
 test_expect_success 'sourcing the completion script clears cached commands' '
        __git_compute_all_commands &&
        verbose test -n "$__git_all_commands" &&
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 11a9c8a65169d47cac20e400e31b02ee9222b23c..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: $*"
@@ -1343,7 +1344,8 @@ fi
 GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
 GIT_CONFIG_NOSYSTEM=1
 GIT_ATTR_NOSYSTEM=1
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
+GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/.."
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM GIT_CEILING_DIRECTORIES
 
 if test -z "$GIT_TEST_CMP"
 then
@@ -1379,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" || {
@@ -1422,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
 
@@ -1434,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
@@ -1533,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
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 ede18c2e06373f30679a6c0ecf77b794225edbc5..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
@@ -340,7 +375,7 @@ void trace2_def_repo_fl(const char *file, int line, struct repository *repo);
  * being started, such as "read_recursive" or "do_read_index".
  *
  * The `repo` field, if set, will be used to get the "repo-id", so that
- * recursive oerations can be attributed to the correct repository.
+ * recursive operations can be attributed to the correct repository.
  */
 void trace2_region_enter_fl(const char *file, int line, const char *category,
                            const char *label, const struct repository *repo, ...);
@@ -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 1572615bd9a7eeac84aef5a41f0e2443cae6417a..437c98a70e47ebc89d258e281f16207b9dfa990d 100644 (file)
@@ -21,7 +21,9 @@
                ALLOC_ARRAY((x), nr); \
 } while(0)
 #define FAST_ARRAY_FREE(x, nr) do { \
-       if ((nr) > 2) \
+       if ((nr) <= 2) \
+               xalloca_free((x)); \
+       else \
                free((x)); \
 } while(0)
 
index b50e686bae0ac1abb2079b31ad5dc091db079283..97c851b27df4a7ea2bc33362a9d2f9dce38c0522 100644 (file)
@@ -26,7 +26,9 @@ static const struct interval zero_width[] = {
 { 0x0825, 0x0827 },
 { 0x0829, 0x082D },
 { 0x0859, 0x085B },
-{ 0x08D3, 0x0902 },
+{ 0x0890, 0x0891 },
+{ 0x0898, 0x089F },
+{ 0x08CA, 0x0902 },
 { 0x093A, 0x093A },
 { 0x093C, 0x093C },
 { 0x0941, 0x0948 },
@@ -66,6 +68,7 @@ static const struct interval zero_width[] = {
 { 0x0BCD, 0x0BCD },
 { 0x0C00, 0x0C00 },
 { 0x0C04, 0x0C04 },
+{ 0x0C3C, 0x0C3C },
 { 0x0C3E, 0x0C40 },
 { 0x0C46, 0x0C48 },
 { 0x0C4A, 0x0C4D },
@@ -116,7 +119,7 @@ static const struct interval zero_width[] = {
 { 0x1160, 0x11FF },
 { 0x135D, 0x135F },
 { 0x1712, 0x1714 },
-{ 0x1732, 0x1734 },
+{ 0x1732, 0x1733 },
 { 0x1752, 0x1753 },
 { 0x1772, 0x1773 },
 { 0x17B4, 0x17B5 },
@@ -124,7 +127,7 @@ static const struct interval zero_width[] = {
 { 0x17C6, 0x17C6 },
 { 0x17C9, 0x17D3 },
 { 0x17DD, 0x17DD },
-{ 0x180B, 0x180E },
+{ 0x180B, 0x180F },
 { 0x1885, 0x1886 },
 { 0x18A9, 0x18A9 },
 { 0x1920, 0x1922 },
@@ -140,7 +143,7 @@ static const struct interval zero_width[] = {
 { 0x1A65, 0x1A6C },
 { 0x1A73, 0x1A7C },
 { 0x1A7F, 0x1A7F },
-{ 0x1AB0, 0x1AC0 },
+{ 0x1AB0, 0x1ACE },
 { 0x1B00, 0x1B03 },
 { 0x1B34, 0x1B34 },
 { 0x1B36, 0x1B3A },
@@ -163,8 +166,7 @@ static const struct interval zero_width[] = {
 { 0x1CED, 0x1CED },
 { 0x1CF4, 0x1CF4 },
 { 0x1CF8, 0x1CF9 },
-{ 0x1DC0, 0x1DF9 },
-{ 0x1DFB, 0x1DFF },
+{ 0x1DC0, 0x1DFF },
 { 0x200B, 0x200F },
 { 0x202A, 0x202E },
 { 0x2060, 0x2064 },
@@ -227,12 +229,16 @@ static const struct interval zero_width[] = {
 { 0x10D24, 0x10D27 },
 { 0x10EAB, 0x10EAC },
 { 0x10F46, 0x10F50 },
+{ 0x10F82, 0x10F85 },
 { 0x11001, 0x11001 },
 { 0x11038, 0x11046 },
+{ 0x11070, 0x11070 },
+{ 0x11073, 0x11074 },
 { 0x1107F, 0x11081 },
 { 0x110B3, 0x110B6 },
 { 0x110B9, 0x110BA },
 { 0x110BD, 0x110BD },
+{ 0x110C2, 0x110C2 },
 { 0x110CD, 0x110CD },
 { 0x11100, 0x11102 },
 { 0x11127, 0x1112B },
@@ -315,6 +321,8 @@ static const struct interval zero_width[] = {
 { 0x16FE4, 0x16FE4 },
 { 0x1BC9D, 0x1BC9E },
 { 0x1BCA0, 0x1BCA3 },
+{ 0x1CF00, 0x1CF2D },
+{ 0x1CF30, 0x1CF46 },
 { 0x1D167, 0x1D169 },
 { 0x1D173, 0x1D182 },
 { 0x1D185, 0x1D18B },
@@ -332,6 +340,7 @@ static const struct interval zero_width[] = {
 { 0x1E023, 0x1E024 },
 { 0x1E026, 0x1E02A },
 { 0x1E130, 0x1E136 },
+{ 0x1E2AE, 0x1E2AE },
 { 0x1E2EC, 0x1E2EF },
 { 0x1E8D0, 0x1E8D6 },
 { 0x1E944, 0x1E94A },
@@ -404,7 +413,10 @@ static const struct interval double_width[] = {
 { 0x17000, 0x187F7 },
 { 0x18800, 0x18CD5 },
 { 0x18D00, 0x18D08 },
-{ 0x1B000, 0x1B11E },
+{ 0x1AFF0, 0x1AFF3 },
+{ 0x1AFF5, 0x1AFFB },
+{ 0x1AFFD, 0x1AFFE },
+{ 0x1B000, 0x1B122 },
 { 0x1B150, 0x1B152 },
 { 0x1B164, 0x1B167 },
 { 0x1B170, 0x1B2FB },
@@ -439,21 +451,23 @@ static const struct interval double_width[] = {
 { 0x1F6CC, 0x1F6CC },
 { 0x1F6D0, 0x1F6D2 },
 { 0x1F6D5, 0x1F6D7 },
+{ 0x1F6DD, 0x1F6DF },
 { 0x1F6EB, 0x1F6EC },
 { 0x1F6F4, 0x1F6FC },
 { 0x1F7E0, 0x1F7EB },
+{ 0x1F7F0, 0x1F7F0 },
 { 0x1F90C, 0x1F93A },
 { 0x1F93C, 0x1F945 },
-{ 0x1F947, 0x1F978 },
-{ 0x1F97A, 0x1F9CB },
-{ 0x1F9CD, 0x1F9FF },
+{ 0x1F947, 0x1F9FF },
 { 0x1FA70, 0x1FA74 },
-{ 0x1FA78, 0x1FA7A },
+{ 0x1FA78, 0x1FA7C },
 { 0x1FA80, 0x1FA86 },
-{ 0x1FA90, 0x1FAA8 },
-{ 0x1FAB0, 0x1FAB6 },
-{ 0x1FAC0, 0x1FAC2 },
-{ 0x1FAD0, 0x1FAD6 },
+{ 0x1FA90, 0x1FAAC },
+{ 0x1FAB0, 0x1FABA },
+{ 0x1FAC0, 0x1FAC5 },
+{ 0x1FAD0, 0x1FAD9 },
+{ 0x1FAE0, 0x1FAE7 },
+{ 0x1FAF0, 0x1FAF6 },
 { 0x20000, 0x2FFFD },
 { 0x30000, 0x3FFFD }
 };
index 5786645f315d51be62badb5faa8cb5b8d26f70d2..a7e1712d2368a42ffb65e64778a322c2e5c18842 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");
@@ -479,7 +479,7 @@ static int check_updates(struct unpack_trees_options *o,
                errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
                                              progress, &cnt);
        stop_progress(&progress);
-       errs |= finish_delayed_checkout(&state, NULL);
+       errs |= finish_delayed_checkout(&state, NULL, o->verbose_update);
        git_attr_set_direction(GIT_ATTR_CHECKIN);
 
        if (o->clone)
@@ -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;
@@ -2156,9 +2178,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 +2221,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 +2245,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 +2272,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 +2288,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 +2298,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 +2388,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 +2411,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 297b76fcb436794c8458bfaf5689e6fa30cae556..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);
@@ -1417,21 +1426,25 @@ static int parse_want_ref(struct packet_writer *writer, const char *line,
                          struct string_list *wanted_refs,
                          struct object_array *want_obj)
 {
-       const char *arg;
-       if (skip_prefix(line, "want-ref ", &arg)) {
+       const char *refname_nons;
+       if (skip_prefix(line, "want-ref ", &refname_nons)) {
                struct object_id oid;
                struct string_list_item *item;
                struct object *o;
+               struct strbuf refname = STRBUF_INIT;
 
-               if (read_ref(arg, &oid)) {
-                       packet_writer_error(writer, "unknown ref %s", arg);
-                       die("unknown ref %s", arg);
+               strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
+               if (ref_is_hidden(refname_nons, refname.buf) ||
+                   read_ref(refname.buf, &oid)) {
+                       packet_writer_error(writer, "unknown ref %s", refname_nons);
+                       die("unknown ref %s", refname_nons);
                }
+               strbuf_release(&refname);
 
-               item = string_list_append(wanted_refs, arg);
+               item = string_list_append(wanted_refs, refname_nons);
                item->util = oiddup(&oid);
 
-               o = parse_object_or_die(&oid, arg);
+               o = parse_object_or_die(&oid, refname_nons);
                if (!(o->flags & WANTED)) {
                        o->flags |= WANTED;
                        add_object_array(o, NULL, want_obj);
@@ -1655,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 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 563ad590df1f2963767858c2d490a3893f34ab7f..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
@@ -193,7 +205,9 @@ int xopen(const char *path, int oflag, ...)
                if (errno == EINTR)
                        continue;
 
-               if ((oflag & O_RDWR) == O_RDWR)
+               if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+                       die_errno(_("unable to create '%s'"), path);
+               else if ((oflag & O_RDWR) == O_RDWR)
                        die_errno(_("could not open '%s' for reading and writing"), path);
                else if ((oflag & O_WRONLY) == O_WRONLY)
                        die_errno(_("could not open '%s' for writing"), path);
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"