summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml13
-rw-r--r--Documentation/RelNotes/2.13.0.txt103
-rw-r--r--Documentation/SubmittingPatches12
-rw-r--r--Documentation/git-archimport.txt2
-rw-r--r--Documentation/git-bisect-lk2009.txt14
-rw-r--r--Documentation/git-format-patch.txt2
-rw-r--r--Documentation/git-pull.txt5
-rw-r--r--Documentation/git-push.txt41
-rw-r--r--Documentation/git-rebase.txt5
-rw-r--r--Documentation/git-receive-pack.txt29
-rw-r--r--Documentation/git-tools.txt2
-rw-r--r--Documentation/git-worktree.txt7
-rw-r--r--Documentation/gitattributes.txt6
-rw-r--r--Documentation/gitcore-tutorial.txt2
-rw-r--r--Documentation/githooks.txt9
-rw-r--r--Documentation/gitmodules.txt35
-rw-r--r--Documentation/gitweb.conf.txt4
-rw-r--r--Documentation/howto/rebuild-from-update-hook.txt4
-rw-r--r--Documentation/technical/pack-protocol.txt7
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--Makefile2
-rw-r--r--bisect.c3
-rw-r--r--builtin/am.c57
-rw-r--r--builtin/branch.c6
-rw-r--r--builtin/commit.c8
-rw-r--r--builtin/config.c28
-rw-r--r--builtin/fsck.c1
-rw-r--r--builtin/gc.c12
-rw-r--r--builtin/ls-files.c9
-rw-r--r--builtin/pull.c4
-rw-r--r--builtin/receive-pack.c5
-rw-r--r--builtin/replace.c1
-rw-r--r--builtin/submodule--helper.c6
-rw-r--r--builtin/tag.c12
-rw-r--r--builtin/worktree.c21
-rw-r--r--cache.h14
-rwxr-xr-xci/run-windows-build.sh2
-rw-r--r--config.c89
-rw-r--r--connect.c4
-rw-r--r--contrib/completion/git-completion.bash12
-rw-r--r--credential-cache.c2
-rw-r--r--credential-store.c2
-rw-r--r--daemon.c4
-rw-r--r--fast-import.c2
-rw-r--r--fetch-pack.c6
-rw-r--r--git-compat-util.h6
-rwxr-xr-xgit-rebase.sh3
-rwxr-xr-xgit-submodule.sh14
-rw-r--r--http.c19
-rw-r--r--http.h2
-rw-r--r--ident.c4
-rw-r--r--notes-merge.c4
-rw-r--r--path.c11
-rw-r--r--pathspec.c2
-rw-r--r--prio-queue.c2
-rw-r--r--read-cache.c151
-rw-r--r--refs.c117
-rw-r--r--refs.h2
-rw-r--r--refs/files-backend.c1249
-rw-r--r--refs/ref-cache.c523
-rw-r--r--refs/ref-cache.h267
-rw-r--r--refs/refs-internal.h22
-rw-r--r--remote-curl.c12
-rw-r--r--sequencer.c34
-rw-r--r--server-info.c8
-rw-r--r--sha1_file.c3
-rw-r--r--string-list.c5
-rw-r--r--submodule.c3
-rw-r--r--t/helper/.gitignore1
-rw-r--r--t/helper/test-strcmp-offset.c22
-rwxr-xr-xt/perf/aggregate.perl3
-rwxr-xr-xt/perf/p0000-perf-lib-sanity.sh2
-rwxr-xr-xt/perf/p0005-status.sh49
-rwxr-xr-xt/perf/p0006-read-tree-checkout.sh67
-rw-r--r--t/perf/repos/.gitignore1
-rwxr-xr-xt/perf/repos/inflate-repo.sh85
-rwxr-xr-xt/perf/repos/many-files.sh110
-rwxr-xr-xt/t0025-crlf-auto.sh26
-rwxr-xr-xt/t0065-strcmp-offset.sh21
-rwxr-xr-xt/t1305-config-include.sh65
-rwxr-xr-xt/t1309-early-config.sh18
-rwxr-xr-xt/t1400-update-ref.sh335
-rwxr-xr-xt/t1450-fsck.sh13
-rwxr-xr-xt/t2025-worktree-add.sh6
-rwxr-xr-xt/t2027-worktree-list.sh35
-rwxr-xr-xt/t3007-ls-files-recurse-submodules.sh12
-rwxr-xr-xt/t3428-rebase-signoff.sh46
-rwxr-xr-xt/t3429-rebase-edit-todo.sh14
-rwxr-xr-xt/t3701-add-interactive.sh22
-rwxr-xr-xt/t5533-push-cas.sh29
-rwxr-xr-xt/t5547-push-quarantine.sh11
-rwxr-xr-xt/t7400-submodule-basic.sh22
-rwxr-xr-xt/t9902-completion.sh34
-rw-r--r--unpack-trees.c38
-rw-r--r--wrapper.c13
95 files changed, 2685 insertions, 1494 deletions
diff --git a/.travis.yml b/.travis.yml
index c757a11..1b32c98 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -61,6 +61,7 @@ matrix:
- >
docker run
--interactive
+ --env DEVELOPER
--env DEFAULT_TEST_TARGET
--env GIT_PROVE_OPTS
--env GIT_TEST_OPTS
@@ -71,6 +72,18 @@ matrix:
# Use the following command to debug the docker build locally:
# $ docker run -itv "${PWD}:/usr/src/git" --entrypoint /bin/bash daald/ubuntu32:xenial
# root@container:/# /usr/src/git/ci/run-linux32-build.sh
+ - env: Static Analysis
+ os: linux
+ compiler:
+ addons:
+ apt:
+ packages:
+ - coccinelle
+ before_install:
+ script:
+ # "before_script" that builds Git is inherited from base job
+ - make coccicheck
+ after_failure:
- env: Documentation
os: linux
compiler: clang
diff --git a/Documentation/RelNotes/2.13.0.txt b/Documentation/RelNotes/2.13.0.txt
index ba19a3b..b76a45d 100644
--- a/Documentation/RelNotes/2.13.0.txt
+++ b/Documentation/RelNotes/2.13.0.txt
@@ -182,6 +182,25 @@ UI, Workflows & Features
to the working tree of the submodule but not the commit that is
checked out.
+ * Allow the http.postbuffer configuration variable to be set to a
+ size that can be expressed in size_t, which can be larger than
+ ulong on some platforms.
+
+ * "git rebase" learns "--signoff" option.
+
+ * The completion script (in contrib/) learned to complete "git push
+ --delete b<TAB>" to complete branch name to be deleted.
+
+ * "git worktree add --lock" allows to lock a worktree immediately
+ after it's created. This helps prevent a race between "git worktree
+ add; git worktree lock" and "git worktree prune".
+
+ * Completion for "git checkout <branch>" that auto-creates the branch
+ out of a remote tracking branch can now be disabled, as this
+ completion often gets in the way when completing to checkout an
+ existing local branch that happens to share the same prefix with
+ bunch of remote tracking branches.
+
Performance, Internal Implementation, Development Support etc.
@@ -242,8 +261,6 @@ Performance, Internal Implementation, Development Support etc.
older one and the newer one interoperate happily has now become
possible.
- * "uchar [40]" to "struct object_id" conversion continues.
-
* "git tag --contains" used to (ab)use the object bits to keep track
of the state of object reachability without clearing them after
use; this has been cleaned up and made to use the newer commit-slab
@@ -278,13 +295,44 @@ Performance, Internal Implementation, Development Support etc.
* Define a new task in .travis.yml that triggers a test session on
Windows run elsewhere.
- * Conversion from unsigned char [40] to struct object_id continues.
+ * Conversion from uchar[20] to struct object_id continues.
* The "submodule" specific field in the ref_store structure is
replaced with a more generic "gitdir" that can later be used also
when dealing with ref_store that represents the set of refs visible
from the other worktrees.
+ * The string-list API used a custom reallocation strategy that was
+ very inefficient, instead of using the usual ALLOC_GROW() macro,
+ which has been fixed.
+ (merge 950a234cbd jh/string-list-micro-optim later to maint).
+
+ * In a 2- and 3-way merge of trees, more than one source trees often
+ end up sharing an identical subtree; optimize by not reading the
+ same tree multiple times in such a case.
+ (merge d12a8cf0af jh/unpack-trees-micro-optim later to maint).
+
+ * The index file has a trailing SHA-1 checksum to detect file
+ corruption, and historically we checked it every time the index
+ file is used. Omit the validation during normal use, and instead
+ verify only in "git fsck".
+
+ * Having a git command on the upstream side of a pipe in a test
+ script will hide the exit status from the command, which may cause
+ us to fail to notice a breakage; rewrite tests in a script to avoid
+ this issue.
+
+ * Travis CI learns to run coccicheck.
+
+ * "git checkout" that handles a lot of paths has been optimized by
+ reducing the number of unnecessary checks of paths in the
+ has_dir_name() function.
+
+ * The internals of the refs API around the cached refs has been
+ streamlined.
+
+ * Output from perf tests have been updated to align their titles.
+
Also contains various documentation updates and code clean-ups.
@@ -492,6 +540,45 @@ notes for details).
checked out; it should use "symbolic-ref HEAD".
(merge eff451101d ld/p4-current-branch-fix later to maint).
+ * "http.proxy" set to an empty string is used to disable the usage of
+ proxy. We broke this early last year.
+ (merge ae51d91105 sr/http-proxy-configuration-fix later to maint).
+
+ * $GIT_DIR may in some cases be normalized with all symlinks resolved
+ while "gitdir" path expansion in the pattern does not receive the
+ same treatment, leading to incorrect mismatch. This has been fixed.
+
+ * "git submodule" script does not work well with strange pathnames.
+ Protect it from a path with slashes in them, at least.
+
+ * "git fetch-pack" was not prepared to accept ERR packet that the
+ upload-pack can send with a human-readable error message. It
+ showed the packet contents with ERR prefix, so there was no data
+ loss, but it was redundant to say "ERR" in an error message.
+ (merge 8e2c7bef03 jt/fetch-pack-error-reporting later to maint).
+
+ * "ls-files --recurse-submodules" did not quite work well in a
+ project with nested submodules.
+
+ * gethostname(2) may not NUL terminate the buffer if hostname does
+ not fit; unfortunately there is no easy way to see if our buffer
+ was too small, but at least this will make sure we will not end up
+ using garbage past the end of the buffer.
+ (merge 5781a9a270 dt/xgethostname-nul-termination later to maint).
+
+ * A recent update broke "git add -p ../foo" from a subdirectory.
+
+ * While handy, "git_path()" is a dangerous function to use as a
+ callsite that uses it safely one day can be broken by changes
+ to other code that calls it. Reduction of its use continues.
+ (merge 16d2676c9e jk/war-on-git-path later to maint).
+
+ * The split-index code configuration code used an unsafe git_path()
+ function without copying its result out.
+
+ * Many stale HTTP(s) links have been updated in our documentation.
+ (merge 613416f0be jk/update-links-in-docs later to maint).
+
* Other minor doc, test and build updates and code cleanups.
(merge df2a6e38b7 jk/pager-in-use later to maint).
(merge 75ec4a6cb0 ab/branch-list-doc later to maint).
@@ -509,3 +596,13 @@ notes for details).
(merge 35ad44cbd8 sb/submodule-rm-absorb later to maint).
(merge 0301f1fd92 va/i18n-perl-scripts later to maint).
(merge 733e064d98 vn/revision-shorthand-for-side-branch-log later to maint).
+ (merge 85999743e7 tb/doc-eol-normalization later to maint).
+ (merge 0747fb49fd jk/loose-object-fsck later to maint).
+ (merge d8f4481c4f jk/quarantine-received-objects later to maint).
+ (merge 7ba1ceef95 xy/format-patch-base later to maint).
+ (merge fa1912c89a rs/misc-cppcheck-fixes later to maint).
+ (merge f17d642d3b ab/push-cas-doc-n-test later to maint).
+ (merge 61e282425a ss/gitmodules-ignore-doc later to maint).
+ (merge 8d3047cd5b ss/submodule-shallow-doc later to maint).
+ (merge 1f9e18b772 jk/prio-queue-avoid-swap-with-self later to maint).
+ (merge 627fde1025 jk/submodule-init-segv-fix later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index bc8ad00..558d465 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -51,7 +51,7 @@ If your description starts to get too long, that's a sign that you
probably need to split up your commit to finer grained pieces.
That being said, patches which plainly describe the things that
help reviewers check the patch, and future maintainers understand
-the code, are the most beautiful patches. Descriptions that summarise
+the code, are the most beautiful patches. Descriptions that summarize
the point in the subject well, and describe the motivation for the
change, the approach taken by the change, and if relevant how this
differs substantially from the prior version, are all good things
@@ -87,7 +87,7 @@ patches separate from other documentation changes.
Oh, another thing. We are picky about whitespaces. Make sure your
changes do not trigger errors with the sample pre-commit hook shipped
in templates/hooks--pre-commit. To help ensure this does not happen,
-run git diff --check on your changes before you commit.
+run "git diff --check" on your changes before you commit.
(2) Describe your changes well.
@@ -111,10 +111,10 @@ Improve...".
The body should provide a meaningful commit message, which:
- . explains the problem the change tries to solve, iow, what is wrong
+ . explains the problem the change tries to solve, i.e. what is wrong
with the current code without the change.
- . justifies the way the change solves the problem, iow, why the
+ . justifies the way the change solves the problem, i.e. why the
result with the change is better.
. alternate solutions considered but discarded, if any.
@@ -122,7 +122,7 @@ The body should provide a meaningful commit message, which:
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
to do frotz", as if you are giving orders to the codebase to change
-its behaviour. Try to make sure your explanation can be understood
+its behavior. Try to make sure your explanation can be understood
without external resources. Instead of giving a URL to a mailing list
archive, summarize the relevant points of the discussion.
@@ -261,7 +261,7 @@ smaller project it is a good discipline to follow it.
The sign-off is a simple line at the end of the explanation for
the patch, which certifies that you wrote it or otherwise have
the right to pass it on as a open-source patch. The rules are
-pretty simple: if you can certify the below:
+pretty simple: if you can certify the below D-C-O:
Developer's Certificate of Origin 1.1
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
index 163b9f6..ea70653 100644
--- a/Documentation/git-archimport.txt
+++ b/Documentation/git-archimport.txt
@@ -96,7 +96,7 @@ OPTIONS
pruned.
-a::
- Attempt to auto-register archives at http://mirrors.sourcecontrol.net
+ Attempt to auto-register archives at `http://mirrors.sourcecontrol.net`
This is particularly useful with the -D option.
-t <tmpdir>::
diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt
index e015f5b..8ac75fc 100644
--- a/Documentation/git-bisect-lk2009.txt
+++ b/Documentation/git-bisect-lk2009.txt
@@ -1347,12 +1347,12 @@ author to given a talk and for publishing this paper.
References
----------
-- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.]
-- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.]
-- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.]
+- [[[1]]] https://www.nist.gov/sites/default/files/documents/director/planning/report02-3.pdf['The Economic Impacts of Inadequate Infratructure for Software Testing'. Nist Planning Report 02-3], see Executive Summary and Chapter 8.
+- [[[2]]] http://www.oracle.com/technetwork/java/codeconvtoc-136057.html['Code Conventions for the Java Programming Language'. Sun Microsystems.]
+- [[[3]]] https://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.]
- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.]
-- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.]
-- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.]
+- [[[5]]] https://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.]
+- [[[6]]] https://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.]
- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.]
-- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.]
-- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.]
+- [[[8]]] https://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.]
+- [[[9]]] https://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.]
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index f7a069b..c890328 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -557,7 +557,7 @@ series A, B, C, the history would be like:
................................................
With `git format-patch --base=P -3 C` (or variants thereof, e.g. with
-`--cover-letter` of using `Z..C` instead of `-3 C` to specify the
+`--cover-letter` or using `Z..C` instead of `-3 C` to specify the
range), the base tree information block is shown at the end of the
first message the command outputs (either the first patch, or the
cover letter), like this:
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 4470e4b..942af8e 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -67,7 +67,7 @@ with uncommitted changes is discouraged: while possible, it leaves you
in a state that may be hard to back out of in the case of a conflict.
If any of the remote changes overlap with local uncommitted changes,
-the merge will be automatically cancelled and the work tree untouched.
+the merge will be automatically canceled and the work tree untouched.
It is generally best to get any local changes in working order before
pulling or stash them away with linkgit:git-stash[1].
@@ -210,7 +210,8 @@ EXAMPLES
current branch:
+
------------------------------------------------
-$ git pull, git pull origin
+$ git pull
+$ git pull origin
------------------------------------------------
+
Normally the branch merged in is the HEAD of the remote repository,
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 1624a35..0a63966 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -217,6 +217,47 @@ with this feature.
+
"--no-force-with-lease" will cancel all the previous --force-with-lease on the
command line.
++
+A general note on safety: supplying this option without an expected
+value, i.e. as `--force-with-lease` or `--force-with-lease=<refname>`
+interacts very badly with anything that implicitly runs `git fetch` on
+the remote to be pushed to in the background, e.g. `git fetch origin`
+on your repository in a cronjob.
++
+The protection it offers over `--force` is ensuring that subsequent
+changes your work wasn't based on aren't clobbered, but this is
+trivially defeated if some background process is updating refs in the
+background. We don't have anything except the remote tracking info to
+go by as a heuristic for refs you're expected to have seen & are
+willing to clobber.
++
+If your editor or some other system is running `git fetch` in the
+background for you a way to mitigate this is to simply set up another
+remote:
++
+ git remote add origin-push $(git config remote.origin.url)
+ git fetch origin-push
++
+Now when the background process runs `git fetch origin` the references
+on `origin-push` won't be updated, and thus commands like:
++
+ git push --force-with-lease origin-push
++
+Will fail unless you manually run `git fetch origin-push`. This method
+is of course entirely defeated by something that runs `git fetch
+--all`, in that case you'd need to either disable it or do something
+more tedious like:
++
+ git fetch # update 'master' from remote
+ git tag base master # mark our base point
+ git rebase -i master # rewrite some commits
+ git push --force-with-lease=master:base master:master
++
+I.e. create a `base` tag for versions of the upstream code that you've
+seen and are willing to overwrite, then rewrite history, and finally
+force push changes to `master` if the remote version is still at
+`base`, regardless of what your local `remotes/origin/master` has been
+updated to in the background.
-f::
--force::
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 67d48e6..53f4e14 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -370,6 +370,11 @@ default is `--no-fork-point`, otherwise the default is `--fork-point`.
of the rebased commits (see linkgit:git-am[1]).
Incompatible with the --interactive option.
+--signoff::
+ This flag is passed to 'git am' to sign off all the rebased
+ commits (see linkgit:git-am[1]). Incompatible with the
+ --interactive option.
+
-i::
--interactive::
Make a list of the commits which are about to be rebased. Let the
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 0ccd5fb..86a4b32 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -114,6 +114,8 @@ will be performed, and the update, post-receive and post-update
hooks will not be invoked either. This can be useful to quickly
bail out if the update is not to be supported.
+See the notes on the quarantine environment below.
+
update Hook
-----------
Before each ref is updated, if $GIT_DIR/hooks/update file exists
@@ -214,6 +216,33 @@ if the repository is packed and is served via a dumb transport.
exec git update-server-info
+Quarantine Environment
+----------------------
+
+When `receive-pack` takes in objects, they are placed into a temporary
+"quarantine" directory within the `$GIT_DIR/objects` directory and
+migrated into the main object store only after the `pre-receive` hook
+has completed. If the push fails before then, the temporary directory is
+removed entirely.
+
+This has a few user-visible effects and caveats:
+
+ 1. Pushes which fail due to problems with the incoming pack, missing
+ objects, or due to the `pre-receive` hook will not leave any
+ on-disk data. This is usually helpful to prevent repeated failed
+ pushes from filling up your disk, but can make debugging more
+ challenging.
+
+ 2. Any objects created by the `pre-receive` hook will be created in
+ the quarantine directory (and migrated only if it succeeds).
+
+ 3. The `pre-receive` hook MUST NOT update any refs to point to
+ quarantined objects. Other programs accessing the repository will
+ not be able to see the objects (and if the pre-receive hook fails,
+ those refs would become corrupted). For safety, any ref updates
+ from within `pre-receive` are automatically rejected.
+
+
SEE ALSO
--------
linkgit:git-send-pack[1], linkgit:gitnamespaces[7]
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
index 2f4ff50..d0fec4c 100644
--- a/Documentation/git-tools.txt
+++ b/Documentation/git-tools.txt
@@ -7,4 +7,4 @@ maintained here. These days, however, search engines fill that role much
more efficiently, so this manually-maintained list has been retired.
See also the `contrib/` area, and the Git wiki:
-http://git.or.cz/gitwiki/InterfacesFrontendsAndTools
+https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 553cf84..b472acc 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
SYNOPSIS
--------
[verse]
-'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
+'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<branch>]
'git worktree list' [--porcelain]
'git worktree lock' [--reason <string>] <worktree>
'git worktree prune' [-n] [-v] [--expire <expire>]
@@ -107,6 +107,11 @@ OPTIONS
such as configuring sparse-checkout. See "Sparse checkout"
in linkgit:git-read-tree[1].
+--lock::
+ Keep the working tree locked after creation. This is the
+ equivalent of `git worktree lock` after `git worktree add`,
+ but without race condition.
+
-n::
--dry-run::
With `prune`, do not remove anything; just report what it would
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index a53d093..4736483 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -229,11 +229,9 @@ From a clean working directory:
-------------------------------------------------
$ echo "* text=auto" >.gitattributes
-$ rm .git/index # Remove the index to force Git to
-$ git reset # re-scan the working directory
+$ rm .git/index # Remove the index to re-scan the working directory
+$ git add .
$ git status # Show files that will be normalized
-$ git add -u
-$ git add .gitattributes
$ git commit -m "Introduce end-of-line normalization"
-------------------------------------------------
diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt
index 3a0ec8c..7577f27 100644
--- a/Documentation/gitcore-tutorial.txt
+++ b/Documentation/gitcore-tutorial.txt
@@ -1429,7 +1429,7 @@ Although Git is a truly distributed system, it is often
convenient to organize your project with an informal hierarchy
of developers. Linux kernel development is run this way. There
is a nice illustration (page 17, "Merges to Mainline") in
-http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation].
+https://web.archive.org/web/20120915203609/http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation].
It should be stressed that this hierarchy is purely *informal*.
There is nothing fundamental in Git that enforces the "chain of
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 9565dc3..706091a 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -22,8 +22,10 @@ changed via the `core.hooksPath` configuration variable (see
linkgit:git-config[1]).
Before Git invokes a hook, it changes its working directory to either
-the root of the working tree in a non-bare repository, or to the
-$GIT_DIR in a bare repository.
+$GIT_DIR in a bare repository or the root of the working tree in a non-bare
+repository. An exception are hooks triggered during a push ('pre-receive',
+'update', 'post-receive', 'post-update', 'push-to-checkout') which are always
+executed in $GIT_DIR.
Hooks can get their arguments via the environment, command-line
arguments, and stdin. See the documentation for each hook below for
@@ -256,6 +258,9 @@ environment variables will not be set. If the client selects
to use push options, but doesn't transmit any, the count variable
will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
+See the section on "Quarantine Environment" in
+linkgit:git-receive-pack[1] for some caveats.
+
[[update]]
update
~~~~~~
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
index 8f7c50f..db5d47e 100644
--- a/Documentation/gitmodules.txt
+++ b/Documentation/gitmodules.txt
@@ -66,17 +66,26 @@ submodule.<name>.fetchRecurseSubmodules::
submodule.<name>.ignore::
Defines under what circumstances "git status" and the diff family show
- a submodule as modified. When set to "all", it will never be considered
- modified (but will nonetheless show up in the output of status and
- commit when it has been staged), "dirty" will ignore all changes
- to the submodules work tree and
- takes only differences between the HEAD of the submodule and the commit
- recorded in the superproject into account. "untracked" will additionally
- let submodules with modified tracked files in their work tree show up.
- Using "none" (the default when this option is not set) also shows
- submodules that have untracked files in their work tree as changed.
- If this option is also present in the submodules entry in .git/config of
- the superproject, the setting there will override the one found in
+ a submodule as modified. The following values are supported:
+
+ all;; The submodule will never be considered modified (but will
+ nonetheless show up in the output of status and commit when it has
+ been staged).
+
+ dirty;; All changes to the submodule's work tree will be ignored, only
+ committed differences between the HEAD of the submodule and its
+ recorded state in the superproject are taken into account.
+
+ untracked;; Only untracked files in submodules will be ignored.
+ Committed differences and modifications to tracked files will show
+ up.
+
+ none;; No modifiations to submodules are ignored, all of committed
+ differences, and modifications to tracked and untracked files are
+ shown. This is the default option.
+
+ If this option is also present in the submodules entry in .git/config
+ of the superproject, the setting there will override the one found in
.gitmodules.
Both settings can be overridden on the command line by using the
"--ignore-submodule" option. The 'git submodule' commands are not
@@ -84,8 +93,8 @@ submodule.<name>.ignore::
submodule.<name>.shallow::
When set to true, a clone of this submodule will be performed as a
- shallow clone unless the user explicitly asks for a non-shallow
- clone.
+ shallow clone (with a history depth of 1) unless the user explicitly
+ asks for a non-shallow clone.
EXAMPLES
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
index e632089..9c8982e 100644
--- a/Documentation/gitweb.conf.txt
+++ b/Documentation/gitweb.conf.txt
@@ -368,8 +368,8 @@ $logo_url::
$logo_label::
URI and label (title) for the Git logo link (or your site logo,
if you chose to use different logo image). By default, these both
- refer to Git homepage, http://git-scm.com[]; in the past, they pointed
- to Git documentation at http://www.kernel.org[].
+ refer to Git homepage, https://git-scm.com[]; in the past, they pointed
+ to Git documentation at https://www.kernel.org[].
Changing gitweb's look
diff --git a/Documentation/howto/rebuild-from-update-hook.txt b/Documentation/howto/rebuild-from-update-hook.txt
index 25378f6..db219f5 100644
--- a/Documentation/howto/rebuild-from-update-hook.txt
+++ b/Documentation/howto/rebuild-from-update-hook.txt
@@ -4,13 +4,13 @@ From: Junio C Hamano <gitster@pobox.com>
Date: Fri, 26 Aug 2005 18:19:10 -0700
Abstract: In this how-to article, JC talks about how he
uses the post-update hook to automate Git documentation page
- shown at http://www.kernel.org/pub/software/scm/git/docs/.
+ shown at https://www.kernel.org/pub/software/scm/git/docs/.
Content-type: text/asciidoc
How to rebuild from update hook
===============================
-The pages under http://www.kernel.org/pub/software/scm/git/docs/
+The pages under https://www.kernel.org/pub/software/scm/git/docs/
are built from Documentation/ directory of the git.git project
and needed to be kept up-to-date. The www.kernel.org/ servers
are mirrored and I was told that the origin of the mirror is on
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index c59ac99..5b0ba3e 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -351,14 +351,19 @@ ACK after 'done' if there is at least one common base and multi_ack or
multi_ack_detailed is enabled. The server always sends NAK after 'done'
if there is no common base found.
+Instead of 'ACK' or 'NAK', the server may send an error message (for
+example, if it does not recognize an object in a 'want' line received
+from the client).
+
Then the server will start sending its packfile data.
----
- server-response = *ack_multi ack / nak
+ server-response = *ack_multi ack / nak / error-line
ack_multi = PKT-LINE("ACK" SP obj-id ack_status)
ack_status = "continue" / "common" / "ready"
ack = PKT-LINE("ACK" SP obj-id)
nak = PKT-LINE("NAK")
+ error-line = PKT-LINE("ERR" SP explanation-text)
----
A simple clone may look like this (with no 'have' lines):
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index e681ede..598353e 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.13.0-rc0
+DEF_VER=v2.13.0-rc1
LF='
'
diff --git a/Makefile b/Makefile
index eb1a1a7..e35542e 100644
--- a/Makefile
+++ b/Makefile
@@ -639,6 +639,7 @@ TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
TEST_PROGRAMS_NEED_X += test-sha1
TEST_PROGRAMS_NEED_X += test-sha1-array
TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-strcmp-offset
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-submodule-config
TEST_PROGRAMS_NEED_X += test-subprocess
@@ -816,6 +817,7 @@ LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
LIB_OBJS += refs/files-backend.o
LIB_OBJS += refs/iterator.o
+LIB_OBJS += refs/ref-cache.o
LIB_OBJS += ref-filter.o
LIB_OBJS += remote.o
LIB_OBJS += replace_object.o
diff --git a/bisect.c b/bisect.c
index 03af06c..08c9fb7 100644
--- a/bisect.c
+++ b/bisect.c
@@ -432,6 +432,7 @@ static int read_bisect_refs(void)
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
+static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static void read_bisect_paths(struct argv_array *array)
{
@@ -906,7 +907,7 @@ static void show_diff_tree(const char *prefix, struct commit *commit)
void read_bisect_terms(const char **read_bad, const char **read_good)
{
struct strbuf str = STRBUF_INIT;
- const char *filename = git_path("BISECT_TERMS");
+ const char *filename = git_path_bisect_terms();
FILE *fp = fopen(filename, "r");
if (!fp) {
diff --git a/builtin/am.c b/builtin/am.c
index f7a7a97..a95dd8b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -134,17 +134,15 @@ struct am_state {
};
/**
- * Initializes am_state with the default values. The state directory is set to
- * dir.
+ * Initializes am_state with the default values.
*/
-static void am_state_init(struct am_state *state, const char *dir)
+static void am_state_init(struct am_state *state)
{
int gpgsign;
memset(state, 0, sizeof(*state));
- assert(dir);
- state->dir = xstrdup(dir);
+ state->dir = git_pathdup("rebase-apply");
state->prec = 4;
@@ -762,14 +760,18 @@ static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
out = fopen(mail, "w");
- if (!out)
+ if (!out) {
+ if (in != stdin)
+ fclose(in);
return error_errno(_("could not open '%s' for writing"),
mail);
+ }
ret = fn(out, in, keep_cr);
fclose(out);
- fclose(in);
+ if (in != stdin)
+ fclose(in);
if (ret)
return error(_("could not parse patch '%s'"), *paths);
@@ -1181,42 +1183,39 @@ static void NORETURN die_user_resolve(const struct am_state *state)
exit(128);
}
-static void am_signoff(struct strbuf *sb)
+/**
+ * Appends signoff to the "msg" field of the am_state.
+ */
+static void am_append_signoff(struct am_state *state)
{
char *cp;
struct strbuf mine = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
- /* Does it end with our own sign-off? */
+ strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
+
+ /* our sign-off */
strbuf_addf(&mine, "\n%s%s\n",
sign_off_header,
fmt_name(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL")));
- if (mine.len < sb->len &&
- !strcmp(mine.buf, sb->buf + sb->len - mine.len))
+
+ /* Does sb end with it already? */
+ if (mine.len < sb.len &&
+ !strcmp(mine.buf, sb.buf + sb.len - mine.len))
goto exit; /* no need to duplicate */
/* Does it have any Signed-off-by: in the text */
- for (cp = sb->buf;
+ for (cp = sb.buf;
cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
cp = strchr(cp, '\n')) {
- if (sb->buf == cp || cp[-1] == '\n')
+ if (sb.buf == cp || cp[-1] == '\n')
break;
}
- strbuf_addstr(sb, mine.buf + !!cp);
+ strbuf_addstr(&sb, mine.buf + !!cp);
exit:
strbuf_release(&mine);
-}
-
-/**
- * Appends signoff to the "msg" field of the am_state.
- */
-static void am_append_signoff(struct am_state *state)
-{
- struct strbuf sb = STRBUF_INIT;
-
- strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
- am_signoff(&sb);
state->msg = strbuf_detach(&sb, &state->msg_len);
}
@@ -1321,9 +1320,6 @@ static int parse_mail(struct am_state *state, const char *mail)
strbuf_addbuf(&msg, &mi.log_message);
strbuf_stripspace(&msg, 0);
- if (state->signoff)
- am_signoff(&msg);
-
assert(!state->author_name);
state->author_name = strbuf_detach(&author_name, NULL);
@@ -1848,6 +1844,9 @@ static void am_run(struct am_state *state, int resume)
if (skip)
goto next; /* mail should be skipped */
+ if (state->signoff)
+ am_append_signoff(state);
+
write_author_script(state);
write_commit_msg(state);
}
@@ -2322,7 +2321,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
git_config(git_am_config, NULL);
- am_state_init(&state, git_path("rebase-apply"));
+ am_state_init(&state);
in_progress = am_in_progress(&state);
if (in_progress)
diff --git a/builtin/branch.c b/builtin/branch.c
index 0552c42..48a513a 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -504,7 +504,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
strbuf_release(&newsection);
}
-static const char edit_description[] = "BRANCH_DESCRIPTION";
+static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
static int edit_branch_description(const char *branch_name)
{
@@ -519,9 +519,9 @@ static int edit_branch_description(const char *branch_name)
" %s\n"
"Lines starting with '%c' will be stripped.\n"),
branch_name, comment_line_char);
- write_file_buf(git_path(edit_description), buf.buf, buf.len);
+ write_file_buf(edit_description(), buf.buf, buf.len);
strbuf_reset(&buf);
- if (launch_editor(git_path(edit_description), &buf, NULL)) {
+ if (launch_editor(edit_description(), &buf, NULL)) {
strbuf_release(&buf);
return -1;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index 4e288bc..1d805f5 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -821,9 +821,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
"If this is not correct, please remove the file\n"
" %s\n"
"and try again.\n"),
- git_path(whence == FROM_MERGE
- ? "MERGE_HEAD"
- : "CHERRY_PICK_HEAD"));
+ whence == FROM_MERGE ?
+ git_path_merge_head() :
+ git_path_cherry_pick_head());
}
fprintf(s->fp, "\n");
@@ -1404,7 +1404,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
static const char *implicit_ident_advice(void)
{
- char *user_config = expand_user_path("~/.gitconfig");
+ char *user_config = expand_user_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
int config_exists = file_exists(user_config) || file_exists(xdg_config);
diff --git a/builtin/config.c b/builtin/config.c
index 4f49a0e..3a554ad 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -26,7 +26,8 @@ static int use_global_config, use_system_config, use_local_config;
static struct git_config_source given_config_source;
static int actions, types;
static int end_null;
-static int respect_includes = -1;
+static int respect_includes_opt = -1;
+static struct config_options config_options;
static int show_origin;
#define ACTION_GET (1<<0)
@@ -81,7 +82,7 @@ static struct option builtin_config_options[] = {
OPT_GROUP(N_("Other")),
OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
- OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")),
+ OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_END(),
};
@@ -242,7 +243,7 @@ static int get_value(const char *key_, const char *regex_)
}
git_config_with_options(collect_config, &values,
- &given_config_source, respect_includes);
+ &given_config_source, &config_options);
ret = !values.nr;
@@ -320,7 +321,7 @@ static void get_color(const char *var, const char *def_color)
get_color_found = 0;
parsed_color[0] = '\0';
git_config_with_options(git_get_color_config, NULL,
- &given_config_source, respect_includes);
+ &given_config_source, &config_options);
if (!get_color_found && def_color) {
if (color_parse(def_color, parsed_color) < 0)
@@ -352,7 +353,7 @@ static int get_colorbool(const char *var, int print)
get_diff_color_found = -1;
get_color_ui_found = -1;
git_config_with_options(git_get_colorbool_config, NULL,
- &given_config_source, respect_includes);
+ &given_config_source, &config_options);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -441,7 +442,7 @@ static int get_urlmatch(const char *var, const char *url)
}
git_config_with_options(urlmatch_config_entry, &config,
- &given_config_source, respect_includes);
+ &given_config_source, &config_options);
ret = !values.nr;
@@ -502,7 +503,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *user_config = expand_user_path("~/.gitconfig");
+ char *user_config = expand_user_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
if (!user_config)
@@ -530,8 +531,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
prefix_filename(prefix, given_config_source.file);
}
- if (respect_includes == -1)
- respect_includes = !given_config_source.file;
+ if (respect_includes_opt == -1)
+ config_options.respect_includes = !given_config_source.file;
+ else
+ config_options.respect_includes = respect_includes_opt;
if (end_null) {
term = '\0';
@@ -578,7 +581,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 0, 0);
if (git_config_with_options(show_all_config, NULL,
&given_config_source,
- respect_includes) < 0) {
+ &config_options) < 0) {
if (given_config_source.file)
die_errno("unable to read config file '%s'",
given_config_source.file);
@@ -597,8 +600,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (given_config_source.blob)
die("editing blobs is not supported");
git_config(git_default_config, NULL);
- config_file = xstrdup(given_config_source.file ?
- given_config_source.file : git_path("config"));
+ config_file = given_config_source.file ?
+ xstrdup(given_config_source.file) :
+ git_pathdup("config");
if (use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
diff --git a/builtin/fsck.c b/builtin/fsck.c
index f76e416..b5e13a4 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -771,6 +771,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
}
if (keep_cache_objects) {
+ verify_index_checksum = 1;
read_cache();
for (i = 0; i < active_nr; i++) {
unsigned int mode;
diff --git a/builtin/gc.c b/builtin/gc.c
index 2daede7..91f7696 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -232,7 +232,7 @@ static int need_to_gc(void)
static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
{
static struct lock_file lock;
- char my_host[128];
+ char my_host[HOST_NAME_MAX + 1];
struct strbuf sb = STRBUF_INIT;
struct stat st;
uintmax_t pid;
@@ -244,15 +244,19 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
/* already locked */
return NULL;
- if (gethostname(my_host, sizeof(my_host)))
+ if (xgethostname(my_host, sizeof(my_host)))
xsnprintf(my_host, sizeof(my_host), "unknown");
pidfile_path = git_pathdup("gc.pid");
fd = hold_lock_file_for_update(&lock, pidfile_path,
LOCK_DIE_ON_ERROR);
if (!force) {
- static char locking_host[128];
+ static char locking_host[HOST_NAME_MAX + 1];
+ static char *scan_fmt;
int should_exit;
+
+ if (!scan_fmt)
+ scan_fmt = xstrfmt("%s %%%dc", "%"SCNuMAX, HOST_NAME_MAX);
fp = fopen(pidfile_path, "r");
memset(locking_host, 0, sizeof(locking_host));
should_exit =
@@ -268,7 +272,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
* running.
*/
time(NULL) - st.st_mtime <= 12 * 3600 &&
- fscanf(fp, "%"SCNuMAX" %127c", &pid, locking_host) == 2 &&
+ fscanf(fp, scan_fmt, &pid, locking_host) == 2 &&
/* be gentle to concurrent "gc" on remote hosts */
(strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
if (fp != NULL)
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index d449e46..a6c70db 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -15,6 +15,7 @@
#include "string-list.h"
#include "pathspec.h"
#include "run-command.h"
+#include "submodule.h"
static int abbrev;
static int show_deleted;
@@ -202,6 +203,10 @@ static void show_gitlink(const struct cache_entry *ce)
{
struct child_process cp = CHILD_PROCESS_INIT;
int status;
+ char *dir;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT);
if (prefix_len)
argv_array_pushf(&cp.env_array, "%s=%s",
@@ -217,8 +222,10 @@ static void show_gitlink(const struct cache_entry *ce)
argv_array_pushv(&cp.args, submodule_options.argv);
cp.git_cmd = 1;
- cp.dir = ce->name;
+ dir = mkpathdup("%s/%s", get_git_work_tree(), ce->name);
+ cp.dir = dir;
status = run_command(&cp);
+ free(dir);
if (status)
exit(status);
}
diff --git a/builtin/pull.c b/builtin/pull.c
index d8aa26d..dd1a4a9 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -332,7 +332,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)
*/
static void get_merge_heads(struct oid_array *merge_heads)
{
- const char *filename = git_path("FETCH_HEAD");
+ const char *filename = git_path_fetch_head();
FILE *fp;
struct strbuf sb = STRBUF_INIT;
struct object_id oid;
@@ -791,7 +791,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (read_cache_unmerged())
die_resolve_conflict("pull");
- if (file_exists(git_path("MERGE_HEAD")))
+ if (file_exists(git_path_merge_head()))
die_conclude_merge();
if (get_oid("HEAD", &orig_head))
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 3cba3fd..f96834f 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -772,7 +772,6 @@ static int run_update_hook(struct command *cmd)
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
proc.argv = argv;
- proc.env = tmp_objdir_env(tmp_objdir);
code = start_command(&proc);
if (code)
@@ -1698,12 +1697,12 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (status)
return "unpack-objects abnormal exit";
} else {
- char hostname[256];
+ char hostname[HOST_NAME_MAX + 1];
argv_array_pushl(&child.args, "index-pack", "--stdin", NULL);
push_header_arg(&child.args, &hdr);
- if (gethostname(hostname, sizeof(hostname)))
+ if (xgethostname(hostname, sizeof(hostname)))
xsnprintf(hostname, sizeof(hostname), "localhost");
argv_array_pushf(&child.args,
"--keep=receive-pack %"PRIuMAX" on %s",
diff --git a/builtin/replace.c b/builtin/replace.c
index 065515b..ab17668 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -120,6 +120,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
if (fn(full_hex, ref.buf, &oid))
had_error = 1;
}
+ strbuf_release(&ref);
return had_error;
}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 36e4231..566a5b6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -376,12 +376,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
strbuf_reset(&sb);
strbuf_addf(&sb, "submodule.%s.url", sub->name);
if (git_config_get_string(sb.buf, &url)) {
- url = xstrdup(sub->url);
-
- if (!url)
+ if (!sub->url)
die(_("No url found for submodule path '%s' in .gitmodules"),
displaypath);
+ url = xstrdup(sub->url);
+
/* Possibly a url relative to parent */
if (starts_with_dot_dot_slash(url) ||
starts_with_dot_slash(url)) {
diff --git a/builtin/tag.c b/builtin/tag.c
index 2224045..bdf1e88 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -309,7 +309,7 @@ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
if (rla) {
strbuf_addstr(sb, rla);
} else {
- strbuf_addstr(sb, _("tag: tagging "));
+ strbuf_addstr(sb, "tag: tagging ");
strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV);
}
@@ -317,14 +317,14 @@ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
type = sha1_object_info(sha1, NULL);
switch (type) {
default:
- strbuf_addstr(sb, _("object of unknown type"));
+ strbuf_addstr(sb, "object of unknown type");
break;
case OBJ_COMMIT:
if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) {
subject_len = find_commit_subject(buf, &subject_start);
strbuf_insert(sb, sb->len, subject_start, subject_len);
} else {
- strbuf_addstr(sb, _("commit object"));
+ strbuf_addstr(sb, "commit object");
}
free(buf);
@@ -332,13 +332,13 @@ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
break;
case OBJ_TREE:
- strbuf_addstr(sb, _("tree object"));
+ strbuf_addstr(sb, "tree object");
break;
case OBJ_BLOB:
- strbuf_addstr(sb, _("blob object"));
+ strbuf_addstr(sb, "blob object");
break;
case OBJ_TAG:
- strbuf_addstr(sb, _("other tag object"));
+ strbuf_addstr(sb, "other tag object");
break;
}
strbuf_addch(sb, ')');
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 9993ded..1722a9b 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -24,6 +24,7 @@ struct add_opts {
int force;
int detach;
int checkout;
+ int keep_locked;
const char *new_branch;
int force_new_branch;
};
@@ -106,8 +107,7 @@ static void prune_worktrees(void)
printf("%s\n", reason.buf);
if (show_only)
continue;
- strbuf_reset(&path);
- strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ git_path_buf(&path, "worktrees/%s", d->d_name);
ret = remove_dir_recursively(&path, 0);
if (ret < 0 && errno == ENOTDIR)
ret = unlink(path.buf);
@@ -215,8 +215,7 @@ static int add_worktree(const char *path, const char *refname,
}
name = worktree_basename(path, &len);
- strbuf_addstr(&sb_repo,
- git_path("worktrees/%.*s", (int)(path + len - name), name));
+ git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
len = sb_repo.len;
if (safe_create_leading_directories_const(sb_repo.buf))
die_errno(_("could not create leading directories of '%s'"),
@@ -242,7 +241,10 @@ static int add_worktree(const char *path, const char *refname,
* after the preparation is over.
*/
strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- write_file(sb.buf, "initializing");
+ if (!opts->keep_locked)
+ write_file(sb.buf, "initializing");
+ else
+ write_file(sb.buf, "added with --lock");
strbuf_addf(&sb_git, "%s/.git", path);
if (safe_create_leading_directories_const(sb_git.buf))
@@ -303,9 +305,11 @@ static int add_worktree(const char *path, const char *refname,
junk_git_dir = NULL;
done:
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- unlink_or_warn(sb.buf);
+ if (ret || !opts->keep_locked) {
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ unlink_or_warn(sb.buf);
+ }
argv_array_clear(&child_env);
strbuf_release(&sb);
strbuf_release(&symref);
@@ -328,6 +332,7 @@ static int add(int ac, const char **av, const char *prefix)
N_("create or reset a branch")),
OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
+ OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
OPT_END()
};
diff --git a/cache.h b/cache.h
index ba27595..e1f0e18 100644
--- a/cache.h
+++ b/cache.h
@@ -599,6 +599,7 @@ extern int write_locked_index(struct index_state *, struct lock_file *lock, unsi
extern int discard_index(struct index_state *);
extern int unmerged_index(const struct index_state *);
extern int verify_path(const char *path);
+extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
extern void adjust_dirname_case(struct index_state *istate, char *name);
extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
@@ -710,6 +711,8 @@ extern void update_index_if_able(struct index_state *, struct lock_file *);
extern int hold_locked_index(struct lock_file *, int);
extern void set_alternate_index_output(const char *);
+extern int verify_index_checksum;
+
/* Environment bits from configuration mechanism */
extern int trust_executable_bit;
extern int trust_ctime;
@@ -1164,7 +1167,7 @@ typedef int create_file_fn(const char *path, void *cb);
int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
int mkdir_in_gitdir(const char *path);
-extern char *expand_user_path(const char *path);
+extern char *expand_user_path(const char *path, int real_home);
const char *enter_repo(const char *path, int strict);
static inline int is_absolute_path(const char *path)
{
@@ -1889,6 +1892,11 @@ enum config_origin_type {
CONFIG_ORIGIN_CMDLINE
};
+struct config_options {
+ unsigned int respect_includes : 1;
+ const char *git_dir;
+};
+
typedef int (*config_fn_t)(const char *, const char *, void *);
extern int git_default_config(const char *, const char *, void *);
extern int git_config_from_file(config_fn_t fn, const char *, void *);
@@ -1902,12 +1910,13 @@ extern void read_early_config(config_fn_t cb, void *data);
extern void git_config(config_fn_t fn, void *);
extern int git_config_with_options(config_fn_t fn, void *,
struct git_config_source *config_source,
- int respect_includes);
+ const struct config_options *opts);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_parse_maybe_bool(const char *);
extern int git_config_int(const char *, const char *);
extern int64_t git_config_int64(const char *, const char *);
extern unsigned long git_config_ulong(const char *, const char *);
+extern ssize_t git_config_ssize_t(const char *, const char *);
extern int git_config_bool_or_int(const char *, const char *, int *);
extern int git_config_bool(const char *, const char *);
extern int git_config_maybe_bool(const char *, const char *);
@@ -1954,6 +1963,7 @@ struct config_include_data {
int depth;
config_fn_t fn;
void *data;
+ const struct config_options *opts;
};
#define CONFIG_INCLUDE_INIT { 0 }
extern int git_config_include(const char *name, const char *value, void *data);
diff --git a/ci/run-windows-build.sh b/ci/run-windows-build.sh
index 9f89d54..d8f0d92 100755
--- a/ci/run-windows-build.sh
+++ b/ci/run-windows-build.sh
@@ -55,7 +55,7 @@ while true
do
LAST_STATUS=$STATUS
STATUS=$(gfwci "action=status&buildId=$BUILD_ID")
- test "$STATUS" = "$LAST_STATUS" || printf "\nStatus: $STATUS "
+ test "$STATUS" = "$LAST_STATUS" || printf "\nStatus: %s " "$STATUS"
printf "."
case "$STATUS" in
diff --git a/config.c b/config.c
index 1a4d855..b4a3205 100644
--- a/config.c
+++ b/config.c
@@ -135,7 +135,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
if (!path)
return config_error_nonbool("include.path");
- expanded = expand_user_path(path);
+ expanded = expand_user_path(path, 0);
if (!expanded)
return error("could not expand include path '%s'", path);
path = expanded;
@@ -177,7 +177,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
char *expanded;
int prefix = 0;
- expanded = expand_user_path(pat->buf);
+ expanded = expand_user_path(pat->buf, 1);
if (expanded) {
strbuf_reset(pat);
strbuf_addstr(pat, expanded);
@@ -191,7 +191,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
return error(_("relative config include "
"conditionals must come from files"));
- strbuf_add_absolute_path(&path, cf->path);
+ strbuf_realpath(&path, cf->path, 1);
slash = find_last_dir_sep(path.buf);
if (!slash)
die("BUG: how is this possible?");
@@ -207,13 +207,22 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
return prefix;
}
-static int include_by_gitdir(const char *cond, size_t cond_len, int icase)
+static int include_by_gitdir(const struct config_options *opts,
+ const char *cond, size_t cond_len, int icase)
{
struct strbuf text = STRBUF_INIT;
struct strbuf pattern = STRBUF_INIT;
int ret = 0, prefix;
+ const char *git_dir;
- strbuf_add_absolute_path(&text, get_git_dir());
+ if (opts->git_dir)
+ git_dir = opts->git_dir;
+ else if (have_git_dir())
+ git_dir = get_git_dir();
+ else
+ goto done;
+
+ strbuf_realpath(&text, git_dir, 1);
strbuf_add(&pattern, cond, cond_len);
prefix = prepare_include_condition_pattern(&pattern);
@@ -242,13 +251,14 @@ done:
return ret;
}
-static int include_condition_is_true(const char *cond, size_t cond_len)
+static int include_condition_is_true(const struct config_options *opts,
+ const char *cond, size_t cond_len)
{
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
- return include_by_gitdir(cond, cond_len, 0);
+ return include_by_gitdir(opts, cond, cond_len, 0);
else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
- return include_by_gitdir(cond, cond_len, 1);
+ return include_by_gitdir(opts, cond, cond_len, 1);
/* unknown conditionals are always false */
return 0;
@@ -273,7 +283,7 @@ int git_config_include(const char *var, const char *value, void *data)
ret = handle_path_include(value, inc);
if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
- (cond && include_condition_is_true(cond, cond_len)) &&
+ (cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
!strcmp(key, "path"))
ret = handle_path_include(value, inc);
@@ -834,6 +844,15 @@ int git_parse_ulong(const char *value, unsigned long *ret)
return 1;
}
+static int git_parse_ssize_t(const char *value, ssize_t *ret)
+{
+ intmax_t tmp;
+ if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
+ return 0;
+ *ret = tmp;
+ return 1;
+}
+
NORETURN
static void die_bad_number(const char *name, const char *value)
{
@@ -892,6 +911,14 @@ unsigned long git_config_ulong(const char *name, const char *value)
return ret;
}
+ssize_t git_config_ssize_t(const char *name, const char *value)
+{
+ ssize_t ret;
+ if (!git_parse_ssize_t(value, &ret))
+ die_bad_number(name, value);
+ return ret;
+}
+
int git_parse_maybe_bool(const char *value)
{
if (!value)
@@ -948,7 +975,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
{
if (!value)
return config_error_nonbool(var);
- *dest = expand_user_path(value);
+ *dest = expand_user_path(value, 0);
if (!*dest)
die(_("failed to expand user dir in: '%s'"), value);
return 0;
@@ -1494,12 +1521,20 @@ int git_config_system(void)
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}
-static int do_git_config_sequence(config_fn_t fn, void *data)
+static int do_git_config_sequence(const struct config_options *opts,
+ config_fn_t fn, void *data)
{
int ret = 0;
char *xdg_config = xdg_config_home("config");
- char *user_config = expand_user_path("~/.gitconfig");
- char *repo_config = have_git_dir() ? git_pathdup("config") : NULL;
+ char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *repo_config;
+
+ if (opts->git_dir)
+ repo_config = mkpathdup("%s/config", opts->git_dir);
+ else if (have_git_dir())
+ repo_config = git_pathdup("config");
+ else
+ repo_config = NULL;
current_parsing_scope = CONFIG_SCOPE_SYSTEM;
if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
@@ -1530,13 +1565,14 @@ static int do_git_config_sequence(config_fn_t fn, void *data)
int git_config_with_options(config_fn_t fn, void *data,
struct git_config_source *config_source,
- int respect_includes)
+ const struct config_options *opts)
{
struct config_include_data inc = CONFIG_INCLUDE_INIT;
- if (respect_includes) {
+ if (opts->respect_includes) {
inc.fn = fn;
inc.data = data;
+ inc.opts = opts;
fn = git_config_include;
data = &inc;
}
@@ -1552,12 +1588,15 @@ int git_config_with_options(config_fn_t fn, void *data,
else if (config_source && config_source->blob)
return git_config_from_blob_ref(fn, config_source->blob, data);
- return do_git_config_sequence(fn, data);
+ return do_git_config_sequence(opts, fn, data);
}
static void git_config_raw(config_fn_t fn, void *data)
{
- if (git_config_with_options(fn, data, NULL, 1) < 0)
+ struct config_options opts = {0};
+
+ opts.respect_includes = 1;
+ if (git_config_with_options(fn, data, NULL, &opts) < 0)
/*
* git_config_with_options() normally returns only
* zero, as most errors are fatal, and
@@ -1597,10 +1636,13 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
void read_early_config(config_fn_t cb, void *data)
{
+ struct config_options opts = {0};
struct strbuf buf = STRBUF_INIT;
- git_config_with_options(cb, data, NULL, 1);
+ opts.respect_includes = 1;
+ if (have_git_dir())
+ opts.git_dir = get_git_dir();
/*
* When setup_git_directory() was not yet asked to discover the
* GIT_DIR, we ask discover_git_directory() to figure out whether there
@@ -1609,14 +1651,11 @@ void read_early_config(config_fn_t cb, void *data)
* notably, the current working directory is still the same after the
* call).
*/
- if (!have_git_dir() && discover_git_directory(&buf)) {
- struct git_config_source repo_config;
+ else if (discover_git_directory(&buf))
+ opts.git_dir = buf.buf;
+
+ git_config_with_options(cb, data, NULL, &opts);
- memset(&repo_config, 0, sizeof(repo_config));
- strbuf_addstr(&buf, "/config");
- repo_config.file = buf.buf;
- git_config_with_options(cb, data, &repo_config, 1);
- }
strbuf_release(&buf);
}
diff --git a/connect.c b/connect.c
index 568a35f..cd21a1b 100644
--- a/connect.c
+++ b/connect.c
@@ -738,8 +738,10 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline,
* any longer.
*/
free(ssh_argv);
- } else
+ } else {
+ free(p);
return;
+ }
}
if (!strcasecmp(variant, "plink") ||
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 1150164..af65899 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -28,6 +28,14 @@
# completion style. For example '!f() { : git commit ; ... }; f' will
# tell the completion to use commit completion. This also works with aliases
# of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '".
+#
+# You can set the following environment variables to influence the behavior of
+# the completion routines:
+#
+# GIT_COMPLETION_CHECKOUT_NO_GUESS
+#
+# When set to "1", do not include "DWIM" suggestions in git-checkout
+# completion (e.g., completing "foo" when "origin/foo" exists).
case "$COMP_WORDBREAKS" in
*:*) : great ;;
@@ -709,6 +717,7 @@ __git_complete_remote_or_refspec ()
i="${words[c]}"
case "$i" in
--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+ -d|--delete) [ "$cmd" = "push" ] && lhs=0 ;;
--all)
case "$cmd" in
push) no_complete_refspec=1 ;;
@@ -1248,7 +1257,8 @@ _git_checkout ()
# check if --track, --no-track, or --no-guess was specified
# if so, disable DWIM mode
local flags="--track --no-track --no-guess" track_opt="--track"
- if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
+ if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
+ [ -n "$(__git_find_on_cmdline "$flags")" ]; then
track_opt=''
fi
__git_complete_refs $track_opt
diff --git a/credential-cache.c b/credential-cache.c
index 3cbd420..91550bf 100644
--- a/credential-cache.c
+++ b/credential-cache.c
@@ -87,7 +87,7 @@ static char *get_socket_path(void)
{
struct stat sb;
char *old_dir, *socket;
- old_dir = expand_user_path("~/.git-credential-cache");
+ old_dir = expand_user_path("~/.git-credential-cache", 0);
if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
socket = xstrfmt("%s/socket", old_dir);
else
diff --git a/credential-store.c b/credential-store.c
index 55ca1b1..ac29542 100644
--- a/credential-store.c
+++ b/credential-store.c
@@ -168,7 +168,7 @@ int cmd_main(int argc, const char **argv)
if (file) {
string_list_append(&fns, file);
} else {
- if ((file = expand_user_path("~/.git-credentials")))
+ if ((file = expand_user_path("~/.git-credentials", 0)))
string_list_append_nodup(&fns, file);
file = xdg_config_home("credentials");
if (file)
diff --git a/daemon.c b/daemon.c
index f70d27b..ac7181a 100644
--- a/daemon.c
+++ b/daemon.c
@@ -4,10 +4,6 @@
#include "strbuf.h"
#include "string-list.h"
-#ifndef HOST_NAME_MAX
-#define HOST_NAME_MAX 256
-#endif
-
#ifdef NO_INITGROUPS
#define initgroups(x, y) (0) /* nothing */
#endif
diff --git a/fast-import.c b/fast-import.c
index 1ea3a08..cf58f87 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -3202,7 +3202,7 @@ static char* make_fast_import_path(const char *path)
{
if (!relative_marks_paths || is_absolute_path(path))
return xstrdup(path);
- return xstrdup(git_path("info/fast-import/%s", path));
+ return git_pathdup("info/fast-import/%s", path);
}
static void option_import_marks(const char *marks,
diff --git a/fetch-pack.c b/fetch-pack.c
index 4296935..afb8b05 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -276,6 +276,8 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1)
return ACK;
}
}
+ if (skip_prefix(line, "ERR ", &arg))
+ die(_("remote error: %s"), arg);
die(_("git fetch-pack: expected ACK/NAK, got '%s'"), line);
}
@@ -802,8 +804,8 @@ static int get_pack(struct fetch_pack_args *args,
if (args->use_thin_pack)
argv_array_push(&cmd.args, "--fix-thin");
if (args->lock_pack || unpack_limit) {
- char hostname[256];
- if (gethostname(hostname, sizeof(hostname)))
+ char hostname[HOST_NAME_MAX + 1];
+ if (xgethostname(hostname, sizeof(hostname)))
xsnprintf(hostname, sizeof(hostname), "localhost");
argv_array_pushf(&cmd.args,
"--keep=fetch-pack %"PRIuMAX " on %s",
diff --git a/git-compat-util.h b/git-compat-util.h
index 8a4a3f8..bd04564 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -884,6 +884,12 @@ static inline size_t xsize_t(off_t len)
__attribute__((format (printf, 3, 4)))
extern int xsnprintf(char *dst, size_t max, const char *fmt, ...);
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
+
+extern int xgethostname(char *buf, size_t len);
+
/* in ctype.c, for kwset users */
extern const unsigned char tolower_trans_tbl[256];
diff --git a/git-rebase.sh b/git-rebase.sh
index 48d7c5d..db1deed 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -34,6 +34,7 @@ root! rebase all reachable commits up to the root(s)
autosquash move commits that begin with squash!/fixup! under -i
committer-date-is-author-date! passed to 'git am'
ignore-date! passed to 'git am'
+signoff passed to 'git am'
whitespace=! passed to 'git apply'
ignore-whitespace! passed to 'git apply'
C=! passed to 'git apply'
@@ -321,7 +322,7 @@ do
--ignore-whitespace)
git_am_opt="$git_am_opt $1"
;;
- --committer-date-is-author-date|--ignore-date)
+ --committer-date-is-author-date|--ignore-date|--signoff|--no-signoff)
git_am_opt="$git_am_opt $1"
force_rebase=t
;;
diff --git a/git-submodule.sh b/git-submodule.sh
index 6ec35e5..c0d0e9a 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -332,7 +332,7 @@ cmd_foreach()
git submodule--helper list --prefix "$wt_prefix" ||
echo "#unmatched" $?
} |
- while read mode sha1 stage sm_path
+ while read -r mode sha1 stage sm_path
do
die_if_unmatched "$mode" "$sha1"
if test -e "$sm_path"/.git
@@ -441,7 +441,7 @@ cmd_deinit()
git submodule--helper list --prefix "$wt_prefix" "$@" ||
echo "#unmatched" $?
} |
- while read mode sha1 stage sm_path
+ while read -r mode sha1 stage sm_path
do
die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path") || exit
@@ -605,7 +605,7 @@ cmd_update()
"$@" || echo "#unmatched" $?
} | {
err=
- while read mode sha1 stage just_cloned sm_path
+ while read -r mode sha1 stage just_cloned sm_path
do
die_if_unmatched "$mode" "$sha1"
@@ -847,7 +847,7 @@ cmd_summary() {
# Get modified modules cared by user
modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
sane_egrep '^:([0-7]* )?160000' |
- while read mod_src mod_dst sha1_src sha1_dst status sm_path
+ while read -r mod_src mod_dst sha1_src sha1_dst status sm_path
do
# Always show modules deleted or type-changed (blob<->module)
if test "$status" = D || test "$status" = T
@@ -873,7 +873,7 @@ cmd_summary() {
git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules |
sane_egrep '^:([0-7]* )?160000' |
cut -c2- |
- while read mod_src mod_dst sha1_src sha1_dst status name
+ while read -r mod_src mod_dst sha1_src sha1_dst status name
do
if test -z "$cached" &&
test $sha1_dst = 0000000000000000000000000000000000000000
@@ -1020,7 +1020,7 @@ cmd_status()
git submodule--helper list --prefix "$wt_prefix" "$@" ||
echo "#unmatched" $?
} |
- while read mode sha1 stage sm_path
+ while read -r mode sha1 stage sm_path
do
die_if_unmatched "$mode" "$sha1"
name=$(git submodule--helper name "$sm_path") || exit
@@ -1100,7 +1100,7 @@ cmd_sync()
git submodule--helper list --prefix "$wt_prefix" "$@" ||
echo "#unmatched" $?
} |
- while read mode sha1 stage sm_path
+ while read -r mode sha1 stage sm_path
do
die_if_unmatched "$mode" "$sha1"
diff --git a/http.c b/http.c
index 8d94e2c..d2e11ec 100644
--- a/http.c
+++ b/http.c
@@ -19,7 +19,7 @@ long int git_curl_ipresolve;
#endif
int active_requests;
int http_is_verbose;
-size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
+ssize_t http_post_buffer = 16 * LARGE_PACKET_MAX;
#if LIBCURL_VERSION_NUM >= 0x070a06
#define LIBCURL_CAN_HANDLE_AUTH_ANY
@@ -331,7 +331,9 @@ static int http_options(const char *var, const char *value, void *cb)
}
if (!strcmp("http.postbuffer", var)) {
- http_post_buffer = git_config_int(var, value);
+ http_post_buffer = git_config_ssize_t(var, value);
+ if (http_post_buffer < 0)
+ warning(_("negative value for http.postbuffer; defaulting to %d"), LARGE_PACKET_MAX);
if (http_post_buffer < LARGE_PACKET_MAX)
http_post_buffer = LARGE_PACKET_MAX;
return 0;
@@ -836,8 +838,14 @@ static CURL *get_curl_handle(void)
}
}
- if (curl_http_proxy) {
- curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
+ if (curl_http_proxy && curl_http_proxy[0] == '\0') {
+ /*
+ * Handle case with the empty http.proxy value here to keep
+ * common code clean.
+ * NB: empty option disables proxying at all.
+ */
+ curl_easy_setopt(result, CURLOPT_PROXY, "");
+ } else if (curl_http_proxy) {
#if LIBCURL_VERSION_NUM >= 0x071800
if (starts_with(curl_http_proxy, "socks5h"))
curl_easy_setopt(result,
@@ -861,6 +869,9 @@ static CURL *get_curl_handle(void)
strbuf_release(&url);
}
+ if (!proxy_auth.host)
+ die("Invalid proxy URL '%s'", curl_http_proxy);
+
curl_easy_setopt(result, CURLOPT_PROXY, proxy_auth.host);
#if LIBCURL_VERSION_NUM >= 0x071304
var_override(&curl_no_proxy, getenv("NO_PROXY"));
diff --git a/http.h b/http.h
index 02bccb7..f7bd3b2 100644
--- a/http.h
+++ b/http.h
@@ -111,7 +111,7 @@ extern struct curl_slist *http_copy_default_headers(void);
extern long int git_curl_ipresolve;
extern int active_requests;
extern int http_is_verbose;
-extern size_t http_post_buffer;
+extern ssize_t http_post_buffer;
extern struct credential http_auth;
extern char curl_errorstr[CURL_ERROR_SIZE];
diff --git a/ident.c b/ident.c
index c0364fe..bea871c 100644
--- a/ident.c
+++ b/ident.c
@@ -120,9 +120,9 @@ static int canonical_name(const char *host, struct strbuf *out)
static void add_domainname(struct strbuf *out, int *is_bogus)
{
- char buf[1024];
+ char buf[HOST_NAME_MAX + 1];
- if (gethostname(buf, sizeof(buf))) {
+ if (xgethostname(buf, sizeof(buf))) {
warning_errno("cannot get host name");
strbuf_addstr(out, "(none)");
*is_bogus = 1;
diff --git a/notes-merge.c b/notes-merge.c
index 5998605..32caaaf 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -676,7 +676,7 @@ int notes_merge_commit(struct notes_merge_options *o,
const char *msg = strstr(buffer, "\n\n");
int baselen;
- strbuf_addstr(&path, git_path(NOTES_MERGE_WORKTREE));
+ git_path_buf(&path, NOTES_MERGE_WORKTREE);
if (o->verbosity >= 3)
printf("Committing notes in notes merge worktree at %s\n",
path.buf);
@@ -741,7 +741,7 @@ int notes_merge_abort(struct notes_merge_options *o)
struct strbuf buf = STRBUF_INIT;
int ret;
- strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
+ git_path_buf(&buf, NOTES_MERGE_WORKTREE);
if (o->verbosity >= 3)
printf("Removing notes merge worktree at %s/*\n", buf.buf);
ret = remove_dir_recursively(&buf, REMOVE_DIR_KEEP_TOPLEVEL);
diff --git a/path.c b/path.c
index 302f9af..c1cb1cf 100644
--- a/path.c
+++ b/path.c
@@ -617,8 +617,10 @@ static struct passwd *getpw_str(const char *username, size_t len)
* Return a string with ~ and ~user expanded via getpw*. If buf != NULL,
* then it is a newly allocated string. Returns NULL on getpw failure or
* if path is NULL.
+ *
+ * If real_home is true, real_path($HOME) is used in the expansion.
*/
-char *expand_user_path(const char *path)
+char *expand_user_path(const char *path, int real_home)
{
struct strbuf user_path = STRBUF_INIT;
const char *to_copy = path;
@@ -633,7 +635,10 @@ char *expand_user_path(const char *path)
const char *home = getenv("HOME");
if (!home)
goto return_null;
- strbuf_addstr(&user_path, home);
+ if (real_home)
+ strbuf_addstr(&user_path, real_path(home));
+ else
+ strbuf_addstr(&user_path, home);
#ifdef GIT_WINDOWS_NATIVE
convert_slashes(user_path.buf);
#endif
@@ -702,7 +707,7 @@ const char *enter_repo(const char *path, int strict)
strbuf_add(&validated_path, path, len);
if (used_path.buf[0] == '~') {
- char *newpath = expand_user_path(used_path.buf);
+ char *newpath = expand_user_path(used_path.buf, 0);
if (!newpath)
return NULL;
strbuf_attach(&used_path, newpath, strlen(newpath),
diff --git a/pathspec.c b/pathspec.c
index 69ef86b..50f76ff 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -505,7 +505,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
* original. Useful for passing to another command.
*/
if ((flags & PATHSPEC_PREFIX_ORIGIN) &&
- prefixlen && !get_literal_global()) {
+ !get_literal_global()) {
struct strbuf sb = STRBUF_INIT;
/* Preserve the actual prefix length of each pattern */
diff --git a/prio-queue.c b/prio-queue.c
index 17252d2..fc3860f 100644
--- a/prio-queue.c
+++ b/prio-queue.c
@@ -21,7 +21,7 @@ void prio_queue_reverse(struct prio_queue *queue)
if (queue->compare != NULL)
die("BUG: prio_queue_reverse() on non-LIFO queue");
- for (i = 0; i <= (j = (queue->nr - 1) - i); i++)
+ for (i = 0; i < (j = (queue->nr - 1) - i); i++)
swap(queue, i, j);
}
diff --git a/read-cache.c b/read-cache.c
index e447751..0d0081a 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -887,9 +887,32 @@ static int has_file_name(struct index_state *istate,
return retval;
}
+
+/*
+ * Like strcmp(), but also return the offset of the first change.
+ * If strings are equal, return the length.
+ */
+int strcmp_offset(const char *s1, const char *s2, size_t *first_change)
+{
+ size_t k;
+
+ if (!first_change)
+ return strcmp(s1, s2);
+
+ for (k = 0; s1[k] == s2[k]; k++)
+ if (s1[k] == '\0')
+ break;
+
+ *first_change = k;
+ return (unsigned char)s1[k] - (unsigned char)s2[k];
+}
+
/*
* Do we have another file with a pathname that is a proper
* subset of the name we're trying to add?
+ *
+ * That is, is there another file in the index with a path
+ * that matches a sub-directory in the given entry?
*/
static int has_dir_name(struct index_state *istate,
const struct cache_entry *ce, int pos, int ok_to_replace)
@@ -898,9 +921,51 @@ static int has_dir_name(struct index_state *istate,
int stage = ce_stage(ce);
const char *name = ce->name;
const char *slash = name + ce_namelen(ce);
+ size_t len_eq_last;
+ int cmp_last = 0;
+
+ /*
+ * We are frequently called during an iteration on a sorted
+ * list of pathnames and while building a new index. Therefore,
+ * there is a high probability that this entry will eventually
+ * be appended to the index, rather than inserted in the middle.
+ * If we can confirm that, we can avoid binary searches on the
+ * components of the pathname.
+ *
+ * Compare the entry's full path with the last path in the index.
+ */
+ if (istate->cache_nr > 0) {
+ cmp_last = strcmp_offset(name,
+ istate->cache[istate->cache_nr - 1]->name,
+ &len_eq_last);
+ if (cmp_last > 0) {
+ if (len_eq_last == 0) {
+ /*
+ * The entry sorts AFTER the last one in the
+ * index and their paths have no common prefix,
+ * so there cannot be a F/D conflict.
+ */
+ return retval;
+ } else {
+ /*
+ * The entry sorts AFTER the last one in the
+ * index, but has a common prefix. Fall through
+ * to the loop below to disect the entry's path
+ * and see where the difference is.
+ */
+ }
+ } else if (cmp_last == 0) {
+ /*
+ * The entry exactly matches the last one in the
+ * index, but because of multiple stage and CE_REMOVE
+ * items, we fall through and let the regular search
+ * code handle it.
+ */
+ }
+ }
for (;;) {
- int len;
+ size_t len;
for (;;) {
if (*--slash == '/')
@@ -910,6 +975,67 @@ static int has_dir_name(struct index_state *istate,
}
len = slash - name;
+ if (cmp_last > 0) {
+ /*
+ * (len + 1) is a directory boundary (including
+ * the trailing slash). And since the loop is
+ * decrementing "slash", the first iteration is
+ * the longest directory prefix; subsequent
+ * iterations consider parent directories.
+ */
+
+ if (len + 1 <= len_eq_last) {
+ /*
+ * The directory prefix (including the trailing
+ * slash) also appears as a prefix in the last
+ * entry, so the remainder cannot collide (because
+ * strcmp said the whole path was greater).
+ *
+ * EQ: last: xxx/A
+ * this: xxx/B
+ *
+ * LT: last: xxx/file_A
+ * this: xxx/file_B
+ */
+ return retval;
+ }
+
+ if (len > len_eq_last) {
+ /*
+ * This part of the directory prefix (excluding
+ * the trailing slash) is longer than the known
+ * equal portions, so this sub-directory cannot
+ * collide with a file.
+ *
+ * GT: last: xxxA
+ * this: xxxB/file
+ */
+ return retval;
+ }
+
+ if (istate->cache_nr > 0 &&
+ ce_namelen(istate->cache[istate->cache_nr - 1]) > len) {
+ /*
+ * The directory prefix lines up with part of
+ * a longer file or directory name, but sorts
+ * after it, so this sub-directory cannot
+ * collide with a file.
+ *
+ * last: xxx/yy-file (because '-' sorts before '/')
+ * this: xxx/yy/abc
+ */
+ return retval;
+ }
+
+ /*
+ * This is a possible collision. Fall through and
+ * let the regular search code handle it.
+ *
+ * last: xxx
+ * this: xxx/file
+ */
+ }
+
pos = index_name_stage_pos(istate, name, len, stage);
if (pos >= 0) {
/*
@@ -1001,7 +1127,16 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
cache_tree_invalidate_path(istate, ce->name);
- pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
+
+ /*
+ * If this entry's path sorts after the last entry in the index,
+ * we can avoid searching for it.
+ */
+ if (istate->cache_nr > 0 &&
+ strcmp(ce->name, istate->cache[istate->cache_nr - 1]->name) > 0)
+ pos = -istate->cache_nr - 1;
+ else
+ pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
/* existing match? Just replace it. */
if (pos >= 0) {
@@ -1371,6 +1506,9 @@ struct ondisk_cache_entry_extended {
ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
ondisk_cache_entry_size(ce_namelen(ce)))
+/* Allow fsck to force verification of the index checksum. */
+int verify_index_checksum;
+
static int verify_hdr(struct cache_header *hdr, unsigned long size)
{
git_SHA_CTX c;
@@ -1382,6 +1520,10 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
hdr_version = ntohl(hdr->hdr_version);
if (hdr_version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < hdr_version)
return error("bad index version %d", hdr_version);
+
+ if (!verify_index_checksum)
+ return 0;
+
git_SHA1_Init(&c);
git_SHA1_Update(&c, hdr, size - 20);
git_SHA1_Final(sha1, &c);
@@ -1682,9 +1824,10 @@ unmap:
*/
static void freshen_shared_index(char *base_sha1_hex, int warn)
{
- const char *shared_index = git_path("sharedindex.%s", base_sha1_hex);
+ char *shared_index = git_pathdup("sharedindex.%s", base_sha1_hex);
if (!check_and_freshen_file(shared_index, 1) && warn)
warning("could not freshen shared index '%s'", shared_index);
+ free(shared_index);
}
int read_index_from(struct index_state *istate, const char *path)
@@ -2229,7 +2372,7 @@ static int should_delete_shared_index(const char *shared_index_path)
if (!expiration)
return 0;
if (stat(shared_index_path, &st))
- return error_errno(_("could not stat '%s"), shared_index_path);
+ return error_errno(_("could not stat '%s'"), shared_index_path);
if (st.st_mtime > expiration)
return 0;
diff --git a/refs.c b/refs.c
index d1e1b43..df75f8e 100644
--- a/refs.c
+++ b/refs.c
@@ -5,6 +5,7 @@
#include "cache.h"
#include "hashmap.h"
#include "lockfile.h"
+#include "iterator.h"
#include "refs.h"
#include "refs/refs-internal.h"
#include "object.h"
@@ -1238,6 +1239,18 @@ int head_ref(each_ref_fn fn, void *cb_data)
return head_ref_submodule(NULL, fn, cb_data);
}
+struct ref_iterator *refs_ref_iterator_begin(
+ struct ref_store *refs,
+ const char *prefix, int trim, int flags)
+{
+ struct ref_iterator *iter;
+
+ iter = refs->be->iterator_begin(refs, prefix, flags);
+ iter = prefix_ref_iterator_begin(iter, prefix, trim);
+
+ return iter;
+}
+
/*
* Call fn for each reference in the specified submodule for which the
* refname begins with prefix. If trim is non-zero, then trim that
@@ -1255,8 +1268,7 @@ static int do_for_each_ref(struct ref_store *refs, const char *prefix,
if (!refs)
return 0;
- iter = refs->be->iterator_begin(refs, prefix, flags);
- iter = prefix_ref_iterator_begin(iter, prefix, trim);
+ iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
return do_for_each_ref_iterator(iter, fn, cb_data);
}
@@ -1334,6 +1346,13 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
return refs_for_each_rawref(get_main_ref_store(), fn, cb_data);
}
+int refs_read_raw_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1,
+ struct strbuf *referent, unsigned int *type)
+{
+ return ref_store->be->read_raw_ref(ref_store, refname, sha1, referent, type);
+}
+
/* This function needs to return a meaningful errno on failure */
const char *refs_resolve_ref_unsafe(struct ref_store *refs,
const char *refname,
@@ -1370,8 +1389,8 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
unsigned int read_flags = 0;
- if (refs->be->read_raw_ref(refs, refname,
- sha1, &sb_refname, &read_flags)) {
+ if (refs_read_raw_ref(refs, refname,
+ sha1, &sb_refname, &read_flags)) {
*flags |= read_flags;
if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING))
return NULL;
@@ -1643,16 +1662,102 @@ int ref_transaction_commit(struct ref_transaction *transaction,
{
struct ref_store *refs = transaction->ref_store;
+ if (getenv(GIT_QUARANTINE_ENVIRONMENT)) {
+ strbuf_addstr(err,
+ _("ref updates forbidden inside quarantine environment"));
+ return -1;
+ }
+
return refs->be->transaction_commit(refs, transaction, err);
}
int refs_verify_refname_available(struct ref_store *refs,
const char *refname,
- const struct string_list *extra,
+ const struct string_list *extras,
const struct string_list *skip,
struct strbuf *err)
{
- return refs->be->verify_refname_available(refs, refname, extra, skip, err);
+ const char *slash;
+ const char *extra_refname;
+ struct strbuf dirname = STRBUF_INIT;
+ struct strbuf referent = STRBUF_INIT;
+ struct object_id oid;
+ unsigned int type;
+ struct ref_iterator *iter;
+ int ok;
+ int ret = -1;
+
+ /*
+ * For the sake of comments in this function, suppose that
+ * refname is "refs/foo/bar".
+ */
+
+ assert(err);
+
+ strbuf_grow(&dirname, strlen(refname) + 1);
+ for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ /* Expand dirname to the new prefix, not including the trailing slash: */
+ strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
+
+ /*
+ * We are still at a leading dir of the refname (e.g.,
+ * "refs/foo"; if there is a reference with that name,
+ * it is a conflict, *unless* it is in skip.
+ */
+ if (skip && string_list_has_string(skip, dirname.buf))
+ continue;
+
+ if (!refs_read_raw_ref(refs, dirname.buf, oid.hash, &referent, &type)) {
+ strbuf_addf(err, "'%s' exists; cannot create '%s'",
+ dirname.buf, refname);
+ goto cleanup;
+ }
+
+ if (extras && string_list_has_string(extras, dirname.buf)) {
+ strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+ refname, dirname.buf);
+ goto cleanup;
+ }
+ }
+
+ /*
+ * We are at the leaf of our refname (e.g., "refs/foo/bar").
+ * There is no point in searching for a reference with that
+ * name, because a refname isn't considered to conflict with
+ * itself. But we still need to check for references whose
+ * names are in the "refs/foo/bar/" namespace, because they
+ * *do* conflict.
+ */
+ strbuf_addstr(&dirname, refname + dirname.len);
+ strbuf_addch(&dirname, '/');
+
+ iter = refs_ref_iterator_begin(refs, dirname.buf, 0,
+ DO_FOR_EACH_INCLUDE_BROKEN);
+ while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
+ if (skip &&
+ string_list_has_string(skip, iter->refname))
+ continue;
+
+ strbuf_addf(err, "'%s' exists; cannot create '%s'",
+ iter->refname, refname);
+ ref_iterator_abort(iter);
+ goto cleanup;
+ }
+
+ if (ok != ITER_DONE)
+ die("BUG: error while iterating over references");
+
+ extra_refname = find_descendant_ref(dirname.buf, extras, skip);
+ if (extra_refname)
+ strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
+ refname, extra_refname);
+ else
+ ret = 0;
+
+cleanup:
+ strbuf_release(&referent);
+ strbuf_release(&dirname);
+ return ret;
}
int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
diff --git a/refs.h b/refs.h
index 49e97d7..07cf4cd 100644
--- a/refs.h
+++ b/refs.h
@@ -97,7 +97,7 @@ int read_ref(const char *refname, unsigned char *sha1);
int refs_verify_refname_available(struct ref_store *refs,
const char *refname,
- const struct string_list *extra,
+ const struct string_list *extras,
const struct string_list *skip,
struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4d705b4..83ea080 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1,6 +1,7 @@
#include "../cache.h"
#include "../refs.h"
#include "refs-internal.h"
+#include "ref-cache.h"
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
@@ -13,511 +14,6 @@ struct ref_lock {
struct object_id old_oid;
};
-struct ref_entry;
-
-/*
- * Information used (along with the information in ref_entry) to
- * describe a single cached reference. This data structure only
- * occurs embedded in a union in struct ref_entry, and only when
- * (ref_entry->flag & REF_DIR) is zero.
- */
-struct ref_value {
- /*
- * The name of the object to which this reference resolves
- * (which may be a tag object). If REF_ISBROKEN, this is
- * null. If REF_ISSYMREF, then this is the name of the object
- * referred to by the last reference in the symlink chain.
- */
- struct object_id oid;
-
- /*
- * If REF_KNOWS_PEELED, then this field holds the peeled value
- * of this reference, or null if the reference is known not to
- * be peelable. See the documentation for peel_ref() for an
- * exact definition of "peelable".
- */
- struct object_id peeled;
-};
-
-struct files_ref_store;
-
-/*
- * Information used (along with the information in ref_entry) to
- * describe a level in the hierarchy of references. This data
- * structure only occurs embedded in a union in struct ref_entry, and
- * only when (ref_entry.flag & REF_DIR) is set. In that case,
- * (ref_entry.flag & REF_INCOMPLETE) determines whether the references
- * in the directory have already been read:
- *
- * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose
- * or packed references, already read.
- *
- * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose
- * references that hasn't been read yet (nor has any of its
- * subdirectories).
- *
- * Entries within a directory are stored within a growable array of
- * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i <
- * sorted are sorted by their component name in strcmp() order and the
- * remaining entries are unsorted.
- *
- * Loose references are read lazily, one directory at a time. When a
- * directory of loose references is read, then all of the references
- * in that directory are stored, and REF_INCOMPLETE stubs are created
- * for any subdirectories, but the subdirectories themselves are not
- * read. The reading is triggered by get_ref_dir().
- */
-struct ref_dir {
- int nr, alloc;
-
- /*
- * Entries with index 0 <= i < sorted are sorted by name. New
- * entries are appended to the list unsorted, and are sorted
- * only when required; thus we avoid the need to sort the list
- * after the addition of every reference.
- */
- int sorted;
-
- /* A pointer to the files_ref_store that contains this ref_dir. */
- struct files_ref_store *ref_store;
-
- struct ref_entry **entries;
-};
-
-/*
- * Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
- * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
- * public values; see refs.h.
- */
-
-/*
- * The field ref_entry->u.value.peeled of this value entry contains
- * the correct peeled value for the reference, which might be
- * null_sha1 if the reference is not a tag or if it is broken.
- */
-#define REF_KNOWS_PEELED 0x10
-
-/* ref_entry represents a directory of references */
-#define REF_DIR 0x20
-
-/*
- * Entry has not yet been read from disk (used only for REF_DIR
- * entries representing loose references)
- */
-#define REF_INCOMPLETE 0x40
-
-/*
- * A ref_entry represents either a reference or a "subdirectory" of
- * references.
- *
- * Each directory in the reference namespace is represented by a
- * ref_entry with (flags & REF_DIR) set and containing a subdir member
- * that holds the entries in that directory that have been read so
- * far. If (flags & REF_INCOMPLETE) is set, then the directory and
- * its subdirectories haven't been read yet. REF_INCOMPLETE is only
- * used for loose reference directories.
- *
- * References are represented by a ref_entry with (flags & REF_DIR)
- * unset and a value member that describes the reference's value. The
- * flag member is at the ref_entry level, but it is also needed to
- * interpret the contents of the value field (in other words, a
- * ref_value object is not very much use without the enclosing
- * ref_entry).
- *
- * Reference names cannot end with slash and directories' names are
- * always stored with a trailing slash (except for the top-level
- * directory, which is always denoted by ""). This has two nice
- * consequences: (1) when the entries in each subdir are sorted
- * lexicographically by name (as they usually are), the references in
- * a whole tree can be generated in lexicographic order by traversing
- * the tree in left-to-right, depth-first order; (2) the names of
- * references and subdirectories cannot conflict, and therefore the
- * presence of an empty subdirectory does not block the creation of a
- * similarly-named reference. (The fact that reference names with the
- * same leading components can conflict *with each other* is a
- * separate issue that is regulated by verify_refname_available().)
- *
- * Please note that the name field contains the fully-qualified
- * reference (or subdirectory) name. Space could be saved by only
- * storing the relative names. But that would require the full names
- * to be generated on the fly when iterating in do_for_each_ref(), and
- * would break callback functions, who have always been able to assume
- * that the name strings that they are passed will not be freed during
- * the iteration.
- */
-struct ref_entry {
- unsigned char flag; /* ISSYMREF? ISPACKED? */
- union {
- struct ref_value value; /* if not (flags&REF_DIR) */
- struct ref_dir subdir; /* if (flags&REF_DIR) */
- } u;
- /*
- * The full name of the reference (e.g., "refs/heads/master")
- * or the full name of the directory with a trailing slash
- * (e.g., "refs/heads/"):
- */
- char name[FLEX_ARRAY];
-};
-
-static void read_loose_refs(const char *dirname, struct ref_dir *dir);
-static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
-static struct ref_entry *create_dir_entry(struct files_ref_store *ref_store,
- const char *dirname, size_t len,
- int incomplete);
-static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
-static int files_log_ref_write(struct files_ref_store *refs,
- const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg,
- int flags, struct strbuf *err);
-
-static struct ref_dir *get_ref_dir(struct ref_entry *entry)
-{
- struct ref_dir *dir;
- assert(entry->flag & REF_DIR);
- dir = &entry->u.subdir;
- if (entry->flag & REF_INCOMPLETE) {
- read_loose_refs(entry->name, dir);
-
- /*
- * Manually add refs/bisect, which, being
- * per-worktree, might not appear in the directory
- * listing for refs/ in the main repo.
- */
- if (!strcmp(entry->name, "refs/")) {
- int pos = search_ref_dir(dir, "refs/bisect/", 12);
- if (pos < 0) {
- struct ref_entry *child_entry;
- child_entry = create_dir_entry(dir->ref_store,
- "refs/bisect/",
- 12, 1);
- add_entry_to_dir(dir, child_entry);
- read_loose_refs("refs/bisect",
- &child_entry->u.subdir);
- }
- }
- entry->flag &= ~REF_INCOMPLETE;
- }
- return dir;
-}
-
-static struct ref_entry *create_ref_entry(const char *refname,
- const unsigned char *sha1, int flag,
- int check_name)
-{
- struct ref_entry *ref;
-
- if (check_name &&
- check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- die("Reference has invalid format: '%s'", refname);
- FLEX_ALLOC_STR(ref, name, refname);
- hashcpy(ref->u.value.oid.hash, sha1);
- oidclr(&ref->u.value.peeled);
- ref->flag = flag;
- return ref;
-}
-
-static void clear_ref_dir(struct ref_dir *dir);
-
-static void free_ref_entry(struct ref_entry *entry)
-{
- if (entry->flag & REF_DIR) {
- /*
- * Do not use get_ref_dir() here, as that might
- * trigger the reading of loose refs.
- */
- clear_ref_dir(&entry->u.subdir);
- }
- free(entry);
-}
-
-/*
- * Add a ref_entry to the end of dir (unsorted). Entry is always
- * stored directly in dir; no recursion into subdirectories is
- * done.
- */
-static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry)
-{
- ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc);
- dir->entries[dir->nr++] = entry;
- /* optimize for the case that entries are added in order */
- if (dir->nr == 1 ||
- (dir->nr == dir->sorted + 1 &&
- strcmp(dir->entries[dir->nr - 2]->name,
- dir->entries[dir->nr - 1]->name) < 0))
- dir->sorted = dir->nr;
-}
-
-/*
- * Clear and free all entries in dir, recursively.
- */
-static void clear_ref_dir(struct ref_dir *dir)
-{
- int i;
- for (i = 0; i < dir->nr; i++)
- free_ref_entry(dir->entries[i]);
- free(dir->entries);
- dir->sorted = dir->nr = dir->alloc = 0;
- dir->entries = NULL;
-}
-
-/*
- * Create a struct ref_entry object for the specified dirname.
- * dirname is the name of the directory with a trailing slash (e.g.,
- * "refs/heads/") or "" for the top-level directory.
- */
-static struct ref_entry *create_dir_entry(struct files_ref_store *ref_store,
- const char *dirname, size_t len,
- int incomplete)
-{
- struct ref_entry *direntry;
- FLEX_ALLOC_MEM(direntry, name, dirname, len);
- direntry->u.subdir.ref_store = ref_store;
- direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
- return direntry;
-}
-
-static int ref_entry_cmp(const void *a, const void *b)
-{
- struct ref_entry *one = *(struct ref_entry **)a;
- struct ref_entry *two = *(struct ref_entry **)b;
- return strcmp(one->name, two->name);
-}
-
-static void sort_ref_dir(struct ref_dir *dir);
-
-struct string_slice {
- size_t len;
- const char *str;
-};
-
-static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
-{
- const struct string_slice *key = key_;
- const struct ref_entry *ent = *(const struct ref_entry * const *)ent_;
- int cmp = strncmp(key->str, ent->name, key->len);
- if (cmp)
- return cmp;
- return '\0' - (unsigned char)ent->name[key->len];
-}
-
-/*
- * Return the index of the entry with the given refname from the
- * ref_dir (non-recursively), sorting dir if necessary. Return -1 if
- * no such entry is found. dir must already be complete.
- */
-static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
-{
- struct ref_entry **r;
- struct string_slice key;
-
- if (refname == NULL || !dir->nr)
- return -1;
-
- sort_ref_dir(dir);
- key.len = len;
- key.str = refname;
- r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries),
- ref_entry_cmp_sslice);
-
- if (r == NULL)
- return -1;
-
- return r - dir->entries;
-}
-
-/*
- * Search for a directory entry directly within dir (without
- * recursing). Sort dir if necessary. subdirname must be a directory
- * name (i.e., end in '/'). If mkdir is set, then create the
- * directory if it is missing; otherwise, return NULL if the desired
- * directory cannot be found. dir must already be complete.
- */
-static struct ref_dir *search_for_subdir(struct ref_dir *dir,
- const char *subdirname, size_t len,
- int mkdir)
-{
- int entry_index = search_ref_dir(dir, subdirname, len);
- struct ref_entry *entry;
- if (entry_index == -1) {
- if (!mkdir)
- return NULL;
- /*
- * Since dir is complete, the absence of a subdir
- * means that the subdir really doesn't exist;
- * therefore, create an empty record for it but mark
- * the record complete.
- */
- entry = create_dir_entry(dir->ref_store, subdirname, len, 0);
- add_entry_to_dir(dir, entry);
- } else {
- entry = dir->entries[entry_index];
- }
- return get_ref_dir(entry);
-}
-
-/*
- * If refname is a reference name, find the ref_dir within the dir
- * tree that should hold refname. If refname is a directory name
- * (i.e., ends in '/'), then return that ref_dir itself. dir must
- * represent the top-level directory and must already be complete.
- * Sort ref_dirs and recurse into subdirectories as necessary. If
- * mkdir is set, then create any missing directories; otherwise,
- * return NULL if the desired directory cannot be found.
- */
-static struct ref_dir *find_containing_dir(struct ref_dir *dir,
- const char *refname, int mkdir)
-{
- const char *slash;
- for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
- size_t dirnamelen = slash - refname + 1;
- struct ref_dir *subdir;
- subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
- if (!subdir) {
- dir = NULL;
- break;
- }
- dir = subdir;
- }
-
- return dir;
-}
-
-/*
- * Find the value entry with the given name in dir, sorting ref_dirs
- * and recursing into subdirectories as necessary. If the name is not
- * found or it corresponds to a directory entry, return NULL.
- */
-static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
-{
- int entry_index;
- struct ref_entry *entry;
- dir = find_containing_dir(dir, refname, 0);
- if (!dir)
- return NULL;
- entry_index = search_ref_dir(dir, refname, strlen(refname));
- if (entry_index == -1)
- return NULL;
- entry = dir->entries[entry_index];
- return (entry->flag & REF_DIR) ? NULL : entry;
-}
-
-/*
- * Remove the entry with the given name from dir, recursing into
- * subdirectories as necessary. If refname is the name of a directory
- * (i.e., ends with '/'), then remove the directory and its contents.
- * If the removal was successful, return the number of entries
- * remaining in the directory entry that contained the deleted entry.
- * If the name was not found, return -1. Please note that this
- * function only deletes the entry from the cache; it does not delete
- * it from the filesystem or ensure that other cache entries (which
- * might be symbolic references to the removed entry) are updated.
- * Nor does it remove any containing dir entries that might be made
- * empty by the removal. dir must represent the top-level directory
- * and must already be complete.
- */
-static int remove_entry(struct ref_dir *dir, const char *refname)
-{
- int refname_len = strlen(refname);
- int entry_index;
- struct ref_entry *entry;
- int is_dir = refname[refname_len - 1] == '/';
- if (is_dir) {
- /*
- * refname represents a reference directory. Remove
- * the trailing slash; otherwise we will get the
- * directory *representing* refname rather than the
- * one *containing* it.
- */
- char *dirname = xmemdupz(refname, refname_len - 1);
- dir = find_containing_dir(dir, dirname, 0);
- free(dirname);
- } else {
- dir = find_containing_dir(dir, refname, 0);
- }
- if (!dir)
- return -1;
- entry_index = search_ref_dir(dir, refname, refname_len);
- if (entry_index == -1)
- return -1;
- entry = dir->entries[entry_index];
-
- memmove(&dir->entries[entry_index],
- &dir->entries[entry_index + 1],
- (dir->nr - entry_index - 1) * sizeof(*dir->entries)
- );
- dir->nr--;
- if (dir->sorted > entry_index)
- dir->sorted--;
- free_ref_entry(entry);
- return dir->nr;
-}
-
-/*
- * Add a ref_entry to the ref_dir (unsorted), recursing into
- * subdirectories as necessary. dir must represent the top-level
- * directory. Return 0 on success.
- */
-static int add_ref(struct ref_dir *dir, struct ref_entry *ref)
-{
- dir = find_containing_dir(dir, ref->name, 1);
- if (!dir)
- return -1;
- add_entry_to_dir(dir, ref);
- return 0;
-}
-
-/*
- * Emit a warning and return true iff ref1 and ref2 have the same name
- * and the same sha1. Die if they have the same name but different
- * sha1s.
- */
-static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
-{
- if (strcmp(ref1->name, ref2->name))
- return 0;
-
- /* Duplicate name; make sure that they don't conflict: */
-
- if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR))
- /* This is impossible by construction */
- die("Reference directory conflict: %s", ref1->name);
-
- if (oidcmp(&ref1->u.value.oid, &ref2->u.value.oid))
- die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
-
- warning("Duplicated ref: %s", ref1->name);
- return 1;
-}
-
-/*
- * Sort the entries in dir non-recursively (if they are not already
- * sorted) and remove any duplicate entries.
- */
-static void sort_ref_dir(struct ref_dir *dir)
-{
- int i, j;
- struct ref_entry *last = NULL;
-
- /*
- * This check also prevents passing a zero-length array to qsort(),
- * which is a problem on some platforms.
- */
- if (dir->sorted == dir->nr)
- return;
-
- QSORT(dir->entries, dir->nr, ref_entry_cmp);
-
- /* Remove any duplicates: */
- for (i = 0, j = 0; j < dir->nr; j++) {
- struct ref_entry *entry = dir->entries[j];
- if (last && is_dup_ref(last, entry))
- free_ref_entry(entry);
- else
- last = dir->entries[i++] = entry;
- }
- dir->sorted = dir->nr = i;
-}
-
/*
* Return true if refname, which has the specified oid and flags, can
* be resolved to an object in the database. If the referred-to object
@@ -536,358 +32,8 @@ static int ref_resolves_to_object(const char *refname,
return 1;
}
-/*
- * Return true if the reference described by entry can be resolved to
- * an object in the database; otherwise, emit a warning and return
- * false.
- */
-static int entry_resolves_to_object(struct ref_entry *entry)
-{
- return ref_resolves_to_object(entry->name,
- &entry->u.value.oid, entry->flag);
-}
-
-typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
-
-/*
- * Call fn for each reference in dir that has index in the range
- * offset <= index < dir->nr. Recurse into subdirectories that are in
- * that index range, sorting them before iterating. This function
- * does not sort dir itself; it should be sorted beforehand. fn is
- * called for all references, including broken ones.
- */
-static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
- each_ref_entry_fn fn, void *cb_data)
-{
- int i;
- assert(dir->sorted == dir->nr);
- for (i = offset; i < dir->nr; i++) {
- struct ref_entry *entry = dir->entries[i];
- int retval;
- if (entry->flag & REF_DIR) {
- struct ref_dir *subdir = get_ref_dir(entry);
- sort_ref_dir(subdir);
- retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data);
- } else {
- retval = fn(entry, cb_data);
- }
- if (retval)
- return retval;
- }
- return 0;
-}
-
-/*
- * Load all of the refs from the dir into our in-memory cache. The hard work
- * of loading loose refs is done by get_ref_dir(), so we just need to recurse
- * through all of the sub-directories. We do not even need to care about
- * sorting, as traversal order does not matter to us.
- */
-static void prime_ref_dir(struct ref_dir *dir)
-{
- int i;
- for (i = 0; i < dir->nr; i++) {
- struct ref_entry *entry = dir->entries[i];
- if (entry->flag & REF_DIR)
- prime_ref_dir(get_ref_dir(entry));
- }
-}
-
-/*
- * A level in the reference hierarchy that is currently being iterated
- * through.
- */
-struct cache_ref_iterator_level {
- /*
- * The ref_dir being iterated over at this level. The ref_dir
- * is sorted before being stored here.
- */
- struct ref_dir *dir;
-
- /*
- * The index of the current entry within dir (which might
- * itself be a directory). If index == -1, then the iteration
- * hasn't yet begun. If index == dir->nr, then the iteration
- * through this level is over.
- */
- int index;
-};
-
-/*
- * Represent an iteration through a ref_dir in the memory cache. The
- * iteration recurses through subdirectories.
- */
-struct cache_ref_iterator {
- struct ref_iterator base;
-
- /*
- * The number of levels currently on the stack. This is always
- * at least 1, because when it becomes zero the iteration is
- * ended and this struct is freed.
- */
- size_t levels_nr;
-
- /* The number of levels that have been allocated on the stack */
- size_t levels_alloc;
-
- /*
- * A stack of levels. levels[0] is the uppermost level that is
- * being iterated over in this iteration. (This is not
- * necessary the top level in the references hierarchy. If we
- * are iterating through a subtree, then levels[0] will hold
- * the ref_dir for that subtree, and subsequent levels will go
- * on from there.)
- */
- struct cache_ref_iterator_level *levels;
-};
-
-static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
-{
- struct cache_ref_iterator *iter =
- (struct cache_ref_iterator *)ref_iterator;
-
- while (1) {
- struct cache_ref_iterator_level *level =
- &iter->levels[iter->levels_nr - 1];
- struct ref_dir *dir = level->dir;
- struct ref_entry *entry;
-
- if (level->index == -1)
- sort_ref_dir(dir);
-
- if (++level->index == level->dir->nr) {
- /* This level is exhausted; pop up a level */
- if (--iter->levels_nr == 0)
- return ref_iterator_abort(ref_iterator);
-
- continue;
- }
-
- entry = dir->entries[level->index];
-
- if (entry->flag & REF_DIR) {
- /* push down a level */
- ALLOC_GROW(iter->levels, iter->levels_nr + 1,
- iter->levels_alloc);
-
- level = &iter->levels[iter->levels_nr++];
- level->dir = get_ref_dir(entry);
- level->index = -1;
- } else {
- iter->base.refname = entry->name;
- iter->base.oid = &entry->u.value.oid;
- iter->base.flags = entry->flag;
- return ITER_OK;
- }
- }
-}
-
-static enum peel_status peel_entry(struct ref_entry *entry, int repeel);
-
-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
- struct object_id *peeled)
-{
- struct cache_ref_iterator *iter =
- (struct cache_ref_iterator *)ref_iterator;
- struct cache_ref_iterator_level *level;
- struct ref_entry *entry;
-
- level = &iter->levels[iter->levels_nr - 1];
-
- if (level->index == -1)
- die("BUG: peel called before advance for cache iterator");
-
- entry = level->dir->entries[level->index];
-
- if (peel_entry(entry, 0))
- return -1;
- oidcpy(peeled, &entry->u.value.peeled);
- return 0;
-}
-
-static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
-{
- struct cache_ref_iterator *iter =
- (struct cache_ref_iterator *)ref_iterator;
-
- free(iter->levels);
- base_ref_iterator_free(ref_iterator);
- return ITER_DONE;
-}
-
-static struct ref_iterator_vtable cache_ref_iterator_vtable = {
- cache_ref_iterator_advance,
- cache_ref_iterator_peel,
- cache_ref_iterator_abort
-};
-
-static struct ref_iterator *cache_ref_iterator_begin(struct ref_dir *dir)
-{
- struct cache_ref_iterator *iter;
- struct ref_iterator *ref_iterator;
- struct cache_ref_iterator_level *level;
-
- iter = xcalloc(1, sizeof(*iter));
- ref_iterator = &iter->base;
- base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
- ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
-
- iter->levels_nr = 1;
- level = &iter->levels[0];
- level->index = -1;
- level->dir = dir;
-
- return ref_iterator;
-}
-
-struct nonmatching_ref_data {
- const struct string_list *skip;
- const char *conflicting_refname;
-};
-
-static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
-{
- struct nonmatching_ref_data *data = vdata;
-
- if (data->skip && string_list_has_string(data->skip, entry->name))
- return 0;
-
- data->conflicting_refname = entry->name;
- return 1;
-}
-
-/*
- * Return 0 if a reference named refname could be created without
- * conflicting with the name of an existing reference in dir.
- * See verify_refname_available for more information.
- */
-static int verify_refname_available_dir(const char *refname,
- const struct string_list *extras,
- const struct string_list *skip,
- struct ref_dir *dir,
- struct strbuf *err)
-{
- const char *slash;
- const char *extra_refname;
- int pos;
- struct strbuf dirname = STRBUF_INIT;
- int ret = -1;
-
- /*
- * For the sake of comments in this function, suppose that
- * refname is "refs/foo/bar".
- */
-
- assert(err);
-
- strbuf_grow(&dirname, strlen(refname) + 1);
- for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
- /* Expand dirname to the new prefix, not including the trailing slash: */
- strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
-
- /*
- * We are still at a leading dir of the refname (e.g.,
- * "refs/foo"; if there is a reference with that name,
- * it is a conflict, *unless* it is in skip.
- */
- if (dir) {
- pos = search_ref_dir(dir, dirname.buf, dirname.len);
- if (pos >= 0 &&
- (!skip || !string_list_has_string(skip, dirname.buf))) {
- /*
- * We found a reference whose name is
- * a proper prefix of refname; e.g.,
- * "refs/foo", and is not in skip.
- */
- strbuf_addf(err, "'%s' exists; cannot create '%s'",
- dirname.buf, refname);
- goto cleanup;
- }
- }
-
- if (extras && string_list_has_string(extras, dirname.buf) &&
- (!skip || !string_list_has_string(skip, dirname.buf))) {
- strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
- refname, dirname.buf);
- goto cleanup;
- }
-
- /*
- * Otherwise, we can try to continue our search with
- * the next component. So try to look up the
- * directory, e.g., "refs/foo/". If we come up empty,
- * we know there is nothing under this whole prefix,
- * but even in that case we still have to continue the
- * search for conflicts with extras.
- */
- strbuf_addch(&dirname, '/');
- if (dir) {
- pos = search_ref_dir(dir, dirname.buf, dirname.len);
- if (pos < 0) {
- /*
- * There was no directory "refs/foo/",
- * so there is nothing under this
- * whole prefix. So there is no need
- * to continue looking for conflicting
- * references. But we need to continue
- * looking for conflicting extras.
- */
- dir = NULL;
- } else {
- dir = get_ref_dir(dir->entries[pos]);
- }
- }
- }
-
- /*
- * We are at the leaf of our refname (e.g., "refs/foo/bar").
- * There is no point in searching for a reference with that
- * name, because a refname isn't considered to conflict with
- * itself. But we still need to check for references whose
- * names are in the "refs/foo/bar/" namespace, because they
- * *do* conflict.
- */
- strbuf_addstr(&dirname, refname + dirname.len);
- strbuf_addch(&dirname, '/');
-
- if (dir) {
- pos = search_ref_dir(dir, dirname.buf, dirname.len);
-
- if (pos >= 0) {
- /*
- * We found a directory named "$refname/"
- * (e.g., "refs/foo/bar/"). It is a problem
- * iff it contains any ref that is not in
- * "skip".
- */
- struct nonmatching_ref_data data;
-
- data.skip = skip;
- data.conflicting_refname = NULL;
- dir = get_ref_dir(dir->entries[pos]);
- sort_ref_dir(dir);
- if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) {
- strbuf_addf(err, "'%s' exists; cannot create '%s'",
- data.conflicting_refname, refname);
- goto cleanup;
- }
- }
- }
-
- extra_refname = find_descendant_ref(dirname.buf, extras, skip);
- if (extra_refname)
- strbuf_addf(err, "cannot process '%s' and '%s' at the same time",
- refname, extra_refname);
- else
- ret = 0;
-
-cleanup:
- strbuf_release(&dirname);
- return ret;
-}
-
struct packed_ref_cache {
- struct ref_entry *root;
+ struct ref_cache *cache;
/*
* Count of references to the data structure in this instance,
@@ -922,7 +68,7 @@ struct files_ref_store {
char *gitcommondir;
char *packed_refs_path;
- struct ref_entry *loose;
+ struct ref_cache *loose;
struct packed_ref_cache *packed;
};
@@ -944,7 +90,7 @@ static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
{
if (!--packed_refs->referrers) {
- free_ref_entry(packed_refs->root);
+ free_ref_cache(packed_refs->cache);
stat_validity_clear(&packed_refs->validity);
free(packed_refs);
return 1;
@@ -968,7 +114,7 @@ static void clear_packed_ref_cache(struct files_ref_store *refs)
static void clear_loose_ref_cache(struct files_ref_store *refs)
{
if (refs->loose) {
- free_ref_entry(refs->loose);
+ free_ref_cache(refs->loose);
refs->loose = NULL;
}
}
@@ -1141,7 +287,7 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
if (peeled == PEELED_FULLY ||
(peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
last->flag |= REF_KNOWS_PEELED;
- add_ref(dir, last);
+ add_ref_entry(dir, last);
continue;
}
if (last &&
@@ -1229,11 +375,12 @@ static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *ref
refs->packed = xcalloc(1, sizeof(*refs->packed));
acquire_packed_ref_cache(refs->packed);
- refs->packed->root = create_dir_entry(refs, "", 0, 0);
+ refs->packed->cache = create_ref_cache(&refs->base, NULL);
+ refs->packed->cache->root->flag &= ~REF_INCOMPLETE;
f = fopen(packed_refs_file, "r");
if (f) {
stat_validity_update(&refs->packed->validity, fileno(f));
- read_packed_refs(f, get_ref_dir(refs->packed->root));
+ read_packed_refs(f, get_ref_dir(refs->packed->cache->root));
fclose(f);
}
}
@@ -1242,7 +389,7 @@ static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *ref
static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
{
- return get_ref_dir(packed_ref_cache->root);
+ return get_ref_dir(packed_ref_cache->cache->root);
}
static struct ref_dir *get_packed_refs(struct files_ref_store *refs)
@@ -1263,8 +410,8 @@ static void add_packed_ref(struct files_ref_store *refs,
if (!packed_ref_cache->lock)
die("internal error: packed refs not locked");
- add_ref(get_packed_ref_dir(packed_ref_cache),
- create_ref_entry(refname, sha1, REF_ISPACKED, 1));
+ add_ref_entry(get_packed_ref_dir(packed_ref_cache),
+ create_ref_entry(refname, sha1, REF_ISPACKED, 1));
}
/*
@@ -1272,9 +419,11 @@ static void add_packed_ref(struct files_ref_store *refs,
* (without recursing). dirname must end with '/'. dir must be the
* directory entry corresponding to dirname.
*/
-static void read_loose_refs(const char *dirname, struct ref_dir *dir)
+static void loose_fill_ref_dir(struct ref_store *ref_store,
+ struct ref_dir *dir, const char *dirname)
{
- struct files_ref_store *refs = dir->ref_store;
+ struct files_ref_store *refs =
+ files_downcast(ref_store, REF_STORE_READ, "fill_ref_dir");
DIR *d;
struct dirent *de;
int dirnamelen = strlen(dirname);
@@ -1310,7 +459,7 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
} else if (S_ISDIR(st.st_mode)) {
strbuf_addch(&refname, '/');
add_entry_to_dir(dir,
- create_dir_entry(refs, refname.buf,
+ create_dir_entry(dir->cache, refname.buf,
refname.len, 1));
} else {
if (!refs_resolve_ref_unsafe(&refs->base,
@@ -1347,9 +496,24 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
strbuf_release(&refname);
strbuf_release(&path);
closedir(d);
+
+ /*
+ * Manually add refs/bisect, which, being per-worktree, might
+ * not appear in the directory listing for refs/ in the main
+ * repo.
+ */
+ if (!strcmp(dirname, "refs/")) {
+ int pos = search_ref_dir(dir, "refs/bisect/", 12);
+
+ if (pos < 0) {
+ struct ref_entry *child_entry = create_dir_entry(
+ dir->cache, "refs/bisect/", 12, 1);
+ add_entry_to_dir(dir, child_entry);
+ }
+ }
}
-static struct ref_dir *get_loose_refs(struct files_ref_store *refs)
+static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs)
{
if (!refs->loose) {
/*
@@ -1357,14 +521,19 @@ static struct ref_dir *get_loose_refs(struct files_ref_store *refs)
* are about to read the only subdirectory that can
* hold references:
*/
- refs->loose = create_dir_entry(refs, "", 0, 0);
+ refs->loose = create_ref_cache(&refs->base, loose_fill_ref_dir);
+
+ /* We're going to fill the top level ourselves: */
+ refs->loose->root->flag &= ~REF_INCOMPLETE;
+
/*
- * Create an incomplete entry for "refs/":
+ * Add an incomplete entry for "refs/" (to be filled
+ * lazily):
*/
- add_entry_to_dir(get_ref_dir(refs->loose),
- create_dir_entry(refs, "refs/", 5, 1));
+ add_entry_to_dir(get_ref_dir(refs->loose->root),
+ create_dir_entry(refs->loose, "refs/", 5, 1));
}
- return get_ref_dir(refs->loose);
+ return refs->loose;
}
/*
@@ -1374,7 +543,7 @@ static struct ref_dir *get_loose_refs(struct files_ref_store *refs)
static struct ref_entry *get_packed_ref(struct files_ref_store *refs,
const char *refname)
{
- return find_ref(get_packed_refs(refs), refname);
+ return find_ref_entry(get_packed_refs(refs), refname);
}
/*
@@ -1564,7 +733,7 @@ static void unlock_ref(struct ref_lock *lock)
*
* If the reference doesn't already exist, verify that refname doesn't
* have a D/F conflict with any existing references. extras and skip
- * are passed to verify_refname_available_dir() for this check.
+ * are passed to refs_verify_refname_available() for this check.
*
* If mustexist is not set and the reference is not found or is
* broken, lock the reference anyway but clear sha1.
@@ -1579,7 +748,7 @@ static void unlock_ref(struct ref_lock *lock)
*
* but it includes a lot more code to
* - Deal with possible races with other processes
- * - Avoid calling verify_refname_available_dir() when it can be
+ * - Avoid calling refs_verify_refname_available() when it can be
* avoided, namely if we were successfully able to read the ref
* - Generate informative error messages in the case of failure
*/
@@ -1636,7 +805,8 @@ retry:
} else {
/*
* The error message set by
- * verify_refname_available_dir() is OK.
+ * refs_verify_refname_available() is
+ * OK.
*/
ret = TRANSACTION_NAME_CONFLICT;
}
@@ -1726,10 +896,9 @@ retry:
goto error_return;
} else if (remove_dir_recursively(&ref_file,
REMOVE_DIR_EMPTY_ONLY)) {
- if (verify_refname_available_dir(
- refname, extras, skip,
- get_loose_refs(refs),
- err)) {
+ if (refs_verify_refname_available(
+ &refs->base, refname,
+ extras, skip, err)) {
/*
* The error message set by
* verify_refname_available() is OK.
@@ -1761,16 +930,13 @@ retry:
/*
* If the ref did not exist and we are creating it,
- * make sure there is no existing packed ref whose
- * name begins with our refname, nor a packed ref
- * whose name is a proper prefix of our refname.
+ * make sure there is no existing ref that conflicts
+ * with refname:
*/
- if (verify_refname_available_dir(
- refname, extras, skip,
- get_packed_refs(refs),
- err)) {
+ if (refs_verify_refname_available(
+ &refs->base, refname,
+ extras, skip, err))
goto error_return;
- }
}
ret = 0;
@@ -1785,41 +951,6 @@ out:
return ret;
}
-/*
- * Peel the entry (if possible) and return its new peel_status. If
- * repeel is true, re-peel the entry even if there is an old peeled
- * value that is already stored in it.
- *
- * It is OK to call this function with a packed reference entry that
- * might be stale and might even refer to an object that has since
- * been garbage-collected. In such a case, if the entry has
- * REF_KNOWS_PEELED then leave the status unchanged and return
- * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
- */
-static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
-{
- enum peel_status status;
-
- if (entry->flag & REF_KNOWS_PEELED) {
- if (repeel) {
- entry->flag &= ~REF_KNOWS_PEELED;
- oidclr(&entry->u.value.peeled);
- } else {
- return is_null_oid(&entry->u.value.peeled) ?
- PEEL_NON_TAG : PEEL_PEELED;
- }
- }
- if (entry->flag & REF_ISBROKEN)
- return PEEL_BROKEN;
- if (entry->flag & REF_ISSYMREF)
- return PEEL_IS_SYMREF;
-
- status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash);
- if (status == PEEL_PEELED || status == PEEL_NON_TAG)
- entry->flag |= REF_KNOWS_PEELED;
- return status;
-}
-
static int files_peel_ref(struct ref_store *ref_store,
const char *refname, unsigned char *sha1)
{
@@ -1935,7 +1066,6 @@ static struct ref_iterator *files_ref_iterator_begin(
const char *prefix, unsigned int flags)
{
struct files_ref_store *refs;
- struct ref_dir *loose_dir, *packed_dir;
struct ref_iterator *loose_iter, *packed_iter;
struct files_ref_iterator *iter;
struct ref_iterator *ref_iterator;
@@ -1959,41 +1089,24 @@ static struct ref_iterator *files_ref_iterator_begin(
* condition if loose refs are migrated to the packed-refs
* file by a simultaneous process, but our in-memory view is
* from before the migration. We ensure this as follows:
- * First, we call prime_ref_dir(), which pre-reads the loose
- * references for the subtree into the cache. (If they've
- * already been read, that's OK; we only need to guarantee
- * that they're read before the packed refs, not *how much*
- * before.) After that, we call get_packed_ref_cache(), which
- * internally checks whether the packed-ref cache is up to
- * date with what is on disk, and re-reads it if not.
+ * First, we call start the loose refs iteration with its
+ * `prime_ref` argument set to true. This causes the loose
+ * references in the subtree to be pre-read into the cache.
+ * (If they've already been read, that's OK; we only need to
+ * guarantee that they're read before the packed refs, not
+ * *how much* before.) After that, we call
+ * get_packed_ref_cache(), which internally checks whether the
+ * packed-ref cache is up to date with what is on disk, and
+ * re-reads it if not.
*/
- loose_dir = get_loose_refs(refs);
-
- if (prefix && *prefix)
- loose_dir = find_containing_dir(loose_dir, prefix, 0);
-
- if (loose_dir) {
- prime_ref_dir(loose_dir);
- loose_iter = cache_ref_iterator_begin(loose_dir);
- } else {
- /* There's nothing to iterate over. */
- loose_iter = empty_ref_iterator_begin();
- }
+ loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
+ prefix, 1);
iter->packed_ref_cache = get_packed_ref_cache(refs);
acquire_packed_ref_cache(iter->packed_ref_cache);
- packed_dir = get_packed_ref_dir(iter->packed_ref_cache);
-
- if (prefix && *prefix)
- packed_dir = find_containing_dir(packed_dir, prefix, 0);
-
- if (packed_dir) {
- packed_iter = cache_ref_iterator_begin(packed_dir);
- } else {
- /* There's nothing to iterate over. */
- packed_iter = empty_ref_iterator_begin();
- }
+ packed_iter = cache_ref_iterator_begin(iter->packed_ref_cache->cache,
+ prefix, 0);
iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
iter->flags = flags;
@@ -2096,9 +1209,9 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
*/
if (remove_empty_directories(&ref_file)) {
last_errno = errno;
- if (!verify_refname_available_dir(
- refname, extras, skip,
- get_loose_refs(refs), err))
+ if (!refs_verify_refname_available(
+ &refs->base,
+ refname, extras, skip, err))
strbuf_addf(err, "there are still refs under '%s'",
refname);
goto error_return;
@@ -2110,9 +1223,8 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
if (!resolved) {
last_errno = errno;
if (last_errno != ENOTDIR ||
- !verify_refname_available_dir(
- refname, extras, skip,
- get_loose_refs(refs), err))
+ !refs_verify_refname_available(&refs->base, refname,
+ extras, skip, err))
strbuf_addf(err, "unable to resolve reference '%s': %s",
refname, strerror(last_errno));
@@ -2126,9 +1238,8 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
* our refname.
*/
if (is_null_oid(&lock->old_oid) &&
- verify_refname_available_dir(refname, extras, skip,
- get_packed_refs(refs),
- err)) {
+ refs_verify_refname_available(&refs->base, refname,
+ extras, skip, err)) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -2163,8 +1274,9 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs,
* Write an entry to the packed-refs file for the specified refname.
* If peeled is non-NULL, write it as the entry's peeled value.
*/
-static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
- unsigned char *peeled)
+static void write_packed_entry(FILE *fh, const char *refname,
+ const unsigned char *sha1,
+ const unsigned char *peeled)
{
fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
if (peeled)
@@ -2172,22 +1284,6 @@ static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
}
/*
- * An each_ref_entry_fn that writes the entry to a packed-refs file.
- */
-static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
-{
- enum peel_status peel_status = peel_entry(entry, 0);
-
- if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
- error("internal error: %s is not a valid packed reference!",
- entry->name);
- write_packed_entry(cb_data, entry->name, entry->u.value.oid.hash,
- peel_status == PEEL_PEELED ?
- entry->u.value.peeled.hash : NULL);
- return 0;
-}
-
-/*
* Lock the packed-refs file for writing. Flags is passed to
* hold_lock_file_for_update(). Return 0 on success. On errors, set
* errno appropriately and return a nonzero value.
@@ -2232,9 +1328,10 @@ static int commit_packed_refs(struct files_ref_store *refs)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(refs);
- int error = 0;
+ int ok, error = 0;
int save_errno = 0;
FILE *out;
+ struct ref_iterator *iter;
files_assert_main_repository(refs, "commit_packed_refs");
@@ -2246,8 +1343,18 @@ static int commit_packed_refs(struct files_ref_store *refs)
die_errno("unable to fdopen packed-refs descriptor");
fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
- do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
- 0, write_packed_entry_fn, out);
+
+ iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
+ while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
+ struct object_id peeled;
+ int peel_error = ref_iterator_peel(iter, &peeled);
+
+ write_packed_entry(out, iter->refname, iter->oid->hash,
+ peel_error ? NULL : peeled.hash);
+ }
+
+ if (ok != ITER_DONE)
+ die("error while iterating over references");
if (commit_lock_file(packed_ref_cache->lock)) {
save_errno = errno;
@@ -2285,65 +1392,6 @@ struct ref_to_prune {
char name[FLEX_ARRAY];
};
-struct pack_refs_cb_data {
- unsigned int flags;
- struct ref_dir *packed_refs;
- struct ref_to_prune *ref_to_prune;
-};
-
-/*
- * An each_ref_entry_fn that is run over loose references only. If
- * the loose reference can be packed, add an entry in the packed ref
- * cache. If the reference should be pruned, also add it to
- * ref_to_prune in the pack_refs_cb_data.
- */
-static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
-{
- struct pack_refs_cb_data *cb = cb_data;
- enum peel_status peel_status;
- struct ref_entry *packed_entry;
- int is_tag_ref = starts_with(entry->name, "refs/tags/");
-
- /* Do not pack per-worktree refs: */
- if (ref_type(entry->name) != REF_TYPE_NORMAL)
- return 0;
-
- /* ALWAYS pack tags */
- if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
- return 0;
-
- /* Do not pack symbolic or broken refs: */
- if ((entry->flag & REF_ISSYMREF) || !entry_resolves_to_object(entry))
- return 0;
-
- /* Add a packed ref cache entry equivalent to the loose entry. */
- peel_status = peel_entry(entry, 1);
- if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
- die("internal error peeling reference %s (%s)",
- entry->name, oid_to_hex(&entry->u.value.oid));
- packed_entry = find_ref(cb->packed_refs, entry->name);
- if (packed_entry) {
- /* Overwrite existing packed entry with info from loose entry */
- packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED;
- oidcpy(&packed_entry->u.value.oid, &entry->u.value.oid);
- } else {
- packed_entry = create_ref_entry(entry->name, entry->u.value.oid.hash,
- REF_ISPACKED | REF_KNOWS_PEELED, 0);
- add_ref(cb->packed_refs, packed_entry);
- }
- oidcpy(&packed_entry->u.value.peeled, &entry->u.value.peeled);
-
- /* Schedule the loose reference for pruning if requested. */
- if ((cb->flags & PACK_REFS_PRUNE)) {
- struct ref_to_prune *n;
- FLEX_ALLOC_STR(n, name, entry->name);
- hashcpy(n->sha1, entry->u.value.oid.hash);
- n->next = cb->ref_to_prune;
- cb->ref_to_prune = n;
- }
- return 0;
-}
-
enum {
REMOVE_EMPTY_PARENTS_REF = 0x01,
REMOVE_EMPTY_PARENTS_REFLOG = 0x02
@@ -2433,21 +1481,73 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB,
"pack_refs");
- struct pack_refs_cb_data cbdata;
-
- memset(&cbdata, 0, sizeof(cbdata));
- cbdata.flags = flags;
+ struct ref_iterator *iter;
+ struct ref_dir *packed_refs;
+ int ok;
+ struct ref_to_prune *refs_to_prune = NULL;
lock_packed_refs(refs, LOCK_DIE_ON_ERROR);
- cbdata.packed_refs = get_packed_refs(refs);
+ packed_refs = get_packed_refs(refs);
+
+ iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
+ while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
+ /*
+ * If the loose reference can be packed, add an entry
+ * in the packed ref cache. If the reference should be
+ * pruned, also add it to refs_to_prune.
+ */
+ struct ref_entry *packed_entry;
+ int is_tag_ref = starts_with(iter->refname, "refs/tags/");
- do_for_each_entry_in_dir(get_loose_refs(refs), 0,
- pack_if_possible_fn, &cbdata);
+ /* Do not pack per-worktree refs: */
+ if (ref_type(iter->refname) != REF_TYPE_NORMAL)
+ continue;
+
+ /* ALWAYS pack tags */
+ if (!(flags & PACK_REFS_ALL) && !is_tag_ref)
+ continue;
+
+ /* Do not pack symbolic or broken refs: */
+ if (iter->flags & REF_ISSYMREF)
+ continue;
+
+ if (!ref_resolves_to_object(iter->refname, iter->oid, iter->flags))
+ continue;
+
+ /*
+ * Create an entry in the packed-refs cache equivalent
+ * to the one from the loose ref cache, except that
+ * we don't copy the peeled status, because we want it
+ * to be re-peeled.
+ */
+ packed_entry = find_ref_entry(packed_refs, iter->refname);
+ if (packed_entry) {
+ /* Overwrite existing packed entry with info from loose entry */
+ packed_entry->flag = REF_ISPACKED;
+ oidcpy(&packed_entry->u.value.oid, iter->oid);
+ } else {
+ packed_entry = create_ref_entry(iter->refname, iter->oid->hash,
+ REF_ISPACKED, 0);
+ add_ref_entry(packed_refs, packed_entry);
+ }
+ oidclr(&packed_entry->u.value.peeled);
+
+ /* Schedule the loose reference for pruning if requested. */
+ if ((flags & PACK_REFS_PRUNE)) {
+ struct ref_to_prune *n;
+ FLEX_ALLOC_STR(n, name, iter->refname);
+ hashcpy(n->sha1, iter->oid->hash);
+ n->next = refs_to_prune;
+ refs_to_prune = n;
+ }
+ }
+ if (ok != ITER_DONE)
+ die("error while iterating over references");
if (commit_packed_refs(refs))
die_errno("unable to overwrite old ref-pack file");
- prune_refs(refs, cbdata.ref_to_prune);
+ prune_refs(refs, refs_to_prune);
return 0;
}
@@ -2488,7 +1588,7 @@ static int repack_without_refs(struct files_ref_store *refs,
/* Remove refnames from the cache */
for_each_string_list_item(refname, refnames)
- if (remove_entry(packed, refname->string) != -1)
+ if (remove_entry_from_dir(packed, refname->string) != -1)
removed = 1;
if (!removed) {
/*
@@ -2608,26 +1708,6 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
return ret;
}
-static int files_verify_refname_available(struct ref_store *ref_store,
- const char *newname,
- const struct string_list *extras,
- const struct string_list *skip,
- struct strbuf *err)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_READ, "verify_refname_available");
- struct ref_dir *packed_refs = get_packed_refs(refs);
- struct ref_dir *loose_refs = get_loose_refs(refs);
-
- if (verify_refname_available_dir(newname, extras, skip,
- packed_refs, err) ||
- verify_refname_available_dir(newname, extras, skip,
- loose_refs, err))
- return -1;
-
- return 0;
-}
-
static int write_ref_to_lockfile(struct ref_lock *lock,
const unsigned char *sha1, struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
@@ -3294,8 +2374,8 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
/* Jump to the end */
if (fseek(logfp, 0, SEEK_END) < 0)
- return error("cannot seek back reflog for %s: %s",
- refname, strerror(errno));
+ ret = error("cannot seek back reflog for %s: %s",
+ refname, strerror(errno));
pos = ftell(logfp);
while (!ret && 0 < pos) {
int cnt;
@@ -3305,13 +2385,17 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store,
/* Fill next block from the end */
cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos;
- if (fseek(logfp, pos - cnt, SEEK_SET))
- return error("cannot seek back reflog for %s: %s",
- refname, strerror(errno));
+ if (fseek(logfp, pos - cnt, SEEK_SET)) {
+ ret = error("cannot seek back reflog for %s: %s",
+ refname, strerror(errno));
+ break;
+ }
nread = fread(buf, cnt, 1, logfp);
- if (nread != 1)
- return error("cannot read %d bytes from reflog for %s: %s",
- cnt, refname, strerror(errno));
+ if (nread != 1) {
+ ret = error("cannot read %d bytes from reflog for %s: %s",
+ cnt, refname, strerror(errno));
+ break;
+ }
pos -= cnt;
scanp = endp = buf + cnt;
@@ -4290,7 +3374,6 @@ struct ref_storage_be refs_be_files = {
files_ref_iterator_begin,
files_read_raw_ref,
- files_verify_refname_available,
files_reflog_iterator_begin,
files_for_each_reflog_ent,
diff --git a/refs/ref-cache.c b/refs/ref-cache.c
new file mode 100644
index 0000000..6059362
--- /dev/null
+++ b/refs/ref-cache.c
@@ -0,0 +1,523 @@
+#include "../cache.h"
+#include "../refs.h"
+#include "refs-internal.h"
+#include "ref-cache.h"
+#include "../iterator.h"
+
+void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry)
+{
+ ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc);
+ dir->entries[dir->nr++] = entry;
+ /* optimize for the case that entries are added in order */
+ if (dir->nr == 1 ||
+ (dir->nr == dir->sorted + 1 &&
+ strcmp(dir->entries[dir->nr - 2]->name,
+ dir->entries[dir->nr - 1]->name) < 0))
+ dir->sorted = dir->nr;
+}
+
+struct ref_dir *get_ref_dir(struct ref_entry *entry)
+{
+ struct ref_dir *dir;
+ assert(entry->flag & REF_DIR);
+ dir = &entry->u.subdir;
+ if (entry->flag & REF_INCOMPLETE) {
+ if (!dir->cache->fill_ref_dir)
+ die("BUG: incomplete ref_store without fill_ref_dir function");
+
+ dir->cache->fill_ref_dir(dir->cache->ref_store, dir, entry->name);
+ entry->flag &= ~REF_INCOMPLETE;
+ }
+ return dir;
+}
+
+struct ref_entry *create_ref_entry(const char *refname,
+ const unsigned char *sha1, int flag,
+ int check_name)
+{
+ struct ref_entry *ref;
+
+ if (check_name &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
+ die("Reference has invalid format: '%s'", refname);
+ FLEX_ALLOC_STR(ref, name, refname);
+ hashcpy(ref->u.value.oid.hash, sha1);
+ oidclr(&ref->u.value.peeled);
+ ref->flag = flag;
+ return ref;
+}
+
+struct ref_cache *create_ref_cache(struct ref_store *refs,
+ fill_ref_dir_fn *fill_ref_dir)
+{
+ struct ref_cache *ret = xcalloc(1, sizeof(*ret));
+
+ ret->ref_store = refs;
+ ret->fill_ref_dir = fill_ref_dir;
+ ret->root = create_dir_entry(ret, "", 0, 1);
+ return ret;
+}
+
+static void clear_ref_dir(struct ref_dir *dir);
+
+static void free_ref_entry(struct ref_entry *entry)
+{
+ if (entry->flag & REF_DIR) {
+ /*
+ * Do not use get_ref_dir() here, as that might
+ * trigger the reading of loose refs.
+ */
+ clear_ref_dir(&entry->u.subdir);
+ }
+ free(entry);
+}
+
+void free_ref_cache(struct ref_cache *cache)
+{
+ free_ref_entry(cache->root);
+ free(cache);
+}
+
+/*
+ * Clear and free all entries in dir, recursively.
+ */
+static void clear_ref_dir(struct ref_dir *dir)
+{
+ int i;
+ for (i = 0; i < dir->nr; i++)
+ free_ref_entry(dir->entries[i]);
+ free(dir->entries);
+ dir->sorted = dir->nr = dir->alloc = 0;
+ dir->entries = NULL;
+}
+
+struct ref_entry *create_dir_entry(struct ref_cache *cache,
+ const char *dirname, size_t len,
+ int incomplete)
+{
+ struct ref_entry *direntry;
+
+ FLEX_ALLOC_MEM(direntry, name, dirname, len);
+ direntry->u.subdir.cache = cache;
+ direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
+ return direntry;
+}
+
+static int ref_entry_cmp(const void *a, const void *b)
+{
+ struct ref_entry *one = *(struct ref_entry **)a;
+ struct ref_entry *two = *(struct ref_entry **)b;
+ return strcmp(one->name, two->name);
+}
+
+static void sort_ref_dir(struct ref_dir *dir);
+
+struct string_slice {
+ size_t len;
+ const char *str;
+};
+
+static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
+{
+ const struct string_slice *key = key_;
+ const struct ref_entry *ent = *(const struct ref_entry * const *)ent_;
+ int cmp = strncmp(key->str, ent->name, key->len);
+ if (cmp)
+ return cmp;
+ return '\0' - (unsigned char)ent->name[key->len];
+}
+
+int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
+{
+ struct ref_entry **r;
+ struct string_slice key;
+
+ if (refname == NULL || !dir->nr)
+ return -1;
+
+ sort_ref_dir(dir);
+ key.len = len;
+ key.str = refname;
+ r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries),
+ ref_entry_cmp_sslice);
+
+ if (r == NULL)
+ return -1;
+
+ return r - dir->entries;
+}
+
+/*
+ * Search for a directory entry directly within dir (without
+ * recursing). Sort dir if necessary. subdirname must be a directory
+ * name (i.e., end in '/'). If mkdir is set, then create the
+ * directory if it is missing; otherwise, return NULL if the desired
+ * directory cannot be found. dir must already be complete.
+ */
+static struct ref_dir *search_for_subdir(struct ref_dir *dir,
+ const char *subdirname, size_t len,
+ int mkdir)
+{
+ int entry_index = search_ref_dir(dir, subdirname, len);
+ struct ref_entry *entry;
+ if (entry_index == -1) {
+ if (!mkdir)
+ return NULL;
+ /*
+ * Since dir is complete, the absence of a subdir
+ * means that the subdir really doesn't exist;
+ * therefore, create an empty record for it but mark
+ * the record complete.
+ */
+ entry = create_dir_entry(dir->cache, subdirname, len, 0);
+ add_entry_to_dir(dir, entry);
+ } else {
+ entry = dir->entries[entry_index];
+ }
+ return get_ref_dir(entry);
+}
+
+/*
+ * If refname is a reference name, find the ref_dir within the dir
+ * tree that should hold refname. If refname is a directory name
+ * (i.e., it ends in '/'), then return that ref_dir itself. dir must
+ * represent the top-level directory and must already be complete.
+ * Sort ref_dirs and recurse into subdirectories as necessary. If
+ * mkdir is set, then create any missing directories; otherwise,
+ * return NULL if the desired directory cannot be found.
+ */
+static struct ref_dir *find_containing_dir(struct ref_dir *dir,
+ const char *refname, int mkdir)
+{
+ const char *slash;
+ for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
+ size_t dirnamelen = slash - refname + 1;
+ struct ref_dir *subdir;
+ subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
+ if (!subdir) {
+ dir = NULL;
+ break;
+ }
+ dir = subdir;
+ }
+
+ return dir;
+}
+
+struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname)
+{
+ int entry_index;
+ struct ref_entry *entry;
+ dir = find_containing_dir(dir, refname, 0);
+ if (!dir)
+ return NULL;
+ entry_index = search_ref_dir(dir, refname, strlen(refname));
+ if (entry_index == -1)
+ return NULL;
+ entry = dir->entries[entry_index];
+ return (entry->flag & REF_DIR) ? NULL : entry;
+}
+
+int remove_entry_from_dir(struct ref_dir *dir, const char *refname)
+{
+ int refname_len = strlen(refname);
+ int entry_index;
+ struct ref_entry *entry;
+ int is_dir = refname[refname_len - 1] == '/';
+ if (is_dir) {
+ /*
+ * refname represents a reference directory. Remove
+ * the trailing slash; otherwise we will get the
+ * directory *representing* refname rather than the
+ * one *containing* it.
+ */
+ char *dirname = xmemdupz(refname, refname_len - 1);
+ dir = find_containing_dir(dir, dirname, 0);
+ free(dirname);
+ } else {
+ dir = find_containing_dir(dir, refname, 0);
+ }
+ if (!dir)
+ return -1;
+ entry_index = search_ref_dir(dir, refname, refname_len);
+ if (entry_index == -1)
+ return -1;
+ entry = dir->entries[entry_index];
+
+ memmove(&dir->entries[entry_index],
+ &dir->entries[entry_index + 1],
+ (dir->nr - entry_index - 1) * sizeof(*dir->entries)
+ );
+ dir->nr--;
+ if (dir->sorted > entry_index)
+ dir->sorted--;
+ free_ref_entry(entry);
+ return dir->nr;
+}
+
+int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref)
+{
+ dir = find_containing_dir(dir, ref->name, 1);
+ if (!dir)
+ return -1;
+ add_entry_to_dir(dir, ref);
+ return 0;
+}
+
+/*
+ * Emit a warning and return true iff ref1 and ref2 have the same name
+ * and the same sha1. Die if they have the same name but different
+ * sha1s.
+ */
+static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
+{
+ if (strcmp(ref1->name, ref2->name))
+ return 0;
+
+ /* Duplicate name; make sure that they don't conflict: */
+
+ if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR))
+ /* This is impossible by construction */
+ die("Reference directory conflict: %s", ref1->name);
+
+ if (oidcmp(&ref1->u.value.oid, &ref2->u.value.oid))
+ die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
+
+ warning("Duplicated ref: %s", ref1->name);
+ return 1;
+}
+
+/*
+ * Sort the entries in dir non-recursively (if they are not already
+ * sorted) and remove any duplicate entries.
+ */
+static void sort_ref_dir(struct ref_dir *dir)
+{
+ int i, j;
+ struct ref_entry *last = NULL;
+
+ /*
+ * This check also prevents passing a zero-length array to qsort(),
+ * which is a problem on some platforms.
+ */
+ if (dir->sorted == dir->nr)
+ return;
+
+ QSORT(dir->entries, dir->nr, ref_entry_cmp);
+
+ /* Remove any duplicates: */
+ for (i = 0, j = 0; j < dir->nr; j++) {
+ struct ref_entry *entry = dir->entries[j];
+ if (last && is_dup_ref(last, entry))
+ free_ref_entry(entry);
+ else
+ last = dir->entries[i++] = entry;
+ }
+ dir->sorted = dir->nr = i;
+}
+
+/*
+ * Load all of the refs from `dir` (recursively) into our in-memory
+ * cache.
+ */
+static void prime_ref_dir(struct ref_dir *dir)
+{
+ /*
+ * The hard work of loading loose refs is done by get_ref_dir(), so we
+ * just need to recurse through all of the sub-directories. We do not
+ * even need to care about sorting, as traversal order does not matter
+ * to us.
+ */
+ int i;
+ for (i = 0; i < dir->nr; i++) {
+ struct ref_entry *entry = dir->entries[i];
+ if (entry->flag & REF_DIR)
+ prime_ref_dir(get_ref_dir(entry));
+ }
+}
+
+/*
+ * A level in the reference hierarchy that is currently being iterated
+ * through.
+ */
+struct cache_ref_iterator_level {
+ /*
+ * The ref_dir being iterated over at this level. The ref_dir
+ * is sorted before being stored here.
+ */
+ struct ref_dir *dir;
+
+ /*
+ * The index of the current entry within dir (which might
+ * itself be a directory). If index == -1, then the iteration
+ * hasn't yet begun. If index == dir->nr, then the iteration
+ * through this level is over.
+ */
+ int index;
+};
+
+/*
+ * Represent an iteration through a ref_dir in the memory cache. The
+ * iteration recurses through subdirectories.
+ */
+struct cache_ref_iterator {
+ struct ref_iterator base;
+
+ /*
+ * The number of levels currently on the stack. This is always
+ * at least 1, because when it becomes zero the iteration is
+ * ended and this struct is freed.
+ */
+ size_t levels_nr;
+
+ /* The number of levels that have been allocated on the stack */
+ size_t levels_alloc;
+
+ /*
+ * A stack of levels. levels[0] is the uppermost level that is
+ * being iterated over in this iteration. (This is not
+ * necessary the top level in the references hierarchy. If we
+ * are iterating through a subtree, then levels[0] will hold
+ * the ref_dir for that subtree, and subsequent levels will go
+ * on from there.)
+ */
+ struct cache_ref_iterator_level *levels;
+};
+
+static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+
+ while (1) {
+ struct cache_ref_iterator_level *level =
+ &iter->levels[iter->levels_nr - 1];
+ struct ref_dir *dir = level->dir;
+ struct ref_entry *entry;
+
+ if (level->index == -1)
+ sort_ref_dir(dir);
+
+ if (++level->index == level->dir->nr) {
+ /* This level is exhausted; pop up a level */
+ if (--iter->levels_nr == 0)
+ return ref_iterator_abort(ref_iterator);
+
+ continue;
+ }
+
+ entry = dir->entries[level->index];
+
+ if (entry->flag & REF_DIR) {
+ /* push down a level */
+ ALLOC_GROW(iter->levels, iter->levels_nr + 1,
+ iter->levels_alloc);
+
+ level = &iter->levels[iter->levels_nr++];
+ level->dir = get_ref_dir(entry);
+ level->index = -1;
+ } else {
+ iter->base.refname = entry->name;
+ iter->base.oid = &entry->u.value.oid;
+ iter->base.flags = entry->flag;
+ return ITER_OK;
+ }
+ }
+}
+
+enum peel_status peel_entry(struct ref_entry *entry, int repeel)
+{
+ enum peel_status status;
+
+ if (entry->flag & REF_KNOWS_PEELED) {
+ if (repeel) {
+ entry->flag &= ~REF_KNOWS_PEELED;
+ oidclr(&entry->u.value.peeled);
+ } else {
+ return is_null_oid(&entry->u.value.peeled) ?
+ PEEL_NON_TAG : PEEL_PEELED;
+ }
+ }
+ if (entry->flag & REF_ISBROKEN)
+ return PEEL_BROKEN;
+ if (entry->flag & REF_ISSYMREF)
+ return PEEL_IS_SYMREF;
+
+ status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash);
+ if (status == PEEL_PEELED || status == PEEL_NON_TAG)
+ entry->flag |= REF_KNOWS_PEELED;
+ return status;
+}
+
+static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+ struct cache_ref_iterator_level *level;
+ struct ref_entry *entry;
+
+ level = &iter->levels[iter->levels_nr - 1];
+
+ if (level->index == -1)
+ die("BUG: peel called before advance for cache iterator");
+
+ entry = level->dir->entries[level->index];
+
+ if (peel_entry(entry, 0))
+ return -1;
+ oidcpy(peeled, &entry->u.value.peeled);
+ return 0;
+}
+
+static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct cache_ref_iterator *iter =
+ (struct cache_ref_iterator *)ref_iterator;
+
+ free(iter->levels);
+ base_ref_iterator_free(ref_iterator);
+ return ITER_DONE;
+}
+
+static struct ref_iterator_vtable cache_ref_iterator_vtable = {
+ cache_ref_iterator_advance,
+ cache_ref_iterator_peel,
+ cache_ref_iterator_abort
+};
+
+struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
+ const char *prefix,
+ int prime_dir)
+{
+ struct ref_dir *dir;
+ struct cache_ref_iterator *iter;
+ struct ref_iterator *ref_iterator;
+ struct cache_ref_iterator_level *level;
+
+ dir = get_ref_dir(cache->root);
+ if (prefix && *prefix)
+ dir = find_containing_dir(dir, prefix, 0);
+ if (!dir)
+ /* There's nothing to iterate over. */
+ return empty_ref_iterator_begin();
+
+ if (prime_dir)
+ prime_ref_dir(dir);
+
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
+ ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
+
+ iter->levels_nr = 1;
+ level = &iter->levels[0];
+ level->index = -1;
+ level->dir = dir;
+
+ if (prefix && *prefix)
+ ref_iterator = prefix_ref_iterator_begin(ref_iterator,
+ prefix, 0);
+
+ return ref_iterator;
+}
diff --git a/refs/ref-cache.h b/refs/ref-cache.h
new file mode 100644
index 0000000..ffdc54f
--- /dev/null
+++ b/refs/ref-cache.h
@@ -0,0 +1,267 @@
+#ifndef REFS_REF_CACHE_H
+#define REFS_REF_CACHE_H
+
+struct ref_dir;
+
+/*
+ * If this ref_cache is filled lazily, this function is used to load
+ * information into the specified ref_dir (shallow or deep, at the
+ * option of the ref_store). dirname includes a trailing slash.
+ */
+typedef void fill_ref_dir_fn(struct ref_store *ref_store,
+ struct ref_dir *dir, const char *dirname);
+
+struct ref_cache {
+ struct ref_entry *root;
+
+ /* A pointer to the ref_store whose cache this is: */
+ struct ref_store *ref_store;
+
+ /*
+ * Function used (if necessary) to lazily-fill cache. May be
+ * NULL.
+ */
+ fill_ref_dir_fn *fill_ref_dir;
+};
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a single cached reference. This data structure only
+ * occurs embedded in a union in struct ref_entry, and only when
+ * (ref_entry->flag & REF_DIR) is zero.
+ */
+struct ref_value {
+ /*
+ * The name of the object to which this reference resolves
+ * (which may be a tag object). If REF_ISBROKEN, this is
+ * null. If REF_ISSYMREF, then this is the name of the object
+ * referred to by the last reference in the symlink chain.
+ */
+ struct object_id oid;
+
+ /*
+ * If REF_KNOWS_PEELED, then this field holds the peeled value
+ * of this reference, or null if the reference is known not to
+ * be peelable. See the documentation for peel_ref() for an
+ * exact definition of "peelable".
+ */
+ struct object_id peeled;
+};
+
+/*
+ * Information used (along with the information in ref_entry) to
+ * describe a level in the hierarchy of references. This data
+ * structure only occurs embedded in a union in struct ref_entry, and
+ * only when (ref_entry.flag & REF_DIR) is set. In that case,
+ * (ref_entry.flag & REF_INCOMPLETE) determines whether the references
+ * in the directory have already been read:
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose
+ * or packed references, already read.
+ *
+ * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose
+ * references that hasn't been read yet (nor has any of its
+ * subdirectories).
+ *
+ * Entries within a directory are stored within a growable array of
+ * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i <
+ * sorted are sorted by their component name in strcmp() order and the
+ * remaining entries are unsorted.
+ *
+ * Loose references are read lazily, one directory at a time. When a
+ * directory of loose references is read, then all of the references
+ * in that directory are stored, and REF_INCOMPLETE stubs are created
+ * for any subdirectories, but the subdirectories themselves are not
+ * read. The reading is triggered by get_ref_dir().
+ */
+struct ref_dir {
+ int nr, alloc;
+
+ /*
+ * Entries with index 0 <= i < sorted are sorted by name. New
+ * entries are appended to the list unsorted, and are sorted
+ * only when required; thus we avoid the need to sort the list
+ * after the addition of every reference.
+ */
+ int sorted;
+
+ /* The ref_cache containing this entry: */
+ struct ref_cache *cache;
+
+ struct ref_entry **entries;
+};
+
+/*
+ * Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
+ * public values; see refs.h.
+ */
+
+/*
+ * The field ref_entry->u.value.peeled of this value entry contains
+ * the correct peeled value for the reference, which might be
+ * null_sha1 if the reference is not a tag or if it is broken.
+ */
+#define REF_KNOWS_PEELED 0x10
+
+/* ref_entry represents a directory of references */
+#define REF_DIR 0x20
+
+/*
+ * Entry has not yet been read from disk (used only for REF_DIR
+ * entries representing loose references)
+ */
+#define REF_INCOMPLETE 0x40
+
+/*
+ * A ref_entry represents either a reference or a "subdirectory" of
+ * references.
+ *
+ * Each directory in the reference namespace is represented by a
+ * ref_entry with (flags & REF_DIR) set and containing a subdir member
+ * that holds the entries in that directory that have been read so
+ * far. If (flags & REF_INCOMPLETE) is set, then the directory and
+ * its subdirectories haven't been read yet. REF_INCOMPLETE is only
+ * used for loose reference directories.
+ *
+ * References are represented by a ref_entry with (flags & REF_DIR)
+ * unset and a value member that describes the reference's value. The
+ * flag member is at the ref_entry level, but it is also needed to
+ * interpret the contents of the value field (in other words, a
+ * ref_value object is not very much use without the enclosing
+ * ref_entry).
+ *
+ * Reference names cannot end with slash and directories' names are
+ * always stored with a trailing slash (except for the top-level
+ * directory, which is always denoted by ""). This has two nice
+ * consequences: (1) when the entries in each subdir are sorted
+ * lexicographically by name (as they usually are), the references in
+ * a whole tree can be generated in lexicographic order by traversing
+ * the tree in left-to-right, depth-first order; (2) the names of
+ * references and subdirectories cannot conflict, and therefore the
+ * presence of an empty subdirectory does not block the creation of a
+ * similarly-named reference. (The fact that reference names with the
+ * same leading components can conflict *with each other* is a
+ * separate issue that is regulated by refs_verify_refname_available().)
+ *
+ * Please note that the name field contains the fully-qualified
+ * reference (or subdirectory) name. Space could be saved by only
+ * storing the relative names. But that would require the full names
+ * to be generated on the fly when iterating in do_for_each_ref(), and
+ * would break callback functions, who have always been able to assume
+ * that the name strings that they are passed will not be freed during
+ * the iteration.
+ */
+struct ref_entry {
+ unsigned char flag; /* ISSYMREF? ISPACKED? */
+ union {
+ struct ref_value value; /* if not (flags&REF_DIR) */
+ struct ref_dir subdir; /* if (flags&REF_DIR) */
+ } u;
+ /*
+ * The full name of the reference (e.g., "refs/heads/master")
+ * or the full name of the directory with a trailing slash
+ * (e.g., "refs/heads/"):
+ */
+ char name[FLEX_ARRAY];
+};
+
+/*
+ * Return the index of the entry with the given refname from the
+ * ref_dir (non-recursively), sorting dir if necessary. Return -1 if
+ * no such entry is found. dir must already be complete.
+ */
+int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
+
+struct ref_dir *get_ref_dir(struct ref_entry *entry);
+
+/*
+ * Create a struct ref_entry object for the specified dirname.
+ * dirname is the name of the directory with a trailing slash (e.g.,
+ * "refs/heads/") or "" for the top-level directory.
+ */
+struct ref_entry *create_dir_entry(struct ref_cache *cache,
+ const char *dirname, size_t len,
+ int incomplete);
+
+struct ref_entry *create_ref_entry(const char *refname,
+ const unsigned char *sha1, int flag,
+ int check_name);
+
+/*
+ * Return a pointer to a new `ref_cache`. Its top-level starts out
+ * marked incomplete. If `fill_ref_dir` is non-NULL, it is the
+ * function called to fill in incomplete directories in the
+ * `ref_cache` when they are accessed. If it is NULL, then the whole
+ * `ref_cache` must be filled (including clearing its directories'
+ * `REF_INCOMPLETE` bits) before it is used.
+ */
+struct ref_cache *create_ref_cache(struct ref_store *refs,
+ fill_ref_dir_fn *fill_ref_dir);
+
+/*
+ * Free the `ref_cache` and all of its associated data.
+ */
+void free_ref_cache(struct ref_cache *cache);
+
+/*
+ * Add a ref_entry to the end of dir (unsorted). Entry is always
+ * stored directly in dir; no recursion into subdirectories is
+ * done.
+ */
+void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
+
+/*
+ * Remove the entry with the given name from dir, recursing into
+ * subdirectories as necessary. If refname is the name of a directory
+ * (i.e., ends with '/'), then remove the directory and its contents.
+ * If the removal was successful, return the number of entries
+ * remaining in the directory entry that contained the deleted entry.
+ * If the name was not found, return -1. Please note that this
+ * function only deletes the entry from the cache; it does not delete
+ * it from the filesystem or ensure that other cache entries (which
+ * might be symbolic references to the removed entry) are updated.
+ * Nor does it remove any containing dir entries that might be made
+ * empty by the removal. dir must represent the top-level directory
+ * and must already be complete.
+ */
+int remove_entry_from_dir(struct ref_dir *dir, const char *refname);
+
+/*
+ * Add a ref_entry to the ref_dir (unsorted), recursing into
+ * subdirectories as necessary. dir must represent the top-level
+ * directory. Return 0 on success.
+ */
+int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref);
+
+/*
+ * Find the value entry with the given name in dir, sorting ref_dirs
+ * and recursing into subdirectories as necessary. If the name is not
+ * found or it corresponds to a directory entry, return NULL.
+ */
+struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname);
+
+/*
+ * Start iterating over references in `cache`. If `prefix` is
+ * specified, only include references whose names start with that
+ * prefix. If `prime_dir` is true, then fill any incomplete
+ * directories before beginning the iteration.
+ */
+struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
+ const char *prefix,
+ int prime_dir);
+
+/*
+ * Peel the entry (if possible) and return its new peel_status. If
+ * repeel is true, re-peel the entry even if there is an old peeled
+ * value that is already stored in it.
+ *
+ * It is OK to call this function with a packed reference entry that
+ * might be stale and might even refer to an object that has since
+ * been garbage-collected. In such a case, if the entry has
+ * REF_KNOWS_PEELED then leave the status unchanged and return
+ * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
+ */
+enum peel_status peel_entry(struct ref_entry *entry, int repeel);
+
+#endif /* REFS_REF_CACHE_H */
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 6904986..3d46131 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -165,6 +165,10 @@ struct ref_update {
const char refname[FLEX_ARRAY];
};
+int refs_read_raw_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1,
+ struct strbuf *referent, unsigned int *type);
+
/*
* Add a ref_update with the specified properties to transaction, and
* return a pointer to the new object. This function does not verify
@@ -332,6 +336,17 @@ struct ref_iterator *empty_ref_iterator_begin(void);
int is_empty_ref_iterator(struct ref_iterator *ref_iterator);
/*
+ * Return an iterator that goes over each reference in `refs` for
+ * which the refname begins with prefix. If trim is non-zero, then
+ * trim that many characters off the beginning of each refname. flags
+ * can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in
+ * the iteration.
+ */
+struct ref_iterator *refs_ref_iterator_begin(
+ struct ref_store *refs,
+ const char *prefix, int trim, int flags);
+
+/*
* A callback function used to instruct merge_ref_iterator how to
* interleave the entries from iter0 and iter1. The function should
* return one of the constants defined in enum iterator_selection. It
@@ -575,12 +590,6 @@ typedef int read_raw_ref_fn(struct ref_store *ref_store,
const char *refname, unsigned char *sha1,
struct strbuf *referent, unsigned int *type);
-typedef int verify_refname_available_fn(struct ref_store *ref_store,
- const char *newname,
- const struct string_list *extras,
- const struct string_list *skip,
- struct strbuf *err);
-
struct ref_storage_be {
struct ref_storage_be *next;
const char *name;
@@ -597,7 +606,6 @@ struct ref_storage_be {
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
- verify_refname_available_fn *verify_refname_available;
reflog_iterator_begin_fn *reflog_iterator_begin;
for_each_reflog_ent_fn *for_each_reflog_ent;
diff --git a/remote-curl.c b/remote-curl.c
index d41fe16..ece4599 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -531,6 +531,12 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
return err;
}
+static curl_off_t xcurl_off_t(ssize_t len) {
+ if (len > maximum_signed_value_of_type(curl_off_t))
+ die("cannot handle pushes this big");
+ return (curl_off_t) len;
+}
+
static int post_rpc(struct rpc_state *rpc)
{
struct active_request_slot *slot;
@@ -614,7 +620,7 @@ retry:
* and we just need to send it.
*/
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
- curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, gzip_size);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size));
} else if (use_gzip && 1024 < rpc->len) {
/* The client backend isn't giving us compressed data so
@@ -645,7 +651,7 @@ retry:
headers = curl_slist_append(headers, "Content-Encoding: gzip");
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body);
- curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, gzip_size);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size));
if (options.verbosity > 1) {
fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n",
@@ -658,7 +664,7 @@ retry:
* more normal Content-Length approach.
*/
curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf);
- curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE, rpc->len);
+ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(rpc->len));
if (options.verbosity > 1) {
fprintf(stderr, "POST %s (%lu bytes)\n",
rpc->service_name, (unsigned long)rpc->len);
diff --git a/sequencer.c b/sequencer.c
index 77afeca..10c3b4f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1065,12 +1065,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
flags |= CLEANUP_MSG;
msg_file = rebase_path_fixup_msg();
} else {
- const char *dest = git_path("SQUASH_MSG");
+ const char *dest = git_path_squash_msg();
unlink(dest);
if (copy_file(dest, rebase_path_squash_msg(), 0666))
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), dest);
- unlink(git_path("MERGE_MSG"));
+ unlink(git_path_merge_msg());
msg_file = dest;
flags |= EDIT_MSG;
}
@@ -1200,6 +1200,7 @@ struct todo_list {
struct todo_item *items;
int nr, alloc, current;
int done_nr, total_nr;
+ struct stat_data stat;
};
#define TODO_LIST_INIT { STRBUF_INIT }
@@ -1330,6 +1331,7 @@ static int count_commands(struct todo_list *todo_list)
static int read_populate_todo(struct todo_list *todo_list,
struct replay_opts *opts)
{
+ struct stat st;
const char *todo_file = get_todo_path(opts);
int fd, res;
@@ -1343,6 +1345,11 @@ static int read_populate_todo(struct todo_list *todo_list,
}
close(fd);
+ res = stat(todo_file, &st);
+ if (res)
+ return error(_("could not stat '%s'"), todo_file);
+ fill_stat_data(&todo_list->stat, &st);
+
res = parse_insn_buffer(todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
@@ -1820,10 +1827,10 @@ static int error_failed_squash(struct commit *commit,
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
unlink(rebase_path_fixup_msg());
- unlink(git_path("MERGE_MSG"));
- if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666))
+ unlink(git_path_merge_msg());
+ if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
- rebase_path_message(), git_path("MERGE_MSG"));
+ rebase_path_message(), git_path_merge_msg());
return error_with_patch(commit, subject, subject_len, opts, 1, 0);
}
@@ -2028,10 +2035,25 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(item->arg + item->arg_len);
int saved = *end_of_arg;
+ struct stat st;
*end_of_arg = '\0';
res = do_exec(item->arg);
*end_of_arg = saved;
+
+ /* Reread the todo file if it has changed. */
+ if (res)
+ ; /* fall through */
+ else if (stat(get_todo_path(opts), &st))
+ res = error_errno(_("could not stat '%s'"),
+ get_todo_path(opts));
+ else if (match_stat_data(&todo_list->stat, &st)) {
+ todo_list_release(todo_list);
+ if (read_populate_todo(todo_list, opts))
+ res = -1; /* message was printed */
+ /* `current` will be incremented below */
+ todo_list->current = -1;
+ }
} else if (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
@@ -2167,7 +2189,7 @@ static int commit_staged_changes(struct replay_opts *opts)
if (has_unstaged_changes(1))
return error(_("cannot rebase: You have unstaged changes."));
if (!has_uncommitted_changes(0)) {
- const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD");
+ const char *cherry_pick_head = git_path_cherry_pick_head();
if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
return error(_("could not remove CHERRY_PICK_HEAD"));
diff --git a/server-info.c b/server-info.c
index 7bc4e75..f6c1a3d 100644
--- a/server-info.c
+++ b/server-info.c
@@ -14,19 +14,21 @@ static int update_info_file(char *path, int (*generate)(FILE *))
char *tmp = mkpathdup("%s_XXXXXX", path);
int ret = -1;
int fd = -1;
- FILE *fp = NULL;
+ FILE *fp = NULL, *to_close;
safe_create_leading_directories(path);
fd = git_mkstemp_mode(tmp, 0666);
if (fd < 0)
goto out;
- fp = fdopen(fd, "w");
+ to_close = fp = fdopen(fd, "w");
if (!fp)
goto out;
+ fd = -1;
ret = generate(fp);
if (ret)
goto out;
- if (fclose(fp))
+ fp = NULL;
+ if (fclose(to_close))
goto out;
if (adjust_shared_perm(tmp) < 0)
goto out;
diff --git a/sha1_file.c b/sha1_file.c
index 1577e2d..59a4ed2 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -3972,7 +3972,6 @@ int read_loose_object(const char *path,
void **contents)
{
int ret = -1;
- int fd = -1;
void *map = NULL;
unsigned long mapsize;
git_zstream stream;
@@ -4022,7 +4021,5 @@ int read_loose_object(const char *path,
out:
if (map)
munmap(map, mapsize);
- if (fd >= 0)
- close(fd);
return ret;
}
diff --git a/string-list.c b/string-list.c
index 45016ad..003ca18 100644
--- a/string-list.c
+++ b/string-list.c
@@ -41,10 +41,7 @@ static int add_entry(int insert_at, struct string_list *list, const char *string
if (exact_match)
return -1 - index;
- if (list->nr + 1 >= list->alloc) {
- list->alloc += 32;
- REALLOC_ARRAY(list->items, list->alloc);
- }
+ ALLOC_GROW(list->items, list->nr+1, list->alloc);
if (index < list->nr)
memmove(list->items + index + 1, list->items + index,
(list->nr - index)
diff --git a/submodule.c b/submodule.c
index 5615d73..d3299e2 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1474,8 +1474,7 @@ int submodule_move_head(const char *path,
cp1.no_stdin = 1;
cp1.dir = path;
- argv_array_pushl(&cp1.args, "update-ref", "HEAD",
- new ? new : EMPTY_TREE_SHA1_HEX, NULL);
+ argv_array_pushl(&cp1.args, "update-ref", "HEAD", new, NULL);
if (run_command(&cp1)) {
ret = -1;
diff --git a/t/helper/.gitignore b/t/helper/.gitignore
index acd5db1..7216502 100644
--- a/t/helper/.gitignore
+++ b/t/helper/.gitignore
@@ -28,6 +28,7 @@
/test-sha1
/test-sha1-array
/test-sigchain
+/test-strcmp-offset
/test-string-list
/test-submodule-config
/test-subprocess
diff --git a/t/helper/test-strcmp-offset.c b/t/helper/test-strcmp-offset.c
new file mode 100644
index 0000000..4a45a54
--- /dev/null
+++ b/t/helper/test-strcmp-offset.c
@@ -0,0 +1,22 @@
+#include "cache.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ int result;
+ size_t offset;
+
+ if (!argv[1] || !argv[2])
+ die("usage: %s <string1> <string2>", argv[0]);
+
+ result = strcmp_offset(argv[1], argv[2], &offset);
+
+ /*
+ * Because differnt CRTs behave differently, only rely on signs
+ * of the result values.
+ */
+ result = (result < 0 ? -1 :
+ result > 0 ? 1 :
+ 0);
+ printf("%d %"PRIuMAX"\n", result, (uintmax_t)offset);
+ return 0;
+}
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
index 924b19d..1dbc85b 100755
--- a/t/perf/aggregate.perl
+++ b/t/perf/aggregate.perl
@@ -88,6 +88,7 @@ for my $t (@tests) {
sub read_descr {
my $name = shift;
open my $fh, "<", $name or return "<error reading description>";
+ binmode $fh, ":utf8" or die "PANIC on binmode: $!";
my $line = <$fh>;
close $fh or die "cannot close $name";
chomp $line;
@@ -147,6 +148,8 @@ for my $t (@subtests) {
my $totalwidth = 3*@dirs+$descrlen;
$totalwidth += $_ for (@colwidth);
+binmode STDOUT, ":utf8" or die "PANIC on binmode: $!";
+
printf "%-${descrlen}s", "Test";
for my $i (0..$#dirs) {
my $d = $dirs[$i];
diff --git a/t/perf/p0000-perf-lib-sanity.sh b/t/perf/p0000-perf-lib-sanity.sh
index cf8e1ef..002c21e 100755
--- a/t/perf/p0000-perf-lib-sanity.sh
+++ b/t/perf/p0000-perf-lib-sanity.sh
@@ -33,6 +33,8 @@ test_perf 'export a weird var' '
test_export bar
'
+test_perf 'éḿíẗ ńöń-ÁŚĆÍÍ ćḧáŕáćẗéŕś' 'true'
+
test_expect_success 'test_export works with weird vars' '
echo "$bar" &&
test "$bar" = "weird # variable"
diff --git a/t/perf/p0005-status.sh b/t/perf/p0005-status.sh
new file mode 100755
index 0000000..0b0aa98
--- /dev/null
+++ b/t/perf/p0005-status.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# This test measures the performance of various read-tree
+# and status operations. It is primarily interested in
+# the algorithmic costs of index operations and recursive
+# tree traversal -- and NOT disk I/O on thousands of files.
+
+test_description="Tests performance of read-tree"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# If the test repo was generated by ./repos/many-files.sh
+# then we know something about the data shape and branches,
+# so we can isolate testing to the ballast-related commits
+# and setup sparse-checkout so we don't have to populate
+# the ballast files and directories.
+#
+# Otherwise, we make some general assumptions about the
+# repo and consider the entire history of the current
+# branch to be the ballast.
+
+test_expect_success "setup repo" '
+ if git rev-parse --verify refs/heads/p0006-ballast^{commit}
+ then
+ echo Assuming synthetic repo from many-files.sh
+ git branch br_base master
+ git branch br_ballast p0006-ballast
+ git config --local core.sparsecheckout 1
+ cat >.git/info/sparse-checkout <<-EOF
+ /*
+ !ballast/*
+ EOF
+ else
+ echo Assuming non-synthetic repo...
+ git branch br_base $(git rev-list HEAD | tail -n 1)
+ git branch br_ballast HEAD
+ fi &&
+ git checkout -q br_ballast &&
+ nr_files=$(git ls-files | wc -l)
+'
+
+test_perf "read-tree status br_ballast ($nr_files)" '
+ git read-tree HEAD &&
+ git status
+'
+
+test_done
diff --git a/t/perf/p0006-read-tree-checkout.sh b/t/perf/p0006-read-tree-checkout.sh
new file mode 100755
index 0000000..78cc23f
--- /dev/null
+++ b/t/perf/p0006-read-tree-checkout.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# This test measures the performance of various read-tree
+# and checkout operations. It is primarily interested in
+# the algorithmic costs of index operations and recursive
+# tree traversal -- and NOT disk I/O on thousands of files.
+
+test_description="Tests performance of read-tree"
+
+. ./perf-lib.sh
+
+test_perf_default_repo
+
+# If the test repo was generated by ./repos/many-files.sh
+# then we know something about the data shape and branches,
+# so we can isolate testing to the ballast-related commits
+# and setup sparse-checkout so we don't have to populate
+# the ballast files and directories.
+#
+# Otherwise, we make some general assumptions about the
+# repo and consider the entire history of the current
+# branch to be the ballast.
+
+test_expect_success "setup repo" '
+ if git rev-parse --verify refs/heads/p0006-ballast^{commit}
+ then
+ echo Assuming synthetic repo from many-files.sh
+ git branch br_base master
+ git branch br_ballast p0006-ballast^
+ git branch br_ballast_alias p0006-ballast^
+ git branch br_ballast_plus_1 p0006-ballast
+ git config --local core.sparsecheckout 1
+ cat >.git/info/sparse-checkout <<-EOF
+ /*
+ !ballast/*
+ EOF
+ else
+ echo Assuming non-synthetic repo...
+ git branch br_base $(git rev-list HEAD | tail -n 1)
+ git branch br_ballast HEAD^ || error "no ancestor commit from current head"
+ git branch br_ballast_alias HEAD^
+ git branch br_ballast_plus_1 HEAD
+ fi &&
+ git checkout -q br_ballast &&
+ nr_files=$(git ls-files | wc -l)
+'
+
+test_perf "read-tree br_base br_ballast ($nr_files)" '
+ git read-tree -m br_base br_ballast -n
+'
+
+test_perf "switch between br_base br_ballast ($nr_files)" '
+ git checkout -q br_base &&
+ git checkout -q br_ballast
+'
+
+test_perf "switch between br_ballast br_ballast_plus_1 ($nr_files)" '
+ git checkout -q br_ballast_plus_1 &&
+ git checkout -q br_ballast
+'
+
+test_perf "switch between aliases ($nr_files)" '
+ git checkout -q br_ballast_alias &&
+ git checkout -q br_ballast
+'
+
+test_done
diff --git a/t/perf/repos/.gitignore b/t/perf/repos/.gitignore
new file mode 100644
index 0000000..72e3dc3
--- /dev/null
+++ b/t/perf/repos/.gitignore
@@ -0,0 +1 @@
+gen-*/
diff --git a/t/perf/repos/inflate-repo.sh b/t/perf/repos/inflate-repo.sh
new file mode 100755
index 0000000..fcfc992
--- /dev/null
+++ b/t/perf/repos/inflate-repo.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+# Inflate the size of an EXISTING repo.
+#
+# This script should be run inside the worktree of a TEST repo.
+# It will use the contents of the current HEAD to generate a
+# commit containing copies of the current worktree such that the
+# total size of the commit has at least <target_size> files.
+#
+# Usage: [-t target_size] [-b branch_name]
+
+set -e
+
+target_size=10000
+branch_name=p0006-ballast
+ballast=ballast
+
+while test "$#" -ne 0
+do
+ case "$1" in
+ -b)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -b requires an argument' >&2; exit 1; }
+ branch_name=$1;
+ shift ;;
+ -t)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -t requires an argument' >&2; exit 1; }
+ target_size=$1;
+ shift ;;
+ *)
+ echo "error: unknown option '$1'" >&2; exit 1 ;;
+ esac
+done
+
+git ls-tree -r HEAD >GEN_src_list
+nr_src_files=$(cat GEN_src_list | wc -l)
+
+src_branch=$(git symbolic-ref --short HEAD)
+
+echo "Branch $src_branch initially has $nr_src_files files."
+
+if test $target_size -le $nr_src_files
+then
+ echo "Repository already exceeds target size $target_size."
+ rm GEN_src_list
+ exit 1
+fi
+
+# Create well-known branch and add 1 file change to start
+# if off before the ballast.
+git checkout -b $branch_name HEAD
+echo "$target_size" > inflate-repo.params
+git add inflate-repo.params
+git commit -q -m params
+
+# Create ballast for in our branch.
+copy=1
+nr_files=$nr_src_files
+while test $nr_files -lt $target_size
+do
+ sed -e "s| | $ballast/$copy/|" <GEN_src_list |
+ git update-index --index-info
+
+ nr_files=$(expr $nr_files + $nr_src_files)
+ copy=$(expr $copy + 1)
+done
+rm GEN_src_list
+git commit -q -m "ballast"
+
+# Modify 1 file and commit.
+echo "$target_size" >> inflate-repo.params
+git add inflate-repo.params
+git commit -q -m "ballast plus 1"
+
+nr_files=$(git ls-files | wc -l)
+
+# Checkout master to put repo in canonical state (because
+# the perf test may need to clone and enable sparse-checkout
+# before attempting to checkout a commit with the ballast
+# (because it may contain 100K directories and 1M files)).
+git checkout $src_branch
+
+echo "Repository inflated. Branch $branch_name has $nr_files files."
+
+exit 0
diff --git a/t/perf/repos/many-files.sh b/t/perf/repos/many-files.sh
new file mode 100755
index 0000000..28720e4
--- /dev/null
+++ b/t/perf/repos/many-files.sh
@@ -0,0 +1,110 @@
+#!/bin/sh
+# Generate test data repository using the given parameters.
+# When omitted, we create "gen-many-files-d-w-f.git".
+#
+# Usage: [-r repo] [-d depth] [-w width] [-f files]
+#
+# -r repo: path to the new repo to be generated
+# -d depth: the depth of sub-directories
+# -w width: the number of sub-directories at each level
+# -f files: the number of files created in each directory
+#
+# Note that all files will have the same SHA-1 and each
+# directory at a level will have the same SHA-1, so we
+# will potentially have a large index, but not a large
+# ODB.
+#
+# Ballast will be created under "ballast/".
+
+EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+
+set -e
+
+# (5, 10, 9) will create 999,999 ballast files.
+# (4, 10, 9) will create 99,999 ballast files.
+depth=5
+width=10
+files=9
+
+while test "$#" -ne 0
+do
+ case "$1" in
+ -r)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -r requires an argument' >&2; exit 1; }
+ repo=$1;
+ shift ;;
+ -d)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -d requires an argument' >&2; exit 1; }
+ depth=$1;
+ shift ;;
+ -w)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -w requires an argument' >&2; exit 1; }
+ width=$1;
+ shift ;;
+ -f)
+ shift;
+ test "$#" -ne 0 || { echo 'error: -f requires an argument' >&2; exit 1; }
+ files=$1;
+ shift ;;
+ *)
+ echo "error: unknown option '$1'" >&2; exit 1 ;;
+ esac
+done
+
+# Inflate the index with thousands of empty files.
+# usage: dir depth width files
+fill_index() {
+ awk -v arg_dir=$1 -v arg_depth=$2 -v arg_width=$3 -v arg_files=$4 '
+ function make_paths(dir, depth, width, files, f, w) {
+ for (f = 1; f <= files; f++) {
+ print dir "/file" f
+ }
+ if (depth > 0) {
+ for (w = 1; w <= width; w++) {
+ make_paths(dir "/dir" w, depth - 1, width, files)
+ }
+ }
+ }
+ END { make_paths(arg_dir, arg_depth, arg_width, arg_files) }
+ ' </dev/null |
+ sed "s/^/100644 $EMPTY_BLOB /" |
+ git update-index --index-info
+ return 0
+}
+
+[ -z "$repo" ] && repo=gen-many-files-$depth.$width.$files.git
+
+mkdir $repo
+cd $repo
+git init .
+
+# Create an initial commit just to define master.
+touch many-files.empty
+echo "$depth $width $files" >many-files.params
+git add many-files.*
+git commit -q -m params
+
+# Create ballast for p0006 based upon the given params and
+# inflate the index with thousands of empty files and commit.
+git checkout -b p0006-ballast
+fill_index "ballast" $depth $width $files
+git commit -q -m "ballast"
+
+nr_files=$(git ls-files | wc -l)
+
+# Modify 1 file and commit.
+echo "$depth $width $files" >>many-files.params
+git add many-files.params
+git commit -q -m "ballast plus 1"
+
+# Checkout master to put repo in canonical state (because
+# the perf test may need to clone and enable sparse-checkout
+# before attempting to checkout a commit with the ballast
+# (because it may contain 100K directories and 1M files)).
+git checkout master
+
+echo "Repository "$repo" ($depth, $width, $files) created. Ballast $nr_files."
+exit 0
diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh
index d0bee08..89826c5 100755
--- a/t/t0025-crlf-auto.sh
+++ b/t/t0025-crlf-auto.sh
@@ -152,4 +152,30 @@ test_expect_success 'eol=crlf _does_ normalize binary files' '
test -z "$LFwithNULdiff"
'
+test_expect_success 'prepare unnormalized' '
+ > .gitattributes &&
+ git config core.autocrlf false &&
+ printf "LINEONE\nLINETWO\r\n" >mixed &&
+ git add mixed .gitattributes &&
+ git commit -m "Add mixed" &&
+ git ls-files --eol | egrep "i/crlf" &&
+ git ls-files --eol | egrep "i/mixed"
+'
+
+test_expect_success 'normalize unnormalized' '
+ echo "* text=auto" >.gitattributes &&
+ rm .git/index &&
+ git add . &&
+ git commit -m "Introduce end-of-line normalization" &&
+ git ls-files --eol | tr "\\t" " " | sort >act &&
+cat >exp <<EOF &&
+i/-text w/-text attr/text=auto LFwithNUL
+i/lf w/crlf attr/text=auto CRLFonly
+i/lf w/crlf attr/text=auto LFonly
+i/lf w/lf attr/text=auto .gitattributes
+i/lf w/mixed attr/text=auto mixed
+EOF
+ test_cmp exp act
+'
+
test_done
diff --git a/t/t0065-strcmp-offset.sh b/t/t0065-strcmp-offset.sh
new file mode 100755
index 0000000..7d6d214
--- /dev/null
+++ b/t/t0065-strcmp-offset.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+test_description='Test strcmp_offset functionality'
+
+. ./test-lib.sh
+
+while read s1 s2 expect
+do
+ test_expect_success "strcmp_offset($s1, $s2)" '
+ echo "$expect" >expect &&
+ test-strcmp-offset "$s1" "$s2" >actual &&
+ test_cmp expect actual
+ '
+done <<-EOF
+abc abc 0 3
+abc def -1 0
+abc abz -1 2
+abc abcdef -1 3
+EOF
+
+test_done
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index e833939..933915e 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -3,6 +3,16 @@
test_description='test config file include directives'
. ./test-lib.sh
+# Force setup_explicit_git_dir() to run until the end. This is needed
+# by some tests to make sure real_path() is called on $GIT_DIR. The
+# caller needs to make sure git commands are run from a subdirectory
+# though or real_path() will not be called.
+force_setup_explicit_git_dir() {
+ GIT_DIR="$(pwd)/.git"
+ GIT_WORK_TREE="$(pwd)"
+ export GIT_DIR GIT_WORK_TREE
+}
+
test_expect_success 'include file by absolute path' '
echo "[test]one = 1" >one &&
echo "[include]path = \"$(pwd)/one\"" >.gitconfig &&
@@ -208,6 +218,61 @@ test_expect_success 'conditional include, both unanchored, icase' '
)
'
+test_expect_success 'conditional include, early config reading' '
+ (
+ cd foo &&
+ echo "[includeIf \"gitdir:foo/\"]path=bar6" >>.git/config &&
+ echo "[test]six=6" >.git/bar6 &&
+ echo 6 >expect &&
+ test-config read_early_config test.six >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success SYMLINKS 'conditional include, set up symlinked $HOME' '
+ mkdir real-home &&
+ ln -s real-home home &&
+ (
+ HOME="$TRASH_DIRECTORY/home" &&
+ export HOME &&
+ cd "$HOME" &&
+
+ git init foo &&
+ cd foo &&
+ mkdir sub
+ )
+'
+
+test_expect_success SYMLINKS 'conditional include, $HOME expansion with symlinks' '
+ (
+ HOME="$TRASH_DIRECTORY/home" &&
+ export HOME &&
+ cd "$HOME"/foo &&
+
+ echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config &&
+ echo "[test]two=2" >.git/bar2 &&
+ echo 2 >expect &&
+ force_setup_explicit_git_dir &&
+ git -C sub config test.two >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success SYMLINKS 'conditional include, relative path with symlinks' '
+ echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >home/.gitconfig &&
+ echo "[test]four=4" >home/bar4 &&
+ (
+ HOME="$TRASH_DIRECTORY/home" &&
+ export HOME &&
+ cd "$HOME"/foo &&
+
+ echo 4 >expect &&
+ force_setup_explicit_git_dir &&
+ git -C sub config test.four >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'include cycles are detected' '
cat >.gitconfig <<-\EOF &&
[test]value = gitconfig
diff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh
index b97357b..1af8c45 100755
--- a/t/t1309-early-config.sh
+++ b/t/t1309-early-config.sh
@@ -47,6 +47,24 @@ test_expect_success 'ceiling #2' '
test xdg = "$(cat output)"
'
+cmdline_config="'test.source=cmdline'"
+test_expect_success 'read config file in right order' '
+ echo "[test]source = home" >>.gitconfig &&
+ git init foo &&
+ (
+ cd foo &&
+ echo "[test]source = repo" >>.git/config &&
+ GIT_CONFIG_PARAMETERS=$cmdline_config test-config \
+ read_early_config test.source >actual &&
+ cat >expected <<-\EOF &&
+ home
+ repo
+ cmdline
+ EOF
+ test_cmp expected actual
+ )
+'
+
test_with_config () {
rm -rf throwaway &&
git init throwaway &&
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index a23cd7b..dc98b4b 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -35,14 +35,14 @@ test_expect_success setup '
cd -
'
-test_expect_success \
- "create $m" \
- "git update-ref $m $A &&
- test $A"' = $(cat .git/'"$m"')'
-test_expect_success \
- "create $m with oldvalue verification" \
- "git update-ref $m $B $A &&
- test $B"' = $(cat .git/'"$m"')'
+test_expect_success "create $m" '
+ git update-ref $m $A &&
+ test $A = $(cat .git/$m)
+'
+test_expect_success "create $m with oldvalue verification" '
+ git update-ref $m $B $A &&
+ test $B = $(cat .git/$m)
+'
test_expect_success "fail to delete $m with stale ref" '
test_must_fail git update-ref -d $m $A &&
test $B = "$(cat .git/$m)"
@@ -67,14 +67,14 @@ test_expect_success "fail to create $n" '
test_must_fail git update-ref $n $A
'
-test_expect_success \
- "create $m (by HEAD)" \
- "git update-ref HEAD $A &&
- test $A"' = $(cat .git/'"$m"')'
-test_expect_success \
- "create $m (by HEAD) with oldvalue verification" \
- "git update-ref HEAD $B $A &&
- test $B"' = $(cat .git/'"$m"')'
+test_expect_success "create $m (by HEAD)" '
+ git update-ref HEAD $A &&
+ test $A = $(cat .git/$m)
+'
+test_expect_success "create $m (by HEAD) with oldvalue verification" '
+ git update-ref HEAD $B $A &&
+ test $B = $(cat .git/$m)
+'
test_expect_success "fail to delete $m (by HEAD) with stale ref" '
test_must_fail git update-ref -d HEAD $A &&
test $B = $(cat .git/$m)
@@ -176,17 +176,17 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always'
test_must_fail git reflog exists $outside
'
-test_expect_success \
- "create $m (by HEAD)" \
- "git update-ref HEAD $A &&
- test $A"' = $(cat .git/'"$m"')'
-test_expect_success \
- "pack refs" \
- "git pack-refs --all"
-test_expect_success \
- "move $m (by HEAD)" \
- "git update-ref HEAD $B $A &&
- test $B"' = $(cat .git/'"$m"')'
+test_expect_success "create $m (by HEAD)" '
+ git update-ref HEAD $A &&
+ test $A = $(cat .git/$m)
+'
+test_expect_success 'pack refs' '
+ git pack-refs --all
+'
+test_expect_success "move $m (by HEAD)" '
+ git update-ref HEAD $B $A &&
+ test $B = $(cat .git/$m)
+'
test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" '
test_when_finished "rm -f .git/$m" &&
git update-ref -d HEAD $B &&
@@ -195,13 +195,13 @@ test_expect_success "delete $m (by HEAD) should remove both packed and loose $m"
'
cp -f .git/HEAD .git/HEAD.orig
-test_expect_success "delete symref without dereference" '
+test_expect_success 'delete symref without dereference' '
test_when_finished "cp -f .git/HEAD.orig .git/HEAD" &&
git update-ref --no-deref -d HEAD &&
test_path_is_missing .git/HEAD
'
-test_expect_success "delete symref without dereference when the referred ref is packed" '
+test_expect_success 'delete symref without dereference when the referred ref is packed' '
test_when_finished "cp -f .git/HEAD.orig .git/HEAD" &&
echo foo >foo.c &&
git add foo.c &&
@@ -239,46 +239,46 @@ test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' '
test_path_is_missing .git/refs/heads/ref-to-bad
'
-test_expect_success '(not) create HEAD with old sha1' "
+test_expect_success '(not) create HEAD with old sha1' '
test_must_fail git update-ref HEAD $A $B
-"
+'
test_expect_success "(not) prior created .git/$m" '
test_when_finished "rm -f .git/$m" &&
test_path_is_missing .git/$m
'
-test_expect_success \
- "create HEAD" \
- "git update-ref HEAD $A"
-test_expect_success '(not) change HEAD with wrong SHA1' "
+test_expect_success 'create HEAD' '
+ git update-ref HEAD $A
+'
+test_expect_success '(not) change HEAD with wrong SHA1' '
test_must_fail git update-ref HEAD $B $Z
-"
+'
test_expect_success "(not) changed .git/$m" '
test_when_finished "rm -f .git/$m" &&
! test $B = $(cat .git/$m)
'
rm -f .git/logs/refs/heads/master
-test_expect_success \
- "create $m (logged by touch)" \
- 'test_config core.logAllRefUpdates false &&
- GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git update-ref --create-reflog HEAD '"$A"' -m "Initial Creation" &&
- test '"$A"' = $(cat .git/'"$m"')'
-test_expect_success \
- "update $m (logged by touch)" \
- 'test_config core.logAllRefUpdates false &&
- GIT_COMMITTER_DATE="2005-05-26 23:31" \
- git update-ref HEAD'" $B $A "'-m "Switch" &&
- test '"$B"' = $(cat .git/'"$m"')'
-test_expect_success \
- "set $m (logged by touch)" \
- 'test_config core.logAllRefUpdates false &&
- GIT_COMMITTER_DATE="2005-05-26 23:41" \
- git update-ref HEAD'" $A &&
- test $A"' = $(cat .git/'"$m"')'
-
-test_expect_success "empty directory removal" '
+test_expect_success "create $m (logged by touch)" '
+ test_config core.logAllRefUpdates false &&
+ GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git update-ref --create-reflog HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)
+'
+test_expect_success "update $m (logged by touch)" '
+ test_config core.logAllRefUpdates false &&
+ GIT_COMMITTER_DATE="2005-05-26 23:31" \
+ git update-ref HEAD $B $A -m "Switch" &&
+ test $B = $(cat .git/$m)
+'
+test_expect_success "set $m (logged by touch)" '
+ test_config core.logAllRefUpdates false &&
+ GIT_COMMITTER_DATE="2005-05-26 23:41" \
+ git update-ref HEAD $A &&
+ test $A = $(cat .git/$m)
+'
+
+test_expect_success 'empty directory removal' '
git branch d1/d2/r1 HEAD &&
git branch d1/r2 HEAD &&
test_path_is_file .git/refs/heads/d1/d2/r1 &&
@@ -290,7 +290,7 @@ test_expect_success "empty directory removal" '
test_path_is_file .git/logs/refs/heads/d1/r2
'
-test_expect_success "symref empty directory removal" '
+test_expect_success 'symref empty directory removal' '
git branch e1/e2/r1 HEAD &&
git branch e1/r2 HEAD &&
git checkout e1/e2/r1 &&
@@ -315,34 +315,34 @@ test_expect_success "verifying $m's log (logged by touch)" '
test_cmp expect .git/logs/$m
'
-test_expect_success \
- "create $m (logged by config)" \
- 'test_config core.logAllRefUpdates true &&
- GIT_COMMITTER_DATE="2005-05-26 23:32" \
- git update-ref HEAD'" $A "'-m "Initial Creation" &&
- test '"$A"' = $(cat .git/'"$m"')'
-test_expect_success \
- "update $m (logged by config)" \
- 'test_config core.logAllRefUpdates true &&
- GIT_COMMITTER_DATE="2005-05-26 23:33" \
- git update-ref HEAD'" $B $A "'-m "Switch" &&
- test '"$B"' = $(cat .git/'"$m"')'
-test_expect_success \
- "set $m (logged by config)" \
- 'test_config core.logAllRefUpdates true &&
- GIT_COMMITTER_DATE="2005-05-26 23:43" \
- git update-ref HEAD '"$A &&
- test $A"' = $(cat .git/'"$m"')'
+test_expect_success "create $m (logged by config)" '
+ test_config core.logAllRefUpdates true &&
+ GIT_COMMITTER_DATE="2005-05-26 23:32" \
+ git update-ref HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)
+'
+test_expect_success "update $m (logged by config)" '
+ test_config core.logAllRefUpdates true &&
+ GIT_COMMITTER_DATE="2005-05-26 23:33" \
+ git update-ref HEAD'" $B $A "'-m "Switch" &&
+ test $B = $(cat .git/$m)
+'
+test_expect_success "set $m (logged by config)" '
+ test_config core.logAllRefUpdates true &&
+ GIT_COMMITTER_DATE="2005-05-26 23:43" \
+ git update-ref HEAD $A &&
+ test $A = $(cat .git/$m)
+'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch
$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
EOF
-test_expect_success \
- "verifying $m's log (logged by config)" \
- 'test_when_finished "rm -f .git/$m .git/logs/$m expect" &&
- test_cmp expect .git/logs/$m'
+test_expect_success "verifying $m's log (logged by config)" '
+ test_when_finished "rm -f .git/$m .git/logs/$m expect" &&
+ test_cmp expect .git/logs/$m
+'
git update-ref $m $D
cat >.git/logs/$m <<EOF
@@ -356,86 +356,85 @@ EOF
ed="Thu, 26 May 2005 18:32:00 -0500"
gd="Thu, 26 May 2005 18:33:00 -0500"
ld="Thu, 26 May 2005 18:43:00 -0500"
-test_expect_success \
- 'Query "master@{May 25 2005}" (before history)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
- test '"$C"' = $(cat o) &&
- test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
-test_expect_success \
- "Query master@{2005-05-25} (before history)" \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify master@{2005-05-25} >o 2>e &&
- test '"$C"' = $(cat o) &&
- echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
-test_expect_success \
- 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
- test '"$C"' = $(cat o) &&
- test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
-test_expect_success \
- 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
- test '"$C"' = $(cat o) &&
- test "" = "$(cat e)"'
-test_expect_success \
- 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
- test '"$A"' = $(cat o) &&
- test "" = "$(cat e)"'
-test_expect_success \
- 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
- test '"$B"' = $(cat o) &&
- test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"'
-test_expect_success \
- 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
- test '"$Z"' = $(cat o) &&
- test "" = "$(cat e)"'
-test_expect_success \
- 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
- test '"$E"' = $(cat o) &&
- test "" = "$(cat e)"'
-test_expect_success \
- 'Query "master@{2005-05-28}" (past end of history)' \
- 'test_when_finished "rm -f o e" &&
- git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
- test '"$D"' = $(cat o) &&
- test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"'
-
+test_expect_success 'Query "master@{May 25 2005}" (before history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+ test $C = $(cat o) &&
+ test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+'
+test_expect_success 'Query master@{2005-05-25} (before history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify master@{2005-05-25} >o 2>e &&
+ test $C = $(cat o) &&
+ echo test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+'
+test_expect_success 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+ test $C = $(cat o) &&
+ test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"
+'
+test_expect_success 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+ test $C = $(cat o) &&
+ test "" = "$(cat e)"
+'
+test_expect_success 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
+ test $A = $(cat o) &&
+ test "" = "$(cat e)"
+'
+test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+ test $B = $(cat o) &&
+ test "warning: Log for ref $m has gap after $gd." = "$(cat e)"
+'
+test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+ test $Z = $(cat o) &&
+ test "" = "$(cat e)"
+'
+test_expect_success 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+ test $E = $(cat o) &&
+ test "" = "$(cat e)"
+'
+test_expect_success 'Query "master@{2005-05-28}" (past end of history)' '
+ test_when_finished "rm -f o e" &&
+ git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+ test $D = $(cat o) &&
+ test "warning: Log for ref $m unexpectedly ended on $ld." = "$(cat e)"
+'
rm -f .git/$m .git/logs/$m expect
-test_expect_success \
- 'creating initial files' \
- 'test_when_finished rm -f M &&
- echo TEST >F &&
- git add F &&
- GIT_AUTHOR_DATE="2005-05-26 23:30" \
- GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
- h_TEST=$(git rev-parse --verify HEAD) &&
- echo The other day this did not work. >M &&
- echo And then Bob told me how to fix it. >>M &&
- echo OTHER >F &&
- GIT_AUTHOR_DATE="2005-05-26 23:41" \
- GIT_COMMITTER_DATE="2005-05-26 23:41" git commit -F M -a &&
- h_OTHER=$(git rev-parse --verify HEAD) &&
- GIT_AUTHOR_DATE="2005-05-26 23:44" \
- GIT_COMMITTER_DATE="2005-05-26 23:44" git commit --amend &&
- h_FIXED=$(git rev-parse --verify HEAD) &&
- echo Merged initial commit and a later commit. >M &&
- echo $h_TEST >.git/MERGE_HEAD &&
- GIT_AUTHOR_DATE="2005-05-26 23:45" \
- GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M &&
- h_MERGED=$(git rev-parse --verify HEAD)'
+test_expect_success 'creating initial files' '
+ test_when_finished rm -f M &&
+ echo TEST >F &&
+ git add F &&
+ GIT_AUTHOR_DATE="2005-05-26 23:30" \
+ GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
+ h_TEST=$(git rev-parse --verify HEAD) &&
+ echo The other day this did not work. >M &&
+ echo And then Bob told me how to fix it. >>M &&
+ echo OTHER >F &&
+ GIT_AUTHOR_DATE="2005-05-26 23:41" \
+ GIT_COMMITTER_DATE="2005-05-26 23:41" git commit -F M -a &&
+ h_OTHER=$(git rev-parse --verify HEAD) &&
+ GIT_AUTHOR_DATE="2005-05-26 23:44" \
+ GIT_COMMITTER_DATE="2005-05-26 23:44" git commit --amend &&
+ h_FIXED=$(git rev-parse --verify HEAD) &&
+ echo Merged initial commit and a later commit. >M &&
+ echo $h_TEST >.git/MERGE_HEAD &&
+ GIT_AUTHOR_DATE="2005-05-26 23:45" \
+ GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M &&
+ h_MERGED=$(git rev-parse --verify HEAD)
+'
cat >expect <<EOF
$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit (initial): add
@@ -443,20 +442,20 @@ $h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 com
$h_OTHER $h_FIXED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151040 +0000 commit (amend): The other day this did not work.
$h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 commit (merge): Merged initial commit and a later commit.
EOF
-test_expect_success \
- 'git commit logged updates' \
- "test_cmp expect .git/logs/$m"
+test_expect_success 'git commit logged updates' '
+ test_cmp expect .git/logs/$m
+'
unset h_TEST h_OTHER h_FIXED h_MERGED
-test_expect_success \
- 'git cat-file blob master:F (expect OTHER)' \
- 'test OTHER = $(git cat-file blob master:F)'
-test_expect_success \
- 'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
- 'test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")'
-test_expect_success \
- 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
- 'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
+test_expect_success 'git cat-file blob master:F (expect OTHER)' '
+ test OTHER = $(git cat-file blob master:F)
+'
+test_expect_success 'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' '
+ test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")
+'
+test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' '
+ test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")
+'
a=refs/heads/a
b=refs/heads/b
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 33a51c9..677e15a 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -689,4 +689,17 @@ test_expect_success 'bogus head does not fallback to all heads' '
! grep $blob out
'
+test_expect_success 'detect corrupt index file in fsck' '
+ cp .git/index .git/index.backup &&
+ test_when_finished "mv .git/index.backup .git/index" &&
+ echo zzzzzzzz >zzzzzzzz &&
+ git add zzzzzzzz &&
+ sed -e "s/zzzzzzzz/yyyyyyyy/" .git/index >.git/index.yyy &&
+ mv .git/index.yyy .git/index &&
+ # Confirm that fsck detects invalid checksum
+ test_must_fail git fsck --cache &&
+ # Confirm that status no longer complains about invalid checksum
+ git status
+'
+
test_done
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index b618d6b..b5c47ac 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -63,6 +63,12 @@ test_expect_success '"add" worktree' '
)
'
+test_expect_success '"add" worktree with lock' '
+ git rev-parse HEAD >expect &&
+ git worktree add --detach --lock here-with-lock master &&
+ test -f .git/worktrees/here-with-lock/locked
+'
+
test_expect_success '"add" worktree from a subdir' '
(
mkdir sub &&
diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh
index 848da5f..720063b 100755
--- a/t/t2027-worktree-list.sh
+++ b/t/t2027-worktree-list.sh
@@ -20,7 +20,7 @@ test_expect_success 'rev-parse --git-common-dir on main worktree' '
test_expect_success 'rev-parse --git-path objects linked worktree' '
echo "$(git rev-parse --show-toplevel)/.git/objects" >expect &&
- test_when_finished "rm -rf linked-tree && git worktree prune" &&
+ test_when_finished "rm -rf linked-tree actual expect && git worktree prune" &&
git worktree add --detach linked-tree master &&
git -C linked-tree rev-parse --git-path objects >actual &&
test_cmp expect actual
@@ -28,19 +28,21 @@ test_expect_success 'rev-parse --git-path objects linked worktree' '
test_expect_success '"list" all worktrees from main' '
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
- test_when_finished "rm -rf here && git worktree prune" &&
+ test_when_finished "rm -rf here out actual expect && git worktree prune" &&
git worktree add --detach here master &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
- git worktree list | sed "s/ */ /g" >actual &&
+ git worktree list >out &&
+ sed "s/ */ /g" <out >actual &&
test_cmp expect actual
'
test_expect_success '"list" all worktrees from linked' '
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
- test_when_finished "rm -rf here && git worktree prune" &&
+ test_when_finished "rm -rf here out actual expect && git worktree prune" &&
git worktree add --detach here master &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
- git -C here worktree list | sed "s/ */ /g" >actual &&
+ git -C here worktree list >out &&
+ sed "s/ */ /g" <out >actual &&
test_cmp expect actual
'
@@ -49,7 +51,7 @@ test_expect_success '"list" all worktrees --porcelain' '
echo "HEAD $(git rev-parse HEAD)" >>expect &&
echo "branch $(git symbolic-ref HEAD)" >>expect &&
echo >>expect &&
- test_when_finished "rm -rf here && git worktree prune" &&
+ test_when_finished "rm -rf here actual expect && git worktree prune" &&
git worktree add --detach here master &&
echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
echo "HEAD $(git rev-parse HEAD)" >>expect &&
@@ -69,16 +71,17 @@ test_expect_success 'bare repo setup' '
'
test_expect_success '"list" all worktrees from bare main' '
- test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+ test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "$(pwd)/bare1 (bare)" >expect &&
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
- git -C bare1 worktree list | sed "s/ */ /g" >actual &&
+ git -C bare1 worktree list >out &&
+ sed "s/ */ /g" <out >actual &&
test_cmp expect actual
'
test_expect_success '"list" all worktrees --porcelain from bare main' '
- test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+ test_when_finished "rm -rf there actual expect && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "worktree $(pwd)/bare1" >expect &&
echo "bare" >>expect &&
@@ -92,11 +95,12 @@ test_expect_success '"list" all worktrees --porcelain from bare main' '
'
test_expect_success '"list" all worktrees from linked with a bare main' '
- test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
+ test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "$(pwd)/bare1 (bare)" >expect &&
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
- git -C there worktree list | sed "s/ */ /g" >actual &&
+ git -C there worktree list >out &&
+ sed "s/ */ /g" <out >actual &&
test_cmp expect actual
'
@@ -118,9 +122,11 @@ test_expect_success 'broken main worktree still at the top' '
cd linked &&
echo "worktree $(pwd)" >expected &&
echo "ref: .broken" >../.git/HEAD &&
- git worktree list --porcelain | head -n 3 >actual &&
+ git worktree list --porcelain >out &&
+ head -n 3 out >actual &&
test_cmp ../expected actual &&
- git worktree list | head -n 1 >actual.2 &&
+ git worktree list >out &&
+ head -n 1 out >actual.2 &&
grep -F "(error)" actual.2
)
'
@@ -134,7 +140,8 @@ test_expect_success 'linked worktrees are sorted' '
test_commit new &&
git worktree add ../first &&
git worktree add ../second &&
- git worktree list --porcelain | grep ^worktree >actual
+ git worktree list --porcelain >out &&
+ grep ^worktree out >actual
) &&
cat >expected <<-EOF &&
worktree $(pwd)/sorted/main
diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh
index 4cf6ccf..ebb956f 100755
--- a/t/t3007-ls-files-recurse-submodules.sh
+++ b/t/t3007-ls-files-recurse-submodules.sh
@@ -77,10 +77,22 @@ test_expect_success 'ls-files recurses more than 1 level' '
git -C submodule/subsub commit -m "add d" &&
git -C submodule submodule add ./subsub &&
git -C submodule commit -m "added subsub" &&
+ git submodule absorbgitdirs &&
git ls-files --recurse-submodules >actual &&
test_cmp expect actual
'
+test_expect_success 'ls-files works with GIT_DIR' '
+ cat >expect <<-\EOF &&
+ .gitmodules
+ c
+ subsub/d
+ EOF
+
+ git --git-dir=submodule/.git ls-files --recurse-submodules >actual &&
+ test_cmp expect actual
+'
+
test_expect_success '--recurse-submodules and pathspecs setup' '
echo e >submodule/subsub/e.txt &&
git -C submodule/subsub add e.txt &&
diff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh
new file mode 100755
index 0000000..2afb564
--- /dev/null
+++ b/t/t3428-rebase-signoff.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='git rebase --signoff
+
+This test runs git rebase --signoff and make sure that it works.
+'
+
+. ./test-lib.sh
+
+# A simple file to commit
+cat >file <<EOF
+a
+EOF
+
+# Expected commit message after rebase --signoff
+cat >expected-signed <<EOF
+first
+
+Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/")
+EOF
+
+# Expected commit message after rebase without --signoff (or with --no-signoff)
+cat >expected-unsigned <<EOF
+first
+EOF
+
+
+# We configure an alias to do the rebase --signoff so that
+# on the next subtest we can show that --no-signoff overrides the alias
+test_expect_success 'rebase --signoff adds a sign-off line' '
+ git commit --allow-empty -m "Initial empty commit" &&
+ git add file && git commit -m first &&
+ git config alias.rbs "rebase --signoff" &&
+ git rbs HEAD^ &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ test_cmp expected-signed actual
+'
+
+test_expect_success 'rebase --no-signoff does not add a sign-off line' '
+ git commit --amend -m "first" &&
+ git rbs --no-signoff HEAD^ &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+ test_cmp expected-unsigned actual
+'
+
+test_done
diff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh
new file mode 100755
index 0000000..b9292df
--- /dev/null
+++ b/t/t3429-rebase-edit-todo.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+test_description='rebase should reread the todo file if an exec modifies it'
+
+. ./test-lib.sh
+
+test_expect_success 'rebase exec modifies rebase-todo' '
+ test_commit initial &&
+ todo=.git/rebase-merge/git-rebase-todo &&
+ git rebase HEAD -x "echo exec touch F >>$todo" &&
+ test -e F
+'
+
+test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index f9528fa..2ecb43a 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -436,6 +436,28 @@ test_expect_success 'add -p handles globs' '
test_cmp expect actual
'
+test_expect_success 'add -p handles relative paths' '
+ git reset --hard &&
+
+ echo base >relpath.c &&
+ git add "*.c" &&
+ git commit -m relpath &&
+
+ echo change >relpath.c &&
+ mkdir -p subdir &&
+ git -C subdir add -p .. 2>error <<-\EOF &&
+ y
+ EOF
+
+ test_must_be_empty error &&
+
+ cat >expect <<-\EOF &&
+ relpath.c
+ EOF
+ git diff --cached --name-only >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'add -p does not expand argument lists' '
git reset --hard &&
diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh
index a2c9e74..d38ecee 100755
--- a/t/t5533-push-cas.sh
+++ b/t/t5533-push-cas.sh
@@ -229,4 +229,33 @@ test_expect_success 'new branch already exists' '
)
'
+test_expect_success 'background updates of REMOTE can be mitigated with a non-updated REMOTE-push' '
+ rm -rf src dst &&
+ git init --bare src.bare &&
+ test_when_finished "rm -rf src.bare" &&
+ git clone --no-local src.bare dst &&
+ test_when_finished "rm -rf dst" &&
+ (
+ cd dst &&
+ test_commit G &&
+ git remote add origin-push ../src.bare &&
+ git push origin-push master:master
+ ) &&
+ git clone --no-local src.bare dst2 &&
+ test_when_finished "rm -rf dst2" &&
+ (
+ cd dst2 &&
+ test_commit H &&
+ git push
+ ) &&
+ (
+ cd dst &&
+ test_commit I &&
+ git fetch origin &&
+ test_must_fail git push --force-with-lease origin-push &&
+ git fetch origin-push &&
+ git push --force-with-lease origin-push
+ )
+'
+
test_done
diff --git a/t/t5547-push-quarantine.sh b/t/t5547-push-quarantine.sh
index af9fcd8..113c870 100755
--- a/t/t5547-push-quarantine.sh
+++ b/t/t5547-push-quarantine.sh
@@ -58,4 +58,15 @@ test_expect_success 'push to repo path with path separator (colon)' '
git push "$(pwd)/xxx${pathsep}yyy.git" HEAD
'
+test_expect_success 'updating a ref from quarantine is forbidden' '
+ git init --bare update.git &&
+ write_script update.git/hooks/pre-receive <<-\EOF &&
+ read old new refname
+ git update-ref refs/heads/unrelated $new
+ exit 1
+ EOF
+ test_must_fail git push update.git HEAD &&
+ git -C update.git fsck
+'
+
test_done
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index cf77a3a..1b8f1db 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -38,6 +38,14 @@ test_expect_success 'submodule update aborts on missing .gitmodules file' '
test_i18ngrep "Submodule path .sub. not initialized" actual
'
+test_expect_success 'submodule update aborts on missing gitmodules url' '
+ test_when_finished "git update-index --remove sub" &&
+ git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub &&
+ test_when_finished "rm -f .gitmodules" &&
+ git config -f .gitmodules submodule.s.path sub &&
+ test_must_fail git submodule init
+'
+
test_expect_success 'configuration parsing' '
test_when_finished "rm -f .gitmodules" &&
cat >.gitmodules <<-\EOF &&
@@ -273,6 +281,20 @@ test_expect_success 'submodule add with ./, /.. and // in path' '
test_cmp empty untracked
'
+test_expect_success 'submodule add with \\ in path' '
+ test_when_finished "rm -rf parent sub\\with\\backslash" &&
+
+ # Initialize a repo with a backslash in its name
+ git init sub\\with\\backslash &&
+ touch sub\\with\\backslash/empty.file &&
+ git -C sub\\with\\backslash add empty.file &&
+ git -C sub\\with\\backslash commit -m "Added empty.file" &&
+
+ # Add that repository as a submodule
+ git init parent &&
+ git -C parent submodule add ../sub\\with\\backslash
+'
+
test_expect_success 'submodule add in subdirectory' '
echo "refs/heads/master" >expect &&
>empty &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 5ed2813..2cb999e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1457,4 +1457,38 @@ test_expect_failure 'complete with tilde expansion' '
test_completion "git add ~/tmp/" "~/tmp/file"
'
+test_expect_success 'setup other remote for remote reference completion' '
+ git remote add other otherrepo &&
+ git fetch other
+'
+
+for flag in -d --delete
+do
+ test_expect_success "__git_complete_remote_or_refspec - push $flag other" '
+ sed -e "s/Z$//" >expected <<-EOF &&
+ master-in-other Z
+ EOF
+ (
+ words=(git push '$flag' other ma) &&
+ cword=${#words[@]} cur=${words[cword-1]} &&
+ __git_complete_remote_or_refspec &&
+ print_comp
+ ) &&
+ test_cmp expected out
+ '
+
+ test_expect_failure "__git_complete_remote_or_refspec - push other $flag" '
+ sed -e "s/Z$//" >expected <<-EOF &&
+ master-in-other Z
+ EOF
+ (
+ words=(git push other '$flag' ma) &&
+ cword=${#words[@]} cur=${words[cword-1]} &&
+ __git_complete_remote_or_refspec &&
+ print_comp
+ ) &&
+ test_cmp expected out
+ '
+done
+
test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 6b7356d..aa15111 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -604,12 +604,18 @@ static int switch_cache_bottom(struct traverse_info *info)
return ret;
}
+static inline int are_same_oid(struct name_entry *name_j, struct name_entry *name_k)
+{
+ return name_j->oid && name_k->oid && !oidcmp(name_j->oid, name_k->oid);
+}
+
static int traverse_trees_recursive(int n, unsigned long dirmask,
unsigned long df_conflicts,
struct name_entry *names,
struct traverse_info *info)
{
int i, ret, bottom;
+ int nr_buf = 0;
struct tree_desc t[MAX_UNPACK_TREES];
void *buf[MAX_UNPACK_TREES];
struct traverse_info newinfo;
@@ -626,18 +632,40 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
newinfo.pathlen += tree_entry_len(p) + 1;
newinfo.df_conflicts |= df_conflicts;
+ /*
+ * Fetch the tree from the ODB for each peer directory in the
+ * n commits.
+ *
+ * For 2- and 3-way traversals, we try to avoid hitting the
+ * ODB twice for the same OID. This should yield a nice speed
+ * up in checkouts and merges when the commits are similar.
+ *
+ * We don't bother doing the full O(n^2) search for larger n,
+ * because wider traversals don't happen that often and we
+ * avoid the search setup.
+ *
+ * When 2 peer OIDs are the same, we just copy the tree
+ * descriptor data. This implicitly borrows the buffer
+ * data from the earlier cell.
+ */
for (i = 0; i < n; i++, dirmask >>= 1) {
- const unsigned char *sha1 = NULL;
- if (dirmask & 1)
- sha1 = names[i].oid->hash;
- buf[i] = fill_tree_descriptor(t+i, sha1);
+ if (i > 0 && are_same_oid(&names[i], &names[i - 1]))
+ t[i] = t[i - 1];
+ else if (i > 1 && are_same_oid(&names[i], &names[i - 2]))
+ t[i] = t[i - 2];
+ else {
+ const unsigned char *sha1 = NULL;
+ if (dirmask & 1)
+ sha1 = names[i].oid->hash;
+ buf[nr_buf++] = fill_tree_descriptor(t+i, sha1);
+ }
}
bottom = switch_cache_bottom(&newinfo);
ret = traverse_trees(n, t, &newinfo);
restore_cache_bottom(&newinfo, bottom);
- for (i = 0; i < n; i++)
+ for (i = 0; i < nr_buf; i++)
free(buf[i]);
return ret;
diff --git a/wrapper.c b/wrapper.c
index 0542fc7..d837417 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -655,3 +655,16 @@ void sleep_millisec(int millisec)
{
poll(NULL, 0, millisec);
}
+
+int xgethostname(char *buf, size_t len)
+{
+ /*
+ * If the full hostname doesn't fit in buf, POSIX does not
+ * specify whether the buffer will be null-terminated, so to
+ * be safe, do it ourselves.
+ */
+ int ret = gethostname(buf, len);
+ if (!ret)
+ buf[len - 1] = 0;
+ return ret;
+}