aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.mailmap2
-rw-r--r--Documentation/BreakingChanges.adoc5
-rw-r--r--Documentation/CodingGuidelines6
-rw-r--r--Documentation/MyFirstContribution.adoc20
-rw-r--r--Documentation/RelNotes/2.52.0.adoc104
-rw-r--r--Documentation/config.adoc9
-rw-r--r--Documentation/config/alias.adoc2
-rw-r--r--Documentation/config/core.adoc20
-rw-r--r--Documentation/config/extensions.adoc4
-rw-r--r--Documentation/config/mergetool.adoc2
-rw-r--r--Documentation/config/sendemail.adoc2
-rw-r--r--Documentation/config/worktree.adoc2
-rw-r--r--Documentation/fetch-options.adoc2
-rw-r--r--Documentation/git-add.adoc34
-rw-r--r--Documentation/git-fast-import.adoc10
-rw-r--r--Documentation/git-format-patch.adoc12
-rw-r--r--Documentation/git-interpret-trailers.adoc8
-rw-r--r--Documentation/git-last-modified.adoc54
-rw-r--r--Documentation/git-multi-pack-index.adoc2
-rw-r--r--Documentation/git-rebase.adoc149
-rw-r--r--Documentation/git-refs.adoc7
-rw-r--r--Documentation/git-repo.adoc9
-rw-r--r--Documentation/git-send-email.adoc34
-rw-r--r--Documentation/git.adoc2
-rw-r--r--Documentation/gitk.adoc8
-rw-r--r--Documentation/meson.build1
-rw-r--r--Documentation/pretty-formats.adoc4
-rw-r--r--Makefile12
-rw-r--r--alloc.c10
-rw-r--r--alloc.h4
-rw-r--r--builtin.h1
-rw-r--r--builtin/add.c5
-rw-r--r--builtin/commit-graph.c4
-rw-r--r--builtin/commit.c7
-rw-r--r--builtin/describe.c75
-rw-r--r--builtin/last-modified.c326
-rw-r--r--builtin/ls-files.c13
-rw-r--r--builtin/merge.c3
-rw-r--r--builtin/multi-pack-index.c31
-rw-r--r--builtin/pack-objects.c2
-rw-r--r--builtin/rebase.c3
-rw-r--r--builtin/refs.c48
-rw-r--r--builtin/repack.c7
-rw-r--r--builtin/repo.c45
-rw-r--r--builtin/revert.c7
-rw-r--r--builtin/unpack-objects.c5
-rw-r--r--builtin/update-index.c7
-rw-r--r--bulk-checkin.c152
-rw-r--r--bulk-checkin.h25
-rw-r--r--cache-tree.c5
-rw-r--r--command-list.txt1
-rw-r--r--commit-graph.c7
-rw-r--r--config.c297
-rw-r--r--environment.c15
-rw-r--r--environment.h3
-rw-r--r--fetch-pack.c3
-rw-r--r--git-curl-compat.h7
-rw-r--r--git-gui/Makefile2
-rwxr-xr-xgit-gui/git-gui--askyesno63
-rwxr-xr-xgit-gui/git-gui.sh42
-rw-r--r--git-gui/lib/index.tcl7
-rwxr-xr-xgit-send-email.perl40
-rw-r--r--git.c1
-rw-r--r--gitk-git/README.md93
-rwxr-xr-xgitk-git/gitk26
-rw-r--r--http.c8
-rw-r--r--imap-send.c26
-rw-r--r--line-log.c65
-rw-r--r--meson.build1
-rw-r--r--midx-write.c245
-rw-r--r--midx.c135
-rw-r--r--midx.h42
-rw-r--r--object-file.c30
-rw-r--r--object-name.c7
-rw-r--r--object.c26
-rw-r--r--odb.c64
-rw-r--r--odb.h27
-rw-r--r--pack-bitmap.c15
-rw-r--r--pack-revindex.c14
-rw-r--r--packfile.c13
-rw-r--r--path-walk.c55
-rw-r--r--progress.c12
-rw-r--r--read-cache.c5
-rw-r--r--refs/files-backend.c34
-rw-r--r--refs/reftable-backend.c53
-rw-r--r--reftable/reftable-stack.h9
-rw-r--r--reftable/reftable-writer.h4
-rw-r--r--reftable/stack.c439
-rw-r--r--reftable/system.c2
-rw-r--r--reftable/system.h4
-rw-r--r--reftable/writer.c23
-rw-r--r--repository.c2
-rw-r--r--repository.h3
-rw-r--r--t/Makefile14
-rw-r--r--t/helper/test-read-midx.c31
-rw-r--r--t/meson.build6
-rwxr-xr-xt/perf/p8020-last-modified.sh22
-rw-r--r--t/show-ref-exists-tests.sh77
-rwxr-xr-xt/t0450-txt-doc-vs-help.sh15
-rw-r--r--t/t0450/adoc-missing9
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh13
-rwxr-xr-xt/t1400-update-ref.sh21
-rwxr-xr-xt/t1403-show-ref.sh65
-rwxr-xr-xt/t1422-show-ref-exists.sh9
-rwxr-xr-xt/t1462-refs-exists.sh10
-rwxr-xr-xt/t1517-outside-repo.sh5
-rwxr-xr-xt/t1900-repo.sh18
-rwxr-xr-xt/t3404-rebase-interactive.sh19
-rwxr-xr-xt/t3418-rebase-continue.sh2
-rwxr-xr-xt/t4211-line-log.sh2
-rw-r--r--t/t4211/sha1/expect.multiple6
-rw-r--r--t/t4211/sha1/expect.no-assertion-error90
-rw-r--r--t/t4211/sha1/expect.two-ranges6
-rw-r--r--t/t4211/sha256/expect.multiple6
-rw-r--r--t/t4211/sha256/expect.no-assertion-error90
-rw-r--r--t/t4211/sha256/expect.two-ranges6
-rwxr-xr-xt/t5319-multi-pack-index.sh30
-rwxr-xr-xt/t5510-fetch.sh543
-rwxr-xr-xt/t5530-upload-pack-error.sh68
-rwxr-xr-xt/t5564-http-proxy.sh4
-rwxr-xr-xt/t6120-describe.sh30
-rwxr-xr-xt/t7502-commit-porcelain.sh52
-rwxr-xr-xt/t7700-repack.sh63
-rwxr-xr-xt/t8020-last-modified.sh210
-rw-r--r--t/unit-tests/u-reftable-stack.c59
-rw-r--r--upload-pack.c19
-rw-r--r--xdiff/xutils.c66
-rw-r--r--xdiff/xutils.h10
130 files changed, 3501 insertions, 1480 deletions
diff --git a/.gitignore b/.gitignore
index 1803023427..802ce70e48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,6 +87,7 @@
/git-init-db
/git-interpret-trailers
/git-instaweb
+/git-last-modified
/git-log
/git-ls-files
/git-ls-remote
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index af10ebb59a..cf122e706f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -119,6 +119,7 @@ build:mingw64:
variables:
NO_PERL: 1
before_script:
+ - Set-MpPreference -DisableRealtimeMonitoring $true
- ./ci/install-sdk.ps1 -directory "git-sdk"
script:
- git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts'
@@ -135,6 +136,7 @@ test:mingw64:
- job: "build:mingw64"
artifacts: true
before_script:
+ - Set-MpPreference -DisableRealtimeMonitoring $true
- git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz'
- New-Item -Path .git/info -ItemType Directory
- New-Item .git/info/exclude -ItemType File -Value "/git-sdk"
@@ -148,6 +150,7 @@ test:mingw64:
tags:
- saas-windows-medium-amd64
before_script:
+ - Set-MpPreference -DisableRealtimeMonitoring $true
- choco install -y git meson ninja openssl
- Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
- refreshenv
diff --git a/.mailmap b/.mailmap
index 96c2740fbb..afa21abbaa 100644
--- a/.mailmap
+++ b/.mailmap
@@ -81,6 +81,8 @@ Fredrik Kuivinen <frekui@gmail.com> <freku045@student.liu.se>
Frédéric Heitzmann <frederic.heitzmann@gmail.com>
Garry Dolley <gdolley@ucla.edu> <gdolley@arpnetworks.com>
Glen Choo <glencbz@gmail.com> <chooglen@google.com>
+Greg Hurrell <greg@hurrell.net> <greg.hurrell@datadoghq.com>
+Greg Hurrell <greg@hurrell.net> <win@wincent.com>
Greg Price <price@mit.edu> <price@MIT.EDU>
Greg Price <price@mit.edu> <price@ksplice.com>
Heiko Voigt <hvoigt@hvoigt.net> <git-list@hvoigt.net>
diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc
index f8d2eba061..344ce50060 100644
--- a/Documentation/BreakingChanges.adoc
+++ b/Documentation/BreakingChanges.adoc
@@ -239,6 +239,11 @@ These features will be removed.
+
The command will be removed.
+* Support for `core.commentString=auto` has been deprecated and will
+ be removed in Git 3.0.
++
+cf. <xmqqa59i45wc.fsf@gitster.g>
+
== Superseded features that will not be deprecated
Some features have gained newer replacements that aim to improve the design in
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 224f0978a8..df72fe0177 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -650,6 +650,12 @@ For C programs:
cases. However, it is recommended to find a more descriptive name wherever
possible to improve the readability and maintainability of the code.
+ - Bit fields should be defined without a space around the colon. E.g.
+
+ unsigned my_field:1;
+ unsigned other_field:1;
+ unsigned field_with_longer_name:1;
+
For Perl programs:
- Most of the C guidelines above apply.
diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc
index aca7212cfe..02ba8ba5f6 100644
--- a/Documentation/MyFirstContribution.adoc
+++ b/Documentation/MyFirstContribution.adoc
@@ -52,6 +52,15 @@ respond to you. It's better to ask your questions in the channel so that you
can be answered if you disconnect and so that others can learn from the
conversation.
+==== https://discord.gg/GRFVkzgxRd[#discord] on Discord
+This is an unofficial Git Discord server for everyone, from people just
+starting out with Git to those who develop it. It's a great place to ask
+questions, share tips, and connect with the broader Git community in real time.
+
+The server has channels for general discussions and specific channels for those
+who use Git and those who develop it. The server's search functionality also
+allows you to find previous conversations and answers to common questions.
+
[[getting-started]]
== Getting Started
@@ -908,10 +917,13 @@ Now you should be able to go and check out your newly created branch on GitHub.
=== Sending a PR to GitGitGadget
In order to have your code tested and formatted for review, you need to start by
-opening a Pull Request against `gitgitgadget/git`. Head to
-https://github.com/gitgitgadget/git and open a PR either with the "New pull
-request" button or the convenient "Compare & pull request" button that may
-appear with the name of your newly pushed branch.
+opening a Pull Request against either `gitgitgadget/git` or `git/git`. Head to
+https://github.com/gitgitgadget/git or https://github.com/git/git and open a PR
+either with the "New pull request" button or the convenient "Compare & pull
+request" button that may appear with the name of your newly pushed branch.
+
+The differences between using `gitgitgadget/git` and `git/git` as your base can
+be found [here](https://gitgitgadget.github.io/#should-i-use-gitgitgadget-on-gitgitgadgets-git-fork-or-on-gits-github-mirror)
Review the PR's title and description, as they're used by GitGitGadget
respectively as the subject and body of the cover letter for your change. Refer
diff --git a/Documentation/RelNotes/2.52.0.adoc b/Documentation/RelNotes/2.52.0.adoc
index 9f8607a752..959c8afe15 100644
--- a/Documentation/RelNotes/2.52.0.adoc
+++ b/Documentation/RelNotes/2.52.0.adoc
@@ -14,6 +14,16 @@ UI, Workflows & Features
* A new subcommand "git repo" gives users a way to grab various
repository characteristics.
+ * A new command "git last-modified" has been added to show the closest
+ ancestor commit that touched each path.
+
+ * "git refs exists" that works like "git show-ref --exists" has been
+ added.
+
+ * "repo info" learns a short-hand option "-z" that is the same as
+ "--format=nul", and learns to report the objects format used in the
+ repository.
+
Performance, Internal Implementation, Development Support etc.
--------------------------------------------------------------
@@ -37,6 +47,22 @@ Performance, Internal Implementation, Development Support etc.
* Remove dependency on the_repository and other globals from the
commit-graph code, and other changes unrelated to de-globaling.
+ * Discord has been added to the first contribution documentation as
+ another way to ask for help.
+
+ * Inspired by Ezekiel's recent effort to showcase Rust interface, the
+ hash function implementation used to hash lines have been updated
+ to the one used for ELF symbol lookup by Glibc.
+
+ * Instead of scanning for the remaining items to see if there are
+ still commits to be explored in the queue, use khash to remember
+ which items are still on the queue (an unacceptable alternative is
+ to reserve one object flag bits).
+
+ * The bulk-checkin code used to depend on a file-scope static
+ singleton variable, which has been updated to pass an instance
+ throughout the callchain.
+
Fixes since v2.51
-----------------
@@ -86,6 +112,71 @@ including security updates, are included in this release.
ignored") did not work well with "--name-only" and friends.
(merge b55e6d36eb ly/diff-name-only-with-diff-from-content later to maint).
+ * Documentation for "git rebase" has been updated.
+ (merge 3f7f2b0359 je/doc-rebase later to maint).
+
+ * The start_delayed_progress() function in the progress eye-candy API
+ did not clear its internal state, making an initial delay value
+ larger than 1 second ineffective, which has been corrected.
+ (merge 457534d041 js/progress-delay-fix later to maint).
+
+ * The compatObjectFormat extension is used to hide an incomplete
+ feature that is not yet usable for any purpose other than
+ developing the feature further. Document it as such to discourage
+ its use by mere mortals.
+ (merge 716d905792 bc/doc-compat-object-format-not-working later to maint).
+
+ * "git log -L..." compared trees of multiple parents with the tree of the
+ merge result in an unnecessarily inefficient way.
+ (merge 0a15bb634c sg/line-log-merge-optim later to maint).
+
+ * Under a race against another process that is repacking the
+ repository, especially a partially cloned one, "git fetch" may
+ mistakenly think some objects we do have are missing, which has
+ been corrected.
+ (merge 8f32a5a6c0 jk/fetch-check-graph-objects-fix later to maint).
+
+ * "git fetch" can clobber a symref that is dangling when the
+ remote-tracking HEAD is set to auto update, which has been
+ corrected.
+
+ * "git describe <blob>" misbehaves and/or crashes in some corner
+ cases, which has been taught to exit with failure gracefully.
+ (merge 7c10e48e81 jk/describe-blob later to maint).
+
+ * Manual page for "gitk" is updated with the current maintainer's
+ name.
+ (merge bcb20dda83 js/doc-gitk-history later to maint).
+
+ * Update the instruction to use of GGG in the MyFirstContribution
+ document to say that a GitHub PR could be made against `git/git`
+ instead of `gitgitgadget/git`.
+ (merge 37001cdbc4 ds/doc-ggg-pr-fork-clarify later to maint).
+
+ * Makefile tried to run multiple "cargo build" which would not work
+ very well; serialize their execution to work it around.
+ (merge 0eeacde50e da/cargo-serialize later to maint).
+
+ * "git repack --path-walk" lost objects in some corner cases, which
+ has been corrected.
+ (merge 93afe9b060 ds/path-walk-repack-fix later to maint).
+
+ * "git ls-files <pathspec>..." should not necessarily have to expand
+ the index fully if a sparsified directory is excluded by the
+ pathspec; the code is taught to expand the index on demand to avoid
+ this.
+ (merge 681f26bccc ds/ls-files-lazy-unsparse later to maint).
+
+ * Windows "real-time monitoring" interferes with the execution of
+ tests and affects negatively in both correctness and performance,
+ which has been disabled in Gitlab CI.
+ (merge 608cf5b793 ps/gitlab-ci-disable-windows-monitoring later to maint).
+
+ * A broken or malicious "git fetch" can say that it has the same
+ object for many many times, and the upload-pack serving it can
+ exhaust memory storing them redundantly, which has been corrected.
+ (merge 88a2dc68c8 ps/upload-pack-oom-protection later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 823d537fa7 kh/doc-git-log-markup-fix later to maint).
(merge cf7efa4f33 rj/t6137-cygwin-fix later to maint).
@@ -94,3 +185,16 @@ including security updates, are included in this release.
(merge 741f36c7d9 kr/clone-synopsis-fix later to maint).
(merge a60203a015 dk/t7005-editor-updates later to maint).
(merge 7d4a5fef7d ds/doc-count-objects-fix later to maint).
+ (merge 16684b6fae ps/reftable-libgit2-cleanup later to maint).
+ (merge f38786baa7 ja/asciidoc-doctor-verbatim-fixes later to maint).
+ (merge 374579c6d4 kh/doc-interpret-trailers-markup-fix later to maint).
+ (merge 44dce6541c kh/doc-config-typofix later to maint).
+ (merge 785628b173 js/doc-sending-patch-via-thunderbird later to maint).
+ (merge e5c27bd3d8 je/doc-add later to maint).
+ (merge 13296ac909 ps/object-store-midx-dedup-info later to maint).
+ (merge 2f4bf83ffc km/alias-doc-markup-fix later to maint).
+ (merge b0d97aac19 kh/doc-markup-fixes later to maint).
+ (merge f9a6705d9a tc/t0450-harden later to maint).
+ (merge c25651aefd ds/midx-write-fixes later to maint).
+ (merge 069c15d256 rs/object-name-extend-abbrev-len-update later to maint).
+ (merge bf5c224537 mm/worktree-doc-typofix later to maint).
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index cc769251be..05f1ca7293 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -114,8 +114,7 @@ whose format and meaning depends on the keyword. Supported keywords
are:
`gitdir`::
-
- The data that follows the keyword `gitdir:` is used as a glob
+ The data that follows the keyword `gitdir` and a colon is used as a glob
pattern. If the location of the .git directory matches the
pattern, the include condition is met.
+
@@ -148,7 +147,7 @@ refer to linkgit:gitignore[5] for details. For convenience:
case-insensitively (e.g. on case-insensitive file systems)
`onbranch`::
- The data that follows the keyword `onbranch:` is taken to be a
+ The data that follows the keyword `onbranch` and a colon is taken to be a
pattern with standard globbing wildcards and two additional
ones, `**/` and `/**`, that can match multiple path components.
If we are in a worktree where the name of the branch that is
@@ -161,8 +160,8 @@ all branches that begin with `foo/`. This is useful if your branches are
organized hierarchically and you would like to apply a configuration to
all the branches in that hierarchy.
-`hasconfig:remote.*.url:`::
- The data that follows this keyword is taken to
+`hasconfig:remote.*.url`::
+ The data that follows this keyword and a colon is taken to
be a pattern with standard globbing wildcards and two
additional ones, `**/` and `/**`, that can match multiple
components. The first time this keyword is seen, the rest of
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 2c5db0ad84..95825354bf 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -38,6 +38,6 @@ it will be treated as a shell command. For example, defining
** A convenient way to deal with this is to write your script
operations in an inline function that is then called with any
arguments from the command-line. For example `alias.cmd = "!c() {
- echo $1 | grep $2 ; }; c" will correctly execute the prior example.
+ echo $1 | grep $2 ; }; c"` will correctly execute the prior example.
** Setting `GIT_TRACE=1` can help you debug the command being run for
your alias.
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc
index 3fbe83eef1..08739bb9d4 100644
--- a/Documentation/config/core.adoc
+++ b/Documentation/config/core.adoc
@@ -531,9 +531,25 @@ core.commentString::
commented, and removes them after the editor returns
(default '#').
+
-If set to "auto", `git-commit` would select a character that is not
+ifndef::with-breaking-changes[]
+If set to "auto", `git-commit` will select a character that is not
the beginning character of any line in existing commit messages.
-+
+Support for this value is deprecated and will be removed in Git 3.0
+due to the following limitations:
++
+--
+* It is incompatible with adding comments in a commit message
+ template. This includes the conflicts comments added to
+ the commit message by `cherry-pick`, `merge`, `rebase` and
+ `revert`.
+* It is incompatible with adding comments to the commit message
+ in the `prepare-commit-msg` hook.
+* It is incompatible with the `fixup` and `squash` commands when
+ rebasing,
+* It is not respected by `git notes`
+--
++
+endif::with-breaking-changes[]
Note that these two variables are aliases of each other, and in modern
versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with
`commentChar`. Versions of Git prior to v2.45.0 will ignore
diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index 9e2f321a6d..829f2523fc 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -14,6 +14,10 @@ compatObjectFormat::
compatObjectFormat. As well as being able to use oids encoded in
compatObjectFormat in addition to oids encoded with objectFormat to
locally specify objects.
++
+Note that the functionality enabled by this extension is incomplete and subject
+to change. It currently exists only to allow development and testing of
+the underlying feature and is not designed to be enabled by end users.
noop::
This extension does not change git's behavior at all. It is useful only
diff --git a/Documentation/config/mergetool.adoc b/Documentation/config/mergetool.adoc
index 6be506145c..7064f5a462 100644
--- a/Documentation/config/mergetool.adoc
+++ b/Documentation/config/mergetool.adoc
@@ -65,7 +65,7 @@ endif::[]
During a merge, Git will automatically resolve as many conflicts as
possible and write the `$MERGED` file containing conflict markers around
any conflicts that it cannot resolve; `$LOCAL` and `$REMOTE` normally
- are the versions of the file from before Git`s conflict
+ are the versions of the file from before Git's conflict
resolution. This flag causes `$LOCAL` and `$REMOTE` to be overwritten so
that only the unresolved conflicts are presented to the merge tool. Can
be configured per-tool via the `mergetool.<tool>.hideResolved`
diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc
index 4722334657..90164c734d 100644
--- a/Documentation/config/sendemail.adoc
+++ b/Documentation/config/sendemail.adoc
@@ -88,6 +88,8 @@ sendemail.smtpServer::
sendemail.smtpServerPort::
sendemail.smtpServerOption::
sendemail.smtpUser::
+sendemail.imapSentFolder::
+sendemail.useImapOnly::
sendemail.thread::
sendemail.transferEncoding::
sendemail.validate::
diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc
index 5e35c7d018..9e3f84f748 100644
--- a/Documentation/config/worktree.adoc
+++ b/Documentation/config/worktree.adoc
@@ -15,5 +15,5 @@ worktree.useRelativePaths::
different locations or environments. Defaults to "false".
+
Note that setting `worktree.useRelativePaths` to "true" implies enabling the
-`extension.relativeWorktrees` config (see linkgit:git-config[1]),
+`extensions.relativeWorktrees` config (see linkgit:git-config[1]),
thus making it incompatible with older versions of Git.
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc
index d3ac31f4e2..ad1e1f49be 100644
--- a/Documentation/fetch-options.adoc
+++ b/Documentation/fetch-options.adoc
@@ -2,7 +2,7 @@
--no-all::
Fetch all remotes, except for the ones that has the
`remote.<name>.skipFetchAll` configuration variable set.
- This overrides the configuration variable fetch.all`.
+ This overrides the configuration variable `fetch.all`.
-a::
--append::
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc
index b7a735824d..ad629c46c5 100644
--- a/Documentation/git-add.adoc
+++ b/Documentation/git-add.adoc
@@ -16,18 +16,18 @@ git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [-
DESCRIPTION
-----------
-This command updates the index using the current content found in
-the working tree, to prepare the content staged for the next commit.
-It typically adds the current content of existing paths as a whole,
-but with some options it can also be used to add content with
-only part of the changes made to the working tree files applied, or
-remove paths that do not exist in the working tree anymore.
-
-The "index" holds a snapshot of the content of the working tree, and it
-is this snapshot that is taken as the contents of the next commit. Thus
-after making any changes to the working tree, and before running
-the commit command, you must use the `add` command to add any new or
-modified files to the index.
+Add contents of new or changed files to the index. The "index" (also
+known as the "staging area") is what you use to prepare the contents of
+the next commit.
+
+When you run `git commit` without any other arguments, it will only
+commit staged changes. For example, if you've edited `file.c` and want
+to commit your changes to that file, you can run:
+
+ git add file.c
+ git commit
+
+You can also add only part of your changes to a file with `git add -p`.
This command can be performed multiple times before a commit. It only
adds the content of the specified file(s) at the time the add command is
@@ -37,12 +37,10 @@ you must run `git add` again to add the new content to the index.
The `git status` command can be used to obtain a summary of which
files have changes that are staged for the next commit.
-The `git add` command will not add ignored files by default. If any
-ignored files were explicitly specified on the command line, `git add`
-will fail with a list of ignored files. Ignored files reached by
-directory recursion or filename globbing performed by Git (quote your
-globs before the shell) will be silently ignored. The `git add` command can
-be used to add ignored files with the `-f` (force) option.
+The `git add` command will not add ignored files by default. You can
+use the `--force` option to add ignored files. If you specify the exact
+filename of an ignored file, `git add` will fail with a list of ignored
+files. Otherwise it will silently ignore the file.
Please see linkgit:git-commit[1] for alternative ways to add content to a
commit.
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index 3144ffcdb6..6e095b02a1 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -61,10 +61,10 @@ OPTIONS
currently impacts only the `export-marks`, `import-marks`, and
`import-marks-if-exists` feature commands.
+
- Only enable this option if you trust the program generating the
- fast-import stream! This option is enabled automatically for
- remote-helpers that use the `import` capability, as they are
- already trusted to run their own code.
+Only enable this option if you trust the program generating the
+fast-import stream! This option is enabled automatically for
+remote-helpers that use the `import` capability, as they are
+already trusted to run their own code.
Options for Frontends
~~~~~~~~~~~~~~~~~~~~~
@@ -647,7 +647,7 @@ External data format::
+
Here usually `<dataref>` must be either a mark reference (`:<idnum>`)
set by a prior `blob` command, or a full 40-byte SHA-1 of an
-existing Git blob object. If `<mode>` is `040000`` then
+existing Git blob object. If `<mode>` is `040000` then
`<dataref>` must be the full 40-byte SHA-1 of an existing
Git tree object or a mark reference set with `--import-marks`.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 048d1b9815..9a7807ca71 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -591,13 +591,19 @@ an external editor to keep Thunderbird from mangling the patches.
Approach #1 (add-on)
^^^^^^^^^^^^^^^^^^^^
-Install the Toggle Word Wrap add-on that is available from
-https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/
-It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu
+Install the Toggle Line Wrap add-on that is available from
+https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap
+It adds a button "Line Wrap" to the composer's toolbar
that you can tick off. Now you can compose the message as you otherwise do
(cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to
insert line breaks manually in any text that you type.
+As a bonus feature, the add-on can detect patch text in the composer
+and warns when line wrapping has not yet been turned off.
+
+The add-on requires a few tweaks of the advanced configuration
+(about:config). These are listed on the download page.
+
Approach #2 (configuration)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Three steps:
diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc
index 82c8780d93..fd335fe772 100644
--- a/Documentation/git-interpret-trailers.adoc
+++ b/Documentation/git-interpret-trailers.adoc
@@ -142,8 +142,8 @@ OPTIONS
provided with '--if-exists' overrides the `trailer.ifExists` and any
applicable `trailer.<keyAlias>.ifExists` configuration variables
and applies to all '--trailer' options until the next occurrence of
- '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists, clear the
- effect of any previous use of '--if-exists, such that the relevant configuration
+ '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists', clear the
+ effect of any previous use of '--if-exists', such that the relevant configuration
variables are no longer overridden. Possible actions are `addIfDifferent`,
`addIfDifferentNeighbor`, `add`, `replace` and `doNothing`.
@@ -154,8 +154,8 @@ OPTIONS
provided with '--if-missing' overrides the `trailer.ifMissing` and any
applicable `trailer.<keyAlias>.ifMissing` configuration variables
and applies to all '--trailer' options until the next occurrence of
- '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing,
- clear the effect of any previous use of '--if-missing, such that the relevant
+ '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing',
+ clear the effect of any previous use of '--if-missing', such that the relevant
configuration variables are no longer overridden. Possible actions are `doNothing`
or `add`.
diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc
new file mode 100644
index 0000000000..602843e095
--- /dev/null
+++ b/Documentation/git-last-modified.adoc
@@ -0,0 +1,54 @@
+git-last-modified(1)
+====================
+
+NAME
+----
+git-last-modified - EXPERIMENTAL: Show when files were last modified
+
+
+SYNOPSIS
+--------
+[synopsis]
+git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...]
+
+DESCRIPTION
+-----------
+
+Shows which commit last modified each of the relevant files and subdirectories.
+A commit renaming a path, or changing it's mode is also taken into account.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+OPTIONS
+-------
+
+`-r`::
+`--recursive`::
+ Instead of showing tree entries, step into subtrees and show all entries
+ inside them recursively.
+
+`-t`::
+`--show-trees`::
+ Show tree entries even when recursing into them. It has no effect
+ without `--recursive`.
+
+`<revision-range>`::
+ Only traverse commits in the specified revision range. When no
+ `<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole
+ history leading to the current commit). For a complete list of ways to
+ spell `<revision-range>`, see the 'Specifying Ranges' section of
+ linkgit:gitrevisions[7].
+
+`[--] <path>...`::
+ For each _<path>_ given, the commit which last modified it is returned.
+ Without an optional path parameter, all files and subdirectories
+ in path traversal the are included in the output.
+
+SEE ALSO
+--------
+linkgit:git-blame[1],
+linkgit:git-log[1].
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc
index e8073bc272..2f642697e9 100644
--- a/Documentation/git-multi-pack-index.adoc
+++ b/Documentation/git-multi-pack-index.adoc
@@ -29,7 +29,7 @@ OPTIONS
--no-progress::
Turn progress on/off explicitly. If neither is specified, progress is
shown if standard error is connected to a terminal. Supported by
- sub-commands `write`, `verify`, `expire`, and `repack.
+ sub-commands `write`, `verify`, `expire`, and `repack`.
The following subcommands are available:
diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc
index 727160c6db..005caf6164 100644
--- a/Documentation/git-rebase.adoc
+++ b/Documentation/git-rebase.adoc
@@ -16,49 +16,12 @@ SYNOPSIS
DESCRIPTION
-----------
-If `<branch>` is specified, `git rebase` will perform an automatic
-`git switch <branch>` before doing anything else. Otherwise
-it remains on the current branch.
+Transplant a series of commits onto a different starting point.
+You can also use `git rebase` to reorder or combine commits: see INTERACTIVE
+MODE below for how to do that.
-If `<upstream>` is not specified, the upstream configured in
-`branch.<name>.remote` and `branch.<name>.merge` options will be used (see
-linkgit:git-config[1] for details) and the `--fork-point` option is
-assumed. If you are currently not on any branch or if the current
-branch does not have a configured upstream, the rebase will abort.
-
-All changes made by commits in the current branch but that are not
-in `<upstream>` are saved to a temporary area. This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`; or by
-`git log 'fork_point'..HEAD`, if `--fork-point` is active (see the
-description on `--fork-point` below); or by `git log HEAD`, if the
-`--root` option is specified.
-
-The current branch is reset to `<upstream>` or `<newbase>` if the
-`--onto` option was supplied. This has the exact same effect as
-`git reset --hard <upstream>` (or `<newbase>`). `ORIG_HEAD` is set
-to point at the tip of the branch before the reset.
-
-[NOTE]
-`ORIG_HEAD` is not guaranteed to still point to the previous branch tip
-at the end of the rebase if other commands that write that pseudo-ref
-(e.g. `git reset`) are used during the rebase. The previous branch tip,
-however, is accessible using the reflog of the current branch
-(i.e. `@{1}`, see linkgit:gitrevisions[7]).
-
-The commits that were previously saved into the temporary area are
-then reapplied to the current branch, one by one, in order. Note that
-any commits in `HEAD` which introduce the same textual changes as a commit
-in `HEAD..<upstream>` are omitted (i.e., a patch already accepted upstream
-with a different commit message or timestamp will be skipped).
-
-It is possible that a merge failure will prevent this process from being
-completely automatic. You will have to resolve any such merge failure
-and run `git rebase --continue`. Another option is to bypass the commit
-that caused the merge failure with `git rebase --skip`. To check out the
-original `<branch>` and remove the `.git/rebase-apply` working files, use
-the command `git rebase --abort` instead.
-
-Assume the following history exists and the current branch is "topic":
+For example, imagine that you have been working on the `topic` branch in this
+history, and you want to "catch up" to the work done on the `master` branch.
------------
A---B---C topic
@@ -66,13 +29,11 @@ Assume the following history exists and the current branch is "topic":
D---E---F---G master
------------
-From this point, the result of either of the following commands:
-
-
- git rebase master
- git rebase master topic
-
-would be:
+You want to transplant the commits you made on `topic` since it diverged from
+`master` (i.e. A, B, and C), on top of the current `master`. You can do this
+by running `git rebase master` while the `topic` branch is checked out. If you
+want to rebase `topic` while on another branch, `git rebase master topic` is a
+shortcut for `git checkout topic && git rebase master`.
------------
A'--B'--C' topic
@@ -80,30 +41,56 @@ would be:
D---E---F---G master
------------
-*NOTE:* The latter form is just a short-hand of `git checkout topic`
-followed by `git rebase master`. When rebase exits `topic` will
-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 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):
+If there is a merge conflict during this process, `git rebase` will stop at the
+first problematic commit and leave conflict markers. If this happens, you can do
+one of these things:
-------------
- A---B---C topic
- /
- D---E---A'---F master
-------------
+1. Resolve the conflict. You can use `git diff` to find the markers (<<<<<<)
+ and make edits to resolve the conflict. For each file you edit, you need to
+ tell Git that the conflict has been resolved. You can mark the conflict as
+ resolved with `git add <filename>`. After resolving all of the conflicts,
+ you can continue the rebasing process with
-will result in:
+ git rebase --continue
-------------
- B'---C' topic
- /
- D---E---A'---F master
-------------
+2. Stop the `git rebase` and return your branch to its original state with
+
+ git rebase --abort
+
+3. Skip the commit that caused the merge conflict with
+
+ git rebase --skip
+
+If you don't specify an `<upstream>` to rebase onto, the upstream configured in
+`branch.<name>.remote` and `branch.<name>.merge` options will be used (see
+linkgit:git-config[1] for details) and the `--fork-point` option is
+assumed. If you are currently not on any branch or if the current
+branch does not have a configured upstream, the rebase will abort.
+
+Here is a simplified description of what `git rebase <upstream>` does:
+
+1. Make a list of all commits on your current branch since it branched
+ off from `<upstream>` that do not have an equivalent commit in
+ `<upstream>`.
+2. Check out `<upstream>` with the equivalent of
+ `git checkout --detach <upstream>`.
+3. Replay the commits, one by one, in order. This is similar to running
+ `git cherry-pick <commit>` for each commit. See REBASING MERGES for how merges
+ are handled.
+4. Update your branch to point to the final commit with the equivalent
+ of `git checkout -B <branch>`.
+
+[NOTE]
+When starting the rebase, `ORIG_HEAD` is set to point to the commit at the tip
+of the to-be-rebased branch. However, `ORIG_HEAD` is not guaranteed to still
+point to that commit at the end of the rebase if other commands that change
+`ORIG_HEAD` (like `git reset`) are used during the rebase. The previous branch
+tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`,
+see linkgit:gitrevisions[7].
+
+TRANSPLANTING A TOPIC BRANCH WITH --ONTO
+----------------------------------------
Here is how you would transplant a topic branch based on one
branch to another, to pretend that you forked the topic branch
@@ -186,28 +173,6 @@ This is useful if F and G were flawed in some way, or should not be
part of topicA. Note that the argument to `--onto` and the `<upstream>`
parameter can be any valid commit-ish.
-In case of conflict, `git rebase` will stop at the first problematic commit
-and leave conflict markers in the tree. You can use `git diff` to locate
-the markers (<<<<<<) and make edits to resolve the conflict. For each
-file you edit, you need to tell Git that the conflict has been resolved,
-typically this would be done with
-
-
- git add <filename>
-
-
-After resolving the conflict manually and updating the index with the
-desired resolution, you can continue the rebasing process with
-
-
- git rebase --continue
-
-
-Alternatively, you can undo the 'git rebase' with
-
-
- git rebase --abort
-
MODE OPTIONS
------------
@@ -253,6 +218,8 @@ As a special case, you may use "A\...B" as a shortcut for the
merge base of A and B if there is exactly one merge base. You can
leave out at most one of A and B, in which case it defaults to HEAD.
+See TRANSPLANTING A TOPIC BRANCH WITH --ONTO above for examples.
+
--keep-base::
Set the starting point at which to create the new commits to the
merge base of `<upstream>` and `<branch>`. Running
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index d462953fb5..bfa9b3ea2d 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -18,6 +18,7 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
[--contains[=<object>]] [--no-contains[=<object>]]
[(--exclude=<pattern>)...] [--start-after=<marker>]
[ --stdin | (<pattern>...)]
+git refs exists <ref>
DESCRIPTION
-----------
@@ -38,6 +39,12 @@ list::
formatting, and sorting. This subcommand is an alias for
linkgit:git-for-each-ref[1] and offers identical functionality.
+exists::
+ Check whether the given reference exists. Returns an exit code of 0 if
+ it does, 2 if it is missing, and 1 in case looking up the reference
+ failed with an error other than the reference being missing. This does
+ not verify whether the reference resolves to an actual object.
+
OPTIONS
-------
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 2870828d93..209afd1b61 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository
SYNOPSIS
--------
[synopsis]
-git repo info [--format=(keyvalue|nul)] [<key>...]
+git repo info [--format=(keyvalue|nul)] [-z] [<key>...]
DESCRIPTION
-----------
@@ -18,7 +18,7 @@ THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
COMMANDS
--------
-`info [--format=(keyvalue|nul)] [<key>...]`::
+`info [--format=(keyvalue|nul)] [-z] [<key>...]`::
Retrieve metadata-related information about the current repository. Only
the requested data will be returned based on their keys (see "INFO KEYS"
section below).
@@ -40,6 +40,8 @@ supported:
between the key and the value and using a NUL character after each value.
This format is better suited for being parsed by another applications than
`keyvalue`. Unlike in the `keyvalue` format, the values are never quoted.
++
+`-z` is an alias for `--format=nul`.
INFO KEYS
---------
@@ -53,6 +55,9 @@ values that they return:
`layout.shallow`::
`true` if this is a shallow repository, otherwise `false`.
+`object.format`::
+ The object format (hash algorithm) used in the repository.
+
`references.format`::
The reference storage format. The valid values are:
+
diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc
index 11b1ab1a07..263b977353 100644
--- a/Documentation/git-send-email.adoc
+++ b/Documentation/git-send-email.adoc
@@ -300,6 +300,32 @@ must be used for each option.
commands and replies will be printed. Useful to debug TLS
connection and authentication problems.
+--imap-sent-folder=<folder>::
+ Some email providers (e.g. iCloud) do not send a copy of the emails sent
+ using SMTP to the `Sent` folder or similar in your mailbox. Use this option
+ to use `git imap-send` to send a copy of the emails to the folder specified
+ using this option. You can run `git imap-send --list` to get a list of
+ valid folder names, including the correct name of the `Sent` folder in
+ your mailbox. You can also use this option to send emails to a dedicated
+ IMAP folder of your choice.
++
+This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
+for instructions.
+
+--use-imap-only::
+--no-use-imap-only::
+ If this is set, all emails will only be copied to the IMAP folder specified
+ with `--imap-sent-folder` or `sendemail.imapSentFolder` and will not be sent
+ to the recipients. Useful if you just want to create a draft of the emails
+ and use another email client to send them.
+ If disabled with `--no-use-imap-only`, the emails will be sent like usual.
+ Disabled by default, but the `sendemail.useImapOnly` configuration
+ variable can be used to enable it.
+
++
+This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
+for instructions.
+
--batch-size=<num>::
Some email servers (e.g. 'smtp.163.com') limit the number of emails to be
sent per session (connection) and this will lead to a failure when
@@ -531,10 +557,10 @@ edit `~/.gitconfig` to specify your account settings:
----
[sendemail]
- smtpEncryption = tls
+ smtpEncryption = ssl
smtpServer = smtp.gmail.com
smtpUser = yourname@gmail.com
- smtpServerPort = 587
+ smtpServerPort = 465
----
Gmail does not allow using your regular password for `git send-email`.
@@ -552,10 +578,10 @@ if you want to use `OAUTHBEARER`, edit your `~/.gitconfig` file and add
----
[sendemail]
- smtpEncryption = tls
+ smtpEncryption = ssl
smtpServer = smtp.gmail.com
smtpUser = yourname@gmail.com
- smtpServerPort = 587
+ smtpServerPort = 465
smtpAuth = OAUTHBEARER
----
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 743b7b00e4..03e9e69d25 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -684,7 +684,7 @@ other
`GIT_PROGRESS_DELAY`::
A number controlling how many seconds to delay before showing
- optional progress indicators. Defaults to 2.
+ optional progress indicators. Defaults to 1.
`GIT_EDITOR`::
This environment variable overrides `$EDITOR` and `$VISUAL`.
diff --git a/Documentation/gitk.adoc b/Documentation/gitk.adoc
index 58ce40ddb1..5b34dcd077 100644
--- a/Documentation/gitk.adoc
+++ b/Documentation/gitk.adoc
@@ -163,16 +163,16 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to
History
-------
-Gitk was the first graphical repository browser. It's written in
-tcl/tk.
+Gitk was the first graphical repository browser, written by
+Paul Mackerras in Tcl/Tk.
'gitk' is actually maintained as an independent project, but stable
versions are distributed as part of the Git suite for the convenience
of end users.
-gitk-git/ comes from Paul Mackerras's gitk project:
+`gitk-git/` comes from Johannes Sixt's gitk project:
- git://ozlabs.org/~paulus/gitk
+ https://github.com/j6t/gitk
SEE ALSO
--------
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 41f43e0336..e34965c5b0 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -74,6 +74,7 @@ manpages = {
'git-init.adoc' : 1,
'git-instaweb.adoc' : 1,
'git-interpret-trailers.adoc' : 1,
+ 'git-last-modified.adoc' : 1,
'git-log.adoc' : 1,
'git-ls-files.adoc' : 1,
'git-ls-remote.adoc' : 1,
diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc
index 9ed0417fc8..618ddc4a0c 100644
--- a/Documentation/pretty-formats.adoc
+++ b/Documentation/pretty-formats.adoc
@@ -233,11 +233,11 @@ colon and zero or more comma-separated options. Option values may contain
literal formatting codes. These must be used for commas (`%x2C`) and closing
parentheses (`%x29`), due to their role in the option syntax.
+
-** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}+(+".
+** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}++(++".
** `suffix=<value>`: Shown after the list of ref names. Defaults to "+)+".
** `separator=<value>`: Shown between ref names. Defaults to "+,+{nbsp}".
** `pointer=<value>`: Shown between HEAD and the branch it points to, if any.
- Defaults to "{nbsp}+->+{nbsp}".
+ Defaults to "{nbsp}++->++{nbsp}".
** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}".
+
diff --git a/Makefile b/Makefile
index 555b7f4dc3..4c95affadb 100644
--- a/Makefile
+++ b/Makefile
@@ -1265,6 +1265,7 @@ BUILTIN_OBJS += builtin/hook.o
BUILTIN_OBJS += builtin/index-pack.o
BUILTIN_OBJS += builtin/init-db.o
BUILTIN_OBJS += builtin/interpret-trailers.o
+BUILTIN_OBJS += builtin/last-modified.o
BUILTIN_OBJS += builtin/log.o
BUILTIN_OBJS += builtin/ls-files.o
BUILTIN_OBJS += builtin/ls-remote.o
@@ -3945,13 +3946,12 @@ unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
.PHONY: libgit-sys libgit-rs
-libgit-sys libgit-rs:
- $(QUIET)(\
- cd contrib/$@ && \
- cargo build \
- )
+libgit-sys:
+ $(QUIET)cargo build --manifest-path contrib/libgit-sys/Cargo.toml
+libgit-rs: libgit-sys
+ $(QUIET)cargo build --manifest-path contrib/libgit-rs/Cargo.toml
ifdef INCLUDE_LIBGIT_RS
-all:: libgit-sys libgit-rs
+all:: libgit-rs
endif
LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o
diff --git a/alloc.c b/alloc.c
index 377e80f5dd..533a045c2a 100644
--- a/alloc.c
+++ b/alloc.c
@@ -36,19 +36,25 @@ struct alloc_state {
int slab_nr, slab_alloc;
};
-struct alloc_state *allocate_alloc_state(void)
+struct alloc_state *alloc_state_alloc(void)
{
return xcalloc(1, sizeof(struct alloc_state));
}
-void clear_alloc_state(struct alloc_state *s)
+void alloc_state_free_and_null(struct alloc_state **s_)
{
+ struct alloc_state *s = *s_;
+
+ if (!s)
+ return;
+
while (s->slab_nr > 0) {
s->slab_nr--;
free(s->slabs[s->slab_nr]);
}
FREE_AND_NULL(s->slabs);
+ FREE_AND_NULL(*s_);
}
static inline void *alloc_node(struct alloc_state *s, size_t node_size)
diff --git a/alloc.h b/alloc.h
index 3f4a0ad310..87a47a9709 100644
--- a/alloc.h
+++ b/alloc.h
@@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r);
void *alloc_tag_node(struct repository *r);
void *alloc_object_node(struct repository *r);
-struct alloc_state *allocate_alloc_state(void);
-void clear_alloc_state(struct alloc_state *s);
+struct alloc_state *alloc_state_alloc(void);
+void alloc_state_free_and_null(struct alloc_state **s_);
#endif
diff --git a/builtin.h b/builtin.h
index e6458e6fb9..1b35565fbd 100644
--- a/builtin.h
+++ b/builtin.h
@@ -176,6 +176,7 @@ int cmd_hook(int argc, const char **argv, const char *prefix, struct repository
int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_interpret_trailers(int argc, const char **argv, const char *prefix, struct repository *repo);
+int cmd_last_modified(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_log_reflog(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_log(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_ls_files(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/add.c b/builtin/add.c
index 0235854f80..740c7c4581 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -389,6 +389,7 @@ int cmd_add(int argc,
char *seen = NULL;
char *ps_matched = NULL;
struct lock_file lock_file = LOCK_INIT;
+ struct odb_transaction *transaction;
repo_config(repo, add_config, NULL);
@@ -574,7 +575,7 @@ int cmd_add(int argc,
string_list_clear(&only_match_skip_worktree, 0);
}
- begin_odb_transaction();
+ transaction = begin_odb_transaction(repo->objects);
ps_matched = xcalloc(pathspec.nr, 1);
if (add_renormalize)
@@ -593,7 +594,7 @@ int cmd_add(int argc,
if (chmod_arg && pathspec.nr)
exit_status |= chmod_pathspec(repo, &pathspec, chmod_arg[0], show_only);
- end_odb_transaction();
+ end_odb_transaction(transaction);
finish:
if (write_locked_index(repo->index, &lock_file,
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 6656187f90..fe3ebaadad 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -102,7 +102,7 @@ static int graph_verify(int argc, const char **argv, const char *prefix,
if (opts.progress)
flags |= COMMIT_GRAPH_WRITE_PROGRESS;
- source = odb_find_source(the_repository->objects, opts.obj_dir);
+ source = odb_find_source_or_die(the_repository->objects, opts.obj_dir);
graph_name = get_commit_graph_filename(source);
chain_name = get_commit_graph_chain_filename(source);
if (open_commit_graph(graph_name, &fd, &st))
@@ -291,7 +291,7 @@ static int graph_write(int argc, const char **argv, const char *prefix,
git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
- source = odb_find_source(the_repository->objects, opts.obj_dir);
+ source = odb_find_source_or_die(the_repository->objects, opts.obj_dir);
if (opts.reachable) {
if (write_commit_graph_reachable(source, flags, &write_opts))
diff --git a/builtin/commit.c b/builtin/commit.c
index 8a5dee384d..384d0b4e93 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -695,6 +695,7 @@ static int author_date_is_interesting(void)
return author_message || force_date;
}
+#ifndef WITH_BREAKING_CHANGES
static void adjust_comment_line_char(const struct strbuf *sb)
{
char candidates[] = "#;@!$%^&|:";
@@ -732,6 +733,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)
free(comment_line_str_to_free);
comment_line_str = comment_line_str_to_free = xstrfmt("%c", *p);
}
+#endif /* !WITH_BREAKING_CHANGES */
static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
struct pretty_print_context *ctx)
@@ -928,8 +930,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
die_errno(_("could not write commit template"));
+#ifndef WITH_BREAKING_CHANGES
if (auto_comment_line_char)
adjust_comment_line_char(&sb);
+#endif /* !WITH_BREAKING_CHANGES */
strbuf_release(&sb);
/* This checks if committer ident is explicitly given */
@@ -1793,6 +1797,9 @@ int cmd_commit(int argc,
show_usage_with_options_if_asked(argc, argv,
builtin_commit_usage, builtin_commit_options);
+#ifndef WITH_BREAKING_CHANGES
+ warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
diff --git a/builtin/describe.c b/builtin/describe.c
index 0540976210..9f4e26d7ff 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -24,6 +24,7 @@
#include "commit-slab.h"
#include "wildmatch.h"
#include "prio-queue.h"
+#include "oidset.h"
#define MAX_TAGS (FLAG_BITS - 1)
#define DEFAULT_CANDIDATES 10
@@ -286,38 +287,47 @@ static void lazy_queue_clear(struct lazy_queue *queue)
queue->get_pending = false;
}
-static bool all_have_flag(const struct lazy_queue *queue, unsigned flag)
+static unsigned long finish_depth_computation(struct lazy_queue *queue,
+ struct possible_tag *best)
{
+ unsigned long seen_commits = 0;
+ struct oidset unflagged = OIDSET_INIT;
+
for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) {
struct commit *commit = queue->queue.array[i].data;
- if (!(commit->object.flags & flag))
- return false;
+ if (!(commit->object.flags & best->flag_within))
+ oidset_insert(&unflagged, &commit->object.oid);
}
- return true;
-}
-static unsigned long finish_depth_computation(struct lazy_queue *queue,
- struct possible_tag *best)
-{
- unsigned long seen_commits = 0;
while (!lazy_queue_empty(queue)) {
struct commit *c = lazy_queue_get(queue);
struct commit_list *parents = c->parents;
seen_commits++;
if (c->object.flags & best->flag_within) {
- if (all_have_flag(queue, best->flag_within))
+ if (!oidset_size(&unflagged))
break;
- } else
+ } else {
+ oidset_remove(&unflagged, &c->object.oid);
best->depth++;
+ }
while (parents) {
+ unsigned seen, flag_before, flag_after;
struct commit *p = parents->item;
repo_parse_commit(the_repository, p);
- if (!(p->object.flags & SEEN))
+ seen = p->object.flags & SEEN;
+ if (!seen)
lazy_queue_put(queue, p);
+ flag_before = p->object.flags & best->flag_within;
p->object.flags |= c->object.flags;
+ flag_after = p->object.flags & best->flag_within;
+ if (!seen && !flag_after)
+ oidset_insert(&unflagged, &p->object.oid);
+ if (seen && !flag_before && flag_after)
+ oidset_remove(&unflagged, &p->object.oid);
parents = parents->next;
}
}
+ oidset_clear(&unflagged);
return seen_commits;
}
@@ -352,9 +362,9 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf
repo_find_unique_abbrev(the_repository, oid, abbrev));
}
-static void describe_commit(struct object_id *oid, struct strbuf *dst)
+static void describe_commit(struct commit *cmit, struct strbuf *dst)
{
- struct commit *cmit, *gave_up_on = NULL;
+ struct commit *gave_up_on = NULL;
struct lazy_queue queue = LAZY_QUEUE_INIT;
struct commit_name *n;
struct possible_tag all_matches[MAX_TAGS];
@@ -362,8 +372,6 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
unsigned long seen_commits = 0;
unsigned int unannotated_cnt = 0;
- cmit = lookup_commit_reference(the_repository, oid);
-
n = find_commit_name(&cmit->object.oid);
if (n && (tags || all || n->prio == 2)) {
/*
@@ -371,7 +379,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
*/
append_name(n, dst);
if (n->misnamed || longformat)
- append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
+ append_suffix(0, n->tag ? get_tagged_oid(n->tag) : &cmit->object.oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
return;
@@ -528,8 +536,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
}
struct process_commit_data {
- struct object_id current_commit;
- struct object_id looking_for;
+ struct commit *current_commit;
+ const struct object_id *looking_for;
struct strbuf *dst;
struct rev_info *revs;
};
@@ -537,30 +545,38 @@ struct process_commit_data {
static void process_commit(struct commit *commit, void *data)
{
struct process_commit_data *pcd = data;
- pcd->current_commit = commit->object.oid;
+ pcd->current_commit = commit;
}
static void process_object(struct object *obj, const char *path, void *data)
{
struct process_commit_data *pcd = data;
- if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+ if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) {
reset_revision_walk();
- describe_commit(&pcd->current_commit, pcd->dst);
- strbuf_addf(pcd->dst, ":%s", path);
+ if (pcd->current_commit) {
+ describe_commit(pcd->current_commit, pcd->dst);
+ strbuf_addf(pcd->dst, ":%s", path);
+ }
free_commit_list(pcd->revs->commits);
pcd->revs->commits = NULL;
}
}
-static void describe_blob(struct object_id oid, struct strbuf *dst)
+static void describe_blob(const struct object_id *oid, struct strbuf *dst)
{
struct rev_info revs;
struct strvec args = STRVEC_INIT;
- struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs};
+ struct object_id head_oid;
+ struct process_commit_data pcd = { NULL, oid, dst, &revs};
+
+ if (repo_get_oid(the_repository, "HEAD", &head_oid))
+ die(_("cannot search for blob '%s' on an unborn branch"),
+ oid_to_hex(oid));
strvec_pushl(&args, "internal: The first arg is not parsed",
- "--objects", "--in-commit-order", "--reverse", "HEAD",
+ "--objects", "--in-commit-order", "--reverse",
+ oid_to_hex(&head_oid),
NULL);
repo_init_revisions(the_repository, &revs, NULL);
@@ -574,6 +590,9 @@ static void describe_blob(struct object_id oid, struct strbuf *dst)
reset_revision_walk();
release_revisions(&revs);
strvec_clear(&args);
+
+ if (!dst->len)
+ die(_("blob '%s' not reachable from HEAD"), oid_to_hex(oid));
}
static void describe(const char *arg, int last_one)
@@ -590,10 +609,10 @@ static void describe(const char *arg, int last_one)
cmit = lookup_commit_reference_gently(the_repository, &oid, 1);
if (cmit)
- describe_commit(&oid, &sb);
+ describe_commit(cmit, &sb);
else if (odb_read_object_info(the_repository->objects,
&oid, NULL) == OBJ_BLOB)
- describe_blob(oid, &sb);
+ describe_blob(&oid, &sb);
else
die(_("%s is neither a commit nor blob"), arg);
diff --git a/builtin/last-modified.c b/builtin/last-modified.c
new file mode 100644
index 0000000000..886ba12cb5
--- /dev/null
+++ b/builtin/last-modified.c
@@ -0,0 +1,326 @@
+#include "git-compat-util.h"
+#include "bloom.h"
+#include "builtin.h"
+#include "commit-graph.h"
+#include "commit.h"
+#include "config.h"
+#include "environment.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "environment.h"
+#include "hashmap.h"
+#include "hex.h"
+#include "log-tree.h"
+#include "object-name.h"
+#include "object.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "repository.h"
+#include "revision.h"
+
+struct last_modified_entry {
+ struct hashmap_entry hashent;
+ struct object_id oid;
+ struct bloom_key key;
+ const char path[FLEX_ARRAY];
+};
+
+static int last_modified_entry_hashcmp(const void *unused UNUSED,
+ const struct hashmap_entry *hent1,
+ const struct hashmap_entry *hent2,
+ const void *path)
+{
+ const struct last_modified_entry *ent1 =
+ container_of(hent1, const struct last_modified_entry, hashent);
+ const struct last_modified_entry *ent2 =
+ container_of(hent2, const struct last_modified_entry, hashent);
+ return strcmp(ent1->path, path ? path : ent2->path);
+}
+
+struct last_modified {
+ struct hashmap paths;
+ struct rev_info rev;
+ bool recursive;
+ bool show_trees;
+};
+
+static void last_modified_release(struct last_modified *lm)
+{
+ struct hashmap_iter iter;
+ struct last_modified_entry *ent;
+
+ hashmap_for_each_entry(&lm->paths, &iter, ent, hashent)
+ bloom_key_clear(&ent->key);
+
+ hashmap_clear_and_free(&lm->paths, struct last_modified_entry, hashent);
+ release_revisions(&lm->rev);
+}
+
+struct last_modified_callback_data {
+ struct last_modified *lm;
+ struct commit *commit;
+};
+
+static void add_path_from_diff(struct diff_queue_struct *q,
+ struct diff_options *opt UNUSED, void *data)
+{
+ struct last_modified *lm = data;
+
+ for (int i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ struct last_modified_entry *ent;
+ const char *path = p->two->path;
+
+ FLEX_ALLOC_STR(ent, path, path);
+ oidcpy(&ent->oid, &p->two->oid);
+ if (lm->rev.bloom_filter_settings)
+ bloom_key_fill(&ent->key, path, strlen(path),
+ lm->rev.bloom_filter_settings);
+ hashmap_entry_init(&ent->hashent, strhash(ent->path));
+ hashmap_add(&lm->paths, &ent->hashent);
+ }
+}
+
+static int populate_paths_from_revs(struct last_modified *lm)
+{
+ int num_interesting = 0;
+ struct diff_options diffopt;
+
+ /*
+ * Create a copy of `struct diff_options`. In this copy a callback is
+ * set that when called adds entries to `paths` in `struct last_modified`.
+ * This copy is used to diff the tree of the target revision against an
+ * empty tree. This results in all paths in the target revision being
+ * listed. After `paths` is populated, we don't need this copy no more.
+ */
+ memcpy(&diffopt, &lm->rev.diffopt, sizeof(diffopt));
+ copy_pathspec(&diffopt.pathspec, &lm->rev.diffopt.pathspec);
+ diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ diffopt.format_callback = add_path_from_diff;
+ diffopt.format_callback_data = lm;
+
+ for (size_t i = 0; i < lm->rev.pending.nr; i++) {
+ struct object_array_entry *obj = lm->rev.pending.objects + i;
+
+ if (obj->item->flags & UNINTERESTING)
+ continue;
+
+ if (num_interesting++)
+ return error(_("last-modified can only operate on one tree at a time"));
+
+ diff_tree_oid(lm->rev.repo->hash_algo->empty_tree,
+ &obj->item->oid, "", &diffopt);
+ diff_flush(&diffopt);
+ }
+ clear_pathspec(&diffopt.pathspec);
+
+ return 0;
+}
+
+static void last_modified_emit(struct last_modified *lm,
+ const char *path, const struct commit *commit)
+
+{
+ if (commit->object.flags & BOUNDARY)
+ putchar('^');
+ printf("%s\t", oid_to_hex(&commit->object.oid));
+
+ if (lm->rev.diffopt.line_termination)
+ write_name_quoted(path, stdout, '\n');
+ else
+ printf("%s%c", path, '\0');
+}
+
+static void mark_path(const char *path, const struct object_id *oid,
+ struct last_modified_callback_data *data)
+{
+ struct last_modified_entry *ent;
+
+ /* Is it even a path that we are interested in? */
+ ent = hashmap_get_entry_from_hash(&data->lm->paths, strhash(path), path,
+ struct last_modified_entry, hashent);
+ if (!ent)
+ return;
+
+ /*
+ * Is it arriving at a version of interest, or is it from a side branch
+ * which did not contribute to the final state?
+ */
+ if (!oideq(oid, &ent->oid))
+ return;
+
+ last_modified_emit(data->lm, path, data->commit);
+
+ hashmap_remove(&data->lm->paths, &ent->hashent, path);
+ bloom_key_clear(&ent->key);
+ free(ent);
+}
+
+static void last_modified_diff(struct diff_queue_struct *q,
+ struct diff_options *opt UNUSED, void *cbdata)
+{
+ struct last_modified_callback_data *data = cbdata;
+
+ for (int i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ switch (p->status) {
+ case DIFF_STATUS_DELETED:
+ /*
+ * There's no point in feeding a deletion, as it could
+ * not have resulted in our current state, which
+ * actually has the file.
+ */
+ break;
+
+ default:
+ /*
+ * Otherwise, we care only that we somehow arrived at
+ * a final oid state. Note that this covers some
+ * potentially controversial areas, including:
+ *
+ * 1. A rename or copy will be found, as it is the
+ * first time the content has arrived at the given
+ * path.
+ *
+ * 2. Even a non-content modification like a mode or
+ * type change will trigger it.
+ *
+ * We take the inclusive approach for now, and find
+ * anything which impacts the path. Options to tweak
+ * the behavior (e.g., to "--follow" the content across
+ * renames) can come later.
+ */
+ mark_path(p->two->path, &p->two->oid, data);
+ break;
+ }
+ }
+}
+
+static bool maybe_changed_path(struct last_modified *lm, struct commit *origin)
+{
+ struct bloom_filter *filter;
+ struct last_modified_entry *ent;
+ struct hashmap_iter iter;
+
+ if (!lm->rev.bloom_filter_settings)
+ return true;
+
+ if (commit_graph_generation(origin) == GENERATION_NUMBER_INFINITY)
+ return true;
+
+ filter = get_bloom_filter(lm->rev.repo, origin);
+ if (!filter)
+ return true;
+
+ hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) {
+ if (bloom_filter_contains(filter, &ent->key,
+ lm->rev.bloom_filter_settings))
+ return true;
+ }
+ return false;
+}
+
+static int last_modified_run(struct last_modified *lm)
+{
+ struct last_modified_callback_data data = { .lm = lm };
+
+ lm->rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ lm->rev.diffopt.format_callback = last_modified_diff;
+ lm->rev.diffopt.format_callback_data = &data;
+
+ prepare_revision_walk(&lm->rev);
+
+ while (hashmap_get_size(&lm->paths)) {
+ data.commit = get_revision(&lm->rev);
+ if (!data.commit)
+ BUG("paths remaining beyond boundary in last-modified");
+
+ if (data.commit->object.flags & BOUNDARY) {
+ diff_tree_oid(lm->rev.repo->hash_algo->empty_tree,
+ &data.commit->object.oid, "",
+ &lm->rev.diffopt);
+ diff_flush(&lm->rev.diffopt);
+
+ break;
+ }
+
+ if (!maybe_changed_path(lm, data.commit))
+ continue;
+
+ log_tree_commit(&lm->rev, data.commit);
+ }
+
+ return 0;
+}
+
+static int last_modified_init(struct last_modified *lm, struct repository *r,
+ const char *prefix, int argc, const char **argv)
+{
+ hashmap_init(&lm->paths, last_modified_entry_hashcmp, NULL, 0);
+
+ repo_init_revisions(r, &lm->rev, prefix);
+ lm->rev.def = "HEAD";
+ lm->rev.combine_merges = 1;
+ lm->rev.show_root_diff = 1;
+ lm->rev.boundary = 1;
+ lm->rev.no_commit_id = 1;
+ lm->rev.diff = 1;
+ lm->rev.diffopt.flags.recursive = lm->recursive;
+ lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees;
+
+ argc = setup_revisions(argc, argv, &lm->rev, NULL);
+ if (argc > 1) {
+ error(_("unknown last-modified argument: %s"), argv[1]);
+ return argc;
+ }
+
+ lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo);
+
+ if (populate_paths_from_revs(lm) < 0)
+ return error(_("unable to setup last-modified"));
+
+ return 0;
+}
+
+int cmd_last_modified(int argc, const char **argv, const char *prefix,
+ struct repository *repo)
+{
+ int ret;
+ struct last_modified lm = { 0 };
+
+ const char * const last_modified_usage[] = {
+ N_("git last-modified [--recursive] [--show-trees] "
+ "[<revision-range>] [[--] <path>...]"),
+ NULL
+ };
+
+ struct option last_modified_options[] = {
+ OPT_BOOL('r', "recursive", &lm.recursive,
+ N_("recurse into subtrees")),
+ OPT_BOOL('t', "show-trees", &lm.show_trees,
+ N_("show tree entries when recursing into subtrees")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, last_modified_options,
+ last_modified_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
+
+ repo_config(repo, git_default_config, NULL);
+
+ ret = last_modified_init(&lm, repo, prefix, argc, argv);
+ if (ret > 0)
+ usage_with_options(last_modified_usage,
+ last_modified_options);
+ if (ret)
+ goto out;
+
+ ret = last_modified_run(&lm);
+ if (ret)
+ goto out;
+
+out:
+ last_modified_release(&lm);
+
+ return ret;
+}
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index c06a6f33e4..b148607f7a 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -414,14 +414,21 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
if (!(show_cached || show_stage || show_deleted || show_modified))
return;
- if (!show_sparse_dirs)
- ensure_full_index(repo->index);
-
for (i = 0; i < repo->index->cache_nr; i++) {
const struct cache_entry *ce = repo->index->cache[i];
struct stat st;
int stat_err;
+ if (S_ISSPARSEDIR(ce->ce_mode) && !show_sparse_dirs) {
+ /*
+ * This is the first time we've hit a sparse dir,
+ * so expansion will leave the first 'i' entries
+ * alone.
+ */
+ ensure_full_index(repo->index);
+ ce = repo->index->cache[i];
+ }
+
construct_fullname(&fullname, repo, ce);
if ((dir->flags & DIR_SHOW_IGNORED) &&
diff --git a/builtin/merge.c b/builtin/merge.c
index b235af730a..c421a11b0b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1379,6 +1379,9 @@ int cmd_merge(int argc,
show_usage_with_options_if_asked(argc, argv,
builtin_merge_usage, builtin_merge_options);
+#ifndef WITH_BREAKING_CHANGES
+ warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index d3b9e98be3..5f364aa816 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -65,12 +65,20 @@ static int parse_object_dir(const struct option *opt, const char *arg,
char **value = opt->value;
free(*value);
if (unset)
- *value = xstrdup(repo_get_object_directory(the_repository));
+ *value = xstrdup(the_repository->objects->sources->path);
else
*value = real_pathdup(arg, 1);
return 0;
}
+static struct odb_source *handle_object_dir_option(struct repository *repo)
+{
+ struct odb_source *source = odb_find_source(repo->objects, opts.object_dir);
+ if (!source)
+ source = odb_add_to_alternates_memory(repo->objects, opts.object_dir);
+ return source;
+}
+
static struct option common_opts[] = {
OPT_CALLBACK(0, "object-dir", &opts.object_dir,
N_("directory"),
@@ -140,6 +148,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
N_("refs snapshot for selecting bitmap commits")),
OPT_END(),
};
+ struct odb_source *source;
int ret;
opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
@@ -158,6 +167,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
if (argc)
usage_with_options(builtin_multi_pack_index_write_usage,
options);
+ source = handle_object_dir_option(repo);
FREE_AND_NULL(options);
@@ -166,7 +176,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
read_packs_from_stdin(&packs);
- ret = write_midx_file_only(repo, opts.object_dir, &packs,
+ ret = write_midx_file_only(source, &packs,
opts.preferred_pack,
opts.refs_snapshot, opts.flags);
@@ -177,7 +187,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
}
- ret = write_midx_file(repo, opts.object_dir, opts.preferred_pack,
+ ret = write_midx_file(source, opts.preferred_pack,
opts.refs_snapshot, opts.flags);
free(opts.refs_snapshot);
@@ -194,6 +204,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
+ struct odb_source *source;
+
options = add_common_options(builtin_multi_pack_index_verify_options);
trace2_cmd_mode(argv[0]);
@@ -206,10 +218,11 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv,
if (argc)
usage_with_options(builtin_multi_pack_index_verify_usage,
options);
+ source = handle_object_dir_option(the_repository);
FREE_AND_NULL(options);
- return verify_midx_file(the_repository, opts.object_dir, opts.flags);
+ return verify_midx_file(source, opts.flags);
}
static int cmd_multi_pack_index_expire(int argc, const char **argv,
@@ -222,6 +235,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
+ struct odb_source *source;
+
options = add_common_options(builtin_multi_pack_index_expire_options);
trace2_cmd_mode(argv[0]);
@@ -234,10 +249,11 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
if (argc)
usage_with_options(builtin_multi_pack_index_expire_usage,
options);
+ source = handle_object_dir_option(the_repository);
FREE_AND_NULL(options);
- return expire_midx_packs(the_repository, opts.object_dir, opts.flags);
+ return expire_midx_packs(source, opts.flags);
}
static int cmd_multi_pack_index_repack(int argc, const char **argv,
@@ -252,6 +268,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
+ struct odb_source *source;
options = add_common_options(builtin_multi_pack_index_repack_options);
@@ -266,11 +283,11 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
if (argc)
usage_with_options(builtin_multi_pack_index_repack_usage,
options);
+ source = handle_object_dir_option(the_repository);
FREE_AND_NULL(options);
- return midx_repack(the_repository, opts.object_dir,
- (size_t)opts.batch_size, opts.flags);
+ return midx_repack(source, (size_t)opts.batch_size, opts.flags);
}
int cmd_multi_pack_index(int argc,
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 53a2256250..1494afcf3d 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1741,7 +1741,7 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
struct multi_pack_index *m = get_multi_pack_index(source);
struct pack_entry e;
- if (m && fill_midx_entry(the_repository, oid, &e, m)) {
+ if (m && fill_midx_entry(m, oid, &e)) {
want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset, found_mtime);
if (want != -1)
return want;
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 3c85768d29..67c0352bf8 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,
builtin_rebase_usage,
builtin_rebase_options);
+#ifndef WITH_BREAKING_CHANGES
+ warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0;
diff --git a/builtin/refs.c b/builtin/refs.c
index 76224feba4..91548783b7 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -7,6 +7,7 @@
#include "strbuf.h"
#include "worktree.h"
#include "for-each-ref.h"
+#include "refs/refs-internal.h"
#define REFS_MIGRATE_USAGE \
N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
@@ -14,6 +15,9 @@
#define REFS_VERIFY_USAGE \
N_("git refs verify [--strict] [--verbose]")
+#define REFS_EXISTS_USAGE \
+ N_("git refs exists <ref>")
+
static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
struct repository *repo UNUSED)
{
@@ -113,6 +117,48 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix,
return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
}
+static int cmd_refs_exists(int argc, const char **argv, const char *prefix,
+ struct repository *repo UNUSED)
+{
+ struct strbuf unused_referent = STRBUF_INIT;
+ struct object_id unused_oid;
+ unsigned int unused_type;
+ int failure_errno = 0;
+ const char *ref;
+ int ret = 0;
+ const char * const exists_usage[] = {
+ REFS_EXISTS_USAGE,
+ NULL,
+ };
+ struct option options[] = {
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, options, exists_usage, 0);
+ if (argc != 1)
+ die(_("'git refs exists' requires a reference"));
+
+ ref = *argv++;
+ if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
+ &unused_oid, &unused_referent, &unused_type,
+ &failure_errno)) {
+ if (failure_errno == ENOENT || failure_errno == EISDIR) {
+ error(_("reference does not exist"));
+ ret = 2;
+ } else {
+ errno = failure_errno;
+ error_errno(_("failed to look up reference"));
+ ret = 1;
+ }
+
+ goto out;
+ }
+
+out:
+ strbuf_release(&unused_referent);
+ return ret;
+}
+
int cmd_refs(int argc,
const char **argv,
const char *prefix,
@@ -122,6 +168,7 @@ int cmd_refs(int argc,
REFS_MIGRATE_USAGE,
REFS_VERIFY_USAGE,
"git refs list " COMMON_USAGE_FOR_EACH_REF,
+ REFS_EXISTS_USAGE,
NULL,
};
parse_opt_subcommand_fn *fn = NULL;
@@ -129,6 +176,7 @@ int cmd_refs(int argc,
OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
+ OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists),
OPT_END(),
};
diff --git a/builtin/repack.c b/builtin/repack.c
index a4def39197..c490a51e91 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -223,9 +223,10 @@ static void mark_packs_for_deletion(struct existing_packs *existing,
static void remove_redundant_pack(const char *dir_name, const char *base_name)
{
struct strbuf buf = STRBUF_INIT;
- struct multi_pack_index *m = get_multi_pack_index(the_repository->objects->sources);
+ struct odb_source *source = the_repository->objects->sources;
+ struct multi_pack_index *m = get_multi_pack_index(source);
strbuf_addf(&buf, "%s.pack", base_name);
- if (m && m->local && midx_contains_pack(m, buf.buf))
+ if (m && source->local && midx_contains_pack(m, buf.buf))
clear_midx_file(the_repository);
strbuf_insertf(&buf, 0, "%s/", dir_name);
unlink_pack_path(buf.buf, 1);
@@ -1711,7 +1712,7 @@ int cmd_repack(int argc,
unsigned flags = 0;
if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))
flags |= MIDX_WRITE_INCREMENTAL;
- write_midx_file(the_repository, repo_get_object_directory(the_repository),
+ write_midx_file(the_repository->objects->sources,
NULL, NULL, flags);
}
diff --git a/builtin/repo.c b/builtin/repo.c
index 8c6e7f42ab..bbb0966f2d 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -9,7 +9,7 @@
#include "shallow.h"
static const char *const repo_usage[] = {
- "git repo info [--format=(keyvalue|nul)] [<key>...]",
+ "git repo info [--format=(keyvalue|nul)] [-z] [<key>...]",
NULL
};
@@ -38,6 +38,12 @@ static int get_layout_shallow(struct repository *repo, struct strbuf *buf)
return 0;
}
+static int get_object_format(struct repository *repo, struct strbuf *buf)
+{
+ strbuf_addstr(buf, repo->hash_algo->name);
+ return 0;
+}
+
static int get_references_format(struct repository *repo, struct strbuf *buf)
{
strbuf_addstr(buf,
@@ -49,6 +55,7 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
static const struct field repo_info_fields[] = {
{ "layout.bare", get_layout_bare },
{ "layout.shallow", get_layout_shallow },
+ { "object.format", get_object_format },
{ "references.format", get_references_format },
};
@@ -112,26 +119,40 @@ static int print_fields(int argc, const char **argv,
return ret;
}
+static int parse_format_cb(const struct option *opt,
+ const char *arg, int unset UNUSED)
+{
+ enum output_format *format = opt->value;
+
+ if (opt->short_name == 'z')
+ *format = FORMAT_NUL_TERMINATED;
+ else if (!strcmp(arg, "nul"))
+ *format = FORMAT_NUL_TERMINATED;
+ else if (!strcmp(arg, "keyvalue"))
+ *format = FORMAT_KEYVALUE;
+ else
+ die(_("invalid format '%s'"), arg);
+
+ return 0;
+}
+
static int repo_info(int argc, const char **argv, const char *prefix,
struct repository *repo)
{
- const char *format_str = "keyvalue";
- enum output_format format;
+ enum output_format format = FORMAT_KEYVALUE;
struct option options[] = {
- OPT_STRING(0, "format", &format_str, N_("format"),
- N_("output format")),
+ OPT_CALLBACK_F(0, "format", &format, N_("format"),
+ N_("output format"),
+ PARSE_OPT_NONEG, parse_format_cb),
+ OPT_CALLBACK_F('z', NULL, &format, NULL,
+ N_("synonym for --format=nul"),
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+ parse_format_cb),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
- if (!strcmp(format_str, "keyvalue"))
- format = FORMAT_KEYVALUE;
- else if (!strcmp(format_str, "nul"))
- format = FORMAT_NUL_TERMINATED;
- else
- die(_("invalid format '%s'"), format_str);
-
return print_fields(argc, argv, repo, format);
}
diff --git a/builtin/revert.c b/builtin/revert.c
index c3f92b585d..bedc40f368 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -4,6 +4,7 @@
#include "builtin.h"
#include "parse-options.h"
#include "diff.h"
+#include "environment.h"
#include "gettext.h"
#include "revision.h"
#include "rerere.h"
@@ -285,6 +286,9 @@ int cmd_revert(int argc,
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
+#ifndef WITH_BREAKING_CHANGES
+ warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
opts.action = REPLAY_REVERT;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, prefix, &opts);
@@ -302,6 +306,9 @@ struct repository *repo UNUSED)
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
+#ifndef WITH_BREAKING_CHANGES
+ warn_on_auto_comment_char = true;
+#endif /* !WITH_BREAKING_CHANGES */
opts.action = REPLAY_PICK;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, prefix, &opts);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 7ae7c82b6c..28124b324d 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -584,6 +584,7 @@ static void unpack_all(void)
{
int i;
unsigned char *hdr = fill(sizeof(struct pack_header));
+ struct odb_transaction *transaction;
if (get_be32(hdr) != PACK_SIGNATURE)
die("bad pack file");
@@ -599,12 +600,12 @@ static void unpack_all(void)
progress = start_progress(the_repository,
_("Unpacking objects"), nr_objects);
CALLOC_ARRAY(obj_list, nr_objects);
- begin_odb_transaction();
+ transaction = begin_odb_transaction(the_repository->objects);
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
display_progress(progress, i + 1);
}
- end_odb_transaction();
+ end_odb_transaction(transaction);
stop_progress(&progress);
if (delta_list)
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 2380f3ccd6..2ba2d29c95 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -77,7 +77,7 @@ static void report(const char *fmt, ...)
* objects invisible while a transaction is active, so flush the
* transaction here before reporting a change made by update-index.
*/
- flush_odb_transaction();
+ flush_odb_transaction(the_repository->objects->transaction);
va_start(vp, fmt);
vprintf(fmt, vp);
putchar('\n');
@@ -940,6 +940,7 @@ int cmd_update_index(int argc,
strbuf_getline_fn getline_fn;
int parseopt_state = PARSE_OPT_UNKNOWN;
struct repository *r = the_repository;
+ struct odb_transaction *transaction;
struct option options[] = {
OPT_BIT('q', NULL, &refresh_args.flags,
N_("continue refresh even when index needs update"),
@@ -1130,7 +1131,7 @@ int cmd_update_index(int argc,
* Allow the object layer to optimize adding multiple objects in
* a batch.
*/
- begin_odb_transaction();
+ transaction = begin_odb_transaction(the_repository->objects);
while (ctx.argc) {
if (parseopt_state != PARSE_OPT_DONE)
parseopt_state = parse_options_step(&ctx, options,
@@ -1213,7 +1214,7 @@ int cmd_update_index(int argc,
/*
* By now we have added all of the new objects
*/
- end_odb_transaction();
+ end_odb_transaction(transaction);
if (split_index > 0) {
if (repo_config_get_split_index(the_repository) == 0)
diff --git a/bulk-checkin.c b/bulk-checkin.c
index b2809ab039..124c493067 100644
--- a/bulk-checkin.c
+++ b/bulk-checkin.c
@@ -19,11 +19,7 @@
#include "object-file.h"
#include "odb.h"
-static int odb_transaction_nesting;
-
-static struct tmp_objdir *bulk_fsync_objdir;
-
-static struct bulk_checkin_packfile {
+struct bulk_checkin_packfile {
char *pack_tmp_name;
struct hashfile *f;
off_t offset;
@@ -32,27 +28,36 @@ static struct bulk_checkin_packfile {
struct pack_idx_entry **written;
uint32_t alloc_written;
uint32_t nr_written;
-} bulk_checkin_packfile;
+};
+
+struct odb_transaction {
+ struct object_database *odb;
+
+ int nesting;
+ struct tmp_objdir *objdir;
+ struct bulk_checkin_packfile packfile;
+};
-static void finish_tmp_packfile(struct strbuf *basename,
- const char *pack_tmp_name,
- struct pack_idx_entry **written_list,
- uint32_t nr_written,
- struct pack_idx_option *pack_idx_opts,
+static void finish_tmp_packfile(struct odb_transaction *transaction,
+ struct strbuf *basename,
unsigned char hash[])
{
+ struct bulk_checkin_packfile *state = &transaction->packfile;
+ struct repository *repo = transaction->odb->repo;
char *idx_tmp_name = NULL;
- stage_tmp_packfiles(the_repository, basename, pack_tmp_name,
- written_list, nr_written, NULL, pack_idx_opts, hash,
- &idx_tmp_name);
- rename_tmp_packfile_idx(the_repository, basename, &idx_tmp_name);
+ stage_tmp_packfiles(repo, basename, state->pack_tmp_name,
+ state->written, state->nr_written, NULL,
+ &state->pack_idx_opts, hash, &idx_tmp_name);
+ rename_tmp_packfile_idx(repo, basename, &idx_tmp_name);
free(idx_tmp_name);
}
-static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)
+static void flush_bulk_checkin_packfile(struct odb_transaction *transaction)
{
+ struct bulk_checkin_packfile *state = &transaction->packfile;
+ struct repository *repo = transaction->odb->repo;
unsigned char hash[GIT_MAX_RAWSZ];
struct strbuf packname = STRBUF_INIT;
@@ -69,17 +74,17 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state)
CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
} else {
int fd = finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK, 0);
- fixup_pack_header_footer(the_hash_algo, fd, hash, state->pack_tmp_name,
+ fixup_pack_header_footer(repo->hash_algo, fd, hash, state->pack_tmp_name,
state->nr_written, hash,
state->offset);
close(fd);
}
- strbuf_addf(&packname, "%s/pack/pack-%s.", repo_get_object_directory(the_repository),
- hash_to_hex(hash));
- finish_tmp_packfile(&packname, state->pack_tmp_name,
- state->written, state->nr_written,
- &state->pack_idx_opts, hash);
+ strbuf_addf(&packname, "%s/pack/pack-%s.",
+ repo_get_object_directory(transaction->odb->repo),
+ hash_to_hex_algop(hash, repo->hash_algo));
+
+ finish_tmp_packfile(transaction, &packname, hash);
for (uint32_t i = 0; i < state->nr_written; i++)
free(state->written[i]);
@@ -90,18 +95,18 @@ clear_exit:
strbuf_release(&packname);
/* Make objects we just wrote available to ourselves */
- reprepare_packed_git(the_repository);
+ reprepare_packed_git(repo);
}
/*
* Cleanup after batch-mode fsync_object_files.
*/
-static void flush_batch_fsync(void)
+static void flush_batch_fsync(struct odb_transaction *transaction)
{
struct strbuf temp_path = STRBUF_INIT;
struct tempfile *temp;
- if (!bulk_fsync_objdir)
+ if (!transaction->objdir)
return;
/*
@@ -113,7 +118,8 @@ static void flush_batch_fsync(void)
* to ensure that the data in each new object file is durable before
* the final name is visible.
*/
- strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", repo_get_object_directory(the_repository));
+ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX",
+ repo_get_object_directory(transaction->odb->repo));
temp = xmks_tempfile(temp_path.buf);
fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp));
delete_tempfile(&temp);
@@ -123,20 +129,21 @@ static void flush_batch_fsync(void)
* Make the object files visible in the primary ODB after their data is
* fully durable.
*/
- tmp_objdir_migrate(bulk_fsync_objdir);
- bulk_fsync_objdir = NULL;
+ tmp_objdir_migrate(transaction->objdir);
+ transaction->objdir = NULL;
}
-static int already_written(struct bulk_checkin_packfile *state, struct object_id *oid)
+static int already_written(struct odb_transaction *transaction,
+ struct object_id *oid)
{
/* The object may already exist in the repository */
- if (odb_has_object(the_repository->objects, oid,
+ if (odb_has_object(transaction->odb, oid,
HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
return 1;
/* Might want to keep the list sorted */
- for (uint32_t i = 0; i < state->nr_written; i++)
- if (oideq(&state->written[i]->oid, oid))
+ for (uint32_t i = 0; i < transaction->packfile.nr_written; i++)
+ if (oideq(&transaction->packfile.written[i]->oid, oid))
return 1;
/* This is a new object we need to keep */
@@ -235,13 +242,15 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state,
}
/* Lazily create backing packfile for the state */
-static void prepare_to_stream(struct bulk_checkin_packfile *state,
+static void prepare_to_stream(struct odb_transaction *transaction,
unsigned flags)
{
+ struct bulk_checkin_packfile *state = &transaction->packfile;
if (!(flags & INDEX_WRITE_OBJECT) || state->f)
return;
- state->f = create_tmp_packfile(the_repository, &state->pack_tmp_name);
+ state->f = create_tmp_packfile(transaction->odb->repo,
+ &state->pack_tmp_name);
reset_pack_idx_option(&state->pack_idx_opts);
/* Pretend we are going to write only one object */
@@ -250,11 +259,11 @@ static void prepare_to_stream(struct bulk_checkin_packfile *state,
die_errno("unable to write pack header");
}
-static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
- struct object_id *result_oid,
- int fd, size_t size,
- const char *path, unsigned flags)
+int index_blob_bulk_checkin(struct odb_transaction *transaction,
+ struct object_id *result_oid, int fd, size_t size,
+ const char *path, unsigned flags)
{
+ struct bulk_checkin_packfile *state = &transaction->packfile;
off_t seekback, already_hashed_to;
struct git_hash_ctx ctx;
unsigned char obuf[16384];
@@ -268,21 +277,21 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
header_len = format_object_header((char *)obuf, sizeof(obuf),
OBJ_BLOB, size);
- the_hash_algo->init_fn(&ctx);
+ transaction->odb->repo->hash_algo->init_fn(&ctx);
git_hash_update(&ctx, obuf, header_len);
/* Note: idx is non-NULL when we are writing */
if ((flags & INDEX_WRITE_OBJECT) != 0) {
CALLOC_ARRAY(idx, 1);
- prepare_to_stream(state, flags);
+ prepare_to_stream(transaction, flags);
hashfile_checkpoint_init(state->f, &checkpoint);
}
already_hashed_to = 0;
while (1) {
- prepare_to_stream(state, flags);
+ prepare_to_stream(transaction, flags);
if (idx) {
hashfile_checkpoint(state->f, &checkpoint);
idx->offset = state->offset;
@@ -300,7 +309,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
BUG("should not happen");
hashfile_truncate(state->f, &checkpoint);
state->offset = checkpoint.offset;
- flush_bulk_checkin_packfile(state);
+ flush_bulk_checkin_packfile(transaction);
if (lseek(fd, seekback, SEEK_SET) == (off_t) -1)
return error("cannot seek back");
}
@@ -309,7 +318,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
return 0;
idx->crc32 = crc32_end(state->f);
- if (already_written(state, result_oid)) {
+ if (already_written(transaction, result_oid)) {
hashfile_truncate(state->f, &checkpoint);
state->offset = checkpoint.offset;
free(idx);
@@ -323,7 +332,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state,
return 0;
}
-void prepare_loose_object_bulk_checkin(void)
+void prepare_loose_object_bulk_checkin(struct odb_transaction *transaction)
{
/*
* We lazily create the temporary object directory
@@ -331,15 +340,16 @@ void prepare_loose_object_bulk_checkin(void)
* callers may not know whether any objects will be
* added at the time they call begin_odb_transaction.
*/
- if (!odb_transaction_nesting || bulk_fsync_objdir)
+ if (!transaction || transaction->objdir)
return;
- bulk_fsync_objdir = tmp_objdir_create(the_repository, "bulk-fsync");
- if (bulk_fsync_objdir)
- tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0);
+ transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync");
+ if (transaction->objdir)
+ tmp_objdir_replace_primary_odb(transaction->objdir, 0);
}
-void fsync_loose_object_bulk_checkin(int fd, const char *filename)
+void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction,
+ int fd, const char *filename)
{
/*
* If we have an active ODB transaction, we issue a call that
@@ -348,7 +358,7 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename)
* before renaming the objects to their final names as part of
* flush_batch_fsync.
*/
- if (!bulk_fsync_objdir ||
+ if (!transaction || !transaction->objdir ||
git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) {
if (errno == ENOSYS)
warning(_("core.fsyncMethod = batch is unsupported on this platform"));
@@ -356,36 +366,38 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename)
}
}
-int index_blob_bulk_checkin(struct object_id *oid,
- int fd, size_t size,
- const char *path, unsigned flags)
+struct odb_transaction *begin_odb_transaction(struct object_database *odb)
{
- int status = deflate_blob_to_pack(&bulk_checkin_packfile, oid, fd, size,
- path, flags);
- if (!odb_transaction_nesting)
- flush_bulk_checkin_packfile(&bulk_checkin_packfile);
- return status;
-}
+ if (!odb->transaction) {
+ CALLOC_ARRAY(odb->transaction, 1);
+ odb->transaction->odb = odb;
+ }
-void begin_odb_transaction(void)
-{
- odb_transaction_nesting += 1;
+ odb->transaction->nesting += 1;
+
+ return odb->transaction;
}
-void flush_odb_transaction(void)
+void flush_odb_transaction(struct odb_transaction *transaction)
{
- flush_batch_fsync();
- flush_bulk_checkin_packfile(&bulk_checkin_packfile);
+ if (!transaction)
+ return;
+
+ flush_batch_fsync(transaction);
+ flush_bulk_checkin_packfile(transaction);
}
-void end_odb_transaction(void)
+void end_odb_transaction(struct odb_transaction *transaction)
{
- odb_transaction_nesting -= 1;
- if (odb_transaction_nesting < 0)
+ if (!transaction || transaction->nesting == 0)
BUG("Unbalanced ODB transaction nesting");
- if (odb_transaction_nesting)
+ transaction->nesting -= 1;
+
+ if (transaction->nesting)
return;
- flush_odb_transaction();
+ flush_odb_transaction(transaction);
+ transaction->odb->transaction = NULL;
+ free(transaction);
}
diff --git a/bulk-checkin.h b/bulk-checkin.h
index 7246ea58dc..ac8887f476 100644
--- a/bulk-checkin.h
+++ b/bulk-checkin.h
@@ -5,13 +5,20 @@
#define BULK_CHECKIN_H
#include "object.h"
+#include "odb.h"
-void prepare_loose_object_bulk_checkin(void);
-void fsync_loose_object_bulk_checkin(int fd, const char *filename);
+struct odb_transaction;
+
+void prepare_loose_object_bulk_checkin(struct odb_transaction *transaction);
+void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction,
+ int fd, const char *filename);
/*
- * This creates one packfile per large blob unless bulk-checkin
- * machinery is "plugged".
+ * This writes the specified object to a packfile. Objects written here
+ * during the same transaction are written to the same packfile. The
+ * packfile is not flushed until the transaction is flushed. The caller
+ * is expected to ensure a valid transaction is setup for objects to be
+ * recorded to.
*
* This also bypasses the usual "convert-to-git" dance, and that is on
* purpose. We could write a streaming version of the converting
@@ -24,8 +31,8 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename);
* binary blobs, they generally do not want to get any conversion, and
* callers should avoid this code path when filters are requested.
*/
-int index_blob_bulk_checkin(struct object_id *oid,
- int fd, size_t size,
+int index_blob_bulk_checkin(struct odb_transaction *transaction,
+ struct object_id *oid, int fd, size_t size,
const char *path, unsigned flags);
/*
@@ -35,20 +42,20 @@ int index_blob_bulk_checkin(struct object_id *oid,
* and objects are only visible after the outermost transaction
* is complete or the transaction is flushed.
*/
-void begin_odb_transaction(void);
+struct odb_transaction *begin_odb_transaction(struct object_database *odb);
/*
* Make any objects that are currently part of a pending object
* database transaction visible. It is valid to call this function
* even if no transaction is active.
*/
-void flush_odb_transaction(void);
+void flush_odb_transaction(struct odb_transaction *transaction);
/*
* Tell the object database to make any objects from the
* current transaction visible if this is the final nested
* transaction.
*/
-void end_odb_transaction(void);
+void end_odb_transaction(struct odb_transaction *transaction);
#endif
diff --git a/cache-tree.c b/cache-tree.c
index 66ef2becbe..d225554eed 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -474,6 +474,7 @@ static int update_one(struct cache_tree *it,
int cache_tree_update(struct index_state *istate, int flags)
{
+ struct odb_transaction *transaction;
int skip, i;
i = verify_cache(istate, flags);
@@ -489,10 +490,10 @@ int cache_tree_update(struct index_state *istate, int flags)
trace_performance_enter();
trace2_region_enter("cache_tree", "update", the_repository);
- begin_odb_transaction();
+ transaction = begin_odb_transaction(the_repository->objects);
i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
"", 0, &skip, flags);
- end_odb_transaction();
+ end_odb_transaction(transaction);
trace2_region_leave("cache_tree", "update", the_repository);
trace_performance_leave("cache_tree_update");
if (i < 0)
diff --git a/command-list.txt b/command-list.txt
index 1b0bdee00d..accd3d0c4b 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -124,6 +124,7 @@ git-index-pack plumbingmanipulators
git-init mainporcelain init
git-instaweb ancillaryinterrogators complete
git-interpret-trailers purehelpers
+git-last-modified plumbinginterrogators
git-log mainporcelain info
git-ls-files plumbinginterrogators
git-ls-remote plumbinginterrogators
diff --git a/commit-graph.c b/commit-graph.c
index 3cd9e73e2a..2f20f66cfd 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -812,7 +812,12 @@ int corrected_commit_dates_enabled(struct repository *r)
struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r)
{
- struct commit_graph *g = r->objects->commit_graph;
+ struct commit_graph *g;
+
+ if (!prepare_commit_graph(r))
+ return NULL;
+
+ g = r->objects->commit_graph;
while (g) {
if (g->bloom_filter_settings)
return g->bloom_filter_settings;
diff --git a/config.c b/config.c
index e0ff35d426..74bf76a97e 100644
--- a/config.c
+++ b/config.c
@@ -8,9 +8,11 @@
#include "git-compat-util.h"
#include "abspath.h"
+#include "advice.h"
#include "date.h"
#include "branch.h"
#include "config.h"
+#include "dir.h"
#include "parse.h"
#include "convert.h"
#include "environment.h"
@@ -1948,10 +1950,290 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d
return 1;
}
+struct comment_char_config {
+ unsigned last_key_id;
+ bool auto_set;
+ bool auto_set_in_file;
+ struct strintmap key_flags;
+ size_t alloc, nr;
+ struct comment_char_config_item {
+ unsigned key_id;
+ char *path;
+ enum config_scope scope;
+ } *item;
+};
+
+#define COMMENT_CHAR_CFG_INIT { \
+ .key_flags = STRINTMAP_INIT, \
+ }
+
+static void comment_char_config_release(struct comment_char_config *config)
+{
+ strintmap_clear(&config->key_flags);
+ for (size_t i = 0; i < config->nr; i++)
+ free(config->item[i].path);
+ free(config->item);
+}
+
+/* Used to track whether the key occurs more than once in a given file */
+#define KEY_SEEN_ONCE 1u
+#define KEY_SEEN_TWICE 2u
+#define COMMENT_KEY_SHIFT(id) (2 * (id))
+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))
+
+static void set_comment_key_flags(struct comment_char_config *config,
+ const char *path, unsigned id, unsigned value)
+{
+ unsigned old = strintmap_get(&config->key_flags, path);
+ unsigned new = (old & ~COMMENT_KEY_MASK(id)) |
+ value << COMMENT_KEY_SHIFT(id);
+
+ strintmap_set(&config->key_flags, path, new);
+}
+
+static unsigned get_comment_key_flags(struct comment_char_config *config,
+ const char *path, unsigned id)
+{
+ unsigned value = strintmap_get(&config->key_flags, path);
+
+ return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);
+}
+
+static const char *comment_key_name(unsigned id)
+{
+ static const char *name[] = {
+ "core.commentChar",
+ "core.commentString",
+ };
+
+ if (id >= ARRAY_SIZE(name))
+ BUG("invalid comment key id");
+
+ return name[id];
+}
+
+static void comment_char_callback(const char *key, const char *value,
+ const struct config_context *ctx, void *data)
+{
+ struct comment_char_config *config = data;
+ const struct key_value_info *kvi = ctx->kvi;
+ unsigned key_id;
+
+ if (!strcmp(key, "core.commentchar"))
+ key_id = 0;
+ else if (!strcmp(key, "core.commentstring"))
+ key_id = 1;
+ else
+ return;
+
+ config->last_key_id = key_id;
+ config->auto_set = value && !strcmp(value, "auto");
+ if (kvi->origin_type != CONFIG_ORIGIN_FILE) {
+ return;
+ } else if (get_comment_key_flags(config, kvi->filename, key_id)) {
+ set_comment_key_flags(config, kvi->filename, key_id,
+ KEY_SEEN_TWICE);
+ } else {
+ struct comment_char_config_item *item;
+
+ ALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);
+ item = &config->item[config->nr - 1];
+ item->key_id = key_id;
+ item->scope = kvi->scope;
+ item->path = xstrdup(kvi->filename);
+ set_comment_key_flags(config, kvi->filename, key_id,
+ KEY_SEEN_ONCE);
+ }
+ config->auto_set_in_file = config->auto_set;
+}
+
+static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,
+ struct comment_char_config_item *item)
+{
+ char *global_config = git_global_config();
+ char *system_config = git_system_config();
+
+ if (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {
+ /*
+ * If the user cannot write to the system config recommend
+ * setting the global config instead.
+ */
+ strbuf_addstr(buf, "--global ");
+ } else if (fspatheq(item->path, system_config)) {
+ strbuf_addstr(buf, "--system ");
+ } else if (fspatheq(item->path, global_config)) {
+ strbuf_addstr(buf, "--global ");
+ } else if (fspatheq(item->path,
+ mkpath("%s/config",
+ repo_get_git_dir(repo)))) {
+ ; /* --local is the default */
+ } else if (fspatheq(item->path,
+ mkpath("%s/config.worktree",
+ repo_get_common_dir(repo)))) {
+ strbuf_addstr(buf, "--worktree ");
+ } else {
+ const char *path = item->path;
+ const char *home = getenv("HOME");
+
+ strbuf_addstr(buf, "--file ");
+ if (home && !fspathncmp(path, home, strlen(home))) {
+ path += strlen(home);
+ if (!fspathncmp(path, "/", 1))
+ path++;
+ strbuf_addstr(buf, "~/");
+ }
+ sq_quote_buf_pretty(buf, path);
+ strbuf_addch(buf, ' ');
+ }
+
+ free(global_config);
+ free(system_config);
+}
+
+static bool can_unset_comment_char_config(struct comment_char_config *config)
+{
+ for (size_t i = 0; i < config->nr; i++) {
+ struct comment_char_config_item *item = &config->item[i];
+
+ if (item->scope == CONFIG_SCOPE_SYSTEM &&
+ access(item->path, W_OK))
+ return false;
+ }
+
+ return true;
+}
+
+static void add_unset_auto_comment_char_advice(struct repository *repo,
+ struct comment_char_config *config)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ if (!can_unset_comment_char_config(config))
+ return;
+
+ for (size_t i = 0; i < config->nr; i++) {
+ struct comment_char_config_item *item = &config->item[i];
+
+ strbuf_addstr(&buf, " git config unset ");
+ add_config_scope_arg(repo, &buf, item);
+ if (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)
+ strbuf_addstr(&buf, "--all ");
+ strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id));
+ }
+ advise(_("\nTo use the default comment string (#) please run\n\n%s"),
+ buf.buf);
+ strbuf_release(&buf);
+}
+
+static void add_comment_char_advice(struct repository *repo,
+ struct comment_char_config *config)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct comment_char_config_item *item;
+ /* TRANSLATORS this is a place holder for the value of core.commentString */
+ const char *placeholder = _("<comment string>");
+
+ /*
+ * If auto is set in the last file that we saw advise the user how to
+ * update their config.
+ */
+ if (!config->auto_set_in_file)
+ return;
+
+ add_unset_auto_comment_char_advice(repo, config);
+ item = &config->item[config->nr - 1];
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, " git config set ");
+ add_config_scope_arg(repo, &buf, item);
+ strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id),
+ placeholder);
+ advise(_("\nTo set a custom comment string please run\n\n"
+ "%s\nwhere '%s' is the string you wish to use.\n"),
+ buf.buf, placeholder);
+ strbuf_release(&buf);
+}
+
+#undef KEY_SEEN_ONCE
+#undef KEY_SEEN_TWICE
+#undef COMMENT_KEY_SHIFT
+#undef COMMENT_KEY_MASK
+
+struct repo_config {
+ struct repository *repo;
+ struct comment_char_config comment_char_config;
+};
+
+#define REPO_CONFIG_INIT(repo_) { \
+ .comment_char_config = COMMENT_CHAR_CFG_INIT, \
+ .repo = repo_, \
+ };
+
+static void repo_config_release(struct repo_config *config)
+{
+ comment_char_config_release(&config->comment_char_config);
+}
+
+#ifdef WITH_BREAKING_CHANGES
+static void check_auto_comment_char_config(struct repository *repo,
+ struct comment_char_config *config)
+{
+ if (!config->auto_set)
+ return;
+
+ die_message(_("Support for '%s=auto' has been removed in Git 3.0"),
+ comment_key_name(config->last_key_id));
+ add_comment_char_advice(repo, config);
+ die(NULL);
+}
+#else
+static void check_auto_comment_char_config(struct repository *repo,
+ struct comment_char_config *config)
+{
+ extern bool warn_on_auto_comment_char;
+ const char *DEPRECATED_CONFIG_ENV =
+ "GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN";
+
+ if (!config->auto_set || !warn_on_auto_comment_char)
+ return;
+
+ /*
+ * Use an environment variable to ensure that subprocesses do not repeat
+ * the warning.
+ */
+ if (git_env_bool(DEPRECATED_CONFIG_ENV, false))
+ return;
+
+ setenv(DEPRECATED_CONFIG_ENV, "true", true);
+
+ warning(_("Support for '%s=auto' is deprecated and will be removed in "
+ "Git 3.0"), comment_key_name(config->last_key_id));
+ add_comment_char_advice(repo, config);
+}
+#endif /* WITH_BREAKING_CHANGES */
+
+static void check_deprecated_config(struct repo_config *config)
+{
+ if (!config->repo->check_deprecated_config)
+ return;
+
+ check_auto_comment_char_config(config->repo,
+ &config->comment_char_config);
+}
+
+static int repo_config_callback(const char *key, const char *value,
+ const struct config_context *ctx, void *data)
+{
+ struct repo_config *config = data;
+
+ comment_char_callback(key, value, ctx, &config->comment_char_config);
+ return config_set_callback(key, value, ctx, config->repo->config);
+}
+
/* Functions use to read configuration from a repository */
static void repo_read_config(struct repository *repo)
{
struct config_options opts = { 0 };
+ struct repo_config config = REPO_CONFIG_INIT(repo);
opts.respect_includes = 1;
opts.commondir = repo->commondir;
@@ -1963,8 +2245,8 @@ static void repo_read_config(struct repository *repo)
git_configset_clear(repo->config);
git_configset_init(repo->config);
- if (config_with_options(config_set_callback, repo->config, NULL,
- repo, &opts) < 0)
+ if (config_with_options(repo_config_callback, &config, NULL, repo,
+ &opts) < 0)
/*
* config_with_options() normally returns only
* zero, as most errors are fatal, and
@@ -1977,6 +2259,8 @@ static void repo_read_config(struct repository *repo)
* immediately.
*/
die(_("unknown error occurred while reading the configuration files"));
+ check_deprecated_config(&config);
+ repo_config_release(&config);
}
static void git_config_check_init(struct repository *repo)
@@ -2664,6 +2948,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
char *contents = NULL;
size_t contents_sz;
struct config_store_data store = CONFIG_STORE_INIT;
+ bool saved_check_deprecated_config = r->check_deprecated_config;
+
+ /*
+ * Do not warn or die if there are deprecated config settings as
+ * we want the user to be able to change those settings by running
+ * "git config".
+ */
+ r->check_deprecated_config = false;
validate_comment_string(comment);
@@ -2895,6 +3187,7 @@ out_free:
if (in_fd >= 0)
close(in_fd);
config_store_data_clear(&store);
+ r->check_deprecated_config = saved_check_deprecated_config;
return ret;
write_err_out:
diff --git a/environment.c b/environment.c
index 0e72fdac85..a770b5921d 100644
--- a/environment.c
+++ b/environment.c
@@ -121,7 +121,10 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;
*/
const char *comment_line_str = "#";
char *comment_line_str_to_free;
+#ifndef WITH_BREAKING_CHANGES
int auto_comment_line_char;
+bool warn_on_auto_comment_char;
+#endif /* !WITH_BREAKING_CHANGES */
/* This is set by setup_git_directory_gently() and/or git_default_config() */
char *git_work_tree_cfg;
@@ -463,16 +466,22 @@ static int git_default_core_config(const char *var, const char *value,
if (!strcmp(var, "core.commentchar") ||
!strcmp(var, "core.commentstring")) {
- if (!value)
+ if (!value) {
return config_error_nonbool(var);
- else if (!strcasecmp(value, "auto"))
+#ifndef WITH_BREAKING_CHANGES
+ } else if (!strcasecmp(value, "auto")) {
auto_comment_line_char = 1;
- else if (value[0]) {
+ FREE_AND_NULL(comment_line_str_to_free);
+ comment_line_str = "#";
+#endif /* !WITH_BREAKING_CHANGES */
+ } else if (value[0]) {
if (strchr(value, '\n'))
return error(_("%s cannot contain newline"), var);
comment_line_str = value;
FREE_AND_NULL(comment_line_str_to_free);
+#ifndef WITH_BREAKING_CHANGES
auto_comment_line_char = 0;
+#endif /* !WITH_BREAKING_CHANGES */
} else
return error(_("%s must have at least one character"), var);
return 0;
diff --git a/environment.h b/environment.h
index 8cfce41015..51898c99cd 100644
--- a/environment.h
+++ b/environment.h
@@ -208,7 +208,10 @@ extern char *excludes_file;
*/
extern const char *comment_line_str;
extern char *comment_line_str_to_free;
+#ifndef WITH_BREAKING_CHANGES
extern int auto_comment_line_char;
+extern bool warn_on_auto_comment_char;
+#endif /* !WITH_BREAKING_CHANGES */
# endif /* USE_THE_REPOSITORY_VARIABLE */
#endif /* ENVIRONMENT_H */
diff --git a/fetch-pack.c b/fetch-pack.c
index 20e5533b21..6ed5662951 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -143,7 +143,8 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid,
commit = lookup_commit_in_graph(the_repository, oid);
if (commit) {
if (mark_tags_complete_and_check_obj_db) {
- if (!odb_has_object(the_repository->objects, oid, 0))
+ if (!odb_has_object(the_repository->objects, oid,
+ HAS_OBJECT_RECHECK_PACKED))
die_in_commit_graph_only(oid);
}
return commit;
diff --git a/git-curl-compat.h b/git-curl-compat.h
index aa8eed7ed2..659e5a3875 100644
--- a/git-curl-compat.h
+++ b/git-curl-compat.h
@@ -46,6 +46,13 @@
#endif
/**
+ * curl_global_trace() was added in 8.3.0, released September 2023.
+ */
+#if LIBCURL_VERSION_NUM >= 0x080300
+#define GIT_CURL_HAVE_GLOBAL_TRACE 1
+#endif
+
+/**
* CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024.
*/
#if LIBCURL_VERSION_NUM >= 0x080900
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 27bbe051de..69b0b84435 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -186,6 +186,7 @@ install: all
$(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
$(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(INSTALL_X0)git-gui--askpass $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+ $(QUIET)$(INSTALL_X0)git-gui--askyesno $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
ifdef GITGUI_WINDOWS_WRAPPER
$(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
@@ -200,6 +201,7 @@ uninstall:
$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askpass $(REMOVE_F1)
+ $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askyesno $(REMOVE_F1)
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
ifdef GITGUI_WINDOWS_WRAPPER
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1)
diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno
new file mode 100755
index 0000000000..142d1bc3de
--- /dev/null
+++ b/git-gui/git-gui--askyesno
@@ -0,0 +1,63 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+# This is an implementation of a simple yes no dialog
+# which is injected into the git commandline by git gui
+# in case a yesno question needs to be answered.
+#
+# The window title, which defaults to "Question?", can be
+# overridden via the optional `--title` command-line
+# option.
+
+set NS {}
+set use_ttk [package vsatisfies [package provide Tk] 8.5]
+if {$use_ttk} {
+ set NS ttk
+}
+
+set title "Question?"
+if {$argc < 1} {
+ puts stderr "Usage: $argv0 <question>"
+ exit 1
+} else {
+ if {$argc > 2 && [lindex $argv 0] == "--title"} {
+ set title [lindex $argv 1]
+ set argv [lreplace $argv 0 1]
+ }
+ set prompt [join $argv " "]
+}
+
+${NS}::frame .t
+${NS}::label .t.m -text $prompt -justify center -width 40
+.t.m configure -wraplength 400
+pack .t.m -side top -fill x -padx 20 -pady 20 -expand 1
+pack .t -side top -fill x -ipadx 20 -ipady 20 -expand 1
+
+${NS}::frame .b
+${NS}::frame .b.left -width 200
+${NS}::button .b.yes -text Yes -command {exit 0}
+${NS}::button .b.no -text No -command {exit 1}
+
+pack .b.left -side left -expand 1 -fill x
+pack .b.yes -side left -expand 1
+pack .b.no -side right -expand 1 -ipadx 5
+pack .b -side bottom -fill x -ipadx 20 -ipady 15
+
+bind . <Key-Return> {exit 0}
+bind . <Key-Escape> {exit 1}
+
+if {$::tcl_platform(platform) eq {windows}} {
+ set icopath [file dirname [file normalize $argv0]]
+ if {[file tail $icopath] eq {git-core}} {
+ set icopath [file dirname $icopath]
+ }
+ set icopath [file dirname $icopath]
+ set icopath [file join $icopath share git git-for-windows.ico]
+ if {[file exists $icopath]} {
+ wm iconbitmap . -default $icopath
+ }
+}
+
+wm title . $title
+tk::PlaceWindow .
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index a931d7f7c9..d3d3aa14a9 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -103,7 +103,6 @@ if {[is_Windows]} {
set _path_sep {:}
}
-set _search_path {}
set _path_seen [dict create]
foreach p [split $env(PATH) $_path_sep] {
# Keep only absolute paths, getting rid of ., empty, etc.
@@ -112,12 +111,9 @@ foreach p [split $env(PATH) $_path_sep] {
}
# Keep only the first occurence of any duplicates.
set norm_p [file normalize $p]
- if {[dict exists $_path_seen $norm_p]} {
- continue
- }
dict set _path_seen $norm_p 1
- lappend _search_path $norm_p
}
+set _search_path [dict keys $_path_seen]
unset _path_seen
set env(PATH) [join $_search_path $_path_sep]
@@ -583,21 +579,6 @@ proc open_cmd_pipe {cmd path} {
return [open |$run r]
}
-proc _lappend_nice {cmd_var} {
- global _nice
- upvar $cmd_var cmd
-
- if {![info exists _nice]} {
- set _nice [_which nice]
- if {[catch {safe_exec [list $_nice git version]}]} {
- set _nice {}
- }
- }
- if {$_nice ne {}} {
- lappend cmd $_nice
- }
-}
-
proc git {args} {
git_redir $args {}
}
@@ -631,15 +612,14 @@ proc git_read {cmd {redir {}}} {
return [safe_open_command $cmdp $redir]
}
-proc git_read_nice {cmd} {
- global _git
- set opt [list]
-
- _lappend_nice opt
-
- set cmdp [concat [list $_git] $cmd]
+set _nice [list [_which nice]]
+if {[catch {safe_exec [list {*}$_nice git version]}]} {
+ set _nice {}
+}
- return [safe_open_command [concat $opt $cmdp]]
+proc git_read_nice {cmd} {
+ set cmdp [list {*}$::_nice $::_git {*}$cmd]
+ return [safe_open_command $cmdp]
}
proc git_write {cmd} {
@@ -1130,6 +1110,12 @@ set argv0dir [file dirname [file normalize $::argv0]]
if {![info exists env(SSH_ASKPASS)]} {
set env(SSH_ASKPASS) [file join $argv0dir git-gui--askpass]
}
+if {![info exists env(GIT_ASKPASS)]} {
+ set env(GIT_ASKPASS) [file join $argv0dir git-gui--askpass]
+}
+if {![info exists env(GIT_ASK_YESNO)]} {
+ set env(GIT_ASK_YESNO) [file join $argv0dir git-gui--askyesno]
+}
unset argv0dir
######################################################################
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
index 7aa09c7728..e1d38e54be 100644
--- a/git-gui/lib/index.tcl
+++ b/git-gui/lib/index.tcl
@@ -425,6 +425,11 @@ proc revert_helper {txt paths} {
if {![lock_index begin-update]} return
+ # Workaround for Tcl < 9.0: chord namespaces are not obeyed and
+ # operated in the global namespace. This clears an error that could
+ # have been left over from a previous operation.
+ set ::err {}
+
# Common "after" functionality that waits until multiple asynchronous
# operations are complete (by waiting for them to activate their notes
# on the chord).
@@ -432,7 +437,7 @@ proc revert_helper {txt paths} {
# The asynchronous operations are each indicated below by a comment
# before the code block that starts the async operation.
set after_chord [SimpleChord::new {
- if {[string trim $err] != ""} {
+ if {[info exists err] && [string trim $err] ne ""} {
rescan_on_error $err
} else {
unlock_index
diff --git a/git-send-email.perl b/git-send-email.perl
index 437f8ac46a..96504e7be1 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -62,7 +62,7 @@ git send-email --translate-aliases
--smtp-user <str> * Username for SMTP-AUTH.
--smtp-pass <str> * Password for SMTP-AUTH; not necessary.
--smtp-encryption <str> * tls or ssl; anything else disables.
- --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'.
+ --smtp-ssl * Deprecated. Use `--smtp-encryption ssl`.
--smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file).
Pass an empty string to disable certificate
verification.
@@ -73,6 +73,10 @@ git send-email --translate-aliases
--no-smtp-auth * Disable SMTP authentication. Shorthand for
`--smtp-auth=none`
--smtp-debug <0|1> * Disable, enable Net::SMTP debug.
+ --imap-sent-folder <str> * IMAP folder where a copy of the emails should be sent.
+ Make sure `git imap-send` is set up to use this feature.
+ --[no-]use-imap-only * Only copy emails to the IMAP folder specified by
+ `--imap-sent-folder` instead of actually sending them.
--batch-size <int> * send max <int> message per connection.
--relogin-delay <int> * delay <int> seconds between two successive login.
@@ -200,7 +204,7 @@ my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/;
# Variables we fill in automatically, or via prompting:
my (@to,@cc,@xh,$envelope_sender,
- $initial_in_reply_to,$reply_to,$initial_subject,@files,
+ $initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy,
$author,$sender,$smtp_authpass,$annotate,$compose,$time);
# Things we either get from config, *or* are overridden on the
# command-line.
@@ -277,6 +281,7 @@ my ($smtp_server, $smtp_server_port, @smtp_server_options);
my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
my ($batch_size, $relogin_delay);
my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
+my ($imap_sent_folder);
my ($confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
@@ -293,6 +298,7 @@ my $mailmap = 0;
my $target_xfer_encoding = 'auto';
my $forbid_sendmail_variables = 1;
my $outlook_id_fix = 'auto';
+my $use_imap_only = 0;
my %config_bool_settings = (
"thread" => \$thread,
@@ -309,6 +315,7 @@ my %config_bool_settings = (
"forbidsendmailvariables" => \$forbid_sendmail_variables,
"mailmap" => \$mailmap,
"outlookidfix" => \$outlook_id_fix,
+ "useimaponly" => \$use_imap_only,
);
my %config_settings = (
@@ -322,6 +329,7 @@ my %config_settings = (
"smtpauth" => \$smtp_auth,
"smtpbatchsize" => \$batch_size,
"smtprelogindelay" => \$relogin_delay,
+ "imapsentfolder" => \$imap_sent_folder,
"to" => \@config_to,
"tocmd" => \$to_cmd,
"cc" => \@config_cc,
@@ -527,6 +535,8 @@ my %options = (
"smtp-domain:s" => \$smtp_domain,
"smtp-auth=s" => \$smtp_auth,
"no-smtp-auth" => sub {$smtp_auth = 'none'},
+ "imap-sent-folder=s" => \$imap_sent_folder,
+ "use-imap-only!" => \$use_imap_only,
"annotate!" => \$annotate,
"compose" => \$compose,
"quiet" => \$quiet,
@@ -1678,6 +1688,8 @@ EOF
if ($dry_run) {
# We don't want to send the email.
+ } elsif ($use_imap_only) {
+ die __("The destination IMAP folder is not properly defined.") if !defined $imap_sent_folder;
} elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
@@ -1829,6 +1841,17 @@ EOF
print "\n";
}
+ if ($imap_sent_folder && !$dry_run) {
+ my $imap_header = $header;
+ if (@initial_bcc) {
+ # Bcc is not a part of $header, so we add it here.
+ # This is only for the IMAP copy, not for the actual email
+ # sent to the recipients.
+ $imap_header .= "Bcc: " . join(", ", @initial_bcc) . "\n";
+ }
+ push @imap_copy, "From git-send-email\n$imap_header\n$message";
+ }
+
return 1;
}
@@ -2223,6 +2246,19 @@ sub cleanup_compose_files {
$smtp->quit if $smtp;
+if ($imap_sent_folder && @imap_copy && !$dry_run) {
+ my $imap_input = join("\n", @imap_copy);
+ eval {
+ print "\nStarting git imap-send...\n";
+ my ($fh, $ctx) = Git::command_input_pipe(['imap-send', '-f', $imap_sent_folder]);
+ print $fh $imap_input;
+ Git::command_close_pipe($fh, $ctx);
+ 1;
+ } or do {
+ warn "Warning: failed to send messages to IMAP folder $imap_sent_folder: $@";
+ };
+}
+
sub apply_transfer_encoding {
my $message = shift;
my $from = shift;
diff --git a/git.c b/git.c
index 5dc210b7b4..d020eef021 100644
--- a/git.c
+++ b/git.c
@@ -565,6 +565,7 @@ static struct cmd_struct commands[] = {
{ "init", cmd_init_db },
{ "init-db", cmd_init_db },
{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
+ { "last-modified", cmd_last_modified, RUN_SETUP },
{ "log", cmd_log, RUN_SETUP },
{ "ls-files", cmd_ls_files, RUN_SETUP },
{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
diff --git a/gitk-git/README.md b/gitk-git/README.md
new file mode 100644
index 0000000000..2e307463c6
--- /dev/null
+++ b/gitk-git/README.md
@@ -0,0 +1,93 @@
+Gitk - The Git Repository Browser
+=================================
+
+Gitk is a graphical Git repository browser. It displays the commit
+history of a Git repository as a graph, showing the relationships
+between commits, branches, and tags.
+
+Usage
+=====
+
+To view the history of the current repository:
+```bash
+gitk
+```
+
+To view the history of specific files or directories:
+```bash
+gitk path/to/file
+gitk path/to/directory
+```
+
+To view a specific branch or range of commits:
+```bash
+gitk branch-name
+gitk v1.0..v2.0
+```
+
+For more usage examples and options, see the [gitk manual](https://git-scm.com/docs/gitk).
+
+Building
+========
+
+Gitk is a Tcl/Tk application. It requires Tcl/Tk to be installed on
+your system.
+
+Running directly
+----------------
+
+Gitk can be run from the source directory without installation:
+
+```bash
+./gitk
+```
+
+This allows for quick testing of changes.
+
+Installation
+------------
+
+To install system-wide, you can use either `make` or `meson`:
+
+```bash
+# Install to default location ($HOME/bin)
+make install
+
+# Install to system-wide location
+sudo make install prefix=/usr/local
+
+# Install to custom location
+make install prefix=/opt/gitk
+
+# Using Meson
+meson setup builddir
+meson compile -C builddir
+meson install -C builddir
+```
+
+Both build systems will handle setting the correct Tcl/Tk interpreter
+path and installing translation files.
+
+Contributing
+============
+
+Contributions are welcome! The preferred method for submitting patches
+is via email to the Git mailing list, as this allows for more thorough
+review and broader community feedback. However, GitHub pull requests
+are also accepted.
+
+All commits must be signed off (use `git commit --signoff`) and should
+have commit messages prefixed with `gitk:`.
+
+Email Patches
+-------------
+
+Send patches to git@vger.kernel.org and CC j6t@kdbg.org. See the Git
+project's [patch submission guidelines](https://git-scm.com/docs/SubmittingPatches)
+for detailed instructions on creating and sending patches.
+
+License
+=======
+
+Gitk is distributed under the GNU General Public License, either
+version 2, or (at your option) any later version.
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 3b6acfc592..6e4d71d585 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2301,6 +2301,11 @@ proc scrollval {D {koff 0}} {
return [expr int(-($D / $scroll_D0) * max(1, $kscroll-$koff))]
}
+proc precisescrollval {D {koff 0}} {
+ global kscroll
+ return [expr (-($D / 10.0) * max(1, $kscroll-$koff))]
+}
+
proc bind_mousewheel {} {
global canv cflist ctext
bindall <MouseWheel> {allcanvs yview scroll [scrollval %D] units}
@@ -2319,6 +2324,25 @@ proc bind_mousewheel {} {
bind $cflist <Alt-MouseWheel> {$cflist yview scroll [scrollval 5*%D 2] units}
bind $cflist <Alt-Shift-MouseWheel> break
bind $canv <Alt-Shift-MouseWheel> {$canv xview scroll [scrollval 5*%D] units}
+
+ bindall <TouchpadScroll> {
+ lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+ allcanvs yview scroll [precisescrollval $deltaY] units
+ }
+ bind $ctext <TouchpadScroll> {
+ lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+ $ctext yview scroll [precisescrollval $deltaY 2] units
+ $ctext xview scroll [precisescrollval $deltaX 2] units
+ }
+ bind $cflist <TouchpadScroll> {
+ lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+ $cflist yview scroll [precisescrollval $deltaY 2] units
+ }
+ bind $canv <TouchpadScroll> {
+ lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
+ $canv xview scroll [precisescrollval $deltaX] units
+ allcanvs yview scroll [precisescrollval $deltaY] units
+ }
}
}
@@ -12596,7 +12620,7 @@ set foundbgcolor yellow
set currentsearchhitbgcolor orange
# button for popping up context menus
-if {[tk windowingsystem] eq "aqua"} {
+if {[tk windowingsystem] eq "aqua" && [package vcompare $::tcl_version 8.7] < 0} {
set ctxbut <Button-2>
} else {
set ctxbut <Button-3>
diff --git a/http.c b/http.c
index 98853d6483..a7d55dcbba 100644
--- a/http.c
+++ b/http.c
@@ -1348,6 +1348,14 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
die("curl_global_init failed");
+#ifdef GIT_CURL_HAVE_GLOBAL_TRACE
+ {
+ const char *comp = getenv("GIT_TRACE_CURL_COMPONENTS");
+ if (comp)
+ curl_global_trace(comp);
+ }
+#endif
+
if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE)
http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS;
diff --git a/imap-send.c b/imap-send.c
index 254ec83ab7..4bd5b8aa0d 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1442,14 +1442,24 @@ static int count_messages(struct strbuf *all_msgs)
while (1) {
if (starts_with(p, "From ")) {
- p = strstr(p+5, "\nFrom: ");
- if (!p) break;
- p = strstr(p+7, "\nDate: ");
- if (!p) break;
- p = strstr(p+7, "\nSubject: ");
- if (!p) break;
- p += 10;
- count++;
+ if (starts_with(p, "From git-send-email")) {
+ p = strstr(p+5, "\nFrom: ");
+ if (!p) break;
+ p += 7;
+ p = strstr(p, "\nTo: ");
+ if (!p) break;
+ p += 5;
+ count++;
+ } else {
+ p = strstr(p+5, "\nFrom: ");
+ if (!p) break;
+ p = strstr(p+7, "\nDate: ");
+ if (!p) break;
+ p = strstr(p+7, "\nSubject: ");
+ if (!p) break;
+ p += 10;
+ count++;
+ }
}
p = strstr(p+5, "\nFrom ");
if (!p)
diff --git a/line-log.c b/line-log.c
index 07f2154e84..8bd422148d 100644
--- a/line-log.c
+++ b/line-log.c
@@ -201,7 +201,7 @@ static void range_set_difference(struct range_set *out,
* b: ------|
*/
j++;
- if (j >= b->nr || end < b->ranges[j].start) {
+ if (j >= b->nr || end <= b->ranges[j].start) {
/*
* b exhausted, or
* a: ----|
@@ -408,7 +408,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out,
assert(out->target.nr == 0);
for (i = 0; i < diff->target.nr; i++) {
- while (diff->target.ranges[i].start > rs->ranges[j].end) {
+ while (diff->target.ranges[i].start >= rs->ranges[j].end) {
j++;
if (j == rs->nr)
return;
@@ -939,9 +939,18 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang
long t_cur = t_start;
unsigned int j_last;
+ /*
+ * If a diff range touches multiple line ranges, then all
+ * those line ranges should be shown, so take a step back if
+ * the current line range is still in the previous diff range
+ * (even if only partially).
+ */
+ if (j > 0 && diff->target.ranges[j-1].end > t_start)
+ j--;
+
while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
j++;
- if (j == diff->target.nr || diff->target.ranges[j].start > t_end)
+ if (j == diff->target.nr || diff->target.ranges[j].start >= t_end)
continue;
/* Scan ahead to determine the last diff that falls in this range */
@@ -1087,13 +1096,6 @@ static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
return new_filepair;
}
-static void free_diffqueues(int n, struct diff_queue_struct *dq)
-{
- for (int i = 0; i < n; i++)
- diff_queue_clear(&dq[i]);
- free(dq);
-}
-
static int process_all_files(struct line_log_data **range_out,
struct rev_info *rev,
struct diff_queue_struct *queue,
@@ -1189,7 +1191,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
struct line_log_data *range)
{
struct commit *parent = NULL;
- struct diff_queue_struct queue;
+ struct diff_queue_struct queue = DIFF_QUEUE_INIT;
struct line_log_data *parent_range;
int changed;
@@ -1209,9 +1211,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c
static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit,
struct line_log_data *range)
{
- struct diff_queue_struct *diffqueues;
struct line_log_data **cand;
- struct commit **parents;
struct commit_list *p;
int i;
int nparents = commit_list_count(commit->parents);
@@ -1220,28 +1220,27 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
if (nparents > 1 && rev->first_parent_only)
nparents = 1;
- ALLOC_ARRAY(diffqueues, nparents);
CALLOC_ARRAY(cand, nparents);
- ALLOC_ARRAY(parents, nparents);
- p = commit->parents;
- for (i = 0; i < nparents; i++) {
- parents[i] = p->item;
- p = p->next;
- queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]);
- }
-
- for (i = 0; i < nparents; i++) {
+ for (p = commit->parents, i = 0;
+ p && i < nparents;
+ p = p->next, i++) {
+ struct commit *parent = p->item;
+ struct diff_queue_struct diffqueue = DIFF_QUEUE_INIT;
int changed;
- changed = process_all_files(&cand[i], rev, &diffqueues[i], range);
+
+ queue_diffs(range, &rev->diffopt, &diffqueue, commit, parent);
+
+ changed = process_all_files(&cand[i], rev, &diffqueue, range);
+ diff_queue_clear(&diffqueue);
if (!changed) {
/*
* This parent can take all the blame, so we
* don't follow any other path in history
*/
- add_line_range(rev, parents[i], cand[i]);
+ add_line_range(rev, parent, cand[i]);
free_commit_list(commit->parents);
- commit_list_append(parents[i], &commit->parents);
+ commit_list_append(parent, &commit->parents);
ret = 0;
goto out;
@@ -1252,14 +1251,15 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm
* No single parent took the blame. We add the candidates
* from the above loop to the parents.
*/
- for (i = 0; i < nparents; i++)
- add_line_range(rev, parents[i], cand[i]);
+ for (p = commit->parents, i = 0;
+ p && i < nparents;
+ p = p->next, i++)
+ add_line_range(rev, p->item, cand[i]);
ret = 1;
out:
clear_commit_line_range(rev, commit);
- free(parents);
for (i = 0; i < nparents; i++) {
if (!cand[i])
continue;
@@ -1267,7 +1267,6 @@ out:
free(cand[i]);
}
free(cand);
- free_diffqueues(nparents, diffqueues);
return ret;
/* NEEDSWORK evil merge detection stuff */
@@ -1283,10 +1282,10 @@ int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit
struct line_log_data *prange = line_log_data_copy(range);
add_line_range(rev, commit->parents->item, prange);
clear_commit_line_range(rev, commit);
- } else if (!commit->parents || !commit->parents->next)
- changed = process_ranges_ordinary_commit(rev, commit, range);
- else
+ } else if (commit->parents && commit->parents->next)
changed = process_ranges_merge_commit(rev, commit, range);
+ else
+ changed = process_ranges_ordinary_commit(rev, commit, range);
}
if (!changed)
diff --git a/meson.build b/meson.build
index e8ec0eca16..b3dfcc0497 100644
--- a/meson.build
+++ b/meson.build
@@ -607,6 +607,7 @@ builtin_sources = [
'builtin/index-pack.c',
'builtin/init-db.c',
'builtin/interpret-trailers.c',
+ 'builtin/last-modified.c',
'builtin/log.c',
'builtin/ls-files.c',
'builtin/ls-remote.c',
diff --git a/midx-write.c b/midx-write.c
index a0aceab5e0..c73010df6d 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1,5 +1,3 @@
-#define DISABLE_SIGN_COMPARE_WARNINGS
-
#include "git-compat-util.h"
#include "abspath.h"
#include "config.h"
@@ -24,11 +22,12 @@
#define BITMAP_POS_UNKNOWN (~((uint32_t)0))
#define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
#define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
+#define NO_PREFERRED_PACK (~((uint32_t)0))
extern int midx_checksum_valid(struct multi_pack_index *m);
-extern void clear_midx_files_ext(const char *object_dir, const char *ext,
+extern void clear_midx_files_ext(struct odb_source *source, const char *ext,
const char *keep_hash);
-extern void clear_incremental_midx_files_ext(const char *object_dir,
+extern void clear_incremental_midx_files_ext(struct odb_source *source,
const char *ext,
const char **keep_hashes,
uint32_t hashes_nr);
@@ -104,7 +103,7 @@ struct write_midx_context {
unsigned large_offsets_needed:1;
uint32_t num_large_offsets;
- int preferred_pack_idx;
+ uint32_t preferred_pack_idx;
int incremental;
uint32_t num_multi_pack_indexes_before;
@@ -112,6 +111,7 @@ struct write_midx_context {
struct string_list *to_include;
struct repository *repo;
+ struct odb_source *source;
};
static int should_include_pack(const struct write_midx_context *ctx,
@@ -260,7 +260,7 @@ static void midx_fanout_sort(struct midx_fanout *fanout)
static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout,
struct multi_pack_index *m,
uint32_t cur_fanout,
- int preferred_pack)
+ uint32_t preferred_pack)
{
uint32_t start = m->num_objects_in_base, end;
uint32_t cur_object;
@@ -274,7 +274,7 @@ static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout,
end = m->num_objects_in_base + ntohl(m->chunk_oid_fanout[cur_fanout]);
for (cur_object = start; cur_object < end; cur_object++) {
- if ((preferred_pack > -1) &&
+ if ((preferred_pack != NO_PREFERRED_PACK) &&
(preferred_pack == nth_midxed_pack_int_id(m, cur_object))) {
/*
* Objects from preferred packs are added
@@ -364,7 +364,8 @@ static void compute_sorted_entries(struct write_midx_context *ctx,
preferred, cur_fanout);
}
- if (-1 < ctx->preferred_pack_idx && ctx->preferred_pack_idx < start_pack)
+ if (ctx->preferred_pack_idx != NO_PREFERRED_PACK &&
+ ctx->preferred_pack_idx < start_pack)
midx_fanout_add_pack_fanout(&fanout, ctx->info,
ctx->preferred_pack_idx, 1,
cur_fanout);
@@ -648,7 +649,6 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx)
}
static void write_midx_reverse_index(struct write_midx_context *ctx,
- const char *object_dir,
unsigned char *midx_hash)
{
struct strbuf buf = STRBUF_INIT;
@@ -657,11 +657,10 @@ static void write_midx_reverse_index(struct write_midx_context *ctx,
trace2_region_enter("midx", "write_midx_reverse_index", ctx->repo);
if (ctx->incremental)
- get_split_midx_filename_ext(ctx->repo->hash_algo, &buf,
- object_dir, midx_hash,
- MIDX_EXT_REV);
+ get_split_midx_filename_ext(ctx->source, &buf,
+ midx_hash, MIDX_EXT_REV);
else
- get_midx_filename_ext(ctx->repo->hash_algo, &buf, object_dir,
+ get_midx_filename_ext(ctx->source, &buf,
midx_hash, MIDX_EXT_REV);
tmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order,
@@ -836,14 +835,13 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr
}
static int write_midx_bitmap(struct write_midx_context *ctx,
- const char *object_dir,
const unsigned char *midx_hash,
struct packing_data *pdata,
struct commit **commits,
uint32_t commits_nr,
unsigned flags)
{
- int ret, i;
+ int ret;
uint16_t options = 0;
struct bitmap_writer writer;
struct pack_idx_entry **index;
@@ -852,12 +850,11 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
trace2_region_enter("midx", "write_midx_bitmap", ctx->repo);
if (ctx->incremental)
- get_split_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name,
- object_dir, midx_hash,
- MIDX_EXT_BITMAP);
+ get_split_midx_filename_ext(ctx->source, &bitmap_name,
+ midx_hash, MIDX_EXT_BITMAP);
else
- get_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name,
- object_dir, midx_hash, MIDX_EXT_BITMAP);
+ get_midx_filename_ext(ctx->source, &bitmap_name,
+ midx_hash, MIDX_EXT_BITMAP);
if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
options |= BITMAP_OPT_HASH_CACHE;
@@ -871,7 +868,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
* this order).
*/
ALLOC_ARRAY(index, pdata->nr_objects);
- for (i = 0; i < pdata->nr_objects; i++)
+ for (uint32_t i = 0; i < pdata->nr_objects; i++)
index[i] = &pdata->objects[i].idx;
bitmap_writer_init(&writer, ctx->repo, pdata,
@@ -892,7 +889,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx,
* happens between bitmap_writer_build_type_index() and
* bitmap_writer_finish().
*/
- for (i = 0; i < pdata->nr_objects; i++)
+ for (uint32_t i = 0; i < pdata->nr_objects; i++)
index[ctx->pack_order[i]] = &pdata->objects[i].idx;
bitmap_writer_select_commits(&writer, commits, commits_nr);
@@ -913,15 +910,7 @@ cleanup:
return ret;
}
-static struct multi_pack_index *lookup_multi_pack_index(struct repository *r,
- const char *object_dir)
-{
- struct odb_source *source = odb_find_source(r->objects, object_dir);
- return get_multi_pack_index(source);
-}
-
-static int fill_packs_from_midx(struct write_midx_context *ctx,
- const char *preferred_pack_name, uint32_t flags)
+static int fill_packs_from_midx(struct write_midx_context *ctx)
{
struct multi_pack_index *m;
@@ -929,30 +918,10 @@ static int fill_packs_from_midx(struct write_midx_context *ctx,
uint32_t i;
for (i = 0; i < m->num_packs; i++) {
- ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc);
-
- /*
- * If generating a reverse index, need to have
- * packed_git's loaded to compare their
- * mtimes and object count.
- *
- * If a preferred pack is specified, need to
- * have packed_git's loaded to ensure the chosen
- * preferred pack has a non-zero object count.
- */
- if (flags & MIDX_WRITE_REV_INDEX ||
- preferred_pack_name) {
- if (prepare_midx_pack(ctx->repo, m,
- m->num_packs_in_base + i)) {
- error(_("could not load pack"));
- return 1;
- }
-
- if (open_pack_index(m->packs[i]))
- die(_("could not open index for %s"),
- m->packs[i]->pack_name);
- }
+ if (prepare_midx_pack(m, m->num_packs_in_base + i))
+ return error(_("could not load pack"));
+ ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc);
fill_pack_info(&ctx->info[ctx->nr++], m->packs[i],
m->pack_names[i],
m->num_packs_in_base + i);
@@ -989,10 +958,9 @@ static int link_midx_to_chain(struct multi_pack_index *m)
for (i = 0; i < ARRAY_SIZE(midx_exts); i++) {
const unsigned char *hash = get_midx_checksum(m);
- get_midx_filename_ext(m->repo->hash_algo, &from, m->object_dir,
+ get_midx_filename_ext(m->source, &from,
hash, midx_exts[i].non_split);
- get_split_midx_filename_ext(m->repo->hash_algo, &to,
- m->object_dir, hash,
+ get_split_midx_filename_ext(m->source, &to, hash,
midx_exts[i].split);
if (link(from.buf, to.buf) < 0 && errno != ENOENT) {
@@ -1011,7 +979,7 @@ done:
return ret;
}
-static void clear_midx_files(struct repository *r, const char *object_dir,
+static void clear_midx_files(struct odb_source *source,
const char **hashes, uint32_t hashes_nr,
unsigned incremental)
{
@@ -1030,16 +998,16 @@ static void clear_midx_files(struct repository *r, const char *object_dir,
uint32_t i, j;
for (i = 0; i < ARRAY_SIZE(exts); i++) {
- clear_incremental_midx_files_ext(object_dir, exts[i],
+ clear_incremental_midx_files_ext(source, exts[i],
hashes, hashes_nr);
for (j = 0; j < hashes_nr; j++)
- clear_midx_files_ext(object_dir, exts[i], hashes[j]);
+ clear_midx_files_ext(source, exts[i], hashes[j]);
}
if (incremental)
- get_midx_filename(r->hash_algo, &buf, object_dir);
+ get_midx_filename(source, &buf);
else
- get_midx_chain_filename(&buf, object_dir);
+ get_midx_chain_filename(source, &buf);
if (unlink(buf.buf) && errno != ENOENT)
die_errno(_("failed to clear multi-pack-index at %s"), buf.buf);
@@ -1047,45 +1015,49 @@ static void clear_midx_files(struct repository *r, const char *object_dir,
strbuf_release(&buf);
}
-static int write_midx_internal(struct repository *r, const char *object_dir,
+static int write_midx_internal(struct odb_source *source,
struct string_list *packs_to_include,
struct string_list *packs_to_drop,
const char *preferred_pack_name,
const char *refs_snapshot,
unsigned flags)
{
+ struct repository *r = source->odb->repo;
struct strbuf midx_name = STRBUF_INIT;
unsigned char midx_hash[GIT_MAX_RAWSZ];
- uint32_t i, start_pack;
+ uint32_t start_pack;
struct hashfile *f = NULL;
struct lock_file lk;
struct tempfile *incr;
- struct write_midx_context ctx = { 0 };
+ struct write_midx_context ctx = {
+ .preferred_pack_idx = NO_PREFERRED_PACK,
+ };
int bitmapped_packs_concat_len = 0;
int pack_name_concat_len = 0;
int dropped_packs = 0;
- int result = 0;
+ int result = -1;
const char **keep_hashes = NULL;
struct chunkfile *cf;
trace2_region_enter("midx", "write_midx_internal", r);
ctx.repo = r;
+ ctx.source = source;
ctx.incremental = !!(flags & MIDX_WRITE_INCREMENTAL);
if (ctx.incremental)
strbuf_addf(&midx_name,
"%s/pack/multi-pack-index.d/tmp_midx_XXXXXX",
- object_dir);
+ source->path);
else
- get_midx_filename(r->hash_algo, &midx_name, object_dir);
+ get_midx_filename(source, &midx_name);
if (safe_create_leading_directories(r, midx_name.buf))
die_errno(_("unable to create leading directories of %s"),
midx_name.buf);
if (!packs_to_include || ctx.incremental) {
- struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+ struct multi_pack_index *m = get_multi_pack_index(source);
if (m && !midx_checksum_valid(m)) {
warning(_("ignoring existing multi-pack-index; checksum mismatch"));
m = NULL;
@@ -1116,15 +1088,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) {
error(_("could not load reverse index for MIDX %s"),
hash_to_hex_algop(get_midx_checksum(m),
- m->repo->hash_algo));
- result = 1;
+ m->source->odb->repo->hash_algo));
goto cleanup;
}
ctx.num_multi_pack_indexes_before++;
m = m->base_midx;
}
- } else if (ctx.m && fill_packs_from_midx(&ctx, preferred_pack_name,
- flags) < 0) {
+ } else if (ctx.m && fill_packs_from_midx(&ctx)) {
goto cleanup;
}
@@ -1139,7 +1109,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
ctx.to_include = packs_to_include;
- for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx);
+ for_each_file_in_pack_dir(source->path, add_pack_to_midx, &ctx);
stop_progress(&ctx.progress);
if ((ctx.m && ctx.nr == ctx.m->num_packs + ctx.m->num_packs_in_base) &&
@@ -1159,18 +1129,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
* corresponding bitmap (or one wasn't requested).
*/
if (!want_bitmap)
- clear_midx_files_ext(object_dir, "bitmap", NULL);
+ clear_midx_files_ext(source, "bitmap", NULL);
+ result = 0;
goto cleanup;
}
}
- if (ctx.incremental && !ctx.nr)
+ if (ctx.incremental && !ctx.nr) {
+ result = 0;
goto cleanup; /* nothing to do */
+ }
if (preferred_pack_name) {
- ctx.preferred_pack_idx = -1;
+ ctx.preferred_pack_idx = NO_PREFERRED_PACK;
- for (i = 0; i < ctx.nr; i++) {
+ for (size_t 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;
@@ -1178,14 +1151,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
}
}
- if (ctx.preferred_pack_idx == -1)
+ if (ctx.preferred_pack_idx == NO_PREFERRED_PACK)
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;
+ struct packed_git *oldest = ctx.info[0].p;
ctx.preferred_pack_idx = 0;
+ /*
+ * Attempt opening the pack index to populate num_objects.
+ * Ignore failiures as they can be expected and are not
+ * fatal during this selection time.
+ */
+ open_pack_index(oldest);
+
if (packs_to_drop && packs_to_drop->nr)
BUG("cannot write a MIDX bitmap during expiration");
@@ -1195,11 +1175,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
* 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++) {
+ for (size_t i = 1; i < ctx.nr; i++) {
struct packed_git *p = ctx.info[i].p;
if (!oldest->num_objects || p->mtime < oldest->mtime) {
oldest = p;
+ open_pack_index(oldest);
ctx.preferred_pack_idx = i;
}
}
@@ -1211,22 +1192,26 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
* objects to resolve, so the preferred value doesn't
* matter.
*/
- ctx.preferred_pack_idx = -1;
+ ctx.preferred_pack_idx = NO_PREFERRED_PACK;
}
} else {
/*
* otherwise don't mark any pack as preferred to avoid
* interfering with expiration logic below
*/
- ctx.preferred_pack_idx = -1;
+ ctx.preferred_pack_idx = NO_PREFERRED_PACK;
}
- if (ctx.preferred_pack_idx > -1) {
+ if (ctx.preferred_pack_idx != NO_PREFERRED_PACK) {
struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p;
+
+ if (open_pack_index(preferred))
+ die(_("failed to open preferred pack %s"),
+ ctx.info[ctx.preferred_pack_idx].pack_name);
+
if (!preferred->num_objects) {
error(_("cannot select preferred pack %s with no objects"),
preferred->pack_name);
- result = 1;
goto cleanup;
}
}
@@ -1234,7 +1219,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
compute_sorted_entries(&ctx, start_pack);
ctx.large_offsets_needed = 0;
- for (i = 0; i < ctx.entries_nr; i++) {
+ for (size_t i = 0; i < ctx.entries_nr; i++) {
if (ctx.entries[i].offset > 0x7fffffff)
ctx.num_large_offsets++;
if (ctx.entries[i].offset > 0xffffffff)
@@ -1244,10 +1229,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
QSORT(ctx.info, ctx.nr, pack_info_compare);
if (packs_to_drop && packs_to_drop->nr) {
- int drop_index = 0;
+ size_t drop_index = 0;
int missing_drops = 0;
- for (i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) {
+ for (size_t i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) {
int cmp = strcmp(ctx.info[i].pack_name,
packs_to_drop->items[drop_index].string);
@@ -1265,10 +1250,8 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
}
}
- if (missing_drops) {
- result = 1;
+ if (missing_drops)
goto cleanup;
- }
}
/*
@@ -1278,7 +1261,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
* pack_perm[old_id] = new_id
*/
ALLOC_ARRAY(ctx.pack_perm, ctx.nr);
- for (i = 0; i < ctx.nr; i++) {
+ for (size_t i = 0; i < ctx.nr; i++) {
if (ctx.info[i].expired) {
dropped_packs++;
ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED;
@@ -1287,7 +1270,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
}
}
- for (i = 0; i < ctx.nr; i++) {
+ for (size_t i = 0; i < ctx.nr; i++) {
if (ctx.info[i].expired)
continue;
pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1;
@@ -1314,7 +1297,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (ctx.nr - dropped_packs == 0) {
error(_("no pack files to index."));
- result = 1;
goto cleanup;
}
@@ -1327,20 +1309,20 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (ctx.incremental) {
struct strbuf lock_name = STRBUF_INIT;
- get_midx_chain_filename(&lock_name, object_dir);
+ get_midx_chain_filename(source, &lock_name);
hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR);
strbuf_release(&lock_name);
incr = mks_tempfile_m(midx_name.buf, 0444);
if (!incr) {
error(_("unable to create temporary MIDX layer"));
- return -1;
+ goto cleanup;
}
if (adjust_shared_perm(r, get_tempfile_path(incr))) {
error(_("unable to adjust shared permissions for '%s'"),
get_tempfile_path(incr));
- return -1;
+ goto cleanup;
}
f = hashfd(r->hash_algo, get_tempfile_fd(incr),
@@ -1390,7 +1372,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (flags & MIDX_WRITE_REV_INDEX &&
git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
- write_midx_reverse_index(&ctx, object_dir, midx_hash);
+ write_midx_reverse_index(&ctx, midx_hash);
if (flags & MIDX_WRITE_BITMAP) {
struct packing_data pdata;
@@ -1413,11 +1395,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
FREE_AND_NULL(ctx.entries);
ctx.entries_nr = 0;
- if (write_midx_bitmap(&ctx, object_dir,
+ if (write_midx_bitmap(&ctx,
midx_hash, &pdata, commits, commits_nr,
flags) < 0) {
error(_("could not write multi-pack bitmap"));
- result = 1;
clear_packing_data(&pdata);
free(commits);
goto cleanup;
@@ -1431,6 +1412,9 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
* have been freed in the previous if block.
*/
+ if (ctx.num_multi_pack_indexes_before == UINT32_MAX)
+ die(_("too many multi-pack-indexes"));
+
CALLOC_ARRAY(keep_hashes, ctx.num_multi_pack_indexes_before + 1);
if (ctx.incremental) {
@@ -1440,18 +1424,18 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (!chainf) {
error_errno(_("unable to open multi-pack-index chain file"));
- return -1;
+ goto cleanup;
}
if (link_midx_to_chain(ctx.base_midx) < 0)
- return -1;
+ goto cleanup;
- get_split_midx_filename_ext(r->hash_algo, &final_midx_name,
- object_dir, midx_hash, MIDX_EXT_MIDX);
+ get_split_midx_filename_ext(source, &final_midx_name,
+ midx_hash, MIDX_EXT_MIDX);
if (rename_tempfile(&incr, final_midx_name.buf) < 0) {
error_errno(_("unable to rename new multi-pack-index layer"));
- return -1;
+ goto cleanup;
}
strbuf_release(&final_midx_name);
@@ -1459,7 +1443,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
keep_hashes[ctx.num_multi_pack_indexes_before] =
xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo));
- for (i = 0; i < ctx.num_multi_pack_indexes_before; i++) {
+ for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) {
uint32_t j = ctx.num_multi_pack_indexes_before - i - 1;
keep_hashes[j] = xstrdup(hash_to_hex_algop(get_midx_checksum(m),
@@ -1467,7 +1451,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
m = m->base_midx;
}
- for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++)
+ for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++)
fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]);
} else {
keep_hashes[ctx.num_multi_pack_indexes_before] =
@@ -1480,12 +1464,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir,
if (commit_lock_file(&lk) < 0)
die_errno(_("could not write multi-pack-index"));
- clear_midx_files(r, object_dir, keep_hashes,
+ clear_midx_files(source, keep_hashes,
ctx.num_multi_pack_indexes_before + 1,
ctx.incremental);
+ result = 0;
cleanup:
- for (i = 0; i < ctx.nr; i++) {
+ for (size_t i = 0; i < ctx.nr; i++) {
if (ctx.info[i].p) {
close_pack(ctx.info[i].p);
free(ctx.info[i].p);
@@ -1498,7 +1483,7 @@ cleanup:
free(ctx.pack_perm);
free(ctx.pack_order);
if (keep_hashes) {
- for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++)
+ for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++)
free((char *)keep_hashes[i]);
free(keep_hashes);
}
@@ -1509,29 +1494,29 @@ cleanup:
return result;
}
-int write_midx_file(struct repository *r, const char *object_dir,
+int write_midx_file(struct odb_source *source,
const char *preferred_pack_name,
const char *refs_snapshot, unsigned flags)
{
- return write_midx_internal(r, object_dir, NULL, NULL,
+ return write_midx_internal(source, NULL, NULL,
preferred_pack_name, refs_snapshot,
flags);
}
-int write_midx_file_only(struct repository *r, const char *object_dir,
+int write_midx_file_only(struct odb_source *source,
struct string_list *packs_to_include,
const char *preferred_pack_name,
const char *refs_snapshot, unsigned flags)
{
- return write_midx_internal(r, object_dir, packs_to_include, NULL,
+ return write_midx_internal(source, packs_to_include, NULL,
preferred_pack_name, refs_snapshot, flags);
}
-int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags)
+int expire_midx_packs(struct odb_source *source, unsigned flags)
{
uint32_t i, *count, result = 0;
struct string_list packs_to_drop = STRING_LIST_INIT_DUP;
- struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+ struct multi_pack_index *m = get_multi_pack_index(source);
struct progress *progress = NULL;
if (!m)
@@ -1544,7 +1529,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
if (flags & MIDX_PROGRESS)
progress = start_delayed_progress(
- r,
+ source->odb->repo,
_("Counting referenced objects"),
m->num_objects);
for (i = 0; i < m->num_objects; i++) {
@@ -1556,7 +1541,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
if (flags & MIDX_PROGRESS)
progress = start_delayed_progress(
- r,
+ source->odb->repo,
_("Finding and deleting unreferenced packfiles"),
m->num_packs);
for (i = 0; i < m->num_packs; i++) {
@@ -1566,7 +1551,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
if (count[i])
continue;
- if (prepare_midx_pack(r, m, i))
+ if (prepare_midx_pack(m, i))
continue;
if (m->packs[i]->pack_keep || m->packs[i]->is_cruft)
@@ -1584,7 +1569,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla
free(count);
if (packs_to_drop.nr)
- result = write_midx_internal(r, object_dir, NULL,
+ result = write_midx_internal(source, NULL,
&packs_to_drop, NULL, NULL, flags);
string_list_clear(&packs_to_drop, 0);
@@ -1612,13 +1597,12 @@ static int compare_by_mtime(const void *a_, const void *b_)
return 0;
}
-static int want_included_pack(struct repository *r,
- struct multi_pack_index *m,
+static int want_included_pack(struct multi_pack_index *m,
int pack_kept_objects,
uint32_t pack_int_id)
{
struct packed_git *p;
- if (prepare_midx_pack(r, m, pack_int_id))
+ if (prepare_midx_pack(m, pack_int_id))
return 0;
p = m->packs[pack_int_id];
if (!pack_kept_objects && p->pack_keep)
@@ -1640,7 +1624,7 @@ static void fill_included_packs_all(struct repository *r,
repo_config_get_bool(r, "repack.packkeptobjects", &pack_kept_objects);
for (i = 0; i < m->num_packs; i++) {
- if (!want_included_pack(r, m, pack_kept_objects, i))
+ if (!want_included_pack(m, pack_kept_objects, i))
continue;
include_pack[i] = 1;
@@ -1664,7 +1648,7 @@ static void fill_included_packs_batch(struct repository *r,
for (i = 0; i < m->num_packs; i++) {
pack_info[i].pack_int_id = i;
- if (prepare_midx_pack(r, m, i))
+ if (prepare_midx_pack(m, i))
continue;
pack_info[i].mtime = m->packs[i]->mtime;
@@ -1683,7 +1667,7 @@ static void fill_included_packs_batch(struct repository *r,
struct packed_git *p = m->packs[pack_int_id];
uint64_t expected_size;
- if (!want_included_pack(r, m, pack_kept_objects, pack_int_id))
+ if (!want_included_pack(m, pack_kept_objects, pack_int_id))
continue;
/*
@@ -1710,14 +1694,15 @@ static void fill_included_packs_batch(struct repository *r,
free(pack_info);
}
-int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags)
+int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags)
{
+ struct repository *r = source->odb->repo;
int result = 0;
uint32_t i, packs_to_repack = 0;
unsigned char *include_pack;
struct child_process cmd = CHILD_PROCESS_INIT;
FILE *cmd_in;
- struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir);
+ struct multi_pack_index *m = get_multi_pack_index(source);
/*
* When updating the default for these configuration
@@ -1751,7 +1736,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
strvec_push(&cmd.args, "pack-objects");
- strvec_pushf(&cmd.args, "%s/pack/pack", object_dir);
+ strvec_pushf(&cmd.args, "%s/pack/pack", source->path);
if (delta_base_offset)
strvec_push(&cmd.args, "--delta-base-offset");
@@ -1792,7 +1777,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size,
goto cleanup;
}
- result = write_midx_internal(r, object_dir, NULL, NULL, NULL, NULL,
+ result = write_midx_internal(source, NULL, NULL, NULL, NULL,
flags);
cleanup:
diff --git a/midx.c b/midx.c
index 7d407682e6..7726c13d7e 100644
--- a/midx.c
+++ b/midx.c
@@ -16,9 +16,9 @@
#define MIDX_PACK_ERROR ((void *)(intptr_t)-1)
int midx_checksum_valid(struct multi_pack_index *m);
-void clear_midx_files_ext(const char *object_dir, const char *ext,
+void clear_midx_files_ext(struct odb_source *source, const char *ext,
const char *keep_hash);
-void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
+void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,
char **keep_hashes,
uint32_t hashes_nr);
int cmp_idx_or_pack_name(const char *idx_or_pack_name,
@@ -26,22 +26,20 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name,
const unsigned char *get_midx_checksum(struct multi_pack_index *m)
{
- return m->data + m->data_len - m->repo->hash_algo->rawsz;
+ return m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz;
}
-void get_midx_filename(const struct git_hash_algo *hash_algo,
- struct strbuf *out, const char *object_dir)
+void get_midx_filename(struct odb_source *source, struct strbuf *out)
{
- get_midx_filename_ext(hash_algo, out, object_dir, NULL, NULL);
+ get_midx_filename_ext(source, out, NULL, NULL);
}
-void get_midx_filename_ext(const struct git_hash_algo *hash_algo,
- struct strbuf *out, const char *object_dir,
+void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,
const unsigned char *hash, const char *ext)
{
- strbuf_addf(out, "%s/pack/multi-pack-index", object_dir);
+ strbuf_addf(out, "%s/pack/multi-pack-index", source->path);
if (ext)
- strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, hash_algo), ext);
+ strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);
}
static int midx_read_oid_fanout(const unsigned char *chunk_start,
@@ -95,11 +93,10 @@ static int midx_read_object_offsets(const unsigned char *chunk_start,
return 0;
}
-static struct multi_pack_index *load_multi_pack_index_one(struct repository *r,
- const char *object_dir,
- const char *midx_name,
- int local)
+static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source,
+ const char *midx_name)
{
+ struct repository *r = source->odb->repo;
struct multi_pack_index *m = NULL;
int fd;
struct stat st;
@@ -129,11 +126,10 @@ static struct multi_pack_index *load_multi_pack_index_one(struct repository *r,
midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
- FLEX_ALLOC_STR(m, object_dir, object_dir);
+ CALLOC_ARRAY(m, 1);
m->data = midx_map;
m->data_len = midx_size;
- m->local = local;
- m->repo = r;
+ m->source = source;
m->signature = get_be32(m->data);
if (m->signature != MIDX_SIGNATURE)
@@ -224,24 +220,23 @@ cleanup_fail:
return NULL;
}
-void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir)
+void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf)
{
- strbuf_addf(buf, "%s/pack/multi-pack-index.d", object_dir);
+ strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->path);
}
-void get_midx_chain_filename(struct strbuf *buf, const char *object_dir)
+void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf)
{
- get_midx_chain_dirname(buf, object_dir);
+ get_midx_chain_dirname(source, buf);
strbuf_addstr(buf, "/multi-pack-index-chain");
}
-void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo,
- struct strbuf *buf, const char *object_dir,
+void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,
const unsigned char *hash, const char *ext)
{
- get_midx_chain_dirname(buf, object_dir);
+ get_midx_chain_dirname(source, buf);
strbuf_addf(buf, "/multi-pack-index-%s.%s",
- hash_to_hex_algop(hash, hash_algo), ext);
+ hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext);
}
static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo,
@@ -297,19 +292,18 @@ static int add_midx_to_chain(struct multi_pack_index *midx,
return 1;
}
-static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
- const char *object_dir,
- int local,
+static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source,
int fd, struct stat *st,
int *incomplete_chain)
{
+ const struct git_hash_algo *hash_algo = source->odb->repo->hash_algo;
struct multi_pack_index *midx_chain = NULL;
struct strbuf buf = STRBUF_INIT;
int valid = 1;
uint32_t i, count;
FILE *fp = xfdopen(fd, "r");
- count = st->st_size / (r->hash_algo->hexsz + 1);
+ count = st->st_size / (hash_algo->hexsz + 1);
for (i = 0; i < count; i++) {
struct multi_pack_index *m;
@@ -318,7 +312,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
if (strbuf_getline_lf(&buf, fp) == EOF)
break;
- if (get_oid_hex_algop(buf.buf, &layer, r->hash_algo)) {
+ if (get_oid_hex_algop(buf.buf, &layer, hash_algo)) {
warning(_("invalid multi-pack-index chain: line '%s' "
"not a hash"),
buf.buf);
@@ -329,9 +323,9 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
valid = 0;
strbuf_reset(&buf);
- get_split_midx_filename_ext(r->hash_algo, &buf, object_dir,
+ get_split_midx_filename_ext(source, &buf,
layer.hash, MIDX_EXT_MIDX);
- m = load_multi_pack_index_one(r, object_dir, buf.buf, local);
+ m = load_multi_pack_index_one(source, buf.buf);
if (m) {
if (add_midx_to_chain(m, midx_chain)) {
@@ -354,40 +348,34 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r,
return midx_chain;
}
-static struct multi_pack_index *load_multi_pack_index_chain(struct repository *r,
- const char *object_dir,
- int local)
+static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source)
{
struct strbuf chain_file = STRBUF_INIT;
struct stat st;
int fd;
struct multi_pack_index *m = NULL;
- get_midx_chain_filename(&chain_file, object_dir);
- if (open_multi_pack_index_chain(r->hash_algo, chain_file.buf, &fd, &st)) {
+ get_midx_chain_filename(source, &chain_file);
+ if (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) {
int incomplete;
/* ownership of fd is taken over by load function */
- m = load_midx_chain_fd_st(r, object_dir, local, fd, &st,
- &incomplete);
+ m = load_midx_chain_fd_st(source, fd, &st, &incomplete);
}
strbuf_release(&chain_file);
return m;
}
-struct multi_pack_index *load_multi_pack_index(struct repository *r,
- const char *object_dir,
- int local)
+struct multi_pack_index *load_multi_pack_index(struct odb_source *source)
{
struct strbuf midx_name = STRBUF_INIT;
struct multi_pack_index *m;
- get_midx_filename(r->hash_algo, &midx_name, object_dir);
+ get_midx_filename(source, &midx_name);
- m = load_multi_pack_index_one(r, object_dir,
- midx_name.buf, local);
+ m = load_multi_pack_index_one(source, midx_name.buf);
if (!m)
- m = load_multi_pack_index_chain(r, object_dir, local);
+ m = load_multi_pack_index_chain(source);
strbuf_release(&midx_name);
@@ -450,9 +438,10 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m,
return pack_int_id - m->num_packs_in_base;
}
-int prepare_midx_pack(struct repository *r, struct multi_pack_index *m,
+int prepare_midx_pack(struct multi_pack_index *m,
uint32_t pack_int_id)
{
+ struct repository *r = m->source->odb->repo;
struct strbuf pack_name = STRBUF_INIT;
struct strbuf key = STRBUF_INIT;
struct packed_git *p;
@@ -464,7 +453,7 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m,
if (m->packs[pack_int_id])
return 0;
- strbuf_addf(&pack_name, "%s/pack/%s", m->object_dir,
+ strbuf_addf(&pack_name, "%s/pack/%s", m->source->path,
m->pack_names[pack_int_id]);
/* pack_map holds the ".pack" name, but we have the .idx */
@@ -475,7 +464,8 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m,
strhash(key.buf), key.buf,
struct packed_git, packmap_ent);
if (!p) {
- p = add_packed_git(r, pack_name.buf, pack_name.len, m->local);
+ p = add_packed_git(r, pack_name.buf, pack_name.len,
+ m->source->local);
if (p) {
install_packed_git(r, p);
list_add_tail(&p->mru, &r->objects->packed_git_mru);
@@ -507,7 +497,7 @@ struct packed_git *nth_midxed_pack(struct multi_pack_index *m,
#define MIDX_CHUNK_BITMAPPED_PACKS_WIDTH (2 * sizeof(uint32_t))
-int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+int nth_bitmapped_pack(struct multi_pack_index *m,
struct bitmapped_pack *bp, uint32_t pack_int_id)
{
uint32_t local_pack_int_id = midx_for_pack(&m, pack_int_id);
@@ -515,7 +505,7 @@ int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
if (!m->chunk_bitmapped_packs)
return error(_("MIDX does not contain the BTMP chunk"));
- if (prepare_midx_pack(r, m, pack_int_id))
+ if (prepare_midx_pack(m, pack_int_id))
return error(_("could not load bitmapped pack %"PRIu32), pack_int_id);
bp->p = m->packs[local_pack_int_id];
@@ -534,7 +524,8 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,
uint32_t *result)
{
int ret = bsearch_hash(oid->hash, m->chunk_oid_fanout,
- m->chunk_oid_lookup, m->repo->hash_algo->rawsz,
+ m->chunk_oid_lookup,
+ m->source->odb->repo->hash_algo->rawsz,
result);
if (result)
*result += m->num_objects_in_base;
@@ -565,7 +556,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
n = midx_for_object(&m, n);
oidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n),
- m->repo->hash_algo);
+ m->source->odb->repo->hash_algo);
return oid;
}
@@ -600,10 +591,9 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
(off_t)pos * MIDX_CHUNK_OFFSET_WIDTH);
}
-int fill_midx_entry(struct repository *r,
+int fill_midx_entry(struct multi_pack_index *m,
const struct object_id *oid,
- struct pack_entry *e,
- struct multi_pack_index *m)
+ struct pack_entry *e)
{
uint32_t pos;
uint32_t pack_int_id;
@@ -615,7 +605,7 @@ int fill_midx_entry(struct repository *r,
midx_for_object(&m, pos);
pack_int_id = nth_midxed_pack_int_id(m, pos);
- if (prepare_midx_pack(r, m, pack_int_id))
+ if (prepare_midx_pack(m, pack_int_id))
return 0;
p = m->packs[pack_int_id - m->num_packs_in_base];
@@ -723,7 +713,7 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)
return 0;
}
-int prepare_multi_pack_index_one(struct odb_source *source, int local)
+int prepare_multi_pack_index_one(struct odb_source *source)
{
struct repository *r = source->odb->repo;
@@ -734,14 +724,14 @@ int prepare_multi_pack_index_one(struct odb_source *source, int local)
if (source->midx)
return 1;
- source->midx = load_multi_pack_index(r, source->path, local);
+ source->midx = load_multi_pack_index(source);
return !!source->midx;
}
int midx_checksum_valid(struct multi_pack_index *m)
{
- return hashfile_checksum_valid(m->repo->hash_algo,
+ return hashfile_checksum_valid(m->source->odb->repo->hash_algo,
m->data, m->data_len);
}
@@ -768,7 +758,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS
die_errno(_("failed to remove %s"), full_path);
}
-void clear_midx_files_ext(const char *object_dir, const char *ext,
+void clear_midx_files_ext(struct odb_source *source, const char *ext,
const char *keep_hash)
{
struct clear_midx_data data;
@@ -782,7 +772,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext,
}
data.ext = ext;
- for_each_file_in_pack_dir(object_dir,
+ for_each_file_in_pack_dir(source->path,
clear_midx_file_ext,
&data);
@@ -791,7 +781,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext,
free(data.keep);
}
-void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
+void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext,
char **keep_hashes,
uint32_t hashes_nr)
{
@@ -807,7 +797,7 @@ void clear_incremental_midx_files_ext(const char *object_dir, const char *ext,
data.keep_nr = hashes_nr;
data.ext = ext;
- for_each_file_in_pack_subdir(object_dir, "multi-pack-index.d",
+ for_each_file_in_pack_subdir(source->path, "multi-pack-index.d",
clear_midx_file_ext, &data);
for (i = 0; i < hashes_nr; i++)
@@ -819,7 +809,7 @@ void clear_midx_file(struct repository *r)
{
struct strbuf midx = STRBUF_INIT;
- get_midx_filename(r->hash_algo, &midx, r->objects->sources->path);
+ get_midx_filename(r->objects->sources, &midx);
if (r->objects) {
struct odb_source *source;
@@ -834,8 +824,8 @@ void clear_midx_file(struct repository *r)
if (remove_path(midx.buf))
die(_("failed to clear multi-pack-index at %s"), midx.buf);
- clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_BITMAP, NULL);
- clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_REV, NULL);
+ clear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL);
+ clear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL);
strbuf_release(&midx);
}
@@ -879,12 +869,13 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b)
display_progress(progress, _n); \
} while (0)
-int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags)
+int verify_midx_file(struct odb_source *source, unsigned flags)
{
+ struct repository *r = source->odb->repo;
struct pair_pos_vs_id *pairs = NULL;
uint32_t i;
struct progress *progress = NULL;
- struct multi_pack_index *m = load_multi_pack_index(r, object_dir, 1);
+ struct multi_pack_index *m = load_multi_pack_index(source);
struct multi_pack_index *curr;
verify_midx_error = 0;
@@ -893,7 +884,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
struct stat sb;
struct strbuf filename = STRBUF_INIT;
- get_midx_filename(r->hash_algo, &filename, object_dir);
+ get_midx_filename(source, &filename);
if (!stat(filename.buf, &sb)) {
error(_("multi-pack-index file exists, but failed to parse"));
@@ -911,7 +902,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
_("Looking for referenced packfiles"),
m->num_packs + m->num_packs_in_base);
for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) {
- if (prepare_midx_pack(r, m, i))
+ if (prepare_midx_pack(m, i))
midx_report("failed to load pack in position %d", i);
display_progress(progress, i + 1);
@@ -988,7 +979,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag
nth_midxed_object_oid(&oid, m, pairs[i].pos);
- if (!fill_midx_entry(r, &oid, &e, m)) {
+ if (!fill_midx_entry(m, &oid, &e)) {
midx_report(_("failed to load pack entry for oid[%d] = %s"),
pairs[i].pos, oid_to_hex(&oid));
continue;
diff --git a/midx.h b/midx.h
index 076382de8a..e241d2d690 100644
--- a/midx.h
+++ b/midx.h
@@ -35,6 +35,8 @@ struct odb_source;
"GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL"
struct multi_pack_index {
+ struct odb_source *source;
+
const unsigned char *data;
size_t data_len;
@@ -50,7 +52,6 @@ struct multi_pack_index {
uint32_t num_objects;
int preferred_pack_idx;
- int local;
int has_chain;
const unsigned char *chunk_pack_names;
@@ -71,10 +72,6 @@ struct multi_pack_index {
const char **pack_names;
struct packed_git **packs;
-
- struct repository *repo;
-
- char object_dir[FLEX_ARRAY];
};
#define MIDX_PROGRESS (1 << 0)
@@ -89,24 +86,19 @@ struct multi_pack_index {
#define MIDX_EXT_MIDX "midx"
const unsigned char *get_midx_checksum(struct multi_pack_index *m);
-void get_midx_filename(const struct git_hash_algo *hash_algo,
- struct strbuf *out, const char *object_dir);
-void get_midx_filename_ext(const struct git_hash_algo *hash_algo,
- struct strbuf *out, const char *object_dir,
+void get_midx_filename(struct odb_source *source, struct strbuf *out);
+void get_midx_filename_ext(struct odb_source *source, struct strbuf *out,
const unsigned char *hash, const char *ext);
-void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir);
-void get_midx_chain_filename(struct strbuf *buf, const char *object_dir);
-void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo,
- struct strbuf *buf, const char *object_dir,
+void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out);
+void get_midx_chain_filename(struct odb_source *source, struct strbuf *out);
+void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf,
const unsigned char *hash, const char *ext);
-struct multi_pack_index *load_multi_pack_index(struct repository *r,
- const char *object_dir,
- int local);
-int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);
+struct multi_pack_index *load_multi_pack_index(struct odb_source *source);
+int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id);
struct packed_git *nth_midxed_pack(struct multi_pack_index *m,
uint32_t pack_int_id);
-int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+int nth_bitmapped_pack(struct multi_pack_index *m,
struct bitmapped_pack *bp, uint32_t pack_int_id);
int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m,
uint32_t *result);
@@ -118,27 +110,27 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos);
struct object_id *nth_midxed_object_oid(struct object_id *oid,
struct multi_pack_index *m,
uint32_t n);
-int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
+int fill_midx_entry(struct multi_pack_index *m, const struct object_id *oid, struct pack_entry *e);
int midx_contains_pack(struct multi_pack_index *m,
const char *idx_or_pack_name);
int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);
-int prepare_multi_pack_index_one(struct odb_source *source, int local);
+int prepare_multi_pack_index_one(struct odb_source *source);
/*
* Variant of write_midx_file which writes a MIDX containing only the packs
* specified in packs_to_include.
*/
-int write_midx_file(struct repository *r, const char *object_dir,
+int write_midx_file(struct odb_source *source,
const char *preferred_pack_name, const char *refs_snapshot,
unsigned flags);
-int write_midx_file_only(struct repository *r, const char *object_dir,
+int write_midx_file_only(struct odb_source *source,
struct string_list *packs_to_include,
const char *preferred_pack_name,
const char *refs_snapshot, unsigned flags);
void clear_midx_file(struct repository *r);
-int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags);
-int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags);
-int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags);
+int verify_midx_file(struct odb_source *source, unsigned flags);
+int expire_midx_packs(struct odb_source *source, unsigned flags);
+int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags);
void close_midx(struct multi_pack_index *m);
diff --git a/object-file.c b/object-file.c
index 2bc36ab3ee..bc15af4245 100644
--- a/object-file.c
+++ b/object-file.c
@@ -674,7 +674,7 @@ static void close_loose_object(struct odb_source *source,
goto out;
if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- fsync_loose_object_bulk_checkin(fd, filename);
+ fsync_loose_object_bulk_checkin(source->odb->transaction, fd, filename);
else if (fsync_object_files > 0)
fsync_or_die(fd, filename);
else
@@ -852,7 +852,7 @@ static int write_loose_object(struct odb_source *source,
static struct strbuf filename = STRBUF_INIT;
if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- prepare_loose_object_bulk_checkin();
+ prepare_loose_object_bulk_checkin(source->odb->transaction);
odb_loose_path(source, &filename, oid);
@@ -941,7 +941,7 @@ int stream_loose_object(struct odb_source *source,
int hdrlen;
if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))
- prepare_loose_object_bulk_checkin();
+ prepare_loose_object_bulk_checkin(source->odb->transaction);
/* Since oid is not determined, save tmp file to odb path. */
strbuf_addf(&filename, "%s/", source->path);
@@ -1253,18 +1253,26 @@ int index_fd(struct index_state *istate, struct object_id *oid,
* Call xsize_t() only when needed to avoid potentially unnecessary
* die() for large files.
*/
- if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path))
+ if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path)) {
ret = index_stream_convert_blob(istate, oid, fd, path, flags);
- else if (!S_ISREG(st->st_mode))
+ } else if (!S_ISREG(st->st_mode)) {
ret = index_pipe(istate, oid, fd, type, path, flags);
- else if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
- type != OBJ_BLOB ||
- (path && would_convert_to_git(istate, path)))
+ } else if ((st->st_size >= 0 &&
+ (size_t)st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||
+ type != OBJ_BLOB ||
+ (path && would_convert_to_git(istate, path))) {
ret = index_core(istate, oid, fd, xsize_t(st->st_size),
type, path, flags);
- else
- ret = index_blob_bulk_checkin(oid, fd, xsize_t(st->st_size), path,
- flags);
+ } else {
+ struct odb_transaction *transaction;
+
+ transaction = begin_odb_transaction(the_repository->objects);
+ ret = index_blob_bulk_checkin(transaction,
+ oid, fd, xsize_t(st->st_size),
+ path, flags);
+ end_odb_transaction(transaction);
+ }
+
close(fd);
return ret;
}
diff --git a/object-name.c b/object-name.c
index 732056ff5e..4407f67ff8 100644
--- a/object-name.c
+++ b/object-name.c
@@ -696,15 +696,14 @@ static inline char get_hex_char_from_oid(const struct object_id *oid,
return hex[oid->hash[pos >> 1] & 0xf];
}
-static int extend_abbrev_len(const struct object_id *oid, void *cb_data)
+static int extend_abbrev_len(const struct object_id *oid,
+ struct min_abbrev_data *mad)
{
- struct min_abbrev_data *mad = cb_data;
-
unsigned int i = mad->init_len;
while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i))
i++;
- if (i < GIT_MAX_RAWSZ && i >= mad->cur_len)
+ if (mad->hex[i] && i >= mad->cur_len)
mad->cur_len = i + 1;
return 0;
diff --git a/object.c b/object.c
index c1553ee433..986114a6db 100644
--- a/object.c
+++ b/object.c
@@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo)
memset(o, 0, sizeof(*o));
o->repo = repo;
- o->blob_state = allocate_alloc_state();
- o->tree_state = allocate_alloc_state();
- o->commit_state = allocate_alloc_state();
- o->tag_state = allocate_alloc_state();
- o->object_state = allocate_alloc_state();
-
+ o->blob_state = alloc_state_alloc();
+ o->tree_state = alloc_state_alloc();
+ o->commit_state = alloc_state_alloc();
+ o->tag_state = alloc_state_alloc();
+ o->object_state = alloc_state_alloc();
o->is_shallow = -1;
CALLOC_ARRAY(o->shallow_stat, 1);
@@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o)
o->buffer_slab = NULL;
parsed_object_pool_reset_commit_grafts(o);
- clear_alloc_state(o->blob_state);
- clear_alloc_state(o->tree_state);
- clear_alloc_state(o->commit_state);
- clear_alloc_state(o->tag_state);
- clear_alloc_state(o->object_state);
+ alloc_state_free_and_null(&o->blob_state);
+ alloc_state_free_and_null(&o->tree_state);
+ alloc_state_free_and_null(&o->commit_state);
+ alloc_state_free_and_null(&o->tag_state);
+ alloc_state_free_and_null(&o->object_state);
stat_validity_clear(o->shallow_stat);
- FREE_AND_NULL(o->blob_state);
- FREE_AND_NULL(o->tree_state);
- FREE_AND_NULL(o->commit_state);
- FREE_AND_NULL(o->tag_state);
- FREE_AND_NULL(o->object_state);
FREE_AND_NULL(o->shallow_stat);
}
diff --git a/odb.c b/odb.c
index 2a92a018c4..75c443fe66 100644
--- a/odb.c
+++ b/odb.c
@@ -139,23 +139,21 @@ static void read_info_alternates(struct object_database *odb,
const char *relative_base,
int depth);
-static int link_alt_odb_entry(struct object_database *odb,
- const struct strbuf *entry,
- const char *relative_base,
- int depth,
- const char *normalized_objdir)
+static struct odb_source *link_alt_odb_entry(struct object_database *odb,
+ const char *dir,
+ const char *relative_base,
+ int depth)
{
- struct odb_source *alternate;
+ struct odb_source *alternate = NULL;
struct strbuf pathbuf = STRBUF_INIT;
struct strbuf tmp = STRBUF_INIT;
khiter_t pos;
- int ret = -1;
- if (!is_absolute_path(entry->buf) && relative_base) {
+ if (!is_absolute_path(dir) && relative_base) {
strbuf_realpath(&pathbuf, relative_base, 1);
strbuf_addch(&pathbuf, '/');
}
- strbuf_addbuf(&pathbuf, entry);
+ strbuf_addstr(&pathbuf, dir);
if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) {
error(_("unable to normalize alternate object path: %s"),
@@ -171,11 +169,15 @@ static int link_alt_odb_entry(struct object_database *odb,
while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/')
strbuf_setlen(&pathbuf, pathbuf.len - 1);
- if (!alt_odb_usable(odb, &pathbuf, normalized_objdir, &pos))
+ strbuf_reset(&tmp);
+ strbuf_realpath(&tmp, odb->sources->path, 1);
+
+ if (!alt_odb_usable(odb, &pathbuf, tmp.buf, &pos))
goto error;
CALLOC_ARRAY(alternate, 1);
alternate->odb = odb;
+ alternate->local = false;
/* pathbuf.buf is already in r->objects->source_by_path */
alternate->path = strbuf_detach(&pathbuf, NULL);
@@ -188,11 +190,11 @@ static int link_alt_odb_entry(struct object_database *odb,
/* recursively add alternates */
read_info_alternates(odb, alternate->path, depth + 1);
- ret = 0;
+
error:
strbuf_release(&tmp);
strbuf_release(&pathbuf);
- return ret;
+ return alternate;
}
static const char *parse_alt_odb_entry(const char *string,
@@ -227,8 +229,7 @@ static const char *parse_alt_odb_entry(const char *string,
static void link_alt_odb_entries(struct object_database *odb, const char *alt,
int sep, const char *relative_base, int depth)
{
- struct strbuf objdirbuf = STRBUF_INIT;
- struct strbuf entry = STRBUF_INIT;
+ struct strbuf dir = STRBUF_INIT;
if (!alt || !*alt)
return;
@@ -239,17 +240,13 @@ static void link_alt_odb_entries(struct object_database *odb, const char *alt,
return;
}
- strbuf_realpath(&objdirbuf, odb->sources->path, 1);
-
while (*alt) {
- alt = parse_alt_odb_entry(alt, sep, &entry);
- if (!entry.len)
+ alt = parse_alt_odb_entry(alt, sep, &dir);
+ if (!dir.len)
continue;
- link_alt_odb_entry(odb, &entry,
- relative_base, depth, objdirbuf.buf);
+ link_alt_odb_entry(odb, dir.buf, relative_base, depth);
}
- strbuf_release(&entry);
- strbuf_release(&objdirbuf);
+ strbuf_release(&dir);
}
static void read_info_alternates(struct object_database *odb,
@@ -272,7 +269,7 @@ static void read_info_alternates(struct object_database *odb,
}
void odb_add_to_alternates_file(struct object_database *odb,
- const char *reference)
+ const char *dir)
{
struct lock_file lock = LOCK_INIT;
char *alts = repo_git_path(odb->repo, "objects/info/alternates");
@@ -289,7 +286,7 @@ void odb_add_to_alternates_file(struct object_database *odb,
struct strbuf line = STRBUF_INIT;
while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(reference, line.buf)) {
+ if (!strcmp(dir, line.buf)) {
found = 1;
break;
}
@@ -305,27 +302,24 @@ void odb_add_to_alternates_file(struct object_database *odb,
if (found) {
rollback_lock_file(&lock);
} else {
- fprintf_or_die(out, "%s\n", reference);
+ fprintf_or_die(out, "%s\n", dir);
if (commit_lock_file(&lock))
die_errno(_("unable to move new alternates file into place"));
if (odb->loaded_alternates)
- link_alt_odb_entries(odb, reference,
- '\n', NULL, 0);
+ link_alt_odb_entries(odb, dir, '\n', NULL, 0);
}
free(alts);
}
-void odb_add_to_alternates_memory(struct object_database *odb,
- const char *reference)
+struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
+ const char *dir)
{
/*
* Make sure alternates are initialized, or else our entry may be
* overwritten when they are.
*/
odb_prepare_alternates(odb);
-
- link_alt_odb_entries(odb, reference,
- '\n', NULL, 0);
+ return link_alt_odb_entry(odb, dir, NULL, 0);
}
struct odb_source *odb_set_temporary_primary_source(struct object_database *odb,
@@ -463,6 +457,12 @@ struct odb_source *odb_find_source(struct object_database *odb, const char *obj_
free(obj_dir_real);
strbuf_release(&odb_path_real);
+ return source;
+}
+
+struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir)
+{
+ struct odb_source *source = odb_find_source(odb, obj_dir);
if (!source)
die(_("could not find object directory matching %s"), obj_dir);
return source;
diff --git a/odb.h b/odb.h
index 3dfc66d75a..bd7374f92f 100644
--- a/odb.h
+++ b/odb.h
@@ -64,6 +64,14 @@ struct odb_source {
struct multi_pack_index *midx;
/*
+ * Figure out whether this is the local source of the owning
+ * repository, which would typically be its ".git/objects" directory.
+ * This local object directory is usually where objects would be
+ * written to.
+ */
+ bool local;
+
+ /*
* This is a temporary object store created by the tmp_objdir
* facility. Disable ref updates since the objects in the store
* might be discarded on rollback.
@@ -84,6 +92,7 @@ struct odb_source {
struct packed_git;
struct cached_object_entry;
+struct odb_transaction;
/*
* The object database encapsulates access to objects in a repository. It
@@ -95,6 +104,13 @@ struct object_database {
struct repository *repo;
/*
+ * State of current current object database transaction. Only one
+ * transaction may be pending at a time. Is NULL when no transaction is
+ * configured.
+ */
+ struct odb_transaction *transaction;
+
+ /*
* Set of all object directories; the main directory is first (and
* cannot be NULL after initialization). Subsequent directories are
* alternates.
@@ -178,11 +194,14 @@ struct object_database *odb_new(struct repository *repo);
void odb_clear(struct object_database *o);
/*
- * Find source by its object directory path. Dies in case the source couldn't
- * be found.
+ * Find source by its object directory path. Returns a `NULL` pointer in case
+ * the source could not be found.
*/
struct odb_source *odb_find_source(struct object_database *odb, const char *obj_dir);
+/* Same as `odb_find_source()`, but dies in case the source doesn't exist. */
+struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir);
+
/*
* Replace the current writable object directory with the specified temporary
* object directory; returns the former primary source.
@@ -257,8 +276,8 @@ void odb_add_to_alternates_file(struct object_database *odb,
* recursive alternates it points to), but do not modify the on-disk alternates
* file.
*/
-void odb_add_to_alternates_memory(struct object_database *odb,
- const char *dir);
+struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
+ const char *dir);
/*
* Read an object from the database. Returns the object data and assigns object
diff --git a/pack-bitmap.c b/pack-bitmap.c
index d14421ee20..058bdb5d7d 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -216,7 +216,7 @@ static uint32_t bitmap_num_objects(struct bitmap_index *index)
static struct repository *bitmap_repo(struct bitmap_index *bitmap_git)
{
if (bitmap_is_midx(bitmap_git))
- return bitmap_git->midx->repo;
+ return bitmap_git->midx->source->odb->repo;
return bitmap_git->pack->repo;
}
@@ -418,13 +418,12 @@ char *midx_bitmap_filename(struct multi_pack_index *midx)
{
struct strbuf buf = STRBUF_INIT;
if (midx->has_chain)
- get_split_midx_filename_ext(midx->repo->hash_algo, &buf,
- midx->object_dir,
+ get_split_midx_filename_ext(midx->source, &buf,
get_midx_checksum(midx),
MIDX_EXT_BITMAP);
else
- get_midx_filename_ext(midx->repo->hash_algo, &buf,
- midx->object_dir, get_midx_checksum(midx),
+ get_midx_filename_ext(midx->source, &buf,
+ get_midx_checksum(midx),
MIDX_EXT_BITMAP);
return strbuf_detach(&buf, NULL);
@@ -463,7 +462,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
if (bitmap_git->pack || bitmap_git->midx) {
struct strbuf buf = STRBUF_INIT;
- get_midx_filename(midx->repo->hash_algo, &buf, midx->object_dir);
+ get_midx_filename(midx->source, &buf);
trace2_data_string("bitmap", bitmap_repo(bitmap_git),
"ignoring extra midx bitmap file", buf.buf);
close(fd);
@@ -493,7 +492,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
}
for (i = 0; i < bitmap_git->midx->num_packs + bitmap_git->midx->num_packs_in_base; i++) {
- if (prepare_midx_pack(bitmap_repo(bitmap_git), bitmap_git->midx, i)) {
+ if (prepare_midx_pack(bitmap_git->midx, i)) {
warning(_("could not open pack %s"),
bitmap_git->midx->pack_names[i]);
goto cleanup;
@@ -2466,7 +2465,7 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
struct multi_pack_index *m = bitmap_git->midx;
for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) {
struct bitmapped_pack pack;
- if (nth_bitmapped_pack(r, bitmap_git->midx, &pack, i) < 0) {
+ if (nth_bitmapped_pack(bitmap_git->midx, &pack, i) < 0) {
warning(_("unable to load pack: '%s', disabling pack-reuse"),
bitmap_git->midx->pack_names[i]);
free(packs);
diff --git a/pack-revindex.c b/pack-revindex.c
index 0cc422a1e6..d0791cc493 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -379,25 +379,25 @@ int load_midx_revindex(struct multi_pack_index *m)
* not want to accidentally call munmap() in the middle of the
* MIDX.
*/
- trace2_data_string("load_midx_revindex", m->repo,
+ trace2_data_string("load_midx_revindex", m->source->odb->repo,
"source", "midx");
m->revindex_data = (const uint32_t *)m->chunk_revindex;
return 0;
}
- trace2_data_string("load_midx_revindex", m->repo,
+ trace2_data_string("load_midx_revindex", m->source->odb->repo,
"source", "rev");
if (m->has_chain)
- get_split_midx_filename_ext(m->repo->hash_algo, &revindex_name,
- m->object_dir, get_midx_checksum(m),
+ get_split_midx_filename_ext(m->source, &revindex_name,
+ get_midx_checksum(m),
MIDX_EXT_REV);
else
- get_midx_filename_ext(m->repo->hash_algo, &revindex_name,
- m->object_dir, get_midx_checksum(m),
+ get_midx_filename_ext(m->source, &revindex_name,
+ get_midx_checksum(m),
MIDX_EXT_REV);
- ret = load_revindex_from_disk(m->repo->hash_algo,
+ ret = load_revindex_from_disk(m->source->odb->repo->hash_algo,
revindex_name.buf,
m->num_objects,
&m->revindex_map,
diff --git a/packfile.c b/packfile.c
index 5d73932f50..acb680966d 100644
--- a/packfile.c
+++ b/packfile.c
@@ -935,14 +935,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len,
report_garbage(PACKDIR_FILE_GARBAGE, full_name);
}
-static void prepare_packed_git_one(struct odb_source *source, int local)
+static void prepare_packed_git_one(struct odb_source *source)
{
struct string_list garbage = STRING_LIST_INIT_DUP;
struct prepare_pack_data data = {
.m = source->midx,
.r = source->odb->repo,
.garbage = &garbage,
- .local = local,
+ .local = source->local,
};
for_each_file_in_pack_dir(source->path, prepare_pack, &data);
@@ -1037,9 +1037,8 @@ static void prepare_packed_git(struct repository *r)
odb_prepare_alternates(r->objects);
for (source = r->objects->sources; source; source = source->next) {
- int local = (source == r->objects->sources);
- prepare_multi_pack_index_one(source, local);
- prepare_packed_git_one(source, local);
+ prepare_multi_pack_index_one(source);
+ prepare_packed_git_one(source);
}
rearrange_packed_git(r);
@@ -1092,7 +1091,7 @@ struct packed_git *get_all_packs(struct repository *r)
if (!m)
continue;
for (uint32_t i = 0; i < m->num_packs + m->num_packs_in_base; i++)
- prepare_midx_pack(r, m, i);
+ prepare_midx_pack(m, i);
}
return r->objects->packed_git;
@@ -2078,7 +2077,7 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa
prepare_packed_git(r);
for (struct odb_source *source = r->objects->sources; source; source = source->next)
- if (source->midx && fill_midx_entry(r, oid, e, source->midx))
+ if (source->midx && fill_midx_entry(source->midx, oid, e))
return 1;
if (!r->objects->packed_git)
diff --git a/path-walk.c b/path-walk.c
index 2d4ddbadd5..f1ceed99e9 100644
--- a/path-walk.c
+++ b/path-walk.c
@@ -105,6 +105,24 @@ static void push_to_stack(struct path_walk_context *ctx,
prio_queue_put(&ctx->path_stack, xstrdup(path));
}
+static void add_path_to_list(struct path_walk_context *ctx,
+ const char *path,
+ enum object_type type,
+ struct object_id *oid,
+ int interesting)
+{
+ struct type_and_oid_list *list = strmap_get(&ctx->paths_to_lists, path);
+
+ if (!list) {
+ CALLOC_ARRAY(list, 1);
+ list->type = type;
+ strmap_put(&ctx->paths_to_lists, path, list);
+ }
+
+ list->maybe_interesting |= interesting;
+ oid_array_append(&list->oids, oid);
+}
+
static int add_tree_entries(struct path_walk_context *ctx,
const char *base_path,
struct object_id *oid)
@@ -129,7 +147,6 @@ static int add_tree_entries(struct path_walk_context *ctx,
init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- struct type_and_oid_list *list;
struct object *o;
/* Not actually true, but we will ignore submodules later. */
enum object_type type = S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB;
@@ -190,17 +207,10 @@ static int add_tree_entries(struct path_walk_context *ctx,
continue;
}
- if (!(list = strmap_get(&ctx->paths_to_lists, path.buf))) {
- CALLOC_ARRAY(list, 1);
- list->type = type;
- strmap_put(&ctx->paths_to_lists, path.buf, list);
- }
- push_to_stack(ctx, path.buf);
-
- if (!(o->flags & UNINTERESTING))
- list->maybe_interesting = 1;
+ add_path_to_list(ctx, path.buf, type, &entry.oid,
+ !(o->flags & UNINTERESTING));
- oid_array_append(&list->oids, &entry.oid);
+ push_to_stack(ctx, path.buf);
}
free_tree_buffer(tree);
@@ -377,15 +387,9 @@ static int setup_pending_objects(struct path_walk_info *info,
if (!info->trees)
continue;
if (pending->path) {
- struct type_and_oid_list *list;
char *path = *pending->path ? xstrfmt("%s/", pending->path)
: xstrdup("");
- if (!(list = strmap_get(&ctx->paths_to_lists, path))) {
- CALLOC_ARRAY(list, 1);
- list->type = OBJ_TREE;
- strmap_put(&ctx->paths_to_lists, path, list);
- }
- oid_array_append(&list->oids, &obj->oid);
+ add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1);
free(path);
} else {
/* assume a root tree, such as a lightweight tag. */
@@ -396,19 +400,10 @@ static int setup_pending_objects(struct path_walk_info *info,
case OBJ_BLOB:
if (!info->blobs)
continue;
- if (pending->path) {
- struct type_and_oid_list *list;
- char *path = pending->path;
- if (!(list = strmap_get(&ctx->paths_to_lists, path))) {
- CALLOC_ARRAY(list, 1);
- list->type = OBJ_BLOB;
- strmap_put(&ctx->paths_to_lists, path, list);
- }
- oid_array_append(&list->oids, &obj->oid);
- } else {
- /* assume a root tree, such as a lightweight tag. */
+ if (pending->path)
+ add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1);
+ else
oid_array_append(&tagged_blobs->oids, &obj->oid);
- }
break;
case OBJ_COMMIT:
diff --git a/progress.c b/progress.c
index 8d5ae70f3a..8315bdc3d4 100644
--- a/progress.c
+++ b/progress.c
@@ -114,16 +114,19 @@ static void display(struct progress *progress, uint64_t n, const char *done)
const char *tp;
struct strbuf *counters_sb = &progress->counters_sb;
int show_update = 0;
+ int update = !!progress_update;
int last_count_len = counters_sb->len;
- if (progress->delay && (!progress_update || --progress->delay))
+ progress_update = 0;
+
+ if (progress->delay && (!update || --progress->delay))
return;
progress->last_value = n;
tp = (progress->throughput) ? progress->throughput->display.buf : "";
if (progress->total) {
unsigned percent = n * 100 / progress->total;
- if (percent != progress->last_percent || progress_update) {
+ if (percent != progress->last_percent || update) {
progress->last_percent = percent;
strbuf_reset(counters_sb);
@@ -133,7 +136,7 @@ static void display(struct progress *progress, uint64_t n, const char *done)
tp);
show_update = 1;
}
- } else if (progress_update) {
+ } else if (update) {
strbuf_reset(counters_sb);
strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp);
show_update = 1;
@@ -166,7 +169,6 @@ static void display(struct progress *progress, uint64_t n, const char *done)
}
fflush(stderr);
}
- progress_update = 0;
}
}
@@ -281,7 +283,7 @@ static int get_default_delay(void)
static int delay_in_secs = -1;
if (delay_in_secs < 0)
- delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2);
+ delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1);
return delay_in_secs;
}
diff --git a/read-cache.c b/read-cache.c
index 06ad74db22..229b8ef11c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -3947,6 +3947,7 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
const struct pathspec *pathspec, char *ps_matched,
int include_sparse, int flags)
{
+ struct odb_transaction *transaction;
struct update_callback_data data;
struct rev_info rev;
@@ -3972,9 +3973,9 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
* This function is invoked from commands other than 'add', which
* may not have their own transaction active.
*/
- begin_odb_transaction();
+ transaction = begin_odb_transaction(repo->objects);
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
- end_odb_transaction();
+ end_odb_transaction(transaction);
release_revisions(&rev);
return !!data.add_errors;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dfc8e9bc50..1b3bf26add 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2515,13 +2515,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
*/
static enum ref_transaction_error check_old_oid(struct ref_update *update,
struct object_id *oid,
+ struct strbuf *referent,
struct strbuf *err)
{
if (update->flags & REF_LOG_ONLY ||
- !(update->flags & REF_HAVE_OLD) ||
- oideq(oid, &update->old_oid))
+ !(update->flags & REF_HAVE_OLD))
return 0;
+ if (oideq(oid, &update->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ */
+ if ((update->flags & REF_NO_DEREF) && referent->len &&
+ is_null_oid(oid)) {
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "dangling symref already exists",
+ ref_update_original_update_refname(update));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ return 0;
+ }
+
if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
@@ -2661,7 +2685,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
if (update->old_target)
ret = ref_update_check_old_target(referent.buf, update, err);
else
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret)
goto out;
} else {
@@ -2693,7 +2718,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
goto out;
} else {
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret) {
goto out;
}
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 570463da41..9e889da2ff 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1012,10 +1012,6 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
if (!arg) {
struct reftable_addition *addition;
- ret = reftable_stack_reload(be->stack);
- if (ret)
- return ret;
-
ret = reftable_stack_new_addition(&addition, be->stack,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
if (ret) {
@@ -1278,9 +1274,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ret = ref_update_check_old_target(referent->buf, u, err);
if (ret)
return ret;
- } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD &&
- !oideq(&current_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid)) {
+ } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) {
+ if (oideq(&current_oid, &u->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ *
+ * Everything else is OK and we can fall through to the
+ * end of the conditional chain.
+ */
+ if ((u->flags & REF_NO_DEREF) &&
+ referent->len &&
+ is_null_oid(&u->old_oid)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "dangling symref already exists"),
+ ref_update_original_update_refname(u));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ } else if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
@@ -1974,7 +1994,8 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,
ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
if (ret)
goto done;
- ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+ ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
assert(ret != REFTABLE_API_ERROR);
@@ -2003,7 +2024,8 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,
ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
if (ret)
goto done;
- ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+ ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
assert(ret != REFTABLE_API_ERROR);
@@ -2375,7 +2397,8 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,
goto done;
arg.stack = be->stack;
- ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg);
+ ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
return ret;
@@ -2446,7 +2469,8 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,
return ret;
arg.stack = be->stack;
- ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg);
+ ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
assert(ret != REFTABLE_API_ERROR);
return ret;
@@ -2567,15 +2591,16 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
if (ret < 0)
goto done;
- ret = reftable_stack_init_log_iterator(be->stack, &it);
+ ret = reftable_stack_new_addition(&add, be->stack,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
if (ret < 0)
goto done;
- ret = reftable_iterator_seek_log(&it, refname);
+ ret = reftable_stack_init_log_iterator(be->stack, &it);
if (ret < 0)
goto done;
- ret = reftable_stack_new_addition(&add, be->stack, 0);
+ ret = reftable_iterator_seek_log(&it, refname);
if (ret < 0)
goto done;
diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h
index 910ec6ef3a..d70fcb705d 100644
--- a/reftable/reftable-stack.h
+++ b/reftable/reftable-stack.h
@@ -68,12 +68,15 @@ int reftable_addition_commit(struct reftable_addition *add);
* transaction. Releases the lock if held. */
void reftable_addition_destroy(struct reftable_addition *add);
-/* add a new table to the stack. The write_table function must call
- * reftable_writer_set_limits, add refs and return an error value. */
+/*
+ * Add a new table to the stack. The write_table function must call
+ * reftable_writer_set_limits, add refs and return an error value.
+ * The flags are passed through to `reftable_stack_new_addition()`.
+ */
int reftable_stack_add(struct reftable_stack *st,
int (*write_table)(struct reftable_writer *wr,
void *write_arg),
- void *write_arg);
+ void *write_arg, unsigned flags);
struct reftable_iterator;
diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h
index 0fbeff17f4..1e7003cd69 100644
--- a/reftable/reftable-writer.h
+++ b/reftable/reftable-writer.h
@@ -156,7 +156,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
the records before adding them, reordering the records array passed in.
*/
int reftable_writer_add_refs(struct reftable_writer *w,
- struct reftable_ref_record *refs, int n);
+ struct reftable_ref_record *refs, size_t n);
/*
adds reftable_log_records. Log records are keyed by (refname, decreasing
@@ -171,7 +171,7 @@ int reftable_writer_add_log(struct reftable_writer *w,
the records before adding them, reordering records array passed in.
*/
int reftable_writer_add_logs(struct reftable_writer *w,
- struct reftable_log_record *logs, int n);
+ struct reftable_log_record *logs, size_t n);
/* reftable_writer_close finalizes the reftable. The writer is retained so
* statistics can be inspected. */
diff --git a/reftable/stack.c b/reftable/stack.c
index 4caf96aa1d..f91ce50bcd 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -17,18 +17,6 @@
#include "table.h"
#include "writer.h"
-static int stack_try_add(struct reftable_stack *st,
- int (*write_table)(struct reftable_writer *wr,
- void *arg),
- void *arg);
-static int stack_write_compact(struct reftable_stack *st,
- struct reftable_writer *wr,
- size_t first, size_t last,
- struct reftable_log_expiry_config *config);
-static void reftable_addition_close(struct reftable_addition *add);
-static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
- int reuse_open);
-
static int stack_filename(struct reftable_buf *dest, struct reftable_stack *st,
const char *name)
{
@@ -84,54 +72,6 @@ static int fd_writer_flush(void *arg)
return stack_fsync(writer->opts, writer->fd);
}
-int reftable_new_stack(struct reftable_stack **dest, const char *dir,
- const struct reftable_write_options *_opts)
-{
- struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
- struct reftable_write_options opts = { 0 };
- struct reftable_stack *p;
- int err;
-
- p = reftable_calloc(1, sizeof(*p));
- if (!p) {
- err = REFTABLE_OUT_OF_MEMORY_ERROR;
- goto out;
- }
-
- if (_opts)
- opts = *_opts;
- if (opts.hash_id == 0)
- opts.hash_id = REFTABLE_HASH_SHA1;
-
- *dest = NULL;
-
- reftable_buf_reset(&list_file_name);
- if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
- (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
- goto out;
-
- p->list_file = reftable_buf_detach(&list_file_name);
- p->list_fd = -1;
- p->opts = opts;
- p->reftable_dir = reftable_strdup(dir);
- if (!p->reftable_dir) {
- err = REFTABLE_OUT_OF_MEMORY_ERROR;
- goto out;
- }
-
- err = reftable_stack_reload_maybe_reuse(p, 1);
- if (err < 0)
- goto out;
-
- *dest = p;
- err = 0;
-
-out:
- if (err < 0)
- reftable_stack_destroy(p);
- return err;
-}
-
static int fd_read_lines(int fd, char ***namesp)
{
char *buf = NULL;
@@ -591,9 +531,59 @@ out:
return err;
}
-/* -1 = error
- 0 = up to date
- 1 = changed. */
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+ const struct reftable_write_options *_opts)
+{
+ struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *p;
+ int err;
+
+ p = reftable_calloc(1, sizeof(*p));
+ if (!p) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ if (_opts)
+ opts = *_opts;
+ if (opts.hash_id == 0)
+ opts.hash_id = REFTABLE_HASH_SHA1;
+
+ *dest = NULL;
+
+ reftable_buf_reset(&list_file_name);
+ if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
+ (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
+ goto out;
+
+ p->list_file = reftable_buf_detach(&list_file_name);
+ p->list_fd = -1;
+ p->opts = opts;
+ p->reftable_dir = reftable_strdup(dir);
+ if (!p->reftable_dir) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ err = reftable_stack_reload_maybe_reuse(p, 1);
+ if (err < 0)
+ goto out;
+
+ *dest = p;
+ err = 0;
+
+out:
+ if (err < 0)
+ reftable_stack_destroy(p);
+ return err;
+}
+
+/*
+ * Check whether the given stack is up-to-date with what we have in memory.
+ * Returns 0 if so, 1 if the stack is out-of-date or a negative error code
+ * otherwise.
+ */
static int stack_uptodate(struct reftable_stack *st)
{
char **names = NULL;
@@ -667,34 +657,6 @@ int reftable_stack_reload(struct reftable_stack *st)
return err;
}
-int reftable_stack_add(struct reftable_stack *st,
- int (*write)(struct reftable_writer *wr, void *arg),
- void *arg)
-{
- int err = stack_try_add(st, write, arg);
- if (err < 0) {
- if (err == REFTABLE_OUTDATED_ERROR) {
- /* Ignore error return, we want to propagate
- REFTABLE_OUTDATED_ERROR.
- */
- reftable_stack_reload(st);
- }
- return err;
- }
-
- return 0;
-}
-
-static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
-{
- char buf[100];
- uint32_t rnd = reftable_rand();
- snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
- min, max, rnd);
- reftable_buf_reset(dest);
- return reftable_buf_addstr(dest, buf);
-}
-
struct reftable_addition {
struct reftable_flock tables_list_lock;
struct reftable_stack *stack;
@@ -704,7 +666,25 @@ struct reftable_addition {
uint64_t next_update_index;
};
-#define REFTABLE_ADDITION_INIT {0}
+static void reftable_addition_close(struct reftable_addition *add)
+{
+ struct reftable_buf nm = REFTABLE_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < add->new_tables_len; i++) {
+ if (!stack_filename(&nm, add->stack, add->new_tables[i]))
+ unlink(nm.buf);
+ reftable_free(add->new_tables[i]);
+ add->new_tables[i] = NULL;
+ }
+ reftable_free(add->new_tables);
+ add->new_tables = NULL;
+ add->new_tables_len = 0;
+ add->new_tables_cap = 0;
+
+ flock_release(&add->tables_list_lock);
+ reftable_buf_release(&nm);
+}
static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_stack *st,
@@ -713,18 +693,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_buf lock_file_name = REFTABLE_BUF_INIT;
int err;
+ memset(add, 0, sizeof(*add));
add->stack = st;
err = flock_acquire(&add->tables_list_lock, st->list_file,
st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST) {
- err = REFTABLE_LOCK_ERROR;
- } else {
- err = REFTABLE_IO_ERROR;
- }
+ if (err < 0)
goto done;
- }
+
if (st->opts.default_permissions) {
if (chmod(add->tables_list_lock.path,
st->opts.default_permissions) < 0) {
@@ -754,24 +730,54 @@ done:
return err;
}
-static void reftable_addition_close(struct reftable_addition *add)
+static int stack_try_add(struct reftable_stack *st,
+ int (*write_table)(struct reftable_writer *wr,
+ void *arg),
+ void *arg, unsigned flags)
{
- struct reftable_buf nm = REFTABLE_BUF_INIT;
- size_t i;
+ struct reftable_addition add;
+ int err;
- for (i = 0; i < add->new_tables_len; i++) {
- if (!stack_filename(&nm, add->stack, add->new_tables[i]))
- unlink(nm.buf);
- reftable_free(add->new_tables[i]);
- add->new_tables[i] = NULL;
+ err = reftable_stack_init_addition(&add, st, flags);
+ if (err < 0)
+ goto done;
+
+ err = reftable_addition_add(&add, write_table, arg);
+ if (err < 0)
+ goto done;
+
+ err = reftable_addition_commit(&add);
+done:
+ reftable_addition_close(&add);
+ return err;
+}
+
+int reftable_stack_add(struct reftable_stack *st,
+ int (*write)(struct reftable_writer *wr, void *arg),
+ void *arg, unsigned flags)
+{
+ int err = stack_try_add(st, write, arg, flags);
+ if (err < 0) {
+ if (err == REFTABLE_OUTDATED_ERROR) {
+ /* Ignore error return, we want to propagate
+ REFTABLE_OUTDATED_ERROR.
+ */
+ reftable_stack_reload(st);
+ }
+ return err;
}
- reftable_free(add->new_tables);
- add->new_tables = NULL;
- add->new_tables_len = 0;
- add->new_tables_cap = 0;
- flock_release(&add->tables_list_lock);
- reftable_buf_release(&nm);
+ return 0;
+}
+
+static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
+{
+ char buf[100];
+ uint32_t rnd = reftable_rand();
+ snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
+ min, max, rnd);
+ reftable_buf_reset(dest);
+ return reftable_buf_addstr(dest, buf);
}
void reftable_addition_destroy(struct reftable_addition *add)
@@ -841,10 +847,13 @@ int reftable_addition_commit(struct reftable_addition *add)
* control. It is possible that a concurrent writer is already
* trying to compact parts of the stack, which would lead to a
* `REFTABLE_LOCK_ERROR` because parts of the stack are locked
- * already. This is a benign error though, so we ignore it.
+ * already. Similarly, the stack may have been rewritten by a
+ * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`.
+ * Both of these errors are benign, so we simply ignore them.
*/
err = reftable_stack_auto_compact(add->stack);
- if (err < 0 && err != REFTABLE_LOCK_ERROR)
+ if (err < 0 && err != REFTABLE_LOCK_ERROR &&
+ err != REFTABLE_OUTDATED_ERROR)
goto done;
err = 0;
}
@@ -858,39 +867,18 @@ int reftable_stack_new_addition(struct reftable_addition **dest,
struct reftable_stack *st,
unsigned int flags)
{
- int err = 0;
- struct reftable_addition empty = REFTABLE_ADDITION_INIT;
+ int err;
REFTABLE_CALLOC_ARRAY(*dest, 1);
if (!*dest)
return REFTABLE_OUT_OF_MEMORY_ERROR;
- **dest = empty;
err = reftable_stack_init_addition(*dest, st, flags);
if (err) {
reftable_free(*dest);
*dest = NULL;
}
- return err;
-}
-static int stack_try_add(struct reftable_stack *st,
- int (*write_table)(struct reftable_writer *wr,
- void *arg),
- void *arg)
-{
- struct reftable_addition add = REFTABLE_ADDITION_INIT;
- int err = reftable_stack_init_addition(&add, st, 0);
- if (err < 0)
- goto done;
-
- err = reftable_addition_add(&add, write_table, arg);
- if (err < 0)
- goto done;
-
- err = reftable_addition_commit(&add);
-done:
- reftable_addition_close(&add);
return err;
}
@@ -1007,72 +995,6 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
return 1;
}
-static int stack_compact_locked(struct reftable_stack *st,
- size_t first, size_t last,
- struct reftable_log_expiry_config *config,
- struct reftable_tmpfile *tab_file_out)
-{
- struct reftable_buf next_name = REFTABLE_BUF_INIT;
- struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
- struct reftable_writer *wr = NULL;
- struct fd_writer writer= {
- .opts = &st->opts,
- };
- struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
- int err = 0;
-
- err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
- reftable_table_max_update_index(st->tables[last]));
- if (err < 0)
- goto done;
-
- err = stack_filename(&tab_file_path, st, next_name.buf);
- if (err < 0)
- goto done;
-
- err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
- if (err < 0)
- goto done;
-
- err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
- if (err < 0)
- goto done;
-
- if (st->opts.default_permissions &&
- chmod(tab_file.path, st->opts.default_permissions) < 0) {
- err = REFTABLE_IO_ERROR;
- goto done;
- }
-
- writer.fd = tab_file.fd;
- err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
- &writer, &st->opts);
- if (err < 0)
- goto done;
-
- err = stack_write_compact(st, wr, first, last, config);
- if (err < 0)
- goto done;
-
- err = reftable_writer_close(wr);
- if (err < 0)
- goto done;
-
- err = tmpfile_close(&tab_file);
- if (err < 0)
- goto done;
-
- *tab_file_out = tab_file;
- tab_file = REFTABLE_TMPFILE_INIT;
-
-done:
- tmpfile_delete(&tab_file);
- reftable_writer_free(wr);
- reftable_buf_release(&next_name);
- reftable_buf_release(&tab_file_path);
- return err;
-}
-
static int stack_write_compact(struct reftable_stack *st,
struct reftable_writer *wr,
size_t first, size_t last,
@@ -1172,6 +1094,72 @@ done:
return err;
}
+static int stack_compact_locked(struct reftable_stack *st,
+ size_t first, size_t last,
+ struct reftable_log_expiry_config *config,
+ struct reftable_tmpfile *tab_file_out)
+{
+ struct reftable_buf next_name = REFTABLE_BUF_INIT;
+ struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
+ struct reftable_writer *wr = NULL;
+ struct fd_writer writer= {
+ .opts = &st->opts,
+ };
+ struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
+ int err = 0;
+
+ err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
+ reftable_table_max_update_index(st->tables[last]));
+ if (err < 0)
+ goto done;
+
+ err = stack_filename(&tab_file_path, st, next_name.buf);
+ if (err < 0)
+ goto done;
+
+ err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
+ if (err < 0)
+ goto done;
+
+ err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
+ if (err < 0)
+ goto done;
+
+ if (st->opts.default_permissions &&
+ chmod(tab_file.path, st->opts.default_permissions) < 0) {
+ err = REFTABLE_IO_ERROR;
+ goto done;
+ }
+
+ writer.fd = tab_file.fd;
+ err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
+ &writer, &st->opts);
+ if (err < 0)
+ goto done;
+
+ err = stack_write_compact(st, wr, first, last, config);
+ if (err < 0)
+ goto done;
+
+ err = reftable_writer_close(wr);
+ if (err < 0)
+ goto done;
+
+ err = tmpfile_close(&tab_file);
+ if (err < 0)
+ goto done;
+
+ *tab_file_out = tab_file;
+ tab_file = REFTABLE_TMPFILE_INIT;
+
+done:
+ tmpfile_delete(&tab_file);
+ reftable_writer_free(wr);
+ reftable_buf_release(&next_name);
+ reftable_buf_release(&tab_file_path);
+ return err;
+}
+
enum stack_compact_range_flags {
/*
* Perform a best-effort compaction. That is, even if we cannot lock
@@ -1219,17 +1207,27 @@ static int stack_compact_range(struct reftable_stack *st,
* which are part of the user-specified range.
*/
err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST)
- err = REFTABLE_LOCK_ERROR;
- else
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
+ /*
+ * Check whether the stack is up-to-date. We unfortunately cannot
+ * handle the situation gracefully in case it's _not_ up-to-date
+ * because the range of tables that the user has requested us to
+ * compact may have been changed. So instead we abort.
+ *
+ * We could in theory improve the situation by having the caller not
+ * pass in a range, but instead the list of tables to compact. If so,
+ * we could check that relevant tables still exist. But for now it's
+ * good enough to just abort.
+ */
err = stack_uptodate(st);
- if (err)
+ if (err < 0)
+ goto done;
+ if (err > 0) {
+ err = REFTABLE_OUTDATED_ERROR;
goto done;
+ }
/*
* Lock all tables in the user-provided range. This is the slice of our
@@ -1264,7 +1262,7 @@ static int stack_compact_range(struct reftable_stack *st,
* tables, otherwise there would be nothing to compact.
* In that case, we return a lock error to our caller.
*/
- if (errno == EEXIST && last - (i - 1) >= 2 &&
+ if (err == REFTABLE_LOCK_ERROR && last - (i - 1) >= 2 &&
flags & STACK_COMPACT_RANGE_BEST_EFFORT) {
err = 0;
/*
@@ -1276,13 +1274,9 @@ static int stack_compact_range(struct reftable_stack *st,
*/
first = (i - 1) + 1;
break;
- } else if (errno == EEXIST) {
- err = REFTABLE_LOCK_ERROR;
- goto done;
- } else {
- err = REFTABLE_IO_ERROR;
- goto done;
}
+
+ goto done;
}
/*
@@ -1291,10 +1285,8 @@ static int stack_compact_range(struct reftable_stack *st,
* of tables.
*/
err = flock_close(&table_locks[nlocks++]);
- if (err < 0) {
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
}
/*
@@ -1326,13 +1318,8 @@ static int stack_compact_range(struct reftable_stack *st,
* the new table.
*/
err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST)
- err = REFTABLE_LOCK_ERROR;
- else
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
if (st->opts.default_permissions) {
if (chmod(tables_list_lock.path,
diff --git a/reftable/system.c b/reftable/system.c
index 1ee268b125..725a25844e 100644
--- a/reftable/system.c
+++ b/reftable/system.c
@@ -72,7 +72,7 @@ int flock_acquire(struct reftable_flock *l, const char *target_path,
reftable_free(lockfile);
if (errno == EEXIST)
return REFTABLE_LOCK_ERROR;
- return -1;
+ return REFTABLE_IO_ERROR;
}
l->fd = get_lock_file_fd(lockfile);
diff --git a/reftable/system.h b/reftable/system.h
index beb9d2431f..c54ed4cad6 100644
--- a/reftable/system.h
+++ b/reftable/system.h
@@ -81,7 +81,9 @@ struct reftable_flock {
* to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative
* we block indefinitely.
*
- * Retrun 0 on success, a reftable error code on error.
+ * Retrun 0 on success, a reftable error code on error. Specifically,
+ * `REFTABLE_LOCK_ERROR` should be returned in case the target path is already
+ * locked.
*/
int flock_acquire(struct reftable_flock *l, const char *target_path,
long timeout_ms);
diff --git a/reftable/writer.c b/reftable/writer.c
index 3b4ebdd6dc..0133b64975 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -395,14 +395,16 @@ out:
}
int reftable_writer_add_refs(struct reftable_writer *w,
- struct reftable_ref_record *refs, int n)
+ struct reftable_ref_record *refs, size_t n)
{
int err = 0;
- int i = 0;
- QSORT(refs, n, reftable_ref_record_compare_name);
- for (i = 0; err == 0 && i < n; i++) {
+
+ if (n)
+ qsort(refs, n, sizeof(*refs), reftable_ref_record_compare_name);
+
+ for (size_t i = 0; err == 0 && i < n; i++)
err = reftable_writer_add_ref(w, &refs[i]);
- }
+
return err;
}
@@ -486,15 +488,16 @@ done:
}
int reftable_writer_add_logs(struct reftable_writer *w,
- struct reftable_log_record *logs, int n)
+ struct reftable_log_record *logs, size_t n)
{
int err = 0;
- int i = 0;
- QSORT(logs, n, reftable_log_record_compare_key);
- for (i = 0; err == 0 && i < n; i++) {
+ if (n)
+ qsort(logs, n, sizeof(*logs), reftable_log_record_compare_key);
+
+ for (size_t i = 0; err == 0 && i < n; i++)
err = reftable_writer_add_log(w, &logs[i]);
- }
+
return err;
}
diff --git a/repository.c b/repository.c
index ecd691181f..6faf5c7398 100644
--- a/repository.c
+++ b/repository.c
@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)
repo->parsed_objects = parsed_object_pool_new(repo);
ALLOC_ARRAY(repo->index, 1);
index_state_init(repo->index, repo);
+ repo->check_deprecated_config = true;
/*
* When a command runs inside a repository, it learns what
@@ -168,6 +169,7 @@ void repo_set_gitdir(struct repository *repo,
if (!repo->objects->sources) {
CALLOC_ARRAY(repo->objects->sources, 1);
repo->objects->sources->odb = repo->objects;
+ repo->objects->sources->local = true;
repo->objects->sources_tail = &repo->objects->sources->next;
}
expand_base_dir(&repo->objects->sources->path, o->object_dir,
diff --git a/repository.h b/repository.h
index 042dc93f0f..5808a5d610 100644
--- a/repository.h
+++ b/repository.h
@@ -161,6 +161,9 @@ struct repository {
/* Indicate if a repository has a different 'commondir' from 'gitdir' */
unsigned different_commondir:1;
+
+ /* Should repo_config() check for deprecated settings */
+ bool check_deprecated_config;
};
#ifdef USE_THE_REPOSITORY_VARIABLE
diff --git a/t/Makefile b/t/Makefile
index 757674e727..ab8a5b54aa 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -189,15 +189,9 @@ perf:
.PHONY: libgit-sys-test libgit-rs-test
libgit-sys-test:
- $(QUIET)(\
- cd ../contrib/libgit-sys && \
- cargo test \
- )
-libgit-rs-test:
- $(QUIET)(\
- cd ../contrib/libgit-rs && \
- cargo test \
- )
+ $(QUIET)cargo test --manifest-path ../contrib/libgit-sys/Cargo.toml
+libgit-rs-test: libgit-sys-test
+ $(QUIET)cargo test --manifest-path ../contrib/libgit-rs/Cargo.toml
ifdef INCLUDE_LIBGIT_RS
-all:: libgit-sys-test libgit-rs-test
+all:: libgit-rs-test
endif
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index da2aa036b5..6de5d1665a 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -11,14 +11,24 @@
#include "gettext.h"
#include "pack-revindex.h"
+static struct multi_pack_index *setup_midx(const char *object_dir)
+{
+ struct odb_source *source;
+ setup_git_directory();
+ source = odb_find_source(the_repository->objects, object_dir);
+ if (!source)
+ source = odb_add_to_alternates_memory(the_repository->objects,
+ object_dir);
+ return load_multi_pack_index(source);
+}
+
static int read_midx_file(const char *object_dir, const char *checksum,
int show_objects)
{
uint32_t i;
struct multi_pack_index *m;
- setup_git_directory();
- m = load_multi_pack_index(the_repository, object_dir, 1);
+ m = setup_midx(object_dir);
if (!m)
return 1;
@@ -56,7 +66,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,
for (i = 0; i < m->num_packs; i++)
printf("%s\n", m->pack_names[i]);
- printf("object-dir: %s\n", m->object_dir);
+ printf("object-dir: %s\n", m->source->path);
if (show_objects) {
struct object_id oid;
@@ -65,7 +75,7 @@ static int read_midx_file(const char *object_dir, const char *checksum,
for (i = 0; i < m->num_objects; i++) {
nth_midxed_object_oid(&oid, m,
i + m->num_objects_in_base);
- fill_midx_entry(the_repository, &oid, &e, m);
+ fill_midx_entry(m, &oid, &e);
printf("%s %"PRIu64"\t%s\n",
oid_to_hex(&oid), e.offset, e.p->pack_name);
@@ -81,8 +91,7 @@ static int read_midx_checksum(const char *object_dir)
{
struct multi_pack_index *m;
- setup_git_directory();
- m = load_multi_pack_index(the_repository, object_dir, 1);
+ m = setup_midx(object_dir);
if (!m)
return 1;
printf("%s\n", hash_to_hex(get_midx_checksum(m)));
@@ -96,9 +105,7 @@ static int read_midx_preferred_pack(const char *object_dir)
struct multi_pack_index *midx = NULL;
uint32_t preferred_pack;
- setup_git_directory();
-
- midx = load_multi_pack_index(the_repository, object_dir, 1);
+ midx = setup_midx(object_dir);
if (!midx)
return 1;
@@ -119,14 +126,12 @@ static int read_midx_bitmapped_packs(const char *object_dir)
struct bitmapped_pack pack;
uint32_t i;
- setup_git_directory();
-
- midx = load_multi_pack_index(the_repository, object_dir, 1);
+ midx = setup_midx(object_dir);
if (!midx)
return 1;
for (i = 0; i < midx->num_packs + midx->num_packs_in_base; i++) {
- if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0) {
+ if (nth_bitmapped_pack(midx, &pack, i) < 0) {
close_midx(midx);
return 1;
}
diff --git a/t/meson.build b/t/meson.build
index baeeba2ce6..7974795fe4 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -206,11 +206,13 @@ integration_tests = [
't1419-exclude-refs.sh',
't1420-lost-found.sh',
't1421-reflog-write.sh',
+ 't1422-show-ref-exists.sh',
't1430-bad-ref-name.sh',
't1450-fsck.sh',
't1451-fsck-buffer.sh',
't1460-refs-migrate.sh',
't1461-refs-list.sh',
+ 't1462-refs-exists.sh',
't1500-rev-parse.sh',
't1501-work-tree.sh',
't1502-rev-parse-parseopt.sh',
@@ -951,6 +953,7 @@ integration_tests = [
't8012-blame-colors.sh',
't8013-blame-ignore-revs.sh',
't8014-blame-ignore-fuzzy.sh',
+ 't8020-last-modified.sh',
't9001-send-email.sh',
't9002-column.sh',
't9003-help-autocorrect.sh',
@@ -1144,6 +1147,7 @@ benchmarks = [
'perf/p7820-grep-engines.sh',
'perf/p7821-grep-engines-fixed.sh',
'perf/p7822-grep-perl-character.sh',
+ 'perf/p8020-last-modified.sh',
'perf/p9210-scalar.sh',
'perf/p9300-fast-import-export.sh',
]
@@ -1219,4 +1223,4 @@ if perl.found() and time.found()
timeout: 0,
)
endforeach
-endif \ No newline at end of file
+endif
diff --git a/t/perf/p8020-last-modified.sh b/t/perf/p8020-last-modified.sh
new file mode 100755
index 0000000000..cb1f98d3db
--- /dev/null
+++ b/t/perf/p8020-last-modified.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+test_description='last-modified perf tests'
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+test_perf 'top-level last-modified' '
+ git last-modified HEAD
+'
+
+test_perf 'top-level recursive last-modified' '
+ git last-modified -r HEAD
+'
+
+test_perf 'subdir last-modified' '
+ git ls-tree -d HEAD >subtrees &&
+ path="$(head -n 1 subtrees | cut -f2)" &&
+ git last-modified -r HEAD -- "$path"
+'
+
+test_done
diff --git a/t/show-ref-exists-tests.sh b/t/show-ref-exists-tests.sh
new file mode 100644
index 0000000000..36e8e9df33
--- /dev/null
+++ b/t/show-ref-exists-tests.sh
@@ -0,0 +1,77 @@
+git_show_ref_exists=${git_show_ref_exists:-git show-ref --exists}
+
+test_expect_success setup '
+ test_commit --annotate A &&
+ git checkout -b side &&
+ test_commit --annotate B &&
+ git checkout main &&
+ test_commit C &&
+ git branch B A^0
+'
+
+test_expect_success '--exists with existing reference' '
+ ${git_show_ref_exists} refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+'
+
+test_expect_success '--exists with missing reference' '
+ test_expect_code 2 ${git_show_ref_exists} refs/heads/does-not-exist
+'
+
+test_expect_success '--exists does not use DWIM' '
+ test_expect_code 2 ${git_show_ref_exists} $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
+ grep "reference does not exist" err
+'
+
+test_expect_success '--exists with HEAD' '
+ ${git_show_ref_exists} HEAD
+'
+
+test_expect_success '--exists with bad reference name' '
+ test_when_finished "git update-ref -d refs/heads/bad...name" &&
+ new_oid=$(git rev-parse HEAD) &&
+ test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+ ${git_show_ref_exists} refs/heads/bad...name
+'
+
+test_expect_success '--exists with arbitrary symref' '
+ test_when_finished "git symbolic-ref -d refs/symref" &&
+ git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+ ${git_show_ref_exists} refs/symref
+'
+
+test_expect_success '--exists with dangling symref' '
+ test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ ${git_show_ref_exists} refs/heads/dangling
+'
+
+test_expect_success '--exists with nonexistent object ID' '
+ test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+ ${git_show_ref_exists} refs/heads/missing-oid
+'
+
+test_expect_success '--exists with non-commit object' '
+ tree_oid=$(git rev-parse HEAD^{tree}) &&
+ test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+ ${git_show_ref_exists} refs/heads/tree
+'
+
+test_expect_success '--exists with directory fails with generic error' '
+ cat >expect <<-EOF &&
+ error: reference does not exist
+ EOF
+ test_expect_code 2 ${git_show_ref_exists} refs/heads 2>err &&
+ test_cmp expect err
+'
+
+test_expect_success '--exists with non-existent special ref' '
+ test_expect_code 2 ${git_show_ref_exists} FETCH_HEAD
+'
+
+test_expect_success '--exists with existing special ref' '
+ test_when_finished "rm .git/FETCH_HEAD" &&
+ git rev-parse HEAD >.git/FETCH_HEAD &&
+ ${git_show_ref_exists} FETCH_HEAD
+'
+
+test_done
diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh
index 2f7504ae7e..e12e18f97f 100755
--- a/t/t0450-txt-doc-vs-help.sh
+++ b/t/t0450-txt-doc-vs-help.sh
@@ -41,7 +41,7 @@ help_to_synopsis () {
}
builtin_to_adoc () {
- echo "$GIT_BUILD_DIR/Documentation/git-$1.adoc"
+ echo "$GIT_SOURCE_DIR/Documentation/git-$1.adoc"
}
adoc_to_synopsis () {
@@ -112,10 +112,19 @@ do
adoc="$(builtin_to_adoc "$builtin")" &&
preq="$(echo BUILTIN_ADOC_$builtin | tr '[:lower:]-' '[:upper:]_')" &&
- if test -f "$adoc"
+ # If and only if *.adoc is missing, builtin shall be listed in t0450/adoc-missing.
+ if grep -q "^$builtin$" "$TEST_DIRECTORY"/t0450/adoc-missing
then
+ test_expect_success "$builtin appropriately marked as not having .adoc" '
+ ! test -f "$adoc"
+ '
+ else
test_set_prereq "$preq"
- fi &&
+
+ test_expect_success "$builtin appropriately marked as having .adoc" '
+ test -f "$adoc"
+ '
+ fi
# *.adoc output assertions
test_expect_success "$preq" "$builtin *.adoc SYNOPSIS has dashed labels" '
diff --git a/t/t0450/adoc-missing b/t/t0450/adoc-missing
new file mode 100644
index 0000000000..1ec9f8dcf3
--- /dev/null
+++ b/t/t0450/adoc-missing
@@ -0,0 +1,9 @@
+checkout--worker
+merge-ours
+merge-recursive
+merge-recursive-ours
+merge-recursive-theirs
+merge-subtree
+pickaxe
+submodule--helper
+upload-archive--writer
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index d8101139b4..b0f691c151 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -1506,6 +1506,8 @@ test_expect_success 'sparse-index is not expanded' '
ensure_not_expanded reset --hard &&
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
+ ensure_not_expanded ls-files deep/deeper1 &&
+
echo >>sparse-index/README.md &&
ensure_not_expanded add -A &&
echo >>sparse-index/extra.txt &&
@@ -1607,6 +1609,17 @@ test_expect_success 'describe tested on all' '
test_all_match git describe --dirty
'
+test_expect_success 'ls-files filtering and expansion' '
+ init_repos &&
+
+ # This filtering will hit a sparse directory midway
+ # through the iteration.
+ test_all_match git ls-files deep &&
+
+ # This pathspec will filter the index to only a sparse
+ # directory.
+ test_all_match git ls-files folder1
+'
test_expect_success 'sparse-index is not expanded: describe' '
init_repos &&
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 96648a6e5d..b7415ec9d5 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -2368,4 +2368,25 @@ test_expect_success REFFILES 'empty directories are pruned when not committing'
test_path_is_missing .git/refs/heads/nested
'
+test_expect_success 'dangling symref not overwritten by creation' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF &&
+ create refs/heads/dangling HEAD
+ EOF
+ test_grep "cannot lock.*dangling symref already exists" err &&
+ test_must_fail git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
+test_expect_success 'dangling symref overwritten without old oid' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ git update-ref --no-deref --stdin <<-\EOF &&
+ update refs/heads/dangling HEAD
+ EOF
+ git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
test_done
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9da3650e91..36c903ca19 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -228,69 +228,4 @@ test_expect_success 'show-ref sub-modes are mutually exclusive' '
grep "cannot be used together" err
'
-test_expect_success '--exists with existing reference' '
- git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
-'
-
-test_expect_success '--exists with missing reference' '
- test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
-'
-
-test_expect_success '--exists does not use DWIM' '
- test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
- grep "reference does not exist" err
-'
-
-test_expect_success '--exists with HEAD' '
- git show-ref --exists HEAD
-'
-
-test_expect_success '--exists with bad reference name' '
- test_when_finished "git update-ref -d refs/heads/bad...name" &&
- new_oid=$(git rev-parse HEAD) &&
- test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
- git show-ref --exists refs/heads/bad...name
-'
-
-test_expect_success '--exists with arbitrary symref' '
- test_when_finished "git symbolic-ref -d refs/symref" &&
- git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
- git show-ref --exists refs/symref
-'
-
-test_expect_success '--exists with dangling symref' '
- test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
- git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
- git show-ref --exists refs/heads/dangling
-'
-
-test_expect_success '--exists with nonexistent object ID' '
- test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
- git show-ref --exists refs/heads/missing-oid
-'
-
-test_expect_success '--exists with non-commit object' '
- tree_oid=$(git rev-parse HEAD^{tree}) &&
- test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
- git show-ref --exists refs/heads/tree
-'
-
-test_expect_success '--exists with directory fails with generic error' '
- cat >expect <<-EOF &&
- error: reference does not exist
- EOF
- test_expect_code 2 git show-ref --exists refs/heads 2>err &&
- test_cmp expect err
-'
-
-test_expect_success '--exists with non-existent special ref' '
- test_expect_code 2 git show-ref --exists FETCH_HEAD
-'
-
-test_expect_success '--exists with existing special ref' '
- test_when_finished "rm .git/FETCH_HEAD" &&
- git rev-parse HEAD >.git/FETCH_HEAD &&
- git show-ref --exists FETCH_HEAD
-'
-
test_done
diff --git a/t/t1422-show-ref-exists.sh b/t/t1422-show-ref-exists.sh
new file mode 100755
index 0000000000..fdca3f16c8
--- /dev/null
+++ b/t/t1422-show-ref-exists.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='show-ref --exists'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/show-ref-exists-tests.sh
diff --git a/t/t1462-refs-exists.sh b/t/t1462-refs-exists.sh
new file mode 100755
index 0000000000..349453c4ca
--- /dev/null
+++ b/t/t1462-refs-exists.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='refs exists'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+git_show_ref_exists='git refs exists'
+. "$TEST_DIRECTORY"/show-ref-exists-tests.sh
diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh
index 1c69d52c76..c824c1a25c 100755
--- a/t/t1517-outside-repo.sh
+++ b/t/t1517-outside-repo.sh
@@ -111,8 +111,11 @@ for cmd in $(git --list-cmds=main)
do
cmd=${cmd%.*} # strip .sh, .perl, etc.
case "$cmd" in
- archimport | cvsexportcommit | cvsimport | cvsserver | daemon | \
+ archimport | citool | credential-netrc | credential-libsecret | \
+ credential-osxkeychain | cvsexportcommit | cvsimport | cvsserver | \
+ daemon | \
difftool--helper | filter-branch | fsck-objects | get-tar-commit-id | \
+ gui | gui--askpass | \
http-backend | http-fetch | http-push | init-db | \
merge-octopus | merge-one-file | merge-resolve | mergetool | \
mktag | p4 | p4.py | pickaxe | remote-ftp | remote-ftps | \
diff --git a/t/t1900-repo.sh b/t/t1900-repo.sh
index a69c715357..2beba67889 100755
--- a/t/t1900-repo.sh
+++ b/t/t1900-repo.sh
@@ -63,6 +63,12 @@ test_expect_success 'setup remote' '
test_repo_info 'shallow repository = true is retrieved correctly' \
'git clone --depth 1 "file://$PWD/remote"' 'shallow' 'layout.shallow' 'true'
+test_repo_info 'object.format = sha1 is retrieved correctly' \
+ 'git init --object-format=sha1' 'sha1' 'object.format' 'sha1'
+
+test_repo_info 'object.format = sha256 is retrieved correctly' \
+ 'git init --object-format=sha256' 'sha256' 'object.format' 'sha256'
+
test_expect_success 'values returned in order requested' '
cat >expect <<-\EOF &&
layout.bare=false
@@ -92,4 +98,16 @@ test_expect_success 'git-repo-info aborts when requesting an invalid format' '
test_cmp expect actual
'
+test_expect_success '-z uses nul-terminated format' '
+ printf "layout.bare\nfalse\0layout.shallow\nfalse\0" >expected &&
+ git repo info -z layout.bare layout.shallow >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'git repo info uses the last requested format' '
+ echo "layout.bare=false" >expected &&
+ git repo info --format=nul -z --format=keyvalue layout.bare >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 34d6ad0770..e778dd8ae4 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'
-test_expect_success 'rebase -i respects core.commentchar=auto' '
+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '
test_config core.commentchar auto &&
write_script copy-edit-script.sh <<-\EOF &&
cp "$1" edit-script
@@ -1184,8 +1184,23 @@ test_expect_success 'rebase -i respects core.commentchar=auto' '
test_when_finished "git rebase --abort || :" &&
(
test_set_editor "$(pwd)/copy-edit-script.sh" &&
- git rebase -i HEAD^
+ git rebase -i HEAD^ 2>err
) &&
+ sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
+ cat >expect <<-EOF &&
+ Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
+
+ To use the default comment string (#) please run
+
+ git config unset core.commentChar
+
+ To set a custom comment string please run
+
+ git config set core.commentChar <comment string>
+
+ where ${SQ}<comment string>${SQ} is the string you wish to use.
+ EOF
+ test_cmp expect actual &&
test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)"
'
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index b8a8dd77e7..f9b8999db5 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -328,7 +328,7 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas
test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
'
-test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '
+test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' '
git checkout -b branch-a &&
test_commit A F1 &&
git checkout -b branch-b HEAD^ &&
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 950451cf6a..0a7c3ca42f 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -78,6 +78,8 @@ canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
+canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error
+
test_bad_opts "-L" "switch.*requires a value"
test_bad_opts "-L b.c" "argument not .start,end:file"
test_bad_opts "-L 1:" "argument not .start,end:file"
diff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple
index 76ad5b598c..1eee8a7801 100644
--- a/t/t4211/sha1/expect.multiple
+++ b/t/t4211/sha1/expect.multiple
@@ -102,3 +102,9 @@ diff --git a/a.c b/a.c
+ s++;
+ }
+}
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error
new file mode 100644
index 0000000000..994c37db1e
--- /dev/null
+++ b/t/t4211/sha1/expect.no-assertion-error
@@ -0,0 +1,90 @@
+commit 0d8dcfc6b968e06a27d5215bad1fdde3de9d6235
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:50:24 2013 +0100
+
+ move within the file
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -25,0 +18,9 @@
++long f(long x)
++{
++ int s = 0;
++ while (x) {
++ x /= 2;
++ s++;
++ }
++ return s;
++}
+
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges
index 6109aa0dce..c5164f3be3 100644
--- a/t/t4211/sha1/expect.two-ranges
+++ b/t/t4211/sha1/expect.two-ranges
@@ -100,3 +100,9 @@ diff --git a/a.c b/a.c
+ s++;
+ }
+}
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple
index ca00409b9a..dbd987b74a 100644
--- a/t/t4211/sha256/expect.multiple
+++ b/t/t4211/sha256/expect.multiple
@@ -102,3 +102,9 @@ diff --git a/a.c b/a.c
+ s++;
+ }
+}
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error
new file mode 100644
index 0000000000..36ed12aa9c
--- /dev/null
+++ b/t/t4211/sha256/expect.no-assertion-error
@@ -0,0 +1,90 @@
+commit eb871b8aa9aff323e484723039c9a92ab0266e060bc0ef2afb08fadda25c5ace
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:50:24 2013 +0100
+
+ move within the file
+
+diff --git a/b.c b/b.c
+--- a/b.c
++++ b/b.c
+@@ -25,0 +18,9 @@
++long f(long x)
++{
++ int s = 0;
++ while (x) {
++ x /= 2;
++ s++;
++ }
++ return s;
++}
+
+commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:43 2013 +0100
+
+ change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:48:10 2013 +0100
+
+ change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+ printf("%ld\n", f(15));
+ return 0;
+-}
++}
+\ No newline at end of file
+
+commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:45:16 2013 +0100
+
+ touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+- printf("%d\n", f(15));
++ printf("%ld\n", f(15));
+ return 0;
+ }
+
+commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592
+Author: Thomas Rast <trast@student.ethz.ch>
+Date: Thu Feb 28 10:44:48 2013 +0100
+
+ initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges
index af57c8b997..6a94d3b9cb 100644
--- a/t/t4211/sha256/expect.two-ranges
+++ b/t/t4211/sha256/expect.two-ranges
@@ -100,3 +100,9 @@ diff --git a/a.c b/a.c
+ s++;
+ }
+}
+@@ -0,0 +16,5 @@
++int main ()
++{
++ printf("%d\n", f(15));
++ return 0;
++}
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index bd75dea950..93f319a4b2 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -28,11 +28,11 @@ midx_read_expect () {
EOF
if test $NUM_PACKS -ge 1
then
- ls $OBJECT_DIR/pack/ | grep idx | sort
+ ls "$OBJECT_DIR"/pack/ | grep idx | sort
fi &&
printf "object-dir: $OBJECT_DIR\n"
} >expect &&
- test-tool read-midx $OBJECT_DIR >actual &&
+ test-tool read-midx "$OBJECT_DIR" >actual &&
test_cmp expect actual
}
@@ -305,7 +305,7 @@ test_expect_success 'midx picks objects from preferred pack' '
ofs=$(git show-index <objects/pack/test-BC-$bc.idx | grep $b |
cut -d" " -f1) &&
- printf "%s %s\tobjects/pack/test-BC-%s.pack\n" \
+ printf "%s %s\t./objects/pack/test-BC-%s.pack\n" \
"$b" "$ofs" "$bc" >expect &&
grep ^$b out >actual &&
@@ -639,7 +639,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' '
( 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"
+ midx_read_expect 1 63 5 "$(pwd)/objects64" " large-offsets"
'
test_expect_success 'verify multi-pack-index with 64-bit offsets' '
@@ -989,6 +989,23 @@ test_expect_success 'repack --batch-size=0 repacks everything' '
)
'
+test_expect_success EXPENSIVE 'repack/expire with many packs' '
+ cp -r dup many &&
+ (
+ cd many &&
+
+ for i in $(test_seq 1 100)
+ do
+ test_commit extra$i &&
+ git maintenance run --task=loose-objects || return 1
+ done &&
+
+ git multi-pack-index write &&
+ git multi-pack-index repack &&
+ git multi-pack-index expire
+ )
+'
+
test_expect_success 'repack --batch-size=<large> repacks everything' '
(
cd dup2 &&
@@ -1083,7 +1100,10 @@ test_expect_success 'load reverse index when missing .idx, .pack' '
mv $idx.bak $idx &&
mv $pack $pack.bak &&
- git cat-file --batch-check="%(objectsize:disk)" <tip
+ git cat-file --batch-check="%(objectsize:disk)" <tip &&
+
+ test_must_fail git multi-pack-index write 2>err &&
+ test_grep "could not load pack" err
)
'
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index ebc696546b..83d1aadf9f 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -14,8 +14,6 @@ then
test_done
fi
-D=$(pwd)
-
test_expect_success setup '
echo >file original &&
git add file &&
@@ -51,46 +49,50 @@ test_expect_success "clone and setup child repos" '
'
test_expect_success "fetch test" '
- cd "$D" &&
echo >file updated by origin &&
git commit -a -m "updated by origin" &&
- cd two &&
- git fetch &&
- git rev-parse --verify refs/heads/one &&
- mine=$(git rev-parse refs/heads/one) &&
- his=$(cd ../one && git rev-parse refs/heads/main) &&
- test "z$mine" = "z$his"
+ (
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/heads/one &&
+ mine=$(git rev-parse refs/heads/one) &&
+ his=$(cd ../one && git rev-parse refs/heads/main) &&
+ test "z$mine" = "z$his"
+ )
'
test_expect_success "fetch test for-merge" '
- cd "$D" &&
- cd three &&
- git fetch &&
- git rev-parse --verify refs/heads/two &&
- git rev-parse --verify refs/heads/one &&
- main_in_two=$(cd ../two && git rev-parse main) &&
- one_in_two=$(cd ../two && git rev-parse one) &&
- {
- echo "$one_in_two " &&
- echo "$main_in_two not-for-merge"
- } >expected &&
- cut -f -2 .git/FETCH_HEAD >actual &&
- test_cmp expected actual'
+ (
+ cd three &&
+ git fetch &&
+ git rev-parse --verify refs/heads/two &&
+ git rev-parse --verify refs/heads/one &&
+ main_in_two=$(cd ../two && git rev-parse main) &&
+ one_in_two=$(cd ../two && git rev-parse one) &&
+ {
+ echo "$one_in_two " &&
+ echo "$main_in_two not-for-merge"
+ } >expected &&
+ cut -f -2 .git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+ )
+'
test_expect_success "fetch test remote HEAD" '
- cd "$D" &&
- cd two &&
- git fetch &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"'
+ (
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
test_expect_success "fetch test remote HEAD in bare repository" '
test_when_finished rm -rf barerepo &&
(
- cd "$D" &&
git init --bare barerepo &&
cd barerepo &&
git remote add upstream ../two &&
@@ -105,262 +107,235 @@ test_expect_success "fetch test remote HEAD in bare repository" '
test_expect_success "fetch test remote HEAD change" '
- cd "$D" &&
- cd two &&
- git switch -c other &&
- git push -u origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git fetch &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"'
-
-test_expect_success "fetch test followRemoteHEAD never" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git config set remote.origin.followRemoteHEAD "never" &&
- GIT_TRACE_PACKET=$PWD/trace.out git fetch &&
- # Confirm that we do not even ask for HEAD when we are
- # not going to act on it.
- test_grep ! "ref-prefix HEAD" trace.out &&
- test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
- )
-'
-
-test_expect_success "fetch test followRemoteHEAD warn no change" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
(
- cd "$D" &&
cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
+ git switch -c other &&
+ git push -u origin other &&
git rev-parse --verify refs/remotes/origin/HEAD &&
git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git fetch >output &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have ${SQ}other${SQ} locally." >expect &&
- test_cmp expect output &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
head=$(git rev-parse refs/remotes/origin/HEAD) &&
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"
)
'
+test_expect_success "fetch test followRemoteHEAD never" '
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ test_config -C two remote.origin.followRemoteHEAD "never" &&
+ GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
+ # Confirm that we do not even ask for HEAD when we are
+ # not going to act on it.
+ test_grep ! "ref-prefix HEAD" trace.out &&
+ test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ test_cmp expect output &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+'
+
test_expect_success "fetch test followRemoteHEAD warn create" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git rev-parse --verify refs/remotes/origin/main &&
- output=$(git fetch) &&
- test "z" = "z$output" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"
- )
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ output=$(git -C two fetch) &&
+ test "z" = "z$output" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn detached" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git update-ref refs/remotes/origin/HEAD HEAD &&
- HEAD=$(git log --pretty="%H") &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git fetch >output &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have a detached HEAD pointing to" \
- "${SQ}${HEAD}${SQ} locally." >expect &&
- test_cmp expect output
- )
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git -C two update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git -C two log --pretty="%H") &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have a detached HEAD pointing to" \
+ "${SQ}${HEAD}${SQ} locally." >expect &&
+ test_cmp expect output
'
test_expect_success "fetch test followRemoteHEAD warn quiet" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- output=$(git fetch --quiet) &&
- test "z" = "z$output" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ output=$(git -C two fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
- actual=$(git fetch) &&
- test "z" = "z$actual" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn-if-not-main" &&
+ actual=$(git -C two fetch) &&
+ test "z" = "z$actual" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
- git fetch >actual &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have ${SQ}other${SQ} locally." >expect &&
- test_cmp expect actual &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+ git -C two fetch >actual &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ test_cmp expect actual &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD always" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "always" &&
- git fetch &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "always" &&
+ git -C two fetch &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
'
test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git remote set-head origin other &&
- git config set remote.origin.followRemoteHEAD always &&
- git fetch origin refs/heads/main:refs/remotes/origin/main &&
- echo refs/remotes/origin/other >expect &&
- git symbolic-ref refs/remotes/origin/HEAD >actual &&
- test_cmp expect actual
- )
+ git -C two remote set-head origin other &&
+ test_config -C two remote.origin.followRemoteHEAD always &&
+ git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
+ echo refs/remotes/origin/other >expect &&
+ git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+ git -C two remote add -m does-not-exist custom-head ../one &&
+ test_config -C two remote.custom-head.followRemoteHEAD create &&
+ git -C two fetch custom-head &&
+ echo refs/remotes/custom-head/does-not-exist >expect &&
+ git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+ test_cmp expect actual
'
test_expect_success 'fetch --prune on its own works as expected' '
- cd "$D" &&
git clone . prune &&
- cd prune &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune origin &&
- test_must_fail git rev-parse origin/extrabranch
+ git fetch --prune origin &&
+ test_must_fail git rev-parse origin/extrabranch
+ )
'
test_expect_success 'fetch --prune with a branch name keeps branches' '
- cd "$D" &&
git clone . prune-branch &&
- cd prune-branch &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune-branch &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune origin main &&
- git rev-parse origin/extrabranch
+ git fetch --prune origin main &&
+ git rev-parse origin/extrabranch
+ )
'
test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
- cd "$D" &&
git clone . prune-namespace &&
- cd prune-namespace &&
+ (
+ cd prune-namespace &&
- git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
- git rev-parse origin/main
+ git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
+ git rev-parse origin/main
+ )
'
test_expect_success 'fetch --prune handles overlapping refspecs' '
- cd "$D" &&
git update-ref refs/pull/42/head main &&
git clone . prune-overlapping &&
- cd prune-overlapping &&
- git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+ (
+ cd prune-overlapping &&
+ git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
- git fetch --prune origin &&
- git rev-parse origin/main &&
- git rev-parse origin/pr/42 &&
+ git fetch --prune origin &&
+ git rev-parse origin/main &&
+ git rev-parse origin/pr/42 &&
- git config --unset-all remote.origin.fetch &&
- git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
- git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
+ git config --unset-all remote.origin.fetch &&
+ git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+ git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
- git fetch --prune origin &&
- git rev-parse origin/main &&
- git rev-parse origin/pr/42
+ git fetch --prune origin &&
+ git rev-parse origin/main &&
+ git rev-parse origin/pr/42
+ )
'
test_expect_success 'fetch --prune --tags prunes branches but not tags' '
- cd "$D" &&
git clone . prune-tags &&
- cd prune-tags &&
- git tag sometag main &&
- # Create what looks like a remote-tracking branch from an earlier
- # fetch that has since been deleted from the remote:
- git update-ref refs/remotes/origin/fake-remote main &&
-
- git fetch --prune --tags origin &&
- git rev-parse origin/main &&
- test_must_fail git rev-parse origin/fake-remote &&
- git rev-parse sometag
+ (
+ cd prune-tags &&
+ git tag sometag main &&
+ # Create what looks like a remote-tracking branch from an earlier
+ # fetch that has since been deleted from the remote:
+ git update-ref refs/remotes/origin/fake-remote main &&
+
+ git fetch --prune --tags origin &&
+ git rev-parse origin/main &&
+ test_must_fail git rev-parse origin/fake-remote &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --prune --tags with branch does not prune other things' '
- cd "$D" &&
git clone . prune-tags-branch &&
- cd prune-tags-branch &&
- git tag sometag main &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune-tags-branch &&
+ git tag sometag main &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune --tags origin main &&
- git rev-parse origin/extrabranch &&
- git rev-parse sometag
+ git fetch --prune --tags origin main &&
+ git rev-parse origin/extrabranch &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' '
- cd "$D" &&
git clone . prune-tags-refspec &&
- cd prune-tags-refspec &&
- git tag sometag main &&
- git update-ref refs/remotes/origin/foo/otherbranch main &&
- git update-ref refs/remotes/origin/extrabranch main &&
-
- git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
- test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
- git rev-parse origin/extrabranch &&
- git rev-parse sometag
+ (
+ cd prune-tags-refspec &&
+ git tag sometag main &&
+ git update-ref refs/remotes/origin/foo/otherbranch main &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+
+ git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
+ test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
+ git rev-parse origin/extrabranch &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --tags gets tags even without a configured remote' '
@@ -381,21 +356,21 @@ test_expect_success 'fetch --tags gets tags even without a configured remote' '
'
test_expect_success REFFILES 'fetch --prune fails to delete branches' '
- cd "$D" &&
git clone . prune-fail &&
- cd prune-fail &&
- git update-ref refs/remotes/origin/extrabranch main &&
- git pack-refs --all &&
- : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
- >.git/packed-refs.new &&
+ (
+ cd prune-fail &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+ git pack-refs --all &&
+ : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
+ >.git/packed-refs.new &&
- test_must_fail git fetch --prune origin
+ test_must_fail git fetch --prune origin
+ )
'
test_expect_success 'fetch --atomic works with a single branch' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-branch &&
oid=$(git rev-parse atomic-branch) &&
@@ -408,9 +383,8 @@ test_expect_success 'fetch --atomic works with a single branch' '
'
test_expect_success 'fetch --atomic works with multiple branches' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-branch-1 &&
git branch atomic-branch-2 &&
@@ -423,9 +397,8 @@ test_expect_success 'fetch --atomic works with multiple branches' '
'
test_expect_success 'fetch --atomic works with mixed branches and tags' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-mixed-branch &&
git tag atomic-mixed-tag &&
@@ -437,9 +410,8 @@ test_expect_success 'fetch --atomic works with mixed branches and tags' '
'
test_expect_success 'fetch --atomic prunes references' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git branch atomic-prune-delete &&
git clone . atomic &&
git branch --delete atomic-prune-delete &&
@@ -453,9 +425,8 @@ test_expect_success 'fetch --atomic prunes references' '
'
test_expect_success 'fetch --atomic aborts with non-fast-forward update' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git branch atomic-non-ff &&
git clone . atomic &&
git rev-parse HEAD >actual &&
@@ -472,9 +443,8 @@ test_expect_success 'fetch --atomic aborts with non-fast-forward update' '
'
test_expect_success 'fetch --atomic executes a single reference transaction only' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-hooks-1 &&
git branch atomic-hooks-2 &&
@@ -499,9 +469,8 @@ test_expect_success 'fetch --atomic executes a single reference transaction only
'
test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-hooks-abort-1 &&
git branch atomic-hooks-abort-2 &&
@@ -536,9 +505,8 @@ test_expect_success 'fetch --atomic aborts all reference updates if hook aborts'
'
test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
oid=$(git rev-parse HEAD) &&
@@ -574,8 +542,7 @@ test_expect_success REFFILES 'fetch --atomic fails transaction if reference lock
'
test_expect_success '--refmap="" ignores configured refspec' '
- cd "$TRASH_DIRECTORY" &&
- git clone "$D" remote-refs &&
+ git clone . remote-refs &&
git -C remote-refs rev-parse remotes/origin/main >old &&
git -C remote-refs update-ref refs/remotes/origin/main main~1 &&
git -C remote-refs rev-parse remotes/origin/main >new &&
@@ -599,34 +566,26 @@ test_expect_success '--refmap="" and --prune' '
test_expect_success 'fetch tags when there is no tags' '
- cd "$D" &&
-
- mkdir notags &&
- cd notags &&
- git init &&
-
- git fetch -t ..
+ git init notags &&
+ git -C notags fetch -t ..
'
test_expect_success 'fetch following tags' '
- cd "$D" &&
git tag -a -m "annotated" anno HEAD &&
git tag light HEAD &&
- mkdir four &&
- cd four &&
- git init &&
-
- git fetch .. :track &&
- git show-ref --verify refs/tags/anno &&
- git show-ref --verify refs/tags/light
-
+ git init four &&
+ (
+ cd four &&
+ git fetch .. :track &&
+ git show-ref --verify refs/tags/anno &&
+ git show-ref --verify refs/tags/light
+ )
'
test_expect_success 'fetch uses remote ref names to describe new refs' '
- cd "$D" &&
git init descriptive &&
(
cd descriptive &&
@@ -654,30 +613,20 @@ test_expect_success 'fetch uses remote ref names to describe new refs' '
test_expect_success 'fetch must not resolve short tag name' '
- cd "$D" &&
-
- mkdir five &&
- cd five &&
- git init &&
-
- test_must_fail git fetch .. anno:five
+ git init five &&
+ test_must_fail git -C five fetch .. anno:five
'
test_expect_success 'fetch can now resolve short remote name' '
- cd "$D" &&
git update-ref refs/remotes/six/HEAD HEAD &&
- mkdir six &&
- cd six &&
- git init &&
-
- git fetch .. six:six
+ git init six &&
+ git -C six fetch .. six:six
'
test_expect_success 'create bundle 1' '
- cd "$D" &&
echo >file updated again by origin &&
git commit -a -m "tip" &&
git bundle create --version=3 bundle1 main^..main
@@ -691,35 +640,36 @@ test_expect_success 'header of bundle looks right' '
OID refs/heads/main
EOF
- sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual &&
+ sed -e "s/$OID_REGEX/OID/g" -e "5q" bundle1 >actual &&
test_cmp expect actual
'
test_expect_success 'create bundle 2' '
- cd "$D" &&
git bundle create bundle2 main~2..main
'
test_expect_success 'unbundle 1' '
- cd "$D/bundle" &&
- git checkout -b some-branch &&
- test_must_fail git fetch "$D/bundle1" main:main
+ (
+ cd bundle &&
+ git checkout -b some-branch &&
+ test_must_fail git fetch bundle1 main:main
+ )
'
test_expect_success 'bundle 1 has only 3 files ' '
- cd "$D" &&
test_bundle_object_count bundle1 3
'
test_expect_success 'unbundle 2' '
- cd "$D/bundle" &&
- git fetch ../bundle2 main:main &&
- test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+ (
+ cd bundle &&
+ git fetch ../bundle2 main:main &&
+ test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+ )
'
test_expect_success 'bundle does not prerequisite objects' '
- cd "$D" &&
touch file2 &&
git add file2 &&
git commit -m add.file2 file2 &&
@@ -729,7 +679,6 @@ test_expect_success 'bundle does not prerequisite objects' '
test_expect_success 'bundle should be able to create a full history' '
- cd "$D" &&
git tag -a -m "1.0" v1.0 main &&
git bundle create bundle4 v1.0
@@ -783,7 +732,6 @@ test_expect_success 'quoting of a strangely named repo' '
test_expect_success 'bundle should record HEAD correctly' '
- cd "$D" &&
git bundle create bundle5 HEAD main &&
git bundle list-heads bundle5 >actual &&
for h in HEAD refs/heads/main
@@ -803,7 +751,6 @@ test_expect_success 'mark initial state of origin/main' '
test_expect_success 'explicit fetch should update tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -818,7 +765,6 @@ test_expect_success 'explicit fetch should update tracking' '
test_expect_success 'explicit pull should update tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -832,7 +778,6 @@ test_expect_success 'explicit pull should update tracking' '
'
test_expect_success 'explicit --refmap is allowed only with command-line refspec' '
- cd "$D" &&
(
cd three &&
test_must_fail git fetch --refmap="*:refs/remotes/none/*"
@@ -840,7 +785,6 @@ test_expect_success 'explicit --refmap is allowed only with command-line refspec
'
test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -855,7 +799,6 @@ test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
'
test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -870,7 +813,6 @@ test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
test_expect_success 'configured fetch updates tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -884,7 +826,6 @@ test_expect_success 'configured fetch updates tracking' '
'
test_expect_success 'non-matching refspecs do not confuse tracking update' '
- cd "$D" &&
git update-ref refs/odd/location HEAD &&
(
cd three &&
@@ -901,14 +842,12 @@ test_expect_success 'non-matching refspecs do not confuse tracking update' '
test_expect_success 'pushing nonexistent branch by mistake should not segv' '
- cd "$D" &&
test_must_fail git push seven no:no
'
test_expect_success 'auto tag following fetches minimum' '
- cd "$D" &&
git clone .git follow &&
git checkout HEAD^0 &&
(
@@ -1307,7 +1246,7 @@ test_expect_success 'fetch --prune prints the remotes url' '
cd only-prunes &&
git fetch --prune origin 2>&1 | head -n1 >../actual
) &&
- echo "From ${D}/." >expect &&
+ echo "From $(pwd)/." >expect &&
test_cmp expect actual
'
@@ -1357,14 +1296,14 @@ test_expect_success 'fetching with auto-gc does not lock up' '
echo "$*" &&
false
EOF
- git clone "file://$D" auto-gc &&
+ git clone "file://$PWD" auto-gc &&
test_commit test2 &&
(
cd auto-gc &&
git config fetch.unpackLimit 1 &&
git config gc.autoPackLimit 1 &&
git config gc.autoDetach false &&
- GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
+ GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 &&
test_grep "Auto packing the repository" fetch.out &&
! grep "Should I try again" fetch.out
)
diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh
index 558eedf25a..d40292cfb7 100755
--- a/t/t5530-upload-pack-error.sh
+++ b/t/t5530-upload-pack-error.sh
@@ -4,8 +4,6 @@ test_description='errors in upload-pack'
. ./test-lib.sh
-D=$(pwd)
-
corrupt_repo () {
object_sha1=$(git rev-parse "$1") &&
ob=$(expr "$object_sha1" : "\(..\)") &&
@@ -21,11 +19,7 @@ test_expect_success 'setup and corrupt repository' '
test_tick &&
echo changed >file &&
git commit -a -m changed &&
- corrupt_repo HEAD:file
-
-'
-
-test_expect_success 'fsck fails' '
+ corrupt_repo HEAD:file &&
test_must_fail git fsck
'
@@ -40,17 +34,12 @@ test_expect_success 'upload-pack fails due to error in pack-objects packing' '
'
test_expect_success 'corrupt repo differently' '
-
git hash-object -w file &&
- corrupt_repo HEAD^^{tree}
-
-'
-
-test_expect_success 'fsck fails' '
+ corrupt_repo HEAD^^{tree} &&
test_must_fail git fsck
'
-test_expect_success 'upload-pack fails due to error in rev-list' '
+test_expect_success 'upload-pack fails due to error in rev-list' '
printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \
$(($hexsz + 10)) $(git rev-parse HEAD) \
$(($hexsz + 12)) $(git rev-parse HEAD^) >input &&
@@ -59,7 +48,6 @@ test_expect_success 'upload-pack fails due to error in rev-list' '
'
test_expect_success 'upload-pack fails due to bad want (no object)' '
-
printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
$(($hexsz + 29)) $(test_oid deadbeef) >input &&
test_must_fail git upload-pack . <input >output 2>output.err &&
@@ -69,7 +57,6 @@ test_expect_success 'upload-pack fails due to bad want (no object)' '
'
test_expect_success 'upload-pack fails due to bad want (not tip)' '
-
oid=$(echo an object we have | git hash-object -w --stdin) &&
printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \
$(($hexsz + 29)) "$oid" >input &&
@@ -80,7 +67,6 @@ test_expect_success 'upload-pack fails due to bad want (not tip)' '
'
test_expect_success 'upload-pack fails due to error in pack-objects enumeration' '
-
printf "%04xwant %s\n00000009done\n0000" \
$((hexsz + 10)) $(git rev-parse HEAD) >input &&
test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
@@ -105,18 +91,48 @@ test_expect_success 'upload-pack tolerates EOF just after stateless client wants
test_cmp expect actual
'
-test_expect_success 'create empty repository' '
-
- mkdir foo &&
- cd foo &&
- git init
-
-'
-
test_expect_success 'fetch fails' '
+ git init foo &&
+ test_must_fail git -C foo fetch .. main
+'
- test_must_fail git fetch .. main
+test_expect_success 'upload-pack ACKs repeated non-commit objects repeatedly (protocol v0)' '
+ commit_id=$(git rev-parse HEAD) &&
+ tree_id=$(git rev-parse HEAD^{tree}) &&
+ test-tool pkt-line pack >request <<-EOF &&
+ want $commit_id
+ 0000
+ have $tree_id
+ have $tree_id
+ 0000
+ EOF
+ git upload-pack --stateless-rpc . <request >actual &&
+ depacketize <actual >actual.raw &&
+ grep ^ACK actual.raw >actual.acks &&
+ cat >expect <<-EOF &&
+ ACK $tree_id
+ ACK $tree_id
+ EOF
+ test_cmp expect actual.acks
+'
+test_expect_success 'upload-pack ACKs repeated non-commit objects once only (protocol v2)' '
+ commit_id=$(git rev-parse HEAD) &&
+ tree_id=$(git rev-parse HEAD^{tree}) &&
+ test-tool pkt-line pack >request <<-EOF &&
+ command=fetch
+ object-format=$(test_oid algo)
+ 0001
+ want $commit_id
+ have $tree_id
+ have $tree_id
+ 0000
+ EOF
+ GIT_PROTOCOL=version=2 git upload-pack . <request >actual &&
+ depacketize <actual >actual.raw &&
+ grep ^ACK actual.raw >actual.acks &&
+ echo "ACK $tree_id" >expect &&
+ test_cmp expect actual.acks
'
test_done
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh
index b27e481f95..c3903faf2d 100755
--- a/t/t5564-http-proxy.sh
+++ b/t/t5564-http-proxy.sh
@@ -72,7 +72,9 @@ test_expect_success SOCKS_PROXY 'clone via Unix socket' '
test_when_finished "rm -rf clone" &&
test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && {
{
- GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
+ GIT_TRACE_CURL=$PWD/trace \
+ GIT_TRACE_CURL_COMPONENTS=socks \
+ git clone "$HTTPD_URL/smart/repo.git" clone 2>err &&
grep -i "SOCKS4 request granted" trace
} ||
old_libcurl_error err
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 256ccaefb7..2c70cc561a 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -409,6 +409,36 @@ test_expect_success 'describe tag object' '
test_grep "fatal: test-blob-1 is neither a commit nor blob" actual
'
+test_expect_success 'describe an unreachable blob' '
+ blob=$(echo not-found-anywhere | git hash-object -w --stdin) &&
+ test_must_fail git describe $blob 2>actual &&
+ test_grep "blob .$blob. not reachable from HEAD" actual
+'
+
+test_expect_success 'describe blob on an unborn branch' '
+ oldbranch=$(git symbolic-ref HEAD) &&
+ test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+ git symbolic-ref HEAD refs/heads/does-not-exist &&
+ test_must_fail git describe test-blob 2>actual &&
+ test_grep "cannot search .* on an unborn branch" actual
+'
+
+# This test creates a repository state that we generally try to disallow: HEAD
+# is pointing to an object that is not a commit. The ref update code forbids
+# non-commit writes directly to HEAD or to any branch in refs/heads/. But we
+# can use the loophole of pointing HEAD to another non-branch ref (something we
+# should forbid, but don't for historical reasons).
+#
+# Do not take this test as an endorsement of the loophole! If we ever tighten
+# it, it is reasonable to just drop this test entirely.
+test_expect_success 'describe blob on a non-commit HEAD' '
+ oldbranch=$(git symbolic-ref HEAD) &&
+ test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+ git symbolic-ref HEAD refs/tags/test-blob &&
+ test_must_fail git describe test-blob 2>actual &&
+ test_grep "blob .* not reachable from HEAD" actual
+'
+
test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
i=1 &&
while test $i -lt 8000
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index b37e2018a7..05f6da4ad9 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -956,13 +956,39 @@ test_expect_success 'commit --status with custom comment character' '
test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
'
-test_expect_success 'switch core.commentchar' '
+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '
test_commit "#foo" foo &&
- GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
+ cat >config-include <<-\EOF &&
+ [core]
+ commentString=:
+ commentString=%
+ commentChar=auto
+ EOF
+ test_when_finished "rm config-include" &&
+ test_config include.path "$(pwd)/config-include" &&
+ test_config core.commentChar ! &&
+ GIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&
+ sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
+ cat >expect <<-EOF &&
+ Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
+
+ To use the default comment string (#) please run
+
+ git config unset core.commentChar
+ git config unset --file ~/config-include --all core.commentString
+ git config unset --file ~/config-include core.commentChar
+
+ To set a custom comment string please run
+
+ git config set --file ~/config-include core.commentChar <comment string>
+
+ where ${SQ}<comment string>${SQ} is the string you wish to use.
+ EOF
+ test_cmp expect actual &&
test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
'
-test_expect_success 'switch core.commentchar but out of options' '
+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '
cat >text <<\EOF &&
# 1
; 2
@@ -982,4 +1008,24 @@ EOF
)
'
+test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '
+ test_config core.commentChar auto &&
+ test_must_fail git rev-parse --git-dir 2>err &&
+ sed -n "s/^hint: *\$//p; s/^hint: //p; s/^fatal: //p" err >actual &&
+ cat >expect <<-EOF &&
+ Support for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0
+
+ To use the default comment string (#) please run
+
+ git config unset core.commentChar
+
+ To set a custom comment string please run
+
+ git config set core.commentChar <comment string>
+
+ where ${SQ}<comment string>${SQ} is the string you wish to use.
+ EOF
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 611755cc13..73b78bdd88 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -838,4 +838,67 @@ test_expect_success '-n overrides repack.updateServerInfo=true' '
test_server_info_missing
'
+test_expect_success 'pending objects are repacked appropriately' '
+ test_when_finished rm -rf pending &&
+ git init pending &&
+
+ (
+ cd pending &&
+
+ # Commit file, a/b/c and never change them.
+ mkdir -p a/b &&
+ echo singleton >file &&
+ echo stuff >a/b/c &&
+ echo more >a/d &&
+ git add file a &&
+ git commit -m "single blobs" &&
+
+ # Files a/d and a/e will not be singletons.
+ echo d >a/d &&
+ echo e >a/e &&
+ git add a &&
+ git commit -m "more blobs" &&
+
+ # This use of a sparse index helps to force
+ # test that the cache-tree is walked, too.
+ git sparse-checkout set --sparse-index a x &&
+
+ # Create staged changes:
+ # * a/e now has multiple versions.
+ # * a/i now has only one version.
+ echo f >a/d &&
+ echo h >a/e &&
+ echo i >a/i &&
+ git add a &&
+
+ # Stage and unstage a change to make use of
+ # resolve-undo cache and how that impacts fsck.
+ mkdir x &&
+ echo y >x/y &&
+ git add x &&
+ xy=$(git rev-parse :x/y) &&
+ git rm --cached x/y &&
+
+ # The blob for x/y must persist through repacks,
+ # but fsck currently ignores the REUC extension
+ # for finding links to the blob.
+ cat >expect <<-EOF &&
+ dangling blob $xy
+ EOF
+
+ # Bring the loose objects into a packfile to avoid
+ # leftovers in next test. Without this, the loose
+ # objects persist and the test succeeds for other
+ # reasons.
+ git repack -adf &&
+ git fsck >out &&
+ test_cmp expect out &&
+
+ # Test path walk version with pack.useSparse.
+ git -c pack.useSparse=true repack -adf --path-walk &&
+ git fsck >out &&
+ test_cmp expect out
+ )
+'
+
test_done
diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh
new file mode 100755
index 0000000000..5eb4cef035
--- /dev/null
+++ b/t/t8020-last-modified.sh
@@ -0,0 +1,210 @@
+#!/bin/sh
+
+test_description='last-modified tests'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit 1 file &&
+ mkdir a &&
+ test_commit 2 a/file &&
+ mkdir a/b &&
+ test_commit 3 a/b/file
+'
+
+test_expect_success 'cannot run last-modified on two trees' '
+ test_must_fail git last-modified HEAD HEAD~1
+'
+
+check_last_modified() {
+ local indir= &&
+ while test $# != 0
+ do
+ case "$1" in
+ -C)
+ indir="$2"
+ shift
+ ;;
+ *)
+ break
+ ;;
+ esac &&
+ shift
+ done &&
+
+ cat >expect &&
+ test_when_finished "rm -f tmp.*" &&
+ git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 &&
+ git name-rev --annotate-stdin --name-only --tags \
+ <tmp.1 >tmp.2 &&
+ tr '\t' ' ' <tmp.2 >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'last-modified non-recursive' '
+ check_last_modified <<-\EOF
+ 3 a
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified recursive' '
+ check_last_modified -r <<-\EOF
+ 3 a/b/file
+ 2 a/file
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified recursive with show-trees' '
+ check_last_modified -r -t <<-\EOF
+ 3 a
+ 3 a/b
+ 3 a/b/file
+ 2 a/file
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified non-recursive with show-trees' '
+ check_last_modified -t <<-\EOF
+ 3 a
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified subdir' '
+ check_last_modified a <<-\EOF
+ 3 a
+ EOF
+'
+
+test_expect_success 'last-modified subdir recursive' '
+ check_last_modified -r a <<-\EOF
+ 3 a/b/file
+ 2 a/file
+ EOF
+'
+
+test_expect_success 'last-modified from non-HEAD commit' '
+ check_last_modified HEAD^ <<-\EOF
+ 2 a
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified from subdir defaults to root' '
+ check_last_modified -C a <<-\EOF
+ 3 a
+ 1 file
+ EOF
+'
+
+test_expect_success 'last-modified from subdir uses relative pathspecs' '
+ check_last_modified -C a -r b <<-\EOF
+ 3 a/b/file
+ EOF
+'
+
+test_expect_success 'limit last-modified traversal by count' '
+ check_last_modified -1 <<-\EOF
+ 3 a
+ ^2 file
+ EOF
+'
+
+test_expect_success 'limit last-modified traversal by commit' '
+ check_last_modified HEAD~2..HEAD <<-\EOF
+ 3 a
+ ^1 file
+ EOF
+'
+
+test_expect_success 'only last-modified files in the current tree' '
+ git rm -rf a &&
+ git commit -m "remove a" &&
+ check_last_modified <<-\EOF
+ 1 file
+ EOF
+'
+
+test_expect_success 'cross merge boundaries in blaming' '
+ git checkout HEAD^0 &&
+ git rm -rf . &&
+ test_commit m1 &&
+ git checkout HEAD^ &&
+ git rm -rf . &&
+ test_commit m2 &&
+ git merge m1 &&
+ check_last_modified <<-\EOF
+ m2 m2.t
+ m1 m1.t
+ EOF
+'
+
+test_expect_success 'last-modified merge for resolved conflicts' '
+ git checkout HEAD^0 &&
+ git rm -rf . &&
+ test_commit c1 conflict &&
+ git checkout HEAD^ &&
+ git rm -rf . &&
+ test_commit c2 conflict &&
+ test_must_fail git merge c1 &&
+ test_commit resolved conflict &&
+ check_last_modified conflict <<-\EOF
+ resolved conflict
+ EOF
+'
+
+
+# Consider `file` with this content through history:
+#
+# A---B---B-------B---B
+# \ /
+# C---D
+test_expect_success 'last-modified merge ignores content from branch' '
+ git checkout HEAD^0 &&
+ git rm -rf . &&
+ test_commit a1 file A &&
+ test_commit a2 file B &&
+ test_commit a3 file C &&
+ test_commit a4 file D &&
+ git checkout a2 &&
+ git merge --no-commit --no-ff a4 &&
+ git checkout a2 -- file &&
+ git merge --continue &&
+ check_last_modified <<-\EOF
+ a2 file
+ EOF
+'
+
+# Consider `file` with this content through history:
+#
+# A---B---B---C---D---B---B
+# \ /
+# B-------B
+test_expect_success 'last-modified merge undoes changes' '
+ git checkout HEAD^0 &&
+ git rm -rf . &&
+ test_commit b1 file A &&
+ test_commit b2 file B &&
+ test_commit b3 file C &&
+ test_commit b4 file D &&
+ git checkout b2 &&
+ test_commit b5 file2 2 &&
+ git checkout b4 &&
+ git merge --no-commit --no-ff b5 &&
+ git checkout b2 -- file &&
+ git merge --continue &&
+ check_last_modified <<-\EOF
+ b5 file2
+ b2 file
+ EOF
+'
+
+test_expect_success 'last-modified complains about unknown arguments' '
+ test_must_fail git last-modified --foo 2>err &&
+ grep "unknown last-modified argument: --foo" err
+'
+
+test_done
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c
index e4ea57138e..a8b91812e8 100644
--- a/t/unit-tests/u-reftable-stack.c
+++ b/t/unit-tests/u-reftable-stack.c
@@ -128,7 +128,7 @@ static void write_n_ref_tables(struct reftable_stack *st,
cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
cl_assert_equal_i(reftable_stack_add(st,
- &write_test_ref, &ref), 0);
+ &write_test_ref, &ref, 0), 0);
}
st->opts.disable_auto_compact = disable_auto_compact;
@@ -171,7 +171,7 @@ void test_reftable_stack__add_one(void)
err = reftable_new_stack(&st, dir, &opts);
cl_assert(!err);
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
err = reftable_stack_read_ref(st, ref.refname, &dest);
@@ -235,12 +235,12 @@ void test_reftable_stack__uptodate(void)
cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st1, write_test_ref,
- &ref1), 0);
+ &ref1, 0), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
- &ref2), REFTABLE_OUTDATED_ERROR);
+ &ref2, 0), REFTABLE_OUTDATED_ERROR);
cl_assert_equal_i(reftable_stack_reload(st2), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
- &ref2), 0);
+ &ref2, 0), 0);
reftable_stack_destroy(st1);
reftable_stack_destroy(st2);
clear_dir(dir);
@@ -406,7 +406,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref), 0);
+ &ref, 0), 0);
cl_assert_equal_i(st->merged->tables_len, 1);
cl_assert_equal_i(st->stats.attempts, 0);
cl_assert_equal_i(st->stats.failures, 0);
@@ -424,7 +424,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
write_file_buf(table_path.buf, "", 0);
ref.update_index = 2;
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
cl_assert_equal_i(st->merged->tables_len, 2);
cl_assert_equal_i(st->stats.attempts, 1);
@@ -460,9 +460,9 @@ void test_reftable_stack__update_index_check(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref1), 0);
+ &ref1, 0), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref2), REFTABLE_API_ERROR);
+ &ref2, 0), REFTABLE_API_ERROR);
reftable_stack_destroy(st);
clear_dir(dir);
}
@@ -477,7 +477,7 @@ void test_reftable_stack__lock_failure(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
cl_assert_equal_i(reftable_stack_add(st, write_error,
- &i), i);
+ &i, 0), i);
reftable_stack_destroy(st);
clear_dir(dir);
@@ -521,7 +521,7 @@ void test_reftable_stack__add(void)
for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &refs[i]), 0);
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@@ -529,7 +529,7 @@ void test_reftable_stack__add(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -612,8 +612,8 @@ void test_reftable_stack__iterator(void)
}
for (i = 0; i < N; i++)
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_ref, &refs[i]), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@@ -621,8 +621,8 @@ void test_reftable_stack__iterator(void)
.update_index = reftable_stack_next_update_index(st),
};
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_log, &arg), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+ &arg, 0), 0);
}
reftable_stack_init_ref_iterator(st, &it);
@@ -697,11 +697,11 @@ void test_reftable_stack__log_normalize(void)
input.value.update.message = (char *) "one\ntwo";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), REFTABLE_API_ERROR);
+ &arg, 0), REFTABLE_API_ERROR);
input.value.update.message = (char *) "one";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "one\n");
@@ -709,7 +709,7 @@ void test_reftable_stack__log_normalize(void)
input.value.update.message = (char *) "two\n";
arg.update_index = 2;
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "two\n");
@@ -759,15 +759,16 @@ void test_reftable_stack__tombstone(void)
}
}
for (i = 0; i < N; i++)
- cl_assert_equal_i(reftable_stack_add(st, write_test_ref, &refs[i]), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
.log = &logs[i],
.update_index = reftable_stack_next_update_index(st),
};
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_log, &arg), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
@@ -815,7 +816,7 @@ void test_reftable_stack__hash_id(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref), 0);
+ &ref, 0), 0);
/* can't read it with the wrong hash ID. */
cl_assert_equal_i(reftable_new_stack(&st32, dir,
@@ -884,7 +885,7 @@ void test_reftable_stack__reflog_expire(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -924,7 +925,7 @@ void test_reftable_stack__empty_add(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_nothing,
- NULL), 0);
+ NULL, 0), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
clear_dir(dir);
reftable_stack_destroy(st);
@@ -963,7 +964,7 @@ void test_reftable_stack__auto_compaction(void)
};
snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
err = reftable_stack_auto_compact(st);
@@ -999,7 +1000,7 @@ void test_reftable_stack__auto_compaction_factor(void)
};
xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
- err = reftable_stack_add(st, &write_test_ref, &ref);
+ err = reftable_stack_add(st, &write_test_ref, &ref, 0);
cl_assert(!err);
cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));
@@ -1078,8 +1079,8 @@ void test_reftable_stack__add_performs_auto_compaction(void)
snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
ref.refname = buf;
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_ref, &ref), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &ref, 0), 0);
/*
* The stack length should grow continuously for all runs where
diff --git a/upload-pack.c b/upload-pack.c
index 91fcdcad9b..f78fabc1e1 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -476,20 +476,17 @@ static void create_pack_file(struct upload_pack_data *pack_data,
static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid)
{
- int we_knew_they_have = 0;
struct object *o = parse_object_with_flags(the_repository, oid,
PARSE_OBJECT_SKIP_HASH_CHECK |
PARSE_OBJECT_DISCARD_TREE);
if (!o)
die("oops (%s)", oid_to_hex(oid));
+
if (o->type == OBJ_COMMIT) {
struct commit_list *parents;
struct commit *commit = (struct commit *)o;
- if (o->flags & THEY_HAVE)
- we_knew_they_have = 1;
- else
- o->flags |= THEY_HAVE;
+
if (!data->oldest_have || (commit->date < data->oldest_have))
data->oldest_have = commit->date;
for (parents = commit->parents;
@@ -497,11 +494,13 @@ static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid
parents = parents->next)
parents->item->object.flags |= THEY_HAVE;
}
- if (!we_knew_they_have) {
- add_object_array(o, NULL, &data->have_obj);
- return 1;
- }
- return 0;
+
+ if (o->flags & THEY_HAVE)
+ return 0;
+ o->flags |= THEY_HAVE;
+
+ add_object_array(o, NULL, &data->have_obj);
+ return 1;
}
static int got_oid(struct upload_pack_data *data,
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 444a108f87..78d1cf74b1 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -249,7 +249,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
return 1;
}
-static unsigned long xdl_hash_record_with_whitespace(char const **data,
+unsigned long xdl_hash_record_with_whitespace(char const **data,
char const *top, long flags) {
unsigned long ha = 5381;
char const *ptr = *data;
@@ -294,19 +294,67 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
return ha;
}
-unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
- unsigned long ha = 5381;
+/*
+ * Compiler reassociation barrier: pretend to modify X and Y to disallow
+ * changing evaluation order with respect to following uses of X and Y.
+ */
+#ifdef __GNUC__
+#define REASSOC_FENCE(x, y) __asm__("" : "+r"(x), "+r"(y))
+#else
+#define REASSOC_FENCE(x, y)
+#endif
+
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top) {
+ unsigned long ha = 5381, c0, c1;
char const *ptr = *data;
-
- if (flags & XDF_WHITESPACE_FLAGS)
- return xdl_hash_record_with_whitespace(data, top, flags);
-
+#if 0
+ /*
+ * The baseline form of the optimized loop below. This is the djb2
+ * hash (the above function uses a variant with XOR instead of ADD).
+ */
for (; ptr < top && *ptr != '\n'; ptr++) {
ha += (ha << 5);
- ha ^= (unsigned long) *ptr;
+ ha += (unsigned long) *ptr;
}
*data = ptr < top ? ptr + 1: ptr;
-
+#else
+ /* Process two characters per iteration. */
+ if (top - ptr >= 2) do {
+ if ((c0 = ptr[0]) == '\n') {
+ *data = ptr + 1;
+ return ha;
+ }
+ if ((c1 = ptr[1]) == '\n') {
+ *data = ptr + 2;
+ c0 += ha;
+ REASSOC_FENCE(c0, ha);
+ ha = ha * 32 + c0;
+ return ha;
+ }
+ /*
+ * Combine characters C0 and C1 into the hash HA. We have
+ * HA = (HA * 33 + C0) * 33 + C1, and we want to ensure
+ * that dependency chain over HA is just one multiplication
+ * and one addition, i.e. we want to evaluate this as
+ * HA = HA * 33 * 33 + (C0 * 33 + C1), and likewise prefer
+ * (C0 * 32 + (C0 + C1)) for the expression in parenthesis.
+ */
+ ha *= 33 * 33;
+ c1 += c0;
+ REASSOC_FENCE(c1, c0);
+ c1 += c0 * 32;
+ REASSOC_FENCE(c1, ha);
+ ha += c1;
+
+ ptr += 2;
+ } while (ptr < top - 1);
+ *data = top;
+ if (ptr < top && (c0 = ptr[0]) != '\n') {
+ c0 += ha;
+ REASSOC_FENCE(c0, ha);
+ ha = ha * 32 + c0;
+ }
+#endif
return ha;
}
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
index fd0bba94e8..13f6831047 100644
--- a/xdiff/xutils.h
+++ b/xdiff/xutils.h
@@ -34,7 +34,15 @@ void *xdl_cha_alloc(chastore_t *cha);
long xdl_guess_lines(mmfile_t *mf, long sample);
int xdl_blankline(const char *line, long size, long flags);
int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
-unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned long xdl_hash_record_verbatim(char const **data, char const *top);
+unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags);
+static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+ if (flags & XDF_WHITESPACE_FLAGS)
+ return xdl_hash_record_with_whitespace(data, top, flags);
+ else
+ return xdl_hash_record_verbatim(data, top);
+}
unsigned int xdl_hashbits(unsigned int size);
int xdl_num_out(char *out, long val);
int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,