diff options
86 files changed, 2600 insertions, 1472 deletions
diff --git a/.travis.yml b/.travis.yml index c757a111ce..297b0bca97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,6 +71,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 ba19a3bcf1..f1560f1406 100644 --- a/Documentation/RelNotes/2.13.0.txt +++ b/Documentation/RelNotes/2.13.0.txt @@ -182,6 +182,19 @@ 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". + Performance, Internal Implementation, Development Support etc. @@ -242,8 +255,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 +289,42 @@ 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. + Also contains various documentation updates and code clean-ups. @@ -492,6 +532,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 +588,11 @@ 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). diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt index 163b9f6f41..ea70653369 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 e015f5b3cc..8ac75fcc25 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 f7a069bb92..c890328b02 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-push.txt b/Documentation/git-push.txt index 1624a35888..0a639664fd 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 67d48e6883..53f4e14444 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 0ccd5fbc78..86a4b32f0f 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 2f4ff50156..d0fec4cddd 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 553cf8413f..b472acc356 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 a53d093ca1..4736483865 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 3a0ec8c53a..7577f27ec2 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 9565dc3fda..32343ae295 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -256,6 +256,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 8f7c50f330..db5d47eb19 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 e6320891b1..9c8982ec98 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 25378f68d3..db219f5c07 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 c59ac9936a..5b0ba3ef20 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 e681ede9d8..598353ea08 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=' ' @@ -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 @@ -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 f7a7a971fb..a95dd8b4e6 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 0552c42ad1..48a513a84d 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 4e288bc513..1d805f5da8 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 4f49a0edb9..3a554ad50c 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 f76e4163ab..b5e13a4556 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 2daede7820..91f7696a85 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 d449e46db5..a6c70dbe9e 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 d8aa26d8ab..dd1a4a94e4 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 3cba3fd278..f96834f42c 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 065515baba..ab17668f43 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/worktree.c b/builtin/worktree.c index 9993ded41a..1722a9bdc2 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() }; @@ -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); @@ -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); } @@ -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 1150164d5c..af658995d5 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 3cbd420019..91550bfb0b 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 55ca1b1334..ac295420dd 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) @@ -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 1ea3a08609..cf58f875b8 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 42969353d6..afb8b05024 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 8a4a3f85e7..bd04564a69 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 48d7c5ded4..db1deed846 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 6ec35e5fcd..c0d0e9a4c6 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" @@ -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")); @@ -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]; @@ -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 5998605acc..32caaaff74 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); @@ -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 69ef86b85a..50f76fff45 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/read-cache.c b/read-cache.c index e447751823..b3d0f3c30b 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) @@ -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) @@ -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 4d705b4037..83ea080e01 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 0000000000..6059362f1d --- /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 0000000000..ffdc54f3f0 --- /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 690498698e..3d46131efb 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 d41fe16ff3..ece45993da 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 77afecaebf..130cc868e5 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; } @@ -1820,10 +1820,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); } @@ -2167,7 +2167,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 7bc4e75d22..f6c1a3dfb0 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 1577e2d7dd..59a4ed2ed3 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 45016ad86d..003ca1879e 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 5615d7392e..d3299e29c0 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 acd5db180f..721650256e 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 0000000000..4a45a54e92 --- /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/p0005-status.sh b/t/perf/p0005-status.sh new file mode 100755 index 0000000000..0b0aa9858f --- /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 0000000000..78cc23fe2f --- /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 0000000000..72e3dc3e19 --- /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 0000000000..fcfc992b5b --- /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 0000000000..28720e4e10 --- /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 d0bee08b2e..89826c568b 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 0000000000..7d6d21425f --- /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 e833939320..933915ec06 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 b97357b8ab..1af8c454cf 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 a23cd7b576..dc98b4bc6d 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 33a51c9a67..677e15a7a4 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 b618d6be21..b5c47ac602 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 848da5f368..720063bf0d 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 4cf6ccf5a8..ebb956fd16 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 0000000000..2afb564701 --- /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/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index f9528fa00c..2ecb43a616 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 a2c9e7439f..d38ecee217 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 af9fcd833a..113c87007f 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 cf77a3a357..c2706fe472 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -273,6 +273,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 5ed28135be..2cb999ecfa 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 6b7356dab2..aa15111fef 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; @@ -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; +} |