Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Documentation/RelNotes/2.29.0.txt103
-rw-r--r--Documentation/config.txt4
-rw-r--r--Documentation/config/commitgraph.txt8
-rw-r--r--Documentation/config/core.txt2
-rw-r--r--Documentation/config/feature.txt4
-rw-r--r--Documentation/config/maintenance.txt16
-rw-r--r--Documentation/config/mergetool.txt10
-rw-r--r--Documentation/config/protocol.txt3
-rw-r--r--Documentation/config/receive.txt22
-rw-r--r--Documentation/fetch-options.txt6
-rw-r--r--Documentation/git-checkout.txt1
-rw-r--r--Documentation/git-clone.txt6
-rw-r--r--Documentation/git-commit-graph.txt7
-rw-r--r--Documentation/git-mailinfo.txt7
-rw-r--r--Documentation/git-maintenance.txt79
-rw-r--r--Documentation/git-worktree.txt13
-rw-r--r--Documentation/git.txt4
-rw-r--r--Documentation/gitfaq.txt86
-rw-r--r--Documentation/githooks.txt62
-rw-r--r--Documentation/technical/commit-graph-format.txt4
-rw-r--r--Documentation/technical/pack-protocol.txt41
-rw-r--r--Documentation/technical/protocol-capabilities.txt17
-rw-r--r--Makefile140
-rw-r--r--add-patch.c166
-rw-r--r--bisect.c13
-rw-r--r--blame.c12
-rw-r--r--bloom.c59
-rw-r--r--bloom.h29
-rw-r--r--builtin.h1
-rw-r--r--builtin/add.c12
-rw-r--r--builtin/am.c2
-rw-r--r--builtin/bisect--helper.c282
-rw-r--r--builtin/blame.c27
-rw-r--r--builtin/clean.c22
-rw-r--r--builtin/clone.c9
-rw-r--r--builtin/commit-graph.c63
-rw-r--r--builtin/commit.c2
-rw-r--r--builtin/config.c29
-rw-r--r--builtin/fast-export.c8
-rw-r--r--builtin/fast-import.c8
-rw-r--r--builtin/fetch.c22
-rw-r--r--builtin/gc.c337
-rw-r--r--builtin/grep.c2
-rw-r--r--builtin/index-pack.c456
-rw-r--r--builtin/init-db.c6
-rw-r--r--builtin/log.c22
-rw-r--r--builtin/merge.c2
-rw-r--r--builtin/pull.c3
-rw-r--r--builtin/push.c54
-rw-r--r--builtin/rebase.c4
-rw-r--r--builtin/receive-pack.c472
-rw-r--r--builtin/remote.c2
-rw-r--r--builtin/send-pack.c19
-rw-r--r--builtin/worktree.c2
-rw-r--r--cache.h2
-rwxr-xr-xci/lib.sh1
-rw-r--r--command-list.txt1
-rw-r--r--commit-graph.c149
-rw-r--r--commit-graph.h18
-rw-r--r--commit.h1
-rw-r--r--compat/bswap.h24
-rw-r--r--contrib/completion/git-completion.bash4
-rw-r--r--contrib/diff-highlight/DiffHighlight.pm2
-rwxr-xr-xcontrib/mw-to-git/git-mw.perl2
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl80
-rw-r--r--contrib/mw-to-git/git-remote-mediawiki.txt2
-rw-r--r--contrib/mw-to-git/t/.gitignore2
-rw-r--r--contrib/mw-to-git/t/README10
-rw-r--r--contrib/mw-to-git/t/install-wiki/.gitignore1
-rw-r--r--contrib/mw-to-git/t/install-wiki/LocalSettings.php129
-rw-r--r--contrib/mw-to-git/t/install-wiki/db_install.php120
-rwxr-xr-xcontrib/mw-to-git/t/t9360-mw-to-git-clone.sh8
-rwxr-xr-xcontrib/mw-to-git/t/t9363-mw-to-git-export-import.sh9
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw-lib.sh162
-rwxr-xr-xcontrib/mw-to-git/t/test-gitmw.pl22
-rw-r--r--contrib/mw-to-git/t/test.config23
-rw-r--r--diff-lib.c25
-rw-r--r--diff.c20
-rw-r--r--diff.h9
-rw-r--r--fuzz-commit-graph.c5
-rwxr-xr-xgenerate-cmdlist.sh2
-rwxr-xr-xgit-add--interactive.perl91
-rwxr-xr-xgit-bisect.sh70
-rwxr-xr-xgit-p4.py2
-rw-r--r--git.c1
-rw-r--r--interdiff.c28
-rw-r--r--interdiff.h8
-rw-r--r--line-log.c2
-rw-r--r--log-tree.c25
-rw-r--r--mergetools/meld85
-rw-r--r--midx.c11
-rw-r--r--object.h1
-rw-r--r--oidset.c9
-rw-r--r--oidset.h9
-rw-r--r--pack-bitmap-write.c3
-rw-r--r--pack-write.c12
-rw-r--r--packfile.c1
-rw-r--r--protocol.c6
-rw-r--r--quote.c47
-rw-r--r--quote.h11
-rw-r--r--read-cache.c2
-rw-r--r--refs.c1
-rw-r--r--refs/debug.c398
-rw-r--r--refs/files-backend.c7
-rw-r--r--refs/refs-internal.h12
-rw-r--r--refspec.c18
-rw-r--r--refspec.h2
-rw-r--r--remote.c10
-rw-r--r--remote.h9
-rw-r--r--repo-settings.c3
-rw-r--r--repository.h1
-rw-r--r--revision.c25
-rw-r--r--run-command.c16
-rw-r--r--run-command.h2
-rw-r--r--send-pack.c159
-rw-r--r--submodule.c20
-rw-r--r--t/chainlint.sed66
-rw-r--r--t/helper/test-bloom.c4
-rw-r--r--t/helper/test-proc-receive.c176
-rw-r--r--t/helper/test-read-graph.c3
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rwxr-xr-xt/perf/p5303-many-packs.sh4
-rwxr-xr-xt/t0095-bloom.sh8
-rwxr-xr-xt/t1300-config.sh13
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh18
-rwxr-xr-xt/t3200-branch.sh1
-rwxr-xr-xt/t3206-range-diff.sh12
-rwxr-xr-xt/t3701-add-interactive.sh46
-rwxr-xr-xt/t4216-log-bloom.sh242
-rwxr-xr-xt/t5324-split-commit-graph.sh13
-rwxr-xr-xt/t5411-proc-receive-hook.sh117
-rw-r--r--t/t5411/common-functions.sh56
-rw-r--r--t/t5411/once-0010-report-status-v1.sh94
-rw-r--r--t/t5411/test-0000-standard-git-push.sh143
-rw-r--r--t/t5411/test-0001-standard-git-push--porcelain.sh147
-rw-r--r--t/t5411/test-0002-pre-receive-declined.sh33
-rw-r--r--t/t5411/test-0003-pre-receive-declined--porcelain.sh34
-rw-r--r--t/t5411/test-0010-proc-receive-settings.sh7
-rw-r--r--t/t5411/test-0011-no-hook-error.sh64
-rw-r--r--t/t5411/test-0012-no-hook-error--porcelain.sh66
-rw-r--r--t/t5411/test-0013-bad-protocol.sh217
-rw-r--r--t/t5411/test-0014-bad-protocol--porcelain.sh160
-rw-r--r--t/t5411/test-0020-report-ng.sh67
-rw-r--r--t/t5411/test-0021-report-ng--porcelain.sh69
-rw-r--r--t/t5411/test-0022-report-unexpect-ref.sh45
-rw-r--r--t/t5411/test-0023-report-unexpect-ref--porcelain.sh46
-rw-r--r--t/t5411/test-0024-report-unknown-ref.sh34
-rw-r--r--t/t5411/test-0025-report-unknown-ref--porcelain.sh35
-rw-r--r--t/t5411/test-0026-push-options.sh79
-rw-r--r--t/t5411/test-0027-push-options--porcelain.sh82
-rw-r--r--t/t5411/test-0030-report-ok.sh35
-rw-r--r--t/t5411/test-0031-report-ok--porcelain.sh36
-rw-r--r--t/t5411/test-0032-report-with-options.sh256
-rw-r--r--t/t5411/test-0033-report-with-options--porcelain.sh265
-rw-r--r--t/t5411/test-0034-report-ft.sh44
-rw-r--r--t/t5411/test-0035-report-ft--porcelain.sh45
-rw-r--r--t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh227
-rw-r--r--t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh172
-rw-r--r--t/t5411/test-0038-report-mixed-refs.sh89
-rw-r--r--t/t5411/test-0039-report-mixed-refs--porcelain.sh91
-rw-r--r--t/t5411/test-0040-process-all-refs.sh113
-rw-r--r--t/t5411/test-0041-process-all-refs--porcelain.sh114
-rw-r--r--t/t5411/test-0050-proc-receive-refs-with-modifiers.sh135
-rwxr-xr-xt/t5510-fetch.sh2
-rwxr-xr-xt/t5514-fetch-multiple.sh2
-rwxr-xr-xt/t5516-fetch-push.sh2
-rwxr-xr-xt/t5534-push-signed.sh23
-rwxr-xr-xt/t5601-clone.sh14
-rwxr-xr-xt/t6030-bisect-porcelain.sh7
-rwxr-xr-xt/t7508-status.sh27
-rwxr-xr-xt/t7601-merge-pull-config.sh21
-rwxr-xr-xt/t7900-maintenance.sh65
-rwxr-xr-xt/t8013-blame-ignore-revs.sh61
-rwxr-xr-xt/t9832-unshelve.sh5
-rw-r--r--t/test-lib-functions.sh33
-rwxr-xr-xtemplates/hooks--pre-push.sample18
-rwxr-xr-xtemplates/hooks--update.sample2
-rw-r--r--transport-helper.c128
-rw-r--r--transport.c156
-rw-r--r--tree-diff.c5
-rw-r--r--wt-status.c37
183 files changed, 7661 insertions, 1507 deletions
diff --git a/.gitignore b/.gitignore
index 9673e792db..800b812c6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,6 +90,7 @@
/git-ls-tree
/git-mailinfo
/git-mailsplit
+/git-maintenance
/git-merge
/git-merge-base
/git-merge-index
@@ -196,6 +197,7 @@
/git.spec
*.exe
*.[aos]
+*.o.json
*.py[co]
.depend/
*.gcda
@@ -217,6 +219,7 @@
/tags
/TAGS
/cscope*
+/compile_commands.json
*.hcc
*.obj
*.lib
diff --git a/Documentation/RelNotes/2.29.0.txt b/Documentation/RelNotes/2.29.0.txt
index 4adb9ce124..e430392340 100644
--- a/Documentation/RelNotes/2.29.0.txt
+++ b/Documentation/RelNotes/2.29.0.txt
@@ -64,6 +64,40 @@ UI, Workflows & Features
learned a few more tricks, e.g. the ":short" suffix that applies to
"objectname" now also can be used for "parent", "tree", etc.
+ * "git worktree add" learns that the "-d" is a synonym to "--detach"
+ option to create a new worktree without being on a branch.
+
+ * "format-patch --range-diff=<prev> <origin>..HEAD" has been taught
+ not to ignore <origin> when <prev> is a single version.
+
+ * "add -p" now allows editing paths that were only added in intent.
+
+ * The 'meld' backend of the "git mergetool" learned to give the
+ underlying 'meld' the '--auto-merge' option, which would help
+ reduce the amount of text that requires manual merging.
+
+ * "git for-each-ref" and friends that list refs used to allow only
+ one --merged or --no-merged to filter them; they learned to take
+ combination of both kind of filtering.
+
+ * "git maintenance", a "git gc"'s big brother, has been introduced to
+ take care of more repository maintenance tasks, not limited to the
+ object database cleaning.
+
+ * "git receive-pack" that accepts requests by "git push" learned to
+ outsource most of the ref updates to the new "proc-receive" hook.
+
+ * "git push" that wants to be atomic and wants to send push
+ certificate learned not to prepare and sign the push certificate
+ when it fails the local check (hence due to atomicity it is known
+ that no certificate is needed).
+
+ * "git commit-graph write" learned to limit the number of bloom
+ filters that are computed from scratch with the --max-new-filters
+ option.
+
+ * The transport protocol v2 has become the default again.
+
Performance, Internal Implementation, Development Support etc.
@@ -145,6 +179,19 @@ Performance, Internal Implementation, Development Support etc.
* Internal API clean-up to handle two options "diff-index" and "log"
have, which happen to share the same short form, more sensibly.
+ * The "add -i/-p" machinery has been written in C but it is not used
+ by default yet. It is made default to those who are participating
+ in feature.experimental experiment.
+
+ * Allow maintainers to tweak $(TAR) invocations done while making
+ distribution tarballs.
+
+ * "git index-pack" learned to resolve deltified objects with greater
+ parallelism.
+
+ * "diff-highlight" (in contrib/) had a logic to flush its output upon
+ seeing a blank line but the way it detected a blank line was broken.
+
Fixes since v2.28
-----------------
@@ -304,7 +351,7 @@ Fixes since v2.28
(merge 6479ea4a8a jk/xrealloc-avoid-use-after-free later to maint).
* "git status" has trouble showing where it came from by interpreting
- reflog entries that recordcertain events, e.g. "checkout @{u}", and
+ reflog entries that record certain events, e.g. "checkout @{u}", and
gives a hard/fatal error. Even though it inherently is impossible
to give a correct answer because the reflog entries lose some
information (e.g. "@{u}" does not record what branch the user was
@@ -313,6 +360,55 @@ Fixes since v2.28
have changed), at least hide the error to allow "status" show its
output.
+ * "git status --short" quoted a path with SP in it when tracked, but
+ not those that are untracked, ignored or unmerged. They are all
+ shown quoted consistently.
+
+ * "git diff/show" on a change that involves a submodule used to read
+ the information on commits in the submodule from a wrong repository
+ and gave a wrong information when the commit-graph is involved.
+ (merge 85a1ec2c32 mf/submodule-summary-with-correct-repository later to maint).
+
+ * Unlike "git config --local", "git config --worktree" did not fail
+ early and cleanly when started outside a git repository.
+ (merge 378fe5fc3d mt/config-fail-nongit-early later to maint).
+
+ * There is a logic to estimate how many objects are in the
+ repository, which is mean to run once per process invocation, but
+ it ran every time the estimated value was requested.
+ (merge 67bb65de5d jk/dont-count-existing-objects-twice later to maint).
+
+ * "git remote set-head" that failed still said something that hints
+ the operation went through, which was misleading.
+ (merge 5a07c6c3c2 cs/don-t-pretend-a-failed-remote-set-head-succeeded later to maint).
+
+ * "git fetch --all --ipv4/--ipv6" forgot to pass the protocol options
+ to instances of the "git fetch" that talk to individual remotes,
+ which has been corrected.
+ (merge 4e735c1326 ar/fetch-ipversion-in-all later to maint).
+
+ * The "unshelve" subcommand of "git p4" used incorrectly used
+ commit^N where it meant to say commit~N to name the Nth generation
+ ancestor, which has been corrected.
+ (merge 0acbf5997f ld/p4-unshelve-fix later to maint).
+
+ * "git clone" that clones from SHA-1 repository, while
+ GIT_DEFAULT_HASH set to use SHA-256 already, resulted in an
+ unusable repository that half-claims to be SHA-256 repository
+ with SHA-1 objects and refs. This has been corrected.
+
+ * Adjust sample hooks for hash algorithm other than SHA-1.
+ (merge d8d3d632f4 dl/zero-oid-in-hooks later to maint).
+
+ * "git range-diff" showed incorrect diffstat, which has been
+ corrected.
+
+ * Earlier we taught "git pull" to warn when the user does not say the
+ histories need to be merged, rebased or accepts only fast-
+ forwarding, but the warning triggered for those who have set the
+ pull.ff configuration variable.
+ (merge 54200cef86 ah/pull later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 84544f2ea3 sk/typofixes later to maint).
(merge b17f411ab5 ar/help-guides-doc later to maint).
@@ -339,3 +435,8 @@ Fixes since v2.28
(merge 3100fd5588 jc/post-checkout-doc later to maint).
(merge 17bae89476 pb/doc-external-diff-env later to maint).
(merge 27ed6ccc12 jk/worktree-check-clean-leakfix later to maint).
+ (merge 1302badd16 ea/blame-use-oideq later to maint).
+ (merge e6d5a11fed al/t3200-back-on-a-branch later to maint).
+ (merge 324efcf6b6 pw/add-p-leakfix later to maint).
+ (merge 1c6ffb546b jk/add-i-fixes later to maint).
+ (merge e40e936551 cd/commit-graph-doc later to maint).
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 3042d80978..bf706b950e 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -340,6 +340,8 @@ include::config/column.txt[]
include::config/commit.txt[]
+include::config/commitgraph.txt[]
+
include::config/credential.txt[]
include::config/completion.txt[]
@@ -398,6 +400,8 @@ include::config/mailinfo.txt[]
include::config/mailmap.txt[]
+include::config/maintenance.txt[]
+
include::config/man.txt[]
include::config/merge.txt[]
diff --git a/Documentation/config/commitgraph.txt b/Documentation/config/commitgraph.txt
new file mode 100644
index 0000000000..4582c39fc4
--- /dev/null
+++ b/Documentation/config/commitgraph.txt
@@ -0,0 +1,8 @@
+commitGraph.maxNewFilters::
+ Specifies the default value for the `--max-new-filters` option of `git
+ commit-graph write` (c.f., linkgit:git-commit-graph[1]).
+
+commitGraph.readChangedPaths::
+ If true, then git will use the changed-path Bloom filters in the
+ commit-graph file (if it exists, and they are present). Defaults to
+ true. See linkgit:git-commit-graph[1] for more information.
diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt
index 74619a9c03..02002cf109 100644
--- a/Documentation/config/core.txt
+++ b/Documentation/config/core.txt
@@ -399,7 +399,7 @@ the largest projects. You probably do not need to adjust this value.
Common unit suffixes of 'k', 'm', or 'g' are supported.
core.deltaBaseCacheLimit::
- Maximum number of bytes to reserve for caching base objects
+ Maximum number of bytes per thread to reserve for caching base objects
that may be referenced by multiple deltified objects. By storing the
entire decompressed base objects in a cache Git is able
to avoid unpacking and decompressing frequently used base
diff --git a/Documentation/config/feature.txt b/Documentation/config/feature.txt
index c0cbf2bb1c..cdecd04e5b 100644
--- a/Documentation/config/feature.txt
+++ b/Documentation/config/feature.txt
@@ -14,10 +14,6 @@ feature.experimental::
+
* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
skipping more commits at a time, reducing the number of round trips.
-+
-* `protocol.version=2` speeds up fetches from repositories with many refs by
-allowing the client to specify which refs to list before the server lists
-them.
feature.manyFiles::
Enable config options that optimize for repos with many files in the
diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt
new file mode 100644
index 0000000000..7cc6700d57
--- /dev/null
+++ b/Documentation/config/maintenance.txt
@@ -0,0 +1,16 @@
+maintenance.<task>.enabled::
+ This boolean config option controls whether the maintenance task
+ with name `<task>` is run when no `--task` option is specified to
+ `git maintenance run`. These config values are ignored if a
+ `--task` option exists. By default, only `maintenance.gc.enabled`
+ is true.
+
+maintenance.commit-graph.auto::
+ This integer config option controls how often the `commit-graph` task
+ should be run as part of `git maintenance run --auto`. If zero, then
+ the `commit-graph` task will not run with the `--auto` option. A
+ negative value will force the task to run every time. Otherwise, a
+ positive value implies the command should run when the number of
+ reachable commits that are not in the commit-graph file is at least
+ the value of `maintenance.commit-graph.auto`. The default value is
+ 100.
diff --git a/Documentation/config/mergetool.txt b/Documentation/config/mergetool.txt
index 09ed31dbfa..16a27443a3 100644
--- a/Documentation/config/mergetool.txt
+++ b/Documentation/config/mergetool.txt
@@ -30,6 +30,16 @@ mergetool.meld.hasOutput::
to `true` tells Git to unconditionally use the `--output` option,
and `false` avoids using `--output`.
+mergetool.meld.useAutoMerge::
+ When the `--auto-merge` is given, meld will merge all non-conflicting
+ parts automatically, highlight the conflicting parts and wait for
+ user decision. Setting `mergetool.meld.useAutoMerge` to `true` tells
+ Git to unconditionally use the `--auto-merge` option with `meld`.
+ Setting this value to `auto` makes git detect whether `--auto-merge`
+ is supported and will only use `--auto-merge` when available. A
+ value of `false` avoids using `--auto-merge` altogether, and is the
+ default value.
+
mergetool.keepBackup::
After performing a merge, the original file with conflict markers
can be saved as a file with a `.orig` extension. If this variable
diff --git a/Documentation/config/protocol.txt b/Documentation/config/protocol.txt
index c46e9b3d00..756591d77b 100644
--- a/Documentation/config/protocol.txt
+++ b/Documentation/config/protocol.txt
@@ -48,8 +48,7 @@ protocol.version::
If set, clients will attempt to communicate with a server
using the specified protocol version. If the server does
not support it, communication falls back to version 0.
- If unset, the default is `0`, unless `feature.experimental`
- is enabled, in which case the default is `2`.
+ If unset, the default is `2`.
Supported versions:
+
--
diff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt
index 65f78aac37..85d5b5a3d2 100644
--- a/Documentation/config/receive.txt
+++ b/Documentation/config/receive.txt
@@ -114,6 +114,28 @@ receive.hideRefs::
An attempt to update or delete a hidden ref by `git push` is
rejected.
+receive.procReceiveRefs::
+ This is a multi-valued variable that defines reference prefixes
+ to match the commands in `receive-pack`. Commands matching the
+ prefixes will be executed by an external hook "proc-receive",
+ instead of the internal `execute_commands` function. If this
+ variable is not defined, the "proc-receive" hook will never be
+ used, and all commands will be executed by the internal
+ `execute_commands` function.
++
+For example, if this variable is set to "refs/for", pushing to reference
+such as "refs/for/master" will not create or update a reference named
+"refs/for/master", but may create or update a pull request directly by
+running the hook "proc-receive".
++
+Optional modifiers can be provided in the beginning of the value to filter
+commands for specific actions: create (a), modify (m), delete (d).
+A `!` can be included in the modifiers to negate the reference prefix entry.
+E.g.:
++
+ git config --system --add receive.procReceiveRefs ad:refs/heads
+ git config --system --add receive.procReceiveRefs !:refs/heads
+
receive.updateServerInfo::
If set to true, git-receive-pack will run git-update-server-info
after receiving data from git-push and updating refs.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index e8104c082e..2bf77b46fd 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -95,9 +95,11 @@ ifndef::git-pull[]
Allow several <repository> and <group> arguments to be
specified. No <refspec>s may be specified.
+--[no-]auto-maintenance::
--[no-]auto-gc::
- Run `git gc --auto` at the end to perform garbage collection
- if needed. This is enabled by default.
+ Run `git maintenance run --auto` at the end to perform automatic
+ repository maintenance if needed. (`--[no-]auto-gc` is a synonym.)
+ This is enabled by default.
--[no-]write-commit-graph::
Write a commit-graph after fetching. This overrides the config
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 5b697eee1b..afa5c11fd3 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -198,6 +198,7 @@ Use `--no-guess` to disable this.
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
+-d::
--detach::
Rather than checking out a branch to work on it, check out a
commit for inspection and discardable experiments.
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index c898310099..097e6a86c5 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any
other Git command that makes any existing commit unreferenced) in the
source repository, some objects may become unreferenced (or dangling).
These objects may be removed by normal Git operations (such as `git commit`)
-which automatically call `git gc --auto`. (See linkgit:git-gc[1].)
-If these objects are removed and were referenced by the cloned repository,
-then the cloned repository will become corrupt.
+which automatically call `git maintenance run --auto`. (See
+linkgit:git-maintenance[1].) If these objects are removed and were referenced
+by the cloned repository, then the cloned repository will become corrupt.
+
Note that running `git repack` without the `--local` option in a repository
cloned with `--shared` will copy objects from the source repository into a pack
diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt
index 17405c73a9..de6b6de230 100644
--- a/Documentation/git-commit-graph.txt
+++ b/Documentation/git-commit-graph.txt
@@ -67,6 +67,13 @@ this option is given, future commit-graph writes will automatically assume
that this option was intended. Use `--no-changed-paths` to stop storing this
data.
+
+With the `--max-new-filters=<n>` option, generate at most `n` new Bloom
+filters (if `--changed-paths` is specified). If `n` is `-1`, no limit is
+enforced. Only commits present in the new layer count against this
+limit. To retroactively compute Bloom filters over earlier layers, it is
+advised to use `--split=replace`. Overrides the `commitGraph.maxNewFilters`
+configuration.
++
With the `--split[=<strategy>]` option, write the commit-graph as a
chain of multiple commit-graph files stored in
`<dir>/info/commit-graphs`. Commit-graph layers are merged based on the
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 3bbc731f67..7a6aed0e30 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -72,10 +72,9 @@ conversion, even with this flag.
is useful in order to associate commits with mailing list discussions.
--scissors::
- Remove everything in body before a scissors line. A line that
- mainly consists of scissors (either ">8" or "8<") and perforation
- (dash "-") marks is called a scissors line, and is used to request
- the reader to cut the message at that line. If such a line
+ Remove everything in body before a scissors line (e.g. "-- >8 --").
+ The line represents scissors and perforation marks, and is used to
+ request the reader to cut the message at that line. If that line
appears in the body of the message before the patch, everything
before it (including the scissors line itself) is ignored when
this option is used.
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
new file mode 100644
index 0000000000..6abcb8255a
--- /dev/null
+++ b/Documentation/git-maintenance.txt
@@ -0,0 +1,79 @@
+git-maintenance(1)
+==================
+
+NAME
+----
+git-maintenance - Run tasks to optimize Git repository data
+
+
+SYNOPSIS
+--------
+[verse]
+'git maintenance' run [<options>]
+
+
+DESCRIPTION
+-----------
+Run tasks to optimize Git repository data, speeding up other Git commands
+and reducing storage requirements for the repository.
+
+Git commands that add repository data, such as `git add` or `git fetch`,
+are optimized for a responsive user experience. These commands do not take
+time to optimize the Git data, since such optimizations scale with the full
+size of the repository while these user commands each perform a relatively
+small action.
+
+The `git maintenance` command provides flexibility for how to optimize the
+Git repository.
+
+SUBCOMMANDS
+-----------
+
+run::
+ Run one or more maintenance tasks. If one or more `--task` options
+ are specified, then those tasks are run in that order. Otherwise,
+ the tasks are determined by which `maintenance.<task>.enabled`
+ config options are true. By default, only `maintenance.gc.enabled`
+ is true.
+
+TASKS
+-----
+
+commit-graph::
+ The `commit-graph` job updates the `commit-graph` files incrementally,
+ then verifies that the written data is correct. The incremental
+ write is safe to run alongside concurrent Git processes since it
+ will not expire `.graph` files that were in the previous
+ `commit-graph-chain` file. They will be deleted by a later run based
+ on the expiration delay.
+
+gc::
+ Clean up unnecessary files and optimize the local repository. "GC"
+ stands for "garbage collection," but this task performs many
+ smaller tasks. This task can be expensive for large repositories,
+ as it repacks all Git objects into a single pack-file. It can also
+ be disruptive in some situations, as it deletes stale data. See
+ linkgit:git-gc[1] for more details on garbage collection in Git.
+
+OPTIONS
+-------
+--auto::
+ When combined with the `run` subcommand, run maintenance tasks
+ only if certain thresholds are met. For example, the `gc` task
+ runs when the number of loose objects exceeds the number stored
+ in the `gc.auto` config setting, or when the number of pack-files
+ exceeds the `gc.autoPackLimit` config setting.
+
+--quiet::
+ Do not report progress or other information over `stderr`.
+
+--task=<task>::
+ If this option is specified one or more times, then only run the
+ specified tasks in the specified order. If no `--task=<task>`
+ arguments are specified, then only the tasks with
+ `maintenance.<task>.enabled` configured as `true` are considered.
+ See the 'TASKS' section for the list of accepted `<task>` values.
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index f70cda4b36..32e8440cde 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -32,6 +32,18 @@ A repository has one main working tree (if it's not a
bare repository) and zero or more linked working trees. When you are done
with a linked working tree, remove it with `git worktree remove`.
+In its simplest form, `git worktree add <path>` automatically creates a
+new branch whose name is the final component of `<path>`, which is
+convenient if you plan to work on a new topic. For instance, `git
+worktree add ../hotfix` creates new branch `hotfix` and checks it out at
+path `../hotfix`. To instead work on an existing branch in a new working
+tree, use `git worktree add <path> <branch>`. On the other hand, if you
+just plan to make some experimental changes or do testing without
+disturbing existing development, it is often convenient to create a
+'throwaway' working tree not associated with any branch. For instance,
+`git worktree add -d <path>` creates a new working tree with a detached
+`HEAD` at the same commit as the current branch.
+
If a working tree is deleted without using `git worktree remove`, then
its associated administrative files, which reside in the repository
(see "DETAILS" below), will eventually be removed automatically (see
@@ -164,6 +176,7 @@ To remove a locked working tree, specify `--force` twice.
exists. `-B` overrides this safeguard, resetting `<new-branch>` to
`<commit-ish>`.
+-d::
--detach::
With `add`, detach `HEAD` in the new working tree. See "DETACHED HEAD"
in linkgit:git-checkout[1].
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 2f72b10224..c463b937a8 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -722,6 +722,10 @@ of clones and fetches.
time of each Git command.
See `GIT_TRACE` for available trace output options.
+`GIT_TRACE_REFS`::
+ Enables trace messages for operations on the ref database.
+ See `GIT_TRACE` for available trace output options.
+
`GIT_TRACE_SETUP`::
Enables trace messages printing the .git, working tree and current
working directory after Git has completed its setup phase.
diff --git a/Documentation/gitfaq.txt b/Documentation/gitfaq.txt
index 9cd7a592ac..afdaeab850 100644
--- a/Documentation/gitfaq.txt
+++ b/Documentation/gitfaq.txt
@@ -241,6 +241,59 @@ How do I know if I want to do a fetch or a pull?::
ignore the upstream changes. A pull consists of a fetch followed
immediately by either a merge or rebase. See linkgit:git-pull[1].
+Merging and Rebasing
+--------------------
+
+[[long-running-squash-merge]]
+What kinds of problems can occur when merging long-lived branches with squash merges?::
+ In general, there are a variety of problems that can occur when using squash
+ merges to merge two branches multiple times. These can include seeing extra
+ commits in `git log` output, with a GUI, or when using the `...` notation to
+ express a range, as well as the possibility of needing to re-resolve conflicts
+ again and again.
++
+When Git does a normal merge between two branches, it considers exactly three
+points: the two branches and a third commit, called the _merge base_, which is
+usually the common ancestor of the commits. The result of the merge is the sum
+of the changes between the merge base and each head. When you merge two
+branches with a regular merge commit, this results in a new commit which will
+end up as a merge base when they're merged again, because there is now a new
+common ancestor. Git doesn't have to consider changes that occurred before the
+merge base, so you don't have to re-resolve any conflicts you resolved before.
++
+When you perform a squash merge, a merge commit isn't created; instead, the
+changes from one side are applied as a regular commit to the other side. This
+means that the merge base for these branches won't have changed, and so when Git
+goes to perform its next merge, it considers all of the changes that it
+considered the last time plus the new changes. That means any conflicts may
+need to be re-resolved. Similarly, anything using the `...` notation in `git
+diff`, `git log`, or a GUI will result in showing all of the changes since the
+original merge base.
++
+As a consequence, if you want to merge two long-lived branches repeatedly, it's
+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
+ 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
+ those commits. Git does not consider the history or the individual commits
+ that have happened on those branches at all.
++
+As a result, if both sides have a change and one side has reverted that change,
+the result is to include the change. This is because the code has changed on
+one side and there is no net change on the other, and in this scenario, Git
+adopts the change.
++
+If this is a problem for you, you can do a rebase instead, rebasing the branch
+with the revert onto the other branch. A rebase in this scenario will revert
+the change, because a rebase applies each individual commit, including the
+revert. Note that rebases rewrite history, so you should avoid rebasing
+published branches unless you're sure you're comfortable with that. See the
+NOTES section in linkgit:git-rebase[1] for more details.
+
Hooks
-----
@@ -310,6 +363,39 @@ information about how to configure files as text or binary.
You can also control this behavior with the `core.whitespace` setting if you
don't wish to remove the carriage returns from your line endings.
+[[always-modified-files-case]]
+Why do I have a file that's always modified?::
+ Internally, Git always stores file names as sequences of bytes and doesn't
+ perform any encoding or case folding. However, Windows and macOS by default
+ both perform case folding on file names. As a result, it's possible to end up
+ with multiple files or directories whose names differ only in case. Git can
+ handle this just fine, but the file system can store only one of these files,
+ so when Git reads the other file to see its contents, it looks modified.
++
+It's best to remove one of the files such that you only have one file. You can
+do this with commands like the following (assuming two files `AFile.txt` and
+`afile.txt`) on an otherwise clean working tree:
++
+----
+$ git rm --cached AFile.txt
+$ git commit -m 'Remove files conflicting in case'
+$ git checkout .
+----
++
+This avoids touching the disk, but removes the additional file. Your project
+may prefer to adopt a naming convention, such as all-lowercase names, to avoid
+this problem from occurring again; such a convention can be checked using a
+`pre-receive` hook or as part of a continuous integration (CI) system.
++
+It is also possible for perpetually modified files to occur on any platform if a
+smudge or clean filter is in use on your system but a file was previously
+committed without running the smudge or clean filter. To fix this, run the
+following on an otherwise clean working tree:
++
+----
+$ git add --renormalize .
+----
+
[[recommended-storage-settings]]
What's the recommended way to store files in Git?::
While Git can store and handle any file of any type, there are some
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index cf95d6d02b..6e461ace6e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,68 @@ The default 'update' hook, when enabled--and with
`hooks.allowunannotated` config option unset or set to false--prevents
unannotated tags to be pushed.
+[[proc-receive]]
+proc-receive
+~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-receive-pack[1]. If the server has
+set the multi-valued config variable `receive.procReceiveRefs`, and the
+commands sent to 'receive-pack' have matching reference names, these
+commands will be executed by this hook, instead of by the internal
+`execute_commands()` function. This hook is responsible for updating
+the relevant references and reporting the results back to 'receive-pack'.
+
+This hook executes once for the receive operation. It takes no
+arguments, but uses a pkt-line format protocol to communicate with
+'receive-pack' to read commands, push-options and send results. In the
+following example for the protocol, the letter 'S' stands for
+'receive-pack' and the letter 'H' stands for this hook.
+
+ # Version and features negotiation.
+ S: PKT-LINE(version=1\0push-options atomic...)
+ S: flush-pkt
+ H: PKT-LINE(version=1\0push-options...)
+ H: flush-pkt
+
+ # Send commands from server to the hook.
+ S: PKT-LINE(<old-oid> <new-oid> <ref>)
+ S: ... ...
+ S: flush-pkt
+ # Send push-options only if the 'push-options' feature is enabled.
+ S: PKT-LINE(push-option)
+ S: ... ...
+ S: flush-pkt
+
+ # Receive result from the hook.
+ # OK, run this command successfully.
+ H: PKT-LINE(ok <ref>)
+ # NO, I reject it.
+ H: PKT-LINE(ng <ref> <reason>)
+ # Fall through, let 'receive-pack' to execute it.
+ H: PKT-LINE(ok <ref>)
+ H: PKT-LINE(option fall-through)
+ # OK, but has an alternate reference. The alternate reference name
+ # and other status can be given in option directives.
+ H: PKT-LINE(ok <ref>)
+ H: PKT-LINE(option refname <refname>)
+ H: PKT-LINE(option old-oid <old-oid>)
+ H: PKT-LINE(option new-oid <new-oid>)
+ H: PKT-LINE(option forced-update)
+ H: ... ...
+ H: flush-pkt
+
+Each command for the 'proc-receive' hook may point to a pseudo-reference
+and always has a zero-old as its old-oid, while the 'proc-receive' hook
+may update an alternate reference and the alternate reference may exist
+already with a non-zero old-oid. For this case, this hook will use
+"option" directives to report extended attributes for the reference given
+by the leading "ok" directive.
+
+The report of the commands of this hook should have the same order as
+the input. The exit status of the 'proc-receive' hook only determines
+the success or failure of the group of commands sent to it, unless
+atomic push is in use.
+
[[post-receive]]
post-receive
~~~~~~~~~~~~
diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt
index 6ddbceba15..b3b58880b9 100644
--- a/Documentation/technical/commit-graph-format.txt
+++ b/Documentation/technical/commit-graph-format.txt
@@ -82,7 +82,7 @@ CHUNK DATA:
Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes)
* The first H bytes are for the OID of the root tree.
* The next 8 bytes are for the positions of the first two parents
- of the ith commit. Stores value 0x7000000 if no parent in that
+ of the ith commit. Stores value 0x70000000 if no parent in that
position. If there are more than two parents, the second value
has its most-significant bit on and the other bits store an array
position into the Extra Edge List chunk.
@@ -125,7 +125,7 @@ CHUNK DATA:
* The rest of the chunk is the concatenation of all the computed Bloom
filters for the commits in lexicographic order.
* Note: Commits with no changes or more than 512 changes have Bloom filters
- of length zero.
+ of length one, with either all bits set to zero or one respectively.
* The BDAT chunk is present if and only if BIDX is present.
Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional]
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index a4573d12ce..e13a2c064d 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the
fetching protocol. Each reference obj-id and name on the server is sent
in packet-line format to the client, followed by a flush-pkt. The only
real difference is that the capability listing is different - the only
-possible values are 'report-status', 'delete-refs', 'ofs-delta' and
-'push-options'.
+possible values are 'report-status', 'report-status-v2', 'delete-refs',
+'ofs-delta', 'atomic' and 'push-options'.
Reference Update Request and Packfile Transfer
----------------------------------------------
@@ -625,7 +625,7 @@ Report Status
-------------
After receiving the pack data from the sender, the receiver sends a
-report if 'report-status' capability is in effect.
+report if 'report-status' or 'report-status-v2' capability is in effect.
It is a short listing of what happened in that update. It will first
list the status of the packfile unpacking as either 'unpack ok' or
'unpack [error]'. Then it will list the status for each of the references
@@ -647,6 +647,41 @@ update was successful, or 'ng [refname] [error]' if the update was not.
error-msg = 1*(OCTET) ; where not "ok"
----
+The 'report-status-v2' capability extends the protocol by adding new option
+lines in order to support reporting of reference rewritten by the
+'proc-receive' hook. The 'proc-receive' hook may handle a command for a
+pseudo-reference which may create or update one or more references, and each
+reference may have different name, different new-oid, and different old-oid.
+
+----
+ report-status-v2 = unpack-status
+ 1*(command-status-v2)
+ flush-pkt
+
+ unpack-status = PKT-LINE("unpack" SP unpack-result)
+ unpack-result = "ok" / error-msg
+
+ command-status-v2 = command-ok-v2 / command-fail
+ command-ok-v2 = command-ok
+ *option-line
+
+ command-ok = PKT-LINE("ok" SP refname)
+ command-fail = PKT-LINE("ng" SP refname SP error-msg)
+
+ error-msg = 1*(OCTET) ; where not "ok"
+
+ option-line = *1(option-refname)
+ *1(option-old-oid)
+ *1(option-new-oid)
+ *1(option-forced-update)
+
+ option-refname = PKT-LINE("option" SP "refname" SP refname)
+ option-old-oid = PKT-LINE("option" SP "old-oid" SP obj-id)
+ option-new-oid = PKT-LINE("option" SP "new-oid" SP obj-id)
+ option-force = PKT-LINE("option" SP "forced-update")
+
+----
+
Updates can be unsuccessful for a number of reasons. The reference can have
changed since the reference discovery phase was originally sent, meaning
someone pushed in the meantime. The reference being pushed could be a
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index 124d716807..ba869a7d36 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -22,9 +22,9 @@ was sent. Server MUST NOT ignore capabilities that client requested
and server advertised. As a consequence of these rules, server MUST
NOT advertise capabilities it does not understand.
-The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'
-capabilities are sent and recognized by the receive-pack (push to server)
-process.
+The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet',
+and 'push-cert' capabilities are sent and recognized by the receive-pack
+(push to server) process.
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
by both upload-pack and receive-pack protocols. The 'agent' capability
@@ -284,6 +284,17 @@ each reference was updated successfully. If any of those were not
successful, it will send back an error message. See pack-protocol.txt
for example messages.
+report-status-v2
+----------------
+
+Capability 'report-status-v2' extends capability 'report-status' by
+adding new "option" directives in order to support reference rewritten by
+the "proc-receive" hook. The "proc-receive" hook may handle a command
+for a pseudo-reference which may create or update a reference with
+different name, new-oid, and old-oid. While the capability
+'report-status' cannot report for such case. See pack-protocol.txt
+for details.
+
delete-refs
-----------
diff --git a/Makefile b/Makefile
index 86e5411f39..fb521dad71 100644
--- a/Makefile
+++ b/Makefile
@@ -348,6 +348,9 @@ all::
# Define NO_INSTALL_HARDLINKS if you prefer to use either symbolic links or
# copies to install built-in git commands e.g. git-cat-file.
#
+# Define SKIP_DASHED_BUILT_INS if you do not need the dashed versions of the
+# built-ins to be linked/copied at all.
+#
# Define USE_NED_ALLOCATOR if you want to replace the platforms default
# memory allocators with the nedmalloc allocator written by Niall Douglas.
#
@@ -462,6 +465,12 @@ all::
# the global variable _wpgmptr containing the absolute path of the current
# executable (this is the case on Windows).
#
+# 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/`
+# directory, and the JSON compilation database 'compile_commands.json' will be
+# created at the root of the repository.
+#
# Define DEVELOPER to enable more compiler warnings. Compiler version
# and family are auto detected, but could be overridden by defining
# COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -714,6 +723,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
TEST_BUILTINS_OBJS += test-path-utils.o
TEST_BUILTINS_OBJS += test-pkt-line.o
TEST_BUILTINS_OBJS += test-prio-queue.o
+TEST_BUILTINS_OBJS += test-proc-receive.o
TEST_BUILTINS_OBJS += test-progress.o
TEST_BUILTINS_OBJS += test-reach.o
TEST_BUILTINS_OBJS += test-read-cache.o
@@ -768,6 +778,16 @@ BUILT_INS += git-whatchanged$X
# what 'all' will build and 'install' will install in gitexecdir,
# excluding programs for built-in commands
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
+ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS)
+ifeq (,$(SKIP_DASHED_BUILT_INS))
+ALL_COMMANDS_TO_INSTALL += $(BUILT_INS)
+else
+# git-upload-pack, git-receive-pack and git-upload-archive are special: they
+# are _expected_ to be present in the `bin/` directory in their dashed form.
+ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X)
+endif
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X
@@ -883,7 +903,6 @@ LIB_OBJS += hashmap.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o
-LIB_OBJS += interdiff.o
LIB_OBJS += json-writer.o
LIB_OBJS += kwset.o
LIB_OBJS += levenshtein.o
@@ -950,6 +969,7 @@ LIB_OBJS += rebase.o
LIB_OBJS += ref-filter.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
+LIB_OBJS += refs/debug.o
LIB_OBJS += refs/files-backend.o
LIB_OBJS += refs/iterator.o
LIB_OBJS += refs/packed-backend.o
@@ -1212,7 +1232,6 @@ SANITIZERS := $(foreach flag,$(subst $(comma),$(space),$(SANITIZE)),$(flag))
BASIC_CFLAGS += -fsanitize=$(SANITIZE) -fno-sanitize-recover=$(SANITIZE)
BASIC_CFLAGS += -fno-omit-frame-pointer
ifneq ($(filter undefined,$(SANITIZERS)),)
-BASIC_CFLAGS += -DNO_UNALIGNED_LOADS
BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS
endif
ifneq ($(filter leak,$(SANITIZERS)),)
@@ -1256,6 +1275,27 @@ $(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
endif
endif
+ifndef GENERATE_COMPILATION_DATABASE
+GENERATE_COMPILATION_DATABASE = no
+endif
+
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+compdb_check = $(shell $(CC) $(ALL_CFLAGS) \
+ -c -MJ /dev/null \
+ -x c /dev/null -o /dev/null 2>&1; \
+ echo $$?)
+ifneq ($(compdb_check),0)
+override GENERATE_COMPILATION_DATABASE = no
+$(warning GENERATE_COMPILATION_DATABASE is set to "yes", but your compiler does not \
+support generating compilation database entries)
+endif
+else
+ifneq ($(GENERATE_COMPILATION_DATABASE),no)
+$(error please set GENERATE_COMPILATION_DATABASE to "yes" or "no" \
+(not "$(GENERATE_COMPILATION_DATABASE)"))
+endif
+endif
+
ifdef SANE_TOOL_PATH
SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH))
BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix "$(SANE_TOOL_PATH_SQ)"|'
@@ -2061,9 +2101,9 @@ profile-fast: profile-clean
$(MAKE) PROFILE=USE all
-all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
+all:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS
ifneq (,$X)
- $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
+ $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';)
endif
all::
@@ -2369,16 +2409,30 @@ missing_dep_dirs =
dep_args =
endif
+compdb_dir = compile_commands
+
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+missing_compdb_dir = $(compdb_dir)
+$(missing_compdb_dir):
+ @mkdir -p $@
+
+compdb_file = $(compdb_dir)/$(subst /,-,$@.json)
+compdb_args = -MJ $(compdb_file)
+else
+missing_compdb_dir =
+compdb_args =
+endif
+
ASM_SRC := $(wildcard $(OBJECTS:o=S))
ASM_OBJ := $(ASM_SRC:S=o)
C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS))
.SUFFIXES:
-$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs)
- $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
-$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
- $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir)
+ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
%.s: %.c GIT-CFLAGS FORCE
$(QUIET_CC)$(CC) -o $@ -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
@@ -2401,6 +2455,14 @@ else
$(OBJECTS): $(LIB_H) $(GENERATED_H)
endif
+ifeq ($(GENERATE_COMPILATION_DATABASE),yes)
+all:: compile_commands.json
+compile_commands.json:
+ @$(RM) $@
+ $(QUIET_GEN)sed -e '1s/^/[/' -e '$$s/,$$/]/' $(compdb_dir)/*.o.json > $@+
+ @if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
+endif
+
exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX
exec-cmd.sp exec-cmd.s exec-cmd.o: EXTRA_CPPFLAGS = \
'-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
@@ -2872,15 +2934,8 @@ ifdef MSVC
# have already been rolled up into the exe's pdb file.
# We DO NOT have pdb files for the builtin commands (like git-status.exe)
# because it is just a copy/hardlink of git.exe, rather than a unique binary.
- $(INSTALL) git.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
- $(INSTALL) git-shell.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
- $(INSTALL) git-daemon.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-backend.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-fetch.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-http-push.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-imap-send.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-remote-http.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) git-sh-i18n--envsubst.pdb '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(patsubst %.exe,%.pdb,$(filter-out $(BUILT_INS),$(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)))) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(patsubst %.exe,%.pdb,$(filter-out $(BUILT_INS) $(REMOTE_CURL_ALIASES),$(PROGRAMS))) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
ifndef DEBUG
$(INSTALL) $(vcpkg_rel_bin)/*.dll '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) $(vcpkg_rel_bin)/*.pdb '$(DESTDIR_SQ)$(bindir_SQ)'
@@ -2908,7 +2963,7 @@ ifndef NO_TCLTK
$(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
endif
ifneq (,$X)
- $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
+ $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
endif
bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
@@ -2926,21 +2981,27 @@ endif
} && \
for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
$(RM) "$$bindir/$$p" && \
- test -n "$(INSTALL_SYMLINKS)" && \
- ln -s "git$X" "$$bindir/$$p" || \
- { test -z "$(NO_INSTALL_HARDLINKS)" && \
- ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
- ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
- cp "$$bindir/git$X" "$$bindir/$$p" || exit; } \
+ if test -z "$(SKIP_DASHED_BUILT_INS)"; \
+ then \
+ test -n "$(INSTALL_SYMLINKS)" && \
+ ln -s "git$X" "$$bindir/$$p" || \
+ { test -z "$(NO_INSTALL_HARDLINKS)" && \
+ ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
+ ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
+ cp "$$bindir/git$X" "$$bindir/$$p" || exit; }; \
+ fi \
done && \
for p in $(BUILT_INS); do \
$(RM) "$$execdir/$$p" && \
- test -n "$(INSTALL_SYMLINKS)" && \
- ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
- { test -z "$(NO_INSTALL_HARDLINKS)" && \
- ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
- ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
- cp "$$execdir/git$X" "$$execdir/$$p" || exit; } \
+ if test -z "$(SKIP_DASHED_BUILT_INS)"; \
+ then \
+ test -n "$(INSTALL_SYMLINKS)" && \
+ ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
+ { test -z "$(NO_INSTALL_HARDLINKS)" && \
+ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
+ ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
+ cp "$$execdir/git$X" "$$execdir/$$p" || exit; }; \
+ fi \
done && \
remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
for p in $$remote_curl_aliases; do \
@@ -2992,6 +3053,9 @@ quick-install-html:
### Maintainer's dist rules
+# Allow tweaking to hide local environment effects, like perm bits.
+# With GNU tar, "--mode=u+rwX,og+rX,og-w" would be a good idea, for example.
+TAR_DIST_EXTRA_OPTS =
GIT_TARNAME = git-$(GIT_VERSION)
dist: git-archive$(X) configure
./git-archive --format=tar \
@@ -3000,7 +3064,7 @@ dist: git-archive$(X) configure
@cp configure $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
@$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version
- $(TAR) rf $(GIT_TARNAME).tar \
+ $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \
$(GIT_TARNAME)/configure \
$(GIT_TARNAME)/version \
$(GIT_TARNAME)/git-gui/version
@@ -3014,7 +3078,7 @@ ifdef DC_SHA1_SUBMODULE
$(GIT_TARNAME)/sha1collisiondetection/lib/
@cp sha1collisiondetection/lib/ubc_check.[ch] \
$(GIT_TARNAME)/sha1collisiondetection/lib/
- $(TAR) rf $(GIT_TARNAME).tar \
+ $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \
$(GIT_TARNAME)/sha1collisiondetection/LICENSE.txt \
$(GIT_TARNAME)/sha1collisiondetection/lib/sha1.[ch] \
$(GIT_TARNAME)/sha1collisiondetection/lib/ubc_check.[ch]
@@ -3031,7 +3095,7 @@ ifneq ($(INCLUDE_DLLS_IN_ARTIFACTS),)
OTHER_PROGRAMS += $(shell echo *.dll t/helper/*.dll)
endif
-artifacts-tar:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) \
+artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
$(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
@@ -3048,7 +3112,7 @@ dist-doc:
$(RM) -r .doc-tmp-dir
mkdir .doc-tmp-dir
$(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
- cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
+ cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar $(TAR_DIST_EXTRA_OPTS) .
gzip -n -9 -f $(htmldocs).tar
:
$(RM) -r .doc-tmp-dir
@@ -3058,7 +3122,7 @@ dist-doc:
man5dir=../.doc-tmp-dir/man5 \
man7dir=../.doc-tmp-dir/man7 \
install
- cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
+ cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar $(TAR_DIST_EXTRA_OPTS) .
gzip -n -9 -f $(manpages).tar
$(RM) -r .doc-tmp-dir
@@ -3085,7 +3149,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) $(TEST_PROGRAMS)
$(RM) $(FUZZ_PROGRAMS)
$(RM) $(HCC)
- $(RM) -r bin-wrappers $(dep_dirs)
+ $(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json
$(RM) -r po/build/
$(RM) *.pyc *.pyo */*.pyc */*.pyo $(GENERATED_H) $(ETAGS_TARGET) tags cscope*
$(RM) -r $(GIT_TARNAME) .doc-tmp-dir
@@ -3126,7 +3190,7 @@ endif
### Check documentation
#
-ALL_COMMANDS = $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS)
+ALL_COMMANDS = $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB)
ALL_COMMANDS += git
ALL_COMMANDS += git-citool
ALL_COMMANDS += git-gui
@@ -3166,7 +3230,7 @@ check-docs::
-e 's/\.txt//'; \
) | while read how cmd; \
do \
- case " $(patsubst %$X,%,$(ALL_COMMANDS) $(EXCLUDED_PROGRAMS)) " in \
+ case " $(patsubst %$X,%,$(ALL_COMMANDS) $(BUILT_INS) $(EXCLUDED_PROGRAMS)) " in \
*" $$cmd "*) ;; \
*) echo "removed but $$how: $$cmd" ;; \
esac; \
diff --git a/add-patch.c b/add-patch.c
index 457b8c550e..bd94bd3a7c 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -266,6 +266,20 @@ struct add_p_state {
const char *revision;
};
+static void add_p_state_clear(struct add_p_state *s)
+{
+ size_t i;
+
+ strbuf_release(&s->answer);
+ strbuf_release(&s->buf);
+ strbuf_release(&s->plain);
+ strbuf_release(&s->colored);
+ for (i = 0; i < s->file_diff_nr; i++)
+ free(s->file_diff[i].hunk);
+ free(s->file_diff);
+ clear_add_i_state(&s->s);
+}
+
static void err(struct add_p_state *s, const char *fmt, ...)
{
va_list args;
@@ -451,7 +465,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
pend = p + plain->len;
while (p != pend) {
char *eol = memchr(p, '\n', pend - p);
- const char *deleted = NULL, *added = NULL, *mode_change = NULL;
+ const char *deleted = NULL, *mode_change = NULL;
if (!eol)
eol = pend;
@@ -468,12 +482,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
} else if (p == plain->buf)
BUG("diff starts with unexpected line:\n"
"%.*s\n", (int)(eol - p), p);
- else if (file_diff->deleted || file_diff->added)
+ else if (file_diff->deleted)
; /* keep the rest of the file in a single "hunk" */
else if (starts_with(p, "@@ ") ||
(hunk == &file_diff->head &&
- (skip_prefix(p, "deleted file", &deleted) ||
- skip_prefix(p, "new file", &added)))) {
+ (skip_prefix(p, "deleted file", &deleted)))) {
if (marker == '-' || marker == '+')
/*
* Should not happen; previous hunk did not end
@@ -491,8 +504,6 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
if (deleted)
file_diff->deleted = 1;
- else if (added)
- file_diff->added = 1;
else if (parse_hunk_header(s, hunk) < 0)
return -1;
@@ -502,6 +513,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
*/
marker = *p;
} else if (hunk == &file_diff->head &&
+ starts_with(p, "new file")) {
+ file_diff->added = 1;
+ } else if (hunk == &file_diff->head &&
skip_prefix(p, "old mode ", &mode_change) &&
is_octal(mode_change, eol - mode_change)) {
if (file_diff->mode_change)
@@ -1362,7 +1376,8 @@ static int patch_update_file(struct add_p_state *s,
ALLOW_EDIT = 1 << 6
} permitted = 0;
- if (!file_diff->hunk_nr)
+ /* Empty added files have no hunks */
+ if (!file_diff->hunk_nr && !file_diff->added)
return 0;
strbuf_reset(&s->buf);
@@ -1371,21 +1386,25 @@ static int patch_update_file(struct add_p_state *s,
for (;;) {
if (hunk_index >= file_diff->hunk_nr)
hunk_index = 0;
- hunk = file_diff->hunk + hunk_index;
-
+ hunk = file_diff->hunk_nr
+ ? file_diff->hunk + hunk_index
+ : &file_diff->head;
undecided_previous = -1;
- for (i = hunk_index - 1; i >= 0; i--)
- if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
- undecided_previous = i;
- break;
- }
-
undecided_next = -1;
- for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
- if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
- undecided_next = i;
- break;
- }
+
+ if (file_diff->hunk_nr) {
+ for (i = hunk_index - 1; i >= 0; i--)
+ if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
+ undecided_previous = i;
+ break;
+ }
+
+ for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
+ if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
+ undecided_next = i;
+ break;
+ }
+ }
/* Everything decided? */
if (undecided_previous < 0 && undecided_next < 0 &&
@@ -1393,38 +1412,40 @@ static int patch_update_file(struct add_p_state *s,
break;
strbuf_reset(&s->buf);
- render_hunk(s, hunk, 0, colored, &s->buf);
- fputs(s->buf.buf, stdout);
+ if (file_diff->hunk_nr) {
+ render_hunk(s, hunk, 0, colored, &s->buf);
+ fputs(s->buf.buf, stdout);
- strbuf_reset(&s->buf);
- if (undecided_previous >= 0) {
- permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
- strbuf_addstr(&s->buf, ",k");
- }
- if (hunk_index) {
- permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
- strbuf_addstr(&s->buf, ",K");
- }
- if (undecided_next >= 0) {
- permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
- strbuf_addstr(&s->buf, ",j");
- }
- if (hunk_index + 1 < file_diff->hunk_nr) {
- permitted |= ALLOW_GOTO_NEXT_HUNK;
- strbuf_addstr(&s->buf, ",J");
- }
- if (file_diff->hunk_nr > 1) {
- permitted |= ALLOW_SEARCH_AND_GOTO;
- strbuf_addstr(&s->buf, ",g,/");
- }
- if (hunk->splittable_into > 1) {
- permitted |= ALLOW_SPLIT;
- strbuf_addstr(&s->buf, ",s");
- }
- if (hunk_index + 1 > file_diff->mode_change &&
- !file_diff->deleted) {
- permitted |= ALLOW_EDIT;
- strbuf_addstr(&s->buf, ",e");
+ strbuf_reset(&s->buf);
+ if (undecided_previous >= 0) {
+ permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
+ strbuf_addstr(&s->buf, ",k");
+ }
+ if (hunk_index) {
+ permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
+ strbuf_addstr(&s->buf, ",K");
+ }
+ if (undecided_next >= 0) {
+ permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
+ strbuf_addstr(&s->buf, ",j");
+ }
+ if (hunk_index + 1 < file_diff->hunk_nr) {
+ permitted |= ALLOW_GOTO_NEXT_HUNK;
+ strbuf_addstr(&s->buf, ",J");
+ }
+ if (file_diff->hunk_nr > 1) {
+ permitted |= ALLOW_SEARCH_AND_GOTO;
+ strbuf_addstr(&s->buf, ",g,/");
+ }
+ if (hunk->splittable_into > 1) {
+ permitted |= ALLOW_SPLIT;
+ strbuf_addstr(&s->buf, ",s");
+ }
+ if (hunk_index + 1 > file_diff->mode_change &&
+ !file_diff->deleted) {
+ permitted |= ALLOW_EDIT;
+ strbuf_addstr(&s->buf, ",e");
+ }
}
if (file_diff->deleted)
prompt_mode_type = PROMPT_DELETION;
@@ -1438,7 +1459,9 @@ static int patch_update_file(struct add_p_state *s,
color_fprintf(stdout, s->s.prompt_color,
"(%"PRIuMAX"/%"PRIuMAX") ",
(uintmax_t)hunk_index + 1,
- (uintmax_t)file_diff->hunk_nr);
+ (uintmax_t)(file_diff->hunk_nr
+ ? file_diff->hunk_nr
+ : 1));
color_fprintf(stdout, s->s.prompt_color,
_(s->mode->prompt_mode[prompt_mode_type]),
s->buf.buf);
@@ -1458,16 +1481,24 @@ soft_increment:
hunk->use = SKIP_HUNK;
goto soft_increment;
} else if (ch == 'a') {
- for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
- hunk = file_diff->hunk + hunk_index;
- if (hunk->use == UNDECIDED_HUNK)
- hunk->use = USE_HUNK;
+ if (file_diff->hunk_nr) {
+ for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
+ hunk = file_diff->hunk + hunk_index;
+ if (hunk->use == UNDECIDED_HUNK)
+ hunk->use = USE_HUNK;
+ }
+ } else if (hunk->use == UNDECIDED_HUNK) {
+ hunk->use = USE_HUNK;
}
} else if (ch == 'd' || ch == 'q') {
- for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
- hunk = file_diff->hunk + hunk_index;
- if (hunk->use == UNDECIDED_HUNK)
- hunk->use = SKIP_HUNK;
+ if (file_diff->hunk_nr) {
+ for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
+ hunk = file_diff->hunk + hunk_index;
+ if (hunk->use == UNDECIDED_HUNK)
+ hunk->use = SKIP_HUNK;
+ }
+ } else if (hunk->use == UNDECIDED_HUNK) {
+ hunk->use = SKIP_HUNK;
}
if (ch == 'q') {
quit = 1;
@@ -1625,7 +1656,8 @@ soft_increment:
if (file_diff->hunk[i].use == USE_HUNK)
break;
- if (i < file_diff->hunk_nr) {
+ if (i < file_diff->hunk_nr ||
+ (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
/* At least one hunk selected: apply */
strbuf_reset(&s->buf);
reassemble_patch(s, file_diff, 0, &s->buf);
@@ -1641,7 +1673,7 @@ soft_increment:
NULL, 0, NULL, 0))
error(_("'git apply' failed"));
}
- if (!repo_read_index(s->s.r))
+ if (repo_read_index(s->s.r) >= 0)
repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
1, NULL, NULL, NULL);
}
@@ -1690,9 +1722,7 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
NULL, NULL, NULL) < 0) ||
parse_diff(&s, ps) < 0) {
- strbuf_release(&s.plain);
- strbuf_release(&s.colored);
- clear_add_i_state(&s.s);
+ add_p_state_clear(&s);
return -1;
}
@@ -1707,10 +1737,6 @@ int run_add_p(struct repository *r, enum add_p_mode mode,
else if (binary_count == s.file_diff_nr)
fprintf(stderr, _("Only binary files changed.\n"));
- strbuf_release(&s.answer);
- strbuf_release(&s.buf);
- strbuf_release(&s.plain);
- strbuf_release(&s.colored);
- clear_add_i_state(&s.s);
+ add_p_state_clear(&s);
return 0;
}
diff --git a/bisect.c b/bisect.c
index d42a3a3767..f5b1368128 100644
--- a/bisect.c
+++ b/bisect.c
@@ -988,8 +988,11 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
* the bisection process finished successfully.
* In this case the calling function or command should not turn a
* BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND return code into an error or a non zero exit code.
- * If no_checkout is non-zero, the bisection process does not
- * checkout the trial commit but instead simply updates BISECT_HEAD.
+ *
+ * Checking BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND
+ * in bisect_helper::bisect_next() and only transforming it to 0 at
+ * the end of bisect_helper::cmd_bisect__helper() helps bypassing
+ * all the code related to finding a commit to test.
*/
enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
{
@@ -999,6 +1002,10 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
enum bisect_error res = BISECT_OK;
struct object_id *bisect_rev;
char *steps_msg;
+ /*
+ * If no_checkout is non-zero, the bisection process does not
+ * checkout the trial commit but instead simply updates BISECT_HEAD.
+ */
int no_checkout = ref_exists("BISECT_HEAD");
unsigned bisect_flags = 0;
@@ -1082,6 +1089,8 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
"Bisecting: %d revisions left to test after this %s\n",
nr), nr, steps_msg);
free(steps_msg);
+ /* Clean up objects used, as they will be reused. */
+ clear_commit_marks_all(ALL_REV_FLAGS);
return bisect_checkout(bisect_rev, no_checkout);
}
diff --git a/blame.c b/blame.c
index 1be1cd82a2..686845b2b4 100644
--- a/blame.c
+++ b/blame.c
@@ -1276,7 +1276,7 @@ static int maybe_changed_path(struct repository *r,
if (commit_graph_generation(origin->commit) == GENERATION_NUMBER_INFINITY)
return 1;
- filter = get_bloom_filter(r, origin->commit, 0);
+ filter = get_bloom_filter(r, origin->commit);
if (!filter)
return 1;
@@ -1353,8 +1353,8 @@ static struct blame_origin *find_origin(struct repository *r,
else {
int compute_diff = 1;
if (origin->commit->parents &&
- !oidcmp(&parent->object.oid,
- &origin->commit->parents->item->object.oid))
+ oideq(&parent->object.oid,
+ &origin->commit->parents->item->object.oid))
compute_diff = maybe_changed_path(r, origin, bd);
if (compute_diff)
@@ -2892,16 +2892,18 @@ void setup_blame_bloom_data(struct blame_scoreboard *sb,
const char *path)
{
struct blame_bloom_data *bd;
+ struct bloom_filter_settings *bs;
if (!sb->repo->objects->commit_graph)
return;
- if (!sb->repo->objects->commit_graph->bloom_filter_settings)
+ bs = get_bloom_filter_settings(sb->repo);
+ if (!bs)
return;
bd = xmalloc(sizeof(struct blame_bloom_data));
- bd->settings = sb->repo->objects->commit_graph->bloom_filter_settings;
+ bd->settings = bs;
bd->alloc = 4;
bd->nr = 0;
diff --git a/bloom.c b/bloom.c
index 1a573226e7..68c73200a5 100644
--- a/bloom.c
+++ b/bloom.c
@@ -38,7 +38,7 @@ static int load_bloom_filter_from_graph(struct commit_graph *g,
while (graph_pos < g->num_commits_in_base)
g = g->base_graph;
- /* The commit graph commit 'c' lives in doesn't carry bloom filters. */
+ /* The commit graph commit 'c' lives in doesn't carry Bloom filters. */
if (!g->chunk_bloom_indexes)
return 0;
@@ -177,15 +177,25 @@ static int pathmap_cmp(const void *hashmap_cmp_fn_data,
return strcmp(e1->path, e2->path);
}
-struct bloom_filter *get_bloom_filter(struct repository *r,
- struct commit *c,
- int compute_if_not_present)
+static void init_truncated_large_filter(struct bloom_filter *filter)
+{
+ filter->data = xmalloc(1);
+ filter->data[0] = 0xFF;
+ filter->len = 1;
+}
+
+struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
+ struct commit *c,
+ int compute_if_not_present,
+ const struct bloom_filter_settings *settings,
+ enum bloom_filter_computed *computed)
{
struct bloom_filter *filter;
- struct bloom_filter_settings settings = DEFAULT_BLOOM_FILTER_SETTINGS;
int i;
struct diff_options diffopt;
- int max_changes = 512;
+
+ if (computed)
+ *computed = BLOOM_NOT_COMPUTED;
if (!bloom_filters.slab_size)
return NULL;
@@ -194,12 +204,11 @@ struct bloom_filter *get_bloom_filter(struct repository *r,
if (!filter->data) {
load_commit_graph_info(r, c);
- if (commit_graph_position(c) != COMMIT_NOT_FROM_GRAPH &&
- r->objects->commit_graph->chunk_bloom_indexes)
+ if (commit_graph_position(c) != COMMIT_NOT_FROM_GRAPH)
load_bloom_filter_from_graph(r->objects->commit_graph, filter, c);
}
- if (filter->data)
+ if (filter->data && filter->len)
return filter;
if (!compute_if_not_present)
return NULL;
@@ -207,7 +216,7 @@ struct bloom_filter *get_bloom_filter(struct repository *r,
repo_diff_setup(r, &diffopt);
diffopt.flags.recursive = 1;
diffopt.detect_rename = 0;
- diffopt.max_changes = max_changes;
+ diffopt.max_changes = settings->max_changed_paths;
diff_setup_done(&diffopt);
/* ensure commit is parsed so we have parent information */
@@ -219,7 +228,7 @@ struct bloom_filter *get_bloom_filter(struct repository *r,
diff_tree_oid(NULL, &c->object.oid, "", &diffopt);
diffcore_std(&diffopt);
- if (diffopt.num_changes <= max_changes) {
+ if (diff_queued_diff.nr <= settings->max_changed_paths) {
struct hashmap pathmap;
struct pathmap_hash_entry *e;
struct hashmap_iter iter;
@@ -256,23 +265,41 @@ struct bloom_filter *get_bloom_filter(struct repository *r,
diff_free_filepair(diff_queued_diff.queue[i]);
}
- filter->len = (hashmap_get_size(&pathmap) * settings.bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ if (hashmap_get_size(&pathmap) > settings->max_changed_paths) {
+ init_truncated_large_filter(filter);
+ if (computed)
+ *computed |= BLOOM_TRUNC_LARGE;
+ goto cleanup;
+ }
+
+ filter->len = (hashmap_get_size(&pathmap) * settings->bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;
+ if (!filter->len) {
+ if (computed)
+ *computed |= BLOOM_TRUNC_EMPTY;
+ filter->len = 1;
+ }
filter->data = xcalloc(filter->len, sizeof(unsigned char));
hashmap_for_each_entry(&pathmap, &iter, e, entry) {
struct bloom_key key;
- fill_bloom_key(e->path, strlen(e->path), &key, &settings);
- add_key_to_filter(&key, filter, &settings);
+ fill_bloom_key(e->path, strlen(e->path), &key, settings);
+ add_key_to_filter(&key, filter, settings);
}
+ cleanup:
hashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);
} else {
for (i = 0; i < diff_queued_diff.nr; i++)
diff_free_filepair(diff_queued_diff.queue[i]);
- filter->data = NULL;
- filter->len = 0;
+ init_truncated_large_filter(filter);
+
+ if (computed)
+ *computed |= BLOOM_TRUNC_LARGE;
}
+ if (computed)
+ *computed |= BLOOM_COMPUTED;
+
free(diff_queued_diff.queue);
DIFF_QUEUE_CLEAR(&diff_queued_diff);
diff --git a/bloom.h b/bloom.h
index d8fbb0fbf1..adde6dfe21 100644
--- a/bloom.h
+++ b/bloom.h
@@ -28,9 +28,18 @@ struct bloom_filter_settings {
* that contain n*b bits.
*/
uint32_t bits_per_entry;
+
+ /*
+ * The maximum number of changed paths per commit
+ * before declaring a Bloom filter to be too-large.
+ *
+ * Not written to the commit-graph file.
+ */
+ uint32_t max_changed_paths;
};
-#define DEFAULT_BLOOM_FILTER_SETTINGS { 1, 7, 10 }
+#define DEFAULT_BLOOM_MAX_CHANGES 512
+#define DEFAULT_BLOOM_FILTER_SETTINGS { 1, 7, 10, DEFAULT_BLOOM_MAX_CHANGES }
#define BITS_PER_WORD 8
#define BLOOMDATA_CHUNK_HEADER_SIZE 3 * sizeof(uint32_t)
@@ -80,9 +89,21 @@ void add_key_to_filter(const struct bloom_key *key,
void init_bloom_filters(void);
-struct bloom_filter *get_bloom_filter(struct repository *r,
- struct commit *c,
- int compute_if_not_present);
+enum bloom_filter_computed {
+ BLOOM_NOT_COMPUTED = (1 << 0),
+ BLOOM_COMPUTED = (1 << 1),
+ BLOOM_TRUNC_LARGE = (1 << 2),
+ BLOOM_TRUNC_EMPTY = (1 << 3),
+};
+
+struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,
+ struct commit *c,
+ int compute_if_not_present,
+ const struct bloom_filter_settings *settings,
+ enum bloom_filter_computed *computed);
+
+#define get_bloom_filter(r, c) get_or_compute_bloom_filter( \
+ (r), (c), 0, NULL, NULL)
int bloom_filter_contains(const struct bloom_filter *filter,
const struct bloom_key *key,
diff --git a/builtin.h b/builtin.h
index ba954e180c..53fb290963 100644
--- a/builtin.h
+++ b/builtin.h
@@ -172,6 +172,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix);
int cmd_ls_remote(int argc, const char **argv, const char *prefix);
int cmd_mailinfo(int argc, const char **argv, const char *prefix);
int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+int cmd_maintenance(int argc, const char **argv, const char *prefix);
int cmd_merge(int argc, const char **argv, const char *prefix);
int cmd_merge_base(int argc, const char **argv, const char *prefix);
int cmd_merge_index(int argc, const char **argv, const char *prefix);
diff --git a/builtin/add.c b/builtin/add.c
index b36a99eb7c..26b6ced09e 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -192,9 +192,15 @@ int run_add_interactive(const char *revision, const char *patch_mode,
int use_builtin_add_i =
git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
- if (use_builtin_add_i < 0)
- git_config_get_bool("add.interactive.usebuiltin",
- &use_builtin_add_i);
+ if (use_builtin_add_i < 0) {
+ int experimental;
+ if (!git_config_get_bool("add.interactive.usebuiltin",
+ &use_builtin_add_i))
+ ; /* ok */
+ else if (!git_config_get_bool("feature.experimental", &experimental) &&
+ experimental)
+ use_builtin_add_i = 1;
+ }
if (use_builtin_add_i == 1) {
enum add_p_mode mode;
diff --git a/builtin/am.c b/builtin/am.c
index b5c63ddf1d..7259186408 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1813,7 +1813,7 @@ next:
if (!state->rebasing) {
am_destroy(state);
close_object_store(the_repository->objects);
- run_auto_gc(state->quiet);
+ run_auto_maintenance(state->quiet);
}
}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 7dcc1b5188..7512b880f0 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -8,6 +8,7 @@
#include "run-command.h"
#include "prompt.h"
#include "quote.h"
+#include "revision.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
@@ -29,9 +30,17 @@ static const char * const git_bisect_helper_usage[] = {
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>...]"),
+ N_("git bisect--helper --bisect-next"),
+ N_("git bisect--helper --bisect-auto-next"),
+ N_("git bisect--helper --bisect-autostart"),
NULL
};
+struct add_bisect_ref_data {
+ struct rev_info *revs;
+ unsigned int object_flags;
+};
+
struct bisect_terms {
char *term_good;
char *term_bad;
@@ -55,6 +64,8 @@ static void set_terms(struct bisect_terms *terms, const char *bad,
static const char vocab_bad[] = "bad|new";
static const char vocab_good[] = "good|old";
+static int bisect_autostart(struct bisect_terms *terms);
+
/*
* Check whether the string `term` belongs to the set of strings
* included in the variable arguments.
@@ -74,6 +85,52 @@ static int one_of(const char *term, ...)
return res;
}
+static int write_in_file(const char *path, const char *mode, const char *format, va_list args)
+{
+ FILE *fp = NULL;
+ int res = 0;
+
+ if (strcmp(mode, "w") && strcmp(mode, "a"))
+ BUG("write-in-file does not support '%s' mode", mode);
+ fp = fopen(path, mode);
+ if (!fp)
+ return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode);
+ res = vfprintf(fp, format, args);
+
+ if (res < 0) {
+ int saved_errno = errno;
+ fclose(fp);
+ errno = saved_errno;
+ return error_errno(_("could not write to file '%s'"), path);
+ }
+
+ return fclose(fp);
+}
+
+static int write_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "w", format, args);
+ va_end(args);
+
+ return res;
+}
+
+static int append_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "a", format, args);
+ va_end(args);
+
+ return res;
+}
+
static int check_term_format(const char *term, const char *orig_term)
{
int res;
@@ -104,7 +161,6 @@ static int check_term_format(const char *term, const char *orig_term)
static int write_terms(const char *bad, const char *good)
{
- FILE *fp = NULL;
int res;
if (!strcmp(bad, good))
@@ -113,13 +169,9 @@ static int write_terms(const char *bad, const char *good)
if (check_term_format(bad, "bad") || check_term_format(good, "good"))
return -1;
- fp = fopen(git_path_bisect_terms(), "w");
- if (!fp)
- return error_errno(_("could not open the file BISECT_TERMS"));
+ res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good);
- res = fprintf(fp, "%s\n%s\n", bad, good);
- res |= fclose(fp);
- return (res < 0) ? -1 : 0;
+ return res;
}
static int is_expected_rev(const char *expected_hex)
@@ -421,6 +473,142 @@ finish:
return res;
}
+static int add_bisect_ref(const char *refname, const struct object_id *oid,
+ int flags, void *cb)
+{
+ struct add_bisect_ref_data *data = cb;
+
+ add_pending_oid(data->revs, refname, oid, data->object_flags);
+
+ return 0;
+}
+
+static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
+{
+ int res = 0;
+ struct add_bisect_ref_data cb = { revs };
+ char *good = xstrfmt("%s-*", terms->term_good);
+
+ /*
+ * We cannot use terms->term_bad directly in
+ * for_each_glob_ref_in() and we have to append a '*' to it,
+ * otherwise for_each_glob_ref_in() will append '/' and '*'.
+ */
+ char *bad = xstrfmt("%s*", terms->term_bad);
+
+ /*
+ * It is important to reset the flags used by revision walks
+ * as the previous call to bisect_next_all() in turn
+ * sets up a revision walk.
+ */
+ reset_revision_walk();
+ init_revisions(revs, NULL);
+ setup_revisions(0, NULL, revs, NULL);
+ for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb);
+ cb.object_flags = UNINTERESTING;
+ for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb);
+ if (prepare_revision_walk(revs))
+ res = error(_("revision walk setup failed\n"));
+
+ free(good);
+ free(bad);
+ return res;
+}
+
+static int bisect_skipped_commits(struct bisect_terms *terms)
+{
+ int res;
+ FILE *fp = NULL;
+ struct rev_info revs;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+
+ res = prepare_revs(terms, &revs);
+ if (res)
+ return res;
+
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp)
+ return error_errno(_("could not open '%s' for appending"),
+ git_path_bisect_log());
+
+ if (fprintf(fp, "# only skipped commits left to test\n") < 0)
+ return error_errno(_("failed to write to '%s'"), git_path_bisect_log());
+
+ while ((commit = get_revision(&revs)) != NULL) {
+ strbuf_reset(&commit_name);
+ format_commit_message(commit, "%s",
+ &commit_name, &pp);
+ fprintf(fp, "# possible first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+ }
+
+ /*
+ * Reset the flags used by revision walks in case
+ * there is another revision walk after this one.
+ */
+ reset_revision_walk();
+
+ strbuf_release(&commit_name);
+ fclose(fp);
+ return 0;
+}
+
+static int bisect_successful(struct bisect_terms *terms)
+{
+ struct object_id oid;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+ char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad);
+ int res;
+
+ read_ref(bad_ref, &oid);
+ commit = lookup_commit_reference_by_name(bad_ref);
+ format_commit_message(commit, "%s", &commit_name, &pp);
+
+ res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+
+ strbuf_release(&commit_name);
+ free(bad_ref);
+ return res;
+}
+
+static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)
+{
+ enum bisect_error res;
+
+ if (bisect_autostart(terms))
+ return BISECT_FAILED;
+
+ if (bisect_next_check(terms, terms->term_good))
+ return BISECT_FAILED;
+
+ /* Perform all bisection computation */
+ res = bisect_next_all(the_repository, prefix);
+
+ if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
+ res = bisect_successful(terms);
+ return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
+ } else if (res == BISECT_ONLY_SKIPPED_LEFT) {
+ res = bisect_skipped_commits(terms);
+ return res ? res : BISECT_ONLY_SKIPPED_LEFT;
+ }
+ return res;
+}
+
+static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)
+{
+ if (bisect_next_check(terms, NULL))
+ return BISECT_OK;
+
+ return bisect_next(terms, prefix);
+}
+
static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
{
int no_checkout = 0;
@@ -484,14 +672,13 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc)
terms->term_bad = xstrdup(arg);
} else if (starts_with(arg, "--")) {
return error(_("unrecognized option: '%s'"), arg);
- } else {
- char *commit_id = xstrfmt("%s^{commit}", arg);
- if (get_oid(commit_id, &oid) && has_double_dash)
- die(_("'%s' does not appear to be a valid "
- "revision"), arg);
-
+ } else if (!get_oidf(&oid, "%s^{commit}", arg)) {
string_list_append(&revs, oid_to_hex(&oid));
- free(commit_id);
+ } else if (has_double_dash) {
+ die(_("'%s' does not appear to be a valid "
+ "revision"), arg);
+ } else {
+ break;
}
}
pathspec_pos = i;
@@ -624,6 +811,38 @@ finish:
return res;
}
+static inline int file_is_not_empty(const char *path)
+{
+ return !is_empty_or_missing_file(path);
+}
+
+static int bisect_autostart(struct bisect_terms *terms)
+{
+ int res;
+ const char *yesno;
+
+ if (file_is_not_empty(git_path_bisect_start()))
+ return 0;
+
+ fprintf_ln(stderr, _("You need to start by \"git bisect "
+ "start\"\n"));
+
+ if (!isatty(STDIN_FILENO))
+ return -1;
+
+ /*
+ * TRANSLATORS: Make sure to include [Y] and [n] in your
+ * translation. The program will only accept English input
+ * at this point.
+ */
+ yesno = git_prompt(_("Do you want me to do it for you "
+ "[Y/n]? "), PROMPT_ECHO);
+ res = tolower(*yesno) == 'n' ?
+ -1 : bisect_start(terms, empty_strvec, 0);
+
+ return res;
+}
+
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
@@ -636,7 +855,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
CHECK_AND_SET_TERMS,
BISECT_NEXT_CHECK,
BISECT_TERMS,
- BISECT_START
+ BISECT_START,
+ BISECT_AUTOSTART,
+ BISECT_NEXT,
+ BISECT_AUTO_NEXT
} cmdmode = 0;
int res = 0, nolog = 0;
struct option options[] = {
@@ -660,6 +882,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START),
+ OPT_CMDMODE(0, "bisect-next", &cmdmode,
+ N_("find the next bisection commit"), BISECT_NEXT),
+ OPT_CMDMODE(0, "bisect-auto-next", &cmdmode,
+ N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT),
+ OPT_CMDMODE(0, "bisect-autostart", &cmdmode,
+ N_("start the bisection if it has not yet been started"), BISECT_AUTOSTART),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
@@ -719,8 +947,26 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
set_terms(&terms, "bad", "good");
res = bisect_start(&terms, argv, argc);
break;
+ case BISECT_NEXT:
+ if (argc)
+ return error(_("--bisect-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_next(&terms, prefix);
+ break;
+ case BISECT_AUTO_NEXT:
+ if (argc)
+ return error(_("--bisect-auto-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_auto_next(&terms, prefix);
+ break;
+ case BISECT_AUTOSTART:
+ if (argc)
+ return error(_("--bisect-autostart does not accept arguments"));
+ set_terms(&terms, "bad", "good");
+ res = bisect_autostart(&terms);
+ break;
default:
- return error("BUG: unknown subcommand '%d'", cmdmode);
+ BUG("unknown subcommand %d", cmdmode);
}
free_terms(&terms);
@@ -728,8 +974,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
* Handle early success
* From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all
*/
- if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE)
+ if ((res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) || (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND))
res = BISECT_OK;
- return abs(res);
+ return -res;
}
diff --git a/builtin/blame.c b/builtin/blame.c
index eb513fbe60..bb0f29300e 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -27,6 +27,7 @@
#include "object-store.h"
#include "blame.h"
#include "refs.h"
+#include "tag.h"
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
@@ -803,6 +804,26 @@ static int is_a_rev(const char *name)
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
}
+static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)
+{
+ struct repository *r = ((struct blame_scoreboard *)cbdata)->repo;
+ struct object_id oid;
+
+ oidcpy(&oid, oid_ret);
+ while (1) {
+ struct object *obj;
+ int kind = oid_object_info(r, &oid, NULL);
+ if (kind == OBJ_COMMIT) {
+ oidcpy(oid_ret, &oid);
+ return 0;
+ }
+ if (kind != OBJ_TAG)
+ return -1;
+ obj = deref_tag(r, parse_object(r, &oid), NULL, 0);
+ oidcpy(&oid, &obj->oid);
+ }
+}
+
static void build_ignorelist(struct blame_scoreboard *sb,
struct string_list *ignore_revs_file_list,
struct string_list *ignore_rev_list)
@@ -815,10 +836,12 @@ static void build_ignorelist(struct blame_scoreboard *sb,
if (!strcmp(i->string, ""))
oidset_clear(&sb->ignore_list);
else
- oidset_parse_file(&sb->ignore_list, i->string);
+ oidset_parse_file_carefully(&sb->ignore_list, i->string,
+ peel_to_commit_oid, sb);
}
for_each_string_list_item(i, ignore_rev_list) {
- if (get_oid_committish(i->string, &oid))
+ if (get_oid_committish(i->string, &oid) ||
+ peel_to_commit_oid(&oid, sb))
die(_("cannot find revision %s to ignore"), i->string);
oidset_insert(&sb->ignore_list, &oid);
}
diff --git a/builtin/clean.c b/builtin/clean.c
index e53ea52d89..687ab473c2 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -162,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
is_nonbare_repository_dir(path)) {
if (!quiet) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
@@ -177,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -202,7 +202,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
ret = 1;
if (gone) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else
*dir_gone = 0;
@@ -210,11 +210,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
} else {
res = dry_run ? 0 : unlink(path->buf);
if (!res) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -238,7 +238,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
*dir_gone = 1;
else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -266,7 +266,7 @@ static void pretty_print_dels(void)
struct column_options copts;
for_each_string_list_item(item, &del_list) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
string_list_append(&list, qname);
}
@@ -753,7 +753,7 @@ static int ask_each_cmd(void)
for_each_string_list_item(item, &del_list) {
/* Ctrl-D should stop removing files */
if (!eof) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
/* TRANSLATORS: Make sure to keep [y/N] as is */
printf(_("Remove %s [y/N]? "), qname);
if (git_read_line_interactively(&confirm) == EOF) {
@@ -1047,19 +1047,19 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
errors++;
if (gone && !quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
} else {
res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
int saved_errno = errno;
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
diff --git a/builtin/clone.c b/builtin/clone.c
index b087ee40c2..391aa41075 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -953,7 +953,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
struct ref *mapped_refs;
const struct ref *ref;
struct strbuf key = STRBUF_INIT;
- struct strbuf default_refspec = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
const char *src_ref_prefix = "refs/heads/";
@@ -1157,9 +1156,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote = remote_get(option_origin);
- strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
- branch_top.buf);
- refspec_append(&remote->fetch, default_refspec.buf);
+ refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
transport = transport_get(remote, remote->url[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1235,7 +1233,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* Now that we know what algorithm the remote side is using,
* let's set ours to the same thing.
*/
- initialize_repository_version(hash_algo);
+ initialize_repository_version(hash_algo, 1);
repo_set_hash_algo(the_repository, hash_algo);
mapped_refs = wanted_peer_refs(refs, &remote->fetch);
@@ -1332,7 +1330,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
- strbuf_release(&default_refspec);
junk_mode = JUNK_LEAVE_ALL;
strvec_clear(&ref_prefixes);
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 523501f217..988445abdf 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -13,7 +13,8 @@ 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-]progress] <split options>"),
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
+ "<split options>"),
NULL
};
@@ -25,7 +26,8 @@ static const char * const builtin_commit_graph_verify_usage[] = {
static const char * const builtin_commit_graph_write_usage[] = {
N_("git commit-graph write [--object-dir <objdir>] [--append] "
"[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
- "[--changed-paths] [--[no-]progress] <split options>"),
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
+ "<split options>"),
NULL
};
@@ -106,7 +108,7 @@ static int graph_verify(int argc, const char **argv)
FREE_AND_NULL(graph_name);
if (open_ok)
- graph = load_commit_graph_one_fd_st(fd, &st, odb);
+ graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
else
graph = read_commit_graph_one(the_repository, odb);
@@ -119,7 +121,7 @@ static int graph_verify(int argc, const char **argv)
}
extern int read_replace_refs;
-static struct split_commit_graph_opts split_opts;
+static struct commit_graph_opts write_opts;
static int write_option_parse_split(const struct option *opt, const char *arg,
int unset)
@@ -162,6 +164,35 @@ static int read_one_commit(struct oidset *commits, struct progress *progress,
return 0;
}
+static int write_option_max_new_filters(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ int *to = opt->value;
+ if (unset)
+ *to = -1;
+ else {
+ const char *s;
+ *to = strtol(arg, (char **)&s, 10);
+ if (*s)
+ return error(_("%s expects a numerical value"),
+ optname(opt, opt->flags));
+ }
+ return 0;
+}
+
+static int git_commit_graph_write_config(const char *var, const char *value,
+ void *cb)
+{
+ if (!strcmp(var, "commitgraph.maxnewfilters"))
+ write_opts.max_new_filters = git_config_int(var, value);
+ /*
+ * No need to fall-back to 'git_default_config', since this was already
+ * called in 'cmd_commit_graph()'.
+ */
+ return 0;
+}
+
static int graph_write(int argc, const char **argv)
{
struct string_list pack_indexes = STRING_LIST_INIT_NODUP;
@@ -187,27 +218,33 @@ static int graph_write(int argc, const char **argv)
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", &split_opts.flags, NULL,
+ OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
N_("allow writing an incremental commit-graph file"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
write_option_parse_split),
- OPT_INTEGER(0, "max-commits", &split_opts.max_commits,
+ OPT_INTEGER(0, "max-commits", &write_opts.max_commits,
N_("maximum number of commits in a non-base split commit-graph")),
- OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple,
+ OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple,
N_("maximum ratio between two levels of a split commit-graph")),
- OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time,
+ OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time,
N_("only expire files older than a given date-time")),
+ 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_END(),
};
opts.progress = isatty(2);
opts.enable_changed_paths = -1;
- split_opts.size_multiple = 2;
- split_opts.max_commits = 0;
- split_opts.expire_time = 0;
+ write_opts.size_multiple = 2;
+ write_opts.max_commits = 0;
+ write_opts.expire_time = 0;
+ write_opts.max_new_filters = -1;
trace2_cmd_mode("write");
+ git_config(git_commit_graph_write_config, &opts);
+
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_write_options,
builtin_commit_graph_write_usage, 0);
@@ -232,7 +269,7 @@ static int graph_write(int argc, const char **argv)
odb = find_odb(the_repository, opts.obj_dir);
if (opts.reachable) {
- if (write_commit_graph_reachable(odb, flags, &split_opts))
+ if (write_commit_graph_reachable(odb, flags, &write_opts))
return 1;
return 0;
}
@@ -261,7 +298,7 @@ static int graph_write(int argc, const char **argv)
opts.stdin_packs ? &pack_indexes : NULL,
opts.stdin_commits ? &commits : NULL,
flags,
- &split_opts))
+ &write_opts))
result = 1;
cleanup:
diff --git a/builtin/commit.c b/builtin/commit.c
index 5d91b13a5c..42b964e0ca 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1700,7 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
git_test_write_commit_graph_or_die();
repo_rerere(the_repository, 0);
- run_auto_gc(quiet);
+ run_auto_maintenance(quiet);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
commit_post_rewrite(the_repository, current_head, &oid);
diff --git a/builtin/config.c b/builtin/config.c
index 5e39f61885..963d65fd3f 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -65,6 +65,7 @@ static int show_scope;
#define TYPE_PATH 4
#define TYPE_EXPIRY_DATE 5
#define TYPE_COLOR 6
+#define TYPE_BOOL_OR_STR 7
#define OPT_CALLBACK_VALUE(s, l, v, h, i) \
{ OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \
@@ -94,6 +95,8 @@ static int option_parse_type(const struct option *opt, const char *arg,
new_type = TYPE_INT;
else if (!strcmp(arg, "bool-or-int"))
new_type = TYPE_BOOL_OR_INT;
+ else if (!strcmp(arg, "bool-or-str"))
+ new_type = TYPE_BOOL_OR_STR;
else if (!strcmp(arg, "path"))
new_type = TYPE_PATH;
else if (!strcmp(arg, "expiry-date"))
@@ -149,6 +152,7 @@ static struct option builtin_config_options[] = {
OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT),
OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
+ OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR),
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
OPT_GROUP(N_("Other")),
@@ -250,6 +254,12 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
strbuf_addstr(buf, v ? "true" : "false");
else
strbuf_addf(buf, "%d", v);
+ } else if (type == TYPE_BOOL_OR_STR) {
+ int v = git_parse_maybe_bool(value_);
+ if (v < 0)
+ strbuf_addstr(buf, value_);
+ else
+ strbuf_addstr(buf, v ? "true" : "false");
} else if (type == TYPE_PATH) {
const char *v;
if (git_config_pathname(&v, key_, value_) < 0)
@@ -411,6 +421,13 @@ static char *normalize_value(const char *key, const char *value)
else
return xstrdup(v ? "true" : "false");
}
+ if (type == TYPE_BOOL_OR_STR) {
+ int v = git_parse_maybe_bool(value);
+ if (v < 0)
+ return xstrdup(value);
+ else
+ return xstrdup(v ? "true" : "false");
+ }
if (type == TYPE_COLOR) {
char v[COLOR_MAXLEN];
if (git_config_color(v, key, value))
@@ -628,11 +645,15 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
- if (use_local_config && nongit)
- die(_("--local can only be used inside a git repository"));
+ if (nongit) {
+ if (use_local_config)
+ die(_("--local can only be used inside a git repository"));
+ if (given_config_source.blob)
+ die(_("--blob can only be used inside a git repository"));
+ if (use_worktree_config)
+ die(_("--worktree can only be used inside a git repository"));
- if (given_config_source.blob && nongit)
- die(_("--blob can only be used inside a git repository"));
+ }
if (given_config_source.file &&
!strcmp(given_config_source.file, "-")) {
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 1b8fca3ee0..e266d90a81 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -405,12 +405,12 @@ static char *generate_fake_oid(void *data)
{
static uint32_t counter = 1; /* avoid null oid */
const unsigned hashsz = the_hash_algo->rawsz;
- unsigned char out[GIT_MAX_RAWSZ];
+ struct object_id oid;
char *hex = xmallocz(GIT_MAX_HEXSZ);
- hashclr(out);
- put_be32(out + hashsz - 4, counter++);
- return hash_to_hex_algop_r(hex, out, the_hash_algo);
+ oidclr(&oid);
+ put_be32(oid.hash + hashsz - 4, counter++);
+ return oid_to_hex_r(hex, &oid);
}
static const char *anonymize_oid(const char *oid_hex)
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index 1c85eafe43..1bf50a73dc 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -739,7 +739,6 @@ static void start_packfile(void)
{
struct strbuf tmp_file = STRBUF_INIT;
struct packed_git *p;
- struct pack_header hdr;
int pack_fd;
pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX");
@@ -750,13 +749,8 @@ static void start_packfile(void)
p->do_not_close = 1;
pack_file = hashfd(pack_fd, p->pack_name);
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(2);
- hdr.hdr_entries = 0;
- hashwrite(pack_file, &hdr, sizeof(hdr));
-
pack_data = p;
- pack_size = sizeof(hdr);
+ pack_size = write_pack_header(pack_file, 0);
object_count = 0;
REALLOC_ARRAY(all_packs, pack_id + 1);
diff --git a/builtin/fetch.c b/builtin/fetch.c
index a6d3268661..d5bf526277 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -200,8 +200,10 @@ static struct option builtin_fetch_options[] = {
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
+ N_("run 'maintenance --auto' after fetching")),
OPT_BOOL(0, "auto-gc", &enable_auto_gc,
- N_("run 'gc --auto' after fetching")),
+ N_("run 'maintenance --auto' after fetching")),
OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates,
N_("check for forced-updates on all updated branches")),
OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph,
@@ -966,8 +968,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
ref->force = rm->peer_ref->force;
}
- if (recurse_submodules != RECURSE_SUBMODULES_OFF)
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF &&
+ (!rm->peer_ref || !oideq(&ref->old_oid, &ref->new_oid))) {
check_for_new_submodule_commits(&rm->old_oid);
+ }
if (!strcmp(rm->name, "HEAD")) {
kind = "";
@@ -1550,7 +1554,10 @@ static void add_options_to_argv(struct strvec *argv)
strvec_push(argv, "-v");
else if (verbosity < 0)
strvec_push(argv, "-q");
-
+ if (family == TRANSPORT_FAMILY_IPV4)
+ strvec_push(argv, "--ipv4");
+ else if (family == TRANSPORT_FAMILY_IPV6)
+ strvec_push(argv, "--ipv6");
}
/* Fetch multiple remotes in parallel */
@@ -1738,15 +1745,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
- char *tag;
i++;
if (i >= argc)
die(_("You need to specify a tag name."));
- tag = xstrfmt("refs/tags/%s:refs/tags/%s",
- argv[i], argv[i]);
- refspec_append(&rs, tag);
- free(tag);
+ refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
+ argv[i], argv[i]);
} else {
refspec_append(&rs, argv[i]);
}
@@ -1923,7 +1927,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
close_object_store(the_repository->objects);
if (enable_auto_gc)
- run_auto_gc(verbosity < 0);
+ run_auto_maintenance(verbosity < 0);
return result;
}
diff --git a/builtin/gc.c b/builtin/gc.c
index aafa0946f5..090959350e 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -28,6 +28,7 @@
#include "blob.h"
#include "tree.h"
#include "promisor-remote.h"
+#include "refs.h"
#define FAILED_RUN "failed to run %s"
@@ -699,3 +700,339 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return 0;
}
+
+static const char * const builtin_maintenance_run_usage[] = {
+ N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>]"),
+ NULL
+};
+
+struct maintenance_run_opts {
+ int auto_flag;
+ int quiet;
+};
+
+/* Remember to update object flag allocation in object.h */
+#define SEEN (1u<<0)
+
+struct cg_auto_data {
+ int num_not_in_graph;
+ int limit;
+};
+
+static int dfs_on_ref(const char *refname,
+ const struct object_id *oid, int flags,
+ void *cb_data)
+{
+ struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
+ int result = 0;
+ struct object_id peeled;
+ struct commit_list *stack = NULL;
+ struct commit *commit;
+
+ if (!peel_ref(refname, &peeled))
+ oid = &peeled;
+ if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
+ return 0;
+
+ commit = lookup_commit(the_repository, oid);
+ if (!commit)
+ return 0;
+ if (parse_commit(commit))
+ return 0;
+
+ commit_list_append(commit, &stack);
+
+ while (!result && stack) {
+ struct commit_list *parent;
+
+ commit = pop_commit(&stack);
+
+ for (parent = commit->parents; parent; parent = parent->next) {
+ if (parse_commit(parent->item) ||
+ commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
+ parent->item->object.flags & SEEN)
+ continue;
+
+ parent->item->object.flags |= SEEN;
+ data->num_not_in_graph++;
+
+ if (data->num_not_in_graph >= data->limit) {
+ result = 1;
+ break;
+ }
+
+ commit_list_append(parent->item, &stack);
+ }
+ }
+
+ free_commit_list(stack);
+ return result;
+}
+
+static int should_write_commit_graph(void)
+{
+ int result;
+ struct cg_auto_data data;
+
+ data.num_not_in_graph = 0;
+ data.limit = 100;
+ git_config_get_int("maintenance.commit-graph.auto",
+ &data.limit);
+
+ if (!data.limit)
+ return 0;
+ if (data.limit < 0)
+ return 1;
+
+ result = for_each_ref(dfs_on_ref, &data);
+
+ clear_commit_marks_all(SEEN);
+
+ return result;
+}
+
+static int run_write_commit_graph(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "commit-graph", "write",
+ "--split", "--reachable", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ return !!run_command(&child);
+}
+
+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
+{
+ close_object_store(the_repository->objects);
+ if (run_write_commit_graph(opts)) {
+ error(_("failed to write commit-graph"));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int maintenance_task_gc(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_push(&child.args, "gc");
+
+ if (opts->auto_flag)
+ strvec_push(&child.args, "--auto");
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+ else
+ strvec_push(&child.args, "--no-quiet");
+
+ close_object_store(the_repository->objects);
+ return run_command(&child);
+}
+
+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);
+
+/*
+ * An auto condition function returns 1 if the task should run
+ * and 0 if the task should NOT run. See needs_to_gc() for an
+ * example.
+ */
+typedef int maintenance_auto_fn(void);
+
+struct maintenance_task {
+ const char *name;
+ maintenance_task_fn *fn;
+ maintenance_auto_fn *auto_condition;
+ unsigned enabled:1;
+
+ /* -1 if not selected. */
+ int selected_order;
+};
+
+enum maintenance_task_label {
+ TASK_GC,
+ TASK_COMMIT_GRAPH,
+
+ /* Leave as final value */
+ TASK__COUNT
+};
+
+static struct maintenance_task tasks[] = {
+ [TASK_GC] = {
+ "gc",
+ maintenance_task_gc,
+ need_to_gc,
+ 1,
+ },
+ [TASK_COMMIT_GRAPH] = {
+ "commit-graph",
+ maintenance_task_commit_graph,
+ should_write_commit_graph,
+ },
+};
+
+static int compare_tasks_by_selection(const void *a_, const void *b_)
+{
+ const struct maintenance_task *a, *b;
+
+ a = (const struct maintenance_task *)&a_;
+ b = (const struct maintenance_task *)&b_;
+
+ return b->selected_order - a->selected_order;
+}
+
+static int maintenance_run_tasks(struct maintenance_run_opts *opts)
+{
+ int i, found_selected = 0;
+ int result = 0;
+ struct lock_file lk;
+ struct repository *r = the_repository;
+ char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ /*
+ * Another maintenance command is running.
+ *
+ * If --auto was provided, then it is likely due to a
+ * recursive process stack. Do not report an error in
+ * that case.
+ */
+ if (!opts->auto_flag && !opts->quiet)
+ warning(_("lock file '%s' exists, skipping maintenance"),
+ lock_path);
+ free(lock_path);
+ return 0;
+ }
+ free(lock_path);
+
+ for (i = 0; !found_selected && i < TASK__COUNT; i++)
+ found_selected = tasks[i].selected_order >= 0;
+
+ if (found_selected)
+ QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ if (found_selected && tasks[i].selected_order < 0)
+ continue;
+
+ if (!found_selected && !tasks[i].enabled)
+ continue;
+
+ if (opts->auto_flag &&
+ (!tasks[i].auto_condition ||
+ !tasks[i].auto_condition()))
+ continue;
+
+ trace2_region_enter("maintenance", tasks[i].name, r);
+ if (tasks[i].fn(opts)) {
+ error(_("task '%s' failed"), tasks[i].name);
+ result = 1;
+ }
+ trace2_region_leave("maintenance", tasks[i].name, r);
+ }
+
+ rollback_lock_file(&lk);
+ return result;
+}
+
+static void initialize_task_config(void)
+{
+ int i;
+ struct strbuf config_name = STRBUF_INIT;
+ gc_config();
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ int config_value;
+
+ strbuf_setlen(&config_name, 0);
+ strbuf_addf(&config_name, "maintenance.%s.enabled",
+ tasks[i].name);
+
+ if (!git_config_get_bool(config_name.buf, &config_value))
+ tasks[i].enabled = config_value;
+ }
+
+ strbuf_release(&config_name);
+}
+
+static int task_option_parse(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i, num_selected = 0;
+ struct maintenance_task *task = NULL;
+
+ BUG_ON_OPT_NEG(unset);
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ if (tasks[i].selected_order >= 0)
+ num_selected++;
+ if (!strcasecmp(tasks[i].name, arg)) {
+ task = &tasks[i];
+ }
+ }
+
+ if (!task) {
+ error(_("'%s' is not a valid task"), arg);
+ return 1;
+ }
+
+ if (task->selected_order >= 0) {
+ error(_("task '%s' cannot be selected multiple times"), arg);
+ return 1;
+ }
+
+ task->selected_order = num_selected + 1;
+
+ return 0;
+}
+
+static int maintenance_run(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct maintenance_run_opts opts;
+ struct option builtin_maintenance_run_options[] = {
+ OPT_BOOL(0, "auto", &opts.auto_flag,
+ N_("run tasks based on the state of the repository")),
+ OPT_BOOL(0, "quiet", &opts.quiet,
+ N_("do not report progress or other information over stderr")),
+ OPT_CALLBACK_F(0, "task", NULL, N_("task"),
+ N_("run a specific task"),
+ PARSE_OPT_NONEG, task_option_parse),
+ OPT_END()
+ };
+ memset(&opts, 0, sizeof(opts));
+
+ opts.quiet = !isatty(2);
+ initialize_task_config();
+
+ for (i = 0; i < TASK__COUNT; i++)
+ tasks[i].selected_order = -1;
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_maintenance_run_options,
+ builtin_maintenance_run_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc != 0)
+ usage_with_options(builtin_maintenance_run_usage,
+ builtin_maintenance_run_options);
+ return maintenance_run_tasks(&opts);
+}
+
+static const char builtin_maintenance_usage[] = N_("git maintenance run [<options>]");
+
+int cmd_maintenance(int argc, const char **argv, const char *prefix)
+{
+ if (argc < 2 ||
+ (argc == 2 && !strcmp(argv[1], "-h")))
+ usage(builtin_maintenance_usage);
+
+ if (!strcmp(argv[1], "run"))
+ return maintenance_run(argc - 1, argv + 1, prefix);
+
+ die(_("invalid subcommand: %s"), argv[1]);
+}
diff --git a/builtin/grep.c b/builtin/grep.c
index f58979bc3f..c8037388c6 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -319,7 +319,7 @@ static void grep_source_name(struct grep_opt *opt, const char *filename,
}
if (opt->relative && opt->prefix_length)
- quote_path_relative(filename + tree_name_len, opt->prefix, out);
+ quote_path(filename + tree_name_len, opt->prefix, out, 0);
else
quote_c_style(filename + tree_name_len, out, NULL, 0);
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 9721bf1ffe..8acd078aa0 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -33,19 +33,61 @@ struct object_stat {
};
struct base_data {
+ /* Initialized by make_base(). */
struct base_data *base;
- struct base_data *child;
struct object_entry *obj;
- void *data;
- unsigned long size;
int ref_first, ref_last;
int ofs_first, ofs_last;
+ /*
+ * Threads should increment retain_data if they are about to call
+ * patch_delta() using this struct's data as a base, and decrement this
+ * when they are done. While retain_data is nonzero, this struct's data
+ * will not be freed even if the delta base cache limit is exceeded.
+ */
+ int retain_data;
+ /*
+ * The number of direct children that have not been fully processed
+ * (entered work_head, entered done_head, left done_head). When this
+ * number reaches zero, this struct base_data can be freed.
+ */
+ int children_remaining;
+
+ /* Not initialized by make_base(). */
+ struct list_head list;
+ void *data;
+ unsigned long size;
};
+/*
+ * Stack of struct base_data that have unprocessed children.
+ * threaded_second_pass() uses this as a source of work (the other being the
+ * objects array).
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(work_head);
+
+/*
+ * Stack of struct base_data that have children, all of whom have been
+ * processed or are being processed, and at least one child is being processed.
+ * These struct base_data must be kept around until the last child is
+ * processed.
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(done_head);
+
+/*
+ * All threads share one delta base cache.
+ *
+ * base_cache_used is guarded by work_mutex, and base_cache_limit is read-only
+ * in a thread.
+ */
+static size_t base_cache_used;
+static size_t base_cache_limit;
+
struct thread_local {
pthread_t thread;
- struct base_data *base_cache;
- size_t base_cache_used;
int pack_fd;
};
@@ -364,56 +406,42 @@ static void set_thread_data(struct thread_local *data)
pthread_setspecific(key, data);
}
-static struct base_data *alloc_base_data(void)
-{
- struct base_data *base = xcalloc(1, sizeof(struct base_data));
- base->ref_last = -1;
- base->ofs_last = -1;
- return base;
-}
-
static void free_base_data(struct base_data *c)
{
if (c->data) {
FREE_AND_NULL(c->data);
- get_thread_data()->base_cache_used -= c->size;
+ base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
- struct base_data *b;
- struct thread_local *data = get_thread_data();
- for (b = data->base_cache;
- data->base_cache_used > delta_base_cache_limit && b;
- b = b->child) {
- if (b->data && b != retain)
- free_base_data(b);
- }
-}
+ struct list_head *pos;
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
- if (base)
- base->child = c;
- else
- get_thread_data()->base_cache = c;
+ if (base_cache_used <= base_cache_limit)
+ return;
- c->base = base;
- c->child = NULL;
- if (c->data)
- get_thread_data()->base_cache_used += c->size;
- prune_base_data(c);
-}
+ list_for_each_prev(pos, &done_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
-static void unlink_base_data(struct base_data *c)
-{
- struct base_data *base = c->base;
- if (base)
- base->child = NULL;
- else
- get_thread_data()->base_cache = NULL;
- free_base_data(c);
+ list_for_each_prev(pos, &work_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
}
static int is_delta_type(enum object_type type)
@@ -614,7 +642,7 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
0;
}
-static int find_ofs_delta(const off_t offset, enum object_type type)
+static int find_ofs_delta(const off_t offset)
{
int first = 0, last = nr_ofs_deltas;
@@ -624,7 +652,8 @@ static int find_ofs_delta(const off_t offset, enum object_type type)
int cmp;
cmp = compare_ofs_delta_bases(offset, delta->offset,
- type, objects[delta->obj_no].type);
+ OBJ_OFS_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -637,10 +666,9 @@ static int find_ofs_delta(const off_t offset, enum object_type type)
}
static void find_ofs_delta_children(off_t offset,
- int *first_index, int *last_index,
- enum object_type type)
+ int *first_index, int *last_index)
{
- int first = find_ofs_delta(offset, type);
+ int first = find_ofs_delta(offset);
int last = first;
int end = nr_ofs_deltas - 1;
@@ -668,7 +696,7 @@ static int compare_ref_delta_bases(const struct object_id *oid1,
return oidcmp(oid1, oid2);
}
-static int find_ref_delta(const struct object_id *oid, enum object_type type)
+static int find_ref_delta(const struct object_id *oid)
{
int first = 0, last = nr_ref_deltas;
@@ -678,7 +706,8 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
int cmp;
cmp = compare_ref_delta_bases(oid, &delta->oid,
- type, objects[delta->obj_no].type);
+ OBJ_REF_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -691,10 +720,9 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
}
static void find_ref_delta_children(const struct object_id *oid,
- int *first_index, int *last_index,
- enum object_type type)
+ int *first_index, int *last_index)
{
- int first = find_ref_delta(oid, type);
+ int first = find_ref_delta(oid);
int last = first;
int end = nr_ref_deltas - 1;
@@ -866,15 +894,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
}
/*
- * This function is part of find_unresolved_deltas(). There are two
- * walkers going in the opposite ways.
- *
- * The first one in find_unresolved_deltas() traverses down from
- * parent node to children, deflating nodes along the way. However,
- * memory for deflated nodes is limited by delta_base_cache_limit, so
- * at some point parent node's deflated content may be freed.
- *
- * The second walker is this function, which goes from current node up
+ * Walk from current node up
* to top parent if necessary to deflate the node. In normal
* situation, its parent node would be already deflated, so it just
* needs to apply delta.
@@ -902,7 +922,7 @@ static void *get_base_data(struct base_data *c)
if (!delta_nr) {
c->data = get_data_from_pack(obj);
c->size = obj->size;
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
for (; delta_nr > 0; delta_nr--) {
@@ -918,7 +938,7 @@ static void *get_base_data(struct base_data *c)
free(raw);
if (!c->data)
bad_object(obj->idx.offset, _("failed to apply delta"));
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
free(delta);
@@ -926,10 +946,27 @@ static void *get_base_data(struct base_data *c)
return c->data;
}
-static void resolve_delta(struct object_entry *delta_obj,
- struct base_data *base, struct base_data *result)
+static struct base_data *make_base(struct object_entry *obj,
+ struct base_data *parent)
+{
+ struct base_data *base = xcalloc(1, sizeof(struct base_data));
+ base->base = parent;
+ base->obj = obj;
+ find_ref_delta_children(&obj->idx.oid,
+ &base->ref_first, &base->ref_last);
+ find_ofs_delta_children(obj->idx.offset,
+ &base->ofs_first, &base->ofs_last);
+ base->children_remaining = base->ref_last - base->ref_first +
+ base->ofs_last - base->ofs_first + 2;
+ return base;
+}
+
+static struct base_data *resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base)
{
- void *base_data, *delta_data;
+ void *delta_data, *result_data;
+ struct base_data *result;
+ unsigned long result_size;
if (show_stat) {
int i = delta_obj - objects;
@@ -942,115 +979,26 @@ static void resolve_delta(struct object_entry *delta_obj,
obj_stat[i].base_object_no = j;
}
delta_data = get_data_from_pack(delta_obj);
- base_data = get_base_data(base);
- result->obj = delta_obj;
- result->data = patch_delta(base_data, base->size,
- delta_data, delta_obj->size, &result->size);
+ assert(base->data);
+ result_data = patch_delta(base->data, base->size,
+ delta_data, delta_obj->size, &result_size);
free(delta_data);
- if (!result->data)
+ if (!result_data)
bad_object(delta_obj->idx.offset, _("failed to apply delta"));
- hash_object_file(the_hash_algo, result->data, result->size,
+ hash_object_file(the_hash_algo, result_data, result_size,
type_name(delta_obj->real_type), &delta_obj->idx.oid);
- sha1_object(result->data, NULL, result->size, delta_obj->real_type,
+ sha1_object(result_data, NULL, result_size, delta_obj->real_type,
&delta_obj->idx.oid);
+
+ result = make_base(delta_obj, base);
+ result->data = result_data;
+ result->size = result_size;
+
counter_lock();
nr_resolved_deltas++;
counter_unlock();
-}
-
-/*
- * Standard boolean compare-and-swap: atomically check whether "*type" is
- * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
- * and return false.
- */
-static int compare_and_swap_type(signed char *type,
- enum object_type want,
- enum object_type set)
-{
- enum object_type old;
-
- type_cas_lock();
- old = *type;
- if (old == want)
- *type = set;
- type_cas_unlock();
- return old == want;
-}
-
-static struct base_data *find_unresolved_deltas_1(struct base_data *base,
- struct base_data *prev_base)
-{
- if (base->ref_last == -1 && base->ofs_last == -1) {
- find_ref_delta_children(&base->obj->idx.oid,
- &base->ref_first, &base->ref_last,
- OBJ_REF_DELTA);
-
- find_ofs_delta_children(base->obj->idx.offset,
- &base->ofs_first, &base->ofs_last,
- OBJ_OFS_DELTA);
-
- if (base->ref_last == -1 && base->ofs_last == -1) {
- free(base->data);
- return NULL;
- }
-
- link_base_data(prev_base, base);
- }
-
- if (base->ref_first <= base->ref_last) {
- struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
- struct base_data *result = alloc_base_data();
-
- if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
- base->obj->real_type))
- die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
- (uintmax_t)child->idx.offset,
- oid_to_hex(&base->obj->idx.oid));
-
- resolve_delta(child, base, result);
- if (base->ref_first == base->ref_last && base->ofs_last == -1)
- free_base_data(base);
-
- base->ref_first++;
- return result;
- }
-
- if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
- struct base_data *result = alloc_base_data();
-
- assert(child->real_type == OBJ_OFS_DELTA);
- child->real_type = base->obj->real_type;
- resolve_delta(child, base, result);
- if (base->ofs_first == base->ofs_last)
- free_base_data(base);
-
- base->ofs_first++;
- return result;
- }
-
- unlink_base_data(base);
- return NULL;
-}
-
-static void find_unresolved_deltas(struct base_data *base)
-{
- struct base_data *new_base, *prev_base = NULL;
- for (;;) {
- new_base = find_unresolved_deltas_1(base, prev_base);
-
- if (new_base) {
- prev_base = base;
- base = new_base;
- } else {
- free(base);
- base = prev_base;
- if (!base)
- return;
- prev_base = base->base;
- }
- }
+ return result;
}
static int compare_ofs_delta_entry(const void *a, const void *b)
@@ -1071,34 +1019,131 @@ static int compare_ref_delta_entry(const void *a, const void *b)
return oidcmp(&delta_a->oid, &delta_b->oid);
}
-static void resolve_base(struct object_entry *obj)
-{
- struct base_data *base_obj = alloc_base_data();
- base_obj->obj = obj;
- base_obj->data = NULL;
- find_unresolved_deltas(base_obj);
-}
-
static void *threaded_second_pass(void *data)
{
- set_thread_data(data);
+ if (data)
+ set_thread_data(data);
for (;;) {
- int i;
- counter_lock();
- display_progress(progress, nr_resolved_deltas);
- counter_unlock();
+ struct base_data *parent = NULL;
+ struct object_entry *child_obj;
+ struct base_data *child;
+
work_lock();
- while (nr_dispatched < nr_objects &&
- is_delta_type(objects[nr_dispatched].type))
- nr_dispatched++;
- if (nr_dispatched >= nr_objects) {
- work_unlock();
- break;
+ if (list_empty(&work_head)) {
+ /*
+ * Take an object from the object array.
+ */
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ child_obj = &objects[nr_dispatched++];
+ } else {
+ /*
+ * Peek at the top of the stack, and take a child from
+ * it.
+ */
+ parent = list_first_entry(&work_head, struct base_data,
+ list);
+
+ if (parent->ref_first <= parent->ref_last) {
+ int offset = ref_deltas[parent->ref_first++].obj_no;
+ child_obj = objects + offset;
+ if (child_obj->real_type != OBJ_REF_DELTA)
+ die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
+ (uintmax_t) child_obj->idx.offset,
+ oid_to_hex(&parent->obj->idx.oid));
+ child_obj->real_type = parent->obj->real_type;
+ } else {
+ child_obj = objects +
+ ofs_deltas[parent->ofs_first++].obj_no;
+ assert(child_obj->real_type == OBJ_OFS_DELTA);
+ child_obj->real_type = parent->obj->real_type;
+ }
+
+ if (parent->ref_first > parent->ref_last &&
+ parent->ofs_first > parent->ofs_last) {
+ /*
+ * This parent has run out of children, so move
+ * it to done_head.
+ */
+ list_del(&parent->list);
+ list_add(&parent->list, &done_head);
+ }
+
+ /*
+ * Ensure that the parent has data, since we will need
+ * it later.
+ *
+ * NEEDSWORK: If parent data needs to be reloaded, this
+ * prolongs the time that the current thread spends in
+ * the mutex. A mitigating factor is that parent data
+ * needs to be reloaded only if the delta base cache
+ * limit is exceeded, so in the typical case, this does
+ * not happen.
+ */
+ get_base_data(parent);
+ parent->retain_data++;
}
- i = nr_dispatched++;
work_unlock();
- resolve_base(&objects[i]);
+ if (parent) {
+ child = resolve_delta(child_obj, parent);
+ if (!child->children_remaining)
+ FREE_AND_NULL(child->data);
+ } else {
+ child = make_base(child_obj, NULL);
+ if (child->children_remaining) {
+ /*
+ * Since this child has its own delta children,
+ * we will need this data in the future.
+ * Inflate now so that future iterations will
+ * have access to this object's data while
+ * outside the work mutex.
+ */
+ child->data = get_data_from_pack(child_obj);
+ child->size = child_obj->size;
+ }
+ }
+
+ work_lock();
+ if (parent)
+ parent->retain_data--;
+ if (child->data) {
+ /*
+ * This child has its own children, so add it to
+ * work_head.
+ */
+ list_add(&child->list, &work_head);
+ base_cache_used += child->size;
+ prune_base_data(NULL);
+ } else {
+ /*
+ * This child does not have its own children. It may be
+ * the last descendant of its ancestors; free those
+ * that we can.
+ */
+ struct base_data *p = parent;
+
+ while (p) {
+ struct base_data *next_p;
+
+ p->children_remaining--;
+ if (p->children_remaining)
+ break;
+
+ next_p = p->base;
+ free_base_data(p);
+ list_del(&p->list);
+ free(p);
+
+ p = next_p;
+ }
+ }
+ work_unlock();
}
return NULL;
}
@@ -1199,6 +1244,7 @@ static void resolve_deltas(void)
nr_ref_deltas + nr_ofs_deltas);
nr_dispatched = 0;
+ base_cache_limit = delta_base_cache_limit * nr_threads;
if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
init_thread();
for (i = 0; i < nr_threads; i++) {
@@ -1213,15 +1259,7 @@ static void resolve_deltas(void)
cleanup_thread();
return;
}
-
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
-
- if (is_delta_type(obj->type))
- continue;
- resolve_base(obj);
- display_progress(progress, nr_resolved_deltas);
- }
+ threaded_second_pass(&nothread_data);
}
/*
@@ -1376,22 +1414,28 @@ static void fix_unresolved_deltas(struct hashfile *f)
for (i = 0; i < nr_ref_deltas; i++) {
struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
- struct base_data *base_obj = alloc_base_data();
+ void *data;
+ unsigned long size;
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_object_file(&d->oid, &type,
- &base_obj->size);
- if (!base_obj->data)
+ data = read_object_file(&d->oid, &type, &size);
+ if (!data)
continue;
if (check_object_signature(the_repository, &d->oid,
- base_obj->data, base_obj->size,
+ data, size,
type_name(type)))
die(_("local object %s is corrupt"), oid_to_hex(&d->oid));
- base_obj->obj = append_obj_to_pack(f, d->oid.hash,
- base_obj->data, base_obj->size, type);
- find_unresolved_deltas(base_obj);
+
+ /*
+ * Add this as an object to the objects array and call
+ * threaded_second_pass() (which will pick up the added
+ * object).
+ */
+ append_obj_to_pack(f, d->oid.hash, data, size, type);
+ threaded_second_pass(NULL);
+
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index cd3e760541..01bc648d41 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -179,7 +179,7 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
return 1;
}
-void initialize_repository_version(int hash_algo)
+void initialize_repository_version(int hash_algo, int reinit)
{
char repo_version_string[10];
int repo_version = GIT_REPO_VERSION;
@@ -195,6 +195,8 @@ void initialize_repository_version(int hash_algo)
if (hash_algo != GIT_HASH_SHA1)
git_config_set("extensions.objectformat",
hash_algos[hash_algo].name);
+ else if (reinit)
+ git_config_set_gently("extensions.objectformat", NULL);
}
static int create_default_files(const char *template_path,
@@ -277,7 +279,7 @@ static int create_default_files(const char *template_path,
free(ref);
}
- initialize_repository_version(fmt->hash_algo);
+ initialize_repository_version(fmt->hash_algo, 0);
/* Check filemode trustability */
path = git_path_buf(&buf, "config");
diff --git a/builtin/log.c b/builtin/log.c
index 4ec7f57cf4..b8824d898f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -33,7 +33,6 @@
#include "commit-slab.h"
#include "repository.h"
#include "commit-reach.h"
-#include "interdiff.h"
#include "range-diff.h"
#define MAIL_DEFAULT_WRAP 72
@@ -1207,7 +1206,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
if (rev->idiff_oid1) {
fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title);
- show_interdiff(rev, 0);
+ show_interdiff(rev->idiff_oid1, rev->idiff_oid2, 0,
+ &rev->diffopt);
}
if (rev->rdiff1) {
@@ -1595,16 +1595,20 @@ static void infer_range_diff_ranges(struct strbuf *r1,
struct commit *head)
{
const char *head_oid = oid_to_hex(&head->object.oid);
+ int prev_is_range = !!strstr(prev, "..");
- if (!strstr(prev, "..")) {
+ if (prev_is_range)
+ strbuf_addstr(r1, prev);
+ else
strbuf_addf(r1, "%s..%s", head_oid, prev);
+
+ if (origin)
+ strbuf_addf(r2, "%s..%s", oid_to_hex(&origin->object.oid), head_oid);
+ else if (prev_is_range)
+ die(_("failed to infer range-diff origin of current series"));
+ else {
+ warning(_("using '%s' as range-diff origin of current series"), prev);
strbuf_addf(r2, "%s..%s", prev, head_oid);
- } else if (!origin) {
- die(_("failed to infer range-diff ranges"));
- } else {
- strbuf_addstr(r1, prev);
- strbuf_addf(r2, "%s..%s",
- oid_to_hex(&origin->object.oid), head_oid);
}
}
diff --git a/builtin/merge.c b/builtin/merge.c
index 032a8f5434..9d5359edc2 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -456,7 +456,7 @@ static void finish(struct commit *head_commit,
* user should see them.
*/
close_object_store(the_repository->objects);
- run_auto_gc(verbosity < 0);
+ run_auto_maintenance(verbosity < 0);
}
}
if (new_head && show_diffstat) {
diff --git a/builtin/pull.c b/builtin/pull.c
index 015f6ded0b..425950f469 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -344,8 +344,7 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
- if (opt_verbosity >= 0 &&
- (!opt_ff || strcmp(opt_ff, "--ff-only"))) {
+ if (opt_verbosity >= 0 && !opt_ff) {
warning(_("Pulling without specifying how to reconcile divergent branches is\n"
"discouraged. You can squelch this message by running one of the following\n"
"commands sometime before your next pull:\n"
diff --git a/builtin/push.c b/builtin/push.c
index bc94078e72..0eeb2c8dd5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -61,26 +61,27 @@ static struct refspec rs = REFSPEC_INIT_PUSH;
static struct string_list push_options_config = STRING_LIST_INIT_DUP;
-static const char *map_refspec(const char *ref,
- struct remote *remote, struct ref *local_refs)
+static void refspec_append_mapped(struct refspec *refspec, const char *ref,
+ struct remote *remote, struct ref *local_refs)
{
const char *branch_name;
struct ref *matched = NULL;
/* Does "ref" uniquely name our ref? */
- if (count_refspec_match(ref, local_refs, &matched) != 1)
- return ref;
+ if (count_refspec_match(ref, local_refs, &matched) != 1) {
+ refspec_append(refspec, ref);
+ return;
+ }
if (remote->push.nr) {
struct refspec_item query;
memset(&query, 0, sizeof(struct refspec_item));
query.src = matched->name;
if (!query_refspecs(&remote->push, &query) && query.dst) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s%s:%s",
- query.force ? "+" : "",
- query.src, query.dst);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s%s:%s",
+ query.force ? "+" : "",
+ query.src, query.dst);
+ return;
}
}
@@ -88,14 +89,13 @@ static const char *map_refspec(const char *ref,
skip_prefix(matched->name, "refs/heads/", &branch_name)) {
struct branch *branch = branch_get(branch_name);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s:%s",
- ref, branch->merge[0]->src);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s:%s",
+ ref, branch->merge[0]->src);
+ return;
}
}
- return ref;
+ refspec_append(refspec, ref);
}
static void set_refspecs(const char **refs, int nr, const char *repo)
@@ -107,30 +107,26 @@ static void set_refspecs(const char **refs, int nr, const char *repo)
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
- struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
ref = refs[i];
if (deleterefs)
- strbuf_addf(&tagref, ":refs/tags/%s", ref);
+ refspec_appendf(&rs, ":refs/tags/%s", ref);
else
- strbuf_addf(&tagref, "refs/tags/%s", ref);
- ref = strbuf_detach(&tagref, NULL);
+ refspec_appendf(&rs, "refs/tags/%s", ref);
} else if (deleterefs) {
- struct strbuf delref = STRBUF_INIT;
if (strchr(ref, ':'))
die(_("--delete only accepts plain target ref names"));
- strbuf_addf(&delref, ":%s", ref);
- ref = strbuf_detach(&delref, NULL);
+ refspec_appendf(&rs, ":%s", ref);
} else if (!strchr(ref, ':')) {
if (!remote) {
/* lazily grab remote and local_refs */
remote = remote_get(repo);
local_refs = get_local_heads();
}
- ref = map_refspec(ref, remote, local_refs);
- }
- refspec_append(&rs, ref);
+ refspec_append_mapped(&rs, ref, remote, local_refs);
+ } else
+ refspec_append(&rs, ref);
}
}
@@ -192,8 +188,6 @@ static const char message_detached_head_die[] =
static void setup_push_upstream(struct remote *remote, struct branch *branch,
int triangular, int simple)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
@@ -219,18 +213,14 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch,
die_push_simple(branch, remote);
}
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src);
}
static void setup_push_current(struct remote *remote, struct branch *branch)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname);
}
static int is_workflow_triangular(struct remote *remote)
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c4ff2039ef..eeca53382f 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -734,10 +734,10 @@ static int finish_rebase(struct rebase_options *opts)
apply_autostash(state_dir_path("autostash", opts));
close_object_store(the_repository->objects);
/*
- * We ignore errors in 'gc --auto', since the
+ * We ignore errors in 'git maintenance run --auto', since the
* user should see them.
*/
- run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
+ run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
if (opts->type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 439f29d6c7..bb9909c52e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -57,6 +57,7 @@ static int advertise_push_options;
static int unpack_limit = 100;
static off_t max_input_size;
static int report_status;
+static int report_status_v2;
static int use_sideband;
static int use_atomic;
static int use_push_options;
@@ -97,6 +98,17 @@ static int keepalive_in_sec = 5;
static struct tmp_objdir *tmp_objdir;
+static struct proc_receive_ref {
+ unsigned int want_add:1,
+ want_delete:1,
+ want_modify:1,
+ negative_ref:1;
+ char *ref_prefix;
+ struct proc_receive_ref *next;
+} *proc_receive_ref;
+
+static void proc_receive_ref_append(const char *prefix);
+
static enum deny_action parse_deny_action(const char *var, const char *value)
{
if (value) {
@@ -229,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.procreceiverefs") == 0) {
+ if (!value)
+ return config_error_nonbool(var);
+ proc_receive_ref_append(value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -240,7 +259,7 @@ static void show_ref(const char *path, const struct object_id *oid)
struct strbuf cap = STRBUF_INIT;
strbuf_addstr(&cap,
- "report-status delete-refs side-band-64k quiet");
+ "report-status report-status-v2 delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
if (prefer_ofs_delta)
@@ -310,17 +329,94 @@ static void write_head_info(void)
packet_flush(1);
}
+#define RUN_PROC_RECEIVE_SCHEDULED 1
+#define RUN_PROC_RECEIVE_RETURNED 2
struct command {
struct command *next;
const char *error_string;
+ struct ref_push_report *report;
unsigned int skip_update:1,
- did_not_exist:1;
+ did_not_exist:1,
+ run_proc_receive:2;
int index;
struct object_id old_oid;
struct object_id new_oid;
char ref_name[FLEX_ARRAY]; /* more */
};
+static void proc_receive_ref_append(const char *prefix)
+{
+ struct proc_receive_ref *ref_pattern;
+ char *p;
+ int len;
+
+ ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));
+ p = strchr(prefix, ':');
+ if (p) {
+ while (prefix < p) {
+ if (*prefix == 'a')
+ ref_pattern->want_add = 1;
+ else if (*prefix == 'd')
+ ref_pattern->want_delete = 1;
+ else if (*prefix == 'm')
+ ref_pattern->want_modify = 1;
+ else if (*prefix == '!')
+ ref_pattern->negative_ref = 1;
+ prefix++;
+ }
+ prefix++;
+ } else {
+ ref_pattern->want_add = 1;
+ ref_pattern->want_delete = 1;
+ ref_pattern->want_modify = 1;
+ }
+ len = strlen(prefix);
+ while (len && prefix[len - 1] == '/')
+ len--;
+ ref_pattern->ref_prefix = xmemdupz(prefix, len);
+ if (!proc_receive_ref) {
+ proc_receive_ref = ref_pattern;
+ } else {
+ struct proc_receive_ref *end;
+
+ end = proc_receive_ref;
+ while (end->next)
+ end = end->next;
+ end->next = ref_pattern;
+ }
+}
+
+static int proc_receive_ref_matches(struct command *cmd)
+{
+ struct proc_receive_ref *p;
+
+ if (!proc_receive_ref)
+ return 0;
+
+ for (p = proc_receive_ref; p; p = p->next) {
+ const char *match = p->ref_prefix;
+ const char *remains;
+
+ if (!p->want_add && is_null_oid(&cmd->old_oid))
+ continue;
+ else if (!p->want_delete && is_null_oid(&cmd->new_oid))
+ continue;
+ else if (!p->want_modify &&
+ !is_null_oid(&cmd->old_oid) &&
+ !is_null_oid(&cmd->new_oid))
+ continue;
+
+ if (skip_prefix(cmd->ref_name, match, &remains) &&
+ (!*remains || *remains == '/')) {
+ if (!p->negative_ref)
+ return 1;
+ } else if (p->negative_ref) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@ -692,6 +788,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)
struct receive_hook_feed_state {
struct command *cmd;
+ struct ref_push_report *report;
int skip_broken;
struct strbuf buf;
const struct string_list *push_options;
@@ -779,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
cmd = cmd->next;
if (!cmd)
return -1; /* EOF */
+ if (!bufp)
+ return 0; /* OK, can feed something. */
strbuf_reset(&state->buf);
- strbuf_addf(&state->buf, "%s %s %s\n",
- oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
- cmd->ref_name);
- state->cmd = cmd->next;
+ if (!state->report)
+ state->report = cmd->report;
+ if (state->report) {
+ struct object_id *old_oid;
+ struct object_id *new_oid;
+ const char *ref_name;
+
+ old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
+ new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
+ ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ oid_to_hex(old_oid), oid_to_hex(new_oid),
+ ref_name);
+ state->report = state->report->next;
+ if (!state->report)
+ state->cmd = cmd->next;
+ } else {
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
+ cmd->ref_name);
+ state->cmd = cmd->next;
+ }
if (bufp) {
*bufp = state->buf.buf;
*sizep = state->buf.len;
@@ -802,6 +919,7 @@ static int run_receive_hook(struct command *commands,
strbuf_init(&state.buf, 0);
state.cmd = commands;
state.skip_broken = skip_broken;
+ state.report = NULL;
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
@@ -840,6 +958,268 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
+static struct command *find_command_by_refname(struct command *list,
+ const char *refname)
+{
+ for (; list; list = list->next)
+ if (!strcmp(list->ref_name, refname))
+ return list;
+ return NULL;
+}
+
+static int read_proc_receive_report(struct packet_reader *reader,
+ struct command *commands,
+ struct strbuf *errmsg)
+{
+ struct command *cmd;
+ struct command *hint = NULL;
+ struct ref_push_report *report = NULL;
+ int new_report = 0;
+ int code = 0;
+ int once = 0;
+
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ const char *head;
+ const char *refname;
+ char *p;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ head = reader->line;
+ p = strchr(head, ' ');
+ if (!p) {
+ strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head);
+ code = -1;
+ continue;
+ }
+ *p++ = '\0';
+ if (!strcmp(head, "option")) {
+ const char *key, *val;
+
+ if (!hint || !(report || new_report)) {
+ if (!once++)
+ strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
+ code = -1;
+ continue;
+ }
+ if (new_report) {
+ if (!hint->report) {
+ hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ report = hint->report;
+ } else {
+ report = hint->report;
+ while (report->next)
+ report = report->next;
+ report->next = xcalloc(1, sizeof(struct ref_push_report));
+ report = report->next;
+ }
+ new_report = 0;
+ }
+ key = p;
+ p = strchr(key, ' ');
+ if (p)
+ *p++ = '\0';
+ val = p;
+ if (!strcmp(key, "refname"))
+ report->ref_name = xstrdup_or_null(val);
+ else if (!strcmp(key, "old-oid") && val &&
+ !parse_oid_hex(val, &old_oid, &val))
+ report->old_oid = oiddup(&old_oid);
+ else if (!strcmp(key, "new-oid") && val &&
+ !parse_oid_hex(val, &new_oid, &val))
+ report->new_oid = oiddup(&new_oid);
+ else if (!strcmp(key, "forced-update"))
+ report->forced_update = 1;
+ else if (!strcmp(key, "fall-through"))
+ /* Fall through, let 'receive-pack' to execute it. */
+ hint->run_proc_receive = 0;
+ continue;
+ }
+
+ report = NULL;
+ new_report = 0;
+ refname = p;
+ p = strchr(refname, ' ');
+ if (p)
+ *p++ = '\0';
+ if (strcmp(head, "ok") && strcmp(head, "ng")) {
+ strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
+ head, refname);
+ code = -1;
+ continue;
+ }
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_command_by_refname(hint, refname);
+ if (!hint)
+ hint = find_command_by_refname(commands, refname);
+ if (!hint) {
+ strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n",
+ refname);
+ code = -1;
+ continue;
+ }
+ if (!hint->run_proc_receive) {
+ strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n",
+ refname);
+ code = -1;
+ continue;
+ }
+ hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
+ if (!strcmp(head, "ng")) {
+ if (p)
+ hint->error_string = xstrdup(p);
+ else
+ hint->error_string = "failed";
+ code = -1;
+ continue;
+ }
+ new_report = 1;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (cmd->run_proc_receive && !cmd->error_string &&
+ !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
+ cmd->error_string = "proc-receive failed to report status";
+ code = -1;
+ }
+ return code;
+}
+
+static int run_proc_receive_hook(struct command *commands,
+ const struct string_list *push_options)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ struct async muxer;
+ struct command *cmd;
+ const char *argv[2];
+ struct packet_reader reader;
+ struct strbuf cap = STRBUF_INIT;
+ struct strbuf errmsg = STRBUF_INIT;
+ int hook_use_push_options = 0;
+ int version = 0;
+ int code;
+
+ argv[0] = find_hook("proc-receive");
+ if (!argv[0]) {
+ rp_error("cannot find hook 'proc-receive'");
+ return -1;
+ }
+ argv[1] = NULL;
+
+ proc.argv = argv;
+ proc.in = -1;
+ proc.out = -1;
+ proc.trace2_hook_name = "proc-receive";
+
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ code = start_async(&muxer);
+ if (code)
+ return code;
+ proc.err = muxer.in;
+ } else {
+ proc.err = 0;
+ }
+
+ code = start_command(&proc);
+ if (code) {
+ if (use_sideband)
+ finish_async(&muxer);
+ return code;
+ }
+
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ /* Version negotiaton */
+ packet_reader_init(&reader, proc.out, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ON_EOF);
+ if (use_atomic)
+ strbuf_addstr(&cap, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap, " push-options");
+ if (cap.len) {
+ packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1);
+ strbuf_release(&cap);
+ } else {
+ packet_write_fmt(proc.in, "version=1\n");
+ }
+ packet_flush(proc.in);
+
+ for (;;) {
+ int linelen;
+
+ if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
+ break;
+
+ if (reader.pktlen > 8 && starts_with(reader.line, "version=")) {
+ version = atoi(reader.line + 8);
+ linelen = strlen(reader.line);
+ if (linelen < reader.pktlen) {
+ const char *feature_list = reader.line + linelen + 1;
+ if (parse_feature_request(feature_list, "push-options"))
+ hook_use_push_options = 1;
+ }
+ }
+ }
+
+ if (version != 1) {
+ strbuf_addf(&errmsg, "proc-receive version '%d' is not supported",
+ version);
+ code = -1;
+ goto cleanup;
+ }
+
+ /* Send commands */
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)
+ continue;
+ packet_write_fmt(proc.in, "%s %s %s",
+ oid_to_hex(&cmd->old_oid),
+ oid_to_hex(&cmd->new_oid),
+ cmd->ref_name);
+ }
+ packet_flush(proc.in);
+
+ /* Send push options */
+ if (hook_use_push_options) {
+ struct string_list_item *item;
+
+ for_each_string_list_item(item, push_options)
+ packet_write_fmt(proc.in, "%s", item->string);
+ packet_flush(proc.in);
+ }
+
+ /* Read result from proc-receive */
+ code = read_proc_receive_report(&reader, commands, &errmsg);
+
+cleanup:
+ close(proc.in);
+ close(proc.out);
+ if (use_sideband)
+ finish_async(&muxer);
+ if (finish_command(&proc))
+ code = -1;
+ if (errmsg.len >0) {
+ char *p = errmsg.buf;
+
+ p += errmsg.len - 1;
+ if (*p == '\n')
+ *p = '\0';
+ rp_error("%s", errmsg.buf);
+ strbuf_release(&errmsg);
+ }
+ sigchain_pop(SIGPIPE);
+
+ return code;
+}
+
static char *refuse_unconfigured_deny_msg =
N_("By default, updating the current branch in a non-bare repository\n"
"is denied, because it will make the index and work tree inconsistent\n"
@@ -1415,7 +1795,7 @@ static void execute_commands_non_atomic(struct command *commands,
struct strbuf err = STRBUF_INIT;
for (cmd = commands; cmd; cmd = cmd->next) {
- if (!should_process_cmd(cmd))
+ if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
transaction = ref_transaction_begin(&err);
@@ -1455,7 +1835,7 @@ static void execute_commands_atomic(struct command *commands,
}
for (cmd = commands; cmd; cmd = cmd->next) {
- if (!should_process_cmd(cmd))
+ if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
cmd->error_string = update(cmd, si);
@@ -1491,6 +1871,7 @@ static void execute_commands(struct command *commands,
struct iterate_data data;
struct async muxer;
int err_fd = 0;
+ int run_proc_receive = 0;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
@@ -1520,6 +1901,22 @@ static void execute_commands(struct command *commands,
reject_updates_to_hidden(commands);
+ /*
+ * Try to find commands that have special prefix in their reference names,
+ * and mark them to run an external "proc-receive" hook later.
+ */
+ if (proc_receive_ref) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ if (proc_receive_ref_matches(cmd)) {
+ cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
+ run_proc_receive = 1;
+ }
+ }
+ }
+
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
@@ -1546,6 +1943,14 @@ static void execute_commands(struct command *commands,
free(head_name_to_free);
head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
+ if (run_proc_receive &&
+ run_proc_receive_hook(commands, push_options))
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (!cmd->error_string &&
+ !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&
+ (cmd->run_proc_receive || use_atomic))
+ cmd->error_string = "fail to run proc-receive hook";
+
if (use_atomic)
execute_commands_atomic(commands, si);
else
@@ -1629,6 +2034,8 @@ static struct command *read_head_info(struct packet_reader *reader,
int len = 0;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
+ if (parse_feature_request(feature_list, "report-status-v2"))
+ report_status_v2 = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
if (parse_feature_request(feature_list, "quiet"))
@@ -1947,6 +2354,51 @@ static void report(struct command *commands, const char *unpack_status)
strbuf_release(&buf);
}
+static void report_v2(struct command *commands, const char *unpack_status)
+{
+ struct command *cmd;
+ struct strbuf buf = STRBUF_INIT;
+ struct ref_push_report *report;
+
+ packet_buf_write(&buf, "unpack %s\n",
+ unpack_status ? unpack_status : "ok");
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ int count = 0;
+
+ if (cmd->error_string) {
+ packet_buf_write(&buf, "ng %s %s\n",
+ cmd->ref_name,
+ cmd->error_string);
+ continue;
+ }
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
+ for (report = cmd->report; report; report = report->next) {
+ if (count++ > 0)
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
+ if (report->ref_name)
+ packet_buf_write(&buf, "option refname %s\n",
+ report->ref_name);
+ if (report->old_oid)
+ packet_buf_write(&buf, "option old-oid %s\n",
+ oid_to_hex(report->old_oid));
+ if (report->new_oid)
+ packet_buf_write(&buf, "option new-oid %s\n",
+ oid_to_hex(report->new_oid));
+ if (report->forced_update)
+ packet_buf_write(&buf, "option forced-update\n");
+ }
+ }
+ packet_buf_flush(&buf);
+
+ if (use_sideband)
+ send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+ else
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
+}
+
static int delete_only(struct command *commands)
{
struct command *cmd;
@@ -2055,7 +2507,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
&push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
- if (report_status)
+ if (report_status_v2)
+ report_v2(commands, unpack_status);
+ else if (report_status)
report(commands, unpack_status);
run_receive_hook(commands, "post-receive", 1,
&push_options);
diff --git a/builtin/remote.c b/builtin/remote.c
index 542f56e387..64b4b551eb 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1356,7 +1356,7 @@ static int set_head(int argc, const char **argv)
result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
result |= error(_("Could not setup %s"), buf.buf);
- if (opt_a)
+ else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 2b9610f121..7af148d733 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -29,10 +29,12 @@ static struct send_pack_args args;
static void print_helper_status(struct ref *ref)
{
struct strbuf buf = STRBUF_INIT;
+ struct ref_push_report *report;
for (; ref; ref = ref->next) {
const char *msg = NULL;
const char *res;
+ int count = 0;
switch(ref->status) {
case REF_STATUS_NONE:
@@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref)
}
strbuf_addch(&buf, '\n');
+ if (ref->status == REF_STATUS_OK) {
+ for (report = ref->report; report; report = report->next) {
+ if (count++ > 0)
+ strbuf_addf(&buf, "ok %s\n", ref->name);
+ if (report->ref_name)
+ strbuf_addf(&buf, "option refname %s\n",
+ report->ref_name);
+ if (report->old_oid)
+ strbuf_addf(&buf, "option old-oid %s\n",
+ oid_to_hex(report->old_oid));
+ if (report->new_oid)
+ strbuf_addf(&buf, "option new-oid %s\n",
+ oid_to_hex(report->new_oid));
+ if (report->forced_update)
+ strbuf_addstr(&buf, "option forced-update\n");
+ }
+ }
write_or_die(1, buf.buf, buf.len);
}
strbuf_release(&buf);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index bb70fde97e..99abaeec6c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -555,7 +555,7 @@ static int add(int ac, const char **av, const char *prefix)
N_("create a new branch")),
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
N_("create or reset a branch")),
- OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
+ OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
diff --git a/cache.h b/cache.h
index cee8aa5dc3..c0072d43b1 100644
--- a/cache.h
+++ b/cache.h
@@ -629,7 +629,7 @@ int path_inside_repo(const char *prefix, const char *path);
int init_db(const char *git_dir, const char *real_git_dir,
const char *template_dir, int hash_algo,
const char *initial_branch, unsigned int flags);
-void initialize_repository_version(int hash_algo);
+void initialize_repository_version(int hash_algo, int reinit);
void sanitize_stdfds(void);
int daemonize(void);
diff --git a/ci/lib.sh b/ci/lib.sh
index 3eefec500d..821e3660d6 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -178,6 +178,7 @@ fi
export DEVELOPER=1
export DEFAULT_TEST_TARGET=prove
export GIT_TEST_CLONE_2GB=true
+export SKIP_DASHED_BUILT_INS=YesPlease
case "$jobname" in
linux-clang|linux-gcc)
diff --git a/command-list.txt b/command-list.txt
index e5901f2213..0e3204e7d1 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -117,6 +117,7 @@ git-ls-remote plumbinginterrogators
git-ls-tree plumbinginterrogators
git-mailinfo purehelpers
git-mailsplit purehelpers
+git-maintenance mainporcelain
git-merge mainporcelain history
git-merge-base plumbinginterrogators
git-merge-file plumbingmanipulators
diff --git a/commit-graph.c b/commit-graph.c
index 0ed003e218..cb042bdba8 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb,
oid_hex);
}
-static char *get_chain_filename(struct object_directory *odb)
+char *get_commit_graph_chain_filename(struct object_directory *odb)
{
return xstrfmt("%s/info/commit-graphs/commit-graph-chain", odb->path);
}
@@ -231,7 +231,8 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st)
return 1;
}
-struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
+struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
+ int fd, struct stat *st,
struct object_directory *odb)
{
void *graph_map;
@@ -247,7 +248,7 @@ struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
}
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
- ret = parse_commit_graph(graph_map, graph_size);
+ ret = parse_commit_graph(r, graph_map, graph_size);
if (ret)
ret->odb = odb;
@@ -287,7 +288,8 @@ static int verify_commit_graph_lite(struct commit_graph *g)
return 0;
}
-struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
+struct commit_graph *parse_commit_graph(struct repository *r,
+ void *graph_map, size_t graph_size)
{
const unsigned char *data, *chunk_lookup;
uint32_t i;
@@ -325,6 +327,8 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
return NULL;
}
+ prepare_repo_settings(r);
+
graph = alloc_commit_graph();
graph->hash_len = the_hash_algo->rawsz;
@@ -401,14 +405,14 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
case GRAPH_CHUNKID_BLOOMINDEXES:
if (graph->chunk_bloom_indexes)
chunk_repeated = 1;
- else
+ else if (r->settings.commit_graph_read_changed_paths)
graph->chunk_bloom_indexes = data + chunk_offset;
break;
case GRAPH_CHUNKID_BLOOMDATA:
if (graph->chunk_bloom_data)
chunk_repeated = 1;
- else {
+ else if (r->settings.commit_graph_read_changed_paths) {
uint32_t hash_version;
graph->chunk_bloom_data = data + chunk_offset;
hash_version = get_be32(data + chunk_offset);
@@ -420,6 +424,7 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size)
graph->bloom_filter_settings->hash_version = hash_version;
graph->bloom_filter_settings->num_hashes = get_be32(data + chunk_offset + 4);
graph->bloom_filter_settings->bits_per_entry = get_be32(data + chunk_offset + 8);
+ graph->bloom_filter_settings->max_changed_paths = DEFAULT_BLOOM_MAX_CHANGES;
}
break;
}
@@ -452,7 +457,8 @@ free_and_return:
return NULL;
}
-static struct commit_graph *load_commit_graph_one(const char *graph_file,
+static struct commit_graph *load_commit_graph_one(struct repository *r,
+ const char *graph_file,
struct object_directory *odb)
{
@@ -464,7 +470,7 @@ static struct commit_graph *load_commit_graph_one(const char *graph_file,
if (!open_ok)
return NULL;
- g = load_commit_graph_one_fd_st(fd, &st, odb);
+ g = load_commit_graph_one_fd_st(r, fd, &st, odb);
if (g)
g->filename = xstrdup(graph_file);
@@ -476,7 +482,7 @@ static struct commit_graph *load_commit_graph_v1(struct repository *r,
struct object_directory *odb)
{
char *graph_name = get_commit_graph_filename(odb);
- struct commit_graph *g = load_commit_graph_one(graph_name, odb);
+ struct commit_graph *g = load_commit_graph_one(r, graph_name, odb);
free(graph_name);
return g;
@@ -523,7 +529,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
struct stat st;
struct object_id *oids;
int i = 0, valid = 1, count;
- char *chain_name = get_chain_filename(odb);
+ char *chain_name = get_commit_graph_chain_filename(odb);
FILE *fp;
int stat_res;
@@ -557,7 +563,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r,
valid = 0;
for (odb = r->objects->odb; odb; odb = odb->next) {
char *graph_name = get_split_graph_filename(odb, line.buf);
- struct commit_graph *g = load_commit_graph_one(graph_name, odb);
+ struct commit_graph *g = load_commit_graph_one(r, graph_name, odb);
free(graph_name);
@@ -667,6 +673,17 @@ int generation_numbers_enabled(struct repository *r)
return !!first_generation;
}
+struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r)
+{
+ struct commit_graph *g = r->objects->commit_graph;
+ while (g) {
+ if (g->bloom_filter_settings)
+ return g->bloom_filter_settings;
+ g = g->base_graph;
+ }
+ return NULL;
+}
+
static void close_commit_graph_one(struct commit_graph *g)
{
if (!g)
@@ -952,9 +969,14 @@ struct write_commit_graph_context {
changed_paths:1,
order_by_pack:1;
- const struct split_commit_graph_opts *split_opts;
+ const struct commit_graph_opts *opts;
size_t total_bloom_filter_data_size;
const struct bloom_filter_settings *bloom_settings;
+
+ int count_bloom_filter_computed;
+ int count_bloom_filter_not_computed;
+ int count_bloom_filter_trunc_empty;
+ int count_bloom_filter_trunc_large;
};
static int write_graph_chunk_fanout(struct hashfile *f,
@@ -1166,7 +1188,7 @@ static int write_graph_chunk_bloom_indexes(struct hashfile *f,
uint32_t cur_pos = 0;
while (list < last) {
- struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+ struct bloom_filter *filter = get_bloom_filter(ctx->r, *list);
size_t len = filter ? filter->len : 0;
cur_pos += len;
display_progress(ctx->progress, ++ctx->progress_cnt);
@@ -1185,6 +1207,7 @@ static void trace2_bloom_filter_settings(struct write_commit_graph_context *ctx)
jw_object_intmax(&jw, "hash_version", ctx->bloom_settings->hash_version);
jw_object_intmax(&jw, "num_hashes", ctx->bloom_settings->num_hashes);
jw_object_intmax(&jw, "bits_per_entry", ctx->bloom_settings->bits_per_entry);
+ jw_object_intmax(&jw, "max_changed_paths", ctx->bloom_settings->max_changed_paths);
jw_end(&jw);
trace2_data_json("bloom", ctx->r, "settings", &jw);
@@ -1205,7 +1228,7 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,
hashwrite_be32(f, ctx->bloom_settings->bits_per_entry);
while (list < last) {
- struct bloom_filter *filter = get_bloom_filter(ctx->r, *list, 0);
+ struct bloom_filter *filter = get_bloom_filter(ctx->r, *list);
size_t len = filter ? filter->len : 0;
display_progress(ctx->progress, ++ctx->progress_cnt);
@@ -1270,8 +1293,8 @@ static void close_reachable(struct write_commit_graph_context *ctx)
{
int i;
struct commit *commit;
- enum commit_graph_split_flags flags = ctx->split_opts ?
- ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
+ enum commit_graph_split_flags flags = ctx->opts ?
+ ctx->opts->split_flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
if (ctx->report_progress)
ctx->progress = start_delayed_progress(
@@ -1375,11 +1398,24 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
stop_progress(&ctx->progress);
}
+static void trace2_bloom_filter_write_statistics(struct write_commit_graph_context *ctx)
+{
+ trace2_data_intmax("commit-graph", ctx->r, "filter-computed",
+ ctx->count_bloom_filter_computed);
+ trace2_data_intmax("commit-graph", ctx->r, "filter-not-computed",
+ ctx->count_bloom_filter_not_computed);
+ trace2_data_intmax("commit-graph", ctx->r, "filter-trunc-empty",
+ ctx->count_bloom_filter_trunc_empty);
+ trace2_data_intmax("commit-graph", ctx->r, "filter-trunc-large",
+ ctx->count_bloom_filter_trunc_large);
+}
+
static void compute_bloom_filters(struct write_commit_graph_context *ctx)
{
int i;
struct progress *progress = NULL;
struct commit **sorted_commits;
+ int max_new_filters;
init_bloom_filters();
@@ -1396,13 +1432,34 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
else
QSORT(sorted_commits, ctx->commits.nr, commit_gen_cmp);
+ max_new_filters = ctx->opts && ctx->opts->max_new_filters >= 0 ?
+ ctx->opts->max_new_filters : ctx->commits.nr;
+
for (i = 0; i < ctx->commits.nr; i++) {
+ enum bloom_filter_computed computed = 0;
struct commit *c = sorted_commits[i];
- struct bloom_filter *filter = get_bloom_filter(ctx->r, c, 1);
- ctx->total_bloom_filter_data_size += sizeof(unsigned char) * filter->len;
+ struct bloom_filter *filter = get_or_compute_bloom_filter(
+ ctx->r,
+ c,
+ ctx->count_bloom_filter_computed < max_new_filters,
+ ctx->bloom_settings,
+ &computed);
+ if (computed & BLOOM_COMPUTED) {
+ ctx->count_bloom_filter_computed++;
+ if (computed & BLOOM_TRUNC_EMPTY)
+ ctx->count_bloom_filter_trunc_empty++;
+ if (computed & BLOOM_TRUNC_LARGE)
+ ctx->count_bloom_filter_trunc_large++;
+ } else if (computed & BLOOM_NOT_COMPUTED)
+ ctx->count_bloom_filter_not_computed++;
+ ctx->total_bloom_filter_data_size += filter
+ ? sizeof(unsigned char) * filter->len : 0;
display_progress(progress, i + 1);
}
+ if (trace2_is_enabled())
+ trace2_bloom_filter_write_statistics(ctx);
+
free(sorted_commits);
stop_progress(&progress);
}
@@ -1431,7 +1488,7 @@ static int add_ref_to_set(const char *refname,
int write_commit_graph_reachable(struct object_directory *odb,
enum commit_graph_write_flags flags,
- const struct split_commit_graph_opts *split_opts)
+ const struct commit_graph_opts *opts)
{
struct oidset commits = OIDSET_INIT;
struct refs_cb_data data;
@@ -1448,7 +1505,7 @@ int write_commit_graph_reachable(struct object_directory *odb,
stop_progress(&data.progress);
result = write_commit_graph(odb, NULL, &commits,
- flags, split_opts);
+ flags, opts);
oidset_clear(&commits);
return result;
@@ -1563,8 +1620,8 @@ static uint32_t count_distinct_commits(struct write_commit_graph_context *ctx)
static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
{
uint32_t i;
- enum commit_graph_split_flags flags = ctx->split_opts ?
- ctx->split_opts->flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
+ enum commit_graph_split_flags flags = ctx->opts ?
+ ctx->opts->split_flags : COMMIT_GRAPH_SPLIT_UNSPECIFIED;
ctx->num_extra_edges = 0;
if (ctx->report_progress)
@@ -1646,15 +1703,6 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
int num_chunks = 3;
uint64_t chunk_offset;
struct object_id file_hash;
- struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
-
- if (!ctx->bloom_settings) {
- bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
- bloom_settings.bits_per_entry);
- bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
- bloom_settings.num_hashes);
- ctx->bloom_settings = &bloom_settings;
- }
if (ctx->split) {
struct strbuf tmp_file = STRBUF_INIT;
@@ -1675,7 +1723,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx)
}
if (ctx->split) {
- char *lock_name = get_chain_filename(ctx->odb);
+ char *lock_name = get_commit_graph_chain_filename(ctx->odb);
hold_lock_file_for_update_mode(&lk, lock_name,
LOCK_DIE_ON_ERROR, 0444);
@@ -1858,13 +1906,13 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx)
int max_commits = 0;
int size_mult = 2;
- if (ctx->split_opts) {
- max_commits = ctx->split_opts->max_commits;
+ if (ctx->opts) {
+ max_commits = ctx->opts->max_commits;
- if (ctx->split_opts->size_multiple)
- size_mult = ctx->split_opts->size_multiple;
+ if (ctx->opts->size_multiple)
+ size_mult = ctx->opts->size_multiple;
- flags = ctx->split_opts->flags;
+ flags = ctx->opts->split_flags;
}
g = ctx->r->objects->commit_graph;
@@ -2042,10 +2090,10 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx)
size_t dirnamelen;
timestamp_t expire_time = time(NULL);
- if (ctx->split_opts && ctx->split_opts->expire_time)
- expire_time = ctx->split_opts->expire_time;
+ if (ctx->opts && ctx->opts->expire_time)
+ expire_time = ctx->opts->expire_time;
if (!ctx->split) {
- char *chain_file_name = get_chain_filename(ctx->odb);
+ char *chain_file_name = get_commit_graph_chain_filename(ctx->odb);
unlink(chain_file_name);
free(chain_file_name);
ctx->num_commit_graphs_after = 0;
@@ -2094,12 +2142,13 @@ int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
struct oidset *commits,
enum commit_graph_write_flags flags,
- const struct split_commit_graph_opts *split_opts)
+ const struct commit_graph_opts *opts)
{
struct write_commit_graph_context *ctx;
uint32_t i, count_distinct = 0;
int res = 0;
int replace = 0;
+ struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
if (!commit_graph_compatible(the_repository))
return 0;
@@ -2110,9 +2159,17 @@ int write_commit_graph(struct object_directory *odb,
ctx->append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0;
ctx->report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0;
ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
- ctx->split_opts = split_opts;
+ ctx->opts = opts;
ctx->total_bloom_filter_data_size = 0;
+ bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
+ bloom_settings.bits_per_entry);
+ bloom_settings.num_hashes = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_NUM_HASHES",
+ bloom_settings.num_hashes);
+ bloom_settings.max_changed_paths = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS",
+ bloom_settings.max_changed_paths);
+ ctx->bloom_settings = &bloom_settings;
+
if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
ctx->changed_paths = 1;
if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
@@ -2150,15 +2207,15 @@ int write_commit_graph(struct object_directory *odb,
}
}
- if (ctx->split_opts)
- replace = ctx->split_opts->flags & COMMIT_GRAPH_SPLIT_REPLACE;
+ if (ctx->opts)
+ replace = ctx->opts->split_flags & COMMIT_GRAPH_SPLIT_REPLACE;
}
ctx->approx_nr_objects = approximate_object_count();
ctx->oids.alloc = ctx->approx_nr_objects / 32;
- if (ctx->split && split_opts && ctx->oids.alloc > split_opts->max_commits)
- ctx->oids.alloc = split_opts->max_commits;
+ if (ctx->split && opts && ctx->oids.alloc > opts->max_commits)
+ ctx->oids.alloc = opts->max_commits;
if (ctx->append) {
prepare_commit_graph_one(ctx->r, ctx->odb);
diff --git a/commit-graph.h b/commit-graph.h
index 09a97030dc..f8e92500c6 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -25,6 +25,7 @@ struct raw_object_store;
struct string_list;
char *get_commit_graph_filename(struct object_directory *odb);
+char *get_commit_graph_chain_filename(struct object_directory *odb);
int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
/*
@@ -75,11 +76,13 @@ struct commit_graph {
struct bloom_filter_settings *bloom_filter_settings;
};
-struct commit_graph *load_commit_graph_one_fd_st(int fd, struct stat *st,
+struct commit_graph *load_commit_graph_one_fd_st(struct repository *r,
+ int fd, struct stat *st,
struct object_directory *odb);
struct commit_graph *read_commit_graph_one(struct repository *r,
struct object_directory *odb);
-struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);
+struct commit_graph *parse_commit_graph(struct repository *r,
+ void *graph_map, size_t graph_size);
/*
* Return 1 if and only if the repository has a commit-graph
@@ -87,6 +90,8 @@ struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);
*/
int generation_numbers_enabled(struct repository *r);
+struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r);
+
enum commit_graph_write_flags {
COMMIT_GRAPH_WRITE_APPEND = (1 << 0),
COMMIT_GRAPH_WRITE_PROGRESS = (1 << 1),
@@ -101,11 +106,12 @@ enum commit_graph_split_flags {
COMMIT_GRAPH_SPLIT_REPLACE = 2
};
-struct split_commit_graph_opts {
+struct commit_graph_opts {
int size_multiple;
int max_commits;
timestamp_t expire_time;
- enum commit_graph_split_flags flags;
+ enum commit_graph_split_flags split_flags;
+ int max_new_filters;
};
/*
@@ -116,12 +122,12 @@ struct split_commit_graph_opts {
*/
int write_commit_graph_reachable(struct object_directory *odb,
enum commit_graph_write_flags flags,
- const struct split_commit_graph_opts *split_opts);
+ const struct commit_graph_opts *opts);
int write_commit_graph(struct object_directory *odb,
struct string_list *pack_indexes,
struct oidset *commits,
enum commit_graph_write_flags flags,
- const struct split_commit_graph_opts *split_opts);
+ const struct commit_graph_opts *opts);
#define COMMIT_GRAPH_VERIFY_SHALLOW (1 << 0)
diff --git a/commit.h b/commit.h
index bd73b84472..e6f8f7c26f 100644
--- a/commit.h
+++ b/commit.h
@@ -95,7 +95,6 @@ static inline int parse_commit_no_graph(struct commit *commit)
#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
#define parse_commit_internal(item, quiet, use) repo_parse_commit_internal(the_repository, item, quiet, use)
-#define parse_commit_gently(item, quiet) repo_parse_commit_gently(the_repository, item, quiet)
#define parse_commit(item) repo_parse_commit(the_repository, item)
#endif
diff --git a/compat/bswap.h b/compat/bswap.h
index e4e25735ce..c0bb744adc 100644
--- a/compat/bswap.h
+++ b/compat/bswap.h
@@ -145,28 +145,6 @@ static inline uint64_t git_bswap64(uint64_t x)
#endif
-/*
- * Performance might be improved if the CPU architecture is OK with
- * unaligned 32-bit loads and a fast ntohl() is available.
- * Otherwise fall back to byte loads and shifts which is portable,
- * and is faster on architectures with memory alignment issues.
- */
-
-#if !defined(NO_UNALIGNED_LOADS) && ( \
- defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64) || \
- defined(__ppc__) || defined(__ppc64__) || \
- defined(__powerpc__) || defined(__powerpc64__) || \
- defined(__s390__) || defined(__s390x__))
-
-#define get_be16(p) ntohs(*(unsigned short *)(p))
-#define get_be32(p) ntohl(*(unsigned int *)(p))
-#define get_be64(p) ntohll(*(uint64_t *)(p))
-#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
-#define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)
-
-#else
-
static inline uint16_t get_be16(const void *ptr)
{
const unsigned char *p = ptr;
@@ -212,6 +190,4 @@ static inline void put_be64(void *ptr, uint64_t value)
p[7] = value >> 0;
}
-#endif
-
#endif /* COMPAT_BSWAP_H */
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 9147fba3d5..8be4a0316e 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1784,6 +1784,10 @@ _git_format_patch ()
" "" "${cur##--thread=}"
return
;;
+ --base=*|--interdiff=*|--range-diff=*)
+ __git_complete_refs --cur="${cur#--*=}"
+ return
+ ;;
--*)
__gitcomp_builtin format-patch "$__git_format_patch_extra_options"
return
diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm
index e2589922a6..376f577737 100644
--- a/contrib/diff-highlight/DiffHighlight.pm
+++ b/contrib/diff-highlight/DiffHighlight.pm
@@ -112,7 +112,7 @@ sub handle_line {
# Since we can receive arbitrary input, there's no optimal
# place to flush. Flushing on a blank line is a heuristic that
# happens to match git-log output.
- if (!length) {
+ if (/^$/) {
$flush_cb->();
}
}
diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl
index 28df3ee321..eb52a53d32 100755
--- a/contrib/mw-to-git/git-mw.perl
+++ b/contrib/mw-to-git/git-mw.perl
@@ -6,7 +6,7 @@
# License: GPL v2 or later
# Set of tools for git repo with a mediawiki remote.
-# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
use strict;
use warnings;
diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl
index d8ff2e69c4..a5624413dc 100755
--- a/contrib/mw-to-git/git-remote-mediawiki.perl
+++ b/contrib/mw-to-git/git-remote-mediawiki.perl
@@ -9,7 +9,7 @@
# License: GPL v2 or later
# Gateway between Git and MediaWiki.
-# Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/
+# Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
use strict;
use MediaWiki::API;
@@ -56,38 +56,38 @@ my $url = $ARGV[1];
# Accept both space-separated and multiple keys in config file.
# Spaces should be written as _ anyway because we'll use chomp.
-my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.${remotename}.pages"));
+my @tracked_pages = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.pages"]));
chomp(@tracked_pages);
# Just like @tracked_pages, but for MediaWiki categories.
-my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.${remotename}.categories"));
+my @tracked_categories = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.categories"]));
chomp(@tracked_categories);
# Just like @tracked_categories, but for MediaWiki namespaces.
-my @tracked_namespaces = split(/[ \n]/, run_git("config --get-all remote.${remotename}.namespaces"));
+my @tracked_namespaces = split(/[ \n]/, run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaces"]));
for (@tracked_namespaces) { s/_/ /g; }
chomp(@tracked_namespaces);
# Import media files on pull
-my $import_media = run_git("config --get --bool remote.${remotename}.mediaimport");
+my $import_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaimport"]);
chomp($import_media);
$import_media = ($import_media eq 'true');
# Export media files on push
-my $export_media = run_git("config --get --bool remote.${remotename}.mediaexport");
+my $export_media = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.mediaexport"]);
chomp($export_media);
$export_media = !($export_media eq 'false');
-my $wiki_login = run_git("config --get remote.${remotename}.mwLogin");
+my $wiki_login = run_git_quoted(["config", "--get", "remote.${remotename}.mwLogin"]);
# Note: mwPassword is discouraged. Use the credential system instead.
-my $wiki_passwd = run_git("config --get remote.${remotename}.mwPassword");
-my $wiki_domain = run_git("config --get remote.${remotename}.mwDomain");
+my $wiki_passwd = run_git_quoted(["config", "--get", "remote.${remotename}.mwPassword"]);
+my $wiki_domain = run_git_quoted(["config", "--get", "remote.${remotename}.mwDomain"]);
chomp($wiki_login);
chomp($wiki_passwd);
chomp($wiki_domain);
# Import only last revisions (both for clone and fetch)
-my $shallow_import = run_git("config --get --bool remote.${remotename}.shallow");
+my $shallow_import = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.shallow"]);
chomp($shallow_import);
$shallow_import = ($shallow_import eq 'true');
@@ -97,9 +97,9 @@ $shallow_import = ($shallow_import eq 'true');
# Possible values:
# - by_rev: perform one query per new revision on the remote wiki
# - by_page: query each tracked page for new revision
-my $fetch_strategy = run_git("config --get remote.${remotename}.fetchStrategy");
+my $fetch_strategy = run_git_quoted(["config", "--get", "remote.${remotename}.fetchStrategy"]);
if (!$fetch_strategy) {
- $fetch_strategy = run_git('config --get mediawiki.fetchStrategy');
+ $fetch_strategy = run_git_quoted(["config", "--get", "mediawiki.fetchStrategy"]);
}
chomp($fetch_strategy);
if (!$fetch_strategy) {
@@ -123,9 +123,9 @@ my %basetimestamps;
# will get the history with information lost). If the import is
# deterministic, this means everybody gets the same sha1 for each
# MediaWiki revision.
-my $dumb_push = run_git("config --get --bool remote.${remotename}.dumbPush");
+my $dumb_push = run_git_quoted(["config", "--get", "--bool", "remote.${remotename}.dumbPush"]);
if (!$dumb_push) {
- $dumb_push = run_git('config --get --bool mediawiki.dumbPush');
+ $dumb_push = run_git_quoted(["config", "--get", "--bool", "mediawiki.dumbPush"]);
}
chomp($dumb_push);
$dumb_push = ($dumb_push eq 'true');
@@ -369,12 +369,14 @@ sub get_mw_pages {
return %pages;
}
-# usage: $out = run_git("command args");
-# $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
-sub run_git {
+# usage: $out = run_git_quoted(["command", "args", ...]);
+# $out = run_git_quoted(["command", "args", ...], "raw"); # don't interpret output as UTF-8.
+# $out = run_git_quoted_nostderr(["command", "args", ...]); # discard stderr
+# $out = run_git_quoted_nostderr(["command", "args", ...], "raw"); # ditto but raw instead of UTF-8 as above
+sub _run_git {
my $args = shift;
my $encoding = (shift || 'encoding(UTF-8)');
- open(my $git, "-|:${encoding}", "git ${args}")
+ open(my $git, "-|:${encoding}", @$args)
or die "Unable to fork: $!\n";
my $res = do {
local $/ = undef;
@@ -385,6 +387,13 @@ sub run_git {
return $res;
}
+sub run_git_quoted {
+ _run_git(["git", @{$_[0]}], $_[1]);
+}
+
+sub run_git_quoted_nostderr {
+ _run_git(['sh', '-c', 'git "$@" 2>/dev/null', '--', @{$_[0]}], $_[1]);
+}
sub get_all_mediafiles {
my $pages = shift;
@@ -511,8 +520,9 @@ sub download_mw_mediafile {
}
sub get_last_local_revision {
- # Get note regarding last mediawiki revision
- my $note = run_git("notes --ref=${remotename}/mediawiki show refs/mediawiki/${remotename}/master 2>/dev/null");
+ # Get note regarding last mediawiki revision.
+ my $note = run_git_quoted_nostderr(["notes", "--ref=${remotename}/mediawiki",
+ "show", "refs/mediawiki/${remotename}/master"]);
my @note_info = split(/ /, $note);
my $lastrevision_number;
@@ -807,7 +817,10 @@ sub get_more_refs {
sub mw_import {
# multiple import commands can follow each other.
my @refs = (shift, get_more_refs('import'));
+ my $processedRefs;
foreach my $ref (@refs) {
+ next if $processedRefs->{$ref}; # skip duplicates: "import refs/heads/master" being issued twice; TODO: why?
+ $processedRefs->{$ref} = 1;
mw_import_ref($ref);
}
print {*STDOUT} "done\n";
@@ -970,7 +983,7 @@ sub mw_import_revids {
}
sub error_non_fast_forward {
- my $advice = run_git('config --bool advice.pushNonFastForward');
+ my $advice = run_git_quoted(["config", "--bool", "advice.pushNonFastForward"]);
chomp($advice);
if ($advice ne 'false') {
# Native git-push would show this after the summary.
@@ -1014,7 +1027,7 @@ sub mw_upload_file {
}
} else {
# Don't let perl try to interpret file content as UTF-8 => use "raw"
- my $content = run_git("cat-file blob ${new_sha1}", 'raw');
+ my $content = run_git_quoted(["cat-file", "blob", $new_sha1], 'raw');
if ($content ne EMPTY) {
$mediawiki = connect_maybe($mediawiki, $remotename, $url);
$mediawiki->{config}->{upload_url} =
@@ -1084,7 +1097,7 @@ sub mw_push_file {
# with this content instead:
$file_content = DELETED_CONTENT;
} else {
- $file_content = run_git("cat-file blob ${new_sha1}");
+ $file_content = run_git_quoted(["cat-file", "blob", $new_sha1]);
}
$mediawiki = connect_maybe($mediawiki, $remotename, $url);
@@ -1174,10 +1187,10 @@ sub mw_push_revision {
my $mw_revision = $last_remote_revid;
# Get sha1 of commit pointed by local HEAD
- my $HEAD_sha1 = run_git("rev-parse ${local} 2>/dev/null");
+ my $HEAD_sha1 = run_git_quoted_nostderr(["rev-parse", $local]);
chomp($HEAD_sha1);
# Get sha1 of commit pointed by remotes/$remotename/master
- my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/${remotename}/master 2>/dev/null");
+ my $remoteorigin_sha1 = run_git_quoted_nostderr(["rev-parse", "refs/remotes/${remotename}/master"]);
chomp($remoteorigin_sha1);
if ($last_local_revid > 0 &&
@@ -1197,7 +1210,7 @@ sub mw_push_revision {
my $parsed_sha1 = $remoteorigin_sha1;
# Find a path from last MediaWiki commit to pushed commit
print {*STDERR} "Computing path from local to remote ...\n";
- my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents ${local} ^${parsed_sha1}"));
+ my @local_ancestry = split(/\n/, run_git_quoted(["rev-list", "--boundary", "--parents", $local, "^${parsed_sha1}"]));
my %local_ancestry;
foreach my $line (@local_ancestry) {
if (my ($child, $parents) = $line =~ /^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
@@ -1221,7 +1234,7 @@ sub mw_push_revision {
# No remote mediawiki revision. Export the whole
# history (linearized with --first-parent)
print {*STDERR} "Warning: no common ancestor, pushing complete history\n";
- my $history = run_git("rev-list --first-parent --children ${local}");
+ my $history = run_git_quoted(["rev-list", "--first-parent", "--children", $local]);
my @history = split(/\n/, $history);
@history = @history[1..$#history];
foreach my $line (reverse @history) {
@@ -1233,12 +1246,12 @@ sub mw_push_revision {
foreach my $commit_info_split (@commit_pairs) {
my $sha1_child = @{$commit_info_split}[0];
my $sha1_commit = @{$commit_info_split}[1];
- my $diff_infos = run_git("diff-tree -r --raw -z ${sha1_child} ${sha1_commit}");
+ my $diff_infos = run_git_quoted(["diff-tree", "-r", "--raw", "-z", $sha1_child, $sha1_commit]);
# TODO: we could detect rename, and encode them with a #redirect on the wiki.
# TODO: for now, it's just a delete+add
my @diff_info_list = split(/\0/, $diff_infos);
# Keep the subject line of the commit message as mediawiki comment for the revision
- my $commit_msg = run_git(qq(log --no-walk --format="%s" ${sha1_commit}));
+ my $commit_msg = run_git_quoted(["log", "--no-walk", '--format="%s"', $sha1_commit]);
chomp($commit_msg);
# Push every blob
while (@diff_info_list) {
@@ -1263,7 +1276,10 @@ sub mw_push_revision {
}
}
if (!$dumb_push) {
- run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit}));
+ run_git_quoted(["notes", "--ref=${remotename}/mediawiki",
+ "add", "-f", "-m",
+ "mediawiki_revision: ${mw_revision}",
+ $sha1_commit]);
}
}
@@ -1304,7 +1320,7 @@ sub get_mw_namespace_id {
# already cached. Namespaces are stored in form:
# "Name_of_namespace:Id_namespace", ex.: "File:6".
my @temp = split(/\n/,
- run_git("config --get-all remote.${remotename}.namespaceCache"));
+ run_git_quoted(["config", "--get-all", "remote.${remotename}.namespaceCache"]));
chomp(@temp);
foreach my $ns (@temp) {
my ($n, $id) = split(/:/, $ns);
@@ -1358,7 +1374,7 @@ sub get_mw_namespace_id {
# Store explicitly requested namespaces on disk
if (!exists $cached_mw_namespace_id{$name}) {
- run_git(qq(config --add remote.${remotename}.namespaceCache "${name}:${store_id}"));
+ run_git_quoted(["config", "--add", "remote.${remotename}.namespaceCache", "${name}:${store_id}"]);
$cached_mw_namespace_id{$name} = 1;
}
return $id;
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
index 23b7ef9f62..5da825f61e 100644
--- a/contrib/mw-to-git/git-remote-mediawiki.txt
+++ b/contrib/mw-to-git/git-remote-mediawiki.txt
@@ -4,4 +4,4 @@ objects from mediawiki just as one would do with a classic git
repository thanks to remote-helpers.
For more information, visit the wiki at
-https://github.com/moy/Git-Mediawiki/wiki
+https://github.com/Git-Mediawiki/Git-Mediawiki
diff --git a/contrib/mw-to-git/t/.gitignore b/contrib/mw-to-git/t/.gitignore
index a7a40b4964..2b8dc30c6d 100644
--- a/contrib/mw-to-git/t/.gitignore
+++ b/contrib/mw-to-git/t/.gitignore
@@ -1,4 +1,4 @@
WEB/
-wiki/
+mediawiki/
trash directory.t*/
test-results/
diff --git a/contrib/mw-to-git/t/README b/contrib/mw-to-git/t/README
index 2ee34be7e4..72c4889db7 100644
--- a/contrib/mw-to-git/t/README
+++ b/contrib/mw-to-git/t/README
@@ -14,11 +14,11 @@ install the following packages (Debian/Ubuntu names, may need to be
adapted for another distribution):
* lighttpd
-* php5
-* php5-cgi
-* php5-cli
-* php5-curl
-* php5-sqlite
+* php
+* php-cgi
+* php-cli
+* php-curl
+* php-sqlite
Principles and Technical Choices
--------------------------------
diff --git a/contrib/mw-to-git/t/install-wiki/.gitignore b/contrib/mw-to-git/t/install-wiki/.gitignore
deleted file mode 100644
index b5a2a4408c..0000000000
--- a/contrib/mw-to-git/t/install-wiki/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-wikidb.sqlite
diff --git a/contrib/mw-to-git/t/install-wiki/LocalSettings.php b/contrib/mw-to-git/t/install-wiki/LocalSettings.php
deleted file mode 100644
index 745e47e881..0000000000
--- a/contrib/mw-to-git/t/install-wiki/LocalSettings.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-# This file was automatically generated by the MediaWiki 1.19.0
-# installer. If you make manual changes, please keep track in case you
-# need to recreate them later.
-#
-# See includes/DefaultSettings.php for all configurable settings
-# and their default values, but don't forget to make changes in _this_
-# file, not there.
-#
-# Further documentation for configuration settings may be found at:
-# http://www.mediawiki.org/wiki/Manual:Configuration_settings
-
-# Protect against web entry
-if ( !defined( 'MEDIAWIKI' ) ) {
- exit;
-}
-
-## Uncomment this to disable output compression
-# $wgDisableOutputCompression = true;
-
-$wgSitename = "Git-MediaWiki-Test";
-$wgMetaNamespace = "Git-MediaWiki-Test";
-
-## The URL base path to the directory containing the wiki;
-## defaults for all runtime URL paths are based off of this.
-## For more information on customizing the URLs please see:
-## http://www.mediawiki.org/wiki/Manual:Short_URL
-$wgScriptPath = "@WG_SCRIPT_PATH@";
-$wgScriptExtension = ".php";
-
-## The protocol and server name to use in fully-qualified URLs
-$wgServer = "@WG_SERVER@";
-
-## The relative URL path to the skins directory
-$wgStylePath = "$wgScriptPath/skins";
-
-## The relative URL path to the logo. Make sure you change this from the default,
-## or else you'll overwrite your logo when you upgrade!
-$wgLogo = "$wgStylePath/common/images/wiki.png";
-
-## UPO means: this is also a user preference option
-
-$wgEnableEmail = true;
-$wgEnableUserEmail = true; # UPO
-
-$wgEmergencyContact = "apache@localhost";
-$wgPasswordSender = "apache@localhost";
-
-$wgEnotifUserTalk = false; # UPO
-$wgEnotifWatchlist = false; # UPO
-$wgEmailAuthentication = true;
-
-## Database settings
-$wgDBtype = "sqlite";
-$wgDBserver = "";
-$wgDBname = "@WG_SQLITE_DATAFILE@";
-$wgDBuser = "";
-$wgDBpassword = "";
-
-# SQLite-specific settings
-$wgSQLiteDataDir = "@WG_SQLITE_DATADIR@";
-
-
-## Shared memory settings
-$wgMainCacheType = CACHE_NONE;
-$wgMemCachedServers = array();
-
-## To enable image uploads, make sure the 'images' directory
-## is writable, then set this to true:
-$wgEnableUploads = true;
-$wgUseImageMagick = true;
-$wgImageMagickConvertCommand ="@CONVERT@";
-$wgFileExtensions[] = 'txt';
-
-# InstantCommons allows wiki to use images from http://commons.wikimedia.org
-$wgUseInstantCommons = false;
-
-## If you use ImageMagick (or any other shell command) on a
-## Linux server, this will need to be set to the name of an
-## available UTF-8 locale
-$wgShellLocale = "en_US.utf8";
-
-## If you want to use image uploads under safe mode,
-## create the directories images/archive, images/thumb and
-## images/temp, and make them all writable. Then uncomment
-## this, if it's not already uncommented:
-#$wgHashedUploadDirectory = false;
-
-## Set $wgCacheDirectory to a writable directory on the web server
-## to make your wiki go slightly faster. The directory should not
-## be publicly accessible from the web.
-#$wgCacheDirectory = "$IP/cache";
-
-# Site language code, should be one of the list in ./languages/Names.php
-$wgLanguageCode = "en";
-
-$wgSecretKey = "1c912bfe3519fb70f5dc523ecc698111cd43d81a11c585b3eefb28f29c2699b7";
-#$wgSecretKey = "@SECRETKEY@";
-
-
-# Site upgrade key. Must be set to a string (default provided) to turn on the
-# web installer while LocalSettings.php is in place
-$wgUpgradeKey = "ddae7dc87cd0a645";
-
-## Default skin: you can change the default skin. Use the internal symbolic
-## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
-$wgDefaultSkin = "vector";
-
-## For attaching licensing metadata to pages, and displaying an
-## appropriate copyright notice / icon. GNU Free Documentation
-## License and Creative Commons licenses are supported so far.
-$wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-$wgRightsUrl = "";
-$wgRightsText = "";
-$wgRightsIcon = "";
-
-# Path to the GNU diff3 utility. Used for conflict resolution.
-$wgDiff3 = "/usr/bin/diff3";
-
-# Query string length limit for ResourceLoader. You should only set this if
-# your web server has a query string length limit (then set it to that limit),
-# or if you have suhosin.get.max_value_length set in php.ini (then set it to
-# that value)
-$wgResourceLoaderMaxQueryLength = -1;
-
-
-
-# End of automatically generated settings.
-# Add more configuration options below.
diff --git a/contrib/mw-to-git/t/install-wiki/db_install.php b/contrib/mw-to-git/t/install-wiki/db_install.php
deleted file mode 100644
index b033849800..0000000000
--- a/contrib/mw-to-git/t/install-wiki/db_install.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * This script generates a SQLite database for a MediaWiki version 1.19.0
- * You must specify the login of the admin (argument 1) and its
- * password (argument 2) and the folder where the database file
- * is located (absolute path in argument 3).
- * It is used by the script install-wiki.sh in order to make easy the
- * installation of a MediaWiki.
- *
- * In order to generate a SQLite database file, MediaWiki ask the user
- * to submit some forms in its web browser. This script simulates this
- * behavior though the functions <get> and <submit>
- *
- */
-$argc = $_SERVER['argc'];
-$argv = $_SERVER['argv'];
-
-$login = $argv[2];
-$pass = $argv[3];
-$tmp = $argv[4];
-$port = $argv[5];
-
-$url = 'http://localhost:'.$port.'/wiki/mw-config/index.php';
-$db_dir = urlencode($tmp);
-$tmp_cookie = tempnam($tmp, "COOKIE_");
-/*
- * Fetches a page with cURL.
- */
-function get($page_name = "") {
- $curl = curl_init();
- $page_name_add = "";
- if ($page_name != "") {
- $page_name_add = '?page='.$page_name;
- }
- $url = $GLOBALS['url'].$page_name_add;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
- curl_setopt($curl, CURLOPT_HEADER, true);
- curl_setopt($curl, CURLOPT_URL, $url);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return $page;
-}
-
-/*
- * Submits a form with cURL.
- */
-function submit($page_name, $option = "") {
- $curl = curl_init();
- $datapost = 'submit-continue=Continue+%E2%86%92';
- if ($option != "") {
- $datapost = $option.'&'.$datapost;
- }
- $url = $GLOBALS['url'].'?page='.$page_name;
- $tmp_cookie = $GLOBALS['tmp_cookie'];
- curl_setopt($curl, CURLOPT_URL, $url);
- curl_setopt($curl, CURLOPT_POST, true);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_POSTFIELDS, $datapost);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
- curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
-
- $page = curl_exec($curl);
- if (!$page) {
- die("Could not get page: $url\n");
- }
- curl_close($curl);
- return "$page";
-}
-
-/*
- * Here starts this script: simulates the behavior of the user
- * submitting forms to generates the database file.
- * Note this simulation was made for the MediaWiki version 1.19.0,
- * we can't assume it works with other versions.
- *
- */
-
-$page = get();
-if (!preg_match('/input type="hidden" value="([0-9]+)" name="LanguageRequestTime"/',
- $page, $matches)) {
- echo "Unexpected content for page downloaded:\n";
- echo "$page";
- die;
-};
-$timestamp = $matches[1];
-$language = "LanguageRequestTime=$timestamp&uselang=en&ContLang=en";
-$page = submit('Language', $language);
-
-submit('Welcome');
-
-$db_config = 'DBType=sqlite';
-$db_config = $db_config.'&sqlite_wgSQLiteDataDir='.$db_dir;
-$db_config = $db_config.'&sqlite_wgDBname='.$argv[1];
-submit('DBConnect', $db_config);
-
-$wiki_config = 'config_wgSitename=TEST';
-$wiki_config = $wiki_config.'&config__NamespaceType=site-name';
-$wiki_config = $wiki_config.'&config_wgMetaNamespace=MyWiki';
-$wiki_config = $wiki_config.'&config__AdminName='.$login;
-
-$wiki_config = $wiki_config.'&config__AdminPassword='.$pass;
-$wiki_config = $wiki_config.'&config__AdminPassword2='.$pass;
-
-$wiki_config = $wiki_config.'&wiki__configEmail=email%40email.org';
-$wiki_config = $wiki_config.'&config__SkipOptional=skip';
-submit('Name', $wiki_config);
-submit('Install');
-submit('Install');
-
-unlink($tmp_cookie);
-?>
diff --git a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
index 9106833578..4c39bda7bf 100755
--- a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
+++ b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
@@ -28,7 +28,7 @@ test_expect_success 'Git clone creates the expected git log with one file' '
git log --format=%s HEAD^..HEAD >log.tmp
) &&
echo "this must be the same" >msg.tmp &&
- diff -b mw_dir_1/log.tmp msg.tmp
+ test_cmp msg.tmp mw_dir_1/log.tmp
'
@@ -50,8 +50,8 @@ test_expect_success 'Git clone creates the expected git log with multiple files'
echo "this must be the same" >>msgDaddy.tmp &&
echo "identical too" >msgDj.tmp &&
echo "identical" >>msgDj.tmp &&
- diff -b mw_dir_2/logDaddy.tmp msgDaddy.tmp &&
- diff -b mw_dir_2/logDj.tmp msgDj.tmp
+ test_cmp msgDaddy.tmp mw_dir_2/logDaddy.tmp &&
+ test_cmp msgDj.tmp mw_dir_2/logDj.tmp
'
@@ -135,7 +135,7 @@ test_expect_success 'Git clone works with one specific page cloned ' '
cd mw_dir_8 &&
echo "this log must stay" >msg.tmp &&
git log --format=%s >log.tmp &&
- diff -b msg.tmp log.tmp
+ test_cmp msg.tmp log.tmp
) &&
wiki_check_content mw_dir_8/Namnam.mw Namnam
'
diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
index 3ff3a09567..6187ec67fa 100755
--- a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
+++ b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
@@ -27,12 +27,12 @@ test_git_reimport () {
# Don't bother with permissions, be administrator by default
test_expect_success 'setup config' '
- git config --global remote.origin.mwLogin WikiAdmin &&
- git config --global remote.origin.mwPassword AdminPass &&
+ git config --global remote.origin.mwLogin "$WIKI_ADMIN" &&
+ git config --global remote.origin.mwPassword "$WIKI_PASSW" &&
test_might_fail git config --global --unset remote.origin.mediaImport
'
-test_expect_success 'git push can upload media (File:) files' '
+test_expect_failure 'git push can upload media (File:) files' '
wiki_reset &&
git clone mediawiki::'"$WIKI_URL"' mw_dir &&
(
@@ -48,13 +48,14 @@ test_expect_success 'git push can upload media (File:) files' '
)
'
-test_expect_success 'git clone works on previously created wiki with media files' '
+test_expect_failure 'git clone works on previously created wiki with media files' '
test_when_finished "rm -rf mw_dir mw_dir_clone" &&
git clone -c remote.origin.mediaimport=true \
mediawiki::'"$WIKI_URL"' mw_dir_clone &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt &&
(cd mw_dir_clone && git checkout HEAD^) &&
(cd mw_dir && git checkout HEAD^) &&
+ test_path_is_file mw_dir_clone/Foo.txt &&
test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt
'
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
index 3948a00282..64e46c1671 100755
--- a/contrib/mw-to-git/t/test-gitmw-lib.sh
+++ b/contrib/mw-to-git/t/test-gitmw-lib.sh
@@ -13,7 +13,8 @@
. ./test.config
-WIKI_URL=http://"$SERVER_ADDR:$PORT/$WIKI_DIR_NAME"
+WIKI_BASE_URL=http://$SERVER_ADDR:$PORT
+WIKI_URL=$WIKI_BASE_URL/$WIKI_DIR_NAME
CURR_DIR=$(pwd)
TEST_OUTPUT_DIRECTORY=$(pwd)
TEST_DIRECTORY="$CURR_DIR"/../../../t
@@ -65,7 +66,7 @@ test_check_precond () {
GIT_EXEC_PATH=$(cd "$(dirname "$0")" && cd "../.." && pwd)
PATH="$GIT_EXEC_PATH"'/bin-wrapper:'"$PATH"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ];
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
then
skip_all='skipping gateway git-mw tests, no mediawiki found'
test_done
@@ -291,27 +292,59 @@ stop_lighttpd () {
test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid")
}
-# Create the SQLite database of the MediaWiki. If the database file already
-# exists, it will be deleted.
-# This script should be runned from the directory where $FILES_FOLDER is
-# located.
-create_db () {
- rm -f "$TMP/$DB_FILE"
-
- echo "Generating the SQLite database file. It can take some time ..."
- # Run the php script to generate the SQLite database file
- # with cURL calls.
- php "$FILES_FOLDER/$DB_INSTALL_SCRIPT" $(basename "$DB_FILE" .sqlite) \
- "$WIKI_ADMIN" "$WIKI_PASSW" "$TMP" "$PORT"
-
- if [ ! -f "$TMP/$DB_FILE" ] ; then
- error "Can't create database file $TMP/$DB_FILE. Try to run ./install-wiki.sh delete first."
+wiki_delete_db () {
+ rm -rf \
+ "$FILES_FOLDER_DB"/* || error "Couldn't delete $FILES_FOLDER_DB/"
+}
+
+wiki_delete_db_backup () {
+ rm -rf \
+ "$FILES_FOLDER_POST_INSTALL_DB"/* || error "Couldn't delete $FILES_FOLDER_POST_INSTALL_DB/"
+}
+
+# Install MediaWiki using its install.php script. If the database file
+# already exists, it will be deleted.
+install_mediawiki () {
+
+ localsettings="$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php"
+ if test -f "$localsettings"
+ then
+ error "We already installed the wiki, since $localsettings exists" \
+ "perhaps you wanted to run 'delete' first?"
fi
- # Copy the generated database file into the directory the
- # user indicated.
- cp "$TMP/$DB_FILE" "$FILES_FOLDER" ||
- error "Unable to copy $TMP/$DB_FILE to $FILES_FOLDER"
+ wiki_delete_db
+ wiki_delete_db_backup
+ mkdir \
+ "$FILES_FOLDER_DB/" \
+ "$FILES_FOLDER_POST_INSTALL_DB/"
+
+ install_script="$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php"
+ echo "Installing MediaWiki using $install_script. This may take some time ..."
+
+ php "$WIKI_DIR_INST/$WIKI_DIR_NAME/maintenance/install.php" \
+ --server $WIKI_BASE_URL \
+ --scriptpath /wiki \
+ --lang en \
+ --dbtype sqlite \
+ --dbpath $PWD/$FILES_FOLDER_DB/ \
+ --pass "$WIKI_PASSW" \
+ Git-MediaWiki-Test \
+ "$WIKI_ADMIN" ||
+ error "Couldn't run $install_script, see errors above. Try to run ./install-wiki.sh delete first."
+ cat <<-'EOF' >>$localsettings
+# Custom settings added by test-gitmw-lib.sh
+#
+# Uploading text files is needed for
+# t9363-mw-to-git-export-import.sh
+$wgEnableUploads = true;
+$wgFileExtensions[] = 'txt';
+EOF
+
+ # Copy the initially generated database file into our backup
+ # folder
+ cp -R "$FILES_FOLDER_DB/"* "$FILES_FOLDER_POST_INSTALL_DB/" ||
+ error "Unable to copy $FILES_FOLDER_DB/* to $FILES_FOLDER_POST_INSTALL_DB/*"
}
# Install a wiki in your web server directory.
@@ -320,30 +353,33 @@ wiki_install () {
start_lighttpd
fi
- SERVER_ADDR=$SERVER_ADDR:$PORT
# In this part, we change directory to $TMP in order to download,
# unpack and copy the files of MediaWiki
(
mkdir -p "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ] ; then
+ if ! test -d "$WIKI_DIR_INST/$WIKI_DIR_NAME"
+ then
error "Folder $WIKI_DIR_INST/$WIKI_DIR_NAME doesn't exist.
Please create it and launch the script again."
fi
- # Fetch MediaWiki's archive if not already present in the TMP directory
+ # Fetch MediaWiki's archive if not already present in the
+ # download directory
+ mkdir -p "$FILES_FOLDER_DOWNLOAD"
MW_FILENAME="mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
- cd "$TMP"
- if [ ! -f $MW_FILENAME ] ; then
+ cd "$FILES_FOLDER_DOWNLOAD"
+ if ! test -f $MW_FILENAME
+ then
echo "Downloading $MW_VERSION_MAJOR.$MW_VERSION_MINOR sources ..."
wget "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/$MW_FILENAME" ||
error "Unable to download "\
"http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\
"$MW_FILENAME. "\
"Please fix your connection and launch the script again."
- echo "$MW_FILENAME downloaded in $(pwd). "\
- "You can delete it later if you want."
+ echo "$MW_FILENAME downloaded in $(pwd)/;" \
+ "you can delete it later if you want."
else
- echo "Reusing existing $MW_FILENAME downloaded in $(pwd)."
+ echo "Reusing existing $MW_FILENAME downloaded in $(pwd)/"
fi
archive_abs_path=$(pwd)/$MW_FILENAME
cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
@@ -352,48 +388,12 @@ wiki_install () {
error "Unable to extract WikiMedia's files from $archive_abs_path to "\
"$WIKI_DIR_INST/$WIKI_DIR_NAME"
) || exit 1
+ echo Extracted in "$WIKI_DIR_INST/$WIKI_DIR_NAME"
- create_db
-
- # Copy the generic LocalSettings.php in the web server's directory
- # And modify parameters according to the ones set at the top
- # of this script.
- # Note that LocalSettings.php is never modified.
- if [ ! -f "$FILES_FOLDER/LocalSettings.php" ] ; then
- error "Can't find $FILES_FOLDER/LocalSettings.php " \
- "in the current folder. "\
- "Please run the script inside its folder."
- fi
- cp "$FILES_FOLDER/LocalSettings.php" \
- "$FILES_FOLDER/LocalSettings-tmp.php" ||
- error "Unable to copy $FILES_FOLDER/LocalSettings.php " \
- "to $FILES_FOLDER/LocalSettings-tmp.php"
-
- # Parse and set the LocalSettings file of the user according to the
- # CONFIGURATION VARIABLES section at the beginning of this script
- file_swap="$FILES_FOLDER/LocalSettings-swap.php"
- sed "s,@WG_SCRIPT_PATH@,/$WIKI_DIR_NAME," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SERVER@,http://$SERVER_ADDR," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATADIR@,$TMP," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
- sed "s,@WG_SQLITE_DATAFILE@,$( basename $DB_FILE .sqlite)," \
- "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
- mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
-
- mv "$FILES_FOLDER/LocalSettings-tmp.php" \
- "$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php" ||
- error "Unable to move $FILES_FOLDER/LocalSettings-tmp.php" \
- "in $WIKI_DIR_INST/$WIKI_DIR_NAME"
- echo "File $FILES_FOLDER/LocalSettings.php is set in" \
- " $WIKI_DIR_INST/$WIKI_DIR_NAME"
+ install_mediawiki
echo "Your wiki has been installed. You can check it at
- http://$SERVER_ADDR/$WIKI_DIR_NAME"
+ $WIKI_URL"
}
# Reset the database of the wiki and the password of the admin
@@ -401,12 +401,18 @@ wiki_install () {
# Warning: This function must be called only in a subdirectory of t/ directory
wiki_reset () {
# Copy initial database of the wiki
- if [ ! -f "../$FILES_FOLDER/$DB_FILE" ] ; then
- error "Can't find ../$FILES_FOLDER/$DB_FILE in the current folder."
+ if ! test -d "../$FILES_FOLDER_DB"
+ then
+ error "No wiki database at ../$FILES_FOLDER_DB, not installed yet?"
+ fi
+ if ! test -d "../$FILES_FOLDER_POST_INSTALL_DB"
+ then
+ error "No wiki backup database at ../$FILES_FOLDER_POST_INSTALL_DB, failed installation?"
fi
- cp "../$FILES_FOLDER/$DB_FILE" "$TMP" ||
- error "Can't copy ../$FILES_FOLDER/$DB_FILE in $TMP"
- echo "File $FILES_FOLDER/$DB_FILE is set in $TMP"
+ wiki_delete_db
+ cp -R "../$FILES_FOLDER_POST_INSTALL_DB/"* "../$FILES_FOLDER_DB/" ||
+ error "Can't copy ../$FILES_FOLDER_POST_INSTALL_DB/* to ../$FILES_FOLDER_DB/*"
+ echo "File $FILES_FOLDER_DB/* has been reset"
}
# Delete the wiki created in the web server's directory and all its content
@@ -420,13 +426,7 @@ wiki_delete () {
rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" ||
error "Wiki's directory $WIKI_DIR_INST/" \
"$WIKI_DIR_NAME could not be deleted"
- # Delete the wiki's SQLite database.
- rm -f "$TMP/$DB_FILE" ||
- error "Database $TMP/$DB_FILE could not be deleted."
fi
-
- # Delete the wiki's SQLite database
- rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
- rm -f "$FILES_FOLDER/$DB_FILE"
- rm -rf "$TMP/mediawiki-$MW_VERSION_MAJOR.$MW_VERSION_MINOR.tar.gz"
+ wiki_delete_db
+ wiki_delete_db_backup
}
diff --git a/contrib/mw-to-git/t/test-gitmw.pl b/contrib/mw-to-git/t/test-gitmw.pl
index 0ff76259fa..c5d687f078 100755
--- a/contrib/mw-to-git/t/test-gitmw.pl
+++ b/contrib/mw-to-git/t/test-gitmw.pl
@@ -24,9 +24,7 @@
use MediaWiki::API;
use Getopt::Long;
-use encoding 'utf8';
use DateTime::Format::ISO8601;
-use open ':encoding(utf8)';
use constant SLASH_REPLACEMENT => "%2F";
#Parsing of the config file
@@ -87,7 +85,7 @@ sub wiki_getpage {
# Replace spaces by underscore in the page name
$pagename =~ s/ /_/g;
$pagename =~ s/\//%2F/g;
- open(my $file, ">$destdir/$pagename.mw");
+ open(my $file, ">:encoding(UTF-8)", "$destdir/$pagename.mw");
print $file "$content";
close ($file);
@@ -172,7 +170,7 @@ sub wiki_getallpagename {
cmlimit => 500 },
)
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -185,7 +183,7 @@ sub wiki_getallpagename {
aplimit => 500,
})
|| die $mw->{error}->{code}.": ".$mw->{error}->{details};
- open(my $file, ">all.txt");
+ open(my $file, ">:encoding(UTF-8)", "all.txt");
foreach my $page (@{$mw_pages}) {
print $file "$page->{title}\n";
}
@@ -214,12 +212,12 @@ my $fct_to_call = shift;
wiki_login($wiki_admin, $wiki_admin_pass);
-my %functions_to_call = qw(
- upload_file wiki_upload_file
- get_page wiki_getpage
- delete_page wiki_delete_page
- edit_page wiki_editpage
- getallpagename wiki_getallpagename
+my %functions_to_call = (
+ upload_file => \&wiki_upload_file,
+ get_page => \&wiki_getpage,
+ delete_page => \&wiki_delete_page,
+ edit_page => \&wiki_editpage,
+ getallpagename => \&wiki_getallpagename,
);
die "$0 ERROR: wrong argument" unless exists $functions_to_call{$fct_to_call};
-&{$functions_to_call{$fct_to_call}}(@ARGV);
+$functions_to_call{$fct_to_call}->(map { utf8::decode($_); $_ } @ARGV);
diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config
index 5ba0684162..ed10b3e4a4 100644
--- a/contrib/mw-to-git/t/test.config
+++ b/contrib/mw-to-git/t/test.config
@@ -3,15 +3,11 @@ WIKI_DIR_NAME=wiki
# Login and password of the wiki's admin
WIKI_ADMIN=WikiAdmin
-WIKI_PASSW=AdminPass
+WIKI_PASSW=AdminPass1
# Address of the web server
SERVER_ADDR=localhost
-# SQLite database of the wiki, named DB_FILE, is located in TMP
-TMP=/tmp
-DB_FILE=wikidb.sqlite
-
# If LIGHTTPD is not set to true, the script will use the default
# web server running in WIKI_DIR_INST.
WIKI_DIR_INST=/var/www
@@ -28,10 +24,17 @@ WEB=WEB
WEB_TMP=$WEB/tmp
WEB_WWW=$WEB/www
+# Where our configuration for the wiki is located
+FILES_FOLDER=mediawiki
+FILES_FOLDER_DOWNLOAD=$FILES_FOLDER/download
+FILES_FOLDER_DB=$FILES_FOLDER/db
+FILES_FOLDER_POST_INSTALL_DB=$FILES_FOLDER/post-install-db
+
# The variables below are used by the script to install a wiki.
# You should not modify these unless you are modifying the script itself.
-# tested versions: 1.19.X -> 1.21.1
-MW_VERSION_MAJOR=1.21
-MW_VERSION_MINOR=1
-FILES_FOLDER=install-wiki
-DB_INSTALL_SCRIPT=db_install.php
+# tested versions: 1.19.X -> 1.21.1 -> 1.34.2
+#
+# See https://www.mediawiki.org/wiki/Download for what the latest
+# version is.
+MW_VERSION_MAJOR=1.34
+MW_VERSION_MINOR=2
diff --git a/diff-lib.c b/diff-lib.c
index 5d5d3dafab..346fdcf0b0 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -565,3 +565,28 @@ int index_differs_from(struct repository *r,
object_array_clear(&rev.pending);
return (rev.diffopt.flags.has_changes != 0);
}
+
+static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data)
+{
+ return data;
+}
+
+void show_interdiff(const struct object_id *oid1, const struct object_id *oid2,
+ int indent, struct diff_options *diffopt)
+{
+ struct diff_options opts;
+ struct strbuf prefix = STRBUF_INIT;
+
+ memcpy(&opts, diffopt, sizeof(opts));
+ opts.output_format = DIFF_FORMAT_PATCH;
+ opts.output_prefix = idiff_prefix_cb;
+ strbuf_addchars(&prefix, ' ', indent);
+ opts.output_prefix_data = &prefix;
+ diff_setup_done(&opts);
+
+ diff_tree_oid(oid1, oid2, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+
+ strbuf_release(&prefix);
+}
diff --git a/diff.c b/diff.c
index a5114fa864..2bb2f8f57e 100644
--- a/diff.c
+++ b/diff.c
@@ -482,14 +482,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
static char *quote_two(const char *one, const char *two)
{
- int need_one = quote_c_style(one, NULL, NULL, 1);
- int need_two = quote_c_style(two, NULL, NULL, 1);
+ int need_one = quote_c_style(one, NULL, NULL, CQUOTE_NODQ);
+ int need_two = quote_c_style(two, NULL, NULL, CQUOTE_NODQ);
struct strbuf res = STRBUF_INIT;
if (need_one + need_two) {
strbuf_addch(&res, '"');
- quote_c_style(one, &res, NULL, 1);
- quote_c_style(two, &res, NULL, 1);
+ quote_c_style(one, &res, NULL, CQUOTE_NODQ);
+ quote_c_style(two, &res, NULL, CQUOTE_NODQ);
strbuf_addch(&res, '"');
} else {
strbuf_addstr(&res, one);
@@ -3663,7 +3663,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
{
mmfile_t mf1, mf2;
struct diffstat_file *data;
- int same_contents;
+ int may_differ;
int complete_rewrite = 0;
if (!DIFF_PAIR_UNMERGED(p)) {
@@ -3681,12 +3681,14 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
return;
}
- same_contents = oideq(&one->oid, &two->oid);
+ /* saves some reads if true, not a guarantee of diff outcome */
+ may_differ = !(one->oid_valid && two->oid_valid &&
+ oideq(&one->oid, &two->oid));
if (diff_filespec_is_binary(o->repo, one) ||
diff_filespec_is_binary(o->repo, two)) {
data->is_binary = 1;
- if (same_contents) {
+ if (!may_differ) {
data->added = 0;
data->deleted = 0;
} else {
@@ -3702,7 +3704,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
data->added = count_lines(two->data, two->size);
}
- else if (!same_contents) {
+ else if (may_differ) {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
xdemitconf_t xecfg;
@@ -3727,7 +3729,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
diffstat->files[diffstat->nr - 1];
/*
* Omit diffstats of modified files where nothing changed.
- * Even if !same_contents, this might be the case due to
+ * Even if may_differ, this might be the case due to
* ignoring whitespace changes, etc.
*
* But note that we special-case additions, deletions,
diff --git a/diff.h b/diff.h
index e0c0af6286..3de343270f 100644
--- a/diff.h
+++ b/diff.h
@@ -287,8 +287,6 @@ struct diff_options {
/* If non-zero, then stop computing after this many changes. */
int max_changes;
- /* For internal use only. */
- int num_changes;
int ita_invisible_in_index;
/* white-space error highlighting */
@@ -601,6 +599,13 @@ int index_differs_from(struct repository *r, const char *def,
int ita_invisible_in_index);
/*
+ * Emit an interdiff of two object ID's to 'diff_options.file' optionally
+ * indented by 'indent' spaces.
+ */
+void show_interdiff(const struct object_id *, const struct object_id *,
+ int indent, struct diff_options *);
+
+/*
* Fill the contents of the filespec "df", respecting any textconv defined by
* its userdiff driver. The "driver" parameter must come from a
* previous call to get_textconv(), and therefore should either be NULL or have
diff --git a/fuzz-commit-graph.c b/fuzz-commit-graph.c
index 430817214d..e7cf6d5b0f 100644
--- a/fuzz-commit-graph.c
+++ b/fuzz-commit-graph.c
@@ -1,7 +1,8 @@
#include "commit-graph.h"
#include "repository.h"
-struct commit_graph *parse_commit_graph(void *graph_map, size_t graph_size);
+struct commit_graph *parse_commit_graph(struct repository *r,
+ void *graph_map, size_t graph_size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
@@ -10,7 +11,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
struct commit_graph *g;
initialize_the_repository();
- g = parse_commit_graph((void *)data, size);
+ g = parse_commit_graph(the_repository, (void *)data, size);
repo_clear(the_repository);
free_commit_graph(g);
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index 45fecf8bdf..9dbbb08e70 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -10,7 +10,7 @@ command_list () {
}
get_categories () {
- tr ' ' '\n'|
+ tr ' ' '\012'|
grep -v '^$' |
sort |
uniq
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index f36c0078ac..8a72018712 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -714,7 +714,7 @@ sub parse_diff {
if (defined $patch_mode_revision) {
push @diff_cmd, get_diff_reference($patch_mode_revision);
}
- my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
+ my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
my @colored = ();
if ($diff_use_color) {
my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
@@ -754,13 +754,16 @@ sub parse_diff_header {
my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
- my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
+ my $addition;
for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
+ if ($src->{TEXT}->[$i] =~ /^new file/) {
+ $addition = 1;
+ $head->{TYPE} = 'addition';
+ }
my $dest =
$src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
$src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
- $src->{TEXT}->[$i] =~ /^new file/ ? $addition :
$head;
push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
@@ -1501,12 +1504,6 @@ sub patch_update_file {
push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
}
@hunk = ($deletion);
- } elsif (@{$addition->{TEXT}}) {
- foreach my $hunk (@hunk) {
- push @{$addition->{TEXT}}, @{$hunk->{TEXT}};
- push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};
- }
- @hunk = ($addition);
}
$num = scalar @hunk;
@@ -1516,6 +1513,7 @@ sub patch_update_file {
my ($prev, $next, $other, $undecided, $i);
$other = '';
+ last if ($ix and !$num);
if ($num <= $ix) {
$ix = 0;
}
@@ -1548,35 +1546,51 @@ sub patch_update_file {
last;
}
}
- last if (!$undecided);
+ last if (!$undecided && ($num || !$addition));
- if ($hunk[$ix]{TYPE} eq 'hunk' &&
- hunk_splittable($hunk[$ix]{TEXT})) {
- $other .= ',s';
- }
- if ($hunk[$ix]{TYPE} eq 'hunk') {
- $other .= ',e';
- }
- for (@{$hunk[$ix]{DISPLAY}}) {
- print;
+ if ($num) {
+ if ($hunk[$ix]{TYPE} eq 'hunk' &&
+ hunk_splittable($hunk[$ix]{TEXT})) {
+ $other .= ',s';
+ }
+ if ($hunk[$ix]{TYPE} eq 'hunk') {
+ $other .= ',e';
+ }
+ for (@{$hunk[$ix]{DISPLAY}}) {
+ print;
+ }
}
- print colored $prompt_color, "(", ($ix+1), "/$num) ",
- sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
+ my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};
+ print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ",
+ sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);
my $line = prompt_single_character;
last unless defined $line;
if ($line) {
if ($line =~ /^y/i) {
- $hunk[$ix]{USE} = 1;
+ if ($num) {
+ $hunk[$ix]{USE} = 1;
+ } else {
+ $head->{USE} = 1;
+ }
}
elsif ($line =~ /^n/i) {
- $hunk[$ix]{USE} = 0;
+ if ($num) {
+ $hunk[$ix]{USE} = 0;
+ } else {
+ $head->{USE} = 0;
+ }
}
elsif ($line =~ /^a/i) {
- while ($ix < $num) {
- if (!defined $hunk[$ix]{USE}) {
- $hunk[$ix]{USE} = 1;
+ if ($num) {
+ while ($ix < $num) {
+ if (!defined $hunk[$ix]{USE}) {
+ $hunk[$ix]{USE} = 1;
+ }
+ $ix++;
}
+ } else {
+ $head->{USE} = 1;
$ix++;
}
next;
@@ -1613,19 +1627,28 @@ sub patch_update_file {
next;
}
elsif ($line =~ /^d/i) {
- while ($ix < $num) {
- if (!defined $hunk[$ix]{USE}) {
- $hunk[$ix]{USE} = 0;
+ if ($num) {
+ while ($ix < $num) {
+ if (!defined $hunk[$ix]{USE}) {
+ $hunk[$ix]{USE} = 0;
+ }
+ $ix++;
}
+ } else {
+ $head->{USE} = 0;
$ix++;
}
next;
}
elsif ($line =~ /^q/i) {
- for ($i = 0; $i < $num; $i++) {
- if (!defined $hunk[$i]{USE}) {
- $hunk[$i]{USE} = 0;
+ if ($num) {
+ for ($i = 0; $i < $num; $i++) {
+ if (!defined $hunk[$i]{USE}) {
+ $hunk[$i]{USE} = 0;
+ }
}
+ } elsif (!defined $head->{USE}) {
+ $head->{USE} = 0;
}
$quit = 1;
last;
@@ -1743,7 +1766,7 @@ sub patch_update_file {
}
}
- @hunk = coalesce_overlapping_hunks(@hunk);
+ @hunk = coalesce_overlapping_hunks(@hunk) if ($num);
my $n_lofs = 0;
my @result = ();
@@ -1753,7 +1776,7 @@ sub patch_update_file {
}
}
- if (@result) {
+ if (@result or $head->{USE}) {
my @patch = reassemble_patch($head->{TEXT}, @result);
my $apply_routine = $patch_mode_flavour{APPLY};
&$apply_routine(@patch);
diff --git a/git-bisect.sh b/git-bisect.sh
index 2f60fefcfa..ea7e684ebb 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -49,27 +49,6 @@ bisect_head()
fi
}
-bisect_autostart() {
- test -s "$GIT_DIR/BISECT_START" || {
- gettextln "You need to start by \"git bisect start\"" >&2
- if test -t 0
- then
- # TRANSLATORS: Make sure to include [Y] and [n] in your
- # translation. The program will only accept English input
- # at this point.
- gettext "Do you want me to do it for you [Y/n]? " >&2
- read yesno
- case "$yesno" in
- [Nn]*)
- exit ;;
- esac
- bisect_start
- else
- exit 1
- fi
- }
-}
-
bisect_start() {
git bisect--helper --bisect-start $@ || exit
@@ -86,8 +65,7 @@ bisect_start() {
#
# Check if we can proceed to the next bisect state.
#
- get_terms
- bisect_auto_next
+ git bisect--helper --bisect-auto-next || exit
trap '-' 0
}
@@ -108,7 +86,7 @@ bisect_skip() {
}
bisect_state() {
- bisect_autostart
+ git bisect--helper --bisect-autostart || exit
state=$1
git bisect--helper --check-and-set-terms $state $TERM_GOOD $TERM_BAD || exit
get_terms
@@ -140,45 +118,7 @@ bisect_state() {
*)
usage ;;
esac
- bisect_auto_next
-}
-
-bisect_auto_next() {
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD && bisect_next || :
-}
-
-bisect_next() {
- case "$#" in 0) ;; *) usage ;; esac
- bisect_autostart
- git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD $TERM_GOOD|| exit
-
- # Perform all bisection computation, display and checkout
- git bisect--helper --next-all
- res=$?
-
- # Check if we should exit because bisection is finished
- if test $res -eq 10
- then
- bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
- bad_commit=$(git show-branch $bad_rev)
- echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
- exit 0
- elif test $res -eq 2
- then
- echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
- good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
- for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
- do
- skipped_commit=$(git show-branch $skipped)
- echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
- done
- exit $res
- fi
-
- # Check for an error in the bisection process
- test $res -ne 0 && exit $res
-
- return 0
+ git bisect--helper --bisect-auto-next
}
bisect_visualize() {
@@ -234,7 +174,7 @@ bisect_replay () {
esac
done <"$file"
IFS="$oIFS"
- bisect_auto_next
+ git bisect--helper --bisect-auto-next || exit
}
bisect_run () {
@@ -331,7 +271,7 @@ case "$#" in
bisect_skip "$@" ;;
next)
# Not sure we want "next" at the UI level anymore.
- bisect_next "$@" ;;
+ git bisect--helper --bisect-next "$@" || exit ;;
visualize|view)
bisect_visualize "$@" ;;
reset)
diff --git a/git-p4.py b/git-p4.py
index ca79dc0900..4433ca53de 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -4237,7 +4237,7 @@ class P4Unshelve(Command):
"""
for parent in (range(65535)):
- log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent))
+ log = extractLogMessageFromGitCommit("{0}~{1}".format(starting_point, parent))
settings = extractSettingsGitLog(log)
if 'change' in settings:
return settings
diff --git a/git.c b/git.c
index 01c456edce..f1e8b56d99 100644
--- a/git.c
+++ b/git.c
@@ -534,6 +534,7 @@ static struct cmd_struct commands[] = {
{ "ls-tree", cmd_ls_tree, RUN_SETUP },
{ "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT },
{ "mailsplit", cmd_mailsplit, NO_PARSEOPT },
+ { "maintenance", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT },
{ "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
diff --git a/interdiff.c b/interdiff.c
deleted file mode 100644
index c81d680a6c..0000000000
--- a/interdiff.c
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "revision.h"
-#include "interdiff.h"
-
-static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data)
-{
- return data;
-}
-
-void show_interdiff(struct rev_info *rev, int indent)
-{
- struct diff_options opts;
- struct strbuf prefix = STRBUF_INIT;
-
- memcpy(&opts, &rev->diffopt, sizeof(opts));
- opts.output_format = DIFF_FORMAT_PATCH;
- opts.output_prefix = idiff_prefix_cb;
- strbuf_addchars(&prefix, ' ', indent);
- opts.output_prefix_data = &prefix;
- diff_setup_done(&opts);
-
- diff_tree_oid(rev->idiff_oid1, rev->idiff_oid2, "", &opts);
- diffcore_std(&opts);
- diff_flush(&opts);
-
- strbuf_release(&prefix);
-}
diff --git a/interdiff.h b/interdiff.h
deleted file mode 100644
index 01c730a5c9..0000000000
--- a/interdiff.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef INTERDIFF_H
-#define INTERDIFF_H
-
-struct rev_info;
-
-void show_interdiff(struct rev_info *, int indent);
-
-#endif
diff --git a/line-log.c b/line-log.c
index bf73ea95ac..68eeb425f8 100644
--- a/line-log.c
+++ b/line-log.c
@@ -1159,7 +1159,7 @@ static int bloom_filter_check(struct rev_info *rev,
return 1;
if (!rev->bloom_filter_settings ||
- !(filter = get_bloom_filter(rev->repo, commit, 0)))
+ !(filter = get_bloom_filter(rev->repo, commit)))
return 1;
if (!range)
diff --git a/log-tree.c b/log-tree.c
index 55a68d0c61..cb8942fec1 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -15,7 +15,6 @@
#include "sequencer.h"
#include "line-log.h"
#include "help.h"
-#include "interdiff.h"
#include "range-diff.h"
static struct decoration name_decoration = { "object names" };
@@ -800,7 +799,8 @@ void show_log(struct rev_info *opt)
next_commentary_block(opt, NULL);
fprintf_ln(opt->diffopt.file, "%s", opt->idiff_title);
- show_interdiff(opt, 2);
+ show_interdiff(opt->idiff_oid1, opt->idiff_oid2, 2,
+ &opt->diffopt);
memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff));
}
@@ -917,26 +917,15 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
}
/* More than one parent? */
- if (parents && parents->next) {
+ if (parents->next) {
if (opt->ignore_merges)
return 0;
else if (opt->combine_merges)
return do_diff_combined(opt, commit);
- else if (opt->first_parent_only) {
- /*
- * Generate merge log entry only for the first
- * parent, showing summary diff of the others
- * we merged _in_.
- */
- parse_commit_or_die(parents->item);
- diff_tree_oid(get_commit_tree_oid(parents->item),
- oid, "", &opt->diffopt);
- log_tree_diff_flush(opt);
- return !opt->loginfo;
+ else if (!opt->first_parent_only) {
+ /* If we show multiple diffs, show the parent info */
+ log->parent = parents->item;
}
-
- /* If we show individual diffs, show the parent info */
- log->parent = parents->item;
}
showed_log = 0;
@@ -952,7 +941,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* Set up the log info for the next parent, if any.. */
parents = parents->next;
- if (!parents)
+ if (!parents || opt->first_parent_only)
break;
log->parent = parents->item;
opt->loginfo = log;
diff --git a/mergetools/meld b/mergetools/meld
index 7a08470f88..aab4ebb935 100644
--- a/mergetools/meld
+++ b/mergetools/meld
@@ -3,34 +3,87 @@ diff_cmd () {
}
merge_cmd () {
- if test -z "${meld_has_output_option:+set}"
+ check_meld_for_features
+
+ option_auto_merge=
+ if test "$meld_use_auto_merge_option" = true
then
- check_meld_for_output_version
+ option_auto_merge="--auto-merge"
fi
if test "$meld_has_output_option" = true
then
- "$merge_tool_path" --output="$MERGED" \
+ "$merge_tool_path" $option_auto_merge --output="$MERGED" \
"$LOCAL" "$BASE" "$REMOTE"
else
- "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+ "$merge_tool_path" $option_auto_merge "$LOCAL" "$MERGED" "$REMOTE"
fi
}
-# Check whether we should use 'meld --output <file>'
-check_meld_for_output_version () {
- meld_path="$(git config mergetool.meld.path)"
- meld_path="${meld_path:-meld}"
+# Get meld help message
+init_meld_help_msg () {
+ if test -z "$meld_help_msg"
+ then
+ meld_path="$(git config mergetool.meld.path || echo meld)"
+ meld_help_msg=$("$meld_path" --help 2>&1)
+ fi
+}
- if meld_has_output_option=$(git config --bool mergetool.meld.hasOutput)
+# Check the features and set flags
+check_meld_for_features () {
+ # Check whether we should use 'meld --output <file>'
+ if test -z "$meld_has_output_option"
then
- : use configured value
- elif "$meld_path" --help 2>&1 |
- grep -e '--output=' -e '\[OPTION\.\.\.\]' >/dev/null
+ meld_has_output_option=$(git config --bool mergetool.meld.hasOutput)
+ case "$meld_has_output_option" in
+ true | false)
+ : use configured value
+ ;;
+ *)
+ : empty or invalid configured value, detecting "--output" automatically
+ init_meld_help_msg
+
+ case "$meld_help_msg" in
+ *"--output="* | *'[OPTION...]'*)
+ # All version that has [OPTION...] supports --output
+ meld_has_output_option=true
+ ;;
+ *)
+ meld_has_output_option=false
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ # Check whether we should use 'meld --auto-merge ...'
+ if test -z "$meld_use_auto_merge_option"
then
- : old ones mention --output and new ones just say OPTION...
- meld_has_output_option=true
- else
- meld_has_output_option=false
+ meld_use_auto_merge_option=$(
+ git config --bool-or-str mergetool.meld.useAutoMerge
+ )
+ case "$meld_use_auto_merge_option" in
+ true | false)
+ : use well formatted boolean value
+ ;;
+ auto)
+ # testing the "--auto-merge" option only if config is "auto"
+ init_meld_help_msg
+
+ case "$meld_help_msg" in
+ *"--auto-merge"* | *'[OPTION...]'*)
+ meld_use_auto_merge_option=true
+ ;;
+ *)
+ meld_use_auto_merge_option=false
+ ;;
+ esac
+ ;;
+ "")
+ meld_use_auto_merge_option=false
+ ;;
+ *)
+ die "unknown mergetool.meld.useAutoMerge: $meld_use_auto_merge_option"
+ ;;
+ esac
fi
}
diff --git a/midx.c b/midx.c
index cc19b66152..0de42ffdfb 100644
--- a/midx.c
+++ b/midx.c
@@ -432,14 +432,11 @@ static size_t write_midx_header(struct hashfile *f,
unsigned char num_chunks,
uint32_t num_packs)
{
- unsigned char byte_values[4];
-
hashwrite_be32(f, MIDX_SIGNATURE);
- byte_values[0] = MIDX_VERSION;
- byte_values[1] = oid_version();
- byte_values[2] = num_chunks;
- byte_values[3] = 0; /* unused */
- hashwrite(f, byte_values, sizeof(byte_values));
+ hashwrite_u8(f, MIDX_VERSION);
+ hashwrite_u8(f, oid_version());
+ hashwrite_u8(f, num_chunks);
+ hashwrite_u8(f, 0); /* unused */
hashwrite_be32(f, num_packs);
return MIDX_HEADER_SIZE;
diff --git a/object.h b/object.h
index 96a2105859..20b18805f0 100644
--- a/object.h
+++ b/object.h
@@ -73,6 +73,7 @@ struct object_array {
* sha1-name.c: 20
* list-objects-filter.c: 21
* builtin/fsck.c: 0--3
+ * builtin/gc.c: 0
* builtin/index-pack.c: 2021
* builtin/pack-objects.c: 20
* builtin/reflog.c: 10--12
diff --git a/oidset.c b/oidset.c
index 15d4e18c37..2d0ab76fb5 100644
--- a/oidset.c
+++ b/oidset.c
@@ -43,6 +43,12 @@ int oidset_size(struct oidset *set)
void oidset_parse_file(struct oidset *set, const char *path)
{
+ oidset_parse_file_carefully(set, path, NULL, NULL);
+}
+
+void oidset_parse_file_carefully(struct oidset *set, const char *path,
+ oidset_parse_tweak_fn fn, void *cbdata)
+{
FILE *fp;
struct strbuf sb = STRBUF_INIT;
struct object_id oid;
@@ -66,7 +72,8 @@ void oidset_parse_file(struct oidset *set, const char *path)
if (!sb.len)
continue;
- if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0')
+ if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0' ||
+ (fn && fn(&oid, cbdata)))
die("invalid object name: %s", sb.buf);
oidset_insert(set, &oid);
}
diff --git a/oidset.h b/oidset.h
index 209ae7a173..01f6560283 100644
--- a/oidset.h
+++ b/oidset.h
@@ -73,6 +73,15 @@ void oidset_clear(struct oidset *set);
*/
void oidset_parse_file(struct oidset *set, const char *path);
+/*
+ * Similar to the above, but with a callback which can (1) return non-zero to
+ * signal displeasure with the object and (2) replace object ID with something
+ * else (meant to be used to "peel").
+ */
+typedef int (*oidset_parse_tweak_fn)(struct object_id *, void *);
+void oidset_parse_file_carefully(struct oidset *set, const char *path,
+ oidset_parse_tweak_fn fn, void *cbdata);
+
struct oidset_iter {
kh_oid_set_t *set;
khiter_t iter;
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index a7a4964b50..5e998bdaa7 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -503,8 +503,7 @@ static void write_hash_cache(struct hashfile *f,
for (i = 0; i < index_nr; ++i) {
struct object_entry *entry = (struct object_entry *)index[i];
- uint32_t hash_value = htonl(entry->hash);
- hashwrite(f, &hash_value, sizeof(hash_value));
+ hashwrite_be32(f, entry->hash);
}
}
diff --git a/pack-write.c b/pack-write.c
index 685d327d80..a6cdb3c67c 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -117,10 +117,8 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
list = sorted_by_sha;
for (i = 0; i < nr_objects; i++) {
struct pack_idx_entry *obj = *list++;
- if (index_version < 2) {
- uint32_t offset = htonl(obj->offset);
- hashwrite(f, &offset, 4);
- }
+ if (index_version < 2)
+ hashwrite_be32(f, obj->offset);
hashwrite(f, obj->oid.hash, the_hash_algo->rawsz);
if ((opts->flags & WRITE_IDX_STRICT) &&
(i && oideq(&list[-2]->oid, &obj->oid)))
@@ -135,8 +133,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
list = sorted_by_sha;
for (i = 0; i < nr_objects; i++) {
struct pack_idx_entry *obj = *list++;
- uint32_t crc32_val = htonl(obj->crc32);
- hashwrite(f, &crc32_val, 4);
+ hashwrite_be32(f, obj->crc32);
}
/* write the 32-bit offset table */
@@ -148,8 +145,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
offset = (need_large_offset(obj->offset, opts)
? (0x80000000 | nr_large_offset++)
: obj->offset);
- offset = htonl(offset);
- hashwrite(f, &offset, 4);
+ hashwrite_be32(f, offset);
}
/* write the large offset table */
diff --git a/packfile.c b/packfile.c
index 9ef27508f2..e69012e7f2 100644
--- a/packfile.c
+++ b/packfile.c
@@ -923,6 +923,7 @@ unsigned long repo_approximate_object_count(struct repository *r)
count += p->num_objects;
}
r->objects->approximate_object_count = count;
+ r->objects->approximate_object_count_valid = 1;
}
return r->objects->approximate_object_count;
}
diff --git a/protocol.c b/protocol.c
index 8d964fc65e..052d7edbb9 100644
--- a/protocol.c
+++ b/protocol.c
@@ -17,7 +17,6 @@ static enum protocol_version parse_protocol_version(const char *value)
enum protocol_version get_protocol_version_config(void)
{
const char *value;
- int val;
const char *git_test_k = "GIT_TEST_PROTOCOL_VERSION";
const char *git_test_v;
@@ -31,9 +30,6 @@ enum protocol_version get_protocol_version_config(void)
return version;
}
- if (!git_config_get_bool("feature.experimental", &val) && val)
- return protocol_v2;
-
git_test_v = getenv(git_test_k);
if (git_test_v && *git_test_v) {
enum protocol_version env = parse_protocol_version(git_test_v);
@@ -43,7 +39,7 @@ enum protocol_version get_protocol_version_config(void)
return env;
}
- return protocol_v0;
+ return protocol_v2;
}
enum protocol_version determine_protocol_version_server(void)
diff --git a/quote.c b/quote.c
index ced0245e80..69f4ca45da 100644
--- a/quote.c
+++ b/quote.c
@@ -210,7 +210,7 @@ int sq_dequote_to_strvec(char *arg, struct strvec *array)
*/
#define X8(x) x, x, x, x, x, x, x, x
#define X16(x) X8(x), X8(x)
-static signed char const sq_lookup[256] = {
+static signed char const cq_lookup[256] = {
/* 0 1 2 3 4 5 6 7 */
/* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a',
/* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1,
@@ -223,9 +223,9 @@ static signed char const sq_lookup[256] = {
/* 0x80 */ /* set to 0 */
};
-static inline int sq_must_quote(char c)
+static inline int cq_must_quote(char c)
{
- return sq_lookup[(unsigned char)c] + quote_path_fully > 0;
+ return cq_lookup[(unsigned char)c] + quote_path_fully > 0;
}
/* returns the longest prefix not needing a quote up to maxlen if positive.
@@ -235,9 +235,9 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen)
{
size_t len;
if (maxlen < 0) {
- for (len = 0; !sq_must_quote(s[len]); len++);
+ for (len = 0; !cq_must_quote(s[len]); len++);
} else {
- for (len = 0; len < maxlen && !sq_must_quote(s[len]); len++);
+ for (len = 0; len < maxlen && !cq_must_quote(s[len]); len++);
}
return len;
}
@@ -256,7 +256,7 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen)
* Return value is the same as in (1).
*/
static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
- struct strbuf *sb, FILE *fp, int no_dq)
+ struct strbuf *sb, FILE *fp, unsigned flags)
{
#undef EMIT
#define EMIT(c) \
@@ -272,6 +272,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
count += (l); \
} while (0)
+ int no_dq = !!(flags & CQUOTE_NODQ);
size_t len, count = 0;
const char *p = name;
@@ -291,8 +292,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
ch = (unsigned char)*p++;
if (maxlen >= 0)
maxlen -= len + 1;
- if (sq_lookup[ch] >= ' ') {
- EMIT(sq_lookup[ch]);
+ if (cq_lookup[ch] >= ' ') {
+ EMIT(cq_lookup[ch]);
} else {
EMIT(((ch >> 6) & 03) + '0');
EMIT(((ch >> 3) & 07) + '0');
@@ -309,19 +310,21 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
return count;
}
-size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq)
+size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, unsigned flags)
{
- return quote_c_style_counted(name, -1, sb, fp, nodq);
+ return quote_c_style_counted(name, -1, sb, fp, flags);
}
-void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, int nodq)
+void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path,
+ unsigned flags)
{
+ int nodq = !!(flags & CQUOTE_NODQ);
if (quote_c_style(prefix, NULL, NULL, 0) ||
quote_c_style(path, NULL, NULL, 0)) {
if (!nodq)
strbuf_addch(sb, '"');
- quote_c_style(prefix, sb, NULL, 1);
- quote_c_style(path, sb, NULL, 1);
+ quote_c_style(prefix, sb, NULL, CQUOTE_NODQ);
+ quote_c_style(path, sb, NULL, CQUOTE_NODQ);
if (!nodq)
strbuf_addch(sb, '"');
} else {
@@ -352,13 +355,25 @@ void write_name_quoted_relative(const char *name, const char *prefix,
}
/* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, const char *prefix,
- struct strbuf *out)
+char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags)
{
struct strbuf sb = STRBUF_INIT;
const char *rel = relative_path(in, prefix, &sb);
+ int force_dq = ((flags & QUOTE_PATH_QUOTE_SP) && strchr(rel, ' '));
+
strbuf_reset(out);
- quote_c_style_counted(rel, strlen(rel), out, NULL, 0);
+
+ /*
+ * If the caller wants us to enclose the output in a dq-pair
+ * whether quote_c_style_counted() needs to, we do it ourselves
+ * and tell quote_c_style_counted() not to.
+ */
+ if (force_dq)
+ strbuf_addch(out, '"');
+ quote_c_style_counted(rel, strlen(rel), out, NULL,
+ force_dq ? CQUOTE_NODQ : 0);
+ if (force_dq)
+ strbuf_addch(out, '"');
strbuf_release(&sb);
return out->buf;
diff --git a/quote.h b/quote.h
index fa09309cf6..4b72a583cf 100644
--- a/quote.h
+++ b/quote.h
@@ -64,16 +64,19 @@ struct strvec;
int sq_dequote_to_strvec(char *arg, struct strvec *);
int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
-size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
-void quote_two_c_style(struct strbuf *, const char *, const char *, int);
+
+/* Bits in the flags parameter to quote_c_style() */
+#define CQUOTE_NODQ 01
+size_t quote_c_style(const char *name, struct strbuf *, FILE *, unsigned);
+void quote_two_c_style(struct strbuf *, const char *, const char *, unsigned);
void write_name_quoted(const char *name, FILE *, int terminator);
void write_name_quoted_relative(const char *name, const char *prefix,
FILE *fp, int terminator);
/* quote path as relative to the given prefix */
-char *quote_path_relative(const char *in, const char *prefix,
- struct strbuf *out);
+char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags);
+#define QUOTE_PATH_QUOTE_SP 01
/* quoting as a string literal for other languages */
void perl_quote_buf(struct strbuf *sb, const char *src);
diff --git a/read-cache.c b/read-cache.c
index fa291cdbee..ecf6f68994 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -2101,7 +2101,7 @@ static unsigned long load_cache_entries_threaded(struct index_state *istate, con
nr = 0;
for (j = p->ieot_start; j < p->ieot_start + p->ieot_blocks; j++)
nr += p->ieot->entries[j].nr;
- istate->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool));
+ p->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool));
if (istate->version == 4) {
mem_pool_init(p->ce_mem_pool,
estimate_cache_size_from_compressed(nr));
diff --git a/refs.c b/refs.c
index 8374dfefa6..fa01153151 100644
--- a/refs.c
+++ b/refs.c
@@ -1774,6 +1774,7 @@ struct ref_store *get_main_ref_store(struct repository *r)
BUG("attempting to get main_ref_store outside of repository");
r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+ r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
return r->refs_private;
}
diff --git a/refs/debug.c b/refs/debug.c
new file mode 100644
index 0000000000..922e64fa6a
--- /dev/null
+++ b/refs/debug.c
@@ -0,0 +1,398 @@
+
+#include "refs-internal.h"
+#include "trace.h"
+
+static struct trace_key trace_refs = TRACE_KEY_INIT(REFS);
+
+struct debug_ref_store {
+ struct ref_store base;
+ struct ref_store *refs;
+};
+
+extern struct ref_storage_be refs_be_debug;
+
+struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store)
+{
+ struct debug_ref_store *res;
+ struct ref_storage_be *be_copy;
+
+ if (!trace_want(&trace_refs)) {
+ return store;
+ }
+ res = xmalloc(sizeof(struct debug_ref_store));
+ be_copy = xmalloc(sizeof(*be_copy));
+ *be_copy = refs_be_debug;
+ /* we never deallocate backends, so safe to copy the pointer. */
+ be_copy->name = store->be->name;
+ trace_printf_key(&trace_refs, "ref_store for %s\n", gitdir);
+ res->refs = store;
+ base_ref_store_init((struct ref_store *)res, be_copy);
+ return (struct ref_store *)res;
+}
+
+static int debug_init_db(struct ref_store *refs, struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
+ int res = drefs->refs->be->init_db(drefs->refs, err);
+ trace_printf_key(&trace_refs, "init_db: %d\n", res);
+ return res;
+}
+
+static int debug_transaction_prepare(struct ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
+ int res;
+ transaction->ref_store = drefs->refs;
+ res = drefs->refs->be->transaction_prepare(drefs->refs, transaction,
+ err);
+ trace_printf_key(&trace_refs, "transaction_prepare: %d\n", res);
+ return res;
+}
+
+static void print_update(int i, const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid, unsigned int flags,
+ unsigned int type, const char *msg)
+{
+ char o[GIT_MAX_HEXSZ + 1] = "null";
+ char n[GIT_MAX_HEXSZ + 1] = "null";
+ if (old_oid)
+ oid_to_hex_r(o, old_oid);
+ if (new_oid)
+ oid_to_hex_r(n, new_oid);
+
+ type &= 0xf; /* see refs.h REF_* */
+ flags &= REF_HAVE_NEW | REF_HAVE_OLD | REF_NO_DEREF |
+ REF_FORCE_CREATE_REFLOG;
+ trace_printf_key(&trace_refs, "%d: %s %s -> %s (F=0x%x, T=0x%x) \"%s\"\n", i, refname,
+ o, n, flags, type, msg);
+}
+
+static void print_transaction(struct ref_transaction *transaction)
+{
+ int i;
+ trace_printf_key(&trace_refs, "transaction {\n");
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *u = transaction->updates[i];
+ print_update(i, u->refname, &u->old_oid, &u->new_oid, u->flags,
+ u->type, u->msg);
+ }
+ trace_printf_key(&trace_refs, "}\n");
+}
+
+static int debug_transaction_finish(struct ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
+ int res;
+ transaction->ref_store = drefs->refs;
+ res = drefs->refs->be->transaction_finish(drefs->refs, transaction,
+ err);
+ print_transaction(transaction);
+ trace_printf_key(&trace_refs, "finish: %d\n", res);
+ return res;
+}
+
+static int debug_transaction_abort(struct ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
+ int res;
+ transaction->ref_store = drefs->refs;
+ res = drefs->refs->be->transaction_abort(drefs->refs, transaction, err);
+ return res;
+}
+
+static int debug_initial_transaction_commit(struct ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
+ int res;
+ transaction->ref_store = drefs->refs;
+ res = drefs->refs->be->initial_transaction_commit(drefs->refs,
+ transaction, err);
+ return res;
+}
+
+static int debug_pack_refs(struct ref_store *ref_store, unsigned int flags)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->pack_refs(drefs->refs, flags);
+ trace_printf_key(&trace_refs, "pack_refs: %d\n", res);
+ return res;
+}
+
+static int debug_create_symref(struct ref_store *ref_store,
+ const char *ref_name, const char *target,
+ const char *logmsg)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
+ logmsg);
+ trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
+ target, logmsg, res);
+ return res;
+}
+
+static int debug_delete_refs(struct ref_store *ref_store, const char *msg,
+ struct string_list *refnames, unsigned int flags)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res =
+ drefs->refs->be->delete_refs(drefs->refs, msg, refnames, flags);
+ int i;
+ trace_printf_key(&trace_refs, "delete_refs {\n");
+ for (i = 0; i < refnames->nr; i++)
+ trace_printf_key(&trace_refs, "%s\n", refnames->items[i].string);
+ trace_printf_key(&trace_refs, "}: %d\n", res);
+ return res;
+}
+
+static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
+ const char *newref, const char *logmsg)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->rename_ref(drefs->refs, oldref, newref,
+ logmsg);
+ trace_printf_key(&trace_refs, "rename_ref: %s -> %s \"%s\": %d\n", oldref, newref,
+ logmsg, res);
+ return res;
+}
+
+static int debug_copy_ref(struct ref_store *ref_store, const char *oldref,
+ const char *newref, const char *logmsg)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res =
+ drefs->refs->be->copy_ref(drefs->refs, oldref, newref, logmsg);
+ trace_printf_key(&trace_refs, "copy_ref: %s -> %s \"%s\": %d\n", oldref, newref,
+ logmsg, res);
+ return res;
+}
+
+struct debug_ref_iterator {
+ struct ref_iterator base;
+ struct ref_iterator *iter;
+};
+
+static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct debug_ref_iterator *diter =
+ (struct debug_ref_iterator *)ref_iterator;
+ int res = diter->iter->vtable->advance(diter->iter);
+ if (res)
+ trace_printf_key(&trace_refs, "iterator_advance: (%d)\n", res);
+ else
+ trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n",
+ diter->iter->refname);
+
+ diter->base.ordered = diter->iter->ordered;
+ diter->base.refname = diter->iter->refname;
+ diter->base.oid = diter->iter->oid;
+ diter->base.flags = diter->iter->flags;
+ return res;
+}
+
+static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct debug_ref_iterator *diter =
+ (struct debug_ref_iterator *)ref_iterator;
+ int res = diter->iter->vtable->peel(diter->iter, peeled);
+ trace_printf_key(&trace_refs, "iterator_peel: %s: %d\n", diter->iter->refname, res);
+ return res;
+}
+
+static int debug_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct debug_ref_iterator *diter =
+ (struct debug_ref_iterator *)ref_iterator;
+ int res = diter->iter->vtable->abort(diter->iter);
+ trace_printf_key(&trace_refs, "iterator_abort: %d\n", res);
+ return res;
+}
+
+static struct ref_iterator_vtable debug_ref_iterator_vtable = {
+ debug_ref_iterator_advance, debug_ref_iterator_peel,
+ debug_ref_iterator_abort
+};
+
+static struct ref_iterator *
+debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix,
+ unsigned int flags)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ struct ref_iterator *res =
+ drefs->refs->be->iterator_begin(drefs->refs, prefix, flags);
+ struct debug_ref_iterator *diter = xcalloc(1, sizeof(*diter));
+ base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable, 1);
+ diter->iter = res;
+ trace_printf_key(&trace_refs, "ref_iterator_begin: %s (0x%x)\n", prefix, flags);
+ return &diter->base;
+}
+
+static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
+ struct object_id *oid, struct strbuf *referent,
+ unsigned int *type)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = 0;
+
+ oidcpy(oid, &null_oid);
+ res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent,
+ type);
+
+ if (res == 0) {
+ trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n",
+ refname, oid_to_hex(oid), referent->buf, *type, res);
+ } else {
+ trace_printf_key(&trace_refs, "read_raw_ref: %s: %d\n", refname, res);
+ }
+ return res;
+}
+
+static struct ref_iterator *
+debug_reflog_iterator_begin(struct ref_store *ref_store)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ struct ref_iterator *res =
+ drefs->refs->be->reflog_iterator_begin(drefs->refs);
+ trace_printf_key(&trace_refs, "for_each_reflog_iterator_begin\n");
+ return res;
+}
+
+struct debug_reflog {
+ const char *refname;
+ each_reflog_ent_fn *fn;
+ void *cb_data;
+};
+
+static int debug_print_reflog_ent(struct object_id *old_oid,
+ struct object_id *new_oid,
+ const char *committer, timestamp_t timestamp,
+ int tz, const char *msg, void *cb_data)
+{
+ struct debug_reflog *dbg = (struct debug_reflog *)cb_data;
+ int ret;
+ char o[GIT_MAX_HEXSZ + 1] = "null";
+ char n[GIT_MAX_HEXSZ + 1] = "null";
+ if (old_oid)
+ oid_to_hex_r(o, old_oid);
+ if (new_oid)
+ oid_to_hex_r(n, new_oid);
+
+ ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg,
+ dbg->cb_data);
+ trace_printf_key(&trace_refs, "reflog_ent %s (ret %d): %s -> %s, %s %ld \"%s\"\n",
+ dbg->refname, ret, o, n, committer, (long int)timestamp, msg);
+ return ret;
+}
+
+static int debug_for_each_reflog_ent(struct ref_store *ref_store,
+ const char *refname, each_reflog_ent_fn fn,
+ void *cb_data)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ struct debug_reflog dbg = {
+ .refname = refname,
+ .fn = fn,
+ .cb_data = cb_data,
+ };
+
+ int res = drefs->refs->be->for_each_reflog_ent(
+ drefs->refs, refname, &debug_print_reflog_ent, &dbg);
+ trace_printf_key(&trace_refs, "for_each_reflog: %s: %d\n", refname, res);
+ return res;
+}
+
+static int debug_for_each_reflog_ent_reverse(struct ref_store *ref_store,
+ const char *refname,
+ each_reflog_ent_fn fn,
+ void *cb_data)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ struct debug_reflog dbg = {
+ .refname = refname,
+ .fn = fn,
+ .cb_data = cb_data,
+ };
+ int res = drefs->refs->be->for_each_reflog_ent_reverse(
+ drefs->refs, refname, &debug_print_reflog_ent, &dbg);
+ trace_printf_key(&trace_refs, "for_each_reflog_reverse: %s: %d\n", refname, res);
+ return res;
+}
+
+static int debug_reflog_exists(struct ref_store *ref_store, const char *refname)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->reflog_exists(drefs->refs, refname);
+ trace_printf_key(&trace_refs, "reflog_exists: %s: %d\n", refname, res);
+ return res;
+}
+
+static int debug_create_reflog(struct ref_store *ref_store, const char *refname,
+ int force_create, struct strbuf *err)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->create_reflog(drefs->refs, refname,
+ force_create, err);
+ trace_printf_key(&trace_refs, "create_reflog: %s: %d\n", refname, res);
+ return res;
+}
+
+static int debug_delete_reflog(struct ref_store *ref_store, const char *refname)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->delete_reflog(drefs->refs, refname);
+ trace_printf_key(&trace_refs, "delete_reflog: %s: %d\n", refname, res);
+ return res;
+}
+
+static int debug_reflog_expire(struct ref_store *ref_store, 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)
+{
+ struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
+ int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid,
+ flags, prepare_fn,
+ should_prune_fn, cleanup_fn,
+ policy_cb_data);
+ trace_printf_key(&trace_refs, "reflog_expire: %s: %d\n", refname, res);
+ return res;
+}
+
+struct ref_storage_be refs_be_debug = {
+ NULL,
+ "debug",
+ NULL,
+ debug_init_db,
+ debug_transaction_prepare,
+ debug_transaction_finish,
+ debug_transaction_abort,
+ debug_initial_transaction_commit,
+
+ debug_pack_refs,
+ debug_create_symref,
+ debug_delete_refs,
+ debug_rename_ref,
+ debug_copy_ref,
+
+ debug_ref_iterator_begin,
+ debug_read_raw_ref,
+
+ debug_reflog_iterator_begin,
+ debug_for_each_reflog_ent,
+ debug_for_each_reflog_ent_reverse,
+ debug_reflog_exists,
+ debug_create_reflog,
+ debug_delete_reflog,
+ debug_reflog_expire,
+};
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dd712e47f4..04e85e7002 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -39,13 +39,6 @@
#define REF_NEEDS_COMMIT (1 << 6)
/*
- * Used as a flag in ref_update::flags when we want to log a ref
- * update but not actually perform it. This is used when a symbolic
- * ref update is split up.
- */
-#define REF_LOG_ONLY (1 << 7)
-
-/*
* Used as a flag in ref_update::flags when the ref_update was via an
* update to HEAD.
*/
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 527b0a6e2e..467f4b3c93 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -32,6 +32,13 @@ struct ref_transaction;
#define REF_HAVE_OLD (1 << 3)
/*
+ * Used as a flag in ref_update::flags when we want to log a ref
+ * update but not actually perform it. This is used when a symbolic
+ * ref update is split up.
+ */
+#define REF_LOG_ONLY (1 << 7)
+
+/*
* Return the length of time to retry acquiring a loose reference lock
* before giving up, in milliseconds:
*/
@@ -690,4 +697,9 @@ int parse_loose_ref_contents(const char *buf, struct object_id *oid,
void base_ref_store_init(struct ref_store *refs,
const struct ref_storage_be *be);
+/*
+ * Support GIT_TRACE_REFS by optionally wrapping the given ref_store instance.
+ */
+struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refspec.c b/refspec.c
index f10ef284ce..8d0affc34a 100644
--- a/refspec.c
+++ b/refspec.c
@@ -153,7 +153,7 @@ void refspec_init(struct refspec *rs, int fetch)
rs->fetch = fetch;
}
-void refspec_append(struct refspec *rs, const char *refspec)
+static void refspec_append_nodup(struct refspec *rs, char *refspec)
{
struct refspec_item item;
@@ -163,7 +163,21 @@ void refspec_append(struct refspec *rs, const char *refspec)
rs->items[rs->nr++] = item;
ALLOC_GROW(rs->raw, rs->raw_nr + 1, rs->raw_alloc);
- rs->raw[rs->raw_nr++] = xstrdup(refspec);
+ rs->raw[rs->raw_nr++] = refspec;
+}
+
+void refspec_append(struct refspec *rs, const char *refspec)
+{
+ refspec_append_nodup(rs, xstrdup(refspec));
+}
+
+void refspec_appendf(struct refspec *rs, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ refspec_append_nodup(rs, xstrvfmt(fmt, ap));
+ va_end(ap);
}
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
diff --git a/refspec.h b/refspec.h
index 8d654e3a3a..7569248d11 100644
--- a/refspec.h
+++ b/refspec.h
@@ -56,6 +56,8 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec,
void refspec_item_clear(struct refspec_item *item);
void refspec_init(struct refspec *rs, int fetch);
void refspec_append(struct refspec *rs, const char *refspec);
+__attribute__((format (printf,2,3)))
+void refspec_appendf(struct refspec *rs, const char *fmt, ...);
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr);
void refspec_clear(struct refspec *rs);
diff --git a/remote.c b/remote.c
index 420150837b..eafc14cbe7 100644
--- a/remote.c
+++ b/remote.c
@@ -287,19 +287,15 @@ static void read_branches_file(struct remote *remote)
frag = (char *)git_default_branch_name();
add_url_alias(remote, strbuf_detach(&buf, NULL));
- strbuf_addf(&buf, "refs/heads/%s:refs/heads/%s",
- frag, remote->name);
- refspec_append(&remote->fetch, buf.buf);
+ refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s",
+ frag, remote->name);
/*
* Cogito compatible push: push current HEAD to remote #branch
* (master if missing)
*/
- strbuf_reset(&buf);
- strbuf_addf(&buf, "HEAD:refs/heads/%s", frag);
- refspec_append(&remote->push, buf.buf);
+ refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag);
remote->fetch_tags = 1; /* always auto-follow */
- strbuf_release(&buf);
}
static int handle_config(const char *key, const char *value, void *cb)
diff --git a/remote.h b/remote.h
index 5e3ea5a26d..eb62a47044 100644
--- a/remote.h
+++ b/remote.h
@@ -93,6 +93,14 @@ int for_each_remote(each_remote_fn fn, void *priv);
int remote_has_url(struct remote *remote, const char *url);
+struct ref_push_report {
+ const char *ref_name;
+ struct object_id *old_oid;
+ struct object_id *new_oid;
+ unsigned int forced_update:1;
+ struct ref_push_report *next;
+};
+
struct ref {
struct ref *next;
struct object_id old_oid;
@@ -140,6 +148,7 @@ struct ref {
REF_STATUS_ATOMIC_PUSH_FAILED
} status;
char *remote_status;
+ struct ref_push_report *report;
struct ref *peer_ref; /* when renaming */
char name[FLEX_ARRAY]; /* more */
};
diff --git a/repo-settings.c b/repo-settings.c
index aa61a35338..88ccce2036 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -17,9 +17,12 @@ void prepare_repo_settings(struct repository *r)
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);
if (!repo_config_get_int(r, "index.version", &value))
diff --git a/repository.h b/repository.h
index 628c834367..bacf843d46 100644
--- a/repository.h
+++ b/repository.h
@@ -30,6 +30,7 @@ struct repo_settings {
int initialized;
int core_commit_graph;
+ int commit_graph_read_changed_paths;
int gc_write_commit_graph;
int fetch_write_commit_graph;
diff --git a/revision.c b/revision.c
index 1239023f93..d9dc5781ac 100644
--- a/revision.c
+++ b/revision.c
@@ -440,7 +440,7 @@ static struct commit *handle_commit(struct rev_info *revs,
if (object->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)object;
- if (parse_commit(commit) < 0)
+ if (repo_parse_commit(revs->repo, commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -681,10 +681,7 @@ static void prepare_to_use_bloom_filter(struct rev_info *revs)
repo_parse_commit(revs->repo, revs->commits->item);
- if (!revs->repo->objects->commit_graph)
- return;
-
- revs->bloom_filter_settings = revs->repo->objects->commit_graph->bloom_filter_settings;
+ revs->bloom_filter_settings = get_bloom_filter_settings(revs->repo);
if (!revs->bloom_filter_settings)
return;
@@ -755,7 +752,7 @@ static int check_maybe_different_in_bloom_filter(struct rev_info *revs,
if (commit_graph_generation(commit) == GENERATION_NUMBER_INFINITY)
return -1;
- filter = get_bloom_filter(revs->repo, commit, 0);
+ filter = get_bloom_filter(revs->repo, commit);
if (!filter) {
count_bloom_filter_not_present++;
@@ -1013,7 +1010,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
ts->treesame[0] = 1;
}
}
- if (parse_commit(p) < 0)
+ if (repo_parse_commit(revs->repo, p) < 0)
die("cannot simplify commit %s (because of %s)",
oid_to_hex(&commit->object.oid),
oid_to_hex(&p->object.oid));
@@ -1058,7 +1055,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
* IOW, we pretend this parent is a
* "root" commit.
*/
- if (parse_commit(p) < 0)
+ if (repo_parse_commit(revs->repo, p) < 0)
die("cannot simplify commit %s (invalid %s)",
oid_to_hex(&commit->object.oid),
oid_to_hex(&p->object.oid));
@@ -1126,7 +1123,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
parent = parent->next;
if (p)
p->object.flags |= UNINTERESTING;
- if (parse_commit_gently(p, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
continue;
if (p->parents)
mark_parents_uninteresting(p);
@@ -1157,7 +1154,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
struct commit *p = parent->item;
int gently = revs->ignore_missing_links ||
revs->exclude_promisor_objects;
- if (parse_commit_gently(p, gently) < 0) {
+ if (repo_parse_commit_gently(revs->repo, p, gently) < 0) {
if (revs->exclude_promisor_objects &&
is_promisor_object(&p->object.oid)) {
if (revs->first_parent_only)
@@ -3337,7 +3334,7 @@ static void explore_walk_step(struct rev_info *revs)
if (!c)
return;
- if (parse_commit_gently(c, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
return;
if (revs->sort_order == REV_SORT_BY_AUTHOR_DATE)
@@ -3375,7 +3372,7 @@ static void indegree_walk_step(struct rev_info *revs)
if (!c)
return;
- if (parse_commit_gently(c, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
return;
explore_to_depth(revs, commit_graph_generation(c));
@@ -3457,7 +3454,7 @@ static void init_topo_walk(struct rev_info *revs)
struct commit *c = list->item;
uint32_t generation;
- if (parse_commit_gently(c, 1))
+ if (repo_parse_commit_gently(revs->repo, c, 1))
continue;
test_flag_and_insert(&info->explore_queue, c, TOPO_WALK_EXPLORED);
@@ -3521,7 +3518,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit)
if (parent->object.flags & UNINTERESTING)
continue;
- if (parse_commit_gently(parent, 1) < 0)
+ if (repo_parse_commit_gently(revs->repo, parent, 1) < 0)
continue;
generation = commit_graph_generation(parent);
diff --git a/run-command.c b/run-command.c
index cc9c3296ba..2ee59acdc8 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
return result;
}
-int run_auto_gc(int quiet)
+int run_auto_maintenance(int quiet)
{
- struct strvec argv_gc_auto = STRVEC_INIT;
- int status;
+ struct child_process maint = CHILD_PROCESS_INIT;
- strvec_pushl(&argv_gc_auto, "gc", "--auto", NULL);
- if (quiet)
- strvec_push(&argv_gc_auto, "--quiet");
- status = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD);
- strvec_clear(&argv_gc_auto);
- return status;
+ maint.git_cmd = 1;
+ strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL);
+ strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet");
+
+ return run_command(&maint);
}
diff --git a/run-command.h b/run-command.h
index 8b9bfaef16..6472b38bde 100644
--- a/run-command.h
+++ b/run-command.h
@@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args);
/*
* Trigger an auto-gc
*/
-int run_auto_gc(int quiet);
+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 */
diff --git a/send-pack.c b/send-pack.c
index 632f1580ca..c9698070fc 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -154,25 +154,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
{
struct ref *hint;
int ret;
+ struct ref_push_report *report = NULL;
+ int new_report = 0;
+ int once = 0;
hint = NULL;
ret = receive_unpack_status(reader);
while (1) {
+ struct object_id old_oid, new_oid;
+ const char *head;
const char *refname;
- char *msg;
+ char *p;
if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
- if (!starts_with(reader->line, "ok ") && !starts_with(reader->line, "ng ")) {
- error("invalid ref status from remote: %s", reader->line);
+ head = reader->line;
+ p = strchr(head, ' ');
+ if (!p) {
+ error("invalid status line from remote: %s", reader->line);
ret = -1;
break;
}
+ *p++ = '\0';
- refname = reader->line + 3;
- msg = strchr(refname, ' ');
- if (msg)
- *msg++ = '\0';
+ if (!strcmp(head, "option")) {
+ const char *key, *val;
+ if (!hint || !(report || new_report)) {
+ if (!once++)
+ error("'option' without a matching 'ok/ng' directive");
+ ret = -1;
+ continue;
+ }
+ if (new_report) {
+ if (!hint->report) {
+ hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ report = hint->report;
+ } else {
+ report = hint->report;
+ while (report->next)
+ report = report->next;
+ report->next = xcalloc(1, sizeof(struct ref_push_report));
+ report = report->next;
+ }
+ new_report = 0;
+ }
+ key = p;
+ p = strchr(key, ' ');
+ if (p)
+ *p++ = '\0';
+ val = p;
+ if (!strcmp(key, "refname"))
+ report->ref_name = xstrdup_or_null(val);
+ else if (!strcmp(key, "old-oid") && val &&
+ !parse_oid_hex(val, &old_oid, &val))
+ report->old_oid = oiddup(&old_oid);
+ else if (!strcmp(key, "new-oid") && val &&
+ !parse_oid_hex(val, &new_oid, &val))
+ report->new_oid = oiddup(&new_oid);
+ else if (!strcmp(key, "forced-update"))
+ report->forced_update = 1;
+ continue;
+ }
+
+ report = NULL;
+ new_report = 0;
+ if (strcmp(head, "ok") && strcmp(head, "ng")) {
+ error("invalid ref status from remote: %s", head);
+ ret = -1;
+ break;
+ }
+ refname = p;
+ p = strchr(refname, ' ');
+ if (p)
+ *p++ = '\0';
/* first try searching at our hint, falling back to all refs */
if (hint)
hint = find_ref_by_name(hint, refname);
@@ -180,22 +234,27 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)
hint = find_ref_by_name(refs, refname);
if (!hint) {
warning("remote reported status on unknown ref: %s",
- refname);
+ refname);
continue;
}
- if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ if (hint->status != REF_STATUS_EXPECTING_REPORT &&
+ hint->status != REF_STATUS_OK &&
+ hint->status != REF_STATUS_REMOTE_REJECT) {
warning("remote reported status on unexpected ref: %s",
- refname);
+ refname);
continue;
}
-
- if (reader->line[0] == 'o' && reader->line[1] == 'k')
- hint->status = REF_STATUS_OK;
- else
+ if (!strcmp(head, "ng")) {
hint->status = REF_STATUS_REMOTE_REJECT;
- hint->remote_status = xstrdup_or_null(msg);
- /* start our next search from the next ref */
- hint = hint->next;
+ if (p)
+ hint->remote_status = xstrdup(p);
+ else
+ hint->remote_status = "failed";
+ } else {
+ hint->status = REF_STATUS_OK;
+ hint->remote_status = xstrdup_or_null(p);
+ new_report = 1;
+ }
}
return ret;
}
@@ -244,7 +303,12 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar
return CHECK_REF_STATUS_REJECTED;
case REF_STATUS_UPTODATE:
return CHECK_REF_UPTODATE;
+
default:
+ case REF_STATUS_EXPECTING_REPORT:
+ /* already passed checks on the local side */
+ case REF_STATUS_OK:
+ /* of course this is OK */
return 0;
}
}
@@ -371,7 +435,9 @@ int send_pack(struct send_pack_args *args,
struct packet_reader reader;
/* Does the other end support the reporting? */
- if (server_supports("report-status"))
+ if (server_supports("report-status-v2"))
+ status_report = 2;
+ else if (server_supports("report-status"))
status_report = 1;
if (server_supports("delete-refs"))
allow_deleting_refs = 1;
@@ -423,8 +489,10 @@ int send_pack(struct send_pack_args *args,
use_push_options = push_options_supported && args->push_options;
- if (status_report)
+ if (status_report == 1)
strbuf_addstr(&cap_buf, " report-status");
+ else if (status_report == 2)
+ strbuf_addstr(&cap_buf, " report-status-v2");
if (use_sideband)
strbuf_addstr(&cap_buf, " side-band-64k");
if (quiet_supported && (args->quiet || !args->progress))
@@ -447,13 +515,6 @@ int send_pack(struct send_pack_args *args,
if (ref->deletion && !allow_deleting_refs)
ref->status = REF_STATUS_REJECT_NODELETE;
- if (!args->dry_run)
- advertise_shallow_grafts_buf(&req_buf);
-
- if (!args->dry_run && push_cert_nonce)
- cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
- cap_buf.buf, push_cert_nonce);
-
/*
* Clear the status for each ref and see if we need to send
* the pack data.
@@ -489,31 +550,35 @@ int send_pack(struct send_pack_args *args,
ref->status = REF_STATUS_EXPECTING_REPORT;
}
+ if (!args->dry_run)
+ advertise_shallow_grafts_buf(&req_buf);
+
/*
* Finally, tell the other end!
*/
- for (ref = remote_refs; ref; ref = ref->next) {
- char *old_hex, *new_hex;
-
- if (args->dry_run || push_cert_nonce)
- continue;
-
- if (check_to_send_update(ref, args) < 0)
- continue;
-
- old_hex = oid_to_hex(&ref->old_oid);
- new_hex = oid_to_hex(&ref->new_oid);
- if (!cmds_sent) {
- packet_buf_write(&req_buf,
- "%s %s %s%c%s",
- old_hex, new_hex, ref->name, 0,
- cap_buf.buf);
- cmds_sent = 1;
- } else {
- packet_buf_write(&req_buf, "%s %s %s",
- old_hex, new_hex, ref->name);
+ if (!args->dry_run && push_cert_nonce)
+ cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
+ cap_buf.buf, push_cert_nonce);
+ else if (!args->dry_run)
+ for (ref = remote_refs; ref; ref = ref->next) {
+ char *old_hex, *new_hex;
+
+ if (check_to_send_update(ref, args) < 0)
+ continue;
+
+ old_hex = oid_to_hex(&ref->old_oid);
+ new_hex = oid_to_hex(&ref->new_oid);
+ if (!cmds_sent) {
+ packet_buf_write(&req_buf,
+ "%s %s %s%c%s",
+ old_hex, new_hex, ref->name, 0,
+ cap_buf.buf);
+ cmds_sent = 1;
+ } else {
+ packet_buf_write(&req_buf, "%s %s %s",
+ old_hex, new_hex, ref->name);
+ }
}
- }
if (use_push_options) {
struct string_list_item *item;
diff --git a/submodule.c b/submodule.c
index 6f8002fc9e..543b1123ae 100644
--- a/submodule.c
+++ b/submodule.c
@@ -438,13 +438,14 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
*/
}
-static int prepare_submodule_diff_summary(struct rev_info *rev, const char *path,
- struct commit *left, struct commit *right,
- struct commit_list *merge_bases)
+static int prepare_submodule_diff_summary(struct repository *r, struct rev_info *rev,
+ const char *path,
+ struct commit *left, struct commit *right,
+ struct commit_list *merge_bases)
{
struct commit_list *list;
- repo_init_revisions(the_repository, rev, NULL);
+ repo_init_revisions(r, rev, NULL);
setup_revisions(0, NULL, rev, NULL);
rev->left_right = 1;
rev->first_parent_only = 1;
@@ -632,7 +633,7 @@ void show_submodule_diff_summary(struct diff_options *o, const char *path,
goto out;
/* Treat revision walker failure the same as missing commits */
- if (prepare_submodule_diff_summary(&rev, path, left, right, merge_bases)) {
+ if (prepare_submodule_diff_summary(sub, &rev, path, left, right, merge_bases)) {
diff_emit_submodule_error(o, "(revision walker failed)\n");
goto out;
}
@@ -840,9 +841,16 @@ static void collect_changed_submodules(struct repository *r,
{
struct rev_info rev;
const struct commit *commit;
+ int save_warning;
+ struct setup_revision_opt s_r_opt = {
+ .assume_dashdash = 1,
+ };
+ save_warning = warn_on_object_refname_ambiguity;
+ warn_on_object_refname_ambiguity = 0;
repo_init_revisions(r, &rev, NULL);
- setup_revisions(argv->nr, argv->v, &rev, NULL);
+ setup_revisions(argv->nr, argv->v, &rev, &s_r_opt);
+ warn_on_object_refname_ambiguity = save_warning;
if (prepare_revision_walk(&rev))
die(_("revision walk setup failed"));
diff --git a/t/chainlint.sed b/t/chainlint.sed
index 70df40e34b..8a25c5b855 100644
--- a/t/chainlint.sed
+++ b/t/chainlint.sed
@@ -117,7 +117,7 @@
/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline
# multi-line "(...\n...)"
-/^[ ]*(/bsubshell
+/^[ ]*(/bsubsh
# innocuous line -- print it and advance to next line
b
@@ -130,11 +130,11 @@ b
}
b
-:subshell
+:subsh
# bare "(" line? -- stash for later printing
/^[ ]*([ ]*$/ {
h
- bnextline
+ bnextln
}
# "(..." line -- split off and stash "(", then process "..." as its own line
x
@@ -143,7 +143,7 @@ x
s/(//
bslurp
-:nextline
+:nextln
N
s/.*\n//
@@ -151,10 +151,10 @@ s/.*\n//
# incomplete line "...\"
/\\$/bicmplte
# multi-line quoted string "...\n..."?
-/"/bdqstring
+/"/bdqstr
# multi-line quoted string '...\n...'? (but not contraction in string "it's")
/'/{
- /"[^'"]*'[^'"]*"/!bsqstring
+ /"[^'"]*'[^'"]*"/!bsqstr
}
:folded
# here-doc -- swallow it
@@ -163,8 +163,8 @@ s/.*\n//
# before closing ")", "done", "elsif", "else", or "fi" will need to be
# re-visited to drop "suspect" marking since final line of those constructs
# legitimately lacks "&&", so "suspect" mark must be removed
-/^[ ]*#/bnextline
-/^[ ]*$/bnextline
+/^[ ]*#/bnextln
+/^[ ]*$/bnextln
# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array
# length, or Perforce "//depot/path#42" revision in filespec)
/[ ]#/{
@@ -175,22 +175,22 @@ s/.*\n//
# multi-line "case ... esac"
/^[ ]*case[ ]..*[ ]in/bcase
# multi-line "for ... done" or "while ... done"
-/^[ ]*for[ ]..*[ ]in/bcontinue
-/^[ ]*while[ ]/bcontinue
-/^[ ]*do[ ]/bcontinue
-/^[ ]*do[ ]*$/bcontinue
-/;[ ]*do/bcontinue
+/^[ ]*for[ ]..*[ ]in/bcont
+/^[ ]*while[ ]/bcont
+/^[ ]*do[ ]/bcont
+/^[ ]*do[ ]*$/bcont
+/;[ ]*do/bcont
/^[ ]*done[ ]*&&[ ]*$/bdone
/^[ ]*done[ ]*$/bdone
/^[ ]*done[ ]*[<>|]/bdone
/^[ ]*done[ ]*)/bdone
-/||[ ]*exit[ ]/bcontinue
-/||[ ]*exit[ ]*$/bcontinue
+/||[ ]*exit[ ]/bcont
+/||[ ]*exit[ ]*$/bcont
# multi-line "if...elsif...else...fi"
-/^[ ]*if[ ]/bcontinue
-/^[ ]*then[ ]/bcontinue
-/^[ ]*then[ ]*$/bcontinue
-/;[ ]*then/bcontinue
+/^[ ]*if[ ]/bcont
+/^[ ]*then[ ]/bcont
+/^[ ]*then[ ]*$/bcont
+/;[ ]*then/bcont
/^[ ]*elif[ ]/belse
/^[ ]*elif[ ]*$/belse
/^[ ]*else[ ]/belse
@@ -234,10 +234,10 @@ s/.*\n//
}
}
# line ends with pipe "...|" -- valid; not missing "&&"
-/|[ ]*$/bcontinue
+/|[ ]*$/bcont
# missing end-of-line "&&" -- mark suspect
/&&[ ]*$/!s/^/?!AMP?!/
-:continue
+:cont
# retrieve and print previous line
x
n
@@ -250,7 +250,7 @@ s/\\\n//
bslurp
# check for multi-line double-quoted string "...\n..." -- fold to one line
-:dqstring
+:dqstr
# remove all quote pairs
s/"\([^"]*\)"/@!\1@!/g
# done if no dangling quote
@@ -258,13 +258,13 @@ s/"\([^"]*\)"/@!\1@!/g
# otherwise, slurp next line and try again
N
s/\n//
-bdqstring
+bdqstr
:dqdone
s/@!/"/g
bfolded
# check for multi-line single-quoted string '...\n...' -- fold to one line
-:sqstring
+:sqstr
# remove all quote pairs
s/'\([^']*\)'/@!\1@!/g
# done if no dangling quote
@@ -272,7 +272,7 @@ s/'\([^']*\)'/@!\1@!/g
# otherwise, slurp next line and try again
N
s/\n//
-bsqstring
+bsqstr
:sqdone
s/@!/'/g
bfolded
@@ -282,11 +282,11 @@ bfolded
:heredoc
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
s/[ ]*<<//
-:heredsub
+:hdocsub
N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
s/\n.*$//
- bheredsub
+ bhdocsub
}
s/^<[^>]*>//
s/\n.*$//
@@ -305,7 +305,7 @@ bcase
x
s/?!AMP?!//
x
-bcontinue
+bcont
# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
# "suspect" from final contained line since that line legitimately lacks "&&"
@@ -321,10 +321,10 @@ bchkchn
# found nested multi-line "(...\n...)" -- pass through untouched
:nest
x
-:nstslurp
+:nstslrp
n
# closing ")" on own line -- stop nested slurp
-/^[ ]*)/bnstclose
+/^[ ]*)/bnstcl
# comment -- not closing ")" if in comment
/^[ ]*#/bnstcnt
# "$((...))" -- arithmetic expansion; not closing ")"
@@ -332,11 +332,11 @@ n
# "$(...)" -- command substitution; not closing ")"
/\$([^)][^)]*)[^)]*$/bnstcnt
# closing "...)" -- stop nested slurp
-/)/bnstclose
+/)/bnstcl
:nstcnt
x
-bnstslurp
-:nstclose
+bnstslrp
+:nstcl
s/^/>>/
# is it "))" which closes nested and parent subshells?
/)[ ]*)/bslurp
diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c
index 5e77d56f59..46e97b04eb 100644
--- a/t/helper/test-bloom.c
+++ b/t/helper/test-bloom.c
@@ -39,7 +39,9 @@ static void get_bloom_filter_for_commit(const struct object_id *commit_oid)
struct bloom_filter *filter;
setup_git_directory();
c = lookup_commit(the_repository, commit_oid);
- filter = get_bloom_filter(the_repository, c, 1);
+ filter = get_or_compute_bloom_filter(the_repository, c, 1,
+ &settings,
+ NULL);
print_bloom_filter(filter);
}
diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c
new file mode 100644
index 0000000000..42164d9898
--- /dev/null
+++ b/t/helper/test-proc-receive.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "connect.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "sigchain.h"
+#include "test-tool.h"
+
+static const char *proc_receive_usage[] = {
+ "test-tool proc-receive [<options>...]",
+ NULL
+};
+
+static int die_version;
+static int die_readline;
+static int no_push_options;
+static int use_atomic;
+static int use_push_options;
+static int verbose;
+static int version = 1;
+static struct string_list returns = STRING_LIST_INIT_NODUP;
+
+struct command {
+ struct command *next;
+ const char *error_string;
+ unsigned int skip_update:1,
+ did_not_exist:1;
+ int index;
+ struct object_id old_oid;
+ struct object_id new_oid;
+ char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static void proc_receive_verison(struct packet_reader *reader) {
+ int server_version = 0;
+
+ for (;;) {
+ int linelen;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ if (reader->pktlen > 8 && starts_with(reader->line, "version=")) {
+ server_version = atoi(reader->line+8);
+ linelen = strlen(reader->line);
+ if (linelen < reader->pktlen) {
+ const char *feature_list = reader->line + linelen + 1;
+ if (parse_feature_request(feature_list, "atomic"))
+ use_atomic= 1;
+ if (parse_feature_request(feature_list, "push-options"))
+ use_push_options = 1;
+ }
+ }
+ }
+
+ if (server_version != 1 || die_version)
+ die("bad protocol version: %d", server_version);
+
+ packet_write_fmt(1, "version=%d%c%s\n",
+ version, '\0',
+ use_push_options && !no_push_options ? "push-options": "");
+ packet_flush(1);
+}
+
+static void proc_receive_read_commands(struct packet_reader *reader,
+ struct command **commands)
+{
+ struct command **tail = commands;
+
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ struct command *cmd;
+ const char *refname;
+ const char *p;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ if (parse_oid_hex(reader->line, &old_oid, &p) ||
+ *p++ != ' ' ||
+ parse_oid_hex(p, &new_oid, &p) ||
+ *p++ != ' ' ||
+ die_readline)
+ die("protocol error: expected 'old new ref', got '%s'",
+ reader->line);
+ refname = p;
+ FLEX_ALLOC_STR(cmd, ref_name, refname);
+ oidcpy(&cmd->old_oid, &old_oid);
+ oidcpy(&cmd->new_oid, &new_oid);
+
+ *tail = cmd;
+ tail = &cmd->next;
+ }
+}
+
+static void proc_receive_read_push_options(struct packet_reader *reader,
+ struct string_list *options)
+{
+
+ if (no_push_options || !use_push_options)
+ return;
+
+ while (1) {
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ string_list_append(options, reader->line);
+ }
+}
+
+int cmd__proc_receive(int argc, const char **argv)
+{
+ int nongit_ok = 0;
+ struct packet_reader reader;
+ struct command *commands = NULL;
+ struct string_list push_options = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ struct option options[] = {
+ OPT_BOOL(0, "no-push-options", &no_push_options,
+ "disable push options"),
+ OPT_BOOL(0, "die-version", &die_version,
+ "die during version negotiation"),
+ OPT_BOOL(0, "die-readline", &die_readline,
+ "die when readline"),
+ OPT_STRING_LIST('r', "return", &returns, "old/new/ref/status/msg",
+ "return of results"),
+ OPT__VERBOSE(&verbose, "be verbose"),
+ OPT_INTEGER('V', "version", &version,
+ "use this protocol version number"),
+ OPT_END()
+ };
+
+ setup_git_directory_gently(&nongit_ok);
+
+ argc = parse_options(argc, argv, "test-tools", options, proc_receive_usage, 0);
+ if (argc > 0)
+ usage_msg_opt("Too many arguments.", proc_receive_usage, options);
+ packet_reader_init(&reader, 0, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ sigchain_push(SIGPIPE, SIG_IGN);
+ proc_receive_verison(&reader);
+ proc_receive_read_commands(&reader, &commands);
+ proc_receive_read_push_options(&reader, &push_options);
+
+ if (verbose) {
+ struct command *cmd;
+
+ if (use_push_options || use_atomic)
+ fprintf(stderr, "proc-receive:%s%s\n",
+ use_atomic? " atomic": "",
+ use_push_options ? " push_options": "");
+
+ for (cmd = commands; cmd; cmd = cmd->next)
+ fprintf(stderr, "proc-receive< %s %s %s\n",
+ oid_to_hex(&cmd->old_oid),
+ oid_to_hex(&cmd->new_oid),
+ cmd->ref_name);
+
+ if (push_options.nr > 0)
+ for_each_string_list_item(item, &push_options)
+ fprintf(stderr, "proc-receive< %s\n", item->string);
+
+ if (returns.nr)
+ for_each_string_list_item(item, &returns)
+ fprintf(stderr, "proc-receive> %s\n", item->string);
+ }
+
+ if (returns.nr)
+ for_each_string_list_item(item, &returns)
+ packet_write_fmt(1, "%s\n", item->string);
+ packet_flush(1);
+ sigchain_pop(SIGPIPE);
+
+ return 0;
+}
diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c
index 6d0c962438..5f585a1725 100644
--- a/t/helper/test-read-graph.c
+++ b/t/helper/test-read-graph.c
@@ -12,11 +12,12 @@ int cmd__read_graph(int argc, const char **argv)
setup_git_directory();
odb = the_repository->objects->odb;
+ prepare_repo_settings(the_repository);
+
graph = read_commit_graph_one(the_repository, odb);
if (!graph)
return 1;
-
printf("header: %08x %d %d %d %d\n",
ntohl(*(uint32_t*)graph->data),
*(unsigned char*)(graph->data + 4),
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 590b2efca7..a0d3966b29 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {
{ "path-utils", cmd__path_utils },
{ "pkt-line", cmd__pkt_line },
{ "prio-queue", cmd__prio_queue },
+ { "proc-receive", cmd__proc_receive},
{ "progress", cmd__progress },
{ "reach", cmd__reach },
{ "read-cache", cmd__read_cache },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index ddc8e990e9..07034d3f38 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);
int cmd__path_utils(int argc, const char **argv);
int cmd__pkt_line(int argc, const char **argv);
int cmd__prio_queue(int argc, const char **argv);
+int cmd__proc_receive(int argc, const char **argv);
int cmd__progress(int argc, const char **argv);
int cmd__reach(int argc, const char **argv);
int cmd__read_cache(int argc, const char **argv);
diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh
index 7ee791669a..f4c2ab0584 100755
--- a/t/perf/p5303-many-packs.sh
+++ b/t/perf/p5303-many-packs.sh
@@ -73,6 +73,10 @@ do
git rev-list --objects --all >/dev/null
'
+ test_perf "abbrev-commit ($nr_packs)" '
+ git rev-list --abbrev-commit HEAD >/dev/null
+ '
+
# This simulates the interesting part of the repack, which is the
# actual pack generation, without smudging the on-disk setup
# between trials.
diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh
index 232ba2c485..7e4ab1795f 100755
--- a/t/t0095-bloom.sh
+++ b/t/t0095-bloom.sh
@@ -71,8 +71,8 @@ test_expect_success 'get bloom filters for commit with no changes' '
git init &&
git commit --allow-empty -m "c0" &&
cat >expect <<-\EOF &&
- Filter_Length:0
- Filter_Data:
+ Filter_Length:1
+ Filter_Data:00|
EOF
test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
test_cmp expect actual
@@ -107,8 +107,8 @@ test_expect_success EXPENSIVE 'get bloom filter for commit with 513 changes' '
git add bigDir &&
git commit -m "commit with 513 changes" &&
cat >expect <<-\EOF &&
- Filter_Length:0
- Filter_Data:
+ Filter_Length:1
+ Filter_Data:ff|
EOF
test-tool bloom get_filter_for_commit "$(git rev-parse HEAD)" >actual &&
test_cmp expect actual
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 97ebfe1f9d..825d9a184f 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1836,11 +1836,14 @@ test_expect_success '--show-scope with --show-origin' '
test_cmp expect output
'
-test_expect_success '--local requires a repo' '
- # we expect 128 to ensure that we do not simply
- # fail to find anything and return code "1"
- test_expect_code 128 nongit git config --local foo.bar
-'
+for opt in --local --worktree
+do
+ test_expect_success "$opt requires a repo" '
+ # we expect 128 to ensure that we do not simply
+ # fail to find anything and return code "1"
+ test_expect_code 128 nongit git config $opt foo.bar
+ '
+done
cat >.git/config <<-\EOF &&
[core]
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index dbf690b9c1..3e657e693b 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -190,6 +190,24 @@ test_expect_success 'dotdot is not an empty set' '
test_cmp expect actual
'
+test_expect_success 'dotdot does not peel endpoints' '
+ git tag -a -m "annote" annotated HEAD &&
+ A=$(git rev-parse annotated) &&
+ H=$(git rev-parse annotated^0) &&
+ {
+ echo $A && echo ^$A
+ } >expect-with-two-dots &&
+ {
+ echo $A && echo $A && echo ^$H
+ } >expect-with-merge-base &&
+
+ git rev-parse annotated..annotated >actual-with-two-dots &&
+ test_cmp expect-with-two-dots actual-with-two-dots &&
+
+ git rev-parse annotated...annotated >actual-with-merge-base &&
+ test_cmp expect-with-merge-base actual-with-merge-base
+'
+
test_expect_success 'arg before dashdash must be a revision (missing)' '
test_must_fail git rev-parse foobar -- 2>stderr &&
test_i18ngrep "bad revision" stderr
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index bf3c3a33ba..c24c6632ee 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1287,6 +1287,7 @@ test_expect_success 'detect typo in branch name when using --edit-description' '
'
test_expect_success 'refuse --edit-description on unborn branch for now' '
+ test_when_finished "git checkout master" &&
write_script editor <<-\EOF &&
echo "New contents" >"$1"
EOF
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index e024cff65c..6eb344be03 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -252,17 +252,13 @@ test_expect_success 'changed commit with --stat diff option' '
git range-diff --no-color --stat topic...changed >actual &&
cat >expect <<-EOF &&
1: $(test_oid t1) = 1: $(test_oid c1) s/5/A/
- a => b | 0
- 1 file changed, 0 insertions(+), 0 deletions(-)
2: $(test_oid t2) = 2: $(test_oid c2) s/4/A/
- a => b | 0
- 1 file changed, 0 insertions(+), 0 deletions(-)
3: $(test_oid t3) ! 3: $(test_oid c3) s/11/B/
- a => b | 0
- 1 file changed, 0 insertions(+), 0 deletions(-)
+ a => b | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
4: $(test_oid t4) ! 4: $(test_oid c4) s/12/B/
- a => b | 0
- 1 file changed, 0 insertions(+), 0 deletions(-)
+ a => b | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
EOF
test_cmp expect actual
'
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index fb73a847cb..ca04fac417 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -569,12 +569,20 @@ test_expect_success 'patch mode ignores unmerged entries' '
diff_cmp expected diff
'
+test_expect_success 'index is refreshed after applying patch' '
+ git reset --hard &&
+ echo content >test &&
+ printf y | git add -p &&
+ git diff-files --exit-code
+'
+
test_expect_success 'diffs can be colorized' '
git reset --hard &&
echo content >test &&
printf y >y &&
force_color git add -p >output 2>&1 <y &&
+ git diff-files --exit-code &&
# We do not want to depend on the exact coloring scheme
# git uses for diffs, so just check that we saw some kind of color.
@@ -814,6 +822,44 @@ test_expect_success 'checkout -p works with pathological context lines' '
test_cmp expect a
'
+# This should be called from a subshell as it sets a temporary editor
+setup_new_file() {
+ write_script new-file-editor.sh <<-\EOF &&
+ sed /^#/d "$1" >patch &&
+ sed /^+c/d patch >"$1"
+ EOF
+ test_set_editor "$(pwd)/new-file-editor.sh" &&
+ test_write_lines a b c d e f >new-file &&
+ test_write_lines a b d e f >new-file-expect &&
+ test_write_lines "@@ -0,0 +1,6 @@" +a +b +c +d +e +f >patch-expect
+}
+
+test_expect_success 'add -N followed by add -p patch editing' '
+ git reset --hard &&
+ (
+ setup_new_file &&
+ git add -N new-file &&
+ test_write_lines e n q | git add -p &&
+ git cat-file blob :new-file >actual &&
+ test_cmp new-file-expect actual &&
+ test_cmp patch-expect patch
+ )
+'
+
+test_expect_success 'checkout -p patch editing of added file' '
+ git reset --hard &&
+ (
+ setup_new_file &&
+ git add new-file &&
+ git commit -m "add new file" &&
+ git rm new-file &&
+ git commit -m "remove new file" &&
+ test_write_lines e n q | git checkout -p HEAD^ &&
+ test_cmp new-file-expect new-file &&
+ test_cmp patch-expect patch
+ )
+'
+
test_expect_success 'show help from add--helper' '
git reset --hard &&
cat >expect <<-EOF &&
diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh
index 4bb9e9dbe2..d11040ce41 100755
--- a/t/t4216-log-bloom.sh
+++ b/t/t4216-log-bloom.sh
@@ -30,6 +30,7 @@ test_expect_success 'setup test - repo, commits, commit graph, log outputs' '
rm file_to_be_deleted &&
git add . &&
git commit -m "file removed" &&
+ git commit --allow-empty -m "empty" &&
git commit-graph write --reachable --changed-paths &&
test_oid_cache <<-EOF
@@ -37,6 +38,7 @@ test_expect_success 'setup test - repo, commits, commit graph, log outputs' '
oid_version sha256:2
EOF
'
+
graph_read_expect () {
NUM_CHUNKS=5
cat >expect <<- EOF
@@ -49,7 +51,7 @@ graph_read_expect () {
}
test_expect_success 'commit-graph write wrote out the bloom chunks' '
- graph_read_expect 15
+ graph_read_expect 16
'
# Turn off any inherited trace2 settings for this test.
@@ -58,14 +60,14 @@ sane_unset GIT_TRACE2_PERF_BRIEF
sane_unset GIT_TRACE2_CONFIG_PARAMS
setup () {
- rm "$TRASH_DIRECTORY/trace.perf"
+ rm -f "$TRASH_DIRECTORY/trace.perf" &&
git -c core.commitGraph=false log --pretty="format:%s" $1 >log_wo_bloom &&
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.perf" git -c core.commitGraph=true log --pretty="format:%s" $1 >log_w_bloom
}
test_bloom_filters_used () {
log_args=$1
- bloom_trace_prefix="statistics:{\"filter_not_present\":0,\"maybe\""
+ bloom_trace_prefix="statistics:{\"filter_not_present\":${2:-0},\"maybe\""
setup "$log_args" &&
grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
test_cmp log_wo_bloom log_w_bloom &&
@@ -95,7 +97,9 @@ do
"--ancestry-path side..master"
do
test_expect_success "git log option: $option for path: $path" '
- test_bloom_filters_used "$option -- $path"
+ test_bloom_filters_used "$option -- $path" &&
+ test_config commitgraph.readChangedPaths false &&
+ test_bloom_filters_not_used "$option -- $path"
'
done
done
@@ -139,8 +143,11 @@ test_expect_success 'setup - add commit-graph to the chain without Bloom filters
test_line_count = 2 .git/objects/info/commit-graphs/commit-graph-chain
'
-test_expect_success 'Do not use Bloom filters if the latest graph does not have Bloom filters.' '
- test_bloom_filters_not_used "-- A/B"
+test_expect_success 'use Bloom filters even if the latest graph does not have Bloom filters' '
+ # Ensure that the number of empty filters is equal to the number of
+ # filters in the latest graph layer to prove that they are loaded (and
+ # ignored).
+ test_bloom_filters_used "-- A/B" 3
'
test_expect_success 'setup - add commit-graph to the chain with Bloom filters' '
@@ -151,7 +158,7 @@ test_expect_success 'setup - add commit-graph to the chain with Bloom filters' '
test_bloom_filters_used_when_some_filters_are_missing () {
log_args=$1
- bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"maybe\":6,\"definitely_not\":8"
+ bloom_trace_prefix="statistics:{\"filter_not_present\":3,\"maybe\":6,\"definitely_not\":9"
setup "$log_args" &&
grep -q "$bloom_trace_prefix" "$TRASH_DIRECTORY/trace.perf" &&
test_cmp log_wo_bloom log_w_bloom
@@ -169,31 +176,230 @@ test_expect_success 'persist filter settings' '
GIT_TEST_BLOOM_SETTINGS_NUM_HASHES=9 \
GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY=15 \
git commit-graph write --reachable --changed-paths &&
- grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15}" trace2.txt &&
+ grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15,\"max_changed_paths\":512" trace2.txt &&
GIT_TRACE2_EVENT="$(pwd)/trace2-auto.txt" \
GIT_TRACE2_EVENT_NESTING=5 \
git commit-graph write --reachable --changed-paths &&
- grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15}" trace2-auto.txt
+ grep "{\"hash_version\":1,\"num_hashes\":9,\"bits_per_entry\":15,\"max_changed_paths\":512" trace2-auto.txt
'
+test_max_changed_paths () {
+ grep "\"max_changed_paths\":$1" $2
+}
+
+test_filter_not_computed () {
+ grep "\"key\":\"filter-not-computed\",\"value\":\"$1\"" $2
+}
+
+test_filter_computed () {
+ grep "\"key\":\"filter-computed\",\"value\":\"$1\"" $2
+}
+
+test_filter_trunc_empty () {
+ grep "\"key\":\"filter-trunc-empty\",\"value\":\"$1\"" $2
+}
+
+test_filter_trunc_large () {
+ grep "\"key\":\"filter-trunc-large\",\"value\":\"$1\"" $2
+}
+
test_expect_success 'correctly report changes over limit' '
- git init 513changes &&
+ git init limits &&
(
- cd 513changes &&
- for i in $(test_seq 1 513)
+ cd limits &&
+ mkdir d &&
+ mkdir d/e &&
+
+ for i in $(test_seq 1 2)
do
- echo $i >file$i.txt || return 1
+ printf $i >d/file$i.txt &&
+ printf $i >d/e/file$i.txt || return 1
done &&
- git add . &&
+
+ mkdir mode &&
+ printf bash >mode/script.sh &&
+
+ mkdir foo &&
+ touch foo/bar &&
+ touch foo.txt &&
+
+ git add d foo foo.txt mode &&
git commit -m "files" &&
- git commit-graph write --reachable --changed-paths &&
- for i in $(test_seq 1 513)
+
+ # Commit has 7 file and 4 directory adds
+ GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=10 \
+ GIT_TRACE2_EVENT="$(pwd)/trace" \
+ git commit-graph write --reachable --changed-paths &&
+ test_max_changed_paths 10 trace &&
+ test_filter_computed 1 trace &&
+ test_filter_trunc_large 1 trace &&
+
+ for path in $(git ls-tree -r --name-only HEAD)
+ do
+ git -c commitGraph.readChangedPaths=false log \
+ -- $path >expect &&
+ git log -- $path >actual &&
+ test_cmp expect actual || return 1
+ done &&
+
+ # Make a variety of path changes
+ printf new1 >d/e/file1.txt &&
+ printf new2 >d/file2.txt &&
+ rm d/e/file2.txt &&
+ rm -r foo &&
+ printf text >foo &&
+ mkdir f &&
+ printf new1 >f/file1.txt &&
+
+ # including a mode-only change (counts as modified)
+ git update-index --chmod=+x mode/script.sh &&
+
+ git add foo d f &&
+ git commit -m "complicated" &&
+
+ # start from scratch and rebuild
+ rm -f .git/objects/info/commit-graph &&
+ GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=10 \
+ GIT_TRACE2_EVENT="$(pwd)/trace-edit" \
+ git commit-graph write --reachable --changed-paths &&
+ test_max_changed_paths 10 trace-edit &&
+ test_filter_computed 2 trace-edit &&
+ test_filter_trunc_large 2 trace-edit &&
+
+ for path in $(git ls-tree -r --name-only HEAD)
+ do
+ git -c commitGraph.readChangedPaths=false log \
+ -- $path >expect &&
+ git log -- $path >actual &&
+ test_cmp expect actual || return 1
+ done &&
+
+ # start from scratch and rebuild
+ rm -f .git/objects/info/commit-graph &&
+ GIT_TEST_BLOOM_SETTINGS_MAX_CHANGED_PATHS=11 \
+ GIT_TRACE2_EVENT="$(pwd)/trace-update" \
+ git commit-graph write --reachable --changed-paths &&
+ test_max_changed_paths 11 trace-update &&
+ test_filter_computed 2 trace-update &&
+ test_filter_trunc_large 0 trace-update &&
+
+ for path in $(git ls-tree -r --name-only HEAD)
do
- git -c core.commitGraph=false log -- file$i.txt >expect &&
- git log -- file$i.txt >actual &&
+ git -c commitGraph.readChangedPaths=false log \
+ -- $path >expect &&
+ git log -- $path >actual &&
test_cmp expect actual || return 1
done
)
'
+test_expect_success 'correctly report commits with no changed paths' '
+ git init empty &&
+ test_when_finished "rm -fr empty" &&
+ (
+ cd empty &&
+
+ git commit --allow-empty -m "initial commit" &&
+
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable --changed-paths &&
+ test_filter_computed 1 trace.event &&
+ test_filter_not_computed 0 trace.event &&
+ test_filter_trunc_empty 1 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ )
+'
+
+test_expect_success 'Bloom generation is limited by --max-new-filters' '
+ (
+ cd limits &&
+ test_commit c2 filter &&
+ test_commit c3 filter &&
+ test_commit c4 no-filter &&
+
+ rm -f trace.event &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable --split=replace \
+ --changed-paths --max-new-filters=2 &&
+
+ test_filter_computed 2 trace.event &&
+ test_filter_not_computed 3 trace.event &&
+ test_filter_trunc_empty 0 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ )
+'
+
+test_expect_success 'Bloom generation backfills previously-skipped filters' '
+ # Check specifying commitGraph.maxNewFilters over "git config" works.
+ test_config -C limits commitGraph.maxNewFilters 1 &&
+ (
+ cd limits &&
+
+ rm -f trace.event &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable --changed-paths \
+ --split=replace &&
+ test_filter_computed 1 trace.event &&
+ test_filter_not_computed 4 trace.event &&
+ test_filter_trunc_empty 0 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ )
+'
+
+test_expect_success '--max-new-filters overrides configuration' '
+ git init override &&
+ test_when_finished "rm -fr override" &&
+ test_config -C override commitGraph.maxNewFilters 2 &&
+ (
+ cd override &&
+ test_commit one &&
+ test_commit two &&
+
+ rm -f trace.event &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable --changed-paths \
+ --max-new-filters=1 &&
+ test_filter_computed 1 trace.event &&
+ test_filter_not_computed 1 trace.event &&
+ test_filter_trunc_empty 0 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ )
+'
+
+test_expect_success 'Bloom generation backfills empty commits' '
+ git init empty &&
+ test_when_finished "rm -fr empty" &&
+ (
+ cd empty &&
+ for i in $(test_seq 1 6)
+ do
+ git commit --allow-empty -m "$i"
+ done &&
+
+ # Generate Bloom filters for empty commits 1-6, two at a time.
+ for i in $(test_seq 1 3)
+ do
+ rm -f trace.event &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable \
+ --changed-paths --max-new-filters=2 &&
+ test_filter_computed 2 trace.event &&
+ test_filter_not_computed 4 trace.event &&
+ test_filter_trunc_empty 2 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ done &&
+
+ # Finally, make sure that once all commits have filters, that
+ # none are subsequently recomputed.
+ rm -f trace.event &&
+ GIT_TRACE2_EVENT="$(pwd)/trace.event" \
+ git commit-graph write --reachable \
+ --changed-paths --max-new-filters=2 &&
+ test_filter_computed 0 trace.event &&
+ test_filter_not_computed 6 trace.event &&
+ test_filter_trunc_empty 0 trace.event &&
+ test_filter_trunc_large 0 trace.event
+ )
+'
+
test_done
diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh
index 18216463c7..c334ee9155 100755
--- a/t/t5324-split-commit-graph.sh
+++ b/t/t5324-split-commit-graph.sh
@@ -427,4 +427,17 @@ done <<\EOF
0600 -r--------
EOF
+test_expect_success '--split=replace with partial Bloom data' '
+ rm -rf $graphdir $infodir/commit-graph &&
+ git reset --hard commits/3 &&
+ git rev-list -1 HEAD~2 >a &&
+ git rev-list -1 HEAD~1 >b &&
+ git commit-graph write --split=no-merge --stdin-commits --changed-paths <a &&
+ git commit-graph write --split=no-merge --stdin-commits <b &&
+ git commit-graph write --split=replace --stdin-commits --changed-paths <c &&
+ ls $graphdir/graph-*.graph >graph-files &&
+ test_line_count = 1 graph-files &&
+ verify_chain_files_exist $graphdir
+'
+
test_done
diff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh
new file mode 100755
index 0000000000..746487286f
--- /dev/null
+++ b/t/t5411-proc-receive-hook.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Jiang Xin
+#
+
+test_description='Test proc-receive hook'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/t5411/common-functions.sh
+
+setup_upstream_and_workbench () {
+ # Refs of upstream : master(A)
+ # Refs of workbench: master(A) tags/v123
+ test_expect_success "setup upstream and workbench" '
+ rm -rf upstream.git &&
+ rm -rf workbench &&
+ git init --bare upstream.git &&
+ git init workbench &&
+ create_commits_in workbench A B &&
+ (
+ cd workbench &&
+ # Try to make a stable fixed width for abbreviated commit ID,
+ # this fixed-width oid will be replaced with "<OID>".
+ git config core.abbrev 7 &&
+ git tag -m "v123" v123 $A &&
+ git remote add origin ../upstream.git &&
+ git push origin master &&
+ git update-ref refs/heads/master $A $B &&
+ git -C ../upstream.git update-ref \
+ refs/heads/master $A $B
+ ) &&
+ TAG=$(git -C workbench rev-parse v123) &&
+
+ # setup pre-receive hook
+ write_script upstream.git/hooks/pre-receive <<-\EOF &&
+ exec >&2
+ echo "# pre-receive hook"
+ while read old new ref
+ do
+ echo "pre-receive< $old $new $ref"
+ done
+ EOF
+
+ # setup post-receive hook
+ write_script upstream.git/hooks/post-receive <<-\EOF &&
+ exec >&2
+ echo "# post-receive hook"
+ while read old new ref
+ do
+ echo "post-receive< $old $new $ref"
+ done
+ EOF
+
+ upstream=upstream.git
+ '
+}
+
+run_proc_receive_hook_test() {
+ case $1 in
+ http)
+ PROTOCOL="HTTP protocol"
+ URL_PREFIX="http://.*"
+ ;;
+ local)
+ PROTOCOL="builtin protocol"
+ URL_PREFIX="\.\."
+ ;;
+ esac
+
+ # Include test cases for both file and HTTP protocol
+ for t in "$TEST_DIRECTORY"/t5411/test-*.sh
+ do
+ . "$t"
+ done
+}
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Load test cases that only need to be executed once.
+for t in "$TEST_DIRECTORY"/t5411/once-*.sh
+do
+ . "$t"
+done
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Run test cases for 'proc-receive' hook on local file protocol.
+run_proc_receive_hook_test local
+
+ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+start_httpd
+
+# Re-initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup for HTTP protocol" '
+ git -C upstream.git config http.receivepack true &&
+ upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" &&
+ mv upstream.git "$upstream" &&
+ git -C workbench remote set-url origin "$HTTPD_URL/auth-push/smart/upstream.git" &&
+ set_askpass user@host pass@host
+'
+
+setup_askpass_helper
+
+# Run test cases for 'proc-receive' hook on HTTP protocol.
+run_proc_receive_hook_test http
+
+test_done
diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh
new file mode 100644
index 0000000000..6580bebd8e
--- /dev/null
+++ b/t/t5411/common-functions.sh
@@ -0,0 +1,56 @@
+# Create commits in <repo> and assign each commit's oid to shell variables
+# given in the arguments (A, B, and C). E.g.:
+#
+# create_commits_in <repo> A B C
+#
+# NOTE: Never calling this function from a subshell since variable
+# assignments will disappear when subshell exits.
+create_commits_in () {
+ repo="$1" &&
+ if ! parent=$(git -C "$repo" rev-parse HEAD^{} --)
+ then
+ parent=
+ fi &&
+ T=$(git -C "$repo" write-tree) &&
+ shift &&
+ while test $# -gt 0
+ do
+ name=$1 &&
+ test_tick &&
+ if test -z "$parent"
+ then
+ oid=$(echo $name | git -C "$repo" commit-tree $T)
+ else
+ oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T)
+ fi &&
+ eval $name=$oid &&
+ parent=$oid &&
+ shift ||
+ return 1
+ done &&
+ git -C "$repo" update-ref refs/heads/master $oid
+}
+
+# Format the output of git-push, git-show-ref and other commands to make a
+# user-friendly and stable text. We can easily prepare the expect text
+# without having to worry about future changes of the commit ID and spaces
+# of the output. Single quotes are replaced with double quotes, because
+# it is boring to prepare unquoted single quotes in expect text. We also
+# remove some locale error messages, which break test if we turn on
+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations
+# on plumbing commands.
+make_user_friendly_and_stable_output () {
+ sed \
+ -e "s/ *\$//" \
+ -e "s/ */ /g" \
+ -e "s/'/\"/g" \
+ -e "s/ / /g" \
+ -e "s/$A/<COMMIT-A>/g" \
+ -e "s/$B/<COMMIT-B>/g" \
+ -e "s/$TAG/<TAG-v123>/g" \
+ -e "s/$ZERO_OID/<ZERO-OID>/g" \
+ -e "s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g" \
+ -e "s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g" \
+ -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#" \
+ -e "/^error: / d"
+}
diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh
new file mode 100644
index 0000000000..dc2cf4a522
--- /dev/null
+++ b/t/t5411/once-0010-report-status-v1.sh
@@ -0,0 +1,94 @@
+test_expect_success "setup receive.procReceiveRefs" '
+ git -C "$upstream" config --add receive.procReceiveRefs refs/for
+'
+
+test_expect_success "setup proc-receive hook" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic1" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic2" \
+ -r "option refname refs/for/changes/23/123/1" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic2" \
+ -r "option refname refs/for/changes/24/124/2" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update" \
+ -r "ng refs/for/next/topic target branch not exist"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) refs/for/master/topic1(A) foo(A) refs/for/next/topic(A) refs/for/master/topic2(A)
+test_expect_success "proc-receive: report status v1" '
+ {
+ if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1"
+ then
+ printf "%s %s refs/heads/master\0report-status\n" \
+ $A $B | packetize
+ else
+ printf "%s %s refs/heads/master\0report-status object-format=$GIT_DEFAULT_HASH\n" \
+ $A $B | packetize
+ fi &&
+ printf "%s %s refs/for/master/topic1\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/heads/foo\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/for/next/topic\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/for/master/topic2\n" \
+ $ZERO_OID $A | packetize &&
+ printf 0000 &&
+ printf "" | git -C "$upstream" pack-objects --stdout
+ } | git receive-pack "$upstream" --stateless-rpc \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ # pre-receive hook
+ pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+ # proc-receive hook
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+ proc-receive> ok refs/for/master/topic1
+ proc-receive> option fall-through
+ proc-receive> ok refs/for/master/topic2
+ proc-receive> option refname refs/for/changes/23/123/1
+ proc-receive> option new-oid <COMMIT-A>
+ proc-receive> ok refs/for/master/topic2
+ proc-receive> option refname refs/for/changes/24/124/2
+ proc-receive> option old-oid <COMMIT-B>
+ proc-receive> option new-oid <COMMIT-A>
+ proc-receive> option forced-update
+ proc-receive> ng refs/for/next/topic target branch not exist
+ 000eunpack ok
+ 0019ok refs/heads/master
+ 001eok refs/for/master/topic1
+ 0016ok refs/heads/foo
+ 0033ng refs/for/next/topic target branch not exist
+ 001eok refs/for/master/topic2
+ 0000# post-receive hook
+ post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ post-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1
+ post-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/for/master/topic1
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh
new file mode 100644
index 0000000000..e206587348
--- /dev/null
+++ b/t/t5411/test-0000-standard-git-push.sh
@@ -0,0 +1,143 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ * [new branch] HEAD -> next
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push --atomic: master(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL)" '
+ test_must_fail git -C workbench push --atomic origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out |
+ sed -n \
+ -e "/^To / { s/ */ /g; p; }" \
+ -e "/^ ! / { s/ */ /g; p; }" \
+ >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [rejected] master -> master (non-fast-forward)
+ ! [rejected] <COMMIT-B> -> next (atomic push failed)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(A) next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL)" '
+ test_must_fail git \
+ -C workbench \
+ -c advice.pushUpdateRejected=false \
+ push origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> next
+ ! [rejected] master -> master (non-fast-forward)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-B> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(B)
+# Refs of workbench: master(A) tags/v123
+# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL)" '
+ git -C workbench push -f origin \
+ refs/tags/v123 \
+ :refs/heads/next \
+ master \
+ master:refs/review/master/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ To <URL/of/upstream.git>
+ + <OID-B>...<OID-A> master -> master (forced update)
+ - [deleted] next
+ * [new tag] v123 -> v123
+ * [new reference] master -> refs/review/master/topic
+ * [new branch] HEAD -> a/b/c
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/a/b/c
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/review/master/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) tags/v123 refs/review/master/topic(A) a/b/c(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/review/master/topic &&
+ git update-ref -d refs/tags/v123 &&
+ git update-ref -d refs/heads/a/b/c
+ )
+'
diff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh
new file mode 100644
index 0000000000..48f6fcc846
--- /dev/null
+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh
@@ -0,0 +1,147 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ * HEAD:refs/heads/next [new branch]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push --atomic: master(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --atomic --porcelain origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out |
+ sed -n \
+ -e "s/^# GETTEXT POISON #//" \
+ -e "/^To / { s/ */ /g; p; }" \
+ -e "/^! / { s/ */ /g; p; }" \
+ >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)
+ ! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(A) next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" '
+ test_must_fail git \
+ -C workbench \
+ -c advice.pushUpdateRejected=false \
+ push --porcelain origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/next <OID-A>..<OID-B>
+ ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ <COMMIT-B> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(B)
+# Refs of workbench: master(A) tags/v123
+# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain -f origin \
+ refs/tags/v123 \
+ :refs/heads/next \
+ master \
+ master:refs/review/master/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ To <URL/of/upstream.git>
+ + refs/heads/master:refs/heads/master <OID-B>...<OID-A> (forced update)
+ - :refs/heads/next [deleted]
+ * refs/tags/v123:refs/tags/v123 [new tag]
+ * refs/heads/master:refs/review/master/topic [new reference]
+ * HEAD:refs/heads/a/b/c [new branch]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/a/b/c
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/review/master/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) tags/v123 refs/review/master/topic(A) a/b/c(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/review/master/topic &&
+ git update-ref -d refs/tags/v123 &&
+ git update-ref -d refs/heads/a/b/c
+ )
+'
diff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh
new file mode 100644
index 0000000000..c246f7e68e
--- /dev/null
+++ b/t/t5411/test-0002-pre-receive-declined.sh
@@ -0,0 +1,33 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ write_script "$upstream/hooks/pre-receive" <<-EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)
+ ! [remote rejected] HEAD -> next (pre-receive hook declined)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "cleanup ($PROTOCOL)" '
+ mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh
new file mode 100644
index 0000000000..b14894de81
--- /dev/null
+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL/porcelain)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ write_script "$upstream/hooks/pre-receive" <<-EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! <COMMIT-B>:refs/heads/master [remote rejected] (pre-receive hook declined)
+ ! HEAD:refs/heads/next [remote rejected] (pre-receive hook declined)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh
new file mode 100644
index 0000000000..a36809927b
--- /dev/null
+++ b/t/t5411/test-0010-proc-receive-settings.sh
@@ -0,0 +1,7 @@
+test_expect_success "add two receive.procReceiveRefs settings" '
+ (
+ cd "$upstream" &&
+ git config --add receive.procReceiveRefs refs/for &&
+ git config --add receive.procReceiveRefs refs/review/
+ )
+'
diff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh
new file mode 100644
index 0000000000..bb6ec92a92
--- /dev/null
+++ b/t/t5411/test-0011-no-hook-error.sh
@@ -0,0 +1,64 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL)" '
+ test_must_fail git -C workbench push --atomic origin \
+ $B:master \
+ HEAD:next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ To <URL/of/upstream.git>
+ ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)
+ ! [remote rejected] HEAD -> next (fail to run proc-receive hook)
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh
new file mode 100644
index 0000000000..4814f74dc2
--- /dev/null
+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh
@@ -0,0 +1,66 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain --atomic origin \
+ $B:master \
+ HEAD:next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ To <URL/of/upstream.git>
+ ! <COMMIT-B>:refs/heads/master [remote rejected] (fail to run proc-receive hook)
+ ! HEAD:refs/heads/next [remote rejected] (fail to run proc-receive hook)
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh
new file mode 100644
index 0000000000..c5fe4cb37b
--- /dev/null
+++ b/t/t5411/test-0013-bad-protocol.sh
@@ -0,0 +1,217 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ # Check status report for git-push
+ sed -n \
+ -e "/^To / { p; n; p; }" \
+ <actual >actual-report &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual-report &&
+
+ # Check error message from "receive-pack", but ignore unstable fatal error
+ # message ("remote: fatal: the remote end hung up unexpectedly") which
+ # is different from the remote HTTP server with different locale settings.
+ grep "^remote: error:" <actual >actual-error &&
+ cat >expect <<-EOF &&
+ remote: error: proc-receive version "2" is not supported
+ EOF
+ test_cmp expect actual-error &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (hook --die-version, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-version
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-version, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: fatal: bad protocol version: 1
+ remote: error: proc-receive version "0" is not supported
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (hook --die-readline, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-readline
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-readline, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ grep "remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"" actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic\
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok
+ remote: error: proc-receive reported incomplete status line: "ok"
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> xx refs/for/master/topic
+ remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh
new file mode 100644
index 0000000000..53b47b0185
--- /dev/null
+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh
@@ -0,0 +1,160 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ # Check status report for git-push
+ sed -n \
+ -e "/^To / { p; n; p; n; p; }" \
+ <actual >actual-report &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual-report &&
+
+ # Check error message from "receive-pack", but ignore unstable fatal error
+ # message ("remote: fatal: the remote end hung up unexpectedly") which
+ # is different from the remote HTTP server with different locale settings.
+ grep "^remote: error:" <actual >actual-error &&
+ cat >expect <<-EOF &&
+ remote: error: proc-receive version "2" is not supported
+ EOF
+ test_cmp expect actual-error &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic\
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok
+ remote: error: proc-receive reported incomplete status line: "ok"
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> xx refs/for/master/topic
+ remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh
new file mode 100644
index 0000000000..f726b7ca9c
--- /dev/null
+++ b/t/t5411/test-0020-report-ng.sh
@@ -0,0 +1,67 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ng refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (failed)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic error msg"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ng refs/for/master/topic error msg
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (error msg)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh
new file mode 100644
index 0000000000..fbf5569103
--- /dev/null
+++ b/t/t5411/test-0021-report-ng--porcelain.sh
@@ -0,0 +1,69 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ng refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (failed)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic error msg"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ng refs/for/master/topic error msg
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (error msg)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh
new file mode 100644
index 0000000000..92a415b929
--- /dev/null
+++ b/t/t5411/test-0022-report-unexpect-ref.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) refs/for/master/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
new file mode 100644
index 0000000000..acbf93e40a
--- /dev/null
+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
@@ -0,0 +1,46 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) refs/for/master/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh
new file mode 100644
index 0000000000..c3946f329a
--- /dev/null
+++ b/t/t5411/test-0024-report-unknown-ref.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/a/b/c/my/topic
+test_expect_success "proc-receive: report unknown reference ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/a/b/c/my/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh
new file mode 100644
index 0000000000..d093b1a579
--- /dev/null
+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/a/b/c/my/topic
+test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/a/b/c/my/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/a/b/c/my/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh
new file mode 100644
index 0000000000..d0c4da8b23
--- /dev/null
+++ b/t/t5411/test-0026-push-options.sh
@@ -0,0 +1,79 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL)" '
+ test_must_fail git -C workbench push \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ test_i18ngrep "fatal: the receiving end does not support push options" \
+ actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "enable push options ($PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions true
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : next(A) refs/for/master/topic
+test_expect_success "proc-receive: push with options ($PROTOCOL)" '
+ git -C workbench push \
+ --atomic \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive: atomic push_options
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< issue=123
+ remote: proc-receive< reviewer=user1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * [new branch] HEAD -> next
+ * [new reference] HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh
new file mode 100644
index 0000000000..c89a1e7c57
--- /dev/null
+++ b/t/t5411/test-0027-push-options--porcelain.sh
@@ -0,0 +1,82 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push \
+ --porcelain \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ test_i18ngrep "fatal: the receiving end does not support push options" \
+ actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "enable push options ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions true
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : next(A) refs/for/master/topic
+test_expect_success "proc-receive: push with options ($PROTOCOL/porcelain)" '
+ git -C workbench push \
+ --porcelain \
+ --atomic \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive: atomic push_options
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< issue=123
+ remote: proc-receive< reviewer=user1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ * HEAD:refs/for/master/topic [new reference]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh
new file mode 100644
index 0000000000..44c99d3831
--- /dev/null
+++ b/t/t5411/test-0030-report-ok.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: ok ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh
new file mode 100644
index 0000000000..3223b26184
--- /dev/null
+++ b/t/t5411/test-0031-report-ok--porcelain.sh
@@ -0,0 +1,36 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * HEAD:refs/for/master/topic [new reference]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh
new file mode 100644
index 0000000000..b77b78c49f
--- /dev/null
+++ b/t/t5411/test-0032-report-with-options.sh
@@ -0,0 +1,256 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ <OID-B>..<OID-A> HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-B>..<OID-A> HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/a/b/c/topic" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option forced-update" \
+ -r "option new-oid $A"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/next/topic \
+ HEAD:refs/for/a/b/c/topic \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/a/b/c/topic
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option forced-update
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ * [new reference] HEAD -> refs/for/a/b/c/topic
+ + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh
new file mode 100644
index 0000000000..1fe352b686
--- /dev/null
+++ b/t/t5411/test-0033-report-with-options--porcelain.sh
@@ -0,0 +1,265 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ HEAD:refs/pull/123/head <OID-B>..<OID-A>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-B>..<OID-A>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/a/b/c/topic" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option forced-update" \
+ -r "option new-oid $A"
+
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/next/topic \
+ HEAD:refs/for/a/b/c/topic \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/a/b/c/topic
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option forced-update
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ * HEAD:refs/for/a/b/c/topic [new reference]
+ + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh
new file mode 100644
index 0000000000..aca2b0676c
--- /dev/null
+++ b/t/t5411/test-0034-report-ft.sh
@@ -0,0 +1,44 @@
+test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option fall-through
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * [new reference] <COMMIT-B> -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/for/master/topic
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) refs/for/master/topic(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh
new file mode 100644
index 0000000000..30ffffb352
--- /dev/null
+++ b/t/t5411/test-0035-report-ft--porcelain.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ $B:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option fall-through
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * <COMMIT-B>:refs/for/master/topic [new reference]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/for/master/topic
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) refs/for/master/topic(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
new file mode 100644
index 0000000000..73283d81e8
--- /dev/null
+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
@@ -0,0 +1,227 @@
+test_expect_success "setup git config for remote-tracking of special refs" '
+ (
+ cd workbench &&
+ if ! git config --get-all remote.origin.fetch | grep refs/for/
+ then
+ git config --add remote.origin.fetch \
+ "+refs/for/*:refs/t/for/*" &&
+ git config --add remote.origin.fetch \
+ "+refs/pull/*:refs/t/pull/*" &&
+ git config --add remote.origin.fetch \
+ "+refs/changes/*:refs/t/changes/*"
+ fi
+ )
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ * [new reference] HEAD -> refs/changes/24/124/1
+ <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #1 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/24/124/1
+ <COMMIT-B> refs/t/changes/25/125/1
+ <COMMIT-B> refs/t/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/master/topic &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/changes/24/124/1
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #2 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/24/124/1
+ <COMMIT-A> refs/t/changes/25/125/1
+ <COMMIT-B> refs/t/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/master/topic &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/23/123/1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/2
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/changes/23/123/1
+ <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "proc-receive: check remote-tracking #3 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/23/123/1
+ <COMMIT-B> refs/t/changes/24/124/2
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/2
+'
diff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
new file mode 100644
index 0000000000..77b5b22ed4
--- /dev/null
+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
@@ -0,0 +1,172 @@
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ * HEAD:refs/changes/24/124/1 [new reference]
+ HEAD:refs/changes/25/125/1 <OID-A>..<OID-B>
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ * HEAD:refs/changes/24/124/1 [new reference]
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ + HEAD:refs/changes/25/125/1 <OID-B>...<OID-A> (forced update)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/23/123/1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/2
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+ To <URL/of/upstream.git>
+ * HEAD:refs/changes/23/123/1 [new reference]
+ HEAD:refs/changes/24/124/2 <OID-A>..<OID-B>
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh
new file mode 100644
index 0000000000..a74a2cb449
--- /dev/null
+++ b/t/t5411/test-0038-report-mixed-refs.sh
@@ -0,0 +1,89 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/next/topic2" \
+ -r "ng refs/for/next/topic1 fail to call Web API" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/bar \
+ HEAD:refs/heads/baz \
+ HEAD:refs/for/next/topic2 \
+ HEAD:refs/for/next/topic1 \
+ HEAD:refs/heads/foo \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic3 \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: proc-receive> ok refs/for/next/topic2
+ remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ * [new branch] HEAD -> bar
+ * [new branch] HEAD -> baz
+ * [new reference] HEAD -> refs/for/next/topic2
+ * [new branch] HEAD -> foo
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)
+ ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $A &&
+ git update-ref -d refs/heads/foo &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh
new file mode 100644
index 0000000000..e4baa13ea3
--- /dev/null
+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh
@@ -0,0 +1,91 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/next/topic2" \
+ -r "ng refs/for/next/topic1 fail to call Web API" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/bar \
+ HEAD:refs/heads/baz \
+ HEAD:refs/for/next/topic2 \
+ HEAD:refs/for/next/topic1 \
+ HEAD:refs/heads/foo \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic3 \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: proc-receive> ok refs/for/next/topic2
+ remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ * HEAD:refs/heads/bar [new branch]
+ * HEAD:refs/heads/baz [new branch]
+ * HEAD:refs/for/next/topic2 [new reference]
+ * HEAD:refs/heads/foo [new branch]
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ ! HEAD:refs/for/next/topic1 [remote rejected] (fail to call Web API)
+ ! HEAD:refs/for/next/topic3 [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $A &&
+ git update-ref -d refs/heads/foo &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+
+'
diff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh
new file mode 100644
index 0000000000..b07c999f53
--- /dev/null
+++ b/t/t5411/test-0040-process-all-refs.sh
@@ -0,0 +1,113 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL)" '
+ git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+ git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/foo $A &&
+ git update-ref refs/heads/bar $A &&
+ git update-ref refs/heads/baz $A
+ )
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option fall-through" \
+ -r "ok refs/heads/foo" \
+ -r "option fall-through" \
+ -r "ok refs/heads/bar" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL)" '
+ git -C workbench push -f origin \
+ HEAD:refs/heads/master \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/foo
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/bar
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> bar
+ - [deleted] foo
+ + <OID-B>...<OID-A> HEAD -> master (forced update)
+ <OID-A>..<OID-B> HEAD -> refs/pull/123/head
+ + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) bar(A) baz(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh
new file mode 100644
index 0000000000..0dd9824616
--- /dev/null
+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh
@@ -0,0 +1,114 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+ git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/foo $A &&
+ git update-ref refs/heads/bar $A &&
+ git update-ref refs/heads/baz $A
+ )
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option fall-through" \
+ -r "ok refs/heads/foo" \
+ -r "option fall-through" \
+ -r "ok refs/heads/bar" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain -f origin \
+ HEAD:refs/heads/master \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/foo
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/bar
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/bar <OID-A>..<OID-B>
+ - :refs/heads/foo [deleted]
+ + HEAD:refs/heads/master <OID-B>...<OID-A> (forced update)
+ HEAD:refs/pull/123/head <OID-A>..<OID-B>
+ + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) bar(A) baz(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
new file mode 100644
index 0000000000..c22849cbe2
--- /dev/null
+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
@@ -0,0 +1,135 @@
+test_expect_success "config receive.procReceiveRefs with modifiers ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git config --unset-all receive.procReceiveRefs &&
+ git config --add receive.procReceiveRefs m:refs/heads/master &&
+ git config --add receive.procReceiveRefs ad:refs/heads &&
+ git config --add receive.procReceiveRefs "a!:refs/heads"
+ )
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/tags/v123 " \
+ -r "option refname refs/pull/124/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : master(B) tags/v123
+test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/master \
+ v123 >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/tags/v123
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head
+ * [new reference] v123 -> refs/pull/124/head
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup upstream: create tags/v123 ($PROTOCOL)" '
+ git -C "$upstream" update-ref refs/heads/topic $A &&
+ git -C "$upstream" update-ref refs/tags/v123 $TAG &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $ZERO_OID" \
+ -r "ok refs/heads/next" \
+ -r "option refname refs/pull/124/head" \
+ -r "option new-oid $A"
+ EOF
+'
+
+# Refs of upstream : master(A) topic(A) tags/v123
+# Refs of workbench: master(A) tags/v123
+# git push : NULL topic(B) NULL next(A)
+test_expect_success "proc-receive: create/delete branch, and delete tag ($PROTOCOL)" '
+ git -C workbench push origin \
+ :refs/heads/master \
+ $B:refs/heads/topic \
+ $A:refs/heads/next \
+ :refs/tags/v123 >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+ remote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <ZERO-OID>
+ remote: proc-receive> ok refs/heads/next
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+ remote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ - [deleted] refs/pull/123/head
+ <OID-A>..<OID-B> <COMMIT-B> -> topic
+ - [deleted] v123
+ * [new reference] <COMMIT-A> -> refs/pull/124/head
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-B> refs/heads/topic
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 759aec9305..dbc724e4c0 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -936,7 +936,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
git config fetch.unpackLimit 1 &&
git config gc.autoPackLimit 1 &&
git config gc.autoDetach false &&
- GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 &&
+ GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
test_i18ngrep "Auto packing the repository" fetch.out &&
! grep "Should I try again" fetch.out
)
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index de8e2f1531..bd202ec6f3 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' '
GIT_TRACE=1 git fetch --multiple one two 2>trace &&
git branch -r > output &&
test_cmp ../expect output &&
- grep "built-in: git gc" trace >gc &&
+ grep "built-in: git maintenance" trace >gc &&
test_line_count = 1 gc
)
'
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 36ad20a849..d11382f769 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1039,7 +1039,7 @@ test_force_fetch_tag "annotated tag" "-f -a -m'tag message'"
test_expect_success 'push --porcelain' '
mk_empty testrepo &&
echo >.git/foo "To testrepo" &&
- echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new branch]" &&
+ echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new reference]" &&
echo >>.git/foo "Done" &&
git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master &&
(
diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh
index 030331f1c5..7e928aff66 100755
--- a/t/t5534-push-signed.sh
+++ b/t/t5534-push-signed.sh
@@ -273,4 +273,27 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
test_cmp expect dst/push-cert-status
'
+test_expect_success GPG 'failed atomic push does not execute GPG' '
+ prepare_dst &&
+ git -C dst config receive.certnonceseed sekrit &&
+ write_script gpg <<-EOF &&
+ # should check atomic push locally before running GPG.
+ exit 1
+ EOF
+ test_must_fail env PATH="$TRASH_DIRECTORY:$PATH" git push \
+ --signed --atomic --porcelain \
+ dst noop ff noff >out 2>&1 &&
+
+ test_i18ngrep ! "gpg failed to sign" out &&
+ sed -n -e "/^To dst/,$ p" out >actual &&
+ cat >expect <<-EOF &&
+ To dst
+ = refs/heads/noop:refs/heads/noop [up to date]
+ ! refs/heads/ff:refs/heads/ff [rejected] (atomic push failed)
+ ! refs/heads/noff:refs/heads/noff [rejected] (non-fast-forward)
+ Done
+ EOF
+ test_i18ncmp expect actual
+'
+
test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 15fb64c18d..b6c8312da1 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -631,6 +631,20 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
test_i18ngrep "the following paths have collided" icasefs/warning
'
+test_expect_success 'clone with GIT_DEFAULT_HASH' '
+ (
+ sane_unset GIT_DEFAULT_HASH &&
+ git init --object-format=sha1 test-sha1 &&
+ git init --object-format=sha256 test-sha256
+ ) &&
+ test_commit -C test-sha1 foo &&
+ test_commit -C test-sha256 foo &&
+ GIT_DEFAULT_HASH=sha1 git clone test-sha256 test-clone-sha256 &&
+ GIT_DEFAULT_HASH=sha256 git clone test-sha1 test-clone-sha1 &&
+ git -C test-clone-sha1 status &&
+ git -C test-clone-sha256 status
+'
+
partial_clone_server () {
SERVER="$1" &&
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index b886529e59..aa226381be 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -82,6 +82,13 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
git bisect bad $HASH4
'
+test_expect_success 'bisect start without -- takes unknown arg as pathspec' '
+ git bisect reset &&
+ git bisect start foo bar &&
+ grep foo ".git/BISECT_NAMES" &&
+ grep bar ".git/BISECT_NAMES"
+'
+
test_expect_success 'bisect reset: back in the master branch' '
git bisect reset &&
echo "* master" > branch.expect &&
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 45e1f6ff68..963fed6821 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -814,6 +814,33 @@ test_expect_success 'status -s without relative paths' '
'
+cat >expect <<\EOF
+ M dir1/modified
+A dir2/added
+A "file with spaces"
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? "file with spaces 2"
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+ test_when_finished "git rm --cached \"file with spaces\"; rm -f file*" &&
+ >"file with spaces" &&
+ >"file with spaces 2" &&
+ >"expect with spaces" &&
+ git add "file with spaces" &&
+
+ git status -s >output &&
+ test_cmp expect output &&
+
+ git status -s --ignored >output &&
+ grep "^!! \"expect with spaces\"$" output &&
+ grep -v "^!! " output >output-wo-ignored &&
+ test_cmp expect output-wo-ignored
+'
+
test_expect_success 'dry-run of partial commit excluding new file in index' '
cat >expect <<EOF &&
On branch master
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index 0f97828cd0..c5c4ea5fc0 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -33,11 +33,18 @@ test_expect_success 'pull.rebase not set' '
test_i18ngrep "Pulling without specifying how to reconcile" 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_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 ! "Pulling without specifying how to reconcile" err
'
test_expect_success 'pull.rebase not set and pull.ff=only' '
@@ -59,6 +66,18 @@ test_expect_success 'pull.rebase not set and --no-rebase given' '
test_i18ngrep ! "Pulling without specifying how to reconcile" 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_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_expect_success 'pull.rebase not set and --ff-only given' '
git reset --hard c0 &&
git pull --ff-only . c1 2>err &&
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
new file mode 100755
index 0000000000..53c883531e
--- /dev/null
+++ b/t/t7900-maintenance.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+test_description='git maintenance builtin'
+
+. ./test-lib.sh
+
+GIT_TEST_COMMIT_GRAPH=0
+
+test_expect_success 'help text' '
+ test_expect_code 129 git maintenance -h 2>err &&
+ test_i18ngrep "usage: git maintenance run" err &&
+ test_expect_code 128 git maintenance barf 2>err &&
+ test_i18ngrep "invalid subcommand: barf" err &&
+ test_expect_code 129 git maintenance 2>err &&
+ test_i18ngrep "usage: git maintenance" err
+'
+
+test_expect_success 'run [--auto|--quiet]' '
+ GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \
+ git maintenance run 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \
+ git maintenance run --auto 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \
+ git maintenance run --no-quiet 2>/dev/null &&
+ test_subcommand git gc --quiet <run-no-auto.txt &&
+ test_subcommand ! git gc --auto --quiet <run-auto.txt &&
+ test_subcommand git gc --no-quiet <run-no-quiet.txt
+'
+
+test_expect_success 'maintenance.<task>.enabled' '
+ git config maintenance.gc.enabled false &&
+ git config maintenance.commit-graph.enabled true &&
+ GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err &&
+ test_subcommand ! git gc --quiet <run-config.txt &&
+ test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt
+'
+
+test_expect_success 'run --task=<task>' '
+ GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+ git maintenance run --task=commit-graph 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \
+ git maintenance run --task=gc 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \
+ git maintenance run --task=commit-graph 2>/dev/null &&
+ GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \
+ git maintenance run --task=commit-graph --task=gc 2>/dev/null &&
+ test_subcommand ! git gc --quiet <run-commit-graph.txt &&
+ test_subcommand git gc --quiet <run-gc.txt &&
+ test_subcommand git gc --quiet <run-both.txt &&
+ test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt &&
+ test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt &&
+ test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt
+'
+
+test_expect_success 'run --task=bogus' '
+ test_must_fail git maintenance run --task=bogus 2>err &&
+ test_i18ngrep "is not a valid task" err
+'
+
+test_expect_success 'run --task duplicate' '
+ test_must_fail git maintenance run --task=gc --task=gc 2>err &&
+ test_i18ngrep "cannot be selected multiple times" err
+'
+
+test_done
diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh
index 36dc31eb39..24ae5018e8 100755
--- a/t/t8013-blame-ignore-revs.sh
+++ b/t/t8013-blame-ignore-revs.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
test_tick &&
git commit -m X &&
git tag X &&
+ git tag -a -m "X (annotated)" XT &&
git blame --line-porcelain file >blame_raw &&
@@ -31,20 +32,36 @@ test_expect_success setup '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse X >expect &&
test_cmp expect actual
+'
+
+# Ensure bogus --ignore-rev requests are caught
+test_expect_success 'validate --ignore-rev' '
+ test_must_fail git blame --ignore-rev X^{tree} file
+'
+
+# Ensure bogus --ignore-revs-file requests are caught
+test_expect_success 'validate --ignore-revs-file' '
+ git rev-parse X^{tree} >ignore_x &&
+ test_must_fail git blame --ignore-revs-file ignore_x file
+'
+
+for I in X XT
+do
+ # Ignore X (or XT), make sure A is blamed for line 1 and B for line 2.
+ # Giving X (i.e. commit) and XT (i.e. annotated tag to commit) should
+ # produce the same result.
+ test_expect_success "ignore_rev_changing_lines ($I)" '
+ git blame --line-porcelain --ignore-rev $I file >blame_raw &&
+
+ grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
+ git rev-parse A >expect &&
+ test_cmp expect actual &&
+
+ grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
+ git rev-parse B >expect &&
+ test_cmp expect actual
'
-
-# Ignore X, make sure A is blamed for line 1 and B for line 2.
-test_expect_success ignore_rev_changing_lines '
- git blame --line-porcelain --ignore-rev X file >blame_raw &&
-
- grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
- git rev-parse A >expect &&
- test_cmp expect actual &&
-
- grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
- git rev-parse B >expect &&
- test_cmp expect actual
- '
+done
# For ignored revs that have added 'unblamable' lines, attribute those to the
# ignored commit.
@@ -67,7 +84,7 @@ test_expect_success ignore_rev_adding_unblamable_lines '
grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual &&
test_cmp expect actual
- '
+'
# Ignore X and Y, both in separate files. Lines 1 == A, 2 == B.
test_expect_success ignore_revs_from_files '
@@ -82,7 +99,7 @@ test_expect_success ignore_revs_from_files '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
- '
+'
# Ignore X from the config option, Y from a file.
test_expect_success ignore_revs_from_configs_and_files '
@@ -96,7 +113,7 @@ test_expect_success ignore_revs_from_configs_and_files '
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse B >expect &&
test_cmp expect actual
- '
+'
# Override blame.ignoreRevsFile (ignore_x) with an empty string. X should be
# blamed now for lines 1 and 2, since we are no longer ignoring X.
@@ -120,7 +137,7 @@ test_expect_success bad_files_and_revs '
echo NOREV >ignore_norev &&
test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
test_i18ngrep "invalid object name: NOREV" err
- '
+'
# For ignored revs that have added 'unblamable' lines, mark those lines with a
# '*'
@@ -138,7 +155,7 @@ test_expect_success mark_unblamable_lines '
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
- '
+'
# Commit Z will touch the first two lines. Y touched all four.
# A--B--X--Y--Z
@@ -171,7 +188,7 @@ test_expect_success mark_ignored_lines '
sed -n "4p" blame_raw | cut -c1 >actual &&
! test_cmp expect actual
- '
+'
# For ignored revs that added 'unblamable' lines and more recent commits changed
# the blamable lines, mark the unblamable lines with a
@@ -190,7 +207,7 @@ test_expect_success mark_unblamable_lines_intermediate '
sed -n "4p" blame_raw | cut -c1 >actual &&
test_cmp expect actual
- '
+'
# The heuristic called by guess_line_blames() tries to find the size of a
# blame_entry 'e' in the parent's address space. Those calculations need to
@@ -227,7 +244,7 @@ test_expect_success ignored_chunk_negative_parent_size '
git tag C &&
git blame file --ignore-rev B >blame_raw
- '
+'
# Resetting the repo and creating:
#
@@ -269,6 +286,6 @@ test_expect_success ignore_merge '
grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual &&
git rev-parse C >expect &&
test_cmp expect actual
- '
+'
test_done
diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh
index e9276c48f4..7194fb2855 100755
--- a/t/t9832-unshelve.sh
+++ b/t/t9832-unshelve.sh
@@ -29,8 +29,11 @@ test_expect_success 'init depot' '
)
'
+# Create an initial clone, with a commit unrelated to the P4 change
+# on HEAD
test_expect_success 'initial clone' '
- git p4 clone --dest="$git" //depot/@all
+ git p4 clone --dest="$git" //depot/@all &&
+ test_commit -C "$git" "unrelated"
'
test_expect_success 'create shelved changelist' '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index f9904066fe..8d59b90348 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1628,3 +1628,36 @@ test_path_is_hidden () {
case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
return 1
}
+
+# Check that the given command was invoked as part of the
+# trace2-format trace on stdin.
+#
+# test_subcommand [!] <command> <args>... < <trace>
+#
+# For example, to look for an invocation of "git upload-pack
+# /path/to/repo"
+#
+# GIT_TRACE2_EVENT=event.log git fetch ... &&
+# test_subcommand git upload-pack "$PATH" <event.log
+#
+# If the first parameter passed is !, this instead checks that
+# the given command was not called.
+#
+test_subcommand () {
+ local negate=
+ if test "$1" = "!"
+ then
+ negate=t
+ shift
+ fi
+
+ local expr=$(printf '"%s",' "$@")
+ expr="${expr%,}"
+
+ if test -n "$negate"
+ then
+ ! grep "\[$expr\]"
+ else
+ grep "\[$expr\]"
+ fi
+}
diff --git a/templates/hooks--pre-push.sample b/templates/hooks--pre-push.sample
index 6187dbf439..4ce688d32b 100755
--- a/templates/hooks--pre-push.sample
+++ b/templates/hooks--pre-push.sample
@@ -14,7 +14,7 @@
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
-# <local ref> <local sha1> <remote ref> <remote sha1>
+# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
@@ -22,27 +22,27 @@
remote="$1"
url="$2"
-z40=0000000000000000000000000000000000000000
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
-while read local_ref local_sha remote_ref remote_sha
+while read local_ref local_oid remote_ref remote_oid
do
- if [ "$local_sha" = $z40 ]
+ if test "$local_oid" = "$zero"
then
# Handle delete
:
else
- if [ "$remote_sha" = $z40 ]
+ if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
- range="$local_sha"
+ range="$local_oid"
else
# Update to existing branch, examine new commits
- range="$remote_sha..$local_sha"
+ range="$remote_oid..$local_oid"
fi
# Check for WIP commit
- commit=`git rev-list -n 1 --grep '^WIP' "$range"`
- if [ -n "$commit" ]
+ commit=$(git rev-list -n 1 --grep '^WIP' "$range")
+ if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
diff --git a/templates/hooks--update.sample b/templates/hooks--update.sample
index 5014c4b31c..c4d426bc6e 100755
--- a/templates/hooks--update.sample
+++ b/templates/hooks--update.sample
@@ -60,7 +60,7 @@ esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
-zero="0000000000000000000000000000000000000000"
+zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
diff --git a/transport-helper.c b/transport-helper.c
index c52c99d829..b573b6c730 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -723,13 +723,61 @@ static int fetch(struct transport *transport,
return -1;
}
+struct push_update_ref_state {
+ struct ref *hint;
+ struct ref_push_report *report;
+ int new_report;
+};
+
static int push_update_ref_status(struct strbuf *buf,
- struct ref **ref,
+ struct push_update_ref_state *state,
struct ref *remote_refs)
{
char *refname, *msg;
int status, forced = 0;
+ if (starts_with(buf->buf, "option ")) {
+ struct object_id old_oid, new_oid;
+ const char *key, *val;
+ char *p;
+
+ if (!state->hint || !(state->report || state->new_report))
+ die(_("'option' without a matching 'ok/error' directive"));
+ if (state->new_report) {
+ if (!state->hint->report) {
+ state->hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ state->report = state->hint->report;
+ } else {
+ state->report = state->hint->report;
+ while (state->report->next)
+ state->report = state->report->next;
+ state->report->next = xcalloc(1, sizeof(struct ref_push_report));
+ state->report = state->report->next;
+ }
+ state->new_report = 0;
+ }
+ key = buf->buf + 7;
+ p = strchr(key, ' ');
+ if (p)
+ *p++ = '\0';
+ val = p;
+ if (!strcmp(key, "refname"))
+ state->report->ref_name = xstrdup_or_null(val);
+ else if (!strcmp(key, "old-oid") && val &&
+ !parse_oid_hex(val, &old_oid, &val))
+ state->report->old_oid = oiddup(&old_oid);
+ else if (!strcmp(key, "new-oid") && val &&
+ !parse_oid_hex(val, &new_oid, &val))
+ state->report->new_oid = oiddup(&new_oid);
+ else if (!strcmp(key, "forced-update"))
+ state->report->forced_update = 1;
+ /* Not update remote namespace again. */
+ return 1;
+ }
+
+ state->report = NULL;
+ state->new_report = 0;
+
if (starts_with(buf->buf, "ok ")) {
status = REF_STATUS_OK;
refname = buf->buf + 3;
@@ -785,16 +833,16 @@ static int push_update_ref_status(struct strbuf *buf,
}
}
- if (*ref)
- *ref = find_ref_by_name(*ref, refname);
- if (!*ref)
- *ref = find_ref_by_name(remote_refs, refname);
- if (!*ref) {
+ if (state->hint)
+ state->hint = find_ref_by_name(state->hint, refname);
+ if (!state->hint)
+ state->hint = find_ref_by_name(remote_refs, refname);
+ if (!state->hint) {
warning(_("helper reported unexpected status of %s"), refname);
return 1;
}
- if ((*ref)->status != REF_STATUS_NONE) {
+ if (state->hint->status != REF_STATUS_NONE) {
/*
* Earlier, the ref was marked not to be pushed, so ignore the ref
* status reported by the remote helper if the latter is 'no match'.
@@ -803,9 +851,11 @@ static int push_update_ref_status(struct strbuf *buf,
return 1;
}
- (*ref)->status = status;
- (*ref)->forced_update |= forced;
- (*ref)->remote_status = msg;
+ if (status == REF_STATUS_OK)
+ state->new_report = 1;
+ state->hint->status = status;
+ state->hint->forced_update |= forced;
+ state->hint->remote_status = msg;
return !(status == REF_STATUS_OK);
}
@@ -813,37 +863,57 @@ static int push_update_refs_status(struct helper_data *data,
struct ref *remote_refs,
int flags)
{
+ struct ref *ref;
+ struct ref_push_report *report;
struct strbuf buf = STRBUF_INIT;
- struct ref *ref = remote_refs;
- int ret = 0;
+ struct push_update_ref_state state = { remote_refs, NULL, 0 };
for (;;) {
- char *private;
-
if (recvline(data, &buf)) {
- ret = 1;
- break;
+ strbuf_release(&buf);
+ return 1;
}
-
if (!buf.len)
break;
+ push_update_ref_status(&buf, &state, remote_refs);
+ }
+ strbuf_release(&buf);
- if (push_update_ref_status(&buf, &ref, remote_refs))
- continue;
+ if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)
+ return 0;
- if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)
- continue;
+ /* propagate back the update to the remote namespace */
+ for (ref = remote_refs; ref; ref = ref->next) {
+ char *private;
- /* propagate back the update to the remote namespace */
- private = apply_refspecs(&data->rs, ref->name);
- if (!private)
+ if (ref->status != REF_STATUS_OK)
continue;
- update_ref("update by helper", private, &ref->new_oid, NULL,
- 0, 0);
- free(private);
+
+ if (!ref->report) {
+ private = apply_refspecs(&data->rs, ref->name);
+ if (!private)
+ continue;
+ update_ref("update by helper", private, &(ref->new_oid),
+ NULL, 0, 0);
+ free(private);
+ } else {
+ for (report = ref->report; report; report = report->next) {
+ private = apply_refspecs(&data->rs,
+ report->ref_name
+ ? report->ref_name
+ : ref->name);
+ if (!private)
+ continue;
+ update_ref("update by helper", private,
+ report->new_oid
+ ? report->new_oid
+ : &(ref->new_oid),
+ NULL, 0, 0);
+ free(private);
+ }
+ }
}
- strbuf_release(&buf);
- return ret;
+ return 0;
}
static void set_common_push_options(struct transport *transport,
diff --git a/transport.c b/transport.c
index 43e24bf1e5..ffe2115845 100644
--- a/transport.c
+++ b/transport.c
@@ -432,38 +432,67 @@ int transport_refs_pushed(struct ref *ref)
return 0;
}
-void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+static void update_one_tracking_ref(struct remote *remote, char *refname,
+ struct object_id *new_oid, int deletion,
+ int verbose)
{
struct refspec_item rs;
- if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
- return;
-
memset(&rs, 0, sizeof(rs));
- rs.src = ref->name;
+ rs.src = refname;
rs.dst = NULL;
if (!remote_find_tracking(remote, &rs)) {
if (verbose)
fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
- if (ref->deletion) {
+ if (deletion)
delete_ref(NULL, rs.dst, NULL, 0);
- } else
- update_ref("update by push", rs.dst, &ref->new_oid,
+ else
+ update_ref("update by push", rs.dst, new_oid,
NULL, 0, 0);
free(rs.dst);
}
}
+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+{
+ char *refname;
+ struct object_id *new_oid;
+ struct ref_push_report *report;
+
+ if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
+ return;
+
+ report = ref->report;
+ if (!report)
+ update_one_tracking_ref(remote, ref->name, &ref->new_oid,
+ ref->deletion, verbose);
+ else
+ for (; report; report = report->next) {
+ refname = report->ref_name ? (char *)report->ref_name : ref->name;
+ new_oid = report->new_oid ? report->new_oid : &ref->new_oid;
+ update_one_tracking_ref(remote, refname, new_oid,
+ is_null_oid(new_oid), verbose);
+ }
+}
+
static void print_ref_status(char flag, const char *summary,
struct ref *to, struct ref *from, const char *msg,
+ struct ref_push_report *report,
int porcelain, int summary_width)
{
+ const char *to_name;
+
+ if (report && report->ref_name)
+ to_name = report->ref_name;
+ else
+ to_name = to->name;
+
if (porcelain) {
if (from)
- fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to->name);
+ fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to_name);
else
- fprintf(stdout, "%c\t:%s\t", flag, to->name);
+ fprintf(stdout, "%c\t:%s\t", flag, to_name);
if (msg)
fprintf(stdout, "%s (%s)\n", summary, msg);
else
@@ -477,9 +506,11 @@ static void print_ref_status(char flag, const char *summary,
fprintf(stderr, " %s%c %-*s%s ", red, flag, summary_width,
summary, reset);
if (from)
- fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
+ fprintf(stderr, "%s -> %s",
+ prettify_refname(from->name),
+ prettify_refname(to_name));
else
- fputs(prettify_refname(to->name), stderr);
+ fputs(prettify_refname(to_name), stderr);
if (msg) {
fputs(" (", stderr);
fputs(msg, stderr);
@@ -489,24 +520,52 @@ static void print_ref_status(char flag, const char *summary,
}
}
-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)
+static void print_ok_ref_status(struct ref *ref,
+ struct ref_push_report *report,
+ int porcelain, int summary_width)
{
+ struct object_id *old_oid;
+ struct object_id *new_oid;
+ const char *ref_name;
+ int forced_update;
+
+ if (report && report->old_oid)
+ old_oid = report->old_oid;
+ else
+ old_oid = &ref->old_oid;
+ if (report && report->new_oid)
+ new_oid = report->new_oid;
+ else
+ new_oid = &ref->new_oid;
+ if (report && report->forced_update)
+ forced_update = report->forced_update;
+ else
+ forced_update = ref->forced_update;
+ if (report && report->ref_name)
+ ref_name = report->ref_name;
+ else
+ ref_name = ref->name;
+
if (ref->deletion)
print_ref_status('-', "[deleted]", ref, NULL, NULL,
- porcelain, summary_width);
- else if (is_null_oid(&ref->old_oid))
+ report, porcelain, summary_width);
+ else if (is_null_oid(old_oid))
print_ref_status('*',
- (starts_with(ref->name, "refs/tags/") ? "[new tag]" :
- "[new branch]"),
- ref, ref->peer_ref, NULL, porcelain, summary_width);
+ (starts_with(ref_name, "refs/tags/")
+ ? "[new tag]"
+ : (starts_with(ref_name, "refs/heads/")
+ ? "[new branch]"
+ : "[new reference]")),
+ ref, ref->peer_ref, NULL,
+ report, porcelain, summary_width);
else {
struct strbuf quickref = STRBUF_INIT;
char type;
const char *msg;
- strbuf_add_unique_abbrev(&quickref, &ref->old_oid,
+ strbuf_add_unique_abbrev(&quickref, old_oid,
DEFAULT_ABBREV);
- if (ref->forced_update) {
+ if (forced_update) {
strbuf_addstr(&quickref, "...");
type = '+';
msg = "forced update";
@@ -515,16 +574,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt
type = ' ';
msg = NULL;
}
- strbuf_add_unique_abbrev(&quickref, &ref->new_oid,
+ strbuf_add_unique_abbrev(&quickref, new_oid,
DEFAULT_ABBREV);
print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,
- porcelain, summary_width);
+ report, porcelain, summary_width);
strbuf_release(&quickref);
}
}
-static int print_one_push_status(struct ref *ref, const char *dest, int count,
+static int print_one_push_report(struct ref *ref, const char *dest, int count,
+ struct ref_push_report *report,
int porcelain, int summary_width)
{
if (!count) {
@@ -536,65 +596,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,
switch(ref->status) {
case REF_STATUS_NONE:
print_ref_status('X', "[no match]", ref, NULL, NULL,
- porcelain, summary_width);
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_NODELETE:
print_ref_status('!', "[rejected]", ref, NULL,
"remote does not support deleting refs",
- porcelain, summary_width);
+ report, porcelain, summary_width);
break;
case REF_STATUS_UPTODATE:
print_ref_status('=', "[up to date]", ref,
- ref->peer_ref, NULL, porcelain, summary_width);
+ ref->peer_ref, NULL,
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_NONFASTFORWARD:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "non-fast-forward", porcelain, summary_width);
+ "non-fast-forward",
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_ALREADY_EXISTS:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "already exists", porcelain, summary_width);
+ "already exists",
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_FETCH_FIRST:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "fetch first", porcelain, summary_width);
+ "fetch first",
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_NEEDS_FORCE:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "needs force", porcelain, summary_width);
+ "needs force",
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_STALE:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "stale info", porcelain, summary_width);
+ "stale info",
+ report, porcelain, summary_width);
break;
case REF_STATUS_REJECT_SHALLOW:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
"new shallow roots not allowed",
- porcelain, summary_width);
+ report, porcelain, summary_width);
break;
case REF_STATUS_REMOTE_REJECT:
print_ref_status('!', "[remote rejected]", ref,
ref->deletion ? NULL : ref->peer_ref,
- ref->remote_status, porcelain, summary_width);
+ ref->remote_status,
+ report, porcelain, summary_width);
break;
case REF_STATUS_EXPECTING_REPORT:
print_ref_status('!', "[remote failure]", ref,
ref->deletion ? NULL : ref->peer_ref,
"remote failed to report status",
- porcelain, summary_width);
+ report, porcelain, summary_width);
break;
case REF_STATUS_ATOMIC_PUSH_FAILED:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
- "atomic push failed", porcelain, summary_width);
+ "atomic push failed",
+ report, porcelain, summary_width);
break;
case REF_STATUS_OK:
- print_ok_ref_status(ref, porcelain, summary_width);
+ print_ok_ref_status(ref, report, porcelain, summary_width);
break;
}
return 1;
}
+static int print_one_push_status(struct ref *ref, const char *dest, int count,
+ int porcelain, int summary_width)
+{
+ struct ref_push_report *report;
+ int n = 0;
+
+ if (!ref->report)
+ return print_one_push_report(ref, dest, count,
+ NULL, porcelain, summary_width);
+
+ for (report = ref->report; report; report = report->next)
+ print_one_push_report(ref, dest, count + n++,
+ report, porcelain, summary_width);
+ return n;
+}
+
static int measure_abbrev(const struct object_id *oid, int sofar)
{
char hex[GIT_MAX_HEXSZ + 1];
diff --git a/tree-diff.c b/tree-diff.c
index 6ebad1a46f..7cebbb327e 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -434,7 +434,7 @@ static struct combine_diff_path *ll_diff_tree_paths(
if (diff_can_quit_early(opt))
break;
- if (opt->max_changes && opt->num_changes > opt->max_changes)
+ if (opt->max_changes && diff_queued_diff.nr > opt->max_changes)
break;
if (opt->pathspec.nr) {
@@ -521,7 +521,6 @@ static struct combine_diff_path *ll_diff_tree_paths(
/* t↓ */
update_tree_entry(&t);
- opt->num_changes++;
}
/* t > p[imin] */
@@ -539,7 +538,6 @@ static struct combine_diff_path *ll_diff_tree_paths(
skip_emit_tp:
/* ∀ pi=p[imin] pi↓ */
update_tp_entries(tp, nparent);
- opt->num_changes++;
}
}
@@ -557,7 +555,6 @@ struct combine_diff_path *diff_tree_paths(
const struct object_id **parents_oid, int nparent,
struct strbuf *base, struct diff_options *opt)
{
- opt->num_changes = 0;
p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt);
/*
diff --git a/wt-status.c b/wt-status.c
index d9872d543b..160d14970a 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -259,8 +259,6 @@ static void wt_longstatus_print_trailer(struct wt_status *s)
status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
}
-#define quote_path quote_path_relative
-
static const char *wt_status_unmerged_status_string(int stagemask)
{
switch (stagemask) {
@@ -338,7 +336,7 @@ static void wt_longstatus_print_unmerged_data(struct wt_status *s,
memset(padding, ' ', label_width);
}
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, 0);
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
how = wt_status_unmerged_status_string(d->stagemask);
@@ -404,8 +402,8 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
if (d->rename_status == status)
one_name = d->rename_source;
- one = quote_path(one_name, s->prefix, &onebuf);
- two = quote_path(two_name, s->prefix, &twobuf);
+ one = quote_path(one_name, s->prefix, &onebuf, 0);
+ two = quote_path(two_name, s->prefix, &twobuf, 0);
status_printf(s, color(WT_STATUS_HEADER, s), "\t");
what = wt_status_diff_status_string(status);
@@ -966,7 +964,7 @@ static void wt_longstatus_print_other(struct wt_status *s,
struct string_list_item *it;
const char *path;
it = &(l->items[i]);
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
if (column_active(s->colopts)) {
string_list_append(&output, path);
continue;
@@ -1850,7 +1848,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it,
} else {
struct strbuf onebuf = STRBUF_INIT;
const char *one;
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
printf(" %s\n", one);
strbuf_release(&onebuf);
}
@@ -1879,21 +1877,12 @@ static void wt_shortstatus_status(struct string_list_item *it,
const char *one;
if (d->rename_source) {
- one = quote_path(d->rename_source, s->prefix, &onebuf);
- if (*one != '"' && strchr(one, ' ') != NULL) {
- putchar('"');
- strbuf_addch(&onebuf, '"');
- one = onebuf.buf;
- }
+ one = quote_path(d->rename_source, s->prefix, &onebuf,
+ QUOTE_PATH_QUOTE_SP);
printf("%s -> ", one);
strbuf_release(&onebuf);
}
- one = quote_path(it->string, s->prefix, &onebuf);
- if (*one != '"' && strchr(one, ' ') != NULL) {
- putchar('"');
- strbuf_addch(&onebuf, '"');
- one = onebuf.buf;
- }
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
printf("%s\n", one);
strbuf_release(&onebuf);
}
@@ -1907,7 +1896,7 @@ static void wt_shortstatus_other(struct string_list_item *it,
} else {
struct strbuf onebuf = STRBUF_INIT;
const char *one;
- one = quote_path(it->string, s->prefix, &onebuf);
+ one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
printf(" %s\n", one);
strbuf_release(&onebuf);
@@ -2224,9 +2213,9 @@ static void wt_porcelain_v2_print_changed_entry(
*/
sep_char = '\t';
eol_char = '\n';
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
if (d->rename_source)
- path_from = quote_path(d->rename_source, s->prefix, &buf_from);
+ path_from = quote_path(d->rename_source, s->prefix, &buf_from, 0);
}
if (path_from)
@@ -2312,7 +2301,7 @@ static void wt_porcelain_v2_print_unmerged_entry(
if (s->null_termination)
path_index = it->string;
else
- path_index = quote_path(it->string, s->prefix, &buf_index);
+ path_index = quote_path(it->string, s->prefix, &buf_index, 0);
fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
unmerged_prefix, key, submodule_token,
@@ -2345,7 +2334,7 @@ static void wt_porcelain_v2_print_other(
path = it->string;
eol_char = '\0';
} else {
- path = quote_path(it->string, s->prefix, &buf);
+ path = quote_path(it->string, s->prefix, &buf, 0);
eol_char = '\n';
}