summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml4
-rw-r--r--Documentation/MyFirstObjectWalk.txt4
-rw-r--r--Documentation/RelNotes/2.34.0.txt149
-rw-r--r--Documentation/config/advice.txt3
-rw-r--r--Documentation/config/gui.txt2
-rw-r--r--Documentation/config/help.txt16
-rw-r--r--Documentation/config/transfer.txt14
-rw-r--r--Documentation/git-branch.txt3
-rw-r--r--Documentation/git-bugreport.txt4
-rw-r--r--Documentation/git-column.txt2
-rw-r--r--Documentation/git-maintenance.txt57
-rw-r--r--Documentation/git-multi-pack-index.txt20
-rw-r--r--Documentation/git-rebase.txt12
-rw-r--r--Documentation/pretty-options.txt6
-rw-r--r--Documentation/rev-list-options.txt8
-rw-r--r--Documentation/technical/bitmap-format.txt71
-rw-r--r--Documentation/technical/multi-pack-index.txt10
-rw-r--r--Makefile22
-rw-r--r--advice.c84
-rw-r--r--advice.h34
-rw-r--r--apply.c21
-rw-r--r--archive.c7
-rw-r--r--branch.c2
-rw-r--r--builtin/add.c15
-rw-r--r--builtin/am.c2
-rw-r--r--builtin/archive.c4
-rw-r--r--builtin/branch.c2
-rw-r--r--builtin/bugreport.c5
-rw-r--r--builtin/checkout.c8
-rw-r--r--builtin/clone.c15
-rw-r--r--builtin/column.c2
-rw-r--r--builtin/commit-graph.c106
-rw-r--r--builtin/commit-tree.c4
-rw-r--r--builtin/commit.c9
-rw-r--r--builtin/diff-index.c6
-rw-r--r--builtin/fast-export.c1
-rw-r--r--builtin/fetch.c93
-rw-r--r--builtin/gc.c672
-rw-r--r--builtin/grep.c64
-rw-r--r--builtin/hash-object.c6
-rw-r--r--builtin/index-pack.c8
-rw-r--r--builtin/log.c2
-rw-r--r--builtin/ls-remote.c2
-rw-r--r--builtin/mailsplit.c4
-rw-r--r--builtin/merge.c8
-rw-r--r--builtin/multi-pack-index.c13
-rw-r--r--builtin/notes.c4
-rw-r--r--builtin/pack-objects.c93
-rw-r--r--builtin/push.c12
-rw-r--r--builtin/rebase.c3
-rw-r--r--builtin/receive-pack.c17
-rw-r--r--builtin/repack.c12
-rw-r--r--builtin/replace.c2
-rw-r--r--builtin/reset.c2
-rw-r--r--builtin/revert.c3
-rw-r--r--builtin/rm.c2
-rw-r--r--builtin/show-branch.c15
-rw-r--r--builtin/submodule--helper.c2
-rw-r--r--builtin/tag.c4
-rw-r--r--builtin/update-index.c4
-rw-r--r--cache.h9
-rwxr-xr-xcheck_bindir13
-rwxr-xr-xci/install-docker-dependencies.sh4
-rwxr-xr-xci/run-build-and-tests.sh11
-rw-r--r--commit-graph.c77
-rw-r--r--commit-graph.h8
-rw-r--r--commit.c4
-rw-r--r--commit.h1
-rw-r--r--compat/linux/procinfo.c169
-rw-r--r--compat/mmap.c7
-rw-r--r--config.c20
-rw-r--r--config.h3
-rw-r--r--connected.c16
-rw-r--r--connected.h2
-rw-r--r--contrib/coccinelle/xopen.cocci16
-rw-r--r--contrib/completion/git-completion.bash14
-rw-r--r--contrib/completion/git-completion.tcsh5
-rw-r--r--credential.c1
-rw-r--r--csum-file.c8
-rwxr-xr-xdetect-compiler9
-rw-r--r--diff-lib.c4
-rw-r--r--diff-merges.c14
-rw-r--r--diff-merges.h2
-rw-r--r--editor.c2
-rw-r--r--entry.c13
-rw-r--r--entry.h3
-rw-r--r--fetch-pack.c12
-rwxr-xr-xgit-send-email.perl28
-rw-r--r--git.c2
-rwxr-xr-xgitweb/gitweb.perl3
-rw-r--r--grep.c51
-rw-r--r--grep.h22
-rw-r--r--help.c21
-rw-r--r--ls-refs.c21
-rw-r--r--mailmap.c2
-rw-r--r--merge-recursive.c5
-rw-r--r--midx.c331
-rw-r--r--midx.h5
-rw-r--r--notes-merge.c2
-rw-r--r--object-file.c26
-rw-r--r--object-name.c2
-rw-r--r--object-store.h7
-rw-r--r--object.h1
-rw-r--r--pack-bitmap-write.c79
-rw-r--r--pack-bitmap.c497
-rw-r--r--pack-bitmap.h8
-rw-r--r--pack-write.c11
-rw-r--r--packfile.c8
-rw-r--r--path.c13
-rw-r--r--pkt-line.c37
-rw-r--r--pkt-line.h11
-rw-r--r--pretty.c6
-rw-r--r--protocol-caps.c5
-rw-r--r--range-diff.c3
-rw-r--r--remote.c18
-rw-r--r--revision.c38
-rw-r--r--revision.h7
-rw-r--r--run-command.c6
-rw-r--r--sequencer.c159
-rw-r--r--sequencer.h1
-rw-r--r--sparse-index.c1
-rw-r--r--submodule-config.c5
-rw-r--r--submodule.c39
-rw-r--r--submodule.h8
-rw-r--r--t/README25
-rw-r--r--t/helper/test-read-midx.c16
-rw-r--r--t/lib-bitmap.sh240
-rw-r--r--t/lib-rebase.sh56
-rw-r--r--t/perf/lib-bitmap.sh69
-rwxr-xr-xt/perf/p5310-pack-bitmaps.sh65
-rwxr-xr-xt/perf/p5326-multi-pack-bitmaps.sh43
-rwxr-xr-xt/t0021-conversion.sh71
-rwxr-xr-xt/t0410-partial-clone.sh12
-rwxr-xr-xt/t3200-branch.sh13
-rwxr-xr-xt/t3403-rebase-skip.sh13
-rwxr-xr-xt/t3404-rebase-interactive.sh13
-rwxr-xr-xt/t3418-rebase-continue.sh18
-rwxr-xr-xt/t3430-rebase-merges.sh38
-rwxr-xr-xt/t3501-revert-cherry-pick.sh16
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh17
-rw-r--r--t/t4018/php-enum4
-rwxr-xr-xt/t4045-diff-relative.sh53
-rwxr-xr-xt/t4060-diff-submodule-option-diff-format.sh159
-rwxr-xr-xt/t4108-apply-threeway.sh45
-rwxr-xr-xt/t4210-log-i18n.sh7
-rwxr-xr-xt/t5310-pack-bitmaps.sh233
-rwxr-xr-xt/t5318-commit-graph.sh19
-rwxr-xr-xt/t5319-multi-pack-index.sh63
-rwxr-xr-xt/t5323-pack-redundant.sh4
-rwxr-xr-xt/t5326-multi-pack-bitmaps.sh286
-rwxr-xr-xt/t5582-fetch-negative-refspec.sh1
-rwxr-xr-xt/t5606-clone-options.sh12
-rwxr-xr-xt/t5702-protocol-v2.sh6
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh208
-rwxr-xr-xt/t6000-rev-list-misc.sh31
-rwxr-xr-xt/t6300-for-each-ref.sh29
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh9
-rwxr-xr-xt/t7700-repack.sh18
-rwxr-xr-xt/t7814-grep-recurse-submodules.sh3
-rwxr-xr-xt/t7900-maintenance.sh127
-rwxr-xr-xt/t9001-send-email.sh62
-rwxr-xr-xt/t9002-column.sh18
-rwxr-xr-xt/t9351-fast-export-anonymize.sh10
-rwxr-xr-xt/t9902-completion.sh15
-rw-r--r--t/test-lib-functions.sh107
-rw-r--r--t/test-lib.sh9
-rw-r--r--trace2/tr2_tls.c1
-rw-r--r--unpack-trees.c20
-rw-r--r--upload-pack.c29
-rw-r--r--userdiff.c2
-rw-r--r--wrapper.c4
-rw-r--r--write-or-die.c12
-rw-r--r--wt-status.c6
173 files changed, 4649 insertions, 1405 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 47876a4f02..b053b01c66 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -259,6 +259,8 @@ jobs:
image: alpine
- jobname: Linux32
image: daald/ubuntu32:xenial
+ - jobname: pedantic
+ image: fedora
env:
jobname: ${{matrix.vector.jobname}}
runs-on: ubuntu-latest
@@ -271,7 +273,7 @@ jobs:
if: failure()
- name: Upload failed tests' directories
if: failure() && env.FAILED_TEST_ARTIFACTS != ''
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v1
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt
index 2d10eea7a9..45eb84d8b4 100644
--- a/Documentation/MyFirstObjectWalk.txt
+++ b/Documentation/MyFirstObjectWalk.txt
@@ -691,7 +691,7 @@ help understand. In our case, that means we omit trees and blobs not directly
referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only
`HEAD` in the `pending` list.)
-First, we'll need to `#include "list-objects-filter-options.h`" and set up the
+First, we'll need to `#include "list-objects-filter-options.h"` and set up the
`struct list_objects_filter_options` at the top of the function.
----
@@ -779,7 +779,7 @@ Count all the objects within and modify the print statement:
while ((oid = oidset_iter_next(&oit)))
omitted_count++;
- printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n",
+ printf("commits %d\nblobs %d\ntags %d\ntrees %d\nomitted %d\n",
commit_count, blob_count, tag_count, tree_count, omitted_count);
----
diff --git a/Documentation/RelNotes/2.34.0.txt b/Documentation/RelNotes/2.34.0.txt
index e378a99416..50710e603c 100644
--- a/Documentation/RelNotes/2.34.0.txt
+++ b/Documentation/RelNotes/2.34.0.txt
@@ -14,6 +14,26 @@ UI, Workflows & Features
* The userdiff pattern for "java" language has been updated.
+ * "git rebase" by default skips changes that are equivalent to
+ commits that are already in the history the branch is rebased onto;
+ give messages when this happens to let the users be aware of
+ skipped commits, and also teach them how to tell "rebase" to keep
+ duplicated changes.
+
+ * The advice message that "git cherry-pick" gives when it asks
+ conflicted replay of a commit to be resolved by the end user has
+ been updated.
+
+ * After "git clone --recurse-submodules", all submodules are cloned
+ but they are not by default recursed into by other commands. With
+ submodule.stickyRecursiveClone configuration set, submodule.recurse
+ configuration is set to true in a repository created by "clone"
+ with "--recurse-submodules" option.
+
+ * The logic for auto-correction of misspelt subcommands learned to go
+ interactive when the help.autocorrect configuration variable is set
+ to 'prompt'.
+
Performance, Internal Implementation, Development Support etc.
@@ -44,6 +64,22 @@ Performance, Internal Implementation, Development Support etc.
that checking for the lack of a prerequisite would not work. Avoid
the use of "if ! test_have_prereq X" in a test script.
+ * The revision traversal API has been optimized by taking advantage
+ of the commit-graph, when available, to determine if a commit is
+ reachable from any of the existing refs.
+
+ * "git fetch --quiet" optimization to avoid useless computation of
+ info that will never be displayed.
+
+ * Callers from older advice_config[] based API has been updated to
+ use the newer advice_if_enabled() and advice_enabled() API.
+
+ * Teach "test_pause" and "debug" helpers to allow using the HOME and
+ TERM environment variables the user usually uses.
+
+ * "make INSTALL_STRIP=-s install" allows the installation step to use
+ "install -s" to strip the binaries as they get installed.
+
Fixes since v2.33
-----------------
@@ -69,9 +105,122 @@ Fixes since v2.33
* "git range-diff" code clean-up.
(merge c4d5907324 jk/range-diff-fixes later to maint).
+ * "git commit --fixup" now works with "--edit" again, after it was
+ broken in v2.32.
+ (merge 8ef6aad664 jk/commit-edit-fixup-fix later to maint).
+
+ * Use upload-artifacts v1 (instead of v2) for 32-bit linux, as the
+ new version has a blocker bug for that architecture.
+ (merge 3cf9bb36bf cb/ci-use-upload-artifacts-v1 later to maint).
+
+ * Checking out all the paths from HEAD during the last conflicted
+ step in "git rebase" and continuing would cause the step to be
+ skipped (which is expected), but leaves MERGE_MSG file behind in
+ $GIT_DIR and confuses the next "git commit", which has been
+ corrected.
+ (merge e5ee33e855 pw/rebase-skip-final-fix later to maint).
+
+ * Various bugs in "git rebase -r" have been fixed.
+ (merge f2563c9ef3 pw/rebase-r-fixes later to maint).
+
+ * mmap() imitation used to call xmalloc() that dies upon malloc()
+ failure, which has been corrected to just return an error to the
+ caller to be handled.
+ (merge 95b4ff3931 rs/git-mmap-uses-malloc later to maint).
+
+ * "git diff --relative" segfaulted and/or produced incorrect result
+ when there are unmerged paths.
+ (merge 8174627b3d dd/diff-files-unmerged-fix later to maint).
+
+ * The delayed checkout code path in "git checkout" etc. were chatty
+ even when --quiet and/or --no-progress options were given.
+ (merge 7a132c628e mt/quiet-with-delayed-checkout later to maint).
+
+ * "git branch -D <branch>" used to refuse to remove a broken branch
+ ref that points at a missing commit, which has been corrected.
+ (merge 597a977489 rs/branch-allow-deleting-dangling later to maint).
+
+ * Build update for Apple clang.
+ (merge f32c5d3716 cb/makefile-apple-clang later to maint).
+
+ * The parser for the "--nl" option of "git column" has been
+ corrected.
+ (merge c93ca46cf5 sg/column-nl later to maint).
+
+ * "git upload-pack" which runs on the other side of "git fetch"
+ forgot to take the ref namespaces into account when handling
+ want-ref requests.
+ (merge 53a66ec37c ka/want-ref-in-namespace later to maint).
+
+ * The sparse-index support can corrupt the index structure by storing
+ a stale and/or uninitialized data, which has been corrected.
+ (merge d9e9b44d7a jh/sparse-index-resize-fix later to maint).
+
+ * Buggy tests could damage repositories outside the throw-away test
+ area we created. We now by default export GIT_CEILING_DIRECTORIES
+ to limit the damage from such a stray test.
+ (merge 614c3d8f2e sg/set-ceiling-during-tests later to maint).
+
+ * Even when running "git send-email" without its own threaded
+ discussion support, a threading related header in one message is
+ carried over to the subsequent message to result in an unwanted
+ threading, which has been corrected.
+ (merge e082113484 mh/send-email-reset-in-reply-to later to maint).
+
+ * The output from "git fast-export", when its anonymization feature
+ is in use, showed an annotated tag incorrectly.
+ (merge 2f040a9671 tk/fast-export-anonymized-tag-fix later to maint).
+
+ * Doc update plus improved error reporting.
+ (merge 1e93770888 jk/log-warn-on-bogus-encoding later to maint).
+
+ * Recent "diff -m" changes broke "gitk", which has been corrected.
+ (merge 5acffd3473 so/diff-index-regression-fix later to maint).
+
+ * Regression fix.
+ (merge b996f84989 ab/send-email-config-fix later to maint).
+
+ * The "git apply -3" code path learned not to bother the lower level
+ merge machinery when the three-way merge can be trivially resolved
+ without the content level merge. This fixes a regression caused by
+ recent "-3way first and fall back to direct application" change.
+ (merge 57f183b698 jc/trivial-threeway-binary-merge later to maint).
+
+ * The code that optionally creates the *.rev reverse index file has
+ been optimized to avoid needless computation when it is not writing
+ the file out.
+ (merge 8fe8bae9d2 ab/reverse-midx-optim later to maint).
+
+ * "git range-diff -I... <range> <range>" segfaulted, which has been
+ corrected.
+ (merge 709b3f32d3 rs/range-diff-avoid-segfault-with-I later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 1d9c8daef8 ab/bundle-doc later to maint).
(merge 81483fe613 en/merge-strategy-docs later to maint).
(merge 626beebdf8 js/log-protocol-version later to maint).
(merge 00e302da76 cb/builtin-merge-format-string-fix later to maint).
(merge ad51ae4dc0 cb/ci-freebsd-update later to maint).
+ (merge be6444d1ca fc/completion-updates later to maint).
+ (merge ff7b83f562 ti/tcsh-completion-regression-fix later to maint).
+ (merge 325b06deda sg/make-fix-ar-invocation later to maint).
+ (merge bd72824c60 me/t5582-cleanup later to maint).
+ (merge f6a5af0f62 ga/send-email-sendmail-cmd later to maint).
+ (merge f58c7468cd ab/ls-remote-packet-trace later to maint).
+ (merge 0160f7e725 ab/rebase-fatal-fatal-fix later to maint).
+ (merge a16eb6b1ff js/maintenance-launchctl-fix later to maint).
+ (merge c21b2511c2 jk/t5323-no-pack-test-fix later to maint).
+ (merge 5146c2f148 mh/credential-leakfix later to maint).
+ (merge 1549577338 dd/t6300-wo-gpg-fix later to maint).
+ (merge 66e905b7dd rs/xopen-reports-open-failures later to maint).
+ (merge 469888e6a5 es/walken-tutorial-fix later to maint).
+ (merge 88682b016d ba/object-info later to maint).
+ (merge b45c172e51 ab/gc-log-rephrase later to maint).
+ (merge ccdd5d1eb1 ab/mailmap-leakfix later to maint).
+ (merge 6540b71614 cb/remote-ndebug-fix later to maint).
+ (merge e4f8d27585 rs/show-branch-simplify later to maint).
+ (merge e124ecf7f7 rs/archive-use-object-id later to maint).
+ (merge cebead1ebf cb/ci-build-pedantic later to maint).
+ (merge ca0cc98e03 bs/doc-bugreport-outdir later to maint).
+ (merge 72b113e562 ab/no-more-check-bindir later to maint).
+ (merge 92a5d1c9b4 jc/prefix-filename-allocates later to maint).
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 8b2849ff7b..063eec2511 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -44,6 +44,9 @@ advice.*::
Shown when linkgit:git-push[1] rejects a forced update of
a branch when its remote-tracking ref has updates that we
do not have locally.
+ skippedCherryPicks::
+ Shown when linkgit:git-rebase[1] skips a commit that has already
+ been cherry-picked onto the upstream branch.
statusAheadBehind::
Shown when linkgit:git-status[1] computes the ahead/behind
counts for a local ref compared to its remote tracking ref,
diff --git a/Documentation/config/gui.txt b/Documentation/config/gui.txt
index d30831a130..0c087fd8c9 100644
--- a/Documentation/config/gui.txt
+++ b/Documentation/config/gui.txt
@@ -11,7 +11,7 @@ gui.displayUntracked::
in the file list. The default is "true".
gui.encoding::
- Specifies the default encoding to use for displaying of
+ Specifies the default character encoding to use for displaying of
file contents in linkgit:git-gui[1] and linkgit:gitk[1].
It can be overridden by setting the 'encoding' attribute
for relevant files (see linkgit:gitattributes[5]).
diff --git a/Documentation/config/help.txt b/Documentation/config/help.txt
index 783a90a0f9..610701f9a3 100644
--- a/Documentation/config/help.txt
+++ b/Documentation/config/help.txt
@@ -9,13 +9,15 @@ help.format::
help.autoCorrect::
If git detects typos and can identify exactly one valid command similar
- to the error, git will automatically run the intended command after
- waiting a duration of time defined by this configuration value in
- deciseconds (0.1 sec). If this value is 0, the suggested corrections
- will be shown, but not executed. If it is a negative integer, or
- "immediate", the suggested command
- is run immediately. If "never", suggestions are not shown at all. The
- default value is zero.
+ to the error, git will try to suggest the correct command or even
+ run the suggestion automatically. Possible config values are:
+ - 0 (default): show the suggested command.
+ - positive number: run the suggested command after specified
+deciseconds (0.1 sec).
+ - "immediate": run the suggested command immediately.
+ - "prompt": show the suggestion and prompt for confirmation to run
+the command.
+ - "never": don't run or show any suggested command.
help.htmlPath::
Specify the path where the HTML documentation resides. File system paths
diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt
index 505126a780..b49429eb4d 100644
--- a/Documentation/config/transfer.txt
+++ b/Documentation/config/transfer.txt
@@ -52,13 +52,17 @@ If you have multiple hideRefs values, later entries override earlier ones
(and entries in more-specific config files override less-specific ones).
+
If a namespace is in use, the namespace prefix is stripped from each
-reference before it is matched against `transfer.hiderefs` patterns.
+reference before it is matched against `transfer.hiderefs` patterns. In
+order to match refs before stripping, add a `^` in front of the ref name. If
+you combine `!` and `^`, `!` must be specified first.
++
For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
-is omitted from the advertisements but `refs/heads/master` and
-`refs/namespaces/bar/refs/heads/master` are still advertised as so-called
-"have" lines. In order to match refs before stripping, add a `^` in front of
-the ref name. If you combine `!` and `^`, `!` must be specified first.
+is omitted from the advertisements. If `uploadpack.allowRefInWant` is set,
+`upload-pack` will treat `want-ref refs/heads/master` in a protocol v2
+`fetch` command as if `refs/namespaces/foo/refs/heads/master` did not exist.
+`receive-pack`, on the other hand, will still advertise the object id the
+ref is pointing to without mentioning its name (a so-called ".have" line).
+
Even if you hide refs, a client may still be able to steal the target
objects via the techniques described in the "SECURITY" section of the
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..5449767121 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -118,7 +118,8 @@ OPTIONS
Reset <branchname> to <startpoint>, even if <branchname> exists
already. Without `-f`, 'git branch' refuses to change an existing branch.
In combination with `-d` (or `--delete`), allow deleting the
- branch irrespective of its merged status. In combination with
+ branch irrespective of its merged status, or whether it even
+ points to a valid commit. In combination with
`-m` (or `--move`), allow renaming the branch even if the new
branch name already exists, the same applies for `-c` (or `--copy`).
diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt
index 66e88c2e31..d8817bf3ce 100644
--- a/Documentation/git-bugreport.txt
+++ b/Documentation/git-bugreport.txt
@@ -40,8 +40,8 @@ OPTIONS
-------
-o <path>::
--output-directory <path>::
- Place the resulting bug report file in `<path>` instead of the root of
- the Git repository.
+ Place the resulting bug report file in `<path>` instead of the current
+ directory.
-s <format>::
--suffix <format>::
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
index f58e9c43e6..6cea9ab463 100644
--- a/Documentation/git-column.txt
+++ b/Documentation/git-column.txt
@@ -39,7 +39,7 @@ OPTIONS
--indent=<string>::
String to be printed at the beginning of each line.
---nl=<N>::
+--nl=<string>::
String to be printed at the end of each line,
including newline character.
diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt
index 1e738ad398..e2cfb68ab5 100644
--- a/Documentation/git-maintenance.txt
+++ b/Documentation/git-maintenance.txt
@@ -179,6 +179,17 @@ OPTIONS
`maintenance.<task>.enabled` configured as `true` are considered.
See the 'TASKS' section for the list of accepted `<task>` values.
+--scheduler=auto|crontab|systemd-timer|launchctl|schtasks::
+ When combined with the `start` subcommand, specify the scheduler
+ for running the hourly, daily and weekly executions of
+ `git maintenance run`.
+ Possible values for `<scheduler>` are `auto`, `crontab`
+ (POSIX), `systemd-timer` (Linux), `launchctl` (macOS), and
+ `schtasks` (Windows). When `auto` is specified, the
+ appropriate platform-specific scheduler is used; on Linux,
+ `systemd-timer` is used if available, otherwise
+ `crontab`. Default is `auto`.
+
TROUBLESHOOTING
---------------
@@ -277,6 +288,52 @@ schedule to ensure you are executing the correct binaries in your
schedule.
+BACKGROUND MAINTENANCE ON LINUX SYSTEMD SYSTEMS
+-----------------------------------------------
+
+While Linux supports `cron`, depending on the distribution, `cron` may
+be an optional package not necessarily installed. On modern Linux
+distributions, systemd timers are superseding it.
+
+If user systemd timers are available, they will be used as a replacement
+of `cron`.
+
+In this case, `git maintenance start` will create user systemd timer units
+and start the timers. The current list of user-scheduled tasks can be found
+by running `systemctl --user list-timers`. The timers written by `git
+maintenance start` are similar to this:
+
+-----------------------------------------------------------------------
+$ systemctl --user list-timers
+NEXT LEFT LAST PASSED UNIT ACTIVATES
+Thu 2021-04-29 19:00:00 CEST 42min left Thu 2021-04-29 18:00:11 CEST 17min ago git-maintenance@hourly.timer git-maintenance@hourly.service
+Fri 2021-04-30 00:00:00 CEST 5h 42min left Thu 2021-04-29 00:00:11 CEST 18h ago git-maintenance@daily.timer git-maintenance@daily.service
+Mon 2021-05-03 00:00:00 CEST 3 days left Mon 2021-04-26 00:00:11 CEST 3 days ago git-maintenance@weekly.timer git-maintenance@weekly.service
+-----------------------------------------------------------------------
+
+One timer is registered for each `--schedule=<frequency>` option.
+
+The definition of the systemd units can be inspected in the following files:
+
+-----------------------------------------------------------------------
+~/.config/systemd/user/git-maintenance@.timer
+~/.config/systemd/user/git-maintenance@.service
+~/.config/systemd/user/timers.target.wants/git-maintenance@hourly.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@daily.timer
+~/.config/systemd/user/timers.target.wants/git-maintenance@weekly.timer
+-----------------------------------------------------------------------
+
+`git maintenance start` will overwrite these files and start the timer
+again with `systemctl --user`, so any customization should be done by
+creating a drop-in file, i.e. a `.conf` suffixed file in the
+`~/.config/systemd/user/git-maintenance@.service.d` directory.
+
+`git maintenance stop` will stop the user systemd timers and delete
+the above mentioned files.
+
+For more details, see `systemd.timer(5)`.
+
+
BACKGROUND MAINTENANCE ON MACOS SYSTEMS
---------------------------------------
diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt
index ffd601bc17..a9df3dbd32 100644
--- a/Documentation/git-multi-pack-index.txt
+++ b/Documentation/git-multi-pack-index.txt
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
'git multi-pack-index' [--object-dir=<dir>] [--[no-]progress]
- [--preferred-pack=<pack>] <subcommand>
+ [--preferred-pack=<pack>] [--[no-]bitmap] <subcommand>
DESCRIPTION
-----------
@@ -23,6 +23,8 @@ OPTIONS
Use given directory for the location of Git objects. We check
`<dir>/packs/multi-pack-index` for the current MIDX file, and
`<dir>/packs` for the pack-files to index.
++
+`<dir>` must be an alternate of the current repository.
--[no-]progress::
Turn progress on/off explicitly. If neither is specified, progress is
@@ -37,9 +39,12 @@ write::
--
--preferred-pack=<pack>::
Optionally specify the tie-breaking pack used when
- multiple packs contain the same object. If not given,
- ties are broken in favor of the pack with the lowest
- mtime.
+ multiple packs contain the same object. `<pack>` must
+ contain at least one object. If not given, ties are
+ broken in favor of the pack with the lowest mtime.
+
+ --[no-]bitmap::
+ Control whether or not a multi-pack bitmap is written.
--
verify::
@@ -81,6 +86,13 @@ EXAMPLES
$ git multi-pack-index write
-----------------------------------------------
+* Write a MIDX file for the packfiles in the current .git folder with a
+corresponding bitmap.
++
+-------------------------------------------------------------
+$ git multi-pack-index write --preferred-pack=<pack> --bitmap
+-------------------------------------------------------------
+
* Write a MIDX file for the packfiles in an alternate object store.
+
-----------------------------------------------
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 3f1030df70..506345cb0e 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -79,9 +79,10 @@ remain the checked-out branch.
If the upstream branch already contains a change you have made (e.g.,
because you mailed a patch which was applied upstream), then that commit
-will be skipped. For example, running `git rebase master` on the
-following history (in which `A'` and `A` introduce the same set of changes,
-but have different committer information):
+will be skipped and warnings will be issued (if the `merge` backend is
+used). For example, running `git rebase master` on the following
+history (in which `A'` and `A` introduce the same set of changes, but
+have different committer information):
------------
A---B---C topic
@@ -312,7 +313,10 @@ See also INCOMPATIBLE OPTIONS below.
By default (or if `--no-reapply-cherry-picks` is given), these commits
will be automatically dropped. Because this necessitates reading all
upstream commits, this can be expensive in repos with a large number
-of upstream commits that need to be read.
+of upstream commits that need to be read. When using the `merge`
+backend, warnings will be issued for each dropped commit (unless
+`--quiet` is given). Advice will also be issued unless
+`advice.skippedCherryPicks` is set to false (see linkgit:git-config[1]).
+
`--reapply-cherry-picks` allows rebase to forgo reading all upstream
commits, potentially improving performance.
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index 27ddaf84a1..b3af850608 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -33,14 +33,16 @@ people using 80-column terminals.
used together.
--encoding=<encoding>::
- The commit objects record the encoding used for the log message
+ Commit objects record the character encoding used for the log message
in their encoding header; this option can be used to tell the
command to re-code the commit log message in the encoding
preferred by the user. For non plumbing commands this
defaults to UTF-8. Note that if an object claims to be encoded
in `X` and we are outputting in `X`, we will output the object
verbatim; this means that invalid sequences in the original
- commit may be copied to the output.
+ commit may be copied to the output. Likewise, if iconv(3) fails
+ to convert the commit, we will output the original object
+ verbatim, along with a warning.
--expand-tabs=<n>::
--expand-tabs::
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 24569b06d1..b7bd27e171 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -968,6 +968,11 @@ list of the missing objects. Object IDs are prefixed with a ``?'' character.
objects.
endif::git-rev-list[]
+--unsorted-input::
+ Show commits in the order they were given on the command line instead
+ of sorting them in reverse chronological order by commit time. Cannot
+ be combined with `--no-walk` or `--no-walk=sorted`.
+
--no-walk[=(sorted|unsorted)]::
Only show the given commits, but do not traverse their ancestors.
This has no effect if a range is specified. If the argument
@@ -975,7 +980,8 @@ endif::git-rev-list[]
given on the command line. Otherwise (if `sorted` or no argument
was given), the commits are shown in reverse chronological order
by commit time.
- Cannot be combined with `--graph`.
+ Cannot be combined with `--graph`. Cannot be combined with
+ `--unsorted-input` if `sorted` or no argument was given.
--do-walk::
Overrides a previous `--no-walk`.
diff --git a/Documentation/technical/bitmap-format.txt b/Documentation/technical/bitmap-format.txt
index f8c18a0f7a..04b3ec2178 100644
--- a/Documentation/technical/bitmap-format.txt
+++ b/Documentation/technical/bitmap-format.txt
@@ -1,6 +1,44 @@
GIT bitmap v1 format
====================
+== Pack and multi-pack bitmaps
+
+Bitmaps store reachability information about the set of objects in a packfile,
+or a multi-pack index (MIDX). The former is defined obviously, and the latter is
+defined as the union of objects in packs contained in the MIDX.
+
+A bitmap may belong to either one pack, or the repository's multi-pack index (if
+it exists). A repository may have at most one bitmap.
+
+An object is uniquely described by its bit position within a bitmap:
+
+ - If the bitmap belongs to a packfile, the __n__th bit corresponds to
+ the __n__th object in pack order. For a function `offset` which maps
+ objects to their byte offset within a pack, pack order is defined as
+ follows:
+
+ o1 <= o2 <==> offset(o1) <= offset(o2)
+
+ - If the bitmap belongs to a MIDX, the __n__th bit corresponds to the
+ __n__th object in MIDX order. With an additional function `pack` which
+ maps objects to the pack they were selected from by the MIDX, MIDX order
+ is defined as follows:
+
+ o1 <= o2 <==> pack(o1) <= pack(o2) /\ offset(o1) <= offset(o2)
+
+ The ordering between packs is done according to the MIDX's .rev file.
+ Notably, the preferred pack sorts ahead of all other packs.
+
+The on-disk representation (described below) of a bitmap is the same regardless
+of whether or not that bitmap belongs to a packfile or a MIDX. The only
+difference is the interpretation of the bits, which is described above.
+
+Certain bitmap extensions are supported (see: Appendix B). No extensions are
+required for bitmaps corresponding to packfiles. For bitmaps that correspond to
+MIDXs, both the bit-cache and rev-cache extensions are required.
+
+== On-disk format
+
- A header appears at the beginning:
4-byte signature: {'B', 'I', 'T', 'M'}
@@ -14,17 +52,19 @@ GIT bitmap v1 format
The following flags are supported:
- BITMAP_OPT_FULL_DAG (0x1) REQUIRED
- This flag must always be present. It implies that the bitmap
- index has been generated for a packfile with full closure
- (i.e. where every single object in the packfile can find
- its parent links inside the same packfile). This is a
- requirement for the bitmap index format, also present in JGit,
- that greatly reduces the complexity of the implementation.
+ This flag must always be present. It implies that the
+ bitmap index has been generated for a packfile or
+ multi-pack index (MIDX) with full closure (i.e. where
+ every single object in the packfile/MIDX can find its
+ parent links inside the same packfile/MIDX). This is a
+ requirement for the bitmap index format, also present in
+ JGit, that greatly reduces the complexity of the
+ implementation.
- BITMAP_OPT_HASH_CACHE (0x4)
If present, the end of the bitmap file contains
`N` 32-bit name-hash values, one per object in the
- pack. The format and meaning of the name-hash is
+ pack/MIDX. The format and meaning of the name-hash is
described below.
4-byte entry count (network byte order)
@@ -33,7 +73,8 @@ GIT bitmap v1 format
20-byte checksum
- The SHA1 checksum of the pack this bitmap index belongs to.
+ The SHA1 checksum of the pack/MIDX this bitmap index
+ belongs to.
- 4 EWAH bitmaps that act as type indexes
@@ -50,7 +91,7 @@ GIT bitmap v1 format
- Tags
In each bitmap, the `n`th bit is set to true if the `n`th object
- in the packfile is of that type.
+ in the packfile or multi-pack index is of that type.
The obvious consequence is that the OR of all 4 bitmaps will result
in a full set (all bits set), and the AND of all 4 bitmaps will
@@ -62,8 +103,9 @@ GIT bitmap v1 format
Each entry contains the following:
- 4-byte object position (network byte order)
- The position **in the index for the packfile** where the
- bitmap for this commit is found.
+ The position **in the index for the packfile or
+ multi-pack index** where the bitmap for this commit is
+ found.
- 1-byte XOR-offset
The xor offset used to compress this bitmap. For an entry
@@ -146,10 +188,11 @@ Name-hash cache
---------------
If the BITMAP_OPT_HASH_CACHE flag is set, the end of the bitmap contains
-a cache of 32-bit values, one per object in the pack. The value at
+a cache of 32-bit values, one per object in the pack/MIDX. The value at
position `i` is the hash of the pathname at which the `i`th object
-(counting in index order) in the pack can be found. This can be fed
-into the delta heuristics to compare objects with similar pathnames.
+(counting in index or multi-pack index order) in the pack/MIDX can be found.
+This can be fed into the delta heuristics to compare objects with similar
+pathnames.
The hash algorithm used is:
diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt
index fb688976c4..1a73c3ee20 100644
--- a/Documentation/technical/multi-pack-index.txt
+++ b/Documentation/technical/multi-pack-index.txt
@@ -71,14 +71,10 @@ Future Work
still reducing the number of binary searches required for object
lookups.
-- The reachability bitmap is currently paired directly with a single
- packfile, using the pack-order as the object order to hopefully
- compress the bitmaps well using run-length encoding. This could be
- extended to pair a reachability bitmap with a multi-pack-index. If
- the multi-pack-index is extended to store a "stable object order"
+- If the multi-pack-index is extended to store a "stable object order"
(a function Order(hash) = integer that is constant for a given hash,
- even as the multi-pack-index is updated) then a reachability bitmap
- could point to a multi-pack-index and be updated independently.
+ even as the multi-pack-index is updated) then MIDX bitmaps could be
+ updated independently of the MIDX.
- Packfiles can be marked as "special" using empty files that share
the initial name but replace ".pack" with ".keep" or ".promisor".
diff --git a/Makefile b/Makefile
index 5abab599b4..65e8097b8d 100644
--- a/Makefile
+++ b/Makefile
@@ -456,6 +456,9 @@ all::
# the global variable _wpgmptr containing the absolute path of the current
# executable (this is the case on Windows).
#
+# INSTALL_STRIP can be set to "-s" to strip binaries during installation,
+# if your $(INSTALL) command supports the option.
+#
# Define GENERATE_COMPILATION_DATABASE to "yes" to generate JSON compilation
# database entries during compilation if your compiler supports it, using the
# `-MJ` flag. The JSON entries will be placed in the `compile_commands/`
@@ -2589,10 +2592,10 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
$(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS)
$(LIB_FILE): $(LIB_OBJS)
- $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
$(XDIFF_LIB): $(XDIFF_OBJS)
- $(QUIET_AR)$(AR) $(ARFLAGS) $@ $^
+ $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
export DEFAULT_EDITOR DEFAULT_PAGER
@@ -2986,7 +2989,8 @@ mergetools_instdir = $(prefix)/$(mergetoolsdir)
endif
mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir))
-install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X)
+install_bindir_xprograms := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X))
+install_bindir_programs := $(install_bindir_xprograms) $(BINDIR_PROGRAMS_NO_X)
.PHONY: profile-install profile-fast-install
profile-install: profile
@@ -2995,12 +2999,17 @@ profile-install: profile
profile-fast-install: profile-fast
$(MAKE) install
+INSTALL_STRIP =
+
install: all
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(INSTALL_STRIP) $(PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
+ $(INSTALL) $(SCRIPTS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
- $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(INSTALL_STRIP) $(install_bindir_xprograms) '$(DESTDIR_SQ)$(bindir_SQ)'
+ $(INSTALL) $(BINDIR_PROGRAMS_NO_X) '$(DESTDIR_SQ)$(bindir_SQ)'
+
ifdef MSVC
# We DO NOT install the individual foo.o.pdb files because they
# have already been rolled up into the exe's pdb file.
@@ -3081,8 +3090,7 @@ endif
ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
- done && \
- ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
+ done
.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf
.PHONY: quick-install-doc quick-install-man quick-install-html
diff --git a/advice.c b/advice.c
index 337e8f342b..2f5499a5e1 100644
--- a/advice.c
+++ b/advice.c
@@ -4,37 +4,6 @@
#include "help.h"
#include "string-list.h"
-int advice_fetch_show_forced_updates = 1;
-int advice_push_update_rejected = 1;
-int advice_push_non_ff_current = 1;
-int advice_push_non_ff_matching = 1;
-int advice_push_already_exists = 1;
-int advice_push_fetch_first = 1;
-int advice_push_needs_force = 1;
-int advice_push_unqualified_ref_name = 1;
-int advice_push_ref_needs_update = 1;
-int advice_status_hints = 1;
-int advice_status_u_option = 1;
-int advice_status_ahead_behind_warning = 1;
-int advice_commit_before_merge = 1;
-int advice_reset_quiet_warning = 1;
-int advice_resolve_conflict = 1;
-int advice_sequencer_in_use = 1;
-int advice_implicit_identity = 1;
-int advice_detached_head = 1;
-int advice_set_upstream_failure = 1;
-int advice_object_name_warning = 1;
-int advice_amworkdir = 1;
-int advice_rm_hints = 1;
-int advice_add_embedded_repo = 1;
-int advice_ignored_hook = 1;
-int advice_waiting_for_editor = 1;
-int advice_graft_file_deprecated = 1;
-int advice_checkout_ambiguous_remote_branch_name = 1;
-int advice_submodule_alternate_error_strategy_die = 1;
-int advice_add_ignored_file = 1;
-int advice_add_empty_pathspec = 1;
-
static int advice_use_color = -1;
static char advice_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
@@ -63,49 +32,12 @@ static const char *advise_get_color(enum color_advice ix)
}
static struct {
- const char *name;
- int *preference;
-} advice_config[] = {
- { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates },
- { "pushUpdateRejected", &advice_push_update_rejected },
- { "pushNonFFCurrent", &advice_push_non_ff_current },
- { "pushNonFFMatching", &advice_push_non_ff_matching },
- { "pushAlreadyExists", &advice_push_already_exists },
- { "pushFetchFirst", &advice_push_fetch_first },
- { "pushNeedsForce", &advice_push_needs_force },
- { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
- { "pushRefNeedsUpdate", &advice_push_ref_needs_update },
- { "statusHints", &advice_status_hints },
- { "statusUoption", &advice_status_u_option },
- { "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
- { "commitBeforeMerge", &advice_commit_before_merge },
- { "resetQuiet", &advice_reset_quiet_warning },
- { "resolveConflict", &advice_resolve_conflict },
- { "sequencerInUse", &advice_sequencer_in_use },
- { "implicitIdentity", &advice_implicit_identity },
- { "detachedHead", &advice_detached_head },
- { "setUpstreamFailure", &advice_set_upstream_failure },
- { "objectNameWarning", &advice_object_name_warning },
- { "amWorkDir", &advice_amworkdir },
- { "rmHints", &advice_rm_hints },
- { "addEmbeddedRepo", &advice_add_embedded_repo },
- { "ignoredHook", &advice_ignored_hook },
- { "waitingForEditor", &advice_waiting_for_editor },
- { "graftFileDeprecated", &advice_graft_file_deprecated },
- { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name },
- { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die },
- { "addIgnoredFile", &advice_add_ignored_file },
- { "addEmptyPathspec", &advice_add_empty_pathspec },
-
- /* make this an alias for backward compatibility */
- { "pushNonFastForward", &advice_push_update_rejected }
-};
-
-static struct {
const char *key;
int enabled;
} advice_setting[] = {
[ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 1 },
+ [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 },
+ [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 },
[ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 },
[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
@@ -133,6 +65,7 @@ static struct {
[ADVICE_RM_HINTS] = { "rmHints", 1 },
[ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 },
[ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 },
+ [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks", 1 },
[ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 },
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
@@ -221,13 +154,6 @@ int git_default_advice_config(const char *var, const char *value)
if (!skip_prefix(var, "advice.", &k))
return 0;
- for (i = 0; i < ARRAY_SIZE(advice_config); i++) {
- if (strcasecmp(k, advice_config[i].name))
- continue;
- *advice_config[i].preference = git_config_bool(var, value);
- break;
- }
-
for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
if (strcasecmp(k, advice_setting[i].key))
continue;
@@ -262,7 +188,7 @@ int error_resolve_conflict(const char *me)
error(_("It is not possible to %s because you have unmerged files."),
me);
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
/*
* Message used both when 'git commit' fails and when
* other commands doing a merge do.
@@ -281,7 +207,7 @@ void NORETURN die_resolve_conflict(const char *me)
void NORETURN die_conclude_merge(void)
{
error(_("You have not concluded your merge (MERGE_HEAD exists)."));
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
advise(_("Please, commit your changes before merging."));
die(_("Exiting because of unfinished merge."));
}
diff --git a/advice.h b/advice.h
index a7227ca703..601265fd10 100644
--- a/advice.h
+++ b/advice.h
@@ -5,37 +5,6 @@
struct string_list;
-extern int advice_fetch_show_forced_updates;
-extern int advice_push_update_rejected;
-extern int advice_push_non_ff_current;
-extern int advice_push_non_ff_matching;
-extern int advice_push_already_exists;
-extern int advice_push_fetch_first;
-extern int advice_push_needs_force;
-extern int advice_push_unqualified_ref_name;
-extern int advice_push_ref_needs_update;
-extern int advice_status_hints;
-extern int advice_status_u_option;
-extern int advice_status_ahead_behind_warning;
-extern int advice_commit_before_merge;
-extern int advice_reset_quiet_warning;
-extern int advice_resolve_conflict;
-extern int advice_sequencer_in_use;
-extern int advice_implicit_identity;
-extern int advice_detached_head;
-extern int advice_set_upstream_failure;
-extern int advice_object_name_warning;
-extern int advice_amworkdir;
-extern int advice_rm_hints;
-extern int advice_add_embedded_repo;
-extern int advice_ignored_hook;
-extern int advice_waiting_for_editor;
-extern int advice_graft_file_deprecated;
-extern int advice_checkout_ambiguous_remote_branch_name;
-extern int advice_submodule_alternate_error_strategy_die;
-extern int advice_add_ignored_file;
-extern int advice_add_empty_pathspec;
-
/*
* To add a new advice, you need to:
* Define a new advice_type.
@@ -45,6 +14,8 @@ extern int advice_add_empty_pathspec;
*/
enum advice_type {
ADVICE_ADD_EMBEDDED_REPO,
+ ADVICE_ADD_EMPTY_PATHSPEC,
+ ADVICE_ADD_IGNORED_FILE,
ADVICE_AM_WORK_DIR,
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
ADVICE_COMMIT_BEFORE_MERGE,
@@ -75,6 +46,7 @@ extern int advice_add_empty_pathspec;
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
+ ADVICE_SKIPPED_CHERRY_PICKS,
};
int git_default_advice_config(const char *var, const char *value);
diff --git a/apply.c b/apply.c
index 4ed4b27169..43a0aebf4e 100644
--- a/apply.c
+++ b/apply.c
@@ -3468,6 +3468,21 @@ static int load_preimage(struct apply_state *state,
return 0;
}
+static int resolve_to(struct image *image, const struct object_id *result_id)
+{
+ unsigned long size;
+ enum object_type type;
+
+ clear_image(image);
+
+ image->buf = read_object_file(result_id, &type, &size);
+ if (!image->buf || type != OBJ_BLOB)
+ die("unable to read blob object %s", oid_to_hex(result_id));
+ image->len = size;
+
+ return 0;
+}
+
static int three_way_merge(struct apply_state *state,
struct image *image,
char *path,
@@ -3479,6 +3494,12 @@ static int three_way_merge(struct apply_state *state,
mmbuffer_t result = { NULL };
int status;
+ /* resolve trivial cases first */
+ if (oideq(base, ours))
+ return resolve_to(image, theirs);
+ else if (oideq(base, theirs) || oideq(ours, theirs))
+ return resolve_to(image, ours);
+
read_mmblob(&base_file, base);
read_mmblob(&our_file, ours);
read_mmblob(&their_file, theirs);
diff --git a/archive.c b/archive.c
index 3c266d1d7b..a3bbb09125 100644
--- a/archive.c
+++ b/archive.c
@@ -191,7 +191,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
return err;
}
-static void queue_directory(const unsigned char *sha1,
+static void queue_directory(const struct object_id *oid,
struct strbuf *base, const char *filename,
unsigned mode, struct archiver_context *c)
{
@@ -203,7 +203,7 @@ static void queue_directory(const unsigned char *sha1,
d->mode = mode;
c->bottom = d;
d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
- oidread(&d->oid, sha1);
+ oidcpy(&d->oid, oid);
}
static int write_directory(struct archiver_context *c)
@@ -250,8 +250,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
if (check_attr_export_ignore(check))
return 0;
- queue_directory(oid->hash, base, filename,
- mode, c);
+ queue_directory(oid, base, filename, mode, c);
return READ_TREE_RECURSIVE;
}
diff --git a/branch.c b/branch.c
index 7a88a4861e..07a46430b3 100644
--- a/branch.c
+++ b/branch.c
@@ -271,7 +271,7 @@ void create_branch(struct repository *r,
real_ref = NULL;
if (get_oid_mb(start_name, &oid)) {
if (explicit_tracking) {
- if (advice_set_upstream_failure) {
+ if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
error(_(upstream_missing), start_name);
advise(_(upstream_advice));
exit(1);
diff --git a/builtin/add.c b/builtin/add.c
index 17528e8f92..2244311d48 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -319,9 +319,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
rev.diffopt.use_color = 0;
rev.diffopt.flags.ignore_dirty_submodules = 1;
- out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (out < 0)
- die(_("Could not open '%s' for writing."), file);
+ out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
@@ -425,6 +423,7 @@ static const char embedded_advice[] = N_(
static void check_embedded_repo(const char *path)
{
struct strbuf name = STRBUF_INIT;
+ static int adviced_on_embedded_repo = 0;
if (!warn_on_embedded_repo)
return;
@@ -436,10 +435,10 @@ static void check_embedded_repo(const char *path)
strbuf_strip_suffix(&name, "/");
warning(_("adding embedded git repository: %s"), name.buf);
- if (advice_add_embedded_repo) {
+ if (!adviced_on_embedded_repo &&
+ advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) {
advise(embedded_advice, name.buf, name.buf);
- /* there may be multiple entries; advise only once */
- advice_add_embedded_repo = 0;
+ adviced_on_embedded_repo = 1;
}
strbuf_release(&name);
@@ -453,7 +452,7 @@ static int add_files(struct dir_struct *dir, int flags)
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- if (advice_add_ignored_file)
+ if (advice_enabled(ADVICE_ADD_IGNORED_FILE))
advise(_("Use -f if you really want to add them.\n"
"Turn this message off by running\n"
"\"git config advice.addIgnoredFile false\""));
@@ -562,7 +561,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (require_pathspec && pathspec.nr == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
- if (advice_add_empty_pathspec)
+ if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC))
advise( _("Maybe you wanted to say 'git add .'?\n"
"Turn this message off by running\n"
"\"git config advice.addEmptyPathspec false\""));
diff --git a/builtin/am.c b/builtin/am.c
index 0c2ad96b70..ff7dd33fcd 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1820,7 +1820,7 @@ static void am_run(struct am_state *state, int resume)
printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
linelen(state->msg), state->msg);
- if (advice_amworkdir)
+ if (advice_enabled(ADVICE_AM_WORK_DIR))
advise(_("Use 'git am --show-current-patch=diff' to see the failed patch"));
die_user_resolve(state);
diff --git a/builtin/archive.c b/builtin/archive.c
index 45d11669aa..7176b041b6 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -12,9 +12,7 @@
static void create_output_file(const char *output_file)
{
- int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
- if (output_fd < 0)
- die_errno(_("could not create archive file '%s'"), output_file);
+ int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (output_fd != 1) {
if (dup2(output_fd, 1) < 0)
die_errno(_("could not redirect output"));
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..03c7b7253a 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -168,7 +168,7 @@ static int check_branch_commit(const char *branchname, const char *refname,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(the_repository, oid);
- if (!rev) {
+ if (!force && !rev) {
error(_("Couldn't look up commit object for '%s'"), refname);
return -1;
}
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
index 9915a5841d..06ed10dc92 100644
--- a/builtin/bugreport.c
+++ b/builtin/bugreport.c
@@ -171,10 +171,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix)
get_populated_hooks(&buffer, !startup_info->have_repository);
/* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
- report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
-
- if (report < 0)
- die(_("couldn't create a new file at '%s'"), report_path.buf);
+ report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (write_in_full(report, buffer.buf, buffer.len) < 0)
die_errno(_("unable to write to %s"), report_path.buf);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..8c69dcdf72 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -404,7 +404,7 @@ static int checkout_worktree(const struct checkout_opts *opts,
mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
remove_marked_cache_entries(&the_index, 1);
remove_scheduled_dirs();
- errs |= finish_delayed_checkout(&state, &nr_checkouts);
+ errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress);
if (opts->count_checkout_paths) {
if (nr_unmerged)
@@ -918,7 +918,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old_branch_info->path &&
- advice_detached_head && !opts->force_detach)
+ advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
detach_advice(new_branch_info->name);
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
@@ -1011,7 +1011,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
sb.buf);
strbuf_release(&sb);
- if (advice_detached_head)
+ if (advice_enabled(ADVICE_DETACHED_HEAD))
fprintf(stderr,
Q_(
/* The singular version */
@@ -1182,7 +1182,7 @@ static const char *parse_remote_branch(const char *arg,
}
if (!remote && num_matches > 1) {
- if (advice_checkout_ambiguous_remote_branch_name) {
+ if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) {
advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
"you can do so by fully qualifying the name with the --track option:\n"
"\n"
diff --git a/builtin/clone.c b/builtin/clone.c
index 66fe66679c..143b4e0a3e 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -657,7 +657,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
}
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
@@ -668,13 +668,11 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
*/
while (ref && !ref->peer_ref)
ref = ref->next;
- /* Returning -1 notes "end of list" to the caller. */
if (!ref)
- return -1;
+ return NULL;
- oidcpy(oid, &ref->old_oid);
*rm = ref->next;
- return 0;
+ return &ref->old_oid;
}
static void update_remote_refs(const struct ref *refs,
@@ -786,7 +784,7 @@ static int checkout(int submodule_progress)
return 0;
}
if (!strcmp(head, "HEAD")) {
- if (advice_detached_head)
+ if (advice_enabled(ADVICE_DETACHED_HEAD))
detach_advice(oid_to_hex(&oid));
FREE_AND_NULL(head);
} else {
@@ -1114,6 +1112,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_recurse_submodules.nr > 0) {
struct string_list_item *item;
struct strbuf sb = STRBUF_INIT;
+ int val;
/* remove duplicates */
string_list_sort(&option_recurse_submodules);
@@ -1130,6 +1129,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
strbuf_detach(&sb, NULL));
}
+ if (!git_config_get_bool("submodule.stickyRecursiveClone", &val) &&
+ val)
+ string_list_append(&option_config, "submodule.recurse=true");
+
if (option_required_reference.nr &&
option_optional_reference.nr)
die(_("clone --recursive is not compatible with "
diff --git a/builtin/column.c b/builtin/column.c
index 40d4b3bee2..158fdf53d9 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -29,7 +29,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "width", &copts.width, N_("maximum width")),
OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")),
- OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")),
+ OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")),
OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
OPT_END()
};
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index cd86315221..0386f5c775 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -9,26 +9,29 @@
#include "progress.h"
#include "tag.h"
-static char const * const builtin_commit_graph_usage[] = {
- N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
- N_("git commit-graph write [--object-dir <objdir>] [--append] "
- "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
- "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
- "<split options>"),
+#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \
+ N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]")
+
+#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \
+ N_("git commit-graph write [--object-dir <objdir>] [--append] " \
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " \
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " \
+ "<split options>")
+
+static const char * builtin_commit_graph_verify_usage[] = {
+ BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
NULL
};
-static const char * const builtin_commit_graph_verify_usage[] = {
- N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
+static const char * builtin_commit_graph_write_usage[] = {
+ BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
NULL
};
-static const char * const builtin_commit_graph_write_usage[] = {
- N_("git commit-graph write [--object-dir <objdir>] [--append] "
- "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
- "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
- "<split options>"),
- NULL
+static char const * const builtin_commit_graph_usage[] = {
+ BUILTIN_COMMIT_GRAPH_VERIFY_USAGE,
+ BUILTIN_COMMIT_GRAPH_WRITE_USAGE,
+ NULL,
};
static struct opts_commit_graph {
@@ -43,26 +46,18 @@ static struct opts_commit_graph {
int enable_changed_paths;
} opts;
-static struct object_directory *find_odb(struct repository *r,
- const char *obj_dir)
-{
- struct object_directory *odb;
- char *obj_dir_real = real_pathdup(obj_dir, 1);
- struct strbuf odb_path_real = STRBUF_INIT;
-
- prepare_alt_odb(r);
- for (odb = r->objects->odb; odb; odb = odb->next) {
- strbuf_realpath(&odb_path_real, odb->path, 1);
- if (!strcmp(obj_dir_real, odb_path_real.buf))
- break;
- }
-
- free(obj_dir_real);
- strbuf_release(&odb_path_real);
+static struct option common_opts[] = {
+ OPT_STRING(0, "object-dir", &opts.obj_dir,
+ N_("dir"),
+ N_("the object directory to store the graph")),
+ OPT_BOOL(0, "progress", &opts.progress,
+ N_("force progress reporting")),
+ OPT_END()
+};
- if (!odb)
- die(_("could not find object directory matching %s"), obj_dir);
- return odb;
+static struct option *add_common_options(struct option *to)
+{
+ return parse_options_concat(common_opts, to);
}
static int graph_verify(int argc, const char **argv)
@@ -76,21 +71,20 @@ static int graph_verify(int argc, const char **argv)
int flags = 0;
static struct option builtin_commit_graph_verify_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
OPT_BOOL(0, "shallow", &opts.shallow,
N_("if the commit-graph is split, only verify the tip file")),
- OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
OPT_END(),
};
+ struct option *options = add_common_options(builtin_commit_graph_verify_options);
trace2_cmd_mode("verify");
opts.progress = isatty(2);
argc = parse_options(argc, argv, NULL,
- builtin_commit_graph_verify_options,
+ options,
builtin_commit_graph_verify_usage, 0);
+ if (argc)
+ usage_with_options(builtin_commit_graph_verify_usage, options);
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
@@ -106,6 +100,7 @@ static int graph_verify(int argc, const char **argv)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
FREE_AND_NULL(graph_name);
+ FREE_AND_NULL(options);
if (open_ok)
graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
@@ -206,9 +201,6 @@ static int graph_write(int argc, const char **argv)
struct progress *progress = NULL;
static struct option builtin_commit_graph_write_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
OPT_BOOL(0, "reachable", &opts.reachable,
N_("start walk at all refs")),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@@ -219,7 +211,6 @@ static int graph_write(int argc, const char **argv)
N_("include all commits already in the commit-graph file")),
OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
N_("enable computation for changed paths")),
- OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
N_("allow writing an incremental commit-graph file"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
@@ -235,6 +226,7 @@ static int graph_write(int argc, const char **argv)
0, write_option_max_new_filters),
OPT_END(),
};
+ struct option *options = add_common_options(builtin_commit_graph_write_options);
opts.progress = isatty(2);
opts.enable_changed_paths = -1;
@@ -248,8 +240,10 @@ static int graph_write(int argc, const char **argv)
git_config(git_commit_graph_write_config, &opts);
argc = parse_options(argc, argv, NULL,
- builtin_commit_graph_write_options,
+ options,
builtin_commit_graph_write_usage, 0);
+ if (argc)
+ usage_with_options(builtin_commit_graph_write_usage, options);
if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1)
die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs"));
@@ -304,6 +298,7 @@ static int graph_write(int argc, const char **argv)
result = 1;
cleanup:
+ FREE_AND_NULL(options);
string_list_clear(&pack_indexes, 0);
strbuf_release(&buf);
return result;
@@ -311,32 +306,25 @@ cleanup:
int cmd_commit_graph(int argc, const char **argv, const char *prefix)
{
- static struct option builtin_commit_graph_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("the object directory to store the graph")),
- OPT_END(),
- };
-
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(builtin_commit_graph_usage,
- builtin_commit_graph_options);
+ struct option *builtin_commit_graph_options = common_opts;
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_commit_graph_options,
builtin_commit_graph_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ goto usage;
save_commit_buffer = 0;
- if (argc > 0) {
- if (!strcmp(argv[0], "verify"))
- return graph_verify(argc, argv);
- if (!strcmp(argv[0], "write"))
- return graph_write(argc, argv);
- }
+ if (!strcmp(argv[0], "verify"))
+ return graph_verify(argc, argv);
+ else if (argc && !strcmp(argv[0], "write"))
+ return graph_write(argc, argv);
+ error(_("unrecognized subcommand: %s"), argv[0]);
+usage:
usage_with_options(builtin_commit_graph_usage,
builtin_commit_graph_options);
}
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 1031b9a491..63ea322933 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -88,9 +88,7 @@ static int parse_file_arg_callback(const struct option *opt,
if (!strcmp(arg, "-"))
fd = 0;
else {
- fd = open(arg, O_RDONLY);
- if (fd < 0)
- die_errno(_("git commit-tree: failed to open '%s'"), arg);
+ fd = xopen(arg, O_RDONLY);
}
if (strbuf_read(buf, fd, 0) < 0)
die_errno(_("git commit-tree: failed to read '%s'"), arg);
diff --git a/builtin/commit.c b/builtin/commit.c
index 243c626307..e7320f66f9 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -203,7 +203,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn)
init_diff_ui_defaults();
git_config(fn, s);
determine_whence(s);
- s->hints = advice_status_hints; /* must come after git_config() */
+ s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */
}
static void rollback_index_files(void)
@@ -1033,7 +1033,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
- s->hints = advice_status_hints;
+ s->hints = advice_enabled(ADVICE_STATUS_HINTS);
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
@@ -1253,8 +1253,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (logfile || have_option_m || use_message)
use_editor = 0;
- if (0 <= edit_flag)
- use_editor = edit_flag;
/* Sanity check options */
if (amend && !current_head)
@@ -1344,6 +1342,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
}
}
+ if (0 <= edit_flag)
+ use_editor = edit_flag;
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index cf09559e42..5fd23ab5b6 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -29,10 +29,10 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
prefix = precompose_argv_prefix(argc, argv, prefix);
/*
- * We need no diff for merges options, and we need to avoid conflict
- * with our own meaning of "-m".
+ * We need (some of) diff for merges options (e.g., --cc), and we need
+ * to avoid conflict with our own meaning of "-m".
*/
- diff_merges_suppress_options_parsing();
+ diff_merges_suppress_m_parsing();
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 3c20f164f0..95e8e89e81 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -821,6 +821,7 @@ static void handle_tag(const char *name, struct tag *tag)
static struct hashmap tags;
message = anonymize_str(&tags, anonymize_tag,
message, message_size, NULL);
+ message_size = strlen(message);
}
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index e064687dbd..75848d851d 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -712,7 +712,7 @@ static void adjust_refcol_width(const struct ref *ref)
int max, rlen, llen, len;
/* uptodate lines are only shown on high verbosity level */
- if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
+ if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid))
return;
max = term_columns();
@@ -748,6 +748,9 @@ static void prepare_format_display(struct ref *ref_map)
struct ref *rm;
const char *format = "full";
+ if (verbosity < 0)
+ return;
+
git_config_get_string_tmp("fetch.output", &format);
if (!strcasecmp(format, "full"))
compact_format = 0;
@@ -827,7 +830,12 @@ static void format_display(struct strbuf *display, char code,
const char *remote, const char *local,
int summary_width)
{
- int width = (summary_width + strlen(summary) - gettext_width(summary));
+ int width;
+
+ if (verbosity < 0)
+ return;
+
+ width = (summary_width + strlen(summary) - gettext_width(summary));
strbuf_addf(display, "%c %-*s ", code, width, summary);
if (!compact_format)
@@ -846,13 +854,11 @@ static int update_local_ref(struct ref *ref,
int summary_width)
{
struct commit *current = NULL, *updated;
- enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
int fast_forward = 0;
- type = oid_object_info(the_repository, &ref->new_oid, NULL);
- if (type < 0)
+ if (!repo_has_object_file(the_repository, &ref->new_oid))
die(_("object %s not found"), oid_to_hex(&ref->new_oid));
if (oideq(&ref->old_oid, &ref->new_oid)) {
@@ -964,7 +970,7 @@ static int update_local_ref(struct ref *ref,
}
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
@@ -972,10 +978,9 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
ref = ref->next;
if (!ref)
- return -1; /* end of the list */
+ return NULL;
*rm = ref->next;
- oidcpy(oid, &ref->old_oid);
- return 0;
+ return &ref->old_oid;
}
struct fetch_head {
@@ -1074,7 +1079,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map)
{
struct fetch_head fetch_head;
- struct commit *commit;
int url_len, i, rc = 0;
struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
struct ref_transaction *transaction = NULL;
@@ -1122,6 +1126,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
want_status <= FETCH_HEAD_IGNORE;
want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
+ struct commit *commit = NULL;
struct ref *ref = NULL;
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
@@ -1131,11 +1136,23 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
continue;
}
- commit = lookup_commit_reference_gently(the_repository,
- &rm->old_oid,
- 1);
- if (!commit)
- rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ /*
+ * References in "refs/tags/" are often going to point
+ * to annotated tags, which are not part of the
+ * commit-graph. We thus only try to look up refs in
+ * the graph which are not in that namespace to not
+ * regress performance in repositories with many
+ * annotated tags.
+ */
+ if (!starts_with(rm->name, "refs/tags/"))
+ commit = lookup_commit_in_graph(the_repository, &rm->old_oid);
+ if (!commit) {
+ commit = lookup_commit_reference_gently(the_repository,
+ &rm->old_oid,
+ 1);
+ if (!commit)
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
+ }
if (rm->fetch_head_status != want_status)
continue;
@@ -1202,13 +1219,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
"FETCH_HEAD", summary_width);
}
if (note.len) {
- if (verbosity >= 0 && !shown_url) {
+ if (!shown_url) {
fprintf(stderr, _("From %.*s\n"),
url_len, url);
shown_url = 1;
}
- if (verbosity >= 0)
- fprintf(stderr, " %s\n", note.buf);
+ fprintf(stderr, " %s\n", note.buf);
}
}
}
@@ -1229,7 +1245,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
- if (advice_fetch_show_forced_updates) {
+ if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) {
if (!fetch_show_forced_updates) {
warning(_(warn_show_forced_updates));
} else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
@@ -1282,37 +1298,35 @@ static int check_exist_and_connected(struct ref *ref_map)
return check_connected(iterate_ref_map, &rm, &opt);
}
-static int fetch_refs(struct transport *transport, struct ref *ref_map)
+static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map)
{
- int ret = check_exist_and_connected(ref_map);
+ int connectivity_checked = 1;
+ int ret;
+
+ /*
+ * We don't need to perform a fetch in case we can already satisfy all
+ * refs.
+ */
+ ret = check_exist_and_connected(ref_map);
if (ret) {
trace2_region_enter("fetch", "fetch_refs", the_repository);
ret = transport_fetch_refs(transport, ref_map);
trace2_region_leave("fetch", "fetch_refs", the_repository);
+ if (ret)
+ goto out;
+ connectivity_checked = transport->smart_options ?
+ transport->smart_options->connectivity_checked : 0;
}
- if (!ret)
- /*
- * Keep the new pack's ".keep" file around to allow the caller
- * time to update refs to reference the new objects.
- */
- return 0;
- transport_unlock_pack(transport);
- return ret;
-}
-/* Update local refs based on the ref values fetched from a remote */
-static int consume_refs(struct transport *transport, struct ref *ref_map)
-{
- int connectivity_checked = transport->smart_options
- ? transport->smart_options->connectivity_checked : 0;
- int ret;
trace2_region_enter("fetch", "consume_refs", the_repository);
ret = store_updated_refs(transport->url,
transport->remote->name,
connectivity_checked,
ref_map);
- transport_unlock_pack(transport);
trace2_region_leave("fetch", "consume_refs", the_repository);
+
+out:
+ transport_unlock_pack(transport);
return ret;
}
@@ -1501,8 +1515,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
- if (!fetch_refs(transport, ref_map))
- consume_refs(transport, ref_map);
+ fetch_and_consume_refs(transport, ref_map);
if (gsecondary) {
transport_disconnect(gsecondary);
@@ -1593,7 +1606,7 @@ static int do_fetch(struct transport *transport,
transport->url);
}
}
- if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) {
+ if (fetch_and_consume_refs(transport, ref_map)) {
free_refs(ref_map);
retcode = 1;
goto cleanup;
diff --git a/builtin/gc.c b/builtin/gc.c
index 6ce5ca4512..84d77104c2 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -502,7 +502,7 @@ static int report_last_gc_error(void)
*/
warning(_("The last gc run reported the following. "
"Please correct the root cause\n"
- "and remove %s.\n"
+ "and remove %s\n"
"Automatic cleanup will not be performed "
"until the file is removed.\n\n"
"%s"),
@@ -1529,6 +1529,93 @@ static const char *get_frequency(enum schedule_priority schedule)
}
}
+/*
+ * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable
+ * to mock the schedulers that `git maintenance start` rely on.
+ *
+ * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated
+ * list of colon-separated key/value pairs where each pair contains a scheduler
+ * and its corresponding mock.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the
+ * arguments unmodified.
+ *
+ * * If $GIT_TEST_MAINT_SCHEDULER is set, return true.
+ * In this case, the *cmd value is read as input.
+ *
+ * * if the input value *cmd is the key of one of the comma-separated list
+ * item, then *is_available is set to true and *cmd is modified and becomes
+ * the mock command.
+ *
+ * * if the input value *cmd isn’t the key of any of the comma-separated list
+ * item, then *is_available is set to false.
+ *
+ * Ex.:
+ * GIT_TEST_MAINT_SCHEDULER not set
+ * +-------+-------------------------------------------------+
+ * | Input | Output |
+ * | *cmd | return code | *cmd | *is_available |
+ * +-------+-------------+-------------------+---------------+
+ * | "foo" | false | "foo" (unchanged) | (unchanged) |
+ * +-------+-------------+-------------------+---------------+
+ *
+ * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh”
+ * +-------+-------------------------------------------------+
+ * | Input | Output |
+ * | *cmd | return code | *cmd | *is_available |
+ * +-------+-------------+-------------------+---------------+
+ * | "foo" | true | "./mock.foo.sh" | true |
+ * | "qux" | true | "qux" (unchanged) | false |
+ * +-------+-------------+-------------------+---------------+
+ */
+static int get_schedule_cmd(const char **cmd, int *is_available)
+{
+ char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
+ struct string_list_item *item;
+ struct string_list list = STRING_LIST_INIT_NODUP;
+
+ if (!testing)
+ return 0;
+
+ if (is_available)
+ *is_available = 0;
+
+ string_list_split_in_place(&list, testing, ',', -1);
+ for_each_string_list_item(item, &list) {
+ struct string_list pair = STRING_LIST_INIT_NODUP;
+
+ if (string_list_split_in_place(&pair, item->string, ':', 2) != 2)
+ continue;
+
+ if (!strcmp(*cmd, pair.items[0].string)) {
+ *cmd = pair.items[1].string;
+ if (is_available)
+ *is_available = 1;
+ string_list_clear(&list, 0);
+ UNLEAK(testing);
+ return 1;
+ }
+ }
+
+ string_list_clear(&list, 0);
+ free(testing);
+ return 1;
+}
+
+static int is_launchctl_available(void)
+{
+ const char *cmd = "launchctl";
+ int is_available;
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+#ifdef __APPLE__
+ return 1;
+#else
+ return 0;
+#endif
+}
+
static char *launchctl_service_name(const char *frequency)
{
struct strbuf label = STRBUF_INIT;
@@ -1555,19 +1642,17 @@ static char *launchctl_get_uid(void)
return xstrfmt("gui/%d", getuid());
}
-static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
+static int launchctl_boot_plist(int enable, const char *filename)
{
+ const char *cmd = "launchctl";
int result;
struct child_process child = CHILD_PROCESS_INIT;
char *uid = launchctl_get_uid();
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&child.args, cmd);
- if (enable)
- strvec_push(&child.args, "bootstrap");
- else
- strvec_push(&child.args, "bootout");
- strvec_push(&child.args, uid);
- strvec_push(&child.args, filename);
+ strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid,
+ filename, NULL);
child.no_stderr = 1;
child.no_stdout = 1;
@@ -1581,38 +1666,62 @@ static int launchctl_boot_plist(int enable, const char *filename, const char *cm
return result;
}
-static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
+static int launchctl_remove_plist(enum schedule_priority schedule)
{
const char *frequency = get_frequency(schedule);
char *name = launchctl_service_name(frequency);
char *filename = launchctl_service_filename(name);
- int result = launchctl_boot_plist(0, filename, cmd);
+ int result = launchctl_boot_plist(0, filename);
unlink(filename);
free(filename);
free(name);
return result;
}
-static int launchctl_remove_plists(const char *cmd)
+static int launchctl_remove_plists(void)
{
- return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
- launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
- launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
+ return launchctl_remove_plist(SCHEDULE_HOURLY) ||
+ launchctl_remove_plist(SCHEDULE_DAILY) ||
+ launchctl_remove_plist(SCHEDULE_WEEKLY);
}
-static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int launchctl_list_contains_plist(const char *name, const char *cmd)
{
- FILE *plist;
- int i;
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ char *uid = launchctl_get_uid();
+
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "list", name, NULL);
+
+ child.no_stderr = 1;
+ child.no_stdout = 1;
+
+ if (start_command(&child))
+ die(_("failed to start launchctl"));
+
+ result = finish_command(&child);
+
+ free(uid);
+
+ /* Returns failure if 'name' doesn't exist. */
+ return !result;
+}
+
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule)
+{
+ int i, fd;
const char *preamble, *repeat;
const char *frequency = get_frequency(schedule);
char *name = launchctl_service_name(frequency);
char *filename = launchctl_service_filename(name);
+ struct lock_file lk = LOCK_INIT;
+ static unsigned long lock_file_timeout_ms = ULONG_MAX;
+ struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT;
+ struct stat st;
+ const char *cmd = "launchctl";
- if (safe_create_leading_directories(filename))
- die(_("failed to create directories for '%s'"), filename);
- plist = xfopen(filename, "w");
-
+ get_schedule_cmd(&cmd, NULL);
preamble = "<?xml version=\"1.0\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">"
@@ -1630,7 +1739,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"</array>\n"
"<key>StartCalendarInterval</key>\n"
"<array>\n";
- fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+ strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
switch (schedule) {
case SCHEDULE_HOURLY:
@@ -1639,7 +1748,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 23; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_DAILY:
@@ -1649,50 +1758,91 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
"<key>Minute</key><integer>0</integer>\n"
"</dict>\n";
for (i = 1; i <= 6; i++)
- fprintf(plist, repeat, i);
+ strbuf_addf(&plist, repeat, i);
break;
case SCHEDULE_WEEKLY:
- fprintf(plist,
- "<dict>\n"
- "<key>Day</key><integer>0</integer>\n"
- "<key>Hour</key><integer>0</integer>\n"
- "<key>Minute</key><integer>0</integer>\n"
- "</dict>\n");
+ strbuf_addstr(&plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n");
break;
default:
/* unreachable */
break;
}
- fprintf(plist, "</array>\n</dict>\n</plist>\n");
- fclose(plist);
+ strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
+
+ if (safe_create_leading_directories(filename))
+ die(_("failed to create directories for '%s'"), filename);
+
+ if ((long)lock_file_timeout_ms < 0 &&
+ git_config_get_ulong("gc.launchctlplistlocktimeoutms",
+ &lock_file_timeout_ms))
+ lock_file_timeout_ms = 150;
- /* bootout might fail if not already running, so ignore */
- launchctl_boot_plist(0, filename, cmd);
- if (launchctl_boot_plist(1, filename, cmd))
- die(_("failed to bootstrap service %s"), filename);
+ fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
+ lock_file_timeout_ms);
+
+ /*
+ * Does this file already exist? With the intended contents? Is it
+ * registered already? Then it does not need to be re-registered.
+ */
+ if (!stat(filename, &st) && st.st_size == plist.len &&
+ strbuf_read_file(&plist2, filename, plist.len) == plist.len &&
+ !strbuf_cmp(&plist, &plist2) &&
+ launchctl_list_contains_plist(name, cmd))
+ rollback_lock_file(&lk);
+ else {
+ if (write_in_full(fd, plist.buf, plist.len) < 0 ||
+ commit_lock_file(&lk))
+ die_errno(_("could not write '%s'"), filename);
+
+ /* bootout might fail if not already running, so ignore */
+ launchctl_boot_plist(0, filename);
+ if (launchctl_boot_plist(1, filename))
+ die(_("failed to bootstrap service %s"), filename);
+ }
free(filename);
free(name);
+ strbuf_release(&plist);
+ strbuf_release(&plist2);
return 0;
}
-static int launchctl_add_plists(const char *cmd)
+static int launchctl_add_plists(void)
{
const char *exec_path = git_exec_path();
- return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
- launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
- launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
+ return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY);
}
-static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int launchctl_update_schedule(int run_maintenance, int fd)
{
if (run_maintenance)
- return launchctl_add_plists(cmd);
+ return launchctl_add_plists();
else
- return launchctl_remove_plists(cmd);
+ return launchctl_remove_plists();
+}
+
+static int is_schtasks_available(void)
+{
+ const char *cmd = "schtasks";
+ int is_available;
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+#ifdef GIT_WINDOWS_NATIVE
+ return 1;
+#else
+ return 0;
+#endif
}
static char *schtasks_task_name(const char *frequency)
@@ -1702,13 +1852,15 @@ static char *schtasks_task_name(const char *frequency)
return strbuf_detach(&label, NULL);
}
-static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
+static int schtasks_remove_task(enum schedule_priority schedule)
{
+ const char *cmd = "schtasks";
int result;
struct strvec args = STRVEC_INIT;
const char *frequency = get_frequency(schedule);
char *name = schtasks_task_name(frequency);
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&args, cmd);
strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
@@ -1719,15 +1871,16 @@ static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd
return result;
}
-static int schtasks_remove_tasks(const char *cmd)
+static int schtasks_remove_tasks(void)
{
- return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
- schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
- schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
+ return schtasks_remove_task(SCHEDULE_HOURLY) ||
+ schtasks_remove_task(SCHEDULE_DAILY) ||
+ schtasks_remove_task(SCHEDULE_WEEKLY);
}
-static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule)
{
+ const char *cmd = "schtasks";
int result;
struct child_process child = CHILD_PROCESS_INIT;
const char *xml;
@@ -1736,6 +1889,8 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
char *name = schtasks_task_name(frequency);
struct strbuf tfilename = STRBUF_INIT;
+ get_schedule_cmd(&cmd, NULL);
+
strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
get_git_common_dir(), frequency);
tfile = xmks_tempfile(tfilename.buf);
@@ -1840,28 +1995,52 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
return result;
}
-static int schtasks_schedule_tasks(const char *cmd)
+static int schtasks_schedule_tasks(void)
{
const char *exec_path = git_exec_path();
- return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
- schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
- schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
+ return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_DAILY) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY);
}
-static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int schtasks_update_schedule(int run_maintenance, int fd)
{
if (run_maintenance)
- return schtasks_schedule_tasks(cmd);
+ return schtasks_schedule_tasks();
else
- return schtasks_remove_tasks(cmd);
+ return schtasks_remove_tasks();
+}
+
+static int is_crontab_available(void)
+{
+ const char *cmd = "crontab";
+ int is_available;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+ strvec_split(&child.args, cmd);
+ strvec_push(&child.args, "-l");
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ child.silent_exec_failure = 1;
+
+ if (start_command(&child))
+ return 0;
+ /* Ignore exit code, as an empty crontab will return error. */
+ finish_command(&child);
+ return 1;
}
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
-static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
+static int crontab_update_schedule(int run_maintenance, int fd)
{
+ const char *cmd = "crontab";
int result = 0;
int in_old_region = 0;
struct child_process crontab_list = CHILD_PROCESS_INIT;
@@ -1869,6 +2048,7 @@ static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
FILE *cron_list, *cron_in;
struct strbuf line = STRBUF_INIT;
+ get_schedule_cmd(&cmd, NULL);
strvec_split(&crontab_list.args, cmd);
strvec_push(&crontab_list.args, "-l");
crontab_list.in = -1;
@@ -1945,66 +2125,376 @@ done_editing:
return result;
}
+static int real_is_systemd_timer_available(void)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL);
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+ child.silent_exec_failure = 1;
+
+ if (start_command(&child))
+ return 0;
+ if (finish_command(&child))
+ return 0;
+ return 1;
+}
+
+static int is_systemd_timer_available(void)
+{
+ const char *cmd = "systemctl";
+ int is_available;
+
+ if (get_schedule_cmd(&cmd, &is_available))
+ return is_available;
+
+ return real_is_systemd_timer_available();
+}
+
+static char *xdg_config_home_systemd(const char *filename)
+{
+ return xdg_config_home_for("systemd/user", filename);
+}
+
+static int systemd_timer_enable_unit(int enable,
+ enum schedule_priority schedule)
+{
+ const char *cmd = "systemctl";
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *frequency = get_frequency(schedule);
+
+ /*
+ * Disabling the systemd unit while it is already disabled makes
+ * systemctl print an error.
+ * Let's ignore it since it means we already are in the expected state:
+ * the unit is disabled.
+ *
+ * On the other hand, enabling a systemd unit which is already enabled
+ * produces no error.
+ */
+ if (!enable)
+ child.no_stderr = 1;
+
+ get_schedule_cmd(&cmd, NULL);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "--user", enable ? "enable" : "disable",
+ "--now", NULL);
+ strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency);
+
+ if (start_command(&child))
+ return error(_("failed to start systemctl"));
+ if (finish_command(&child))
+ /*
+ * Disabling an already disabled systemd unit makes
+ * systemctl fail.
+ * Let's ignore this failure.
+ *
+ * Enabling an enabled systemd unit doesn't fail.
+ */
+ if (enable)
+ return error(_("failed to run systemctl"));
+ return 0;
+}
+
+static int systemd_timer_delete_unit_templates(void)
+{
+ int ret = 0;
+ char *filename = xdg_config_home_systemd("git-maintenance@.timer");
+ if (unlink(filename) && !is_missing_file_error(errno))
+ ret = error_errno(_("failed to delete '%s'"), filename);
+ FREE_AND_NULL(filename);
+
+ filename = xdg_config_home_systemd("git-maintenance@.service");
+ if (unlink(filename) && !is_missing_file_error(errno))
+ ret = error_errno(_("failed to delete '%s'"), filename);
+
+ free(filename);
+ return ret;
+}
+
+static int systemd_timer_delete_units(void)
+{
+ return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
+ systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
+ systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
+ systemd_timer_delete_unit_templates();
+}
+
+static int systemd_timer_write_unit_templates(const char *exec_path)
+{
+ char *filename;
+ FILE *file;
+ const char *unit;
+
+ filename = xdg_config_home_systemd("git-maintenance@.timer");
+ if (safe_create_leading_directories(filename)) {
+ error(_("failed to create directories for '%s'"), filename);
+ goto error;
+ }
+ file = fopen_or_warn(filename, "w");
+ if (file == NULL)
+ goto error;
+
+ unit = "# This file was created and is maintained by Git.\n"
+ "# Any edits made in this file might be replaced in the future\n"
+ "# by a Git command.\n"
+ "\n"
+ "[Unit]\n"
+ "Description=Optimize Git repositories data\n"
+ "\n"
+ "[Timer]\n"
+ "OnCalendar=%i\n"
+ "Persistent=true\n"
+ "\n"
+ "[Install]\n"
+ "WantedBy=timers.target\n";
+ if (fputs(unit, file) == EOF) {
+ error(_("failed to write to '%s'"), filename);
+ fclose(file);
+ goto error;
+ }
+ if (fclose(file) == EOF) {
+ error_errno(_("failed to flush '%s'"), filename);
+ goto error;
+ }
+ free(filename);
+
+ filename = xdg_config_home_systemd("git-maintenance@.service");
+ file = fopen_or_warn(filename, "w");
+ if (file == NULL)
+ goto error;
+
+ unit = "# This file was created and is maintained by Git.\n"
+ "# Any edits made in this file might be replaced in the future\n"
+ "# by a Git command.\n"
+ "\n"
+ "[Unit]\n"
+ "Description=Optimize Git repositories data\n"
+ "\n"
+ "[Service]\n"
+ "Type=oneshot\n"
+ "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n"
+ "LockPersonality=yes\n"
+ "MemoryDenyWriteExecute=yes\n"
+ "NoNewPrivileges=yes\n"
+ "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n"
+ "RestrictNamespaces=yes\n"
+ "RestrictRealtime=yes\n"
+ "RestrictSUIDSGID=yes\n"
+ "SystemCallArchitectures=native\n"
+ "SystemCallFilter=@system-service\n";
+ if (fprintf(file, unit, exec_path, exec_path) < 0) {
+ error(_("failed to write to '%s'"), filename);
+ fclose(file);
+ goto error;
+ }
+ if (fclose(file) == EOF) {
+ error_errno(_("failed to flush '%s'"), filename);
+ goto error;
+ }
+ free(filename);
+ return 0;
+
+error:
+ free(filename);
+ systemd_timer_delete_unit_templates();
+ return -1;
+}
+
+static int systemd_timer_setup_units(void)
+{
+ const char *exec_path = git_exec_path();
+
+ int ret = systemd_timer_write_unit_templates(exec_path) ||
+ systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
+ systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
+ systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
+ if (ret)
+ systemd_timer_delete_units();
+ return ret;
+}
+
+static int systemd_timer_update_schedule(int run_maintenance, int fd)
+{
+ if (run_maintenance)
+ return systemd_timer_setup_units();
+ else
+ return systemd_timer_delete_units();
+}
+
+enum scheduler {
+ SCHEDULER_INVALID = -1,
+ SCHEDULER_AUTO,
+ SCHEDULER_CRON,
+ SCHEDULER_SYSTEMD,
+ SCHEDULER_LAUNCHCTL,
+ SCHEDULER_SCHTASKS,
+};
+
+static const struct {
+ const char *name;
+ int (*is_available)(void);
+ int (*update_schedule)(int run_maintenance, int fd);
+} scheduler_fn[] = {
+ [SCHEDULER_CRON] = {
+ .name = "crontab",
+ .is_available = is_crontab_available,
+ .update_schedule = crontab_update_schedule,
+ },
+ [SCHEDULER_SYSTEMD] = {
+ .name = "systemctl",
+ .is_available = is_systemd_timer_available,
+ .update_schedule = systemd_timer_update_schedule,
+ },
+ [SCHEDULER_LAUNCHCTL] = {
+ .name = "launchctl",
+ .is_available = is_launchctl_available,
+ .update_schedule = launchctl_update_schedule,
+ },
+ [SCHEDULER_SCHTASKS] = {
+ .name = "schtasks",
+ .is_available = is_schtasks_available,
+ .update_schedule = schtasks_update_schedule,
+ },
+};
+
+static enum scheduler parse_scheduler(const char *value)
+{
+ if (!value)
+ return SCHEDULER_INVALID;
+ else if (!strcasecmp(value, "auto"))
+ return SCHEDULER_AUTO;
+ else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab"))
+ return SCHEDULER_CRON;
+ else if (!strcasecmp(value, "systemd") ||
+ !strcasecmp(value, "systemd-timer"))
+ return SCHEDULER_SYSTEMD;
+ else if (!strcasecmp(value, "launchctl"))
+ return SCHEDULER_LAUNCHCTL;
+ else if (!strcasecmp(value, "schtasks"))
+ return SCHEDULER_SCHTASKS;
+ else
+ return SCHEDULER_INVALID;
+}
+
+static int maintenance_opt_scheduler(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum scheduler *scheduler = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ *scheduler = parse_scheduler(arg);
+ if (*scheduler == SCHEDULER_INVALID)
+ return error(_("unrecognized --scheduler argument '%s'"), arg);
+ return 0;
+}
+
+struct maintenance_start_opts {
+ enum scheduler scheduler;
+};
+
+static enum scheduler resolve_scheduler(enum scheduler scheduler)
+{
+ if (scheduler != SCHEDULER_AUTO)
+ return scheduler;
+
#if defined(__APPLE__)
-static const char platform_scheduler[] = "launchctl";
+ return SCHEDULER_LAUNCHCTL;
+
#elif defined(GIT_WINDOWS_NATIVE)
-static const char platform_scheduler[] = "schtasks";
+ return SCHEDULER_SCHTASKS;
+
+#elif defined(__linux__)
+ if (is_systemd_timer_available())
+ return SCHEDULER_SYSTEMD;
+ else if (is_crontab_available())
+ return SCHEDULER_CRON;
+ else
+ die(_("neither systemd timers nor crontab are available"));
+
#else
-static const char platform_scheduler[] = "crontab";
+ return SCHEDULER_CRON;
#endif
+}
-static int update_background_schedule(int enable)
+static void validate_scheduler(enum scheduler scheduler)
{
- int result;
- const char *scheduler = platform_scheduler;
- const char *cmd = scheduler;
- char *testing;
+ if (scheduler == SCHEDULER_INVALID)
+ BUG("invalid scheduler");
+ if (scheduler == SCHEDULER_AUTO)
+ BUG("resolve_scheduler should have been called before");
+
+ if (!scheduler_fn[scheduler].is_available())
+ die(_("%s scheduler is not available"),
+ scheduler_fn[scheduler].name);
+}
+
+static int update_background_schedule(const struct maintenance_start_opts *opts,
+ int enable)
+{
+ unsigned int i;
+ int result = 0;
struct lock_file lk;
char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
- testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
- if (testing) {
- char *sep = strchr(testing, ':');
- if (!sep)
- die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
- *sep = '\0';
- scheduler = testing;
- cmd = sep + 1;
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ free(lock_path);
+ return error(_("another process is scheduling background maintenance"));
}
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
- result = error(_("another process is scheduling background maintenance"));
- goto cleanup;
+ for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) {
+ if (enable && opts->scheduler == i)
+ continue;
+ if (!scheduler_fn[i].is_available())
+ continue;
+ scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk));
}
- if (!strcmp(scheduler, "launchctl"))
- result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else if (!strcmp(scheduler, "schtasks"))
- result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else if (!strcmp(scheduler, "crontab"))
- result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd);
- else
- die("unknown background scheduler: %s", scheduler);
+ if (enable)
+ result = scheduler_fn[opts->scheduler].update_schedule(
+ 1, get_lock_file_fd(&lk));
rollback_lock_file(&lk);
-cleanup:
free(lock_path);
- free(testing);
return result;
}
-static int maintenance_start(void)
+static const char *const builtin_maintenance_start_usage[] = {
+ N_("git maintenance start [--scheduler=<scheduler>]"),
+ NULL
+};
+
+static int maintenance_start(int argc, const char **argv, const char *prefix)
{
+ struct maintenance_start_opts opts = { 0 };
+ struct option options[] = {
+ OPT_CALLBACK_F(
+ 0, "scheduler", &opts.scheduler, N_("scheduler"),
+ N_("scheduler to trigger git maintenance run"),
+ PARSE_OPT_NONEG, maintenance_opt_scheduler),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ builtin_maintenance_start_usage, 0);
+ if (argc)
+ usage_with_options(builtin_maintenance_start_usage, options);
+
+ opts.scheduler = resolve_scheduler(opts.scheduler);
+ validate_scheduler(opts.scheduler);
+
if (maintenance_register())
warning(_("failed to add repo to global config"));
-
- return update_background_schedule(1);
+ return update_background_schedule(&opts, 1);
}
static int maintenance_stop(void)
{
- return update_background_schedule(0);
+ return update_background_schedule(NULL, 0);
}
static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
@@ -2018,7 +2508,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[1], "run"))
return maintenance_run(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "start"))
- return maintenance_start();
+ return maintenance_start(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "stop"))
return maintenance_stop();
if (!strcmp(argv[1], "register"))
diff --git a/builtin/grep.c b/builtin/grep.c
index 7d2f8e5adb..51278b01fa 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -65,6 +65,9 @@ static int todo_done;
/* Has all work items been added? */
static int all_work_added;
+static struct repository **repos_to_free;
+static size_t repos_to_free_nr, repos_to_free_alloc;
+
/* This lock protects all the variables above. */
static pthread_mutex_t grep_mutex;
@@ -168,6 +171,19 @@ static void work_done(struct work_item *w)
grep_unlock();
}
+static void free_repos(void)
+{
+ int i;
+
+ for (i = 0; i < repos_to_free_nr; i++) {
+ repo_clear(repos_to_free[i]);
+ free(repos_to_free[i]);
+ }
+ FREE_AND_NULL(repos_to_free);
+ repos_to_free_nr = 0;
+ repos_to_free_alloc = 0;
+}
+
static void *run(void *arg)
{
int hit = 0;
@@ -333,7 +349,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
struct grep_source gs;
grep_source_name(opt, filename, tree_name_len, &pathbuf);
- grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid);
+ grep_source_init_oid(&gs, pathbuf.buf, path, oid, opt->repo);
strbuf_release(&pathbuf);
if (num_threads > 1) {
@@ -359,7 +375,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct grep_source gs;
grep_source_name(opt, filename, 0, &buf);
- grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
+ grep_source_init_file(&gs, buf.buf, filename);
strbuf_release(&buf);
if (num_threads > 1) {
@@ -415,19 +431,24 @@ static int grep_submodule(struct grep_opt *opt,
const struct object_id *oid,
const char *filename, const char *path, int cached)
{
- struct repository subrepo;
+ struct repository *subrepo;
struct repository *superproject = opt->repo;
const struct submodule *sub;
struct grep_opt subopt;
- int hit;
+ int hit = 0;
sub = submodule_from_path(superproject, null_oid(), path);
if (!is_submodule_active(superproject, path))
return 0;
- if (repo_submodule_init(&subrepo, superproject, sub))
+ subrepo = xmalloc(sizeof(*subrepo));
+ if (repo_submodule_init(subrepo, superproject, sub)) {
+ free(subrepo);
return 0;
+ }
+ ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc);
+ repos_to_free[repos_to_free_nr++] = subrepo;
/*
* NEEDSWORK: repo_read_gitmodules() might call
@@ -438,53 +459,49 @@ static int grep_submodule(struct grep_opt *opt,
* subrepo's odbs to the in-memory alternates list.
*/
obj_read_lock();
- repo_read_gitmodules(&subrepo, 0);
+ repo_read_gitmodules(subrepo, 0);
/*
- * NEEDSWORK: This adds the submodule's object directory to the list of
- * alternates for the single in-memory object store. This has some bad
- * consequences for memory (processed objects will never be freed) and
- * performance (this increases the number of pack files git has to pay
- * attention to, to the sum of the number of pack files in all the
- * repositories processed so far). This can be removed once the object
- * store is no longer global and instead is a member of the repository
- * object.
+ * All code paths tested by test code no longer need submodule ODBs to
+ * be added as alternates, but add it to the list just in case.
+ * Submodule ODBs added through add_submodule_odb_by_path() will be
+ * lazily registered as alternates when needed (and except in an
+ * unexpected code interaction, it won't be needed).
*/
- add_to_alternates_memory(subrepo.objects->odb->path);
+ add_submodule_odb_by_path(subrepo->objects->odb->path);
obj_read_unlock();
memcpy(&subopt, opt, sizeof(subopt));
- subopt.repo = &subrepo;
+ subopt.repo = subrepo;
if (oid) {
- struct object *object;
+ enum object_type object_type;
struct tree_desc tree;
void *data;
unsigned long size;
struct strbuf base = STRBUF_INIT;
obj_read_lock();
- object = parse_object_or_die(oid, NULL);
+ object_type = oid_object_info(subrepo, oid, NULL);
obj_read_unlock();
- data = read_object_with_reference(&subrepo,
- &object->oid, tree_type,
+ data = read_object_with_reference(subrepo,
+ oid, tree_type,
&size, NULL);
if (!data)
- die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
+ die(_("unable to read tree (%s)"), oid_to_hex(oid));
strbuf_addstr(&base, filename);
strbuf_addch(&base, '/');
init_tree_desc(&tree, data, size);
hit = grep_tree(&subopt, pathspec, &tree, &base, base.len,
- object->type == OBJ_COMMIT);
+ object_type == OBJ_COMMIT);
strbuf_release(&base);
free(data);
} else {
hit = grep_cache(&subopt, pathspec, cached);
}
- repo_clear(&subrepo);
return hit;
}
@@ -1182,5 +1199,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
run_pager(&opt, prefix);
clear_pathspec(&pathspec);
free_grep_patterns(&opt);
+ free_repos();
return !hit;
}
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 640ef4ded5..c7b3ad74c6 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -53,9 +53,7 @@ static void hash_object(const char *path, const char *type, const char *vpath,
unsigned flags, int literally)
{
int fd;
- fd = open(path, O_RDONLY);
- if (fd < 0)
- die_errno("Cannot open '%s'", path);
+ fd = xopen(path, O_RDONLY);
hash_fd(fd, type, vpath, flags, literally);
}
@@ -117,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
prefix = setup_git_directory_gently(&nongit);
if (vpath && prefix)
- vpath = xstrdup(prefix_filename(prefix, vpath));
+ vpath = prefix_filename(prefix, vpath);
git_config(git_default_config, NULL);
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 8336466865..6cc4890217 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -338,15 +338,11 @@ static const char *open_pack_file(const char *pack_name)
"pack/tmp_pack_XXXXXX");
pack_name = strbuf_detach(&tmp_file, NULL);
} else {
- output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
- if (output_fd < 0)
- die_errno(_("unable to create '%s'"), pack_name);
+ output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
}
nothread_data.pack_fd = output_fd;
} else {
- input_fd = open(pack_name, O_RDONLY);
- if (input_fd < 0)
- die_errno(_("cannot open packfile '%s'"), pack_name);
+ input_fd = xopen(pack_name, O_RDONLY);
output_fd = -1;
nothread_data.pack_fd = input_fd;
}
diff --git a/builtin/log.c b/builtin/log.c
index 3d7717ba5c..f75d87e8d7 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -637,7 +637,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
- rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
+ rev.no_walk = 1;
rev.diffopt.stat_width = -1; /* Scale to real terminal size */
memset(&opt, 0, sizeof(opt));
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1794548c71..f4fd823af8 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -84,6 +84,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
dest = argv[0];
+ packet_trace_identity("ls-remote");
+
UNLEAK(sorting);
if (argc > 1) {
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 664400b816..7baef30569 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -75,9 +75,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
fprintf(stderr, "corrupt mailbox\n");
exit(1);
}
- fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0)
- die_errno("cannot open output file '%s'", name);
+ fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
output = xfdopen(fd, "w");
/* Copy it out, while searching for a line that begins with
diff --git a/builtin/merge.c b/builtin/merge.c
index febb0c99c9..d2c52b6e97 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1138,9 +1138,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
merge_names = &fetch_head_file;
filename = git_path_fetch_head(the_repository);
- fd = open(filename, O_RDONLY);
- if (fd < 0)
- die_errno(_("could not open '%s' for reading"), filename);
+ fd = xopen(filename, O_RDONLY);
if (strbuf_read(merge_names, fd, 0) < 0)
die_errno(_("could not read '%s'"), filename);
@@ -1370,14 +1368,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* There is no unmerged entry, don't advise 'git
* add/rm <file>', just 'git commit'.
*/
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
"Please, commit your changes before you merge."));
else
die(_("You have not concluded your merge (MERGE_HEAD exists)."));
}
if (ref_exists("CHERRY_PICK_HEAD")) {
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you merge."));
else
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 8ff0dee2ec..66de6efd41 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -68,6 +68,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv)
OPT_STRING(0, "preferred-pack", &opts.preferred_pack,
N_("preferred-pack"),
N_("pack for reuse when computing a multi-pack bitmap")),
+ OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
+ MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
OPT_END(),
};
@@ -164,7 +166,7 @@ int cmd_multi_pack_index(int argc, const char **argv,
if (!opts.object_dir)
opts.object_dir = get_object_directory();
- if (argc == 0)
+ if (!argc)
goto usage;
if (!strcmp(argv[0], "repack"))
@@ -175,10 +177,9 @@ int cmd_multi_pack_index(int argc, const char **argv,
return cmd_multi_pack_index_verify(argc, argv);
else if (!strcmp(argv[0], "expire"))
return cmd_multi_pack_index_expire(argc, argv);
- else {
- error(_("unrecognized subcommand: %s"), argv[0]);
+
+ error(_("unrecognized subcommand: %s"), argv[0]);
usage:
- usage_with_options(builtin_multi_pack_index_usage,
- builtin_multi_pack_index_options);
- }
+ usage_with_options(builtin_multi_pack_index_usage,
+ builtin_multi_pack_index_options);
}
diff --git a/builtin/notes.c b/builtin/notes.c
index 74bba39ca8..71c59583a1 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -172,9 +172,7 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
/* write the template message before editing: */
d->edit_path = git_pathdup("NOTES_EDITMSG");
- fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), d->edit_path);
+ fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (d->given)
write_or_die(fd, d->buf.buf, d->buf.len);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index df49f656b9..e27f7cdb91 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1124,6 +1124,11 @@ static void write_reused_pack(struct hashfile *f)
break;
offset += ewah_bit_ctz64(word >> offset);
+ /*
+ * Can use bit positions directly, even for MIDX
+ * bitmaps. See comment in try_partial_reuse()
+ * for why.
+ */
write_reused_pack_one(pos + offset, f, &w_curs);
display_progress(progress_state, ++written);
}
@@ -1256,7 +1261,8 @@ static void write_pack_file(void)
bitmap_writer_show_progress(progress);
bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
- bitmap_writer_build(&to_pack);
+ if (bitmap_writer_build(&to_pack) < 0)
+ die(_("failed to write bitmap index"));
bitmap_writer_finish(written_list, nr_written,
tmpname.buf, write_bitmap_options);
write_bitmap_index = 0;
@@ -3405,13 +3411,9 @@ static void read_object_list_from_stdin(void)
}
}
-/* Remember to update object flag allocation in object.h */
-#define OBJECT_ADDED (1u<<20)
-
static void show_commit(struct commit *commit, void *data)
{
add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0);
- commit->object.flags |= OBJECT_ADDED;
if (write_bitmap_index)
index_commit_for_bitmap(commit);
@@ -3424,7 +3426,6 @@ static void show_object(struct object *obj, const char *name, void *data)
{
add_preferred_base_object(name);
add_object_entry(&obj->oid, obj->type, name, 0);
- obj->flags |= OBJECT_ADDED;
if (use_delta_islands) {
const char *p;
@@ -3505,79 +3506,23 @@ static void show_edge(struct commit *commit)
add_preferred_base(&commit->object.oid);
}
-struct in_pack_object {
- off_t offset;
- struct object *object;
-};
-
-struct in_pack {
- unsigned int alloc;
- unsigned int nr;
- struct in_pack_object *array;
-};
-
-static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+static int add_object_in_unpacked_pack(const struct object_id *oid,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *_data)
{
- in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p);
- in_pack->array[in_pack->nr].object = object;
- in_pack->nr++;
-}
-
-/*
- * Compare the objects in the offset order, in order to emulate the
- * "git rev-list --objects" output that produced the pack originally.
- */
-static int ofscmp(const void *a_, const void *b_)
-{
- struct in_pack_object *a = (struct in_pack_object *)a_;
- struct in_pack_object *b = (struct in_pack_object *)b_;
-
- if (a->offset < b->offset)
- return -1;
- else if (a->offset > b->offset)
- return 1;
- else
- return oidcmp(&a->object->oid, &b->object->oid);
+ add_object_entry(oid, OBJ_NONE, "", 0);
+ return 0;
}
static void add_objects_in_unpacked_packs(void)
{
- struct packed_git *p;
- struct in_pack in_pack;
- uint32_t i;
-
- memset(&in_pack, 0, sizeof(in_pack));
-
- for (p = get_all_packs(the_repository); p; p = p->next) {
- struct object_id oid;
- struct object *o;
-
- if (!p->pack_local || p->pack_keep || p->pack_keep_in_core)
- continue;
- if (open_pack_index(p))
- die(_("cannot open pack index"));
-
- ALLOC_GROW(in_pack.array,
- in_pack.nr + p->num_objects,
- in_pack.alloc);
-
- for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_id(&oid, p, i);
- o = lookup_unknown_object(the_repository, &oid);
- if (!(o->flags & OBJECT_ADDED))
- mark_in_pack_object(o, p, &in_pack);
- o->flags |= OBJECT_ADDED;
- }
- }
-
- if (in_pack.nr) {
- QSORT(in_pack.array, in_pack.nr, ofscmp);
- for (i = 0; i < in_pack.nr; i++) {
- struct object *o = in_pack.array[i].object;
- add_object_entry(&o->oid, o->type, "", 0);
- }
- }
- free(in_pack.array);
+ if (for_each_packed_object(add_object_in_unpacked_pack, NULL,
+ FOR_EACH_OBJECT_PACK_ORDER |
+ FOR_EACH_OBJECT_LOCAL_ONLY |
+ FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS |
+ FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS))
+ die(_("cannot open pack index"));
}
static int add_loose_object(const struct object_id *oid, const char *path,
diff --git a/builtin/push.c b/builtin/push.c
index e8b10a9b7e..4b026ce6c6 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -289,42 +289,42 @@ static const char message_advice_ref_needs_update[] =
static void advise_pull_before_push(void)
{
- if (!advice_push_non_ff_current || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_pull_before_push));
}
static void advise_checkout_pull_push(void)
{
- if (!advice_push_non_ff_matching || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NON_FF_MATCHING) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_checkout_pull_push));
}
static void advise_ref_already_exists(void)
{
- if (!advice_push_already_exists || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_ALREADY_EXISTS) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_already_exists));
}
static void advise_ref_fetch_first(void)
{
- if (!advice_push_fetch_first || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_FETCH_FIRST) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_fetch_first));
}
static void advise_ref_needs_force(void)
{
- if (!advice_push_needs_force || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_NEEDS_FORCE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_needs_force));
}
static void advise_ref_needs_update(void)
{
- if (!advice_push_ref_needs_update || !advice_push_update_rejected)
+ if (!advice_enabled(ADVICE_PUSH_REF_NEEDS_UPDATE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
return;
advise(_(message_advice_ref_needs_update));
}
diff --git a/builtin/rebase.c b/builtin/rebase.c
index c284a7ace1..eb01f4d790 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -405,6 +405,7 @@ static int run_sequencer_rebase(struct rebase_options *opts,
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
+ flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0;
switch (command) {
case ACTION_NONE: {
@@ -1918,7 +1919,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
&options.orig_head))
options.head_name = NULL;
else
- die(_("fatal: no such branch/commit '%s'"),
+ die(_("no such branch/commit '%s'"),
branch_name);
} else if (argc == 0) {
/* Do not need to switch branches, we are already on it. */
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 2d1f97e1ca..041e915454 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1306,7 +1306,7 @@ static void refuse_unconfigured_deny_delete_current(void)
rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
}
-static int command_singleton_iterator(void *cb_data, struct object_id *oid);
+static const struct object_id *command_singleton_iterator(void *cb_data);
static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
{
struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
@@ -1731,16 +1731,15 @@ static void check_aliased_updates(struct command *commands)
string_list_clear(&ref_list, 0);
}
-static int command_singleton_iterator(void *cb_data, struct object_id *oid)
+static const struct object_id *command_singleton_iterator(void *cb_data)
{
struct command **cmd_list = cb_data;
struct command *cmd = *cmd_list;
if (!cmd || is_null_oid(&cmd->new_oid))
- return -1; /* end of list */
+ return NULL;
*cmd_list = NULL; /* this returns only one */
- oidcpy(oid, &cmd->new_oid);
- return 0;
+ return &cmd->new_oid;
}
static void set_connectivity_errors(struct command *commands,
@@ -1770,7 +1769,7 @@ struct iterate_data {
struct shallow_info *si;
};
-static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_receive_command_list(void *cb_data)
{
struct iterate_data *data = cb_data;
struct command **cmd_list = &data->cmds;
@@ -1781,13 +1780,11 @@ static int iterate_receive_command_list(void *cb_data, struct object_id *oid)
/* to be checked in update_shallow_ref() */
continue;
if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) {
- oidcpy(oid, &cmd->new_oid);
*cmd_list = cmd->next;
- return 0;
+ return &cmd->new_oid;
}
}
- *cmd_list = NULL;
- return -1; /* end of list */
+ return NULL;
}
static void reject_updates_to_hidden(struct command *commands)
diff --git a/builtin/repack.c b/builtin/repack.c
index 5f9bc74adc..82ab668272 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -515,6 +515,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!(pack_everything & ALL_INTO_ONE) ||
!is_bare_repository())
write_bitmaps = 0;
+ } else if (write_bitmaps &&
+ git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0) &&
+ git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0)) {
+ write_bitmaps = 0;
}
if (pack_kept_objects < 0)
pack_kept_objects = write_bitmaps > 0;
@@ -725,8 +729,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
update_server_info(0);
remove_temporary_files();
- if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
- write_midx_file(get_object_directory(), NULL, 0);
+ if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {
+ unsigned flags = 0;
+ if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP, 0))
+ flags |= MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX;
+ write_midx_file(get_object_directory(), NULL, flags);
+ }
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
diff --git a/builtin/replace.c b/builtin/replace.c
index cd48765911..946938d011 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -507,7 +507,7 @@ static int convert_graft_file(int force)
if (!fp)
return -1;
- advice_graft_file_deprecated = 0;
+ no_graft_file_deprecated_advice = 1;
while (strbuf_getline(&buf, fp) != EOF) {
if (*buf.buf == '#')
continue;
diff --git a/builtin/reset.c b/builtin/reset.c
index 43e855cb88..51c9e2f43f 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -412,7 +412,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
refresh_index(&the_index, flags, NULL, NULL,
_("Unstaged changes after reset:"));
t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
- if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
+ if (advice_enabled(ADVICE_RESET_QUIET_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) {
printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n"
"use '--quiet' to avoid this. Set the config setting reset.quiet to true\n"
"to make this the default.\n"), t_delta_in_ms / 1000.0);
diff --git a/builtin/revert.c b/builtin/revert.c
index 237f2f18d4..2e13660e4b 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -191,7 +191,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
struct setup_revision_opt s_r_opt;
opts->revs = xmalloc(sizeof(*opts->revs));
repo_init_revisions(the_repository, opts->revs, NULL);
- opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+ opts->revs->no_walk = 1;
+ opts->revs->unsorted_input = 1;
if (argc < 2)
usage_with_options(usage_str, options);
if (!strcmp(argv[1], "-"))
diff --git a/builtin/rm.c b/builtin/rm.c
index 8a24c715e0..3b44b807e5 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -55,7 +55,7 @@ static void print_error_files(struct string_list *files_list,
strbuf_addf(&err_msg,
"\n %s",
files_list->items[i].string);
- if (advice_rm_hints)
+ if (advice_enabled(ADVICE_RM_HINTS))
strbuf_addstr(&err_msg, hints_msg);
*errs = error("%s", err_msg.buf);
strbuf_release(&err_msg);
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index d77ce7aeb3..bea4bbf468 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -482,10 +482,9 @@ static void snarf_refs(int head, int remotes)
}
}
-static int rev_is_head(const char *head, const char *name,
- unsigned char *head_sha1, unsigned char *sha1)
+static int rev_is_head(const char *head, const char *name)
{
- if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1)))
+ if (!head)
return 0;
skip_prefix(head, "refs/heads/", &head);
if (!skip_prefix(name, "refs/heads/", &name))
@@ -806,9 +805,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head,
- ref_name[i],
- head_oid.hash, NULL))
+ if (rev_is_head(head, ref_name[i]))
has_head++;
}
if (!has_head) {
@@ -867,10 +864,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head,
- ref_name[i],
- head_oid.hash,
- rev[i]->object.oid.hash);
+ int is_head = rev_is_head(head, ref_name[i]) &&
+ oideq(&head_oid, &rev[i]->object.oid);
if (extra < 0)
printf("%c [%s] ",
is_head ? '*' : ' ', ref_name[i]);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index ef2776a9e4..4da9781b99 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1724,7 +1724,7 @@ static int add_possible_reference_from_superproject(
} else {
switch (sas->error_mode) {
case SUBMODULE_ALTERNATE_ERROR_DIE:
- if (advice_submodule_alternate_error_strategy_die)
+ if (advice_enabled(ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE))
advise(_(alternate_error_advice));
die(_("submodule '%s' cannot add alternate: %s"),
sas->submodule_name, err.buf);
diff --git a/builtin/tag.c b/builtin/tag.c
index 452558ec95..065b6bf093 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -293,9 +293,7 @@ static void create_tag(const struct object_id *object, const char *object_ref,
/* write the template message before editing: */
path = git_pathdup("TAG_EDITMSG");
- fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("could not create file '%s'"), path);
+ fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (opt->message_given) {
write_or_die(fd, buf->buf, buf->len);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index f1f16f2de5..187203e8bb 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -95,9 +95,7 @@ static int create_file(const char *path)
{
int fd;
path = get_mtime_path(path);
- fd = open(path, O_CREAT | O_RDWR, 0644);
- if (fd < 0)
- die_errno(_("failed to create file %s"), path);
+ fd = xopen(path, O_CREAT | O_RDWR, 0644);
return fd;
}
diff --git a/cache.h b/cache.h
index d23de69368..0c245d4f10 100644
--- a/cache.h
+++ b/cache.h
@@ -1299,6 +1299,13 @@ int looks_like_command_line_option(const char *str);
/**
* Return a newly allocated string with the evaluation of
+ * "$XDG_CONFIG_HOME/$subdir/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
+ * "$HOME/.config/$subdir/$filename". Return NULL upon error.
+ */
+char *xdg_config_home_for(const char *subdir, const char *filename);
+
+/**
+ * Return a newly allocated string with the evaluation of
* "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
* "$HOME/.config/git/$filename". Return NULL upon error.
*/
@@ -1738,6 +1745,8 @@ extern const char *git_mailmap_blob;
void maybe_flush_or_die(FILE *, const char *);
__attribute__((format (printf, 2, 3)))
void fprintf_or_die(FILE *, const char *fmt, ...);
+void fwrite_or_die(FILE *f, const void *buf, size_t count);
+void fflush_or_die(FILE *f);
#define COPY_READ_ERROR (-2)
#define COPY_WRITE_ERROR (-3)
diff --git a/check_bindir b/check_bindir
deleted file mode 100755
index 623eadcbb7..0000000000
--- a/check_bindir
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-bindir="$1"
-gitexecdir="$2"
-gitcmd="$3"
-if test "$bindir" != "$gitexecdir" && test -x "$gitcmd"
-then
- echo
- echo "!! You have installed git-* commands to new gitexecdir."
- echo "!! Old version git-* commands still remain in bindir."
- echo "!! Mixing two versions of Git will lead to problems."
- echo "!! Please remove old version commands in bindir now."
- echo
-fi
diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh
index 26a6689766..07a8c6b199 100755
--- a/ci/install-docker-dependencies.sh
+++ b/ci/install-docker-dependencies.sh
@@ -15,4 +15,8 @@ linux-musl)
apk add --update build-base curl-dev openssl-dev expat-dev gettext \
pcre2-dev python3 musl-libintl perl-utils ncurses >/dev/null
;;
+pedantic)
+ dnf -yq update >/dev/null &&
+ dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null
+ ;;
esac
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index 3ce81ffee9..cc62616d80 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -10,6 +10,11 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
*) ln -s "$cache_dir/.prove" t/.prove;;
esac
+if test "$jobname" = "pedantic"
+then
+ export DEVOPTS=pedantic
+fi
+
make
case "$jobname" in
linux-gcc)
@@ -23,6 +28,7 @@ linux-gcc)
export GIT_TEST_COMMIT_GRAPH=1
export GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1
export GIT_TEST_MULTI_PACK_INDEX=1
+ export GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=1
export GIT_TEST_ADD_I_USE_BUILTIN=1
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_WRITE_REV_INDEX=1
@@ -35,10 +41,9 @@ linux-clang)
export GIT_TEST_DEFAULT_HASH=sha256
make test
;;
-linux-gcc-4.8)
+linux-gcc-4.8|pedantic)
# Don't run the tests; we only care about whether Git can be
- # built with GCC 4.8, as it errors out on some undesired (C99)
- # constructs that newer compilers seem to quietly accept.
+ # built with GCC 4.8 or with pedantic
;;
*)
make test
diff --git a/commit-graph.c b/commit-graph.c
index 3860a0d847..4617059220 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -723,7 +723,7 @@ void close_commit_graph(struct raw_object_store *o)
o->commit_graph = NULL;
}
-static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
+static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos)
{
return bsearch_hash(oid->hash, g->chunk_oid_fanout,
g->chunk_oid_lookup, g->hash_len, pos);
@@ -864,26 +864,55 @@ static int fill_commit_in_graph(struct repository *r,
return 1;
}
-static int find_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
+static int search_commit_pos_in_graph(const struct object_id *id, struct commit_graph *g, uint32_t *pos)
+{
+ struct commit_graph *cur_g = g;
+ uint32_t lex_index;
+
+ while (cur_g && !bsearch_graph(cur_g, id, &lex_index))
+ cur_g = cur_g->base_graph;
+
+ if (cur_g) {
+ *pos = lex_index + cur_g->num_commits_in_base;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int find_commit_pos_in_graph(struct commit *item, struct commit_graph *g, uint32_t *pos)
{
uint32_t graph_pos = commit_graph_position(item);
if (graph_pos != COMMIT_NOT_FROM_GRAPH) {
*pos = graph_pos;
return 1;
} else {
- struct commit_graph *cur_g = g;
- uint32_t lex_index;
+ return search_commit_pos_in_graph(&item->object.oid, g, pos);
+ }
+}
+
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
+{
+ struct commit *commit;
+ uint32_t pos;
- while (cur_g && !bsearch_graph(cur_g, &(item->object.oid), &lex_index))
- cur_g = cur_g->base_graph;
+ if (!repo->objects->commit_graph)
+ return NULL;
+ if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
+ return NULL;
+ if (!repo_has_object_file(repo, id))
+ return NULL;
- if (cur_g) {
- *pos = lex_index + cur_g->num_commits_in_base;
- return 1;
- }
+ commit = lookup_commit(repo, id);
+ if (!commit)
+ return NULL;
+ if (commit->object.parsed)
+ return commit;
- return 0;
- }
+ if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
+ return NULL;
+
+ return commit;
}
static int parse_commit_in_graph_one(struct repository *r,
@@ -895,7 +924,7 @@ static int parse_commit_in_graph_one(struct repository *r,
if (item->object.parsed)
return 1;
- if (find_commit_in_graph(item, g, &pos))
+ if (find_commit_pos_in_graph(item, g, &pos))
return fill_commit_in_graph(r, item, g, pos);
return 0;
@@ -921,7 +950,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item)
uint32_t pos;
if (!prepare_commit_graph(r))
return;
- if (find_commit_in_graph(item, r->objects->commit_graph, &pos))
+ if (find_commit_pos_in_graph(item, r->objects->commit_graph, &pos))
fill_commit_graph_info(item, r->objects->commit_graph, pos);
}
@@ -1091,9 +1120,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -1122,9 +1151,9 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -1235,9 +1264,9 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
edge_value += ctx->new_num_commits_in_base;
else if (ctx->new_base_graph) {
uint32_t pos;
- if (find_commit_in_graph(parent->item,
- ctx->new_base_graph,
- &pos))
+ if (find_commit_pos_in_graph(parent->item,
+ ctx->new_base_graph,
+ &pos))
edge_value = pos;
}
@@ -2096,7 +2125,7 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
ctx->num_extra_edges = 0;
for (i = 0; i < ctx->commits.nr; i++) {
- display_progress(ctx->progress, i);
+ display_progress(ctx->progress, i + 1);
if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
&ctx->commits.list[i]->object.oid)) {
diff --git a/commit-graph.h b/commit-graph.h
index 96c24fb577..04a94e1830 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -41,6 +41,14 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st);
int parse_commit_in_graph(struct repository *r, struct commit *item);
/*
+ * Look up the given commit ID in the commit-graph. This will only return a
+ * commit if the ID exists both in the graph and in the object database such
+ * that we don't return commits whose object has been pruned. Otherwise, this
+ * function returns `NULL`.
+ */
+struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id);
+
+/*
* It is possible that we loaded commit contents from the commit buffer,
* but we also want to ensure the commit-graph content is correctly
* checked and filled. Fill the graph_pos and generation members of
diff --git a/commit.c b/commit.c
index 143f472c0f..551de4903c 100644
--- a/commit.c
+++ b/commit.c
@@ -25,6 +25,7 @@
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
int save_commit_buffer = 1;
+int no_graft_file_deprecated_advice;
const char *commit_type = "commit";
@@ -190,7 +191,8 @@ static int read_graft_file(struct repository *r, const char *graft_file)
struct strbuf buf = STRBUF_INIT;
if (!fp)
return -1;
- if (advice_graft_file_deprecated)
+ if (!no_graft_file_deprecated_advice &&
+ advice_enabled(ADVICE_GRAFT_FILE_DEPRECATED))
advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
"and will be removed in a future Git version.\n"
"\n"
diff --git a/commit.h b/commit.h
index df42eb434f..3ea32766bc 100644
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ struct commit {
};
extern int save_commit_buffer;
+extern int no_graft_file_deprecated_advice;
extern const char *commit_type;
/* While we can decorate any object with a name, it's only used for commits.. */
diff --git a/compat/linux/procinfo.c b/compat/linux/procinfo.c
index 578fed4cd3..bc2f9382a1 100644
--- a/compat/linux/procinfo.c
+++ b/compat/linux/procinfo.c
@@ -4,51 +4,172 @@
#include "strvec.h"
#include "trace2.h"
-static void get_ancestry_names(struct strvec *names)
+/*
+ * We need more complex parsing in stat_parent_pid() and
+ * parse_proc_stat() below than a dumb fscanf(). That's because while
+ * the statcomm field is surrounded by parentheses, the process itself
+ * is free to insert any arbitrary byte sequence its its name. That
+ * can include newlines, spaces, closing parentheses etc.
+ *
+ * See do_task_stat() in fs/proc/array.c in linux.git, this is in
+ * contrast with the escaped version of the name found in
+ * /proc/%d/status.
+ *
+ * So instead of using fscanf() we'll read N bytes from it, look for
+ * the first "(", and then the last ")", anything in-between is our
+ * process name.
+ *
+ * How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
+ * default, but it can be raised set to values of up to 2^22. So
+ * that's 7 digits for a PID. We have 2 PIDs in the first four fields
+ * we're interested in, so 2 * 7 = 14.
+ *
+ * We then have 3 spaces between those four values, and we'd like to
+ * get to the space between the 4th and the 5th (the "pgrp" field) to
+ * make sure we read the entire "ppid" field. So that brings us up to
+ * 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
+ * and it's 20. The "state" value itself is then one character (now at
+ * 21).
+ *
+ * Finally the maximum length of the "comm" name itself is 15
+ * characters, e.g. a setting of "123456789abcdefg" will be truncated
+ * to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
+ * we'd need to read 21 + 15 = 36 bytes.
+ *
+ * Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
+ * grows past 2^22 we'll be future-proof. We'll then anchor at the
+ * last ")" we find to locate the parent PID.
+ */
+#define STAT_PARENT_PID_READ_N 64
+
+static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
+ int *statppid)
{
+ const char *comm_lhs = strchr(sb->buf, '(');
+ const char *comm_rhs = strrchr(sb->buf, ')');
+ const char *ppid_lhs, *ppid_rhs;
+ char *p;
+ pid_t ppid;
+
+ if (!comm_lhs || !comm_rhs)
+ goto bad_kernel;
+
+ /*
+ * We're at the ")", that's followed by " X ", where X is a
+ * single "state" character. So advance by 4 bytes.
+ */
+ ppid_lhs = comm_rhs + 4;
+
+ /*
+ * Read until the space between the "ppid" and "pgrp" fields
+ * to make sure we're anchored after the untruncated "ppid"
+ * field..
+ */
+ ppid_rhs = strchr(ppid_lhs, ' ');
+ if (!ppid_rhs)
+ goto bad_kernel;
+
+ ppid = strtol(ppid_lhs, &p, 10);
+ if (ppid_rhs == p) {
+ const char *comm = comm_lhs + 1;
+ size_t commlen = comm_rhs - comm;
+
+ strbuf_add(name, comm, commlen);
+ *statppid = ppid;
+
+ return 0;
+ }
+
+bad_kernel:
/*
- * NEEDSWORK: We could gather the entire pstree into an array to match
- * functionality with compat/win32/trace2_win32_process_info.c.
- * To do so, we may want to examine /proc/<pid>/stat. For now, just
- * gather the immediate parent name which is readily accessible from
- * /proc/$(getppid())/comm.
+ * We were able to read our STAT_PARENT_PID_READ_N bytes from
+ * /proc/%d/stat, but the content is bad. Broken kernel?
+ * Should not happen, but handle it gracefully.
*/
+ return -1;
+}
+
+static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
+{
struct strbuf procfs_path = STRBUF_INIT;
- struct strbuf name = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp;
+ int ret = -1;
/* try to use procfs if it's present. */
- strbuf_addf(&procfs_path, "/proc/%d/comm", getppid());
- if (strbuf_read_file(&name, procfs_path.buf, 0)) {
- strbuf_release(&procfs_path);
- strbuf_trim_trailing_newline(&name);
- strvec_push(names, strbuf_detach(&name, NULL));
- }
+ strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
+ fp = fopen(procfs_path.buf, "r");
+ if (!fp)
+ goto cleanup;
+
+ /*
+ * We could be more strict here and assert that we read at
+ * least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
+ * that on any modern kernel (at least since 2.6.0 released in
+ * 2003) even if all the mandatory numeric fields were zero'd
+ * out we'd get at least 100 bytes, but let's just check that
+ * we got anything at all and trust the parse_proc_stat()
+ * function to handle its "Bad Kernel?" error checking.
+ */
+ if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
+ goto cleanup;
+ if (parse_proc_stat(&sb, name, statppid) < 0)
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ if (fp)
+ fclose(fp);
+ strbuf_release(&procfs_path);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static void push_ancestry_name(struct strvec *names, pid_t pid)
+{
+ struct strbuf name = STRBUF_INIT;
+ int ppid;
+
+ if (stat_parent_pid(pid, &name, &ppid) < 0)
+ goto cleanup;
+
+ strvec_push(names, name.buf);
+
+ /*
+ * Both errors and reaching the end of the process chain are
+ * reported as fields of 0 by proc(5)
+ */
+ if (ppid)
+ push_ancestry_name(names, ppid);
+cleanup:
+ strbuf_release(&name);
return;
- /* NEEDSWORK: add non-procfs-linux implementations here */
}
void trace2_collect_process_info(enum trace2_process_info_reason reason)
{
- if (!trace2_is_enabled())
- return;
+ struct strvec names = STRVEC_INIT;
- /* someday we may want to write something extra here, but not today */
- if (reason == TRACE2_PROCESS_INFO_EXIT)
+ if (!trace2_is_enabled())
return;
- if (reason == TRACE2_PROCESS_INFO_STARTUP) {
+ switch (reason) {
+ case TRACE2_PROCESS_INFO_EXIT:
/*
- * NEEDSWORK: we could do the entire ptree in an array instead,
- * see compat/win32/trace2_win32_process_info.c.
+ * The Windows version of this calls its
+ * get_peak_memory_info() here. We may want to insert
+ * similar process-end statistics here in the future.
*/
- struct strvec names = STRVEC_INIT;
-
- get_ancestry_names(&names);
+ break;
+ case TRACE2_PROCESS_INFO_STARTUP:
+ push_ancestry_name(&names, getppid());
if (names.nr)
trace2_cmd_ancestry(names.v);
strvec_clear(&names);
+ break;
}
return;
diff --git a/compat/mmap.c b/compat/mmap.c
index 14d31010df..8d6c02d4bc 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -7,7 +7,12 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
if (start != NULL || flags != MAP_PRIVATE || prot != PROT_READ)
die("Invalid usage of mmap when built with NO_MMAP");
- start = xmalloc(length);
+ if (length == 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = malloc(length);
if (start == NULL) {
errno = ENOMEM;
return MAP_FAILED;
diff --git a/config.c b/config.c
index cb4a8058bf..696d77c08f 100644
--- a/config.c
+++ b/config.c
@@ -1796,6 +1796,7 @@ int git_config_from_mem(config_fn_t fn,
int git_config_from_blob_oid(config_fn_t fn,
const char *name,
+ struct repository *repo,
const struct object_id *oid,
void *data)
{
@@ -1804,7 +1805,7 @@ int git_config_from_blob_oid(config_fn_t fn,
unsigned long size;
int ret;
- buf = read_object_file(oid, &type, &size);
+ buf = repo_read_object_file(repo, oid, &type, &size);
if (!buf)
return error(_("unable to load config blob object '%s'"), name);
if (type != OBJ_BLOB) {
@@ -1820,14 +1821,15 @@ int git_config_from_blob_oid(config_fn_t fn,
}
static int git_config_from_blob_ref(config_fn_t fn,
+ struct repository *repo,
const char *name,
void *data)
{
struct object_id oid;
- if (get_oid(name, &oid) < 0)
+ if (repo_get_oid(repo, name, &oid) < 0)
return error(_("unable to resolve config blob '%s'"), name);
- return git_config_from_blob_oid(fn, name, &oid, data);
+ return git_config_from_blob_oid(fn, name, repo, &oid, data);
}
char *git_system_config(void)
@@ -1958,12 +1960,16 @@ int config_with_options(config_fn_t fn, void *data,
* If we have a specific filename, use it. Otherwise, follow the
* regular lookup sequence.
*/
- if (config_source && config_source->use_stdin)
+ if (config_source && config_source->use_stdin) {
return git_config_from_stdin(fn, data);
- else if (config_source && config_source->file)
+ } else if (config_source && config_source->file) {
return git_config_from_file(fn, config_source->file, data);
- else if (config_source && config_source->blob)
- return git_config_from_blob_ref(fn, config_source->blob, data);
+ } else if (config_source && config_source->blob) {
+ struct repository *repo = config_source->repo ?
+ config_source->repo : the_repository;
+ return git_config_from_blob_ref(fn, repo, config_source->blob,
+ data);
+ }
return do_git_config_sequence(opts, fn, data);
}
diff --git a/config.h b/config.h
index a2200f3111..147f5e0490 100644
--- a/config.h
+++ b/config.h
@@ -49,6 +49,8 @@ const char *config_scope_name(enum config_scope scope);
struct git_config_source {
unsigned int use_stdin:1;
const char *file;
+ /* The repository if blob is not NULL; leave blank for the_repository */
+ struct repository *repo;
const char *blob;
enum config_scope scope;
};
@@ -136,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,
const char *buf, size_t len,
void *data, const struct config_options *opts);
int git_config_from_blob_oid(config_fn_t fn, const char *name,
+ struct repository *repo,
const struct object_id *oid, void *data);
void git_config_push_parameter(const char *text);
void git_config_push_env(const char *spec);
diff --git a/connected.c b/connected.c
index b18299fdf0..cf68e37a97 100644
--- a/connected.c
+++ b/connected.c
@@ -24,7 +24,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
struct child_process rev_list = CHILD_PROCESS_INIT;
FILE *rev_list_in;
struct check_connected_options defaults = CHECK_CONNECTED_INIT;
- struct object_id oid;
+ const struct object_id *oid;
int err = 0;
struct packed_git *new_pack = NULL;
struct transport *transport;
@@ -34,7 +34,8 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
opt = &defaults;
transport = opt->transport;
- if (fn(cb_data, &oid)) {
+ oid = fn(cb_data);
+ if (!oid) {
if (opt->err_fd)
close(opt->err_fd);
return err;
@@ -73,7 +74,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
for (p = get_all_packs(the_repository); p; p = p->next) {
if (!p->pack_promisor)
continue;
- if (find_pack_entry_one(oid.hash, p))
+ if (find_pack_entry_one(oid->hash, p))
goto promisor_pack_found;
}
/*
@@ -83,7 +84,7 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
goto no_promisor_pack_found;
promisor_pack_found:
;
- } while (!fn(cb_data, &oid));
+ } while ((oid = fn(cb_data)) != NULL);
return 0;
}
@@ -106,6 +107,7 @@ no_promisor_pack_found:
if (opt->progress)
strvec_pushf(&rev_list.args, "--progress=%s",
_("Checking connectivity"));
+ strvec_push(&rev_list.args, "--unsorted-input");
rev_list.git_cmd = 1;
rev_list.env = opt->env;
@@ -132,12 +134,12 @@ no_promisor_pack_found:
* are sure the ref is good and not sending it to
* rev-list for verification.
*/
- if (new_pack && find_pack_entry_one(oid.hash, new_pack))
+ if (new_pack && find_pack_entry_one(oid->hash, new_pack))
continue;
- if (fprintf(rev_list_in, "%s\n", oid_to_hex(&oid)) < 0)
+ if (fprintf(rev_list_in, "%s\n", oid_to_hex(oid)) < 0)
break;
- } while (!fn(cb_data, &oid));
+ } while ((oid = fn(cb_data)) != NULL);
if (ferror(rev_list_in) || fflush(rev_list_in)) {
if (errno != EPIPE && errno != EINVAL)
diff --git a/connected.h b/connected.h
index 8d5a6b3ad6..6e59c92aa3 100644
--- a/connected.h
+++ b/connected.h
@@ -9,7 +9,7 @@ struct transport;
* When called after returning the name for the last object, return -1
* to signal EOF, otherwise return 0.
*/
-typedef int (*oid_iterate_fn)(void *, struct object_id *oid);
+typedef const struct object_id *(*oid_iterate_fn)(void *);
/*
* Named-arguments struct for check_connected. All arguments are
diff --git a/contrib/coccinelle/xopen.cocci b/contrib/coccinelle/xopen.cocci
new file mode 100644
index 0000000000..814d7b8a1a
--- /dev/null
+++ b/contrib/coccinelle/xopen.cocci
@@ -0,0 +1,16 @@
+@@
+identifier fd;
+identifier die_fn =~ "^(die|die_errno)$";
+@@
+(
+ fd =
+- open
++ xopen
+ (...);
+|
+ int fd =
+- open
++ xopen
+ (...);
+)
+- if ( \( fd < 0 \| fd == -1 \) ) { die_fn(...); }
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 4bdd27ddc8..8108eda1e8 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -356,7 +356,7 @@ __gitcomp ()
local cur_="${3-$cur}"
case "$cur_" in
- --*=)
+ *=)
;;
--no-*)
local c i=0 IFS=$' \t\n'
@@ -421,7 +421,7 @@ __gitcomp_builtin ()
local incl="${2-}"
local excl="${3-}"
- local var=__gitcomp_builtin_"${cmd/-/_}"
+ local var=__gitcomp_builtin_"${cmd//-/_}"
local options
eval "options=\${$var-}"
@@ -2650,10 +2650,10 @@ __git_complete_config_variable_name ()
return
;;
branch.*)
- local pfx="${cur%.*}."
- cur_="${cur#*.}"
+ local pfx="${cur_%.*}."
+ cur_="${cur_#*.}"
__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
- __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
return
;;
guitool.*.*)
@@ -2687,7 +2687,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__git_compute_all_commands
- __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
return
;;
remote.*.*)
@@ -2703,7 +2703,7 @@ __git_complete_config_variable_name ()
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
- __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
return
;;
url.*.*)
diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh
index 4a790d8f4e..ba797e5b3c 100644
--- a/contrib/completion/git-completion.tcsh
+++ b/contrib/completion/git-completion.tcsh
@@ -80,8 +80,9 @@ else
COMP_CWORD=\$((\${#COMP_WORDS[@]}-1))
fi
-# Call _git() or _gitk() of the bash script, based on the first argument
-_\${1}
+# Call __git_wrap__git_main() or __git_wrap__gitk_main() of the bash script,
+# based on the first argument
+__git_wrap__\${1}_main
IFS=\$'\n'
if [ \${#COMPREPLY[*]} -eq 0 ]; then
diff --git a/credential.c b/credential.c
index 3c05c7c669..000ac7a8d4 100644
--- a/credential.c
+++ b/credential.c
@@ -128,6 +128,7 @@ static void credential_apply_config(struct credential *c)
normalized_url = url_normalize(url.buf, &config.url);
git_config(urlmatch_config_entry, &config);
+ string_list_clear(&config.vars, 1);
free(normalized_url);
strbuf_release(&url);
diff --git a/csum-file.c b/csum-file.c
index c951cf8277..26e8a6df44 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -131,12 +131,8 @@ struct hashfile *hashfd_check(const char *name)
int sink, check;
struct hashfile *f;
- sink = open("/dev/null", O_WRONLY);
- if (sink < 0)
- die_errno("unable to open /dev/null");
- check = open(name, O_RDONLY);
- if (check < 0)
- die_errno("unable to open '%s'", name);
+ sink = xopen("/dev/null", O_WRONLY);
+ check = xopen(name, O_RDONLY);
f = hashfd(sink, name);
f->check_fd = check;
f->check_buffer = xmalloc(f->buffer_len);
diff --git a/detect-compiler b/detect-compiler
index 70b754481c..11d60da5b7 100755
--- a/detect-compiler
+++ b/detect-compiler
@@ -13,11 +13,11 @@ get_version_line() {
}
get_family() {
- get_version_line | sed 's/^\(.*\) version [0-9][^ ]* .*/\1/'
+ get_version_line | sed 's/^\(.*\) version [0-9].*/\1/'
}
get_version() {
- get_version_line | sed 's/^.* version \([0-9][^ ]*\) .*/\1/'
+ get_version_line | sed 's/^.* version \([0-9][^ ]*\).*/\1/'
}
print_flags() {
@@ -38,10 +38,7 @@ case "$(get_family)" in
gcc)
print_flags gcc
;;
-clang)
- print_flags clang
- ;;
-"FreeBSD clang")
+clang | *" clang")
print_flags clang
;;
"Apple LLVM")
diff --git a/diff-lib.c b/diff-lib.c
index f9eadc4fc1..ca085a03ef 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -117,6 +117,10 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
continue;
+ if (revs->diffopt.prefix &&
+ strncmp(ce->name, revs->diffopt.prefix, revs->diffopt.prefix_length))
+ continue;
+
if (ce_stage(ce)) {
struct combine_diff_path *dpath;
struct diff_filepair *pair;
diff --git a/diff-merges.c b/diff-merges.c
index d897fd8a29..5060ccd890 100644
--- a/diff-merges.c
+++ b/diff-merges.c
@@ -6,7 +6,7 @@ typedef void (*diff_merges_setup_func_t)(struct rev_info *);
static void set_separate(struct rev_info *revs);
static diff_merges_setup_func_t set_to_default = set_separate;
-static int suppress_parsing;
+static int suppress_m_parsing;
static void suppress(struct rev_info *revs)
{
@@ -91,9 +91,9 @@ int diff_merges_config(const char *value)
return 0;
}
-void diff_merges_suppress_options_parsing(void)
+void diff_merges_suppress_m_parsing(void)
{
- suppress_parsing = 1;
+ suppress_m_parsing = 1;
}
int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
@@ -102,10 +102,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
const char *optarg;
const char *arg = argv[0];
- if (suppress_parsing)
- return 0;
-
- if (!strcmp(arg, "-m")) {
+ if (!suppress_m_parsing && !strcmp(arg, "-m")) {
set_to_default(revs);
} else if (!strcmp(arg, "-c")) {
set_combined(revs);
@@ -153,9 +150,6 @@ void diff_merges_set_dense_combined_if_unset(struct rev_info *revs)
void diff_merges_setup_revs(struct rev_info *revs)
{
- if (suppress_parsing)
- return;
-
if (revs->combine_merges == 0)
revs->dense_combined_merges = 0;
if (revs->separate_merges == 0)
diff --git a/diff-merges.h b/diff-merges.h
index b5d57f6563..19639689bb 100644
--- a/diff-merges.h
+++ b/diff-merges.h
@@ -11,7 +11,7 @@ struct rev_info;
int diff_merges_config(const char *value);
-void diff_merges_suppress_options_parsing(void);
+void diff_merges_suppress_m_parsing(void);
int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
diff --git a/editor.c b/editor.c
index 6303ae0ab0..fdd3eeafa9 100644
--- a/editor.c
+++ b/editor.c
@@ -58,7 +58,7 @@ static int launch_specified_editor(const char *editor, const char *path,
const char *args[] = { editor, NULL, NULL };
struct child_process p = CHILD_PROCESS_INIT;
int ret, sig;
- int print_waiting_for_editor = advice_waiting_for_editor && isatty(2);
+ int print_waiting_for_editor = advice_enabled(ADVICE_WAITING_FOR_EDITOR) && isatty(2);
if (print_waiting_for_editor) {
/*
diff --git a/entry.c b/entry.c
index 125fabdbd5..9b0f968a70 100644
--- a/entry.c
+++ b/entry.c
@@ -159,25 +159,25 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data)
return !available;
}
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
+ int show_progress)
{
int errs = 0;
- unsigned delayed_object_count;
+ unsigned processed_paths = 0;
off_t filtered_bytes = 0;
struct string_list_item *filter, *path;
- struct progress *progress;
+ struct progress *progress = NULL;
struct delayed_checkout *dco = state->delayed_checkout;
if (!state->delayed_checkout)
return errs;
dco->state = CE_RETRY;
- delayed_object_count = dco->paths.nr;
- progress = start_delayed_progress(_("Filtering content"), delayed_object_count);
+ if (show_progress)
+ progress = start_delayed_progress(_("Filtering content"), dco->paths.nr);
while (dco->filters.nr > 0) {
for_each_string_list_item(filter, &dco->filters) {
struct string_list available_paths = STRING_LIST_INIT_NODUP;
- display_progress(progress, delayed_object_count - dco->paths.nr);
if (!async_query_available_blobs(filter->string, &available_paths)) {
/* Filter reported an error */
@@ -224,6 +224,7 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts)
ce = index_file_exists(state->istate, path->string,
strlen(path->string), 0);
if (ce) {
+ display_progress(progress, ++processed_paths);
errs |= checkout_entry(ce, state, NULL, nr_checkouts);
filtered_bytes += ce->ce_stat_data.sd_size;
display_throughput(progress, filtered_bytes);
diff --git a/entry.h b/entry.h
index b8c0e170dc..7c889e58fd 100644
--- a/entry.h
+++ b/entry.h
@@ -43,7 +43,8 @@ static inline int checkout_entry(struct cache_entry *ce,
}
void enable_delayed_checkout(struct checkout *state);
-int finish_delayed_checkout(struct checkout *state, int *nr_checkouts);
+int finish_delayed_checkout(struct checkout *state, int *nr_checkouts,
+ int show_progress);
/*
* Unlink the last component and schedule the leading directories for
diff --git a/fetch-pack.c b/fetch-pack.c
index 0bf7ed7e47..a9604f35a3 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -119,6 +119,11 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
{
enum object_type type;
struct object_info info = { .typep = &type };
+ struct commit *commit;
+
+ commit = lookup_commit_in_graph(the_repository, oid);
+ if (commit)
+ return commit;
while (1) {
if (oid_object_info_extended(the_repository, oid, &info,
@@ -1912,16 +1917,15 @@ static void update_shallow(struct fetch_pack_args *args,
oid_array_clear(&ref);
}
-static int iterate_ref_map(void *cb_data, struct object_id *oid)
+static const struct object_id *iterate_ref_map(void *cb_data)
{
struct ref **rm = cb_data;
struct ref *ref = *rm;
if (!ref)
- return -1; /* end of the list */
+ return NULL;
*rm = ref->next;
- oidcpy(oid, &ref->old_oid);
- return 0;
+ return &ref->old_oid;
}
struct ref *fetch_pack(struct fetch_pack_args *args,
diff --git a/git-send-email.perl b/git-send-email.perl
index e65d969d0b..5262d88ee3 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -376,7 +376,7 @@ sub read_config {
@$target = @values;
}
else {
- my $v = $known_keys->{$key}->[0];
+ my $v = $known_keys->{$key}->[-1];
next unless defined $v;
next if $configured->{$setting}++;
$$target = $v;
@@ -1697,7 +1697,6 @@ EOF
$in_reply_to = $initial_in_reply_to;
$references = $initial_in_reply_to || '';
-$subject = $initial_subject;
$message_num = 0;
# Prepares the email, prompts the user, sends it out
@@ -1720,6 +1719,7 @@ sub process_file {
@xh = ();
my $input_format = undef;
my @header = ();
+ $subject = $initial_subject;
$message = "";
$message_num++;
# First unfold multiline header fields
@@ -1926,15 +1926,23 @@ sub process_file {
}
# set up for the next message
- if ($thread && $message_was_sent &&
- ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
- $message_num == 1)) {
- $in_reply_to = $message_id;
- if (length $references > 0) {
- $references .= "\n $message_id";
- } else {
- $references = "$message_id";
+ if ($thread) {
+ if ($message_was_sent &&
+ ($chain_reply_to || !defined $in_reply_to || length($in_reply_to) == 0 ||
+ $message_num == 1)) {
+ $in_reply_to = $message_id;
+ if (length $references > 0) {
+ $references .= "\n $message_id";
+ } else {
+ $references = "$message_id";
+ }
}
+ } elsif (!defined $initial_in_reply_to) {
+ # --thread and --in-reply-to manage the "In-Reply-To" header and by
+ # extension the "References" header. If these commands are not used, reset
+ # the header values to their defaults.
+ $in_reply_to = undef;
+ $references = '';
}
$message_id = undef;
$num_sent++;
diff --git a/git.c b/git.c
index 18bed9a996..60c2784be4 100644
--- a/git.c
+++ b/git.c
@@ -561,7 +561,7 @@ static struct cmd_struct commands[] = {
{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
{ "mktree", cmd_mktree, RUN_SETUP },
- { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY },
+ { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "notes", cmd_notes, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index e09e024a09..fbd1c20a23 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -3796,7 +3796,8 @@ sub git_get_heads_list {
my @headslist;
open my $fd, '-|', git_cmd(), 'for-each-ref',
- ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+ ($limit ? '--count='.($limit+1) : ()),
+ '--sort=-HEAD', '--sort=-committerdate',
'--format=%(objectname) %(refname) %(subject)%00%(committer)',
@patterns
or return;
diff --git a/grep.c b/grep.c
index 424a39591b..79598f245f 100644
--- a/grep.c
+++ b/grep.c
@@ -1825,14 +1825,24 @@ int grep_source(struct grep_opt *opt, struct grep_source *gs)
return grep_source_1(opt, gs, 0);
}
+static void grep_source_init_buf(struct grep_source *gs, char *buf,
+ unsigned long size)
+{
+ gs->type = GREP_SOURCE_BUF;
+ gs->name = NULL;
+ gs->path = NULL;
+ gs->buf = buf;
+ gs->size = size;
+ gs->driver = NULL;
+ gs->identifier = NULL;
+}
+
int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
{
struct grep_source gs;
int r;
- grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL, NULL);
- gs.buf = buf;
- gs.size = size;
+ grep_source_init_buf(&gs, buf, size);
r = grep_source(opt, &gs);
@@ -1840,28 +1850,30 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size)
return r;
}
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
- const char *name, const char *path,
- const void *identifier)
+void grep_source_init_file(struct grep_source *gs, const char *name,
+ const char *path)
{
- gs->type = type;
+ gs->type = GREP_SOURCE_FILE;
gs->name = xstrdup_or_null(name);
gs->path = xstrdup_or_null(path);
gs->buf = NULL;
gs->size = 0;
gs->driver = NULL;
+ gs->identifier = xstrdup(path);
+}
- switch (type) {
- case GREP_SOURCE_FILE:
- gs->identifier = xstrdup(identifier);
- break;
- case GREP_SOURCE_OID:
- gs->identifier = oiddup(identifier);
- break;
- case GREP_SOURCE_BUF:
- gs->identifier = NULL;
- break;
- }
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+ const char *path, const struct object_id *oid,
+ struct repository *repo)
+{
+ gs->type = GREP_SOURCE_OID;
+ gs->name = xstrdup_or_null(name);
+ gs->path = xstrdup_or_null(path);
+ gs->buf = NULL;
+ gs->size = 0;
+ gs->driver = NULL;
+ gs->identifier = oiddup(oid);
+ gs->repo = repo;
}
void grep_source_clear(struct grep_source *gs)
@@ -1890,7 +1902,8 @@ static int grep_source_load_oid(struct grep_source *gs)
{
enum object_type type;
- gs->buf = read_object_file(gs->identifier, &type, &gs->size);
+ gs->buf = repo_read_object_file(gs->repo, gs->identifier, &type,
+ &gs->size);
if (!gs->buf)
return error(_("'%s': unable to read %s"),
gs->name,
diff --git a/grep.h b/grep.h
index 72f82b1e30..128007db65 100644
--- a/grep.h
+++ b/grep.h
@@ -120,7 +120,20 @@ struct grep_opt {
struct grep_pat *header_list;
struct grep_pat **header_tail;
struct grep_expr *pattern_expression;
+
+ /*
+ * NEEDSWORK: See if we can remove this field, because the repository
+ * should probably be per-source. That is, grep.c functions using this
+ * field should probably start using "repo" in "struct grep_source"
+ * instead.
+ *
+ * This is potentially the cause of at least one bug - "git grep"
+ * ignoring the textconv attributes from submodules. See [1] for more
+ * information.
+ * [1] https://lore.kernel.org/git/CAHd-oW5iEQarYVxEXoTG-ua2zdoybTrSjCBKtO0YT292fm0NQQ@mail.gmail.com/
+ */
struct repository *repo;
+
const char *prefix;
int prefix_length;
regex_t regexp;
@@ -187,6 +200,7 @@ struct grep_source {
GREP_SOURCE_BUF,
} type;
void *identifier;
+ struct repository *repo; /* if GREP_SOURCE_OID */
char *buf;
unsigned long size;
@@ -195,9 +209,11 @@ struct grep_source {
struct userdiff_driver *driver;
};
-void grep_source_init(struct grep_source *gs, enum grep_source_type type,
- const char *name, const char *path,
- const void *identifier);
+void grep_source_init_file(struct grep_source *gs, const char *name,
+ const char *path);
+void grep_source_init_oid(struct grep_source *gs, const char *name,
+ const char *path, const struct object_id *oid,
+ struct repository *repo);
void grep_source_clear_data(struct grep_source *gs);
void grep_source_clear(struct grep_source *gs);
void grep_source_load_driver(struct grep_source *gs,
diff --git a/help.c b/help.c
index 3c3bdec213..be2fa64241 100644
--- a/help.c
+++ b/help.c
@@ -11,6 +11,7 @@
#include "version.h"
#include "refs.h"
#include "parse-options.h"
+#include "prompt.h"
struct category_description {
uint32_t category;
@@ -472,6 +473,7 @@ int is_in_cmdlist(struct cmdnames *c, const char *s)
static int autocorrect;
static struct cmdnames aliases;
+#define AUTOCORRECT_PROMPT (-3)
#define AUTOCORRECT_NEVER (-2)
#define AUTOCORRECT_IMMEDIATELY (-1)
@@ -486,6 +488,8 @@ static int git_unknown_cmd_config(const char *var, const char *value, void *cb)
autocorrect = AUTOCORRECT_NEVER;
} else if (!strcmp(value, "immediate")) {
autocorrect = AUTOCORRECT_IMMEDIATELY;
+ } else if (!strcmp(value, "prompt")) {
+ autocorrect = AUTOCORRECT_PROMPT;
} else {
int v = git_config_int(var, value);
autocorrect = (v < 0)
@@ -539,6 +543,12 @@ const char *help_unknown_cmd(const char *cmd)
read_early_config(git_unknown_cmd_config, NULL);
+ /*
+ * Disable autocorrection prompt in a non-interactive session
+ */
+ if ((autocorrect == AUTOCORRECT_PROMPT) && (!isatty(0) || !isatty(2)))
+ autocorrect = AUTOCORRECT_NEVER;
+
if (autocorrect == AUTOCORRECT_NEVER) {
fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
exit(1);
@@ -618,7 +628,16 @@ const char *help_unknown_cmd(const char *cmd)
_("Continuing under the assumption that "
"you meant '%s'."),
assumed);
- else {
+ else if (autocorrect == AUTOCORRECT_PROMPT) {
+ char *answer;
+ struct strbuf msg = STRBUF_INIT;
+ strbuf_addf(&msg, _("Run '%s' instead? (y/N)"), assumed);
+ answer = git_prompt(msg.buf, PROMPT_ECHO);
+ strbuf_release(&msg);
+ if (!(starts_with(answer, "y") ||
+ starts_with(answer, "Y")))
+ exit(1);
+ } else {
fprintf_ln(stderr,
_("Continuing in %0.1f seconds, "
"assuming that you meant '%s'."),
diff --git a/ls-refs.c b/ls-refs.c
index 88f6c3f60d..5ad521c6c7 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -65,6 +65,7 @@ struct ls_refs_data {
unsigned peel;
unsigned symrefs;
struct strvec prefixes;
+ struct strbuf buf;
unsigned unborn : 1;
};
@@ -73,7 +74,8 @@ static int send_ref(const char *refname, const struct object_id *oid,
{
struct ls_refs_data *data = cb_data;
const char *refname_nons = strip_namespace(refname);
- struct strbuf refline = STRBUF_INIT;
+
+ strbuf_reset(&data->buf);
if (ref_is_hidden(refname_nons, refname))
return 0;
@@ -82,9 +84,9 @@ static int send_ref(const char *refname, const struct object_id *oid,
return 0;
if (oid)
- strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
+ strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons);
else
- strbuf_addf(&refline, "unborn %s", refname_nons);
+ strbuf_addf(&data->buf, "unborn %s", refname_nons);
if (data->symrefs && flag & REF_ISSYMREF) {
struct object_id unused;
const char *symref_target = resolve_ref_unsafe(refname, 0,
@@ -94,20 +96,19 @@ static int send_ref(const char *refname, const struct object_id *oid,
if (!symref_target)
die("'%s' is a symref but it is not?", refname);
- strbuf_addf(&refline, " symref-target:%s",
+ strbuf_addf(&data->buf, " symref-target:%s",
strip_namespace(symref_target));
}
if (data->peel && oid) {
struct object_id peeled;
if (!peel_iterated_oid(oid, &peeled))
- strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
+ strbuf_addf(&data->buf, " peeled:%s", oid_to_hex(&peeled));
}
- strbuf_addch(&refline, '\n');
- packet_write(1, refline.buf, refline.len);
+ strbuf_addch(&data->buf, '\n');
+ packet_fwrite(stdout, data->buf.buf, data->buf.len);
- strbuf_release(&refline);
return 0;
}
@@ -145,6 +146,7 @@ int ls_refs(struct repository *r, struct strvec *keys,
memset(&data, 0, sizeof(data));
strvec_init(&data.prefixes);
+ strbuf_init(&data.buf, 0);
ensure_config_read();
git_config(ls_refs_config, NULL);
@@ -171,8 +173,9 @@ int ls_refs(struct repository *r, struct strvec *keys,
strvec_push(&data.prefixes, "");
for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v,
send_ref, &data, 0);
- packet_flush(1);
+ packet_fflush(stdout);
strvec_clear(&data.prefixes);
+ strbuf_release(&data.buf);
return 0;
}
diff --git a/mailmap.c b/mailmap.c
index 462b395634..40ce152024 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -37,6 +37,7 @@ static void free_mailmap_info(void *p, const char *s)
s, debug_str(mi->name), debug_str(mi->email));
free(mi->name);
free(mi->email);
+ free(mi);
}
static void free_mailmap_entry(void *p, const char *s)
@@ -52,6 +53,7 @@ static void free_mailmap_entry(void *p, const char *s)
me->namemap.strdup_strings = 1;
string_list_clear_func(&me->namemap, free_mailmap_info);
+ free(me);
}
/*
diff --git a/merge-recursive.c b/merge-recursive.c
index 3355d50e8a..840599fd53 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -55,10 +55,7 @@ static int path_hashmap_cmp(const void *cmp_data,
a = container_of(eptr, const struct path_hashmap_entry, e);
b = container_of(entry_or_key, const struct path_hashmap_entry, e);
- if (ignore_case)
- return strcasecmp(a->path, key ? key : b->path);
- else
- return strcmp(a->path, key ? key : b->path);
+ return fspathcmp(a->path, key ? key : b->path);
}
/*
diff --git a/midx.c b/midx.c
index 321c6fdd2f..864034a6ad 100644
--- a/midx.c
+++ b/midx.c
@@ -13,6 +13,10 @@
#include "repository.h"
#include "chunk-format.h"
#include "pack.h"
+#include "pack-bitmap.h"
+#include "refs.h"
+#include "revision.h"
+#include "list-objects.h"
#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
#define MIDX_VERSION 1
@@ -48,12 +52,12 @@ static uint8_t oid_version(void)
}
}
-static const unsigned char *get_midx_checksum(struct multi_pack_index *m)
+const unsigned char *get_midx_checksum(struct multi_pack_index *m)
{
return m->data + m->data_len - the_hash_algo->rawsz;
}
-static char *get_midx_filename(const char *object_dir)
+char *get_midx_filename(const char *object_dir)
{
return xstrfmt("%s/pack/multi-pack-index", object_dir);
}
@@ -195,6 +199,8 @@ void close_midx(struct multi_pack_index *m)
if (!m)
return;
+ close_midx(m->next);
+
munmap((unsigned char *)m->data, m->data_len);
for (i = 0; i < m->num_packs; i++) {
@@ -203,6 +209,7 @@ void close_midx(struct multi_pack_index *m)
}
FREE_AND_NULL(m->packs);
FREE_AND_NULL(m->pack_names);
+ free(m);
}
int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id)
@@ -882,7 +889,7 @@ static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash,
strbuf_release(&buf);
}
-static void clear_midx_files_ext(struct repository *r, const char *ext,
+static void clear_midx_files_ext(const char *object_dir, const char *ext,
unsigned char *keep_hash);
static int midx_checksum_valid(struct multi_pack_index *m)
@@ -890,7 +897,167 @@ static int midx_checksum_valid(struct multi_pack_index *m)
return hashfile_checksum_valid(m->data, m->data_len);
}
-static int write_midx_internal(const char *object_dir, struct multi_pack_index *m,
+static void prepare_midx_packing_data(struct packing_data *pdata,
+ struct write_midx_context *ctx)
+{
+ uint32_t i;
+
+ memset(pdata, 0, sizeof(struct packing_data));
+ prepare_packing_data(the_repository, pdata);
+
+ for (i = 0; i < ctx->entries_nr; i++) {
+ struct pack_midx_entry *from = &ctx->entries[ctx->pack_order[i]];
+ struct object_entry *to = packlist_alloc(pdata, &from->oid);
+
+ oe_set_in_pack(pdata, to,
+ ctx->info[ctx->pack_perm[from->pack_int_id]].p);
+ }
+}
+
+static int add_ref_to_pending(const char *refname,
+ const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ struct rev_info *revs = (struct rev_info*)cb_data;
+ struct object *object;
+
+ if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {
+ warning("symbolic ref is dangling: %s", refname);
+ return 0;
+ }
+
+ object = parse_object_or_die(oid, refname);
+ if (object->type != OBJ_COMMIT)
+ return 0;
+
+ add_pending_object(revs, object, "");
+ if (bitmap_is_preferred_refname(revs->repo, refname))
+ object->flags |= NEEDS_BITMAP;
+ return 0;
+}
+
+struct bitmap_commit_cb {
+ struct commit **commits;
+ size_t commits_nr, commits_alloc;
+
+ struct write_midx_context *ctx;
+};
+
+static const struct object_id *bitmap_oid_access(size_t index,
+ const void *_entries)
+{
+ const struct pack_midx_entry *entries = _entries;
+ return &entries[index].oid;
+}
+
+static void bitmap_show_commit(struct commit *commit, void *_data)
+{
+ struct bitmap_commit_cb *data = _data;
+ int pos = oid_pos(&commit->object.oid, data->ctx->entries,
+ data->ctx->entries_nr,
+ bitmap_oid_access);
+ if (pos < 0)
+ return;
+
+ ALLOC_GROW(data->commits, data->commits_nr + 1, data->commits_alloc);
+ data->commits[data->commits_nr++] = commit;
+}
+
+static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p,
+ struct write_midx_context *ctx)
+{
+ struct rev_info revs;
+ struct bitmap_commit_cb cb = {0};
+
+ cb.ctx = ctx;
+
+ repo_init_revisions(the_repository, &revs, NULL);
+ setup_revisions(0, NULL, &revs, NULL);
+ for_each_ref(add_ref_to_pending, &revs);
+
+ /*
+ * Skipping promisor objects here is intentional, since it only excludes
+ * them from the list of reachable commits that we want to select from
+ * when computing the selection of MIDX'd commits to receive bitmaps.
+ *
+ * Reachability bitmaps do require that their objects be closed under
+ * reachability, but fetching any objects missing from promisors at this
+ * point is too late. But, if one of those objects can be reached from
+ * an another object that is included in the bitmap, then we will
+ * complain later that we don't have reachability closure (and fail
+ * appropriately).
+ */
+ fetch_if_missing = 0;
+ revs.exclude_promisor_objects = 1;
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+
+ traverse_commit_list(&revs, bitmap_show_commit, NULL, &cb);
+ if (indexed_commits_nr_p)
+ *indexed_commits_nr_p = cb.commits_nr;
+
+ return cb.commits;
+}
+
+static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
+ struct write_midx_context *ctx,
+ unsigned flags)
+{
+ struct packing_data pdata;
+ struct pack_idx_entry **index;
+ struct commit **commits = NULL;
+ uint32_t i, commits_nr;
+ char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
+ int ret;
+
+ prepare_midx_packing_data(&pdata, ctx);
+
+ commits = find_commits_for_midx_bitmap(&commits_nr, ctx);
+
+ /*
+ * Build the MIDX-order index based on pdata.objects (which is already
+ * in MIDX order; c.f., 'midx_pack_order_cmp()' for the definition of
+ * this order).
+ */
+ ALLOC_ARRAY(index, pdata.nr_objects);
+ for (i = 0; i < pdata.nr_objects; i++)
+ index[i] = &pdata.objects[i].idx;
+
+ bitmap_writer_show_progress(flags & MIDX_PROGRESS);
+ bitmap_writer_build_type_index(&pdata, index, pdata.nr_objects);
+
+ /*
+ * bitmap_writer_finish expects objects in lex order, but pack_order
+ * gives us exactly that. use it directly instead of re-sorting the
+ * array.
+ *
+ * This changes the order of objects in 'index' between
+ * bitmap_writer_build_type_index and bitmap_writer_finish.
+ *
+ * The same re-ordering takes place in the single-pack bitmap code via
+ * write_idx_file(), which is called by finish_tmp_packfile(), which
+ * happens between bitmap_writer_build_type_index() and
+ * bitmap_writer_finish().
+ */
+ for (i = 0; i < pdata.nr_objects; i++)
+ index[ctx->pack_order[i]] = &pdata.objects[i].idx;
+
+ bitmap_writer_select_commits(commits, commits_nr, -1);
+ ret = bitmap_writer_build(&pdata);
+ if (ret < 0)
+ goto cleanup;
+
+ bitmap_writer_set_checksum(midx_hash);
+ bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, 0);
+
+cleanup:
+ free(index);
+ free(bitmap_name);
+ return ret;
+}
+
+static int write_midx_internal(const char *object_dir,
struct string_list *packs_to_drop,
const char *preferred_pack_name,
unsigned flags)
@@ -901,20 +1068,26 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
struct hashfile *f = NULL;
struct lock_file lk;
struct write_midx_context ctx = { 0 };
+ struct multi_pack_index *cur;
int pack_name_concat_len = 0;
int dropped_packs = 0;
int result = 0;
struct chunkfile *cf;
+ /* Ensure the given object_dir is local, or a known alternate. */
+ find_odb(the_repository, object_dir);
+
midx_name = get_midx_filename(object_dir);
if (safe_create_leading_directories(midx_name))
die_errno(_("unable to create leading directories of %s"),
midx_name);
- if (m)
- ctx.m = m;
- else
- ctx.m = load_multi_pack_index(object_dir, 1);
+ for (cur = get_multi_pack_index(the_repository); cur; cur = cur->next) {
+ if (!strcmp(object_dir, cur->object_dir)) {
+ ctx.m = cur;
+ break;
+ }
+ }
if (ctx.m && !midx_checksum_valid(ctx.m)) {
warning(_("ignoring existing multi-pack-index; checksum mismatch"));
@@ -932,8 +1105,27 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
ctx.info[ctx.nr].orig_pack_int_id = i;
ctx.info[ctx.nr].pack_name = xstrdup(ctx.m->pack_names[i]);
- ctx.info[ctx.nr].p = NULL;
+ ctx.info[ctx.nr].p = ctx.m->packs[i];
ctx.info[ctx.nr].expired = 0;
+
+ if (flags & MIDX_WRITE_REV_INDEX) {
+ /*
+ * If generating a reverse index, need to have
+ * packed_git's loaded to compare their
+ * mtimes and object count.
+ */
+ if (prepare_midx_pack(the_repository, ctx.m, i)) {
+ error(_("could not load pack"));
+ result = 1;
+ goto cleanup;
+ }
+
+ if (open_pack_index(ctx.m->packs[i]))
+ die(_("could not open index for %s"),
+ ctx.m->packs[i]->pack_name);
+ ctx.info[ctx.nr].p = ctx.m->packs[i];
+ }
+
ctx.nr++;
}
}
@@ -947,18 +1139,89 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx);
stop_progress(&ctx.progress);
- if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop)
- goto cleanup;
+ if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop) {
+ struct bitmap_index *bitmap_git;
+ int bitmap_exists;
+ int want_bitmap = flags & MIDX_WRITE_BITMAP;
+
+ bitmap_git = prepare_midx_bitmap_git(ctx.m);
+ bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git);
+ free_bitmap_index(bitmap_git);
+
+ if (bitmap_exists || !want_bitmap) {
+ /*
+ * The correct MIDX already exists, and so does a
+ * corresponding bitmap (or one wasn't requested).
+ */
+ if (!want_bitmap)
+ clear_midx_files_ext(object_dir, ".bitmap",
+ NULL);
+ goto cleanup;
+ }
+ }
- ctx.preferred_pack_idx = -1;
if (preferred_pack_name) {
+ int found = 0;
for (i = 0; i < ctx.nr; i++) {
if (!cmp_idx_or_pack_name(preferred_pack_name,
ctx.info[i].pack_name)) {
ctx.preferred_pack_idx = i;
+ found = 1;
break;
}
}
+
+ if (!found)
+ warning(_("unknown preferred pack: '%s'"),
+ preferred_pack_name);
+ } else if (ctx.nr &&
+ (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) {
+ struct packed_git *oldest = ctx.info[ctx.preferred_pack_idx].p;
+ ctx.preferred_pack_idx = 0;
+
+ if (packs_to_drop && packs_to_drop->nr)
+ BUG("cannot write a MIDX bitmap during expiration");
+
+ /*
+ * set a preferred pack when writing a bitmap to ensure that
+ * the pack from which the first object is selected in pseudo
+ * pack-order has all of its objects selected from that pack
+ * (and not another pack containing a duplicate)
+ */
+ for (i = 1; i < ctx.nr; i++) {
+ struct packed_git *p = ctx.info[i].p;
+
+ if (!oldest->num_objects || p->mtime < oldest->mtime) {
+ oldest = p;
+ ctx.preferred_pack_idx = i;
+ }
+ }
+
+ if (!oldest->num_objects) {
+ /*
+ * If all packs are empty; unset the preferred index.
+ * This is acceptable since there will be no duplicate
+ * objects to resolve, so the preferred value doesn't
+ * matter.
+ */
+ ctx.preferred_pack_idx = -1;
+ }
+ } else {
+ /*
+ * otherwise don't mark any pack as preferred to avoid
+ * interfering with expiration logic below
+ */
+ ctx.preferred_pack_idx = -1;
+ }
+
+ if (ctx.preferred_pack_idx > -1) {
+ struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p;
+ if (!preferred->num_objects) {
+ error(_("cannot select preferred pack %s with no objects"),
+ preferred->pack_name);
+ result = 1;
+ goto cleanup;
+ }
}
ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr,
@@ -1029,11 +1292,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
ctx.info, ctx.nr,
sizeof(*ctx.info),
idx_or_pack_name_cmp);
-
- if (!preferred)
- warning(_("unknown preferred pack: '%s'"),
- preferred_pack_name);
- else {
+ if (preferred) {
uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id];
if (perm == PACK_EXPIRED)
warning(_("preferred pack '%s' is expired"),
@@ -1048,9 +1307,6 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk));
- if (ctx.m)
- close_midx(ctx.m);
-
if (ctx.nr - dropped_packs == 0) {
error(_("no pack files to index."));
result = 1;
@@ -1081,15 +1337,27 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index *
finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
free_chunkfile(cf);
- if (flags & MIDX_WRITE_REV_INDEX)
+ if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
ctx.pack_order = midx_pack_order(&ctx);
if (flags & MIDX_WRITE_REV_INDEX)
write_midx_reverse_index(midx_name, midx_hash, &ctx);
- clear_midx_files_ext(the_repository, ".rev", midx_hash);
+ if (flags & MIDX_WRITE_BITMAP) {
+ if (write_midx_bitmap(midx_name, midx_hash, &ctx, flags) < 0) {
+ error(_("could not write multi-pack bitmap"));
+ result = 1;
+ goto cleanup;
+ }
+ }
+
+ if (ctx.m)
+ close_object_store(the_repository->objects);
commit_lock_file(&lk);
+ clear_midx_files_ext(object_dir, ".bitmap", midx_hash);
+ clear_midx_files_ext(object_dir, ".rev", midx_hash);
+
cleanup:
for (i = 0; i < ctx.nr; i++) {
if (ctx.info[i].p) {
@@ -1104,6 +1372,7 @@ cleanup:
free(ctx.pack_perm);
free(ctx.pack_order);
free(midx_name);
+
return result;
}
@@ -1111,8 +1380,7 @@ int write_midx_file(const char *object_dir,
const char *preferred_pack_name,
unsigned flags)
{
- return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name,
- flags);
+ return write_midx_internal(object_dir, NULL, preferred_pack_name, flags);
}
struct clear_midx_data {
@@ -1135,7 +1403,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len,
die_errno(_("failed to remove %s"), full_path);
}
-static void clear_midx_files_ext(struct repository *r, const char *ext,
+static void clear_midx_files_ext(const char *object_dir, const char *ext,
unsigned char *keep_hash)
{
struct clear_midx_data data;
@@ -1146,7 +1414,7 @@ static void clear_midx_files_ext(struct repository *r, const char *ext,
hash_to_hex(keep_hash), ext);
data.ext = ext;
- for_each_file_in_pack_dir(r->objects->odb->path,
+ for_each_file_in_pack_dir(object_dir,
clear_midx_file_ext,
&data);
@@ -1165,7 +1433,8 @@ void clear_midx_file(struct repository *r)
if (remove_path(midx))
die(_("failed to clear multi-pack-index at %s"), midx);
- clear_midx_files_ext(r, ".rev", NULL);
+ clear_midx_files_ext(r->objects->odb->path, ".bitmap", NULL);
+ clear_midx_files_ext(r->objects->odb->path, ".rev", NULL);
free(midx);
}
@@ -1390,8 +1659,10 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
free(count);
- if (packs_to_drop.nr)
- result = write_midx_internal(object_dir, m, &packs_to_drop, NULL, flags);
+ if (packs_to_drop.nr) {
+ result = write_midx_internal(object_dir, &packs_to_drop, NULL, flags);
+ m = NULL;
+ }
string_list_clear(&packs_to_drop, 0);
return result;
@@ -1580,7 +1851,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
goto cleanup;
}
- result = write_midx_internal(object_dir, m, NULL, NULL, flags);
+ result = write_midx_internal(object_dir, NULL, NULL, flags);
m = NULL;
cleanup:
diff --git a/midx.h b/midx.h
index 8684cf0fef..aa3da557bb 100644
--- a/midx.h
+++ b/midx.h
@@ -8,6 +8,8 @@ struct pack_entry;
struct repository;
#define GIT_TEST_MULTI_PACK_INDEX "GIT_TEST_MULTI_PACK_INDEX"
+#define GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP \
+ "GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP"
struct multi_pack_index {
struct multi_pack_index *next;
@@ -41,7 +43,10 @@ struct multi_pack_index {
#define MIDX_PROGRESS (1 << 0)
#define MIDX_WRITE_REV_INDEX (1 << 1)
+#define MIDX_WRITE_BITMAP (1 << 2)
+const unsigned char *get_midx_checksum(struct multi_pack_index *m);
+char *get_midx_filename(const char *object_dir);
char *get_midx_rev_filename(struct multi_pack_index *m);
struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);
diff --git a/notes-merge.c b/notes-merge.c
index 46c1f7c7f1..b4a3a903e8 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -273,7 +273,7 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
*/
if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
!is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
- if (advice_resolve_conflict)
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your previous "
"notes merge (%s exists).\nPlease, use "
"'git notes merge --commit' or 'git notes "
diff --git a/object-file.c b/object-file.c
index a8be899481..c1f7ece055 100644
--- a/object-file.c
+++ b/object-file.c
@@ -32,6 +32,7 @@
#include "packfile.h"
#include "object-store.h"
#include "promisor-remote.h"
+#include "submodule.h"
/* The maximum size for an object header. */
#define MAX_HEADER_LEN 32
@@ -820,6 +821,27 @@ out:
return ref_git;
}
+struct object_directory *find_odb(struct repository *r, const char *obj_dir)
+{
+ struct object_directory *odb;
+ char *obj_dir_real = real_pathdup(obj_dir, 1);
+ struct strbuf odb_path_real = STRBUF_INIT;
+
+ prepare_alt_odb(r);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ strbuf_realpath(&odb_path_real, odb->path, 1);
+ if (!strcmp(obj_dir_real, odb_path_real.buf))
+ break;
+ }
+
+ free(obj_dir_real);
+ strbuf_release(&odb_path_real);
+
+ if (!odb)
+ die(_("could not find object directory matching %s"), obj_dir);
+ return odb;
+}
+
static void fill_alternate_refs_command(struct child_process *cmd,
const char *repo_path)
{
@@ -1592,6 +1614,10 @@ static int do_oid_object_info_extended(struct repository *r,
break;
}
+ if (register_all_submodule_odb_as_alternates())
+ /* We added some alternates; retry */
+ continue;
+
/* Check if it is a missing object */
if (fetch_if_missing && repo_has_promisor_remote(r) &&
!already_retried &&
diff --git a/object-name.c b/object-name.c
index 3263c19457..fdff4601b2 100644
--- a/object-name.c
+++ b/object-name.c
@@ -806,7 +806,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
if (refs_found > 0) {
warning(warn_msg, len, str);
- if (advice_object_name_warning)
+ if (advice_enabled(ADVICE_OBJECT_NAME_WARNING))
fprintf(stderr, "%s\n", _(object_name_msg));
}
free(real_ref);
diff --git a/object-store.h b/object-store.h
index d24915ced1..ebc55274e6 100644
--- a/object-store.h
+++ b/object-store.h
@@ -38,6 +38,7 @@ KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
void prepare_alt_odb(struct repository *r);
char *compute_alternate_path(const char *path, struct strbuf *err);
+struct object_directory *find_odb(struct repository *r, const char *obj_dir);
typedef int alt_odb_fn(struct object_directory *, void *);
int foreach_alt_odb(alt_odb_fn, void*);
typedef void alternate_ref_fn(const struct object_id *oid, void *);
@@ -455,6 +456,12 @@ enum for_each_object_flags {
* Visit objects within a pack in packfile order rather than .idx order
*/
FOR_EACH_OBJECT_PACK_ORDER = (1<<2),
+
+ /* Only iterate over packs that are not marked as kept in-core. */
+ FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3),
+
+ /* Only iterate over packs that do not have .keep files. */
+ FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
};
/*
diff --git a/object.h b/object.h
index 3b38c9cc98..549f2d256b 100644
--- a/object.h
+++ b/object.h
@@ -75,7 +75,6 @@ struct object_array {
* builtin/fsck.c: 0--3
* builtin/gc.c: 0
* builtin/index-pack.c: 2021
- * builtin/pack-objects.c: 20
* builtin/reflog.c: 10--12
* builtin/show-branch.c: 0-------------------------------------------26
* builtin/unpack-objects.c: 2021
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index 88d9e696a5..9c55c1531e 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -48,7 +48,7 @@ void bitmap_writer_show_progress(int show)
}
/**
- * Build the initial type index for the packfile
+ * Build the initial type index for the packfile or multi-pack-index
*/
void bitmap_writer_build_type_index(struct packing_data *to_pack,
struct pack_idx_entry **index,
@@ -125,15 +125,20 @@ static inline void push_bitmapped_commit(struct commit *commit)
writer.selected_nr++;
}
-static uint32_t find_object_pos(const struct object_id *oid)
+static uint32_t find_object_pos(const struct object_id *oid, int *found)
{
struct object_entry *entry = packlist_find(writer.to_pack, oid);
if (!entry) {
- die("Failed to write bitmap index. Packfile doesn't have full closure "
+ if (found)
+ *found = 0;
+ warning("Failed to write bitmap index. Packfile doesn't have full closure "
"(object %s is missing)", oid_to_hex(oid));
+ return 0;
}
+ if (found)
+ *found = 1;
return oe_in_pack_pos(writer.to_pack, entry);
}
@@ -331,9 +336,10 @@ static void bitmap_builder_clear(struct bitmap_builder *bb)
bb->commits_nr = bb->commits_alloc = 0;
}
-static void fill_bitmap_tree(struct bitmap *bitmap,
- struct tree *tree)
+static int fill_bitmap_tree(struct bitmap *bitmap,
+ struct tree *tree)
{
+ int found;
uint32_t pos;
struct tree_desc desc;
struct name_entry entry;
@@ -342,9 +348,11 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
* If our bit is already set, then there is nothing to do. Both this
* tree and all of its children will be set.
*/
- pos = find_object_pos(&tree->object.oid);
+ pos = find_object_pos(&tree->object.oid, &found);
+ if (!found)
+ return -1;
if (bitmap_get(bitmap, pos))
- return;
+ return 0;
bitmap_set(bitmap, pos);
if (parse_tree(tree) < 0)
@@ -355,11 +363,15 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
while (tree_entry(&desc, &entry)) {
switch (object_type(entry.mode)) {
case OBJ_TREE:
- fill_bitmap_tree(bitmap,
- lookup_tree(the_repository, &entry.oid));
+ if (fill_bitmap_tree(bitmap,
+ lookup_tree(the_repository, &entry.oid)) < 0)
+ return -1;
break;
case OBJ_BLOB:
- bitmap_set(bitmap, find_object_pos(&entry.oid));
+ pos = find_object_pos(&entry.oid, &found);
+ if (!found)
+ return -1;
+ bitmap_set(bitmap, pos);
break;
default:
/* Gitlink, etc; not reachable */
@@ -368,15 +380,18 @@ static void fill_bitmap_tree(struct bitmap *bitmap,
}
free_tree_buffer(tree);
+ return 0;
}
-static void fill_bitmap_commit(struct bb_commit *ent,
- struct commit *commit,
- struct prio_queue *queue,
- struct prio_queue *tree_queue,
- struct bitmap_index *old_bitmap,
- const uint32_t *mapping)
+static int fill_bitmap_commit(struct bb_commit *ent,
+ struct commit *commit,
+ struct prio_queue *queue,
+ struct prio_queue *tree_queue,
+ struct bitmap_index *old_bitmap,
+ const uint32_t *mapping)
{
+ int found;
+ uint32_t pos;
if (!ent->bitmap)
ent->bitmap = bitmap_new();
@@ -401,11 +416,16 @@ static void fill_bitmap_commit(struct bb_commit *ent,
* Mark ourselves and queue our tree. The commit
* walk ensures we cover all parents.
*/
- bitmap_set(ent->bitmap, find_object_pos(&c->object.oid));
+ pos = find_object_pos(&c->object.oid, &found);
+ if (!found)
+ return -1;
+ bitmap_set(ent->bitmap, pos);
prio_queue_put(tree_queue, get_commit_tree(c));
for (p = c->parents; p; p = p->next) {
- int pos = find_object_pos(&p->item->object.oid);
+ pos = find_object_pos(&p->item->object.oid, &found);
+ if (!found)
+ return -1;
if (!bitmap_get(ent->bitmap, pos)) {
bitmap_set(ent->bitmap, pos);
prio_queue_put(queue, p->item);
@@ -413,8 +433,12 @@ static void fill_bitmap_commit(struct bb_commit *ent,
}
}
- while (tree_queue->nr)
- fill_bitmap_tree(ent->bitmap, prio_queue_get(tree_queue));
+ while (tree_queue->nr) {
+ if (fill_bitmap_tree(ent->bitmap,
+ prio_queue_get(tree_queue)) < 0)
+ return -1;
+ }
+ return 0;
}
static void store_selected(struct bb_commit *ent, struct commit *commit)
@@ -432,7 +456,7 @@ static void store_selected(struct bb_commit *ent, struct commit *commit)
kh_value(writer.bitmaps, hash_pos) = stored;
}
-void bitmap_writer_build(struct packing_data *to_pack)
+int bitmap_writer_build(struct packing_data *to_pack)
{
struct bitmap_builder bb;
size_t i;
@@ -441,6 +465,7 @@ void bitmap_writer_build(struct packing_data *to_pack)
struct prio_queue tree_queue = { NULL };
struct bitmap_index *old_bitmap;
uint32_t *mapping;
+ int closed = 1; /* until proven otherwise */
writer.bitmaps = kh_init_oid_map();
writer.to_pack = to_pack;
@@ -463,8 +488,11 @@ void bitmap_writer_build(struct packing_data *to_pack)
struct commit *child;
int reused = 0;
- fill_bitmap_commit(ent, commit, &queue, &tree_queue,
- old_bitmap, mapping);
+ if (fill_bitmap_commit(ent, commit, &queue, &tree_queue,
+ old_bitmap, mapping) < 0) {
+ closed = 0;
+ break;
+ }
if (ent->selected) {
store_selected(ent, commit);
@@ -492,6 +520,7 @@ void bitmap_writer_build(struct packing_data *to_pack)
clear_prio_queue(&queue);
clear_prio_queue(&tree_queue);
bitmap_builder_clear(&bb);
+ free_bitmap_index(old_bitmap);
free(mapping);
trace2_region_leave("pack-bitmap-write", "building_bitmaps_total",
@@ -499,7 +528,9 @@ void bitmap_writer_build(struct packing_data *to_pack)
stop_progress(&writer.progress);
- compute_xor_offsets();
+ if (closed)
+ compute_xor_offsets();
+ return closed ? 0 : -1;
}
/**
diff --git a/pack-bitmap.c b/pack-bitmap.c
index d999616c9e..8504110a4d 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -13,6 +13,7 @@
#include "repository.h"
#include "object-store.h"
#include "list-objects-filter-options.h"
+#include "midx.h"
#include "config.h"
/*
@@ -35,8 +36,15 @@ struct stored_bitmap {
* the active bitmap index is the largest one.
*/
struct bitmap_index {
- /* Packfile to which this bitmap index belongs to */
+ /*
+ * The pack or multi-pack index (MIDX) that this bitmap index belongs
+ * to.
+ *
+ * Exactly one of these must be non-NULL; this specifies the object
+ * order used to interpret this bitmap.
+ */
struct packed_git *pack;
+ struct multi_pack_index *midx;
/*
* Mark the first `reuse_objects` in the packfile as reused:
@@ -71,6 +79,9 @@ struct bitmap_index {
/* If not NULL, this is a name-hash cache pointing into map. */
uint32_t *hashes;
+ /* The checksum of the packfile or MIDX; points into map. */
+ const unsigned char *checksum;
+
/*
* Extended index.
*
@@ -136,6 +147,13 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index)
return b;
}
+static uint32_t bitmap_num_objects(struct bitmap_index *index)
+{
+ if (index->midx)
+ return index->midx->num_objects;
+ return index->pack->num_objects;
+}
+
static int load_bitmap_header(struct bitmap_index *index)
{
struct bitmap_disk_header *header = (void *)index->map;
@@ -154,7 +172,7 @@ static int load_bitmap_header(struct bitmap_index *index)
/* Parse known bitmap format options */
{
uint32_t flags = ntohs(header->options);
- size_t cache_size = st_mult(index->pack->num_objects, sizeof(uint32_t));
+ size_t cache_size = st_mult(bitmap_num_objects(index), sizeof(uint32_t));
unsigned char *index_end = index->map + index->map_size - the_hash_algo->rawsz;
if ((flags & BITMAP_OPT_FULL_DAG) == 0)
@@ -170,6 +188,7 @@ static int load_bitmap_header(struct bitmap_index *index)
}
index->entry_count = ntohl(header->entry_count);
+ index->checksum = header->checksum;
index->map_pos += header_size;
return 0;
}
@@ -218,6 +237,15 @@ static inline uint8_t read_u8(const unsigned char *buffer, size_t *pos)
#define MAX_XOR_OFFSET 160
+static int nth_bitmap_object_oid(struct bitmap_index *index,
+ struct object_id *oid,
+ uint32_t n)
+{
+ if (index->midx)
+ return nth_midxed_object_oid(oid, index->midx, n) ? 0 : -1;
+ return nth_packed_object_id(oid, index->pack, n);
+}
+
static int load_bitmap_entries_v1(struct bitmap_index *index)
{
uint32_t i;
@@ -237,7 +265,7 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
xor_offset = read_u8(index->map, &index->map_pos);
flags = read_u8(index->map, &index->map_pos);
- if (nth_packed_object_id(&oid, index->pack, commit_idx_pos) < 0)
+ if (nth_bitmap_object_oid(index, &oid, commit_idx_pos) < 0)
return error("corrupt ewah bitmap: commit index %u out of range",
(unsigned)commit_idx_pos);
@@ -262,7 +290,14 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
return 0;
}
-static char *pack_bitmap_filename(struct packed_git *p)
+char *midx_bitmap_filename(struct multi_pack_index *midx)
+{
+ return xstrfmt("%s-%s.bitmap",
+ get_midx_filename(midx->object_dir),
+ hash_to_hex(get_midx_checksum(midx)));
+}
+
+char *pack_bitmap_filename(struct packed_git *p)
{
size_t len;
@@ -271,6 +306,57 @@ static char *pack_bitmap_filename(struct packed_git *p)
return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
}
+static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
+ struct multi_pack_index *midx)
+{
+ struct stat st;
+ char *idx_name = midx_bitmap_filename(midx);
+ int fd = git_open(idx_name);
+
+ free(idx_name);
+
+ if (fd < 0)
+ return -1;
+
+ if (fstat(fd, &st)) {
+ close(fd);
+ return -1;
+ }
+
+ if (bitmap_git->pack || bitmap_git->midx) {
+ /* ignore extra bitmap file; we can only handle one */
+ warning("ignoring extra bitmap file: %s",
+ get_midx_filename(midx->object_dir));
+ close(fd);
+ return -1;
+ }
+
+ bitmap_git->midx = midx;
+ bitmap_git->map_size = xsize_t(st.st_size);
+ bitmap_git->map_pos = 0;
+ bitmap_git->map = xmmap(NULL, bitmap_git->map_size, PROT_READ,
+ MAP_PRIVATE, fd, 0);
+ close(fd);
+
+ if (load_bitmap_header(bitmap_git) < 0)
+ goto cleanup;
+
+ if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum))
+ goto cleanup;
+
+ if (load_midx_revindex(bitmap_git->midx) < 0) {
+ warning(_("multi-pack bitmap is missing required reverse index"));
+ goto cleanup;
+ }
+ return 0;
+
+cleanup:
+ munmap(bitmap_git->map, bitmap_git->map_size);
+ bitmap_git->map_size = 0;
+ bitmap_git->map = NULL;
+ return -1;
+}
+
static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git *packfile)
{
int fd;
@@ -292,7 +378,8 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
return -1;
}
- if (bitmap_git->pack) {
+ if (bitmap_git->pack || bitmap_git->midx) {
+ /* ignore extra bitmap file; we can only handle one */
warning("ignoring extra bitmap file: %s", packfile->pack_name);
close(fd);
return -1;
@@ -319,13 +406,39 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
return 0;
}
-static int load_pack_bitmap(struct bitmap_index *bitmap_git)
+static int load_reverse_index(struct bitmap_index *bitmap_git)
+{
+ if (bitmap_is_midx(bitmap_git)) {
+ uint32_t i;
+ int ret;
+
+ /*
+ * The multi-pack-index's .rev file is already loaded via
+ * open_pack_bitmap_1().
+ *
+ * But we still need to open the individual pack .rev files,
+ * since we will need to make use of them in pack-objects.
+ */
+ for (i = 0; i < bitmap_git->midx->num_packs; i++) {
+ if (prepare_midx_pack(the_repository, bitmap_git->midx, i))
+ die(_("load_reverse_index: could not open pack"));
+ ret = load_pack_revindex(bitmap_git->midx->packs[i]);
+ if (ret)
+ return ret;
+ }
+ return 0;
+ }
+ return load_pack_revindex(bitmap_git->pack);
+}
+
+static int load_bitmap(struct bitmap_index *bitmap_git)
{
assert(bitmap_git->map);
bitmap_git->bitmaps = kh_init_oid_map();
bitmap_git->ext_index.positions = kh_init_oid_pos();
- if (load_pack_revindex(bitmap_git->pack))
+
+ if (load_reverse_index(bitmap_git))
goto failed;
if (!(bitmap_git->commits = read_bitmap_1(bitmap_git)) ||
@@ -369,11 +482,46 @@ static int open_pack_bitmap(struct repository *r,
return ret;
}
+static int open_midx_bitmap(struct repository *r,
+ struct bitmap_index *bitmap_git)
+{
+ struct multi_pack_index *midx;
+
+ assert(!bitmap_git->map);
+
+ for (midx = get_multi_pack_index(r); midx; midx = midx->next) {
+ if (!open_midx_bitmap_1(bitmap_git, midx))
+ return 0;
+ }
+ return -1;
+}
+
+static int open_bitmap(struct repository *r,
+ struct bitmap_index *bitmap_git)
+{
+ assert(!bitmap_git->map);
+
+ if (!open_midx_bitmap(r, bitmap_git))
+ return 0;
+ return open_pack_bitmap(r, bitmap_git);
+}
+
struct bitmap_index *prepare_bitmap_git(struct repository *r)
{
struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
- if (!open_pack_bitmap(r, bitmap_git) && !load_pack_bitmap(bitmap_git))
+ if (!open_bitmap(r, bitmap_git) && !load_bitmap(bitmap_git))
+ return bitmap_git;
+
+ free_bitmap_index(bitmap_git);
+ return NULL;
+}
+
+struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx)
+{
+ struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git));
+
+ if (!open_midx_bitmap_1(bitmap_git, midx) && !load_bitmap(bitmap_git))
return bitmap_git;
free_bitmap_index(bitmap_git);
@@ -404,7 +552,7 @@ static inline int bitmap_position_extended(struct bitmap_index *bitmap_git,
if (pos < kh_end(positions)) {
int bitmap_pos = kh_value(positions, pos);
- return bitmap_pos + bitmap_git->pack->num_objects;
+ return bitmap_pos + bitmap_num_objects(bitmap_git);
}
return -1;
@@ -423,10 +571,26 @@ static inline int bitmap_position_packfile(struct bitmap_index *bitmap_git,
return pos;
}
+static int bitmap_position_midx(struct bitmap_index *bitmap_git,
+ const struct object_id *oid)
+{
+ uint32_t want, got;
+ if (!bsearch_midx(oid, bitmap_git->midx, &want))
+ return -1;
+
+ if (midx_to_pack_pos(bitmap_git->midx, want, &got) < 0)
+ return -1;
+ return got;
+}
+
static int bitmap_position(struct bitmap_index *bitmap_git,
const struct object_id *oid)
{
- int pos = bitmap_position_packfile(bitmap_git, oid);
+ int pos;
+ if (bitmap_is_midx(bitmap_git))
+ pos = bitmap_position_midx(bitmap_git, oid);
+ else
+ pos = bitmap_position_packfile(bitmap_git, oid);
return (pos >= 0) ? pos : bitmap_position_extended(bitmap_git, oid);
}
@@ -456,7 +620,7 @@ static int ext_index_add_object(struct bitmap_index *bitmap_git,
bitmap_pos = kh_value(eindex->positions, hash_pos);
}
- return bitmap_pos + bitmap_git->pack->num_objects;
+ return bitmap_pos + bitmap_num_objects(bitmap_git);
}
struct bitmap_show_data {
@@ -673,7 +837,7 @@ static void show_extended_objects(struct bitmap_index *bitmap_git,
for (i = 0; i < eindex->count; ++i) {
struct object *obj;
- if (!bitmap_get(objects, bitmap_git->pack->num_objects + i))
+ if (!bitmap_get(objects, bitmap_num_objects(bitmap_git) + i))
continue;
obj = eindex->objects[i];
@@ -737,6 +901,7 @@ static void show_objects_for_type(
continue;
for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+ struct packed_git *pack;
struct object_id oid;
uint32_t hash = 0, index_pos;
off_t ofs;
@@ -746,14 +911,28 @@ static void show_objects_for_type(
offset += ewah_bit_ctz64(word >> offset);
- index_pos = pack_pos_to_index(bitmap_git->pack, pos + offset);
- ofs = pack_pos_to_offset(bitmap_git->pack, pos + offset);
- nth_packed_object_id(&oid, bitmap_git->pack, index_pos);
+ if (bitmap_is_midx(bitmap_git)) {
+ struct multi_pack_index *m = bitmap_git->midx;
+ uint32_t pack_id;
+
+ index_pos = pack_pos_to_midx(m, pos + offset);
+ ofs = nth_midxed_offset(m, index_pos);
+ nth_midxed_object_oid(&oid, m, index_pos);
+
+ pack_id = nth_midxed_pack_int_id(m, index_pos);
+ pack = bitmap_git->midx->packs[pack_id];
+ } else {
+ index_pos = pack_pos_to_index(bitmap_git->pack, pos + offset);
+ ofs = pack_pos_to_offset(bitmap_git->pack, pos + offset);
+ nth_bitmap_object_oid(bitmap_git, &oid, index_pos);
+
+ pack = bitmap_git->pack;
+ }
if (bitmap_git->hashes)
hash = get_be32(bitmap_git->hashes + index_pos);
- show_reach(&oid, object_type, 0, hash, bitmap_git->pack, ofs);
+ show_reach(&oid, object_type, 0, hash, pack, ofs);
}
}
}
@@ -765,8 +944,13 @@ static int in_bitmapped_pack(struct bitmap_index *bitmap_git,
struct object *object = roots->item;
roots = roots->next;
- if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0)
- return 1;
+ if (bitmap_is_midx(bitmap_git)) {
+ if (bsearch_midx(&object->oid, bitmap_git->midx, NULL))
+ return 1;
+ } else {
+ if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0)
+ return 1;
+ }
}
return 0;
@@ -832,7 +1016,7 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git,
* them individually.
*/
for (i = 0; i < eindex->count; i++) {
- uint32_t pos = i + bitmap_git->pack->num_objects;
+ uint32_t pos = i + bitmap_num_objects(bitmap_git);
if (eindex->objects[i]->type == type &&
bitmap_get(to_filter, pos) &&
!bitmap_get(tips, pos))
@@ -853,23 +1037,35 @@ static void filter_bitmap_blob_none(struct bitmap_index *bitmap_git,
static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git,
uint32_t pos)
{
- struct packed_git *pack = bitmap_git->pack;
unsigned long size;
struct object_info oi = OBJECT_INFO_INIT;
oi.sizep = &size;
- if (pos < pack->num_objects) {
- off_t ofs = pack_pos_to_offset(pack, pos);
+ if (pos < bitmap_num_objects(bitmap_git)) {
+ struct packed_git *pack;
+ off_t ofs;
+
+ if (bitmap_is_midx(bitmap_git)) {
+ uint32_t midx_pos = pack_pos_to_midx(bitmap_git->midx, pos);
+ uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos);
+
+ pack = bitmap_git->midx->packs[pack_id];
+ ofs = nth_midxed_offset(bitmap_git->midx, midx_pos);
+ } else {
+ pack = bitmap_git->pack;
+ ofs = pack_pos_to_offset(pack, pos);
+ }
+
if (packed_object_info(the_repository, pack, ofs, &oi) < 0) {
struct object_id oid;
- nth_packed_object_id(&oid, pack,
- pack_pos_to_index(pack, pos));
+ nth_bitmap_object_oid(bitmap_git, &oid,
+ pack_pos_to_index(pack, pos));
die(_("unable to get size of %s"), oid_to_hex(&oid));
}
} else {
struct eindex *eindex = &bitmap_git->ext_index;
- struct object *obj = eindex->objects[pos - pack->num_objects];
+ struct object *obj = eindex->objects[pos - bitmap_num_objects(bitmap_git)];
if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0)
die(_("unable to get size of %s"), oid_to_hex(&obj->oid));
}
@@ -911,7 +1107,7 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git,
}
for (i = 0; i < eindex->count; i++) {
- uint32_t pos = i + bitmap_git->pack->num_objects;
+ uint32_t pos = i + bitmap_num_objects(bitmap_git);
if (eindex->objects[i]->type == OBJ_BLOB &&
bitmap_get(to_filter, pos) &&
!bitmap_get(tips, pos) &&
@@ -1041,7 +1237,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
/* try to open a bitmapped pack, but don't parse it yet
* because we may not need to use it */
CALLOC_ARRAY(bitmap_git, 1);
- if (open_pack_bitmap(revs->repo, bitmap_git) < 0)
+ if (open_bitmap(revs->repo, bitmap_git) < 0)
goto cleanup;
for (i = 0; i < revs->pending.nr; ++i) {
@@ -1085,7 +1281,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
* from disk. this is the point of no return; after this the rev_list
* becomes invalidated and we must perform the revwalk through bitmaps
*/
- if (load_pack_bitmap(bitmap_git) < 0)
+ if (load_bitmap(bitmap_git) < 0)
goto cleanup;
object_array_clear(&revs->pending);
@@ -1128,22 +1324,49 @@ cleanup:
return NULL;
}
-static void try_partial_reuse(struct bitmap_index *bitmap_git,
- size_t pos,
- struct bitmap *reuse,
- struct pack_window **w_curs)
+/*
+ * -1 means "stop trying further objects"; 0 means we may or may not have
+ * reused, but you can keep feeding bits.
+ */
+static int try_partial_reuse(struct packed_git *pack,
+ size_t pos,
+ struct bitmap *reuse,
+ struct pack_window **w_curs)
{
- off_t offset, header;
+ off_t offset, delta_obj_offset;
enum object_type type;
unsigned long size;
- if (pos >= bitmap_git->pack->num_objects)
- return; /* not actually in the pack */
+ /*
+ * try_partial_reuse() is called either on (a) objects in the
+ * bitmapped pack (in the case of a single-pack bitmap) or (b)
+ * objects in the preferred pack of a multi-pack bitmap.
+ * Importantly, the latter can pretend as if only a single pack
+ * exists because:
+ *
+ * - The first pack->num_objects bits of a MIDX bitmap are
+ * reserved for the preferred pack, and
+ *
+ * - Ties due to duplicate objects are always resolved in
+ * favor of the preferred pack.
+ *
+ * Therefore we do not need to ever ask the MIDX for its copy of
+ * an object by OID, since it will always select it from the
+ * preferred pack. Likewise, the selected copy of the base
+ * object for any deltas will reside in the same pack.
+ *
+ * This means that we can reuse pos when looking up the bit in
+ * the reuse bitmap, too, since bits corresponding to the
+ * preferred pack precede all bits from other packs.
+ */
+
+ if (pos >= pack->num_objects)
+ return -1; /* not actually in the pack or MIDX preferred pack */
- offset = header = pack_pos_to_offset(bitmap_git->pack, pos);
- type = unpack_object_header(bitmap_git->pack, w_curs, &offset, &size);
+ offset = delta_obj_offset = pack_pos_to_offset(pack, pos);
+ type = unpack_object_header(pack, w_curs, &offset, &size);
if (type < 0)
- return; /* broken packfile, punt */
+ return -1; /* broken packfile, punt */
if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) {
off_t base_offset;
@@ -1157,12 +1380,12 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
* and the normal slow path will complain about it in
* more detail.
*/
- base_offset = get_delta_base(bitmap_git->pack, w_curs,
- &offset, type, header);
+ base_offset = get_delta_base(pack, w_curs, &offset, type,
+ delta_obj_offset);
if (!base_offset)
- return;
- if (offset_to_pack_pos(bitmap_git->pack, base_offset, &base_pos) < 0)
- return;
+ return 0;
+ if (offset_to_pack_pos(pack, base_offset, &base_pos) < 0)
+ return 0;
/*
* We assume delta dependencies always point backwards. This
@@ -1174,7 +1397,7 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
* odd parameters.
*/
if (base_pos >= pos)
- return;
+ return 0;
/*
* And finally, if we're not sending the base as part of our
@@ -1185,13 +1408,22 @@ static void try_partial_reuse(struct bitmap_index *bitmap_git,
* object_entry code path handle it.
*/
if (!bitmap_get(reuse, base_pos))
- return;
+ return 0;
}
/*
* If we got here, then the object is OK to reuse. Mark it.
*/
bitmap_set(reuse, pos);
+ return 0;
+}
+
+static uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git)
+{
+ struct multi_pack_index *m = bitmap_git->midx;
+ if (!m)
+ BUG("midx_preferred_pack: requires non-empty MIDX");
+ return nth_midxed_pack_int_id(m, pack_pos_to_midx(bitmap_git->midx, 0));
}
int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
@@ -1199,20 +1431,37 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
uint32_t *entries,
struct bitmap **reuse_out)
{
+ struct packed_git *pack;
struct bitmap *result = bitmap_git->result;
struct bitmap *reuse;
struct pack_window *w_curs = NULL;
size_t i = 0;
uint32_t offset;
+ uint32_t objects_nr;
assert(result);
+ load_reverse_index(bitmap_git);
+
+ if (bitmap_is_midx(bitmap_git))
+ pack = bitmap_git->midx->packs[midx_preferred_pack(bitmap_git)];
+ else
+ pack = bitmap_git->pack;
+ objects_nr = pack->num_objects;
+
while (i < result->word_alloc && result->words[i] == (eword_t)~0)
i++;
- /* Don't mark objects not in the packfile */
- if (i > bitmap_git->pack->num_objects / BITS_IN_EWORD)
- i = bitmap_git->pack->num_objects / BITS_IN_EWORD;
+ /*
+ * Don't mark objects not in the packfile or preferred pack. This bitmap
+ * marks objects eligible for reuse, but the pack-reuse code only
+ * understands how to reuse a single pack. Since the preferred pack is
+ * guaranteed to have all bases for its deltas (in a multi-pack bitmap),
+ * we use it instead of another pack. In single-pack bitmaps, the choice
+ * is made for us.
+ */
+ if (i > objects_nr / BITS_IN_EWORD)
+ i = objects_nr / BITS_IN_EWORD;
reuse = bitmap_word_alloc(i);
memset(reuse->words, 0xFF, i * sizeof(eword_t));
@@ -1226,10 +1475,23 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
break;
offset += ewah_bit_ctz64(word >> offset);
- try_partial_reuse(bitmap_git, pos + offset, reuse, &w_curs);
+ if (try_partial_reuse(pack, pos + offset,
+ reuse, &w_curs) < 0) {
+ /*
+ * try_partial_reuse indicated we couldn't reuse
+ * any bits, so there is no point in trying more
+ * bits in the current word, or any other words
+ * in result.
+ *
+ * Jump out of both loops to avoid future
+ * unnecessary calls to try_partial_reuse.
+ */
+ goto done;
+ }
}
}
+done:
unuse_pack(&w_curs);
*entries = bitmap_popcount(reuse);
@@ -1243,7 +1505,7 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
* need to be handled separately.
*/
bitmap_and_not(result, reuse);
- *packfile_out = bitmap_git->pack;
+ *packfile_out = pack;
*reuse_out = reuse;
return 0;
}
@@ -1296,7 +1558,7 @@ static uint32_t count_object_type(struct bitmap_index *bitmap_git,
for (i = 0; i < eindex->count; ++i) {
if (eindex->objects[i]->type == type &&
- bitmap_get(objects, bitmap_git->pack->num_objects + i))
+ bitmap_get(objects, bitmap_num_objects(bitmap_git) + i))
count++;
}
@@ -1325,10 +1587,52 @@ void count_bitmap_commit_list(struct bitmap_index *bitmap_git,
struct bitmap_test_data {
struct bitmap_index *bitmap_git;
struct bitmap *base;
+ struct bitmap *commits;
+ struct bitmap *trees;
+ struct bitmap *blobs;
+ struct bitmap *tags;
struct progress *prg;
size_t seen;
};
+static void test_bitmap_type(struct bitmap_test_data *tdata,
+ struct object *obj, int pos)
+{
+ enum object_type bitmap_type = OBJ_NONE;
+ int bitmaps_nr = 0;
+
+ if (bitmap_get(tdata->commits, pos)) {
+ bitmap_type = OBJ_COMMIT;
+ bitmaps_nr++;
+ }
+ if (bitmap_get(tdata->trees, pos)) {
+ bitmap_type = OBJ_TREE;
+ bitmaps_nr++;
+ }
+ if (bitmap_get(tdata->blobs, pos)) {
+ bitmap_type = OBJ_BLOB;
+ bitmaps_nr++;
+ }
+ if (bitmap_get(tdata->tags, pos)) {
+ bitmap_type = OBJ_TAG;
+ bitmaps_nr++;
+ }
+
+ if (bitmap_type == OBJ_NONE)
+ die("object %s not found in type bitmaps",
+ oid_to_hex(&obj->oid));
+
+ if (bitmaps_nr > 1)
+ die("object %s does not have a unique type",
+ oid_to_hex(&obj->oid));
+
+ if (bitmap_type != obj->type)
+ die("object %s: real type %s, expected: %s",
+ oid_to_hex(&obj->oid),
+ type_name(obj->type),
+ type_name(bitmap_type));
+}
+
static void test_show_object(struct object *object, const char *name,
void *data)
{
@@ -1338,6 +1642,7 @@ static void test_show_object(struct object *object, const char *name,
bitmap_pos = bitmap_position(tdata->bitmap_git, &object->oid);
if (bitmap_pos < 0)
die("Object not in bitmap: %s\n", oid_to_hex(&object->oid));
+ test_bitmap_type(tdata, object, bitmap_pos);
bitmap_set(tdata->base, bitmap_pos);
display_progress(tdata->prg, ++tdata->seen);
@@ -1352,6 +1657,7 @@ static void test_show_commit(struct commit *commit, void *data)
&commit->object.oid);
if (bitmap_pos < 0)
die("Object not in bitmap: %s\n", oid_to_hex(&commit->object.oid));
+ test_bitmap_type(tdata, &commit->object, bitmap_pos);
bitmap_set(tdata->base, bitmap_pos);
display_progress(tdata->prg, ++tdata->seen);
@@ -1399,6 +1705,10 @@ void test_bitmap_walk(struct rev_info *revs)
tdata.bitmap_git = bitmap_git;
tdata.base = bitmap_new();
+ tdata.commits = ewah_to_bitmap(bitmap_git->commits);
+ tdata.trees = ewah_to_bitmap(bitmap_git->trees);
+ tdata.blobs = ewah_to_bitmap(bitmap_git->blobs);
+ tdata.tags = ewah_to_bitmap(bitmap_git->tags);
tdata.prg = start_progress("Verifying bitmap entries", result_popcnt);
tdata.seen = 0;
@@ -1469,15 +1779,26 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
uint32_t i, num_objects;
uint32_t *reposition;
- num_objects = bitmap_git->pack->num_objects;
+ if (!bitmap_is_midx(bitmap_git))
+ load_reverse_index(bitmap_git);
+ else if (load_midx_revindex(bitmap_git->midx) < 0)
+ BUG("rebuild_existing_bitmaps: missing required rev-cache "
+ "extension");
+
+ num_objects = bitmap_num_objects(bitmap_git);
CALLOC_ARRAY(reposition, num_objects);
for (i = 0; i < num_objects; ++i) {
struct object_id oid;
struct object_entry *oe;
- nth_packed_object_id(&oid, bitmap_git->pack,
- pack_pos_to_index(bitmap_git->pack, i));
+ if (bitmap_is_midx(bitmap_git))
+ nth_midxed_object_oid(&oid,
+ bitmap_git->midx,
+ pack_pos_to_midx(bitmap_git->midx, i));
+ else
+ nth_packed_object_id(&oid, bitmap_git->pack,
+ pack_pos_to_index(bitmap_git->pack, i));
oe = packlist_find(mapping, &oid);
if (oe)
@@ -1503,6 +1824,19 @@ void free_bitmap_index(struct bitmap_index *b)
free(b->ext_index.hashes);
bitmap_free(b->result);
bitmap_free(b->haves);
+ if (bitmap_is_midx(b)) {
+ /*
+ * Multi-pack bitmaps need to have resources associated with
+ * their on-disk reverse indexes unmapped so that stale .rev and
+ * .bitmap files can be removed.
+ *
+ * Unlike pack-based bitmaps, multi-pack bitmaps can be read and
+ * written in the same 'git multi-pack-index write --bitmap'
+ * process. Close resources so they can be removed safely on
+ * platforms like Windows.
+ */
+ close_midx_revindex(b->midx);
+ }
free(b);
}
@@ -1517,7 +1851,6 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
enum object_type object_type)
{
struct bitmap *result = bitmap_git->result;
- struct packed_git *pack = bitmap_git->pack;
off_t total = 0;
struct ewah_iterator it;
eword_t filter;
@@ -1534,15 +1867,35 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
continue;
for (offset = 0; offset < BITS_IN_EWORD; offset++) {
- size_t pos;
-
if ((word >> offset) == 0)
break;
offset += ewah_bit_ctz64(word >> offset);
- pos = base + offset;
- total += pack_pos_to_offset(pack, pos + 1) -
- pack_pos_to_offset(pack, pos);
+
+ if (bitmap_is_midx(bitmap_git)) {
+ uint32_t pack_pos;
+ uint32_t midx_pos = pack_pos_to_midx(bitmap_git->midx, base + offset);
+ off_t offset = nth_midxed_offset(bitmap_git->midx, midx_pos);
+
+ uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos);
+ struct packed_git *pack = bitmap_git->midx->packs[pack_id];
+
+ if (offset_to_pack_pos(pack, offset, &pack_pos) < 0) {
+ struct object_id oid;
+ nth_midxed_object_oid(&oid, bitmap_git->midx, midx_pos);
+
+ die(_("could not find %s in pack %s at offset %"PRIuMAX),
+ oid_to_hex(&oid),
+ pack->pack_name,
+ (uintmax_t)offset);
+ }
+
+ total += pack_pos_to_offset(pack, pack_pos + 1) - offset;
+ } else {
+ size_t pos = base + offset;
+ total += pack_pos_to_offset(bitmap_git->pack, pos + 1) -
+ pack_pos_to_offset(bitmap_git->pack, pos);
+ }
}
}
@@ -1552,7 +1905,6 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git,
static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git)
{
struct bitmap *result = bitmap_git->result;
- struct packed_git *pack = bitmap_git->pack;
struct eindex *eindex = &bitmap_git->ext_index;
off_t total = 0;
struct object_info oi = OBJECT_INFO_INIT;
@@ -1564,7 +1916,7 @@ static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git)
for (i = 0; i < eindex->count; i++) {
struct object *obj = eindex->objects[i];
- if (!bitmap_get(result, pack->num_objects + i))
+ if (!bitmap_get(result, bitmap_num_objects(bitmap_git) + i))
continue;
if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0)
@@ -1594,7 +1946,28 @@ off_t get_disk_usage_from_bitmap(struct bitmap_index *bitmap_git,
return total;
}
+int bitmap_is_midx(struct bitmap_index *bitmap_git)
+{
+ return !!bitmap_git->midx;
+}
+
const struct string_list *bitmap_preferred_tips(struct repository *r)
{
return repo_config_get_value_multi(r, "pack.preferbitmaptips");
}
+
+int bitmap_is_preferred_refname(struct repository *r, const char *refname)
+{
+ const struct string_list *preferred_tips = bitmap_preferred_tips(r);
+ struct string_list_item *item;
+
+ if (!preferred_tips)
+ return 0;
+
+ for_each_string_list_item(item, preferred_tips) {
+ if (starts_with(refname, item->string))
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 99d733eb26..469090bad2 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -44,6 +44,7 @@ typedef int (*show_reachable_fn)(
struct bitmap_index;
struct bitmap_index *prepare_bitmap_git(struct repository *r);
+struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx);
void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits,
uint32_t *trees, uint32_t *blobs, uint32_t *tags);
void traverse_bitmap_commit_list(struct bitmap_index *,
@@ -87,12 +88,17 @@ struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
struct commit *commit);
void bitmap_writer_select_commits(struct commit **indexed_commits,
unsigned int indexed_commits_nr, int max_bitmaps);
-void bitmap_writer_build(struct packing_data *to_pack);
+int bitmap_writer_build(struct packing_data *to_pack);
void bitmap_writer_finish(struct pack_idx_entry **index,
uint32_t index_nr,
const char *filename,
uint16_t options);
+char *midx_bitmap_filename(struct multi_pack_index *midx);
+char *pack_bitmap_filename(struct packed_git *p);
+
+int bitmap_is_midx(struct bitmap_index *bitmap_git);
const struct string_list *bitmap_preferred_tips(struct repository *r);
+int bitmap_is_preferred_refname(struct repository *r, const char *refname);
#endif
diff --git a/pack-write.c b/pack-write.c
index f1fc3ecafa..067054f097 100644
--- a/pack-write.c
+++ b/pack-write.c
@@ -75,9 +75,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec
index_name = strbuf_detach(&tmp_file, NULL);
} else {
unlink(index_name);
- fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- if (fd < 0)
- die_errno("unable to create '%s'", index_name);
+ fd = xopen(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
}
f = hashfd(fd, index_name);
}
@@ -224,6 +222,9 @@ const char *write_rev_file(const char *rev_name,
uint32_t i;
const char *ret;
+ if (!(flags & WRITE_REV) && !(flags & WRITE_REV_VERIFY))
+ return NULL;
+
ALLOC_ARRAY(pack_order, nr_objects);
for (i = 0; i < nr_objects; i++)
pack_order[i] = i;
@@ -256,9 +257,7 @@ const char *write_rev_file_order(const char *rev_name,
rev_name = strbuf_detach(&tmp_file, NULL);
} else {
unlink(rev_name);
- fd = open(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- if (fd < 0)
- die_errno("unable to create '%s'", rev_name);
+ fd = xopen(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
}
f = hashfd(fd, rev_name);
} else if (flags & WRITE_REV_VERIFY) {
diff --git a/packfile.c b/packfile.c
index 9ef6d98292..2aea4737b4 100644
--- a/packfile.c
+++ b/packfile.c
@@ -860,7 +860,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
if (!strcmp(file_name, "multi-pack-index"))
return;
if (starts_with(file_name, "multi-pack-index") &&
- ends_with(file_name, ".rev"))
+ (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev")))
return;
if (ends_with(file_name, ".idx") ||
ends_with(file_name, ".rev") ||
@@ -2205,6 +2205,12 @@ int for_each_packed_object(each_packed_object_fn cb, void *data,
if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) &&
!p->pack_promisor)
continue;
+ if ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) &&
+ p->pack_keep_in_core)
+ continue;
+ if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) &&
+ p->pack_keep)
+ continue;
if (open_pack_index(p)) {
pack_errors = 1;
continue;
diff --git a/path.c b/path.c
index 0bc788ea40..2c895471d9 100644
--- a/path.c
+++ b/path.c
@@ -1510,21 +1510,28 @@ int looks_like_command_line_option(const char *str)
return str && str[0] == '-';
}
-char *xdg_config_home(const char *filename)
+char *xdg_config_home_for(const char *subdir, const char *filename)
{
const char *home, *config_home;
+ assert(subdir);
assert(filename);
config_home = getenv("XDG_CONFIG_HOME");
if (config_home && *config_home)
- return mkpathdup("%s/git/%s", config_home, filename);
+ return mkpathdup("%s/%s/%s", config_home, subdir, filename);
home = getenv("HOME");
if (home)
- return mkpathdup("%s/.config/git/%s", home, filename);
+ return mkpathdup("%s/.config/%s/%s", home, subdir, filename);
+
return NULL;
}
+char *xdg_config_home(const char *filename)
+{
+ return xdg_config_home_for("git", filename);
+}
+
char *xdg_cache_home(const char *filename)
{
const char *home, *cache_home;
diff --git a/pkt-line.c b/pkt-line.c
index 9f63eae2e6..de4a94b437 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -243,6 +243,43 @@ void packet_write(int fd_out, const char *buf, size_t size)
die("%s", err.buf);
}
+void packet_fwrite(FILE *f, const char *buf, size_t size)
+{
+ size_t packet_size;
+ char header[4];
+
+ if (size > LARGE_PACKET_DATA_MAX)
+ die(_("packet write failed - data exceeds max packet size"));
+
+ packet_trace(buf, size, 1);
+ packet_size = size + 4;
+
+ set_packet_header(header, packet_size);
+ fwrite_or_die(f, header, 4);
+ fwrite_or_die(f, buf, size);
+}
+
+void packet_fwrite_fmt(FILE *fh, const char *fmt, ...)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ va_list args;
+
+ strbuf_reset(&buf);
+
+ va_start(args, fmt);
+ format_packet(&buf, "", fmt, args);
+ va_end(args);
+
+ fwrite_or_die(fh, buf.buf, buf.len);
+}
+
+void packet_fflush(FILE *f)
+{
+ packet_trace("0000", 4, 1);
+ fwrite_or_die(f, "0000", 4);
+ fflush_or_die(f);
+}
+
void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
{
va_list args;
diff --git a/pkt-line.h b/pkt-line.h
index 5af5f45687..82b95e4bdd 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -36,6 +36,17 @@ int write_packetized_from_fd_no_flush(int fd_in, int fd_out);
int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
/*
+ * Stdio versions of packet_write functions. When mixing these with fd
+ * based functions, take care to call fflush(3) before doing fd writes or
+ * closing the fd.
+ */
+void packet_fwrite(FILE *f, const char *buf, size_t size);
+void packet_fwrite_fmt(FILE *f, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+/* packet_fflush writes a flush packet and flushes the stdio buffer of f */
+void packet_fflush(FILE *f);
+
+/*
* Read a packetized line into the buffer, which must be at least size bytes
* long. The return value specifies the number of bytes read into the buffer.
*
diff --git a/pretty.c b/pretty.c
index 9631529c10..73b5ead509 100644
--- a/pretty.c
+++ b/pretty.c
@@ -671,7 +671,11 @@ const char *repo_logmsg_reencode(struct repository *r,
* If the re-encoding failed, out might be NULL here; in that
* case we just return the commit message verbatim.
*/
- return out ? out : msg;
+ if (!out) {
+ warning("unable to reencode commit to '%s'", output_encoding);
+ return msg;
+ }
+ return out;
}
static int mailmap_name(const char **email, size_t *email_len,
diff --git a/protocol-caps.c b/protocol-caps.c
index 13a9e63a04..901b6795e4 100644
--- a/protocol-caps.c
+++ b/protocol-caps.c
@@ -69,9 +69,10 @@ static void send_info(struct repository *r, struct packet_writer *writer,
}
}
- packet_writer_write(writer, "%s",
- strbuf_detach(&send_buffer, NULL));
+ packet_writer_write(writer, "%s", send_buffer.buf);
+ strbuf_reset(&send_buffer);
}
+ strbuf_release(&send_buffer);
}
int cap_object_info(struct repository *r, struct strvec *keys,
diff --git a/range-diff.c b/range-diff.c
index e731525e66..cac89a2f4f 100644
--- a/range-diff.c
+++ b/range-diff.c
@@ -482,6 +482,7 @@ static void output(struct string_list *a, struct string_list *b,
else
diff_setup(&opts);
+ opts.no_free = 1;
if (!opts.output_format)
opts.output_format = DIFF_FORMAT_PATCH;
opts.flags.suppress_diff_headers = 1;
@@ -542,6 +543,8 @@ static void output(struct string_list *a, struct string_list *b,
strbuf_release(&buf);
strbuf_release(&dashes);
strbuf_release(&indent);
+ opts.no_free = 0;
+ diff_free(&opts);
}
int show_range_diff(const char *range1, const char *range2,
diff --git a/remote.c b/remote.c
index dfb863d808..31e141b01f 100644
--- a/remote.c
+++ b/remote.c
@@ -135,7 +135,7 @@ static inline void init_remotes_hash(void)
static struct remote *make_remote(const char *name, int len)
{
- struct remote *ret, *replaced;
+ struct remote *ret;
struct remotes_hash_key lookup;
struct hashmap_entry lookup_entry, *e;
@@ -162,8 +162,8 @@ static struct remote *make_remote(const char *name, int len)
remotes[remotes_nr++] = ret;
hashmap_entry_init(&ret->ent, lookup_entry.hash);
- replaced = hashmap_put_entry(&remotes_hash, ret, ent);
- assert(replaced == NULL); /* no previous entry overwritten */
+ if (hashmap_put_entry(&remotes_hash, ret, ent))
+ BUG("hashmap_put overwrote entry after hashmap_get returned NULL");
return ret;
}
@@ -1111,7 +1111,7 @@ static void show_push_unqualified_ref_name_error(const char *dst_value,
"Neither worked, so we gave up. You must fully qualify the ref."),
dst_value, matched_src_name);
- if (!advice_push_unqualified_ref_name)
+ if (!advice_enabled(ADVICE_PUSH_UNQUALIFIED_REF_NAME))
return;
if (get_oid(matched_src_name, &oid))
@@ -2118,7 +2118,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
base);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
@@ -2129,7 +2129,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
base);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2138,7 +2138,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is ahead of '%s' by %d commits.\n",
ours),
base, ours);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2149,7 +2149,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
base, theirs);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2162,7 +2162,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"respectively.\n",
ours + theirs),
base, ours, theirs);
- if (advice_status_hints)
+ if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to merge the remote branch into yours)\n"));
}
diff --git a/revision.c b/revision.c
index cddd0542a6..0dabb5a0bc 100644
--- a/revision.c
+++ b/revision.c
@@ -360,20 +360,18 @@ static struct object *get_reference(struct rev_info *revs, const char *name,
unsigned int flags)
{
struct object *object;
+ struct commit *commit;
/*
- * If the repository has commit graphs, repo_parse_commit() avoids
- * reading the object buffer, so use it whenever possible.
+ * If the repository has commit graphs, we try to opportunistically
+ * look up the object ID in those graphs. Like this, we can avoid
+ * parsing commit data from disk.
*/
- if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) {
- struct commit *c = lookup_commit(revs->repo, oid);
- if (!repo_parse_commit(revs->repo, c))
- object = (struct object *) c;
- else
- object = NULL;
- } else {
+ commit = lookup_commit_in_graph(revs->repo, oid);
+ if (commit)
+ object = &commit->object;
+ else
object = parse_object(revs->repo, oid);
- }
if (!object) {
if (revs->ignore_missing)
@@ -1534,7 +1532,7 @@ static int handle_one_ref(const char *path, const struct object_id *oid,
object = get_reference(cb->all_revs, path, oid, cb->all_flags);
add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
- add_pending_oid(cb->all_revs, path, oid, cb->all_flags);
+ add_pending_object(cb->all_revs, object, path);
return 0;
}
@@ -2256,6 +2254,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--author-date-order")) {
revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
revs->topo_order = 1;
+ } else if (!strcmp(arg, "--unsorted-input")) {
+ if (revs->no_walk)
+ die(_("--unsorted-input is incompatible with --no-walk"));
+ revs->unsorted_input = 1;
} else if (!strcmp(arg, "--early-output")) {
revs->early_output = 100;
revs->topo_order = 1;
@@ -2651,16 +2653,22 @@ static int handle_revision_pseudo_opt(const char *submodule,
} else if (!strcmp(arg, "--not")) {
*flags ^= UNINTERESTING | BOTTOM;
} else if (!strcmp(arg, "--no-walk")) {
- revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+ if (!revs->no_walk && revs->unsorted_input)
+ die(_("--no-walk is incompatible with --unsorted-input"));
+ revs->no_walk = 1;
} else if (skip_prefix(arg, "--no-walk=", &optarg)) {
+ if (!revs->no_walk && revs->unsorted_input)
+ die(_("--no-walk is incompatible with --unsorted-input"));
+
/*
* Detached form ("--no-walk X" as opposed to "--no-walk=X")
* not allowed, since the argument is optional.
*/
+ revs->no_walk = 1;
if (!strcmp(optarg, "sorted"))
- revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
+ revs->unsorted_input = 0;
else if (!strcmp(optarg, "unsorted"))
- revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
+ revs->unsorted_input = 1;
else
return error("invalid argument to --no-walk");
} else if (!strcmp(arg, "--do-walk")) {
@@ -3584,7 +3592,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (!revs->reflog_info)
prepare_to_use_bloom_filter(revs);
- if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
+ if (!revs->unsorted_input)
commit_list_sort_by_date(&revs->commits);
if (revs->no_walk)
return 0;
diff --git a/revision.h b/revision.h
index fbb068da9f..0c65a760ee 100644
--- a/revision.h
+++ b/revision.h
@@ -79,10 +79,6 @@ struct rev_cmdline_info {
} *rev;
};
-#define REVISION_WALK_WALK 0
-#define REVISION_WALK_NO_WALK_SORTED 1
-#define REVISION_WALK_NO_WALK_UNSORTED 2
-
struct oidset;
struct topo_walk_info;
@@ -129,7 +125,8 @@ struct rev_info {
/* Traversal flags */
unsigned int dense:1,
prune:1,
- no_walk:2,
+ no_walk:1,
+ unsorted_input:1,
remove_empty_trees:1,
simplify_history:1,
show_pulls:1,
diff --git a/run-command.c b/run-command.c
index f72e72cce7..3e4e082e94 100644
--- a/run-command.c
+++ b/run-command.c
@@ -761,9 +761,7 @@ fail_pipe:
notify_pipe[0] = notify_pipe[1] = -1;
if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) {
- null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
- if (null_fd < 0)
- die_errno(_("open /dev/null failed"));
+ null_fd = xopen("/dev/null", O_RDWR | O_CLOEXEC);
set_cloexec(null_fd);
}
@@ -1336,7 +1334,7 @@ const char *find_hook(const char *name)
err = errno;
#endif
- if (err == EACCES && advice_ignored_hook) {
+ if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
static struct string_list advise_given = STRING_LIST_INIT_DUP;
if (!string_list_lookup(&advise_given, name)) {
diff --git a/sequencer.c b/sequencer.c
index 1ceb4c0d7f..6831663692 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -403,7 +403,7 @@ static void print_advice(struct repository *r, int show_hint,
char *msg = getenv("GIT_CHERRY_PICK_HELP");
if (msg) {
- fprintf(stderr, "%s\n", msg);
+ advise("%s\n", msg);
/*
* A conflict has occurred but the porcelain
* (typically rebase --interactive) wants to take care
@@ -418,10 +418,22 @@ static void print_advice(struct repository *r, int show_hint,
if (opts->no_commit)
advise(_("after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"));
+ else if (opts->action == REPLAY_PICK)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git cherry-pick --continue\".\n"
+ "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+ "To abort and get back to the state before \"git cherry-pick\",\n"
+ "run \"git cherry-pick --abort\"."));
+ else if (opts->action == REPLAY_REVERT)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git revert --continue\".\n"
+ "You can instead skip this commit with \"git revert --skip\".\n"
+ "To abort and get back to the state before \"git revert\",\n"
+ "run \"git revert --abort\"."));
else
- advise(_("after resolving the conflicts, mark the corrected paths\n"
- "with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result with 'git commit'"));
+ BUG("unexpected pick action in print_advice()");
}
}
@@ -486,7 +498,7 @@ static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
error(_("your local changes would be overwritten by %s."),
_(action_name(opts)));
- if (advice_commit_before_merge)
+ if (advice_enabled(ADVICE_COMMIT_BEFORE_MERGE))
advise(_("commit your changes or stash them to proceed."));
return -1;
}
@@ -983,7 +995,8 @@ static int run_git_commit(const char *defmsg,
cmd.git_cmd = 1;
- if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+ if (is_rebase_i(opts) && !(!defmsg && (flags & AMEND_MSG)) &&
+ read_env_script(&cmd.env_array)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error(_(staged_changes_advice),
@@ -1293,7 +1306,7 @@ void print_commit_summary(struct repository *r,
if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
- if (advice_implicit_identity) {
+ if (advice_enabled(ADVICE_IMPLICIT_IDENTITY)) {
strbuf_addch(&format, '\n');
strbuf_addstr(&format, implicit_ident_advice());
}
@@ -3041,7 +3054,7 @@ static int create_seq_dir(struct repository *r)
}
if (in_progress_error) {
error("%s", in_progress_error);
- if (advice_sequencer_in_use)
+ if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
advise(in_progress_advice,
advise_skip ? "--skip | " : "");
return -1;
@@ -3245,7 +3258,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
give_advice:
error(_("there is nothing to skip"));
- if (advice_resolve_conflict) {
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) {
advise(_("have you committed already?\n"
"try \"git %s --continue\""),
action == REPLAY_REVERT ? "revert" : "cherry-pick");
@@ -3739,10 +3752,9 @@ static struct commit *lookup_label(const char *label, int len,
static int do_merge(struct repository *r,
struct commit *commit,
const char *arg, int arg_len,
- int flags, struct replay_opts *opts)
+ int flags, int *check_todo, struct replay_opts *opts)
{
- int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
- EDIT_MSG | VERIFY_MSG : 0;
+ int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
@@ -3816,6 +3828,45 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
+ /*
+ * If HEAD is not identical to the first parent of the original merge
+ * commit, we cannot fast-forward.
+ */
+ can_fast_forward = opts->allow_ff && commit && commit->parents &&
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
+
+ /*
+ * If any merge head is different from the original one, we cannot
+ * fast-forward.
+ */
+ if (can_fast_forward) {
+ struct commit_list *p = commit->parents->next;
+
+ for (j = to_merge; j && p; j = j->next, p = p->next)
+ if (!oideq(&j->item->object.oid,
+ &p->item->object.oid)) {
+ can_fast_forward = 0;
+ break;
+ }
+ /*
+ * If the number of merge heads differs from the original merge
+ * commit, we cannot fast-forward.
+ */
+ if (j || p)
+ can_fast_forward = 0;
+ }
+
+ if (can_fast_forward) {
+ rollback_lock_file(&lock);
+ ret = fast_forward_to(r, &commit->object.oid,
+ &head_commit->object.oid, 0, opts);
+ if (flags & TODO_EDIT_MERGE_MSG)
+ goto fast_forward_edit;
+
+ goto leave_merge;
+ }
+
if (commit) {
const char *encoding = get_commit_output_encoding();
const char *message = logmsg_reencode(commit, NULL, encoding);
@@ -3865,46 +3916,6 @@ static int do_merge(struct repository *r,
}
}
- /*
- * If HEAD is not identical to the first parent of the original merge
- * commit, we cannot fast-forward.
- */
- can_fast_forward = opts->allow_ff && commit && commit->parents &&
- oideq(&commit->parents->item->object.oid,
- &head_commit->object.oid);
-
- /*
- * If any merge head is different from the original one, we cannot
- * fast-forward.
- */
- if (can_fast_forward) {
- struct commit_list *p = commit->parents->next;
-
- for (j = to_merge; j && p; j = j->next, p = p->next)
- if (!oideq(&j->item->object.oid,
- &p->item->object.oid)) {
- can_fast_forward = 0;
- break;
- }
- /*
- * If the number of merge heads differs from the original merge
- * commit, we cannot fast-forward.
- */
- if (j || p)
- can_fast_forward = 0;
- }
-
- if (can_fast_forward) {
- rollback_lock_file(&lock);
- ret = fast_forward_to(r, &commit->object.oid,
- &head_commit->object.oid, 0, opts);
- if (flags & TODO_EDIT_MERGE_MSG) {
- run_commit_flags |= AMEND_MSG;
- goto fast_forward_edit;
- }
- goto leave_merge;
- }
-
if (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -3935,7 +3946,10 @@ static int do_merge(struct repository *r,
strvec_pushf(&cmd.args,
"-X%s", opts->xopts[k]);
}
- strvec_push(&cmd.args, "--no-edit");
+ if (!(flags & TODO_EDIT_MERGE_MSG))
+ strvec_push(&cmd.args, "--no-edit");
+ else
+ strvec_push(&cmd.args, "--edit");
strvec_push(&cmd.args, "--no-ff");
strvec_push(&cmd.args, "--no-log");
strvec_push(&cmd.args, "--no-stat");
@@ -4035,10 +4049,17 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- fast_forward_edit:
ret = !!run_git_commit(git_path_merge_msg(r), opts,
run_commit_flags);
+ if (!ret && flags & TODO_EDIT_MERGE_MSG) {
+ fast_forward_edit:
+ *check_todo = 1;
+ run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
+ ret = !!run_git_commit(NULL, opts, run_commit_flags);
+ }
+
+
leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
@@ -4405,9 +4426,8 @@ static int pick_commits(struct repository *r,
if ((res = do_reset(r, arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
- if ((res = do_merge(r, item->commit,
- arg, item->arg_len,
- item->flags, opts)) < 0)
+ if ((res = do_merge(r, item->commit, arg, item->arg_len,
+ item->flags, &check_todo, opts)) < 0)
reschedule = 1;
else if (item->commit)
record_in_rewritten(&item->commit->object.oid,
@@ -4716,6 +4736,9 @@ static int commit_staged_changes(struct repository *r,
refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0))
return error(_("could not remove CHERRY_PICK_HEAD"));
+ if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
+ return error_errno(_("could not remove '%s'"),
+ git_path_merge_msg(r));
if (!final_fixup)
return 0;
}
@@ -5099,6 +5122,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
+ int skipped_commit = 0;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -5149,8 +5173,13 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidset_insert(&interesting, &commit->object.oid);
is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
@@ -5214,6 +5243,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidcpy(&entry->entry.oid, &commit->object.oid);
oidmap_put(&commit2todo, entry);
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
/*
* Second phase:
@@ -5334,6 +5366,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
+ int skipped_commit = 0;
repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
@@ -5369,8 +5402,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
while ((commit = get_revision(&revs))) {
int is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
strbuf_addf(out, "%s %s ", insn,
@@ -5380,6 +5418,9 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
strbuf_addf(out, " %c empty", comment_line_char);
strbuf_addch(out, '\n');
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
return 0;
}
diff --git a/sequencer.h b/sequencer.h
index d57d8ea23d..2088344cb3 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -156,6 +156,7 @@ int sequencer_remove_state(struct replay_opts *opts);
*/
#define TODO_LIST_ROOT_WITH_ONTO (1U << 6)
#define TODO_LIST_REAPPLY_CHERRY_PICKS (1U << 7)
+#define TODO_LIST_WARN_SKIPPED_CHERRY_PICKS (1U << 8)
int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
const char **argv, unsigned flags);
diff --git a/sparse-index.c b/sparse-index.c
index c6b4feec41..56eb65dc34 100644
--- a/sparse-index.c
+++ b/sparse-index.c
@@ -283,6 +283,7 @@ void ensure_full_index(struct index_state *istate)
/* Copy back into original index. */
memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash));
+ memcpy(&istate->dir_hash, &full->dir_hash, sizeof(full->dir_hash));
istate->sparse_index = 0;
free(istate->cache);
istate->cache = full->cache;
diff --git a/submodule-config.c b/submodule-config.c
index 2026120fb3..f95344028b 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -649,9 +649,10 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void
config_source.file = file;
} else if (repo_get_oid(repo, GITMODULES_INDEX, &oid) >= 0 ||
repo_get_oid(repo, GITMODULES_HEAD, &oid) >= 0) {
+ config_source.repo = repo;
config_source.blob = oidstr = xstrdup(oid_to_hex(&oid));
if (repo != the_repository)
- add_to_alternates_memory(repo->objects->odb->path);
+ add_submodule_odb_by_path(repo->objects->odb->path);
} else {
goto out;
}
@@ -702,7 +703,7 @@ void gitmodules_config_oid(const struct object_id *commit_oid)
if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {
git_config_from_blob_oid(gitmodules_cb, rev.buf,
- &oid, the_repository);
+ the_repository, &oid, the_repository);
}
strbuf_release(&rev);
diff --git a/submodule.c b/submodule.c
index 8e611fe1db..5e63e91caf 100644
--- a/submodule.c
+++ b/submodule.c
@@ -165,6 +165,8 @@ void stage_updated_gitmodules(struct index_state *istate)
die(_("staging updated .gitmodules failed"));
}
+static struct string_list added_submodule_odb_paths = STRING_LIST_INIT_NODUP;
+
/* TODO: remove this function, use repo_submodule_init instead. */
int add_submodule_odb(const char *path)
{
@@ -178,12 +180,33 @@ int add_submodule_odb(const char *path)
ret = -1;
goto done;
}
- add_to_alternates_memory(objects_directory.buf);
+ string_list_insert(&added_submodule_odb_paths,
+ strbuf_detach(&objects_directory, NULL));
done:
strbuf_release(&objects_directory);
return ret;
}
+void add_submodule_odb_by_path(const char *path)
+{
+ string_list_insert(&added_submodule_odb_paths, xstrdup(path));
+}
+
+int register_all_submodule_odb_as_alternates(void)
+{
+ int i;
+ int ret = added_submodule_odb_paths.nr;
+
+ for (i = 0; i < added_submodule_odb_paths.nr; i++)
+ add_to_alternates_memory(added_submodule_odb_paths.items[i].string);
+ if (ret) {
+ string_list_clear(&added_submodule_odb_paths, 0);
+ if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0))
+ BUG("register_all_submodule_odb_as_alternates() called");
+ }
+ return ret;
+}
+
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path)
{
@@ -697,8 +720,20 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
strvec_push(&cp.args, oid_to_hex(new_oid));
prepare_submodule_repo_env(&cp.env_array);
- if (start_command(&cp))
+
+ if (!is_directory(path)) {
+ /* fall back to absorbed git dir, if any */
+ if (!sub)
+ goto done;
+ cp.dir = sub->gitdir;
+ strvec_push(&cp.env_array, GIT_DIR_ENVIRONMENT "=.");
+ strvec_push(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT "=.");
+ }
+
+ if (start_command(&cp)) {
diff_emit_submodule_error(o, "(diff failed)\n");
+ goto done;
+ }
while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
diff --git a/submodule.h b/submodule.h
index 84640c49c1..17a06cc43b 100644
--- a/submodule.h
+++ b/submodule.h
@@ -97,7 +97,15 @@ int submodule_uses_gitfile(const char *path);
#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
int bad_to_remove_submodule(const char *path, unsigned flags);
+/*
+ * Call add_submodule_odb() to add the submodule at the given path to a list.
+ * When register_all_submodule_odb_as_alternates() is called, the object stores
+ * of all submodules in that list will be added as alternates in
+ * the_repository.
+ */
int add_submodule_odb(const char *path);
+void add_submodule_odb_by_path(const char *path);
+int register_all_submodule_odb_as_alternates(void);
/*
* Checks if there are submodule changes in a..b. If a is the null OID,
diff --git a/t/README b/t/README
index 9e70122302..51065d0800 100644
--- a/t/README
+++ b/t/README
@@ -425,6 +425,10 @@ GIT_TEST_MULTI_PACK_INDEX=<boolean>, when true, forces the multi-pack-
index to be written after every 'git repack' command, and overrides the
'core.multiPackIndex' setting to true.
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=<boolean>, when true, sets the
+'--bitmap' option on all invocations of 'git multi-pack-index write',
+and ignores pack-objects' '--write-bitmap-index'.
+
GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the
'uploadpack.allowSidebandAll' setting to true, and when false, forces
fetch-pack to not request sideband-all (even if the server advertises
@@ -448,6 +452,16 @@ GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting
to <n> and 'checkout.thresholdForParallelism' to 0, forcing the
execution of the parallel-checkout code.
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<boolean>, when true, makes
+registering submodule ODBs as alternates a fatal action. Support for
+this environment variable can be removed once the migration to
+explicitly providing repositories when accessing submodule objects is
+complete (in which case we might want to replace this with a trace2
+call so that users can make it visible if accessing submodule objects
+without an explicit repository still happens) or needs to be abandoned
+for whatever reason (in which case the migrated codepaths still retain
+their performance benefits).
+
Naming Tests
------------
@@ -753,7 +767,8 @@ Test harness library
--------------------
There are a handful helper functions defined in the test harness
-library for your script to use.
+library for your script to use. Some of them are listed below;
+see test-lib-functions.sh for the full list and their options.
- test_expect_success [<prereq>] <message> <script>
@@ -799,10 +814,12 @@ library for your script to use.
argument. This is primarily meant for use during the
development of a new test script.
- - debug <git-command>
+ - debug [options] <git-command>
Run a git command inside a debugger. This is primarily meant for
- use when debugging a failing test script.
+ use when debugging a failing test script. With '-t', use your
+ original TERM instead of test-lib.sh's "dumb", so that your
+ debugger interface has colors.
- test_done
@@ -989,7 +1006,7 @@ library for your script to use.
EOF
- - test_pause
+ - test_pause [options]
This command is useful for writing and debugging tests and must be
removed before submitting. It halts the execution of the test and
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index 7c2eb11a8e..cb0d27049a 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -60,12 +60,26 @@ static int read_midx_file(const char *object_dir, int show_objects)
return 0;
}
+static int read_midx_checksum(const char *object_dir)
+{
+ struct multi_pack_index *m;
+
+ setup_git_directory();
+ m = load_multi_pack_index(object_dir, 1);
+ if (!m)
+ return 1;
+ printf("%s\n", hash_to_hex(get_midx_checksum(m)));
+ return 0;
+}
+
int cmd__read_midx(int argc, const char **argv)
{
if (!(argc == 2 || argc == 3))
- usage("read-midx [--show-objects] <object-dir>");
+ usage("read-midx [--show-objects|--checksum] <object-dir>");
if (!strcmp(argv[1], "--show-objects"))
return read_midx_file(argv[2], 1);
+ else if (!strcmp(argv[1], "--checksum"))
+ return read_midx_checksum(argv[2]);
return read_midx_file(argv[1], 0);
}
diff --git a/t/lib-bitmap.sh b/t/lib-bitmap.sh
index fe3f98be24..21d0392dda 100644
--- a/t/lib-bitmap.sh
+++ b/t/lib-bitmap.sh
@@ -1,3 +1,6 @@
+# Helpers for scripts testing bitmap functionality; see t5310 for
+# example usage.
+
# Compare a file containing rev-list bitmap traversal output to its non-bitmap
# counterpart. You can't just use test_cmp for this, because the two produce
# subtly different output:
@@ -24,3 +27,240 @@ test_bitmap_traversal () {
test_cmp "$1.normalized" "$2.normalized" &&
rm -f "$1.normalized" "$2.normalized"
}
+
+# To ensure the logic for "maximal commits" is exercised, make
+# the repository a bit more complicated.
+#
+# other second
+# * *
+# (99 commits) (99 commits)
+# * *
+# |\ /|
+# | * octo-other octo-second * |
+# |/|\_________ ____________/|\|
+# | \ \/ __________/ |
+# | | ________/\ / |
+# * |/ * merge-right *
+# | _|__________/ \____________ |
+# |/ | \|
+# (l1) * * merge-left * (r1)
+# | / \________________________ |
+# |/ \|
+# (l2) * * (r2)
+# \___________________________ |
+# \|
+# * (base)
+#
+# We only push bits down the first-parent history, which
+# makes some of these commits unimportant!
+#
+# The important part for the maximal commit algorithm is how
+# the bitmasks are extended. Assuming starting bit positions
+# for second (bit 0) and other (bit 1), the bitmasks at the
+# end should be:
+#
+# second: 1 (maximal, selected)
+# other: 01 (maximal, selected)
+# (base): 11 (maximal)
+#
+# This complicated history was important for a previous
+# version of the walk that guarantees never walking a
+# commit multiple times. That goal might be important
+# again, so preserve this complicated case. For now, this
+# test will guarantee that the bitmaps are computed
+# correctly, even with the repeat calculations.
+setup_bitmap_history() {
+ test_expect_success 'setup repo with moderate-sized history' '
+ test_commit_bulk --id=file 10 &&
+ git branch -M second &&
+ git checkout -b other HEAD~5 &&
+ test_commit_bulk --id=side 10 &&
+
+ # add complicated history setup, including merges and
+ # ambiguous merge-bases
+
+ git checkout -b merge-left other~2 &&
+ git merge second~2 -m "merge-left" &&
+
+ git checkout -b merge-right second~1 &&
+ git merge other~1 -m "merge-right" &&
+
+ git checkout -b octo-second second &&
+ git merge merge-left merge-right -m "octopus-second" &&
+
+ git checkout -b octo-other other &&
+ git merge merge-left merge-right -m "octopus-other" &&
+
+ git checkout other &&
+ git merge octo-other -m "pull octopus" &&
+
+ git checkout second &&
+ git merge octo-second -m "pull octopus" &&
+
+ # Remove these branches so they are not selected
+ # as bitmap tips
+ git branch -D merge-left &&
+ git branch -D merge-right &&
+ git branch -D octo-other &&
+ git branch -D octo-second &&
+
+ # add padding to make these merges less interesting
+ # and avoid having them selected for bitmaps
+ test_commit_bulk --id=file 100 &&
+ git checkout other &&
+ test_commit_bulk --id=side 100 &&
+ git checkout second &&
+
+ bitmaptip=$(git rev-parse second) &&
+ blob=$(echo tagged-blob | git hash-object -w --stdin) &&
+ git tag tagged-blob $blob
+ '
+}
+
+rev_list_tests_head () {
+ test_expect_success "counting commits via bitmap ($state, $branch)" '
+ git rev-list --count $branch >expect &&
+ git rev-list --use-bitmap-index --count $branch >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "counting partial commits via bitmap ($state, $branch)" '
+ git rev-list --count $branch~5..$branch >expect &&
+ git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "counting commits with limit ($state, $branch)" '
+ git rev-list --count -n 1 $branch >expect &&
+ git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "counting non-linear history ($state, $branch)" '
+ git rev-list --count other...second >expect &&
+ git rev-list --use-bitmap-index --count other...second >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "counting commits with limiting ($state, $branch)" '
+ git rev-list --count $branch -- 1.t >expect &&
+ git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "counting objects via bitmap ($state, $branch)" '
+ git rev-list --count --objects $branch >expect &&
+ git rev-list --use-bitmap-index --count --objects $branch >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "enumerate commits ($state, $branch)" '
+ git rev-list --use-bitmap-index $branch >actual &&
+ git rev-list $branch >expect &&
+ test_bitmap_traversal --no-confirm-bitmaps expect actual
+ '
+
+ test_expect_success "enumerate --objects ($state, $branch)" '
+ git rev-list --objects --use-bitmap-index $branch >actual &&
+ git rev-list --objects $branch >expect &&
+ test_bitmap_traversal expect actual
+ '
+
+ test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
+ git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
+ grep $blob actual
+ '
+}
+
+rev_list_tests () {
+ state=$1
+
+ for branch in "second" "other"
+ do
+ rev_list_tests_head
+ done
+}
+
+basic_bitmap_tests () {
+ tip="$1"
+ test_expect_success 'rev-list --test-bitmap verifies bitmaps' "
+ git rev-list --test-bitmap "${tip:-HEAD}"
+ "
+
+ rev_list_tests 'full bitmap'
+
+ test_expect_success 'clone from bitmapped repository' '
+ rm -fr clone.git &&
+ git clone --no-local --bare . clone.git &&
+ git rev-parse HEAD >expect &&
+ git --git-dir=clone.git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'partial clone from bitmapped repository' '
+ test_config uploadpack.allowfilter true &&
+ rm -fr partial-clone.git &&
+ git clone --no-local --bare --filter=blob:none . partial-clone.git &&
+ (
+ cd partial-clone.git &&
+ pack=$(echo objects/pack/*.pack) &&
+ git verify-pack -v "$pack" >have &&
+ awk "/blob/ { print \$1 }" <have >blobs &&
+ # we expect this single blob because of the direct ref
+ git rev-parse refs/tags/tagged-blob >expect &&
+ test_cmp expect blobs
+ )
+ '
+
+ test_expect_success 'setup further non-bitmapped commits' '
+ test_commit_bulk --id=further 10
+ '
+
+ rev_list_tests 'partial bitmap'
+
+ test_expect_success 'fetch (partial bitmap)' '
+ git --git-dir=clone.git fetch origin second:second &&
+ git rev-parse HEAD >expect &&
+ git --git-dir=clone.git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'enumerating progress counts pack-reused objects' '
+ count=$(git rev-list --objects --all --count) &&
+ git repack -adb &&
+
+ # check first with only reused objects; confirm that our
+ # progress showed the right number, and also that we did
+ # pack-reuse as expected. Check only the final "done"
+ # line of the meter (there may be an arbitrary number of
+ # intermediate lines ending with CR).
+ GIT_PROGRESS_DELAY=0 \
+ git pack-objects --all --stdout --progress \
+ </dev/null >/dev/null 2>stderr &&
+ grep "Enumerating objects: $count, done" stderr &&
+ grep "pack-reused $count" stderr &&
+
+ # now the same but with one non-reused object
+ git commit --allow-empty -m "an extra commit object" &&
+ GIT_PROGRESS_DELAY=0 \
+ git pack-objects --all --stdout --progress \
+ </dev/null >/dev/null 2>stderr &&
+ grep "Enumerating objects: $((count+1)), done" stderr &&
+ grep "pack-reused $count" stderr
+ '
+}
+
+# have_delta <obj> <expected_base>
+#
+# Note that because this relies on cat-file, it might find _any_ copy of an
+# object in the repository. The caller is responsible for making sure
+# there's only one (e.g., via "repack -ad", or having just fetched a copy).
+have_delta () {
+ echo $2 >expect &&
+ echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
+ test_cmp expect actual
+}
+
+midx_checksum () {
+ test-tool read-midx --checksum "$1"
+}
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index dc75b83451..ec6b9b107d 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -151,3 +151,59 @@ test_editor_unchanged () {
EOF
test_cmp expect actual
}
+
+# Set up an editor for testing reword commands
+# Checks that there are no uncommitted changes when rewording and that the
+# todo-list is reread after each
+set_reword_editor () {
+ >reword-actual &&
+ >reword-oid &&
+
+ # Check rewording keeps the original authorship
+ GIT_AUTHOR_NAME="Reword Author"
+ GIT_AUTHOR_EMAIL="reword.author@example.com"
+ GIT_AUTHOR_DATE=@123456
+
+ write_script reword-sequence-editor.sh <<-\EOF &&
+ todo="$(cat "$1")" &&
+ echo "exec git log -1 --pretty=format:'%an <%ae> %at%n%B%n' \
+ >>reword-actual" >"$1" &&
+ printf "%s\n" "$todo" >>"$1"
+ EOF
+
+ write_script reword-editor.sh <<-EOF &&
+ # Save the oid of the first reworded commit so we can check rebase
+ # fast-forwards to it. Also check that we do not write .git/MERGE_MSG
+ # when fast-forwarding
+ if ! test -s reword-oid
+ then
+ git rev-parse HEAD >reword-oid &&
+ if test -f .git/MERGE_MSG
+ then
+ echo 1>&2 "error: .git/MERGE_MSG exists"
+ exit 1
+ fi
+ fi &&
+ # There should be no uncommited changes
+ git diff --exit-code HEAD &&
+ # The todo-list should be re-read after a reword
+ GIT_SEQUENCE_EDITOR="\"$PWD/reword-sequence-editor.sh\"" \
+ git rebase --edit-todo &&
+ echo edited >>"\$1"
+ EOF
+
+ test_set_editor "$PWD/reword-editor.sh"
+}
+
+# Check the results of a rebase after calling set_reword_editor
+# Pass the commits that were reworded in the order that they were picked
+# Expects the first pick to be a fast-forward
+check_reworded_commits () {
+ test_cmp_rev "$(cat reword-oid)" "$1^{commit}" &&
+ git log --format="%an <%ae> %at%n%B%nedited%n" --no-walk=unsorted "$@" \
+ >reword-expected &&
+ test_cmp reword-expected reword-actual &&
+ git log --format="%an <%ae> %at%n%B" -n $# --first-parent --reverse \
+ >reword-log &&
+ test_cmp reword-expected reword-log
+}
diff --git a/t/perf/lib-bitmap.sh b/t/perf/lib-bitmap.sh
new file mode 100644
index 0000000000..63d3bc7cec
--- /dev/null
+++ b/t/perf/lib-bitmap.sh
@@ -0,0 +1,69 @@
+# Helper functions for testing bitmap performance; see p5310.
+
+test_full_bitmap () {
+ test_perf 'simulated clone' '
+ git pack-objects --stdout --all </dev/null >/dev/null
+ '
+
+ test_perf 'simulated fetch' '
+ have=$(git rev-list HEAD~100 -1) &&
+ {
+ echo HEAD &&
+ echo ^$have
+ } | git pack-objects --revs --stdout >/dev/null
+ '
+
+ test_perf 'pack to file (bitmap)' '
+ git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
+ '
+
+ test_perf 'rev-list (commits)' '
+ git rev-list --all --use-bitmap-index >/dev/null
+ '
+
+ test_perf 'rev-list (objects)' '
+ git rev-list --all --use-bitmap-index --objects >/dev/null
+ '
+
+ test_perf 'rev-list with tag negated via --not --all (objects)' '
+ git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null
+ '
+
+ test_perf 'rev-list with negative tag (objects)' '
+ git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null
+ '
+
+ test_perf 'rev-list count with blob:none' '
+ git rev-list --use-bitmap-index --count --objects --all \
+ --filter=blob:none >/dev/null
+ '
+
+ test_perf 'rev-list count with blob:limit=1k' '
+ git rev-list --use-bitmap-index --count --objects --all \
+ --filter=blob:limit=1k >/dev/null
+ '
+
+ test_perf 'rev-list count with tree:0' '
+ git rev-list --use-bitmap-index --count --objects --all \
+ --filter=tree:0 >/dev/null
+ '
+
+ test_perf 'simulated partial clone' '
+ git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
+ '
+}
+
+test_partial_bitmap () {
+ test_perf 'clone (partial bitmap)' '
+ git pack-objects --stdout --all </dev/null >/dev/null
+ '
+
+ test_perf 'pack to file (partial bitmap)' '
+ git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
+ '
+
+ test_perf 'rev-list with tree filter (partial bitmap)' '
+ git rev-list --use-bitmap-index --count --objects --all \
+ --filter=tree:0 >/dev/null
+ '
+}
diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh
index 452be01056..7ad4f237bc 100755
--- a/t/perf/p5310-pack-bitmaps.sh
+++ b/t/perf/p5310-pack-bitmaps.sh
@@ -2,6 +2,7 @@
test_description='Tests pack performance using bitmaps'
. ./perf-lib.sh
+. "${TEST_DIRECTORY}/perf/lib-bitmap.sh"
test_perf_large_repo
@@ -25,56 +26,7 @@ test_perf 'repack to disk' '
git repack -ad
'
-test_perf 'simulated clone' '
- git pack-objects --stdout --all </dev/null >/dev/null
-'
-
-test_perf 'simulated fetch' '
- have=$(git rev-list HEAD~100 -1) &&
- {
- echo HEAD &&
- echo ^$have
- } | git pack-objects --revs --stdout >/dev/null
-'
-
-test_perf 'pack to file (bitmap)' '
- git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null
-'
-
-test_perf 'rev-list (commits)' '
- git rev-list --all --use-bitmap-index >/dev/null
-'
-
-test_perf 'rev-list (objects)' '
- git rev-list --all --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list with tag negated via --not --all (objects)' '
- git rev-list perf-tag --not --all --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list with negative tag (objects)' '
- git rev-list HEAD --not perf-tag --use-bitmap-index --objects >/dev/null
-'
-
-test_perf 'rev-list count with blob:none' '
- git rev-list --use-bitmap-index --count --objects --all \
- --filter=blob:none >/dev/null
-'
-
-test_perf 'rev-list count with blob:limit=1k' '
- git rev-list --use-bitmap-index --count --objects --all \
- --filter=blob:limit=1k >/dev/null
-'
-
-test_perf 'rev-list count with tree:0' '
- git rev-list --use-bitmap-index --count --objects --all \
- --filter=tree:0 >/dev/null
-'
-
-test_perf 'simulated partial clone' '
- git pack-objects --stdout --all --filter=blob:none </dev/null >/dev/null
-'
+test_full_bitmap
test_expect_success 'create partial bitmap state' '
# pick a commit to represent the repo tip in the past
@@ -97,17 +49,6 @@ test_expect_success 'create partial bitmap state' '
git update-ref HEAD $orig_tip
'
-test_perf 'clone (partial bitmap)' '
- git pack-objects --stdout --all </dev/null >/dev/null
-'
-
-test_perf 'pack to file (partial bitmap)' '
- git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null
-'
-
-test_perf 'rev-list with tree filter (partial bitmap)' '
- git rev-list --use-bitmap-index --count --objects --all \
- --filter=tree:0 >/dev/null
-'
+test_partial_bitmap
test_done
diff --git a/t/perf/p5326-multi-pack-bitmaps.sh b/t/perf/p5326-multi-pack-bitmaps.sh
new file mode 100755
index 0000000000..5845109ac7
--- /dev/null
+++ b/t/perf/p5326-multi-pack-bitmaps.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='Tests performance using midx bitmaps'
+. ./perf-lib.sh
+. "${TEST_DIRECTORY}/perf/lib-bitmap.sh"
+
+test_perf_large_repo
+
+test_expect_success 'enable multi-pack index' '
+ git config core.multiPackIndex true
+'
+
+test_perf 'setup multi-pack index' '
+ git repack -ad &&
+ git multi-pack-index write --bitmap
+'
+
+test_full_bitmap
+
+test_expect_success 'create partial bitmap state' '
+ # pick a commit to represent the repo tip in the past
+ cutoff=$(git rev-list HEAD~100 -1) &&
+ orig_tip=$(git rev-parse HEAD) &&
+
+ # now pretend we have just one tip
+ rm -rf .git/logs .git/refs/* .git/packed-refs &&
+ git update-ref HEAD $cutoff &&
+
+ # and then repack, which will leave us with a nice
+ # big bitmap pack of the "old" history, and all of
+ # the new history will be loose, as if it had been pushed
+ # up incrementally and exploded via unpack-objects
+ git repack -Ad &&
+ git multi-pack-index write --bitmap &&
+
+ # and now restore our original tip, as if the pushes
+ # had happened
+ git update-ref HEAD $orig_tip
+'
+
+test_partial_bitmap
+
+test_done
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index b5749f327d..33dfc9cd56 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
TEST_ROOT="$PWD"
PATH=$TEST_ROOT:$PATH
@@ -1061,4 +1062,74 @@ test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \
)
'
+test_expect_success PERL 'setup for progress tests' '
+ git init progress &&
+ (
+ cd progress &&
+ git config filter.delay.process "rot13-filter.pl delay-progress.log clean smudge delay" &&
+ git config filter.delay.required true &&
+
+ echo "*.a filter=delay" >.gitattributes &&
+ touch test-delay10.a &&
+ git add . &&
+ git commit -m files
+ )
+'
+
+test_delayed_checkout_progress () {
+ if test "$1" = "!"
+ then
+ local expect_progress=N &&
+ shift
+ else
+ local expect_progress=
+ fi &&
+
+ if test $# -lt 1
+ then
+ BUG "no command given to test_delayed_checkout_progress"
+ fi &&
+
+ (
+ cd progress &&
+ GIT_PROGRESS_DELAY=0 &&
+ export GIT_PROGRESS_DELAY &&
+ rm -f *.a delay-progress.log &&
+
+ "$@" 2>err &&
+ grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delay-progress.log &&
+ if test "$expect_progress" = N
+ then
+ ! grep "Filtering content" err
+ else
+ grep "Filtering content" err
+ fi
+ )
+}
+
+for mode in pathspec branch
+do
+ case "$mode" in
+ pathspec) opt='.' ;;
+ branch) opt='-f HEAD' ;;
+ esac
+
+ test_expect_success PERL,TTY "delayed checkout shows progress by default on tty ($mode checkout)" '
+ test_delayed_checkout_progress test_terminal git checkout $opt
+ '
+
+ test_expect_success PERL "delayed checkout ommits progress on non-tty ($mode checkout)" '
+ test_delayed_checkout_progress ! git checkout $opt
+ '
+
+ test_expect_success PERL,TTY "delayed checkout ommits progress with --quiet ($mode checkout)" '
+ test_delayed_checkout_progress ! test_terminal git checkout --quiet $opt
+ '
+
+ test_expect_success PERL,TTY "delayed checkout honors --[no]-progress ($mode checkout)" '
+ test_delayed_checkout_progress ! test_terminal git checkout --no-progress $opt &&
+ test_delayed_checkout_progress test_terminal git checkout --quiet --progress $opt
+ '
+done
+
test_done
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh
index a211a66c67..bba679685f 100755
--- a/t/t0410-partial-clone.sh
+++ b/t/t0410-partial-clone.sh
@@ -4,6 +4,9 @@ test_description='partial clone'
. ./test-lib.sh
+# missing promisor objects cause repacks which write bitmaps to fail
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
delete_object () {
rm $1/.git/objects/$(echo $2 | sed -e 's|^..|&/|')
}
@@ -536,7 +539,13 @@ test_expect_success 'gc does not repack promisor objects if there are none' '
repack_and_check () {
rm -rf repo2 &&
cp -r repo repo2 &&
- git -C repo2 repack $1 -d &&
+ if test x"$1" = "x--must-fail"
+ then
+ shift
+ test_must_fail git -C repo2 repack $1 -d
+ else
+ git -C repo2 repack $1 -d
+ fi &&
git -C repo2 fsck &&
git -C repo2 cat-file -e $2 &&
@@ -561,6 +570,7 @@ test_expect_success 'repack -d does not irreversibly delete promisor objects' '
printf "$THREE\n" | pack_as_from_promisor &&
delete_object repo "$ONE" &&
+ repack_and_check --must-fail -ab "$TWO" "$THREE" &&
repack_and_check -a "$TWO" "$THREE" &&
repack_and_check -A "$TWO" "$THREE" &&
repack_and_check -l "$TWO" "$THREE"
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..e575ffb4ff 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1272,6 +1272,19 @@ test_expect_success 'attempt to delete a branch merged to its base' '
test_must_fail git branch -d my10
'
+test_expect_success 'branch --delete --force removes dangling branch' '
+ git checkout main &&
+ test_commit unstable &&
+ hash=$(git rev-parse HEAD) &&
+ objpath=$(echo $hash | sed -e "s|^..|.git/objects/&/|") &&
+ git branch --no-track dangling &&
+ mv $objpath $objpath.x &&
+ test_when_finished "mv $objpath.x $objpath" &&
+ git branch --delete --force dangling &&
+ git for-each-ref refs/heads/dangling >actual &&
+ test_must_be_empty actual
+'
+
test_expect_success 'use --edit-description' '
write_script editor <<-\EOF &&
echo "New contents" >"$1"
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index e26762d0b2..f6e4864497 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -20,6 +20,7 @@ test_expect_success setup '
git add hello &&
git commit -m "hello" &&
git branch skip-reference &&
+ git tag hello &&
echo world >> hello &&
git commit -a -m "hello world" &&
@@ -36,7 +37,8 @@ test_expect_success setup '
test_tick &&
GIT_AUTHOR_NAME="Another Author" \
GIT_AUTHOR_EMAIL="another.author@example.com" \
- git commit --amend --no-edit -m amended-goodbye &&
+ git commit --amend --no-edit -m amended-goodbye \
+ --reset-author &&
test_tick &&
git tag amended-goodbye &&
@@ -51,7 +53,7 @@ test_expect_success setup '
'
test_expect_success 'rebase with git am -3 (default)' '
- test_must_fail git rebase main
+ test_must_fail git rebase --apply main
'
test_expect_success 'rebase --skip can not be used with other options' '
@@ -95,6 +97,13 @@ test_expect_success 'moved back to branch correctly' '
test_debug 'gitk --all & sleep 1'
+test_expect_success 'skipping final pick removes .git/MERGE_MSG' '
+ test_must_fail git rebase --onto hello reverted-goodbye^ \
+ reverted-goodbye &&
+ git rebase --skip &&
+ test_path_is_missing .git/MERGE_MSG
+'
+
test_expect_success 'correct advice upon picking empty commit' '
test_when_finished "git rebase --abort" &&
test_must_fail git rebase -i --onto goodbye \
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 66bcbbf952..d877872e8f 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -839,6 +839,19 @@ test_expect_success 'reword' '
git show HEAD~2 | grep "C changed"
'
+test_expect_success 'no uncommited changes when rewording the todo list is reloaded' '
+ git checkout E &&
+ test_when_finished "git checkout @{-1}" &&
+ (
+ set_fake_editor &&
+ GIT_SEQUENCE_EDITOR="\"$PWD/fake-editor.sh\"" &&
+ export GIT_SEQUENCE_EDITOR &&
+ set_reword_editor &&
+ FAKE_LINES="reword 1 reword 2" git rebase -i C
+ ) &&
+ check_reworded_commits D E
+'
+
test_expect_success 'rebase -i can copy notes' '
git config notes.rewrite.rebase true &&
git config notes.rewriteRef "refs/notes/*" &&
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index f4c2ee02bc..738fbae9b2 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -21,7 +21,7 @@ test_expect_success 'setup' '
git checkout main
'
-test_expect_success 'interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue with works with touched file' '
rm -fr .git/rebase-* &&
git reset --hard &&
git checkout main &&
@@ -31,12 +31,22 @@ test_expect_success 'interactive rebase --continue works with touched file' '
git rebase --continue
'
-test_expect_success 'non-interactive rebase --continue works with touched file' '
+test_expect_success 'merge based rebase --continue removes .git/MERGE_MSG' '
+ git checkout -f --detach topic &&
+
+ test_must_fail git rebase --onto main HEAD^ &&
+ git read-tree --reset -u HEAD &&
+ test_path_is_file .git/MERGE_MSG &&
+ git rebase --continue &&
+ test_path_is_missing .git/MERGE_MSG
+'
+
+test_expect_success 'apply based rebase --continue works with touched file' '
rm -fr .git/rebase-* &&
git reset --hard &&
git checkout main &&
- test_must_fail git rebase --onto main main topic &&
+ test_must_fail git rebase --apply --onto main main topic &&
echo "Resolved" >F2 &&
git add F2 &&
test-tool chmtime =-60 F1 &&
@@ -254,7 +264,7 @@ test_rerere_autoupdate () {
'
}
-test_rerere_autoupdate
+test_rerere_autoupdate --apply
test_rerere_autoupdate -m
GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
test_rerere_autoupdate -i
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index 6748070df5..43c82d9a33 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -172,19 +172,39 @@ test_expect_success 'failed `merge <branch>` does not crash' '
grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
'
-test_expect_success 'fast-forward merge -c still rewords' '
- git checkout -b fast-forward-merge-c H &&
+test_expect_success 'merge -c commits before rewording and reloads todo-list' '
+ cat >script-from-scratch <<-\EOF &&
+ merge -c E B
+ merge -c H G
+ EOF
+
+ git checkout -b merge-c H &&
(
- set_fake_editor &&
- FAKE_COMMIT_MESSAGE=edited \
- GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
- git rebase -ir @^
+ set_reword_editor &&
+ GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
+ git rebase -i -r D
) &&
- echo edited >expected &&
- git log --pretty=format:%B -1 >actual &&
- test_cmp expected actual
+ check_reworded_commits E H
'
+test_expect_success 'merge -c rewords when a strategy is given' '
+ git checkout -b merge-c-with-strategy H &&
+ write_script git-merge-override <<-\EOF &&
+ echo overridden$1 >G.t
+ git add G.t
+ EOF
+
+ PATH="$PWD:$PATH" \
+ GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
+ GIT_EDITOR="echo edited >>" \
+ git rebase --no-ff -ir -s override -Xxopt E &&
+ test_write_lines overridden--xopt >expect &&
+ test_cmp expect G.t &&
+ test_write_lines H "" edited "" >expect &&
+ git log --format=%B -1 >actual &&
+ test_cmp expect actual
+
+'
test_expect_success 'with a branch tip that was cherry-picked already' '
git checkout -b already-upstream main &&
base="$(git rev-parse --verify HEAD)" &&
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 9d100cd188..4b5b607673 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -158,4 +158,20 @@ test_expect_success 'cherry-pick works with dirty renamed file' '
grep -q "^modified$" renamed
'
+test_expect_success 'advice from failed revert' '
+ test_commit --no-tag "add dream" dream dream &&
+ dream_oid=$(git rev-parse --short HEAD) &&
+ cat <<-EOF >expected &&
+ error: could not revert $dream_oid... add dream
+ hint: After resolving the conflicts, mark them with
+ hint: "git add/rm <pathspec>", then run
+ hint: "git revert --continue".
+ hint: You can instead skip this commit with "git revert --skip".
+ hint: To abort and get back to the state before "git revert",
+ hint: run "git revert --abort".
+ EOF
+ test_commit --append --no-tag "double-add dream" dream dream &&
+ test_must_fail git revert HEAD^ 2>actual &&
+ test_cmp expected actual
+'
test_done
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index 014001b8f3..979e843c65 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh
@@ -47,20 +47,23 @@ test_expect_success 'failed cherry-pick does not advance HEAD' '
test "$head" = "$newhead"
'
-test_expect_success 'advice from failed cherry-pick' "
+test_expect_success 'advice from failed cherry-pick' '
pristine_detach initial &&
- picked=\$(git rev-parse --short picked) &&
+ picked=$(git rev-parse --short picked) &&
cat <<-EOF >expected &&
- error: could not apply \$picked... picked
- hint: after resolving the conflicts, mark the corrected paths
- hint: with 'git add <paths>' or 'git rm <paths>'
- hint: and commit the result with 'git commit'
+ error: could not apply $picked... picked
+ hint: After resolving the conflicts, mark them with
+ hint: "git add/rm <pathspec>", then run
+ hint: "git cherry-pick --continue".
+ hint: You can instead skip this commit with "git cherry-pick --skip".
+ hint: To abort and get back to the state before "git cherry-pick",
+ hint: run "git cherry-pick --abort".
EOF
test_must_fail git cherry-pick picked 2>actual &&
test_cmp expected actual
-"
+'
test_expect_success 'advice from failed cherry-pick --no-commit' "
pristine_detach initial &&
diff --git a/t/t4018/php-enum b/t/t4018/php-enum
new file mode 100644
index 0000000000..91a69c1a2b
--- /dev/null
+++ b/t/t4018/php-enum
@@ -0,0 +1,4 @@
+enum RIGHT: string
+{
+ case Foo = 'ChangeMe';
+}
diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh
index 61ba5f707f..fab351b48a 100755
--- a/t/t4045-diff-relative.sh
+++ b/t/t4045-diff-relative.sh
@@ -162,4 +162,57 @@ check_diff_relative_option subdir file2 true --no-relative --relative
check_diff_relative_option . file2 false --no-relative --relative=subdir
check_diff_relative_option . file2 true --no-relative --relative=subdir
+test_expect_success 'setup diff --relative unmerged' '
+ test_commit zero file0 &&
+ test_commit base subdir/file0 &&
+ git switch -c br1 &&
+ test_commit one file0 &&
+ test_commit sub1 subdir/file0 &&
+ git switch -c br2 base &&
+ test_commit two file0 &&
+ git switch -c br3 &&
+ test_commit sub3 subdir/file0
+'
+
+test_expect_success 'diff --relative without change in subdir' '
+ git switch br2 &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge one &&
+ git -C subdir diff --relative >out &&
+ test_must_be_empty out &&
+ git -C subdir diff --relative --name-only >out &&
+ test_must_be_empty out
+'
+
+test_expect_success 'diff --relative --name-only with change in subdir' '
+ git switch br3 &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge sub1 &&
+ test_write_lines file0 file0 >expected &&
+ git -C subdir diff --relative --name-only >out &&
+ test_cmp expected out
+'
+
+test_expect_failure 'diff --relative with change in subdir' '
+ git switch br3 &&
+ br1_blob=$(git rev-parse --short --verify br1:subdir/file0) &&
+ br3_blob=$(git rev-parse --short --verify br3:subdir/file0) &&
+ test_when_finished "git merge --abort" &&
+ test_must_fail git merge br1 &&
+ cat >expected <<-EOF &&
+ diff --cc file0
+ index $br3_blob,$br1_blob..0000000
+ --- a/file0
+ +++ b/file0
+ @@@ -1,1 -1,1 +1,5 @@@
+ ++<<<<<<< HEAD
+ +sub3
+ ++=======
+ + sub1
+ ++>>>>>>> br1
+ EOF
+ git -C subdir diff --relative >out &&
+ test_cmp expected out
+'
+
test_done
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
index dc7b242697..d86e38abd8 100755
--- a/t/t4060-diff-submodule-option-diff-format.sh
+++ b/t/t4060-diff-submodule-option-diff-format.sh
@@ -361,7 +361,6 @@ test_expect_success 'typechanged submodule(submodule->blob)' '
rm -f sm1 &&
test_create_repo sm1 &&
head6=$(add_file sm1 foo6 foo7)
-fullhead6=$(cd sm1; git rev-parse --verify HEAD)
test_expect_success 'nonexistent commit' '
git diff-index -p --submodule=diff HEAD >actual &&
cat >expected <<-EOF &&
@@ -704,10 +703,26 @@ test_expect_success 'path filter' '
diff_cmp expected actual
'
-commit_file sm2
+cat >.gitmodules <<-EOF
+[submodule "sm2"]
+ path = sm2
+ url = bogus_url
+EOF
+git add .gitmodules
+commit_file sm2 .gitmodules
+
test_expect_success 'given commit' '
git diff-index -p --submodule=diff HEAD^ >actual &&
cat >expected <<-EOF &&
+ diff --git a/.gitmodules b/.gitmodules
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/.gitmodules
+ @@ -0,0 +1,3 @@
+ +[submodule "sm2"]
+ +path = sm2
+ +url = bogus_url
Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 0000000...$head9 (new submodule)
diff --git a/sm2/foo8 b/sm2/foo8
@@ -729,15 +744,21 @@ test_expect_success 'given commit' '
'
test_expect_success 'setup .git file for sm2' '
- (cd sm2 &&
- REAL="$(pwd)/../.real" &&
- mv .git "$REAL" &&
- echo "gitdir: $REAL" >.git)
+ git submodule absorbgitdirs sm2
'
test_expect_success 'diff --submodule=diff with .git file' '
git diff --submodule=diff HEAD^ >actual &&
cat >expected <<-EOF &&
+ diff --git a/.gitmodules b/.gitmodules
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/.gitmodules
+ @@ -0,0 +1,3 @@
+ +[submodule "sm2"]
+ +path = sm2
+ +url = bogus_url
Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 0000000...$head9 (new submodule)
diff --git a/sm2/foo8 b/sm2/foo8
@@ -758,9 +779,67 @@ test_expect_success 'diff --submodule=diff with .git file' '
diff_cmp expected actual
'
+mv sm2 sm2-bak
+
+test_expect_success 'deleted submodule with .git file' '
+ git diff-index -p --submodule=diff HEAD >actual &&
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head9...0000000 (submodule deleted)
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ EOF
+ diff_cmp expected actual
+'
+
+echo submodule-to-blob>sm2
+
+test_expect_success 'typechanged(submodule->blob) submodule with .git file' '
+ git diff-index -p --submodule=diff HEAD >actual &&
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head9...0000000 (submodule deleted)
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 1234567..89abcde
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ diff --git a/sm2 b/sm2
+ new file mode 100644
+ index 1234567..89abcde
+ --- /dev/null
+ +++ b/sm2
+ @@ -0,0 +1 @@
+ +submodule-to-blob
+ EOF
+ diff_cmp expected actual
+'
+
+rm sm2
+mv sm2-bak sm2
+
test_expect_success 'setup nested submodule' '
- git submodule add -f ./sm2 &&
- git commit -a -m "add sm2" &&
git -C sm2 submodule add ../sm2 nested &&
git -C sm2 commit -a -m "nested sub" &&
head10=$(git -C sm2 rev-parse --short --verify HEAD)
@@ -791,6 +870,7 @@ test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' '
test_expect_success 'diff --submodule=diff recurses into nested submodules' '
cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
Submodule sm2 contains modified content
Submodule sm2 $head9..$head10:
diff --git a/sm2/.gitmodules b/sm2/.gitmodules
@@ -830,4 +910,67 @@ test_expect_success 'diff --submodule=diff recurses into nested submodules' '
diff_cmp expected actual
'
+(cd sm2; commit_file nested)
+commit_file sm2
+head12=$(cd sm2; git rev-parse --short --verify HEAD)
+
+mv sm2 sm2-bak
+
+test_expect_success 'diff --submodule=diff recurses into deleted nested submodules' '
+ cat >expected <<-EOF &&
+ Submodule sm1 $head7...0000000 (submodule deleted)
+ Submodule sm2 $head12...0000000 (submodule deleted)
+ diff --git a/sm2/.gitmodules b/sm2/.gitmodules
+ deleted file mode 100644
+ index 3a816b8..0000000
+ --- a/sm2/.gitmodules
+ +++ /dev/null
+ @@ -1,3 +0,0 @@
+ -[submodule "nested"]
+ - path = nested
+ - url = ../sm2
+ diff --git a/sm2/foo8 b/sm2/foo8
+ deleted file mode 100644
+ index db9916b..0000000
+ --- a/sm2/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/foo9 b/sm2/foo9
+ deleted file mode 100644
+ index 9c3b4f6..0000000
+ --- a/sm2/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ Submodule nested $head11...0000000 (submodule deleted)
+ diff --git a/sm2/nested/file b/sm2/nested/file
+ deleted file mode 100644
+ index ca281f5..0000000
+ --- a/sm2/nested/file
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -nested content
+ diff --git a/sm2/nested/foo8 b/sm2/nested/foo8
+ deleted file mode 100644
+ index db9916b..0000000
+ --- a/sm2/nested/foo8
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo8
+ diff --git a/sm2/nested/foo9 b/sm2/nested/foo9
+ deleted file mode 100644
+ index 9c3b4f6..0000000
+ --- a/sm2/nested/foo9
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -foo9
+ EOF
+ git diff --submodule=diff >actual 2>err &&
+ test_must_be_empty err &&
+ diff_cmp expected actual
+'
+
+mv sm2-bak sm2
+
test_done
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
index 65147efdea..cc3aa3314a 100755
--- a/t/t4108-apply-threeway.sh
+++ b/t/t4108-apply-threeway.sh
@@ -230,4 +230,49 @@ test_expect_success 'apply with --3way --cached and conflicts' '
test_cmp expect.diff actual.diff
'
+test_expect_success 'apply binary file patch' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --binary >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply bin.diff
+'
+
+test_expect_success 'apply binary file patch with 3way' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --binary >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply --3way --index bin.diff
+'
+
+test_expect_success 'apply full-index patch with 3way' '
+ git reset --hard main &&
+ cp "$TEST_DIRECTORY/test-binary-1.png" bin.png &&
+ git add bin.png &&
+ git commit -m "add binary file" &&
+
+ cp "$TEST_DIRECTORY/test-binary-2.png" bin.png &&
+
+ git diff --full-index >bin.diff &&
+ git reset --hard &&
+
+ # Apply must succeed.
+ git apply --3way --index bin.diff
+'
+
test_done
diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh
index d2dfcf164e..0141f36e33 100755
--- a/t/t4210-log-i18n.sh
+++ b/t/t4210-log-i18n.sh
@@ -131,4 +131,11 @@ do
fi
done
+test_expect_success 'log shows warning when conversion fails' '
+ enc=this-encoding-does-not-exist &&
+ git log -1 --encoding=$enc 2>err &&
+ echo "warning: unable to reencode commit to ${SQ}${enc}${SQ}" >expect &&
+ test_cmp expect err
+'
+
test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index b02838750e..673baa5c3c 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -8,6 +8,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. "$TEST_DIRECTORY"/lib-bundle.sh
. "$TEST_DIRECTORY"/lib-bitmap.sh
+# t5310 deals only with single-pack bitmaps, so don't write MIDX bitmaps in
+# their place.
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
objpath () {
echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')"
}
@@ -25,93 +29,10 @@ has_any () {
grep -Ff "$1" "$2"
}
-# To ensure the logic for "maximal commits" is exercised, make
-# the repository a bit more complicated.
-#
-# other second
-# * *
-# (99 commits) (99 commits)
-# * *
-# |\ /|
-# | * octo-other octo-second * |
-# |/|\_________ ____________/|\|
-# | \ \/ __________/ |
-# | | ________/\ / |
-# * |/ * merge-right *
-# | _|__________/ \____________ |
-# |/ | \|
-# (l1) * * merge-left * (r1)
-# | / \________________________ |
-# |/ \|
-# (l2) * * (r2)
-# \___________________________ |
-# \|
-# * (base)
-#
-# We only push bits down the first-parent history, which
-# makes some of these commits unimportant!
-#
-# The important part for the maximal commit algorithm is how
-# the bitmasks are extended. Assuming starting bit positions
-# for second (bit 0) and other (bit 1), the bitmasks at the
-# end should be:
-#
-# second: 1 (maximal, selected)
-# other: 01 (maximal, selected)
-# (base): 11 (maximal)
-#
-# This complicated history was important for a previous
-# version of the walk that guarantees never walking a
-# commit multiple times. That goal might be important
-# again, so preserve this complicated case. For now, this
-# test will guarantee that the bitmaps are computed
-# correctly, even with the repeat calculations.
-
-test_expect_success 'setup repo with moderate-sized history' '
- test_commit_bulk --id=file 10 &&
- git branch -M second &&
- git checkout -b other HEAD~5 &&
- test_commit_bulk --id=side 10 &&
-
- # add complicated history setup, including merges and
- # ambiguous merge-bases
-
- git checkout -b merge-left other~2 &&
- git merge second~2 -m "merge-left" &&
-
- git checkout -b merge-right second~1 &&
- git merge other~1 -m "merge-right" &&
-
- git checkout -b octo-second second &&
- git merge merge-left merge-right -m "octopus-second" &&
-
- git checkout -b octo-other other &&
- git merge merge-left merge-right -m "octopus-other" &&
-
- git checkout other &&
- git merge octo-other -m "pull octopus" &&
-
- git checkout second &&
- git merge octo-second -m "pull octopus" &&
-
- # Remove these branches so they are not selected
- # as bitmap tips
- git branch -D merge-left &&
- git branch -D merge-right &&
- git branch -D octo-other &&
- git branch -D octo-second &&
-
- # add padding to make these merges less interesting
- # and avoid having them selected for bitmaps
- test_commit_bulk --id=file 100 &&
- git checkout other &&
- test_commit_bulk --id=side 100 &&
- git checkout second &&
-
- bitmaptip=$(git rev-parse second) &&
- blob=$(echo tagged-blob | git hash-object -w --stdin) &&
- git tag tagged-blob $blob &&
- git config repack.writebitmaps true
+setup_bitmap_history
+
+test_expect_success 'setup writing bitmaps during repack' '
+ git config repack.writeBitmaps true
'
test_expect_success 'full repack creates bitmaps' '
@@ -123,109 +44,7 @@ test_expect_success 'full repack creates bitmaps' '
grep "\"key\":\"num_maximal_commits\",\"value\":\"107\"" trace
'
-test_expect_success 'rev-list --test-bitmap verifies bitmaps' '
- git rev-list --test-bitmap HEAD
-'
-
-rev_list_tests_head () {
- test_expect_success "counting commits via bitmap ($state, $branch)" '
- git rev-list --count $branch >expect &&
- git rev-list --use-bitmap-index --count $branch >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "counting partial commits via bitmap ($state, $branch)" '
- git rev-list --count $branch~5..$branch >expect &&
- git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "counting commits with limit ($state, $branch)" '
- git rev-list --count -n 1 $branch >expect &&
- git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "counting non-linear history ($state, $branch)" '
- git rev-list --count other...second >expect &&
- git rev-list --use-bitmap-index --count other...second >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "counting commits with limiting ($state, $branch)" '
- git rev-list --count $branch -- 1.t >expect &&
- git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "counting objects via bitmap ($state, $branch)" '
- git rev-list --count --objects $branch >expect &&
- git rev-list --use-bitmap-index --count --objects $branch >actual &&
- test_cmp expect actual
- '
-
- test_expect_success "enumerate commits ($state, $branch)" '
- git rev-list --use-bitmap-index $branch >actual &&
- git rev-list $branch >expect &&
- test_bitmap_traversal --no-confirm-bitmaps expect actual
- '
-
- test_expect_success "enumerate --objects ($state, $branch)" '
- git rev-list --objects --use-bitmap-index $branch >actual &&
- git rev-list --objects $branch >expect &&
- test_bitmap_traversal expect actual
- '
-
- test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
- git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
- grep $blob actual
- '
-}
-
-rev_list_tests () {
- state=$1
-
- for branch in "second" "other"
- do
- rev_list_tests_head
- done
-}
-
-rev_list_tests 'full bitmap'
-
-test_expect_success 'clone from bitmapped repository' '
- git clone --no-local --bare . clone.git &&
- git rev-parse HEAD >expect &&
- git --git-dir=clone.git rev-parse HEAD >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'partial clone from bitmapped repository' '
- test_config uploadpack.allowfilter true &&
- git clone --no-local --bare --filter=blob:none . partial-clone.git &&
- (
- cd partial-clone.git &&
- pack=$(echo objects/pack/*.pack) &&
- git verify-pack -v "$pack" >have &&
- awk "/blob/ { print \$1 }" <have >blobs &&
- # we expect this single blob because of the direct ref
- git rev-parse refs/tags/tagged-blob >expect &&
- test_cmp expect blobs
- )
-'
-
-test_expect_success 'setup further non-bitmapped commits' '
- test_commit_bulk --id=further 10
-'
-
-rev_list_tests 'partial bitmap'
-
-test_expect_success 'fetch (partial bitmap)' '
- git --git-dir=clone.git fetch origin second:second &&
- git rev-parse HEAD >expect &&
- git --git-dir=clone.git rev-parse HEAD >actual &&
- test_cmp expect actual
-'
+basic_bitmap_tests
test_expect_success 'incremental repack fails when bitmaps are requested' '
test_commit more-1 &&
@@ -461,40 +280,6 @@ test_expect_success 'truncated bitmap fails gracefully (cache)' '
test_i18ngrep corrupted.bitmap.index stderr
'
-test_expect_success 'enumerating progress counts pack-reused objects' '
- count=$(git rev-list --objects --all --count) &&
- git repack -adb &&
-
- # check first with only reused objects; confirm that our progress
- # showed the right number, and also that we did pack-reuse as expected.
- # Check only the final "done" line of the meter (there may be an
- # arbitrary number of intermediate lines ending with CR).
- GIT_PROGRESS_DELAY=0 \
- git pack-objects --all --stdout --progress \
- </dev/null >/dev/null 2>stderr &&
- grep "Enumerating objects: $count, done" stderr &&
- grep "pack-reused $count" stderr &&
-
- # now the same but with one non-reused object
- git commit --allow-empty -m "an extra commit object" &&
- GIT_PROGRESS_DELAY=0 \
- git pack-objects --all --stdout --progress \
- </dev/null >/dev/null 2>stderr &&
- grep "Enumerating objects: $((count+1)), done" stderr &&
- grep "pack-reused $count" stderr
-'
-
-# have_delta <obj> <expected_base>
-#
-# Note that because this relies on cat-file, it might find _any_ copy of an
-# object in the repository. The caller is responsible for making sure
-# there's only one (e.g., via "repack -ad", or having just fetched a copy).
-have_delta () {
- echo $2 >expect &&
- echo $1 | git cat-file --batch-check="%(deltabase)" >actual &&
- test_cmp expect actual
-}
-
# Create a state of history with these properties:
#
# - refs that allow a client to fetch some new history, while sharing some old
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index af88f805aa..295c5bd94d 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -5,6 +5,25 @@ test_description='commit graph'
GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
+test_expect_success 'usage' '
+ test_expect_code 129 git commit-graph write blah 2>err &&
+ test_expect_code 129 git commit-graph write verify
+'
+
+test_expect_success 'usage shown without sub-command' '
+ test_expect_code 129 git commit-graph 2>err &&
+ ! grep error: err
+'
+
+test_expect_success 'usage shown with an error on unknown sub-command' '
+ cat >expect <<-\EOF &&
+ error: unrecognized subcommand: unknown
+ EOF
+ test_expect_code 129 git commit-graph unknown 2>stderr &&
+ grep error stderr >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'setup full repo' '
mkdir full &&
cd "$TRASH_DIRECTORY/full" &&
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index 3d4d9f10c3..bb04f0f23b 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -201,6 +201,34 @@ test_expect_success 'write midx with twelve packs' '
compare_results_with_midx "twelve packs"
+test_expect_success 'multi-pack-index *.rev cleanup with --object-dir' '
+ git init repo &&
+ git clone -s repo alternate &&
+
+ test_when_finished "rm -rf repo alternate" &&
+
+ (
+ cd repo &&
+ test_commit base &&
+ git repack -d
+ ) &&
+
+ ours="alternate/.git/objects/pack/multi-pack-index-123.rev" &&
+ theirs="repo/.git/objects/pack/multi-pack-index-abc.rev" &&
+ touch "$ours" "$theirs" &&
+
+ (
+ cd alternate &&
+ git multi-pack-index --object-dir ../repo/.git/objects write
+ ) &&
+
+ # writing a midx in "repo" should not remove the .rev file in the
+ # alternate
+ test_path_is_file repo/.git/objects/pack/multi-pack-index &&
+ test_path_is_file $ours &&
+ test_path_is_missing $theirs
+'
+
test_expect_success 'warn on improper hash version' '
git init --object-format=sha1 sha1 &&
(
@@ -277,6 +305,23 @@ test_expect_success 'midx picks objects from preferred pack' '
)
'
+test_expect_success 'preferred packs must be non-empty' '
+ test_when_finished rm -rf preferred.git &&
+ git init preferred.git &&
+ (
+ cd preferred.git &&
+
+ test_commit base &&
+ git repack -ad &&
+
+ empty="$(git pack-objects $objdir/pack/pack </dev/null)" &&
+
+ test_must_fail git multi-pack-index write \
+ --preferred-pack=pack-$empty.pack 2>err &&
+ grep "with no objects" err
+ )
+'
+
test_expect_success 'verify multi-pack-index success' '
git multi-pack-index verify --object-dir=$objdir
'
@@ -487,7 +532,8 @@ test_expect_success 'repack preserves multi-pack-index when creating packs' '
compare_results_with_midx "after repack"
test_expect_success 'multi-pack-index and pack-bitmap' '
- git -c repack.writeBitmaps=true repack -ad &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -c repack.writeBitmaps=true repack -ad &&
git multi-pack-index write &&
git rev-list --test-bitmap HEAD
'
@@ -537,7 +583,15 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '
idx64=objects64/pack/test-64-$pack64.idx &&
chmod u+w $idx64 &&
corrupt_data $idx64 $(test_oid idxoff) "\02" &&
- midx64=$(git multi-pack-index --object-dir=objects64 write) &&
+ # objects64 is not a real repository, but can serve as an alternate
+ # anyway so we can write a MIDX into it
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+ ( cd ../objects64 && pwd ) >.git/objects/info/alternates &&
+ midx64=$(git multi-pack-index --object-dir=../objects64 write)
+ ) &&
midx_read_expect 1 63 5 objects64 " large-offsets"
'
@@ -842,4 +896,9 @@ test_expect_success 'usage shown without sub-command' '
! test_i18ngrep "unrecognized subcommand" err
'
+test_expect_success 'complains when run outside of a repository' '
+ nongit test_must_fail git multi-pack-index write 2>err &&
+ grep "not a git repository" err
+'
+
test_done
diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh
index 8b01793845..8dbbcc5e51 100755
--- a/t/t5323-pack-redundant.sh
+++ b/t/t5323-pack-redundant.sh
@@ -114,9 +114,9 @@ test_expect_success 'setup main repo' '
create_commits_in "$main_repo" A B C D E F G H I J K L M N O P Q R
'
-test_expect_success 'master: pack-redundant works with no packfile' '
+test_expect_success 'main: pack-redundant works with no packfile' '
(
- cd "$master_repo" &&
+ cd "$main_repo" &&
cat >expect <<-EOF &&
fatal: Zero packs found!
EOF
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
new file mode 100755
index 0000000000..4ad7c2c969
--- /dev/null
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -0,0 +1,286 @@
+#!/bin/sh
+
+test_description='exercise basic multi-pack bitmap functionality'
+. ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+
+# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# automatic ones.
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
+# midx_pack_source <obj>
+midx_pack_source () {
+ test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
+}
+
+setup_bitmap_history
+
+test_expect_success 'enable core.multiPackIndex' '
+ git config core.multiPackIndex true
+'
+
+test_expect_success 'create single-pack midx with bitmaps' '
+ git repack -ad &&
+ git multi-pack-index write --bitmap &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests
+
+test_expect_success 'create new additional packs' '
+ for i in $(test_seq 1 16)
+ do
+ test_commit "$i" &&
+ git repack -d || return 1
+ done &&
+
+ git checkout -b other2 HEAD~8 &&
+ for i in $(test_seq 1 8)
+ do
+ test_commit "side-$i" &&
+ git repack -d || return 1
+ done &&
+ git checkout second
+'
+
+test_expect_success 'create multi-pack midx with bitmaps' '
+ git multi-pack-index write --bitmap &&
+
+ ls $objdir/pack/pack-*.pack >packs &&
+ test_line_count = 25 packs &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests
+
+test_expect_success '--no-bitmap is respected when bitmaps exist' '
+ git multi-pack-index write --bitmap &&
+
+ test_commit respect--no-bitmap &&
+ git repack -d &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+
+ git multi-pack-index write --no-bitmap &&
+
+ test_path_is_file $midx &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).rev
+'
+
+test_expect_success 'setup midx with base from later pack' '
+ # Write a and b so that "a" is a delta on top of base "b", since Git
+ # prefers to delete contents out of a base rather than add to a shorter
+ # object.
+ test_seq 1 128 >a &&
+ test_seq 1 130 >b &&
+
+ git add a b &&
+ git commit -m "initial commit" &&
+
+ a=$(git rev-parse HEAD:a) &&
+ b=$(git rev-parse HEAD:b) &&
+
+ # In the first pack, "a" is stored as a delta to "b".
+ p1=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $a
+ $b
+ EOF
+ ) &&
+
+ # In the second pack, "a" is missing, and "b" is not a delta nor base to
+ # any other object.
+ p2=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $b
+ $(git rev-parse HEAD)
+ $(git rev-parse HEAD^{tree})
+ EOF
+ ) &&
+
+ git prune-packed &&
+ # Use the second pack as the preferred source, so that "b" occurs
+ # earlier in the MIDX object order, rendering "a" unusable for pack
+ # reuse.
+ git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
+
+ have_delta $a $b &&
+ test $(midx_pack_source $a) != $(midx_pack_source $b)
+'
+
+rev_list_tests 'full bitmap with backwards delta'
+
+test_expect_success 'clone with bitmaps enabled' '
+ git clone --no-local --bare . clone-reverse-delta.git &&
+ test_when_finished "rm -fr clone-reverse-delta.git" &&
+
+ git rev-parse HEAD >expect &&
+ git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
+ test_cmp expect actual
+'
+
+bitmap_reuse_tests() {
+ from=$1
+ to=$2
+
+ test_expect_success "setup pack reuse tests ($from -> $to)" '
+ rm -fr repo &&
+ git init repo &&
+ (
+ cd repo &&
+ test_commit_bulk 16 &&
+ git tag old-tip &&
+
+ git config core.multiPackIndex true &&
+ if test "MIDX" = "$from"
+ then
+ git repack -Ad &&
+ git multi-pack-index write --bitmap
+ else
+ git repack -Adb
+ fi
+ )
+ '
+
+ test_expect_success "build bitmap from existing ($from -> $to)" '
+ (
+ cd repo &&
+ test_commit_bulk --id=further 16 &&
+ git tag new-tip &&
+
+ if test "MIDX" = "$to"
+ then
+ git repack -d &&
+ git multi-pack-index write --bitmap
+ else
+ git repack -Adb
+ fi
+ )
+ '
+
+ test_expect_success "verify resulting bitmaps ($from -> $to)" '
+ (
+ cd repo &&
+ git for-each-ref &&
+ git rev-list --test-bitmap refs/tags/old-tip &&
+ git rev-list --test-bitmap refs/tags/new-tip
+ )
+ '
+}
+
+bitmap_reuse_tests 'pack' 'MIDX'
+bitmap_reuse_tests 'MIDX' 'pack'
+bitmap_reuse_tests 'MIDX' 'MIDX'
+
+test_expect_success 'missing object closure fails gracefully' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit loose &&
+ test_commit packed &&
+
+ # Do not pass "--revs"; we want a pack without the "loose"
+ # commit.
+ git pack-objects $objdir/pack/pack <<-EOF &&
+ $(git rev-parse packed)
+ EOF
+
+ test_must_fail git multi-pack-index write --bitmap 2>err &&
+ grep "doesn.t have full closure" err &&
+ test_path_is_missing $midx
+ )
+'
+
+test_expect_success 'setup partial bitmaps' '
+ test_commit packed &&
+ git repack &&
+ test_commit loose &&
+ git multi-pack-index write --bitmap 2>err &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev
+'
+
+basic_bitmap_tests HEAD~
+
+test_expect_success 'removing a MIDX clears stale bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+ test_commit base &&
+ git repack &&
+ git multi-pack-index write --bitmap &&
+
+ # Write a MIDX and bitmap; remove the MIDX but leave the bitmap.
+ stale_bitmap=$midx-$(midx_checksum $objdir).bitmap &&
+ stale_rev=$midx-$(midx_checksum $objdir).rev &&
+ rm $midx &&
+
+ # Then write a new MIDX.
+ test_commit new &&
+ git repack &&
+ git multi-pack-index write --bitmap &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+ test_path_is_missing $stale_bitmap &&
+ test_path_is_missing $stale_rev
+ )
+'
+
+test_expect_success 'pack.preferBitmapTips' '
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit_bulk --message="%s" 103 &&
+
+ git log --format="%H" >commits.raw &&
+ sort <commits.raw >commits &&
+
+ git log --format="create refs/tags/%s %H" HEAD >refs &&
+ git update-ref --stdin <refs &&
+
+ git multi-pack-index write --bitmap &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_file $midx-$(midx_checksum $objdir).rev &&
+
+ test-tool bitmap list-commits | sort >bitmaps &&
+ comm -13 bitmaps commits >before &&
+ test_line_count = 1 before &&
+
+ perl -ne "printf(\"create refs/tags/include/%d \", $.); print" \
+ <before | git update-ref --stdin &&
+
+ rm -fr $midx-$(midx_checksum $objdir).bitmap &&
+ rm -fr $midx-$(midx_checksum $objdir).rev &&
+ rm -fr $midx &&
+
+ git -c pack.preferBitmapTips=refs/tags/include \
+ multi-pack-index write --bitmap &&
+ test-tool bitmap list-commits | sort >bitmaps &&
+ comm -13 bitmaps commits >after &&
+
+ ! test_cmp before after
+ )
+'
+
+test_done
diff --git a/t/t5582-fetch-negative-refspec.sh b/t/t5582-fetch-negative-refspec.sh
index e5d2e79ad3..7a80e47c2b 100755
--- a/t/t5582-fetch-negative-refspec.sh
+++ b/t/t5582-fetch-negative-refspec.sh
@@ -105,7 +105,6 @@ test_expect_success "fetch with negative pattern refspec does not expand prefix"
'
test_expect_success "fetch with negative refspec avoids duplicate conflict" '
- cd "$D" &&
(
cd one &&
git branch dups/a &&
diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh
index 3a595c0f82..d822153e4d 100755
--- a/t/t5606-clone-options.sh
+++ b/t/t5606-clone-options.sh
@@ -16,6 +16,18 @@ test_expect_success 'setup' '
'
+test_expect_success 'submodule.stickyRecursiveClone flag manipulates submodule.recurse value' '
+
+ test_config_global submodule.stickyRecursiveClone true &&
+ git clone --recurse-submodules parent clone_recurse_true &&
+ test_cmp_config -C clone_recurse_true true submodule.recurse &&
+
+ test_config_global submodule.stickyRecursiveClone false &&
+ git clone --recurse-submodules parent clone_recurse_false &&
+ test_expect_code 1 git -C clone_recurse_false config --get submodule.recurse
+
+'
+
test_expect_success 'clone -o' '
git clone -o foo parent clone-o &&
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 78de1ff2ad..d3687b1a2e 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -27,9 +27,9 @@ test_expect_success 'list refs with git:// using protocol v2' '
ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
# Client requested to use protocol v2
- grep "git> .*\\\0\\\0version=2\\\0$" log &&
+ grep "ls-remote> .*\\\0\\\0version=2\\\0$" log &&
# Server responded using protocol v2
- grep "git< version 2" log &&
+ grep "ls-remote< version 2" log &&
git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
test_cmp expect actual
@@ -151,7 +151,7 @@ test_expect_success 'list refs with file:// using protocol v2' '
ls-remote --symref "file://$(pwd)/file_parent" >actual &&
# Server responded using protocol v2
- grep "git< version 2" log &&
+ grep "ls-remote< version 2" log &&
git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
test_cmp expect actual
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index e9e471621d..220098523a 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -40,6 +40,30 @@ write_command () {
fi
}
+# Write a complete fetch command to stdout, suitable for use with `test-tool
+# pkt-line`. "want-ref", "want", and "have" lines are read from stdin.
+#
+# Examples:
+#
+# write_fetch_command <<-EOF
+# want-ref refs/heads/main
+# have $(git rev-parse a)
+# EOF
+#
+# write_fetch_command <<-EOF
+# want $(git rev-parse b)
+# have $(git rev-parse a)
+# EOF
+#
+write_fetch_command () {
+ write_command fetch &&
+ echo "0001" &&
+ echo "no-progress" &&
+ cat &&
+ echo "done" &&
+ echo "0000"
+}
+
# c(o/foo) d(o/bar)
# \ /
# b e(baz) f(main)
@@ -77,15 +101,11 @@ test_expect_success 'config controls ref-in-want advertisement' '
'
test_expect_success 'invalid want-ref line' '
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/non-existent
- done
- 0000
EOF
+ test-tool pkt-line pack <pkt >in &&
test_must_fail test-tool serve-v2 --stateless-rpc 2>out <in &&
grep "unknown ref" out
'
@@ -97,16 +117,11 @@ test_expect_success 'basic want-ref' '
EOF
git rev-parse f >expected_commits &&
- oid=$(git rev-parse a) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/main
- have $oid
- done
- 0000
+ have $(git rev-parse a)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -121,17 +136,12 @@ test_expect_success 'multiple want-ref lines' '
EOF
git rev-parse c d >expected_commits &&
- oid=$(git rev-parse b) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/o/foo
want-ref refs/heads/o/bar
- have $oid
- done
- 0000
+ have $(git rev-parse b)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -144,16 +154,12 @@ test_expect_success 'mix want and want-ref' '
EOF
git rev-parse e f >expected_commits &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/main
want $(git rev-parse e)
have $(git rev-parse a)
- done
- 0000
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -166,16 +172,11 @@ test_expect_success 'want-ref with ref we already have commit for' '
EOF
>expected_commits &&
- oid=$(git rev-parse c) &&
- test-tool pkt-line pack >in <<-EOF &&
- $(write_command fetch)
- 0001
- no-progress
+ write_fetch_command >pkt <<-EOF &&
want-ref refs/heads/o/foo
- have $oid
- done
- 0000
+ have $(git rev-parse c)
EOF
+ test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in &&
check_output
@@ -298,6 +299,141 @@ test_expect_success 'fetching with wildcard that matches multiple refs' '
grep "want-ref refs/heads/o/bar" log
'
+REPO="$(pwd)/repo-ns"
+
+test_expect_success 'setup namespaced repo' '
+ (
+ git init -b main "$REPO" &&
+ cd "$REPO" &&
+ test_commit a &&
+ test_commit b &&
+ git checkout a &&
+ test_commit c &&
+ git checkout a &&
+ test_commit d &&
+ git update-ref refs/heads/ns-no b &&
+ git update-ref refs/namespaces/ns/refs/heads/ns-yes c &&
+ git update-ref refs/namespaces/ns/refs/heads/hidden d
+ ) &&
+ git -C "$REPO" config uploadpack.allowRefInWant true
+'
+
+test_expect_success 'with namespace: want-ref is considered relative to namespace' '
+ wanted_ref=refs/heads/ns-yes &&
+
+ oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'with namespace: want-ref outside namespace is unknown' '
+ wanted_ref=refs/heads/ns-no &&
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test_must_fail env GIT_NAMESPACE=ns \
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ grep "unknown ref" out
+'
+
+# Cross-check refs/heads/ns-no indeed exists
+test_expect_success 'without namespace: want-ref outside namespace succeeds' '
+ wanted_ref=refs/heads/ns-no &&
+
+ oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'with namespace: hideRefs is matched, relative to namespace' '
+ wanted_ref=refs/heads/hidden &&
+ git -C "$REPO" config transfer.hideRefs $wanted_ref &&
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test_must_fail env GIT_NAMESPACE=ns \
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ grep "unknown ref" out
+'
+
+# Cross-check refs/heads/hidden indeed exists
+test_expect_success 'with namespace: want-ref succeeds if hideRefs is removed' '
+ wanted_ref=refs/heads/hidden &&
+ git -C "$REPO" config --unset transfer.hideRefs $wanted_ref &&
+
+ oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+test_expect_success 'without namespace: relative hideRefs does not match' '
+ wanted_ref=refs/namespaces/ns/refs/heads/hidden &&
+ git -C "$REPO" config transfer.hideRefs refs/heads/hidden &&
+
+ oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
+ cat >expected_refs <<-EOF &&
+ $oid $wanted_ref
+ EOF
+ cat >expected_commits <<-EOF &&
+ $oid
+ $(git -C "$REPO" rev-parse a)
+ EOF
+
+ write_fetch_command >pkt <<-EOF &&
+ want-ref $wanted_ref
+ EOF
+ test-tool pkt-line pack <pkt >in &&
+
+ test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
+ check_output
+'
+
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index 12def7bcbf..ef849e5bc8 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -169,4 +169,35 @@ test_expect_success 'rev-list --count --objects' '
test_line_count = $count actual
'
+test_expect_success 'rev-list --unsorted-input results in different sorting' '
+ git rev-list --unsorted-input HEAD HEAD~ >first &&
+ git rev-list --unsorted-input HEAD~ HEAD >second &&
+ ! test_cmp first second &&
+ sort first >first.sorted &&
+ sort second >second.sorted &&
+ test_cmp first.sorted second.sorted
+'
+
+test_expect_success 'rev-list --unsorted-input incompatible with --no-walk' '
+ cat >expect <<-EOF &&
+ fatal: --no-walk is incompatible with --unsorted-input
+ EOF
+ test_must_fail git rev-list --unsorted-input --no-walk HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --unsorted-input --no-walk=sorted HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --unsorted-input --no-walk=unsorted HEAD 2>error &&
+ test_cmp expect error &&
+
+ cat >expect <<-EOF &&
+ fatal: --unsorted-input is incompatible with --no-walk
+ EOF
+ test_must_fail git rev-list --no-walk --unsorted-input HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --no-walk=sorted --unsorted-input HEAD 2>error &&
+ test_cmp expect error &&
+ test_must_fail git rev-list --no-walk=unsorted --unsorted-input HEAD 2>error &&
+ test_cmp expect error
+'
+
test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 0d2e062f79..80679d5e12 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -59,18 +59,25 @@ test_atom() {
# Automatically test "contents:size" atom after testing "contents"
if test "$2" = "contents"
then
- case $(git cat-file -t "$ref") in
- tag)
- # We cannot use $3 as it expects sanitize_pgp to run
- expect=$(git cat-file tag $ref | tail -n +6 | wc -c) ;;
- tree | blob)
- expect='' ;;
- commit)
- expect=$(printf '%s' "$3" | wc -c) ;;
- esac
- # Leave $expect unquoted to lose possible leading whitespaces
- echo $expect >expected
+ # for commit leg, $3 is changed there
+ expect=$(printf '%s' "$3" | wc -c)
test_expect_${4:-success} $PREREQ "basic atom: $1 contents:size" '
+ type=$(git cat-file -t "$ref") &&
+ case $type in
+ tag)
+ # We cannot use $3 as it expects sanitize_pgp to run
+ git cat-file tag $ref >out &&
+ expect=$(tail -n +6 out | wc -c) &&
+ rm -f out ;;
+ tree | blob)
+ expect="" ;;
+ commit)
+ : "use the calculated expect" ;;
+ *)
+ BUG "unknown object type" ;;
+ esac &&
+ # Leave $expect unquoted to lose possible leading whitespaces
+ echo $expect >expected &&
git for-each-ref --format="%(contents:size)" "$ref" >actual &&
test_cmp expected actual
'
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 54c2082acb..8dd0f98812 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -270,7 +270,7 @@ EOF
test_expect_success 'commit --fixup provides correct one-line commit message' '
commit_for_rebase_autosquash_setup &&
- git commit --fixup HEAD~1 &&
+ EDITOR="echo ignored >>" git commit --fixup HEAD~1 &&
commit_msg_is "fixup! target message subject line"
'
@@ -281,6 +281,13 @@ test_expect_success 'commit --fixup -m"something" -m"extra"' '
extra"
'
+test_expect_success 'commit --fixup --edit' '
+ commit_for_rebase_autosquash_setup &&
+ EDITOR="printf \"something\nextra\" >>" git commit --fixup HEAD~1 --edit &&
+ commit_msg_is "fixup! target message subject linesomething
+extra"
+'
+
get_commit_msg () {
rev="$1" &&
git log -1 --pretty=format:"%B" "$rev"
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 25b235c063..98eda3bfeb 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -63,13 +63,14 @@ test_expect_success 'objects in packs marked .keep are not repacked' '
test_expect_success 'writing bitmaps via command-line can duplicate .keep objects' '
# build on $oid, $packid, and .keep state from previous
- git repack -Adbl &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 git repack -Adbl &&
test_has_duplicate_object true
'
test_expect_success 'writing bitmaps via config can duplicate .keep objects' '
# build on $oid, $packid, and .keep state from previous
- git -c repack.writebitmaps=true repack -Adl &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -c repack.writebitmaps=true repack -Adl &&
test_has_duplicate_object true
'
@@ -189,7 +190,9 @@ test_expect_success 'repack --keep-pack' '
test_expect_success 'bitmaps are created by default in bare repos' '
git clone --bare .git bare.git &&
- git -C bare.git repack -ad &&
+ rm -f bare.git/objects/pack/*.bitmap &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -C bare.git repack -ad &&
bitmap=$(ls bare.git/objects/pack/*.bitmap) &&
test_path_is_file "$bitmap"
'
@@ -200,7 +203,8 @@ test_expect_success 'incremental repack does not complain' '
'
test_expect_success 'bitmaps can be disabled on bare repos' '
- git -c repack.writeBitmaps=false -C bare.git repack -ad &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -c repack.writeBitmaps=false -C bare.git repack -ad &&
bitmap=$(ls bare.git/objects/pack/*.bitmap || :) &&
test -z "$bitmap"
'
@@ -211,7 +215,8 @@ test_expect_success 'no bitmaps created if .keep files present' '
keep=${pack%.pack}.keep &&
test_when_finished "rm -f \"\$keep\"" &&
>"$keep" &&
- git -C bare.git repack -ad 2>stderr &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -C bare.git repack -ad 2>stderr &&
test_must_be_empty stderr &&
find bare.git/objects/pack/ -type f -name "*.bitmap" >actual &&
test_must_be_empty actual
@@ -222,7 +227,8 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' '
blob=$(test-tool genrandom big $((1024*1024)) |
git -C bare.git hash-object -w --stdin) &&
git -C bare.git update-ref refs/tags/big $blob &&
- git -C bare.git repack -ad 2>stderr &&
+ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
+ git -C bare.git repack -ad 2>stderr &&
test_must_be_empty stderr &&
find bare.git/objects/pack -type f -name "*.bitmap" >actual &&
test_must_be_empty actual
diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
index 828cb3ba58..3172f5b936 100755
--- a/t/t7814-grep-recurse-submodules.sh
+++ b/t/t7814-grep-recurse-submodules.sh
@@ -8,6 +8,9 @@ submodules.
. ./test-lib.sh
+GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1
+export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB
+
test_expect_success 'setup directory structure and submodule' '
echo "(1|2)d(3|4)" >a &&
mkdir b &&
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 58f46c77e6..36a4218745 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -20,6 +20,18 @@ test_xmllint () {
fi
}
+test_lazy_prereq SYSTEMD_ANALYZE '
+ systemd-analyze --help >out &&
+ grep verify out
+'
+
+test_systemd_analyze_verify () {
+ if test_have_prereq SYSTEMD_ANALYZE
+ then
+ systemd-analyze verify "$@"
+ fi
+}
+
test_expect_success 'help text' '
test_expect_code 129 git maintenance -h 2>err &&
test_i18ngrep "usage: git maintenance <subcommand>" err &&
@@ -492,8 +504,21 @@ test_expect_success !MINGW 'register and unregister with regex metacharacters' '
maintenance.repo "$(pwd)/$META"
'
+test_expect_success 'start --scheduler=<scheduler>' '
+ test_expect_code 129 git maintenance start --scheduler=foo 2>err &&
+ test_i18ngrep "unrecognized --scheduler argument" err &&
+
+ test_expect_code 129 git maintenance start --no-scheduler 2>err &&
+ test_i18ngrep "unknown option" err &&
+
+ test_expect_code 128 \
+ env GIT_TEST_MAINT_SCHEDULER="launchctl:true,schtasks:true" \
+ git maintenance start --scheduler=crontab 2>err &&
+ test_i18ngrep "fatal: crontab scheduler is not available" err
+'
+
test_expect_success 'start from empty cron table' '
- GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -516,7 +541,7 @@ test_expect_success 'stop from existing schedule' '
test_expect_success 'start preserves existing schedule' '
echo "Important information!" >cron.txt &&
- GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt" git maintenance start --scheduler=crontab &&
grep "Important information!" cron.txt
'
@@ -545,7 +570,7 @@ test_expect_success 'start and stop macOS maintenance' '
EOF
rm -f args &&
- GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -582,6 +607,23 @@ test_expect_success 'start and stop macOS maintenance' '
test_line_count = 0 actual
'
+test_expect_success 'use launchctl list to prevent extra work' '
+ # ensure we are registered
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
+
+ # do it again on a fresh args file
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER=launchctl:./print-args git maintenance start --scheduler=launchctl &&
+
+ ls "$HOME/Library/LaunchAgents" >actual &&
+ cat >expect <<-\EOF &&
+ list org.git-scm.git.hourly
+ list org.git-scm.git.daily
+ list org.git-scm.git.weekly
+ EOF
+ test_cmp expect args
+'
+
test_expect_success 'start and stop Windows maintenance' '
write_script print-args <<-\EOF &&
echo $* >>args
@@ -596,7 +638,7 @@ test_expect_success 'start and stop Windows maintenance' '
EOF
rm -f args &&
- GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start &&
+ GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start --scheduler=schtasks &&
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
@@ -619,6 +661,83 @@ test_expect_success 'start and stop Windows maintenance' '
test_cmp expect args
'
+test_expect_success 'start and stop Linux/systemd maintenance' '
+ write_script print-args <<-\EOF &&
+ printf "%s\n" "$*" >>args
+ EOF
+
+ XDG_CONFIG_HOME="$PWD" &&
+ export XDG_CONFIG_HOME &&
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance start --scheduler=systemd-timer &&
+
+ # start registers the repo
+ git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+ test_systemd_analyze_verify "systemd/user/git-maintenance@.service" &&
+
+ printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args" git maintenance stop &&
+
+ # stop does not unregister the repo
+ git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
+
+ test_path_is_missing "systemd/user/git-maintenance@.timer" &&
+ test_path_is_missing "systemd/user/git-maintenance@.service" &&
+
+ printf -- "--user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ test_cmp expect args
+'
+
+test_expect_success 'start and stop when several schedulers are available' '
+ write_script print-args <<-\EOF &&
+ printf "%s\n" "$*" | sed "s:gui/[0-9][0-9]*:gui/[UID]:; s:\(schtasks /create .* /xml\).*:\1:;" >>args
+ EOF
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=systemd-timer &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ printf -- "systemctl --user enable --now git-maintenance@%s.timer\n" hourly daily weekly >>expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=launchctl &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ for frequency in hourly daily weekly
+ do
+ PLIST="$pfx/Library/LaunchAgents/org.git-scm.git.$frequency.plist" &&
+ echo "launchctl bootout gui/[UID] $PLIST" >>expect &&
+ echo "launchctl bootstrap gui/[UID] $PLIST" >>expect || return 1
+ done &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance start --scheduler=schtasks &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >>expect &&
+ printf "schtasks /create /tn Git Maintenance (%s) /f /xml\n" \
+ hourly daily weekly >>expect &&
+ test_cmp expect args &&
+
+ rm -f args &&
+ GIT_TEST_MAINT_SCHEDULER="systemctl:./print-args systemctl,launchctl:./print-args launchctl,schtasks:./print-args schtasks" git maintenance stop &&
+ printf -- "systemctl --user disable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
+ printf "launchctl bootout gui/[UID] $pfx/Library/LaunchAgents/org.git-scm.git.%s.plist\n" \
+ hourly daily weekly >>expect &&
+ printf "schtasks /delete /tn Git Maintenance (%s) /f\n" \
+ hourly daily weekly >>expect &&
+ test_cmp expect args
+'
+
test_expect_success 'register preserves existing strategy' '
git config maintenance.strategy none &&
git maintenance register &&
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 57fc10e7f8..aa0c20499b 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -1533,6 +1533,21 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding works' '
test_cmp content-type-decl actual
'
+test_expect_success $PREREQ 'sendemail.8bitEncoding in .git/config overrides --global .gitconfig' '
+ clean_fake_sendmail &&
+ git config sendemail.assume8bitEncoding UTF-8 &&
+ test_when_finished "rm -rf home" &&
+ mkdir home &&
+ git config -f home/.gitconfig sendemail.assume8bitEncoding "bogus too" &&
+ echo bogus |
+ env HOME="$(pwd)/home" DEBUG=1 \
+ git send-email --from=author@example.com --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ email-using-8bit >stdout &&
+ egrep "Content|MIME" msgtxt1 >actual &&
+ test_cmp content-type-decl actual
+'
+
test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' '
clean_fake_sendmail &&
git config sendemail.assume8bitEncoding "bogus too" &&
@@ -2198,7 +2213,7 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
clean_fake_sendmail &&
- PATH="$(pwd):$PATH" \
+ PATH="$PWD:$PATH" \
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
@@ -2227,6 +2242,51 @@ test_expect_success $PREREQ 'test shell expression with --sendmail-cmd' '
test_path_is_file commandline1
'
+test_expect_success $PREREQ 'set up in-reply-to/references patches' '
+ cat >has-reply.patch <<-\EOF &&
+ From: A U Thor <author@example.com>
+ Subject: patch with in-reply-to
+ Message-ID: <patch.with.in.reply.to@example.com>
+ In-Reply-To: <replied.to@example.com>
+ References: <replied.to@example.com>
+
+ This is the body.
+ EOF
+ cat >no-reply.patch <<-\EOF
+ From: A U Thor <author@example.com>
+ Subject: patch without in-reply-to
+ Message-ID: <patch.without.in.reply.to@example.com>
+
+ This is the body.
+ EOF
+'
+
+test_expect_success $PREREQ 'patch reply headers correct with --no-thread' '
+ clean_fake_sendmail &&
+ git send-email \
+ --no-thread \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ has-reply.patch no-reply.patch &&
+ grep "In-Reply-To: <replied.to@example.com>" msgtxt1 &&
+ grep "References: <replied.to@example.com>" msgtxt1 &&
+ ! grep replied.to@example.com msgtxt2
+'
+
+test_expect_success $PREREQ 'cmdline in-reply-to used with --no-thread' '
+ clean_fake_sendmail &&
+ git send-email \
+ --no-thread \
+ --in-reply-to="<cmdline.reply@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ has-reply.patch no-reply.patch &&
+ grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt1 &&
+ grep "References: <cmdline.reply@example.com>" msgtxt1 &&
+ grep "In-Reply-To: <cmdline.reply@example.com>" msgtxt2 &&
+ grep "References: <cmdline.reply@example.com>" msgtxt2
+'
+
test_expect_success $PREREQ 'invoke hook' '
mkdir -p .git/hooks &&
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
index 89983527b6..6d3dbde3fe 100755
--- a/t/t9002-column.sh
+++ b/t/t9002-column.sh
@@ -42,6 +42,24 @@ EOF
test_cmp expected actual
'
+test_expect_success '--nl' '
+ cat >expected <<\EOF &&
+oneZ
+twoZ
+threeZ
+fourZ
+fiveZ
+sixZ
+sevenZ
+eightZ
+nineZ
+tenZ
+elevenZ
+EOF
+ git column --nl="Z$LF" --mode=plain <lista >actual &&
+ test_cmp expected actual
+'
+
test_expect_success '80 columns' '
cat >expected <<\EOF &&
one two three four five six seven eight nine ten eleven
diff --git a/t/t9351-fast-export-anonymize.sh b/t/t9351-fast-export-anonymize.sh
index 1c6e6fcdaf..77047e250d 100755
--- a/t/t9351-fast-export-anonymize.sh
+++ b/t/t9351-fast-export-anonymize.sh
@@ -18,7 +18,8 @@ test_expect_success 'setup simple repo' '
git update-index --add --cacheinfo 160000,$fake_commit,link1 &&
git update-index --add --cacheinfo 160000,$fake_commit,link2 &&
git commit -m "add gitlink" &&
- git tag -m "annotated tag" mytag
+ git tag -m "annotated tag" mytag &&
+ git tag -m "annotated tag with long message" longtag
'
test_expect_success 'export anonymized stream' '
@@ -55,7 +56,8 @@ test_expect_success 'stream retains other as refname' '
test_expect_success 'stream omits other refnames' '
! grep main stream &&
- ! grep mytag stream
+ ! grep mytag stream &&
+ ! grep longtag stream
'
test_expect_success 'stream omits identities' '
@@ -118,9 +120,9 @@ test_expect_success 'identical gitlinks got identical oid' '
test_line_count = 1 commits
'
-test_expect_success 'tag points to branch tip' '
+test_expect_success 'all tags point to branch tip' '
git rev-parse $other_branch >expect &&
- git for-each-ref --format="%(*objectname)" | grep . >actual &&
+ git for-each-ref --format="%(*objectname)" | grep . | uniq >actual &&
test_cmp expect actual
'
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 11573936d5..5decc3b269 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -540,6 +540,15 @@ test_expect_success '__gitcomp - expand/narrow all negative options' '
EOF
'
+test_expect_success '__gitcomp - equal skip' '
+ test_gitcomp "--option=" "--option=" <<-\EOF &&
+
+ EOF
+ test_gitcomp "option=" "option=" <<-\EOF
+
+ EOF
+'
+
test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
__gitcomp "$invalid_variable_name"
'
@@ -2380,6 +2389,12 @@ test_expect_success 'git clone --config= - value' '
EOF
'
+test_expect_success 'options with value' '
+ test_completion "git merge -X diff-algorithm=" <<-\EOF
+
+ EOF
+'
+
test_expect_success 'sourcing the completion script clears cached commands' '
__git_compute_all_commands &&
verbose test -n "$__git_all_commands" &&
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index e28411bb75..eef2262a36 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -137,33 +137,110 @@ test_tick () {
# Stop execution and start a shell. This is useful for debugging tests.
#
# Be sure to remove all invocations of this command before submitting.
+# WARNING: the shell invoked by this helper does not have the same environment
+# as the one running the tests (shell variables and functions are not
+# available, and the options below further modify the environment). As such,
+# commands copied from a test script might behave differently than when
+# running the test.
+#
+# Usage: test_pause [options]
+# -t
+# Use your original TERM instead of test-lib.sh's "dumb".
+# This usually restores color output in the invoked shell.
+# -s
+# Invoke $SHELL instead of $TEST_SHELL_PATH.
+# -h
+# Use your original HOME instead of test-lib.sh's "$TRASH_DIRECTORY".
+# This allows you to use your regular shell environment and Git aliases.
+# CAUTION: running commands copied from a test script into the paused shell
+# might result in files in your HOME being overwritten.
+# -a
+# Shortcut for -t -s -h
test_pause () {
- "$SHELL_PATH" <&6 >&5 2>&7
+ PAUSE_TERM=$TERM &&
+ PAUSE_SHELL=$TEST_SHELL_PATH &&
+ PAUSE_HOME=$HOME &&
+ while test $# != 0
+ do
+ case "$1" in
+ -t)
+ PAUSE_TERM="$USER_TERM"
+ ;;
+ -s)
+ PAUSE_SHELL="$SHELL"
+ ;;
+ -h)
+ PAUSE_HOME="$USER_HOME"
+ ;;
+ -a)
+ PAUSE_TERM="$USER_TERM"
+ PAUSE_SHELL="$SHELL"
+ PAUSE_HOME="$USER_HOME"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done &&
+ TERM="$PAUSE_TERM" HOME="$PAUSE_HOME" "$PAUSE_SHELL" <&6 >&5 2>&7
}
# Wrap git with a debugger. Adding this to a command can make it easier
# to understand what is going on in a failing test.
#
+# Usage: debug [options] <git command>
+# -d <debugger>
+# --debugger=<debugger>
+# Use <debugger> instead of GDB
+# -t
+# Use your original TERM instead of test-lib.sh's "dumb".
+# This usually restores color output in the debugger.
+# WARNING: the command being debugged might behave differently than when
+# running the test.
+#
# Examples:
# debug git checkout master
# debug --debugger=nemiver git $ARGS
# debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS
debug () {
- case "$1" in
- -d)
- GIT_DEBUGGER="$2" &&
- shift 2
- ;;
- --debugger=*)
- GIT_DEBUGGER="${1#*=}" &&
- shift 1
- ;;
- *)
- GIT_DEBUGGER=1
- ;;
- esac &&
- GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
+ GIT_DEBUGGER=1 &&
+ DEBUG_TERM=$TERM &&
+ while test $# != 0
+ do
+ case "$1" in
+ -t)
+ DEBUG_TERM="$USER_TERM"
+ ;;
+ -d)
+ GIT_DEBUGGER="$2" &&
+ shift
+ ;;
+ --debugger=*)
+ GIT_DEBUGGER="${1#*=}"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done &&
+
+ dotfiles=".gdbinit .lldbinit"
+
+ for dotfile in $dotfiles
+ do
+ dotfile="$USER_HOME/$dotfile" &&
+ test -f "$dotfile" && cp "$dotfile" "$HOME" || :
+ done &&
+
+ TERM="$DEBUG_TERM" GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7 &&
+
+ for dotfile in $dotfiles
+ do
+ rm -f "$HOME/$dotfile"
+ done
}
# Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
diff --git a/t/test-lib.sh b/t/test-lib.sh
index abcfbed6d6..d5ee964254 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -585,8 +585,9 @@ else
}
fi
+USER_TERM="$TERM"
TERM=dumb
-export TERM
+export TERM USER_TERM
error () {
say_color error "error: $*"
@@ -1343,7 +1344,8 @@ fi
GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
GIT_CONFIG_NOSYSTEM=1
GIT_ATTR_NOSYSTEM=1
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
+GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/.."
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM GIT_CEILING_DIRECTORIES
if test -z "$GIT_TEST_CMP"
then
@@ -1380,9 +1382,10 @@ then
fi
# Last-minute variable setup
+USER_HOME="$HOME"
HOME="$TRASH_DIRECTORY"
GNUPGHOME="$HOME/gnupg-home-not-used"
-export HOME GNUPGHOME
+export HOME GNUPGHOME USER_HOME
# Test repository
rm -fr "$TRASH_DIRECTORY" || {
diff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c
index 067c23755f..7da94aba52 100644
--- a/trace2/tr2_tls.c
+++ b/trace2/tr2_tls.c
@@ -95,6 +95,7 @@ void tr2tls_unset_self(void)
pthread_setspecific(tr2tls_key, NULL);
+ strbuf_release(&ctx->thread_name);
free(ctx->array_us_start);
free(ctx);
}
diff --git a/unpack-trees.c b/unpack-trees.c
index 5786645f31..a903e71386 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -111,17 +111,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
strvec_init(&opts->msgs_to_free);
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by checkout:\n%%s"
"Please commit your changes or stash them before you switch branches.")
: _("Your local changes to the following files would be overwritten by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by merge:\n%%s"
"Please commit your changes or stash them before you merge.")
: _("Your local changes to the following files would be overwritten by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("Your local changes to the following files would be overwritten by %s:\n%%s"
"Please commit your changes or stash them before you %s.")
: _("Your local changes to the following files would be overwritten by %s:\n%%s");
@@ -132,17 +132,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
_("Updating the following directories would lose untracked files in them:\n%s");
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by checkout:\n%%s"
"Please move or remove them before you switch branches.")
: _("The following untracked working tree files would be removed by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by merge:\n%%s"
"Please move or remove them before you merge.")
: _("The following untracked working tree files would be removed by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be removed by %s:\n%%s"
"Please move or remove them before you %s.")
: _("The following untracked working tree files would be removed by %s:\n%%s");
@@ -150,17 +150,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
strvec_pushf(&opts->msgs_to_free, msg, cmd, cmd);
if (!strcmp(cmd, "checkout"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by checkout:\n%%s"
"Please move or remove them before you switch branches.")
: _("The following untracked working tree files would be overwritten by checkout:\n%%s");
else if (!strcmp(cmd, "merge"))
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by merge:\n%%s"
"Please move or remove them before you merge.")
: _("The following untracked working tree files would be overwritten by merge:\n%%s");
else
- msg = advice_commit_before_merge
+ msg = advice_enabled(ADVICE_COMMIT_BEFORE_MERGE)
? _("The following untracked working tree files would be overwritten by %s:\n%%s"
"Please move or remove them before you %s.")
: _("The following untracked working tree files would be overwritten by %s:\n%%s");
@@ -479,7 +479,7 @@ static int check_updates(struct unpack_trees_options *o,
errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
progress, &cnt);
stop_progress(&progress);
- errs |= finish_delayed_checkout(&state, NULL);
+ errs |= finish_delayed_checkout(&state, NULL, o->verbose_update);
git_attr_set_direction(GIT_ATTR_CHECKIN);
if (o->clone)
diff --git a/upload-pack.c b/upload-pack.c
index 297b76fcb4..8ad5ee9bfc 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1207,7 +1207,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
format_symref_info(&symref_info, &data->symref);
format_session_id(&session_id, data);
- packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
+ packet_fwrite_fmt(stdout, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n",
oid_to_hex(oid), refname_nons,
0, capabilities,
(data->allow_uor & ALLOW_TIP_SHA1) ?
@@ -1223,11 +1223,11 @@ static int send_ref(const char *refname, const struct object_id *oid,
strbuf_release(&symref_info);
strbuf_release(&session_id);
} else {
- packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
+ packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(oid), refname_nons);
}
capabilities = NULL;
if (!peel_iterated_oid(oid, &peeled))
- packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
+ packet_fwrite_fmt(stdout, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
return 0;
}
@@ -1348,6 +1348,11 @@ void upload_pack(struct upload_pack_options *options)
reset_timeout(data.timeout);
head_ref_namespaced(send_ref, &data);
for_each_namespaced_ref(send_ref, &data);
+ /*
+ * fflush stdout before calling advertise_shallow_grafts because send_ref
+ * uses stdio.
+ */
+ fflush_or_die(stdout);
advertise_shallow_grafts(1);
packet_flush(1);
} else {
@@ -1417,21 +1422,25 @@ static int parse_want_ref(struct packet_writer *writer, const char *line,
struct string_list *wanted_refs,
struct object_array *want_obj)
{
- const char *arg;
- if (skip_prefix(line, "want-ref ", &arg)) {
+ const char *refname_nons;
+ if (skip_prefix(line, "want-ref ", &refname_nons)) {
struct object_id oid;
struct string_list_item *item;
struct object *o;
+ struct strbuf refname = STRBUF_INIT;
- if (read_ref(arg, &oid)) {
- packet_writer_error(writer, "unknown ref %s", arg);
- die("unknown ref %s", arg);
+ strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
+ if (ref_is_hidden(refname_nons, refname.buf) ||
+ read_ref(refname.buf, &oid)) {
+ packet_writer_error(writer, "unknown ref %s", refname_nons);
+ die("unknown ref %s", refname_nons);
}
+ strbuf_release(&refname);
- item = string_list_append(wanted_refs, arg);
+ item = string_list_append(wanted_refs, refname_nons);
item->util = oiddup(&oid);
- o = parse_object_or_die(&oid, arg);
+ o = parse_object_or_die(&oid, refname_nons);
if (!(o->flags & WANTED)) {
o->flags |= WANTED;
add_object_array(o, NULL, want_obj);
diff --git a/userdiff.c b/userdiff.c
index b073e5563b..af02b1878c 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -228,7 +228,7 @@ PATTERNS("perl",
"|<<|<>|<=>|>>"),
PATTERNS("php",
"^[\t ]*(((public|protected|private|static|abstract|final)[\t ]+)*function.*)$\n"
- "^[\t ]*((((final|abstract)[\t ]+)?class|interface|trait).*)$",
+ "^[\t ]*((((final|abstract)[\t ]+)?class|enum|interface|trait).*)$",
/* -- */
"[a-zA-Z_][a-zA-Z0-9_]*"
"|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
diff --git a/wrapper.c b/wrapper.c
index 563ad590df..7c6586af32 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -193,7 +193,9 @@ int xopen(const char *path, int oflag, ...)
if (errno == EINTR)
continue;
- if ((oflag & O_RDWR) == O_RDWR)
+ if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ die_errno(_("unable to create '%s'"), path);
+ else if ((oflag & O_RDWR) == O_RDWR)
die_errno(_("could not open '%s' for reading and writing"), path);
else if ((oflag & O_WRONLY) == O_WRONLY)
die_errno(_("could not open '%s' for writing"), path);
diff --git a/write-or-die.c b/write-or-die.c
index d33e68f6ab..0b1ec8190b 100644
--- a/write-or-die.c
+++ b/write-or-die.c
@@ -70,3 +70,15 @@ void write_or_die(int fd, const void *buf, size_t count)
die_errno("write error");
}
}
+
+void fwrite_or_die(FILE *f, const void *buf, size_t count)
+{
+ if (fwrite(buf, 1, count, f) != count)
+ die_errno("fwrite error");
+}
+
+void fflush_or_die(FILE *f)
+{
+ if (fflush(f))
+ die_errno("fflush error");
+}
diff --git a/wt-status.c b/wt-status.c
index eaed30eafb..e4f29b2b4c 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -787,7 +787,7 @@ static void wt_status_collect_untracked(struct wt_status *s)
dir_clear(&dir);
- if (advice_status_u_option)
+ if (advice_enabled(ADVICE_STATUS_U_OPTION))
s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
}
@@ -1158,7 +1158,7 @@ static void wt_longstatus_print_tracking(struct wt_status *s)
if (!format_tracking_info(branch, &sb, s->ahead_behind_flags))
return;
- if (advice_status_ahead_behind_warning &&
+ if (advice_enabled(ADVICE_STATUS_AHEAD_BEHIND_WARNING) &&
s->ahead_behind_flags == AHEAD_BEHIND_FULL) {
uint64_t t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
if (t_delta_in_ms > AB_DELAY_WARNING_IN_MS) {
@@ -1845,7 +1845,7 @@ static void wt_longstatus_print(struct wt_status *s)
wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
if (s->show_ignored_mode)
wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
- if (advice_status_u_option && 2000 < s->untracked_in_ms) {
+ if (advice_enabled(ADVICE_STATUS_U_OPTION) && 2000 < s->untracked_in_ms) {
status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
status_printf_ln(s, GIT_COLOR_NORMAL,
_("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"