diff --git a/.b4-config b/.b4-config new file mode 100644 index 00000000000000..fd4fb56b6d5678 --- /dev/null +++ b/.b4-config @@ -0,0 +1,6 @@ +# Note that these are default values that you can tweak via the typical +# git-config(1) machinery. You thus shouldn't ever have to change this file. +# See also https://b4.docs.kernel.org/en/latest/config.html. +[b4] +send-same-thread = shallow +prep-cover-template = ./.b4-cover-template diff --git a/.b4-cover-template b/.b4-cover-template new file mode 100644 index 00000000000000..ab864933b5c846 --- /dev/null +++ b/.b4-cover-template @@ -0,0 +1,11 @@ +${cover} + +--- +${shortlog} + +${diffstat} + +${range_diff} +--- +base-commit: ${base_commit} +${prerequisites} diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index b9fdefce0224c9..4832e5bad59bf1 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -790,7 +790,7 @@ We can note a few things: v3", etc. in place of "PATCH". For example, "[PATCH v2 1/3]" would be the first of three patches in the second iteration. Each iteration is sent with a new cover letter (like "[PATCH v2 0/3]" above), itself a reply to the cover letter of the - previous iteration (more on that below). + first iteration (more on that below). NOTE: A single-patch topic is sent with "[PATCH]", "[PATCH v2]", etc. without _i_/_n_ numbering (in the above thread overview, no single-patch topic appears, @@ -833,7 +833,7 @@ This patchset is part of the MyFirstContribution tutorial and should not be merged. ---- -At this point the tutorial diverges, in order to demonstrate two +At this point the tutorial diverges, in order to demonstrate three different methods of formatting your patchset and getting it reviewed. The first method to be covered is GitGitGadget, which is useful for those @@ -845,9 +845,14 @@ more fine-grained control over the emails to be sent. This method requires some setup which can change depending on your system and will not be covered in this tutorial. +The third method to be covered is `b4`, which builds on top of `git +format-patch` and `git send-email`. This method is the recommended way to +submit patches via mail as it automates a lot of the bookkeeping required by +`git send-email`. + Regardless of which method you choose, your engagement with reviewers will be -the same; the review process will be covered after the sections on GitGitGadget -and `git send-email`. +the same; the review process will be covered after the sections on GitGitGadget, +`git send-email` and `b4`. [[howto-ggg]] == Sending Patches via GitGitGadget @@ -1214,7 +1219,7 @@ between your last version and now, if it's something significant. You do not need the exact same body in your second cover letter; focus on explaining to reviewers the changes you've made that may not be as visible. -You will also need to go and find the Message-ID of your previous cover letter. +You will also need to go and find the Message-ID of your first cover letter. You can either note it when you send the first series, from the output of `git send-email`, or you can look it up on the https://lore.kernel.org/git[mailing list]. Find your cover letter in the @@ -1227,8 +1232,8 @@ Message-ID: Your Message-ID is ``. This example will be used below as well; make sure to replace it with the correct Message-ID for your -**previous cover letter** - that is, if you're sending v2, use the Message-ID -from v1; if you're sending v3, use the Message-ID from v2. +**first cover letter** - that is, for any subsequent version that you send, +always use the Message-ID from v1. While you're looking at the email, you should also note who is CC'd, as it's common practice in the mailing list to keep all CCs on a thread. You can add @@ -1296,6 +1301,87 @@ index 88f126184c..38da593a60 100644 2.21.0.392.gf8f6787159e-goog ---- +[[howto-b4]] +== Sending Patches with `b4` + +`b4` is a tool that builds on top of `git format-patch` and `git send-email`. +It automates much of the bookkeeping involved in sending a patch series to a +mailing-list-based project. + +Refer to the https://b4.docs.kernel.org/[b4 documentation] for a full reference. + +[[prep-b4]] +=== Preparing a Patch Series + +`b4` tracks your patch series as a branch. To start tracking the `psuh` branch +you have been working on, run: + +---- +$ b4 prep --enroll master +---- + +This enrolls the current branch, using `master` as the base of the topic. `b4` +manages the cover letter as part of the branch, so you can edit it at any time +with: + +---- +$ b4 prep --edit-cover +---- + +The cover letter not only tracks the content of the top-level mail, but also +the set of recipients. You can add recipients by adding `To:` and `Cc:` +trailer lines. + +[[send-b4]] +=== Sending the Patches + +Before sending the series out for real, you can inspect what `b4` would send by +passing `--dry-run`: + +---- +$ b4 send --dry-run +---- + +Once you are happy with the result, send the series with: + +---- +$ b4 send +---- + +[[v2-b4]] +=== Sending v2 + +When you are ready to send a new iteration of your series, refine your +patches as usual using linkgit:git-rebase[1]. Note that you typically want to +rebase on top of the cover letter. You can configure an alias to enable easy +rebases going forward: + +--- +$ git config set alias.b4-rebase 'rebase "HEAD^{/--- b4-submit-tracking ---}"' +$ git b4-rebase -i +--- + +Before sending out the new version you should also update the cover letter with +`b4 prep --edit-cover` to note the relevant changes compared to the previous +version. You can inspect the changes between the two versions with `b4 prep +--compare-to=v1`. + +Same as with the first version, you can use `b4 send` to send out the second +version. `b4` automatically bumps the version to `v2`, generates the range-diff +against the previous iteration, and threads the new series as a reply to the +cover letter of the first version. + +[[configure-b4]] +=== Configure b4 + +`b4` can be configured via linkgit:git-config[1]. In addition to that, projects +can have their own set of defaults in `.b4-config` in the root tree, which also +uses Git's config format. The user's configuration always takes precedence over +the per-project defaults. + +Refer to the https://b4.docs.kernel.org/en/latest/config.html[b4 config documentation] +for more information on the available options. + [[now-what]] == My Patch Got Emailed - Now What? @@ -1330,6 +1416,28 @@ previous one" patches over 2 days), reviewers would strongly prefer if a single polished version came 2 days later instead, and that version with fewer mistakes were the only one they would need to review. +This consideration applies not only when going from the initial patch to v2, +but also to later iterations of the same series. There is no fixed rule for how +long to wait before sending a new version. A useful default is to send at most +one new version of the same patch series per day. This gives multiple reviewers +time to comment, gives reviewers across time zones a fair chance to +participate, lets you batch feedback together, and gives you time to think +through the comments you received. Knowing that you should not immediately send +another version also encourages you to review the patches more carefully before +sending them, catch small mistakes such as typos and off-by-one errors +yourself, and let reviewers spend more of their attention on design, +algorithms, and other substantial issues. + +The right timing depends on the topic and the feedback. Larger series usually +need more review time. If the only comments so far are minor, such as typo +fixes, it often makes sense to wait a little longer in case deeper reviews are +still coming. If the comments call for substantial rework, do not rush out an +updated version before you have reviewed the larger changes carefully. Instead, +reply to the review that prompted the rewrite, say that you are preparing a +substantial rework, and mention which parts of the current series will become +obsolete so reviewers can avoid spending time on them until the updated series +is ready. + [[reviewing]] === Responding to Reviews @@ -1337,11 +1445,13 @@ fewer mistakes were the only one they would need to review. After a few days, you will hopefully receive a reply to your patchset with some comments. Woohoo! Now you can get back to work. -It's good manners to reply to each comment, notifying the reviewer that you have -made the change suggested, feel the original is better, or that the comment -inspired you to do something a new way which is superior to both the original -and the suggested change. This way reviewers don't need to inspect your v2 to -figure out whether you implemented their comment or not. +It's good manners to reply to each comment in the mailing list discussion +instead of letting the next version of your patch be your only response. Tell +the reviewer whether you plan to make the suggested change, keep the original, +or pursue a different approach. This way reviewers can respond to your reasoning +before you spend time preparing a version they may not agree with, and later do +not need to inspect your v2 to figure out whether you implemented their comment +or not. Reviewers may ask you about what you wrote in the patchset, either in the proposed commit log message or in the changes themselves. You diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc new file mode 100644 index 00000000000000..fda10a014f85ba --- /dev/null +++ b/Documentation/RelNotes/2.56.0.adoc @@ -0,0 +1,113 @@ +Git v2.56 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * Advice shown by "git status" when the local branch is behind or has + diverged from its push branch has been updated to suggest "git pull + ". + + * The handling of promisor-remote protocol capability has been updated + to allow the other side to add to the list of promisor remotes via the + 'promisor.acceptFromServerURL' configuration variable. + + * The 'ort' merge backend has been hardened against corrupt trees by + ensuring it aborts under appropriate error conditions. + + * The `fetch.followRemoteHEAD` configuration variable has been added to + provide a default for the per-remote `remote..followRemoteHEAD` + setting. + + * "git log --follow" has been updated to better handle non-linear + history, in which the path being tracked gets renamed differently in + multiple history lines. + + * The "git repo info" command has been taught new keys to output both + absolute and relative paths for "gitdir" and "commondir", supported by + a new path-formatting helper extracted from "git rev-parse". + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * The refactoring of 'setup.c' has been continued to drop remaining + global state (`git_work_tree_cfg`, `is_bare_repository_cfg`), updating + `is_bare_repository()` to no longer implicitly rely on + `the_repository`. + + * Project-specific configuration for b4 has been introduced, and the + documentation has been updated to recommend using it as a + streamlined method for submitting patches. + + * The default format path of git cat-file --batch has been optimized + to use strbuf_add_oid_hex() and strbuf_add_uint() instead of + strbuf_addf(), yielding a noticeable speedup. + + * Commands that list branches and tags (like git branch and git tag) + have been optimized to pass the namespace prefix when initializing + their ref iterator, avoiding a loose-ref scaling regression in + repositories with many unrelated loose references. + + * The packed object source has been refactored into a proper struct + odb_source. + + * The global configuration variables protect_hfs and protect_ntfs have + been migrated into struct repo_config_values to tie them to + per-repository configuration state. + + * The trailer sections in SubmittingPatches have been updated to + encourage use of standard trailers. + + * The documentation in SubmittingPatches has been updated to clarify how + patch contributors should respond to design and viability critiques, + and how the resolution of such critiques should be recorded in the + final commit messages. + + * The pack-objects command has been updated to support reachability + bitmaps and delta-islands concurrently with the `--path-walk` option, + allowing faster packaging by falling back to path-walk when bitmaps + cannot fully satisfy the request. + + * Documentation on community contribution guidelines has been updated to + encourage replying to review comments before rerolling, and to advise + a default limit of at most one reroll per day to give reviewers across + different time zones enough time to participate. + + +Fixes since v2.55 +----------------- + + * A regression in the error diagnosis code for invalid .git files has + been fixed, avoiding a potential NULL-pointer crash when reporting + that a .git file does not point to a valid repository. + (merge 54a441bcea jk/setup-gitfile-diag-fix later to maint). + + * Support for hashing loose or packed objects larger than 4GB on Windows + and other LLP64 platforms has been improved by converting object header + buffers and data-handling functions from 'unsigned long' to 'size_t'. + (merge d99e13d0be po/hash-object-size-t later to maint). + + * The display of the rebase todo list in "git status" has been + improved to correctly abbreviate object IDs for more commands and + avoid misinterpreting refs as object IDs. + (merge 6f34e5f9e3 pw/status-rebase-todo later to maint). + + * Reference backend configuration has been updated to load lazily to + avoid recursive calls during repository initialization when 'onbranch' + configuration conditions are evaluated. This has also fixed a memory + leak and allowed the unused `chdir_notify_reparent()` machinery to be + dropped. + (merge d6522d01df ps/refs-onbranch-fixes later to maint). + + * The connectivity check has been refactored to search for promisor + objects in a generic way using the object database interface, + rather than iterating packfiles directly. This allows connectivity + checks to work properly in repositories that do not use packfiles. + (merge 66ee9cb930 ps/connected-generic-promisor-checks later to maint). + + * A test checking interactions between git rebase --quit and + autostash in t3420-rebase-autostash.sh has been corrected to use + test_path_is_missing instead of ! grep on a file that shouldn't + exist in the conflicted state. + (merge eaad121fef sg/t3420-do-not-grep-in-missing-file later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index f042bb5aaf4a45..d2d82eb543279d 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -48,13 +48,41 @@ area. . You get comments and suggestions for improvements. You may even get them in an "on top of your change" patch form. You are expected to - respond to them with "Reply-All" on the mailing list, while taking - them into account while preparing an updated set of patches. + respond to them with "Reply-All" on the mailing list, instead of + letting an updated patch series be your only response. Tell + reviewers which suggestions you plan to use, which ones you disagree + with, and when a comment leads you to consider a different approach. + Use these replies and any follow-up discussion as input when + preparing an updated set of patches. ++ +Be particularly mindful of critiques regarding the high-level design +or viability of your proposal (e.g., questioning if the feature is +worth implementing, or if the chosen approach is appropriate). Defend +your design decisions on the list first and work with reviewers and +other members to improve the design before revising the implementation. +This will avoid wasting effort on an implementation before its design is +solid. ++ +Make sure that any new version explains and justifies those design +decisions more clearly, in the cover letter and in the revised commit +messages. Aim to make the reviewers say "it is now clear why we may +want to do this with the updated version". ++ +Topics with unresolved fundamental design critiques will not be +considered ready for merging. + It is often beneficial to allow some time for reviewers to provide feedback before sending a new version, rather than sending an updated series immediately after receiving a review. This helps collect broader -input and avoids unnecessary churn from many rapid iterations. +input, gives reviewers in different time zones a fair chance to comment, +and avoids unnecessary churn from many rapid iterations. Waiting also +encourages you to polish each version before sending it, so reviewers +can focus on substantial issues rather than typos or other small +mistakes. ++ +As a rough default, avoid sending more than one new version of the same +series per day, while considering the size of the series and the depth +of review. . These early update iterations are expected to be full replacements, not incremental updates on top of what you posted already. If you @@ -323,6 +351,10 @@ The body should provide a meaningful commit message, which: . alternate solutions considered but discarded, if any. +. records the resolution of design or viability concerns raised by the + community during the review, if any, ensuring the historical record + explains why the chosen approach was accepted over alternatives. + [[present-tense]] The problem statement that describes the status quo is written in the present tense. Write "The code does X when it is given input Y", @@ -375,7 +407,7 @@ or, on an older version of Git without support for --pretty=reference: .... [[sign-off]] -=== Certify your work by adding your `Signed-off-by` trailer +=== Certify your work by adding your `Signed-off-by:` trailer To improve tracking of who did what, we ask you to certify that you wrote the patch or have the right to pass it on under the same license @@ -412,7 +444,7 @@ d. I understand and agree that this project and the contribution this project or the open source license(s) involved. ____ -you add a "Signed-off-by" trailer to your commit, that looks like +you add a `Signed-off-by:` trailer to your commit, that looks like this: .... @@ -422,19 +454,23 @@ this: This line can be added by Git if you run the git-commit command with the -s option. -Notice that you can place your own `Signed-off-by` trailer when +Notice that you can place your own `Signed-off-by:` trailer when forwarding somebody else's patch with the above rules for D-C-O. Indeed you are encouraged to do so. Do not forget to place an in-body "From: " line at the beginning to properly attribute the change to its true author (see (2) above). +Place this `Signed-off-by:` trailer at the end, after trailers added by +others and after other trailers added by you; see +<> below ("chronological order"). + This procedure originally came from the Linux kernel project, so our rule is quite similar to theirs, but what exactly it means to sign-off your patch differs from project to project, so it may be different from that of the project you are accustomed to. [[real-name]] -Please use a known identity in the `Signed-off-by` trailer, since we cannot +Please use a known identity in the `Signed-off-by:` trailer, since we cannot accept anonymous contributions. It is common, but not required, to use some form of your real name. We realize that some contributors are not comfortable doing so or prefer to contribute under a pseudonym or preferred name and we can accept @@ -445,7 +481,15 @@ The goal of this policy is to allow us to have sufficient information to contact you if questions arise about your contribution. [[commit-trailers]] -If you like, you can put extra trailers at the end: +=== Commit trailers +It is polite to credit people who have helped with your work to a +substantial enough degree. This project uses commit trailers for that, +where the credited person is written out like a Git author, i.e. with +both their name and their email address. Note that the threshold to +credit someone is a judgement call, and crediting someone for simple +review work is certainly not necessary. + +These are the common trailers in use: . `Reported-by:` is used to credit someone who found the bug that the patch attempts to fix. @@ -458,6 +502,10 @@ If you like, you can put extra trailers at the end: and found it to have the desired effect. . `Co-authored-by:` is used to indicate that people exchanged drafts of a patch before submitting it. +. `Based-on-patch-by:` is used when someone else authored parts of the + patch that you are submitting. This might be relevant if someone sent + a patch to the mailing list with their sign-off. (Be mindful and ask + them to sign off on it if they did not.) . `Helped-by:` is used to credit someone who suggested ideas for changes without providing the precise changes in patch form. . `Mentored-by:` is used to credit someone with helping develop a @@ -469,8 +517,18 @@ While you can also create your own trailer if the situation warrants it, we encourage you to instead use one of the common trailers in this project highlighted above. +Other projects might regularly refer to other kinds of data, like +`Fixes:` and `Link:` in the Linux Kernel project, but these ones in +particular are not used in this project. + Only capitalize the very first letter of the trailer, i.e. favor -"Signed-off-by" over "Signed-Off-By" and "Acked-by:" over "Acked-By". +`Signed-off-by:` over `Signed-Off-By:` and `Acked-by:` over `Acked-By:`. + +As mentioned under <> above, trailers are added in +chronological order; one person might sign-off on a patch and send it to +someone else, who then in turn adds her own sign-off. Further, any +trailers that you add beyond your sign-off should come before that +sign-off. That makes it clear what trailers which person added. [[cover-letter]] === Cover Letter @@ -587,8 +645,8 @@ when the maintainer did not heavily participate in the discussion and instead left the review to trusted others. Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and -`Tested-by:` lines as necessary to credit people who helped your -patch, and "cc:" them when sending such a final version for inclusion. +`Tested-by:` (see <>), and "cc:" them +when sending such a final version for inclusion. ==== `format-patch` and `send-email` @@ -598,8 +656,10 @@ your existing e-mail client (often optimized for "multipart/*" MIME type e-mails) might render your patches unusable. NOTE: Here we outline the procedure using `format-patch` and -`send-email`, but you can instead use GitGitGadget to send in your -patches (see link:MyFirstContribution.html[MyFirstContribution]). +`send-email`, but you can instead use GitGitGadget or `b4` to send in +your patches (see link:MyFirstContribution.html[MyFirstContribution]). +Contributors are encouraged to use `b4`, which automates much of the +bookkeeping that is otherwise done by hand. People on the Git mailing list need to be able to read and comment on the changes you are submitting. It is important for @@ -613,10 +673,13 @@ grouped into their own e-mail thread to help readers find all parts of the series. To that end, send them as replies to either an additional "cover letter" message (see below), the first patch, or the respective preceding patch. Here is a link:MyFirstContribution.html#v2-git-send-email[step-by-step guide] on -how to submit updated versions of a patch series. +how to submit updated versions of a patch series. Before sending another +version, make sure you have answered meaningful review comments in the existing +discussion. Also give reviewers enough time to comment before sending another +version. If your log message (including your name on the -`Signed-off-by` trailer) is not writable in ASCII, make sure that +`Signed-off-by:` trailer) is not writable in ASCII, make sure that you send off a message in the correct encoding. WARNING: Be wary of your MUAs word-wrap @@ -636,7 +699,7 @@ previously sent. The `git format-patch` command follows the best current practice to format the body of an e-mail message. At the beginning of the patch should come your commit message, ending with the -`Signed-off-by` trailers, and a line that consists of three dashes, +`Signed-off-by:` trailers, and a line that consists of three dashes, followed by the diffstat information and the patch itself. If you are forwarding a patch from somebody else, optionally, at the beginning of the e-mail message just before the commit diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc index 257db58918179a..c3c190ba6a4fb8 100644 --- a/Documentation/config/advice.adoc +++ b/Documentation/config/advice.adoc @@ -48,6 +48,10 @@ all advice messages. to create a local branch after the fact. diverging:: Shown when a fast-forward is not possible. + fetchRemoteHEADWarn:: + Shown when linkgit:git-fetch[1] reveals that a remote `HEAD` + differs from what is set locally and the user has opted into + receiving a warning in this situation. fetchShowForcedUpdates:: Shown when linkgit:git-fetch[1] takes a long time to calculate forced updates after ref updates, or to warn diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc index 04ac90912d3a58..00435e9a16d9f9 100644 --- a/Documentation/config/fetch.adoc +++ b/Documentation/config/fetch.adoc @@ -126,3 +126,22 @@ the new bundle URI. The creation token values are chosen by the provider serving the specific bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to remove the value for the `fetch.bundleCreationToken` value before fetching. + +`fetch.followRemoteHEAD`:: + When fetching using a default refspec, this setting determines how to handle + differences between a fetched remote's `HEAD` and the local + `remotes//HEAD` symbolic-ref. Its value is one of ++ +-- +`create`;; + Create `remotes//HEAD` if a ref exists on the remote, but not locally. + An existing symbolic-ref will not be touched. This is the default value. +`warn`;; + Display a warning if the remote advertises a different `HEAD` than what is + set locally. Behaves like "create" if the local symbolic-ref doesn't exist. +`always`;; + Silently update `remotes//HEAD` whenever the remote advertises a new + value. +`never`;; + Never create or modify the `remotes//HEAD` symbolic-ref. +-- diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc index f20cc25cd7c3bf..757a7be196ab38 100644 --- a/Documentation/config/log.adoc +++ b/Documentation/config/log.adoc @@ -53,8 +53,7 @@ This is the same as the `--decorate` option of the `git log`. `log.follow`:: If `true`, `git log` will act as if the `--follow` option was used when a single is given. This has the same limitations as `--follow`, - i.e. it cannot be used to follow multiple files and does not work well - on non-linear history. + i.e. it cannot be used to follow multiple files. `log.graphColors`:: A list of colors, separated by commas, that can be used to draw diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index 39af63dcb87a32..c1f3d766a64cc0 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -32,24 +32,136 @@ variable is set to "true", and the "name" and "url" fields are always advertised regardless of this setting. promisor.acceptFromServer:: - If set to "all", a client will accept all the promisor remotes - a server might advertise using the "promisor-remote" - capability. If set to "knownName" the client will accept - promisor remotes which are already configured on the client - and have the same name as those advertised by the client. This - is not very secure, but could be used in a corporate setup - where servers and clients are trusted to not switch name and - URLs. If set to "knownUrl", the client will accept promisor - remotes which have both the same name and the same URL - configured on the client as the name and URL advertised by the - server. This is more secure than "all" or "knownName", so it - should be used if possible instead of those options. Default - is "none", which means no promisor remote advertised by a - server will be accepted. By accepting a promisor remote, the - client agrees that the server might omit objects that are - lazily fetchable from this promisor remote from its responses - to "fetch" and "clone" requests from the client. Name and URL - comparisons are case sensitive. See linkgit:gitprotocol-v2[5]. + Controls which promisor remotes advertised by a server (using the + "promisor-remote" protocol capability) a client will accept. By + accepting a promisor remote, the client agrees that the server + might omit objects that are lazily fetchable from this promisor + remote from its responses to "fetch" and "clone" requests. ++ +Note that this option does not cause new remotes to be automatically +created in the client's configuration. It only allows remotes which +are somehow already configured to be trusted for the current +operation, or their fields to be updated (if `promisor.storeFields` is +set and the remote already exists locally). To allow Git to +automatically create and persist new remotes from server +advertisements, use `promisor.acceptFromServerUrl`. ++ +The available options are: ++ +* `none` (default): No promisor remote advertised by a server will be + accepted. ++ +* `knownUrl`: The client will accept promisor remotes that are already + configured on the client and have both the same name and the same URL + as advertised by the server. This is more secure than `all` or + `knownName`, and should be used if possible instead of those options. ++ +* `knownName`: The client will accept promisor remotes that are already + configured on the client and have the same name as those advertised + by the server. This is not very secure, but could be used in a corporate + setup where servers and clients are trusted to not switch names and URLs. ++ +* `all`: The client will accept all the promisor remotes a server might + advertise. This is the least secure option and should only be used in + fully trusted environments. ++ +Name and URL comparisons are case-sensitive. See linkgit:gitprotocol-v2[5] +for protocol details. + +promisor.acceptFromServerUrl:: + A glob pattern to specify which server-advertised URLs a + client is allowed to act on. When a URL matches, the client + will accept the advertised remote as a promisor remote, may + automatically create a new remote configuration for it and may + automatically accept field updates (such as authentication + tokens) from the server, even if `promisor.acceptFromServer` + is set to `none` (the default). ++ +This option can appear multiple times in config files. An advertised +URL will be accepted if it matches _ANY_ glob pattern specified by +this option in _ANY_ config file read by Git. ++ +When both `promisor.acceptFromServer` and `promisor.acceptFromServerUrl` +are set, `promisor.acceptFromServerUrl` is consulted first and takes +precedence: if a matching pattern leads to acceptance (either by +auto-configuring an unknown remote or by accepting field updates for +a known remote whose URL matches both the local configuration and the +allowlist), the advertised remote is accepted regardless of the +`promisor.acceptFromServer` setting. If no pattern in +`promisor.acceptFromServerUrl` triggers acceptance, the decision is +left to `promisor.acceptFromServer`. ++ +Note however that, even when an advertised URL matches a pattern in +`promisor.acceptFromServerUrl`, an already-existing remote on the +client whose name matches the advertised name but whose configured URL +differs from the advertised one will _NOT_ be accepted through +`promisor.acceptFromServerUrl`. This prevents a server from silently +re-pointing an existing client-side remote at a different URL. (Such a +remote may still be accepted through `promisor.acceptFromServer=all` +or `=knownName`, which have their own, looser semantics; see the +documentation of that option.) ++ +Be _VERY_ careful with these patterns: `*` matches any sequence of +characters within the 'host' and 'path' parts of a URL (but cannot +cross part boundaries). An overly broad pattern is a major security +risk, as a matching URL allows a server to auto-configure new remotes +and to update fields (such as authentication tokens) on known remotes +without further confirmation. To minimize security risks, follow these +guidelines: ++ +-- +1. Start with a secure protocol scheme, like `https://` or `ssh://`. ++ +2. Only allow domain names or paths where you control and trust _ALL_ + the content. Be especially careful with shared hosting platforms + like `github.com` or `gitlab.com`. A broad pattern like + `https://gitlab.com/*` is dangerous because it trusts every + repository on the entire platform. Always restrict such patterns to + your specific organization or namespace (e.g., + `https://gitlab.com/your-org/*`). ++ +3. Never use globs at the end of domain names. For example, + `https://cdn.your-org.com/*` might be safe, but + `https://cdn.your-org.com*/*` is a major security risk because + the latter matches `https://cdn.your-org.com.hacker.net/repo`. ++ +4. Be careful using globs at the beginning of domain names. While the + code ensures a `*` in the host cannot cross into the path, a + pattern like `https://*.example.com/*` will still match any + subdomain. This is extremely dangerous on shared hosting platforms + (e.g., `https://*.github.io/*` trusts every user's site on the + entire platform). +-- ++ +Before matching, both the advertised URL and the pattern are +normalized: the scheme and host are lowercased, percent-encoded +characters are decoded where possible, and path segments like `..` +are resolved. The port must also match exactly (e.g., +`https://example.com:8080/*` will not match a URL advertised on +port 9999). The username and password components of the URL are +ignored during matching. Note that embedding credentials in URLs is +discouraged. Passing authentication tokens via the `token` field of +the `promisor-remote` capability is strongly preferred. ++ +The glob pattern can optionally be prefixed with a remote name and an +equals sign (e.g., `cdn=https://cdn.example.com/*`). If such a prefix +is provided, accepted remotes will be saved under that name. If no +such prefix is provided, a safe remote name will be automatically +generated by sanitizing the URL and prefixing it with +`promisor-auto-`. ++ +If a remote with the chosen name already exists but points to a +different URL, Git will append a numeric suffix (e.g., `-1`, `-2`) to +the name to prevent overwriting existing configurations. You should +make sure that this doesn't happen often though, as remotes will be +rejected if the numeric suffix increases too much. In all cases, the +original name advertised by the server is recorded in the +`remote..advertisedAs` configuration variable for tracing and +debugging purposes. ++ +For the security implications of accepting a promisor remote, see the +documentation of `promisor.acceptFromServer`. For details on the +protocol, see linkgit:gitprotocol-v2[5]. promisor.checkFields:: A comma or space separated list of additional remote related diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc index eb9c8a3c488448..3a20d0f7524e72 100644 --- a/Documentation/config/remote.adoc +++ b/Documentation/config/remote.adoc @@ -91,6 +91,15 @@ remote..promisor:: When set to true, this remote will be used to fetch promisor objects. +remote..advertisedAs:: + When a promisor remote is automatically configured using + information advertised by a server through the + `promisor-remote` protocol capability (see + `promisor.acceptFromServerUrl`), the server's originally + advertised name is saved in this variable. This is for + information, tracing and debugging purposes. Users should not + typically modify or create such configuration entries. + remote..partialclonefilter:: The filter that will be applied when fetching from this promisor remote. Changing or clearing this value will only affect fetches for new commits. @@ -157,15 +166,12 @@ Blank values signal to ignore all previous values, allowing a reset of the list from broader config scenarios. remote..followRemoteHEAD:: - How linkgit:git-fetch[1] should handle updates to `remotes//HEAD` - when fetching using the configured refspecs of a remote. - The default value is "create", which will create `remotes//HEAD` - if it exists on the remote, but not locally; this will not touch an - already existing local reference. Setting it to "warn" will print - a message if the remote has a different value than the local one; - in case there is no local reference, it behaves like "create". - A variant on "warn" is "warn-if-not-$branch", which behaves like - "warn", but if `HEAD` on the remote is `$branch` it will be silent. - Setting it to "always" will silently update `remotes//HEAD` to - the value on the remote. Finally, setting it to "never" will never - change or create the local reference. + When fetching this remote using its default refspec, this setting determines + how to handle differences between the remote's `HEAD` and the local + `remotes//HEAD` symbolic-ref. Overrides the value of + `fetch.followRemoteHEAD`. See `fetch.followRemoteHEAD` for a description of + accepted values. ++ +In addition to the values supported by `fetch.followRemoteHEAD`, this setting +may also take on the value "warn-if-not-`$branch`", which behaves like "warn", +but ignores the warning if the remote's `HEAD` is `remotes//$branch`. diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index 8a27aa19fd3f1f..65cd00c152f495 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -402,11 +402,13 @@ will be automatically changed to version `1`. of filenames that cause collisions in Git's default name-hash algorithm. + -Incompatible with `--delta-islands`. The `--use-bitmap-index` option is -ignored in the presence of `--path-walk`. The `--path-walk` option -supports the `--filter=` forms `blob:none`, `blob:limit=`, -`tree:0`, `object:type=`, and `sparse:`. These supported filter -types can be combined with the `combine:+` form. +When `--use-bitmap-index` is specified with `--path-walk`, a successful +bitmap traversal is used for object enumeration, with path-walk +remaining as the fallback traversal when the bitmap cannot satisfy the +request. The `--path-walk` option supports the `--filter=` forms +`blob:none`, `blob:limit=`, `tree:0`, `object:type=`, and +`sparse:`. These supported filter types can be combined with the +`combine:+` form. DELTA ISLANDS diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 42262c198347e5..ed7d80c690c720 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -104,6 +104,21 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.commondir.absolute`:: + The canonical absolute path to the Git repository's common + directory (the shared `.git` directory containing objects, + refs, and global configuration). + +`path.commondir.relative`:: + The path to the Git repository's common directory relative to + the current working directory. + +`path.gitdir.absolute`:: + The canonical absolute path to the Git repository directory (the `.git` directory). + +`path.gitdir.relative`:: + The path to the Git repository directory relative to the current working directory. + `references.format`:: The reference storage format. The valid values are: + diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index befa697d21c281..2beb70595fc1e5 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -866,10 +866,11 @@ the server advertised, the client shouldn't advertise the On the server side, the "promisor.advertise" and "promisor.sendFields" configuration options can be used to control what it advertises. On -the client side, the "promisor.acceptFromServer" configuration option -can be used to control what it accepts, and the "promisor.storeFields" -option, to control what it stores. See the documentation of these -configuration options in linkgit:git-config[1] for more information. +the client side, the "promisor.acceptFromServer" and +"promisor.acceptFromServerUrl" configuration options can be used to +control what it accepts, and the "promisor.storeFields" option, to +control what it stores. See the documentation of these configuration +options in linkgit:git-config[1] for more information. Note that in the future it would be nice if the "promisor-remote" protocol capability could be used by the server, when responding to diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 6e4df991932910..a72f090fe2b8d2 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.55.0 +DEF_VER=v2.55.GIT LF=' ' diff --git a/Makefile b/Makefile index 1cec251f4387cf..1f3f099f5c5705 100644 --- a/Makefile +++ b/Makefile @@ -1218,6 +1218,7 @@ LIB_OBJS += odb/source.o LIB_OBJS += odb/source-files.o LIB_OBJS += odb/source-inmemory.o LIB_OBJS += odb/source-loose.o +LIB_OBJS += odb/source-packed.o LIB_OBJS += odb/streaming.o LIB_OBJS += odb/transaction.o LIB_OBJS += oid-array.o @@ -1233,6 +1234,7 @@ LIB_OBJS += pack-refs.o LIB_OBJS += pack-revindex.o LIB_OBJS += pack-write.o LIB_OBJS += packfile.o +LIB_OBJS += packfile-list.o LIB_OBJS += pager.o LIB_OBJS += parallel-checkout.o LIB_OBJS += parse.o diff --git a/RelNotes b/RelNotes index 159e44a9490cc3..752580e69384ba 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.55.0.adoc \ No newline at end of file +Documentation/RelNotes/2.56.0.adoc \ No newline at end of file diff --git a/attr.c b/attr.c index c61472a4e61e0a..0e63f1b6de8f53 100644 --- a/attr.c +++ b/attr.c @@ -681,7 +681,7 @@ static enum git_attr_direction direction; void git_attr_set_direction(enum git_attr_direction new_direction) { - if (is_bare_repository() && new_direction != GIT_ATTR_INDEX) + if (is_bare_repository(the_repository) && new_direction != GIT_ATTR_INDEX) BUG("non-INDEX attr direction in a bare repo"); if (new_direction != direction) @@ -848,7 +848,7 @@ static struct attr_stack *read_attr(struct index_state *istate, res = read_attr_from_index(istate, path, flags); } else if (tree_oid) { res = read_attr_from_blob(istate, tree_oid, path, flags); - } else if (!is_bare_repository()) { + } else if (!is_bare_repository(the_repository)) { if (direction == GIT_ATTR_CHECKOUT) { res = read_attr_from_index(istate, path, flags); if (!res) diff --git a/builtin/bisect.c b/builtin/bisect.c index e7c2d2f3bb0f4a..798e28f5012d31 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -724,7 +724,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc, struct object_id oid; const char *head; - if (is_bare_repository()) + if (is_bare_repository(the_repository)) no_checkout = 1; /* diff --git a/builtin/blame.c b/builtin/blame.c index ffbd3ce5c5a2e3..553f4cb78055eb 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1163,7 +1163,7 @@ int cmd_blame(int argc, revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); - if (!revs.pending.nr && is_bare_repository()) { + if (!revs.pending.nr && is_bare_repository(the_repository)) { struct commit *head_commit; struct object_id head_oid; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index d6ef8414ee5a0b..60869b8b37fa6b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -458,9 +458,12 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d static void print_default_format(struct strbuf *scratch, struct expand_data *data, struct batch_options *opt) { - strbuf_addf(scratch, "%s %s %"PRIuMAX"%c", oid_to_hex(&data->oid), - type_name(data->type), - (uintmax_t)data->size, opt->output_delim); + strbuf_add_oid_hex(scratch, &data->oid); + strbuf_addch(scratch, ' '); + strbuf_addstr(scratch, type_name(data->type)); + strbuf_addch(scratch, ' '); + strbuf_add_uint(scratch, data->size); + strbuf_addch(scratch, opt->output_delim); } static void report_object_status(struct batch_options *opt, @@ -910,8 +913,8 @@ static void batch_each_object(struct batch_options *opt, for (source = the_repository->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - int ret = packfile_store_for_each_object(files->packed, &oi, - batch_one_object_oi, &payload, &opts); + int ret = odb_source_for_each_object(&files->packed->base, &oi, + batch_one_object_oi, &payload, &opts); if (ret) break; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 98f64d5b922e6c..217d83ea7d5de0 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -116,7 +116,7 @@ int cmd_check_attr(int argc, struct object_id initialized_oid; int cnt, i, doubledash, filei; - if (!is_bare_repository()) + if (!is_bare_repository(the_repository)) setup_work_tree(the_repository); repo_config(the_repository, git_default_config, NULL); diff --git a/builtin/checkout.c b/builtin/checkout.c index b78b3a1d16def4..aee84ca89742b0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -952,10 +952,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { - enum log_refs_config log_all_ref_updates = - repo_settings_get_log_all_ref_updates(the_repository); + enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; + const char *value; char *refname; + if (!repo_config_get_string_tmp(the_repository, "core.logallrefupdates", &value)) + log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); if (opts->new_branch_log && !should_autocreate_reflog(log_all_ref_updates, refname)) { diff --git a/builtin/fetch.c b/builtin/fetch.c index c1d7c672f4e0d8..8e676b79ba1a91 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP; struct fetch_config { enum display_format display_format; + enum follow_remote_head_settings follow_remote_head; int all; int prune; int prune_tags; @@ -171,6 +172,23 @@ static int git_fetch_config(const char *k, const char *v, else die(_("invalid value for '%s': '%s'"), "fetch.output", v); + return 0; + } + + if (!strcmp(k, "fetch.followremotehead")) { + if (!v) + return config_error_nonbool(k); + else if (!strcmp(v, "never")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER; + else if (!strcmp(v, "create")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE; + else if (!strcmp(v, "warn")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN; + else if (!strcmp(v, "always")) + fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS; + else + warning(_("unrecognized fetch.followRemoteHEAD value '%s' ignored"), v); + return 0; } return git_default_config(k, v, ctx, cb); @@ -1697,17 +1715,19 @@ static const char *strip_refshead(const char *name){ static void set_head_advice_msg(const char *remote, const char *head_name) { const char message_advice_set_head[] = - N_("Run 'git remote set-head %s %s' to follow the change, or set\n" - "'remote.%s.followRemoteHEAD' configuration option to a different value\n" - "if you do not want to see this message. Specifically running\n" - "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n" - "will disable the warning until the remote changes HEAD to something else."); + N_("Run 'git remote set-head %s %s' to follow the change, or modify\n" + "either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n" + "configuration variables to handle the situation differently.\n\n" + + "Using this specific setting\n\n" + " git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n" + "will suppress the warning until the remote changes HEAD to something else."); advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head), remote, head_name, remote, remote, head_name); } -static void report_set_head(const char *remote, const char *head_name, +static void warn_set_head(const char *remote, const char *head_name, struct strbuf *buf_prev, int updateres) { struct strbuf buf_prefix = STRBUF_INIT; const char *prev_head = NULL; @@ -1729,12 +1749,12 @@ static void report_set_head(const char *remote, const char *head_name, strbuf_release(&buf_prefix); } -static int set_head(const struct ref *remote_refs, struct remote *remote) +static int set_head(const struct ref *remote_refs, struct remote *remote, + int follow_remote_head) { int result = 0, create_only, baremirror, was_detached; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT; - int follow_remote_head = remote->follow_remote_head; const char *no_warn_branch = remote->no_warn_branch; char *head_name = NULL; struct ref *ref, *matches; @@ -1764,7 +1784,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) if (!head_name) goto cleanup; - baremirror = is_bare_repository() && remote->mirror; + baremirror = is_bare_repository(the_repository) && remote->mirror; create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !baremirror; if (baremirror) { strbuf_addstr(&b_head, "HEAD"); @@ -1773,7 +1793,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote->name); strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote->name, head_name); } - /* make sure it's valid */ + /* make sure it's valid */ if (!baremirror && !refs_ref_exists(refs, b_remote_head.buf)) { result = 1; goto cleanup; @@ -1787,7 +1807,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote) if (verbosity >= 0 && follow_remote_head == FOLLOW_REMOTE_WARN && (!no_warn_branch || strcmp(no_warn_branch, head_name))) - report_set_head(remote->name, head_name, &b_local_head, was_detached); + warn_set_head(remote->name, head_name, &b_local_head, was_detached); cleanup: free(head_name); @@ -1901,6 +1921,7 @@ static int do_fetch(struct transport *transport, struct ref_update_display_info_array display_array = { 0 }; struct strmap rejected_refs = STRMAP_INIT; int summary_width = 0; + int follow_remote_head; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1916,6 +1937,22 @@ static int do_fetch(struct transport *transport, goto cleanup; } + /* + * NEEDSWORK: By the time this function executes, we have already parsed + * all such followRemoteHEAD values from the external configuration, + * potentially emitting warning messages for bogus values. Ideally, if + * this fetch ends up not needing to consult these values, then git would + * not ever output a value warning. (eg: when pulling from a URL directly - + * rather than a configured remote, or when a remote's followRemoteHEAD + * overrides the fallback fetch setting) + */ + if (transport->remote->follow_remote_head) + follow_remote_head = transport->remote->follow_remote_head; + else if (config->follow_remote_head) + follow_remote_head = config->follow_remote_head; + else + follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT; + if (rs->nr) { refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); } else { @@ -1924,7 +1961,7 @@ static int do_fetch(struct transport *transport, if (transport->remote->fetch.nr) { refspec_ref_prefixes(&transport->remote->fetch, &transport_ls_refs_options.ref_prefixes); - if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER) + if (follow_remote_head != FOLLOW_REMOTE_NEVER) do_set_head = 1; } if (branch && branch_has_merge_config(branch) && @@ -2131,7 +2168,7 @@ static int do_fetch(struct transport *transport, * Way too many cases where this can go wrong so let's just * ignore errors and fail silently for now. */ - set_head(remote_refs, transport->remote); + set_head(remote_refs, transport->remote, follow_remote_head); } cleanup: @@ -2471,6 +2508,7 @@ int cmd_fetch(int argc, { struct fetch_config config = { .display_format = DISPLAY_FORMAT_FULL, + .follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED, .prune = -1, .prune_tags = -1, .show_forced_updates = 1, diff --git a/builtin/gc.c b/builtin/gc.c index c26c93ee0fe4a3..d32af422af5e58 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -902,7 +902,7 @@ int cmd_gc(int argc, die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire); if (cfg.pack_refs < 0) - cfg.pack_refs = !is_bare_repository(); + cfg.pack_refs = !is_bare_repository(the_repository); argc = parse_options(argc, argv, prefix, builtin_gc_options, builtin_gc_usage, 0); diff --git a/builtin/grep.c b/builtin/grep.c index 26b85479ca0d76..77f0c4dc90c4f5 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -1363,7 +1363,7 @@ int cmd_grep(int argc, odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_prepare(files->packed); + odb_source_packed_prepare(files->packed); } } diff --git a/builtin/history.c b/builtin/history.c index 091465a59e2f96..fd83de8265e817 100644 --- a/builtin/history.c +++ b/builtin/history.c @@ -525,7 +525,7 @@ static int cmd_history_fixup(int argc, if (action == REF_ACTION_DEFAULT) action = REF_ACTION_BRANCHES; - if (is_bare_repository()) { + if (is_bare_repository(repo)) { ret = error(_("cannot run fixup in a bare repository")); goto out; } diff --git a/builtin/init-db.c b/builtin/init-db.c index c55517ad94d447..566732c9f4a8ee 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -81,6 +81,7 @@ int cmd_init_db(int argc, const char *template_dir = NULL; char *template_dir_to_free = NULL; unsigned int flags = 0; + int bare = startup_info->force_bare_repository ? 1 : -1; const char *object_format = NULL; const char *ref_format = NULL; const char *initial_branch = NULL; @@ -90,7 +91,7 @@ int cmd_init_db(int argc, const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), N_("directory from which templates will be used")), - OPT_SET_INT(0, "bare", &is_bare_repository_cfg, + OPT_SET_INT(0, "bare", &bare, N_("create a bare repository"), 1), { .type = OPTION_CALLBACK, @@ -116,7 +117,7 @@ int cmd_init_db(int argc, argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); - if (real_git_dir && is_bare_repository_cfg == 1) + if (real_git_dir && bare == 1) die(_("options '%s' and '%s' cannot be used together"), "--separate-git-dir", "--bare"); if (real_git_dir && !is_absolute_path(real_git_dir)) @@ -160,7 +161,7 @@ int cmd_init_db(int argc, } else if (0 < argc) { usage(init_db_usage[0]); } - if (is_bare_repository_cfg == 1) { + if (bare == 1) { char *cwd = xgetcwd(); setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0); free(cwd); @@ -187,7 +188,7 @@ int cmd_init_db(int argc, */ git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT)); work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT)); - if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) + if ((!git_dir || bare == 1) && work_tree) die(_("%s (or --work-tree=) not allowed without " "specifying %s (or --git-dir=)"), GIT_WORK_TREE_ENVIRONMENT, @@ -224,22 +225,31 @@ int cmd_init_db(int argc, strbuf_release(&sb); } - if (is_bare_repository_cfg < 0) - is_bare_repository_cfg = guess_repository_type(git_dir); + if (bare < 0) + bare = guess_repository_type(git_dir); - if (!is_bare_repository_cfg) { + if (!bare) { const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = real_pathdup(rel, 1); - free(rel); - } - if (!git_work_tree_cfg) - git_work_tree_cfg = xgetcwd(); - if (work_tree) + + if (work_tree) { set_git_work_tree(the_repository, work_tree); - else - set_git_work_tree(the_repository, git_work_tree_cfg); + } else { + char *work_tree_cfg = NULL; + + if (git_dir_parent) { + char *rel = xstrndup(git_dir, git_dir_parent - git_dir); + work_tree_cfg = real_pathdup(rel, 1); + free(rel); + } + + if (!work_tree_cfg) + work_tree_cfg = xgetcwd(); + + set_git_work_tree(the_repository, work_tree_cfg); + + free(work_tree_cfg); + } + if (access(repo_get_work_tree(the_repository), X_OK)) die_errno (_("Cannot access work tree '%s'"), repo_get_work_tree(the_repository)); diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 00ffb36394d08c..6e73c85cde324f 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -10,6 +10,7 @@ #include "trace2.h" #include "odb.h" #include "odb/source.h" +#include "odb/source-files.h" #include "replace-object.h" #include "repository.h" @@ -85,12 +86,12 @@ static int parse_object_dir(const struct option *opt, const char *arg, return 0; } -static struct odb_source *handle_object_dir_option(struct repository *repo) +static struct odb_source_files *handle_object_dir_option(struct repository *repo) { struct odb_source *source = odb_find_source(repo->objects, opts.object_dir); if (!source) source = odb_add_to_alternates_memory(repo->objects, opts.object_dir); - return source; + return odb_source_files_downcast(source); } static struct option common_opts[] = { @@ -167,7 +168,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; int ret; opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; @@ -211,7 +212,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, read_packs_from_stdin(&packs); - ret = write_midx_file_only(source, &packs, + ret = write_midx_file_only(source->packed, &packs, opts.preferred_pack, opts.refs_snapshot, opts.incremental_base, opts.flags); @@ -223,7 +224,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, } - ret = write_midx_file(source, opts.preferred_pack, + ret = write_midx_file(source->packed, opts.preferred_pack, opts.refs_snapshot, opts.flags); free(opts.refs_snapshot); @@ -237,7 +238,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, struct multi_pack_index *m, *cur; struct multi_pack_index *from_midx = NULL; struct multi_pack_index *to_midx = NULL; - struct odb_source *source; + struct odb_source_files *source; int ret; struct option *options; @@ -282,7 +283,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, FREE_AND_NULL(options); - m = get_multi_pack_index(source); + m = get_multi_pack_index(source->packed); for (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) { const char *midx_csum = midx_get_checksum_hex(cur); @@ -305,7 +306,7 @@ static int cmd_multi_pack_index_compact(int argc, const char **argv, die(_("MIDX %s must be an ancestor of %s"), argv[0], argv[1]); } - ret = write_midx_file_compact(source, from_midx, to_midx, + ret = write_midx_file_compact(source->packed, from_midx, to_midx, opts.incremental_base, opts.flags); return ret; @@ -319,7 +320,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, static struct option builtin_multi_pack_index_verify_options[] = { OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_verify_options); @@ -337,7 +338,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, FREE_AND_NULL(options); - return verify_midx_file(source, opts.flags); + return verify_midx_file(source->packed, opts.flags); } static int cmd_multi_pack_index_expire(int argc, const char **argv, @@ -348,7 +349,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, static struct option builtin_multi_pack_index_expire_options[] = { OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_expire_options); @@ -366,7 +367,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, FREE_AND_NULL(options); - return expire_midx_packs(source, opts.flags); + return expire_midx_packs(source->packed, opts.flags); } static int cmd_multi_pack_index_repack(int argc, const char **argv, @@ -379,7 +380,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_END(), }; - struct odb_source *source; + struct odb_source_files *source; options = add_common_options(builtin_multi_pack_index_repack_options); @@ -398,7 +399,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, FREE_AND_NULL(options); - return midx_repack(source, (size_t)opts.batch_size, opts.flags); + return midx_repack(source->packed, (size_t)opts.batch_size, opts.flags); } int cmd_multi_pack_index(int argc, diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 27048bbb4dd3c9..e3760b34925bbf 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1781,7 +1781,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid, odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(source); + struct multi_pack_index *m = get_multi_pack_index(files->packed); struct pack_entry e; if (m && fill_midx_entry(m, oid, &e)) { @@ -2743,6 +2744,22 @@ static inline void oe_set_tree_depth(struct packing_data *pack, pack->tree_depth[e - pack->objects] = tree_depth; } +static void record_tree_depth(const struct object_id *oid, const char *name) +{ + const char *p; + unsigned depth; + struct object_entry *ent; + + /* the empty string is a root tree, which is depth 0 */ + depth = *name ? 1 : 0; + for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) + depth++; + + ent = packlist_find(&to_pack, oid); + if (ent && depth > oe_tree_depth(&to_pack, ent)) + oe_set_tree_depth(&to_pack, ent, depth); +} + /* * Return the size of the object without doing any delta * reconstruction (so non-deltas are true object sizes, but deltas @@ -4405,20 +4422,8 @@ static void show_object(struct object *obj, const char *name, add_preferred_base_object(name); add_object_entry(&obj->oid, obj->type, name, 0); - if (use_delta_islands) { - const char *p; - unsigned depth; - struct object_entry *ent; - - /* the empty string is a root tree, which is depth 0 */ - depth = *name ? 1 : 0; - for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) - depth++; - - ent = packlist_find(&to_pack, &obj->oid); - if (ent && depth > oe_tree_depth(&to_pack, ent)) - oe_set_tree_depth(&to_pack, ent, depth); - } + if (use_delta_islands) + record_tree_depth(&obj->oid, name); } static void show_object__ma_allow_any(struct object *obj, const char *name, void *data) @@ -4519,8 +4524,8 @@ static void add_objects_in_unpacked_packs(void) if (!source->local) continue; - if (packfile_store_for_each_object(files->packed, &oi, - add_object_in_unpacked_pack, NULL, &opts)) + if (odb_source_for_each_object(&files->packed->base, &oi, + add_object_in_unpacked_pack, NULL, &opts)) die(_("cannot open pack index")); } } @@ -4762,6 +4767,31 @@ static int add_objects_by_path(const char *path, continue; add_object_entry(oid, type, path, exclude); + + if (type == OBJ_COMMIT) { + struct commit *commit; + + if (!write_bitmap_index && !use_delta_islands) + continue; + + commit = lookup_commit(the_repository, oid); + if (!commit) + die(_("could not find commit %s"), oid_to_hex(oid)); + if (write_bitmap_index) + index_commit_for_bitmap(commit); + /* + * Skip island propagation for boundary commits. + * The regular traversal's show_commit() is only + * called for interesting commits; matching that + * here keeps path-walk from doing extra work that + * would only be a no-op anyway (boundary commits + * are not in island_marks). + */ + if (use_delta_islands && !exclude) + propagate_island_marks(the_repository, commit); + } else if (type == OBJ_TREE && use_delta_islands) { + record_tree_depth(oid, path); + } } oe_end = to_pack.nr_objects; @@ -4794,6 +4824,13 @@ static int get_object_list_path_walk(struct rev_info *revs) info.path_fn = add_objects_by_path; info.path_fn_data = &processed; + /* + * Path-walk needs boundary commits to discover thin-pack bases, but + * bitmap traversal does not understand the boundary state. Set it + * here so any prior bitmap attempt sees the usual non-boundary walk. + */ + revs->boundary = 1; + /* * Allow the --[no-]sparse option to be interesting here, if only * for testing purposes. Paths with no interesting objects will not @@ -5217,8 +5254,6 @@ int cmd_pack_objects(int argc, const char *option = NULL; if (!path_walk_filter_compatible(&filter_options)) option = "--filter"; - else if (use_delta_islands) - option = "--delta-islands"; if (option) { warning(_("cannot use %s with %s"), @@ -5227,9 +5262,7 @@ int cmd_pack_objects(int argc, } } if (path_walk) { - strvec_push(&rp, "--boundary"); strvec_push(&rp, "--objects"); - use_bitmap_index = 0; } else if (thin) { use_internal_rev_list = 1; strvec_push(&rp, shallow diff --git a/builtin/repack.c b/builtin/repack.c index 1524a9c13ad5b8..db504d673fcf52 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" @@ -265,7 +264,7 @@ int cmd_repack(int argc, if (write_bitmaps < 0) { if (write_midx == REPACK_WRITE_MIDX_NONE && - (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository())) + (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository(repo))) write_bitmaps = 0; } if (po_args.pack_kept_objects < 0) @@ -458,6 +457,8 @@ int cmd_repack(int argc, } if (!names.nr) { + struct odb_source_files *files = odb_source_files_downcast(existing.source); + if (!po_args.quiet) printf_ln(_("Nothing new to pack.")); /* @@ -473,7 +474,7 @@ int cmd_repack(int argc, * midx_has_unknown_packs() will make the decision for * us. */ - if (!get_multi_pack_index(existing.source)) + if (!get_multi_pack_index(files->packed)) midx_must_contain_cruft = 1; } @@ -626,10 +627,12 @@ int cmd_repack(int argc, update_server_info(repo, 0); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) { + struct odb_source_files *files = odb_source_files_downcast(existing.source); unsigned flags = 0; + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0)) flags |= MIDX_WRITE_INCREMENTAL; - write_midx_file(existing.source, NULL, NULL, flags); + write_midx_file(files->packed, NULL, NULL, flags); } cleanup: diff --git a/builtin/repo.c b/builtin/repo.c index 69f3626467cadf..042d6de558e930 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -7,12 +7,14 @@ #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h" #include "ref-filter.h" #include "refs.h" #include "revision.h" +#include "setup.h" #include "strbuf.h" #include "string-list.h" #include "shallow.h" @@ -58,7 +60,7 @@ struct repo_info_field { static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) { - strbuf_addstr(buf, is_bare_repository() ? "true" : "false"); + strbuf_addstr(buf, is_bare_repository(the_repository) ? "true" : "false"); return 0; } @@ -75,6 +77,50 @@ static int get_object_format(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *common_dir = repo_get_common_dir(repo); + + if (!common_dir) + return error(_("unable to get common directory")); + + format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf) +{ + const char *common_dir = repo_get_common_dir(repo); + + if (!common_dir) + return error(_("unable to get common directory")); + + format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + +static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *git_dir = repo_get_git_dir(repo); + + if (!git_dir) + return error(_("unable to get git directory")); + + format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) +{ + const char *git_dir = repo_get_git_dir(repo); + + if (!git_dir) + return error(_("unable to get git directory")); + + format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + static int get_references_format(struct repository *repo, struct strbuf *buf) { strbuf_addstr(buf, @@ -87,6 +133,10 @@ static const struct repo_info_field repo_info_field[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.commondir.absolute", get_path_commondir_absolute }, + { "path.commondir.relative", get_path_commondir_relative }, + { "path.gitdir.absolute", get_path_gitdir_absolute }, + { "path.gitdir.relative", get_path_gitdir_relative }, { "references.format", get_references_format }, }; diff --git a/builtin/reset.c b/builtin/reset.c index 3be6bd0121afe5..78e69bd84ba2c3 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -470,7 +470,7 @@ int cmd_reset(int argc, if (reset_type != SOFT && (reset_type != MIXED || repo_get_work_tree(the_repository))) setup_work_tree(the_repository); - if (reset_type == MIXED && is_bare_repository()) + if (reset_type == MIXED && is_bare_repository(the_repository)) die(_("%s reset is not allowed in a bare repository"), _(reset_type_names[reset_type])); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index bb882678fe2a9e..5e04b0e2bd6416 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -653,53 +653,46 @@ enum default_type { DEFAULT_UNMODIFIED, }; -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def) +static void print_path(const char *path, const char *prefix, + enum format_type format, enum default_type def) { - char *cwd = NULL; - /* - * We don't ever produce a relative path if prefix is NULL, so set the - * prefix to the current directory so that we can produce a relative - * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then - * we want an absolute path unless the two share a common prefix, so don't - * set it in that case, since doing so causes a relative path to always - * be produced if possible. - */ - if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED)) - prefix = cwd = xgetcwd(); - if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) { - puts(path); - } else if (format == FORMAT_RELATIVE || - (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) { - /* - * In order for relative_path to work as expected, we need to - * make sure that both paths are absolute paths. If we don't, - * we can end up with an unexpected absolute path that the user - * didn't want. - */ - struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT; - if (!is_absolute_path(path)) { - strbuf_realpath_forgiving(&realbuf, path, 1); - path = realbuf.buf; - } - if (!is_absolute_path(prefix)) { - strbuf_realpath_forgiving(&prefixbuf, prefix, 1); - prefix = prefixbuf.buf; + struct strbuf sb = STRBUF_INIT; + enum path_format fmt; + + if (format == FORMAT_DEFAULT) { + switch (def) { + case DEFAULT_RELATIVE: + fmt = PATH_FORMAT_RELATIVE; + break; + case DEFAULT_RELATIVE_IF_SHARED: + fmt = PATH_FORMAT_RELATIVE_IF_SHARED; + break; + case DEFAULT_CANONICAL: + fmt = PATH_FORMAT_CANONICAL; + break; + case DEFAULT_UNMODIFIED: + default: + fmt = PATH_FORMAT_UNMODIFIED; + break; } - puts(relative_path(path, prefix, &buf)); - strbuf_release(&buf); - strbuf_release(&realbuf); - strbuf_release(&prefixbuf); - } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) { - struct strbuf buf = STRBUF_INIT; - puts(relative_path(path, prefix, &buf)); - strbuf_release(&buf); } else { - struct strbuf buf = STRBUF_INIT; - strbuf_realpath_forgiving(&buf, path, 1); - puts(buf.buf); - strbuf_release(&buf); + switch (format) { + case FORMAT_RELATIVE: + fmt = PATH_FORMAT_RELATIVE; + break; + case FORMAT_CANONICAL: + fmt = PATH_FORMAT_CANONICAL; + break; + default: + fmt = PATH_FORMAT_UNMODIFIED; + break; + } } - free(cwd); + + format_path(&sb, path, prefix, fmt); + puts(sb.buf); + + strbuf_release(&sb); } int cmd_rev_parse(int argc, @@ -1084,7 +1077,7 @@ int cmd_rev_parse(int argc, continue; } if (!strcmp(arg, "--is-bare-repository")) { - printf("%s\n", is_bare_repository() ? "true" + printf("%s\n", is_bare_repository(the_repository) ? "true" : "false"); continue; } diff --git a/cache-tree.c b/cache-tree.c index 184f7e2635b9f4..9204eb3ac7a120 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -162,6 +162,54 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path) istate->cache_changed |= CACHE_TREE_CHANGED; } +/* + * Check whether this_ce and the next entry in the index form a D/F + * conflict ("path" vs "path/file"). Returns the conflicting "path/..." + * name when one is found, or NULL otherwise. + * + * The cache is sorted, so "path/file" sorts after "path" and the + * conflict is usually visible as adjacent entries. But other entries + * can sort between them -- e.g. "path-internal" sits between "path" + * and "path/file" because '-' (0x2D) precedes '/' (0x2F) -- so when + * the immediately following entry shares our prefix but starts with a + * character that sorts before '/', binary search for "path/" instead. + */ +static const char *find_df_conflict(struct index_state *istate, + const struct cache_entry *this_ce, + const struct cache_entry *next_ce) +{ + const char *this_name = this_ce->name; + const char *next_name = next_ce->name; + int this_len = ce_namelen(this_ce); + const struct cache_entry *other; + struct strbuf probe = STRBUF_INIT; + int pos; + + if (this_len >= ce_namelen(next_ce) || + next_name[this_len] > '/' || + strncmp(this_name, next_name, this_len)) + return NULL; + + if (next_name[this_len] == '/') + return next_name; + + strbuf_add(&probe, this_name, this_len); + strbuf_addch(&probe, '/'); + pos = index_name_pos_sparse(istate, probe.buf, probe.len); + strbuf_release(&probe); + + if (pos < 0) + pos = -pos - 1; + if (pos >= (int)istate->cache_nr) + return NULL; + other = istate->cache[pos]; + if (ce_namelen(other) > this_len && + other->name[this_len] == '/' && + !strncmp(this_name, other->name, this_len)) + return other->name; + return NULL; +} + static int verify_cache(struct index_state *istate, int flags) { unsigned i, funny; @@ -191,24 +239,18 @@ static int verify_cache(struct index_state *istate, int flags) */ funny = 0; for (i = 0; i + 1 < istate->cache_nr; i++) { - /* path/file always comes after path because of the way - * the cache is sorted. Also path can appear only once, - * which means conflicting one would immediately follow. - */ const struct cache_entry *this_ce = istate->cache[i]; const struct cache_entry *next_ce = istate->cache[i + 1]; - const char *this_name = this_ce->name; - const char *next_name = next_ce->name; - int this_len = ce_namelen(this_ce); - if (this_len < ce_namelen(next_ce) && - next_name[this_len] == '/' && - strncmp(this_name, next_name, this_len) == 0) { + const char *conflict_name; + + conflict_name = find_df_conflict(istate, this_ce, next_ce); + if (conflict_name) { if (10 < ++funny) { fprintf(stderr, "...\n"); break; } fprintf(stderr, "You have both %s and %s\n", - this_name, next_name); + this_ce->name, conflict_name); } } if (funny) diff --git a/chdir-notify.c b/chdir-notify.c index f8bfe3cbef9aba..1237a45e2e6492 100644 --- a/chdir-notify.c +++ b/chdir-notify.c @@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb, } } -static void reparent_cb(const char *name, - const char *old_cwd, - const char *new_cwd, - void *data) -{ - char **path = data; - char *tmp = *path; - - if (!tmp) - return; - - *path = reparent_relative_path(old_cwd, new_cwd, tmp); - free(tmp); - - if (name) { - trace_printf_key(&trace_setup_key, - "setup: reparent %s to '%s'", - name, *path); - } -} - -void chdir_notify_reparent(const char *name, char **path) -{ - chdir_notify_register(name, reparent_cb, path); -} - int chdir_notify(const char *new_cwd) { struct strbuf old_cwd = STRBUF_INIT; diff --git a/chdir-notify.h b/chdir-notify.h index 81eb69d846e45d..36b4114472e31d 100644 --- a/chdir-notify.h +++ b/chdir-notify.h @@ -19,10 +19,7 @@ * chdir_notify_register("description", foo, data); * * In practice most callers will want to move a relative path to the new root; - * they can use the reparent_relative_path() helper for that. If that's all - * you're doing, you can also use the convenience function: - * - * chdir_notify_reparent("description", &my_path); + * they can use the reparent_relative_path() helper for that. * * Whenever a chdir event occurs, that will update my_path (if it's relative) * to adjust for the new cwd by freeing any existing string and allocating a @@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name, void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); void chdir_notify_unregister(const char *name, chdir_notify_callback cb, void *data); -void chdir_notify_reparent(const char *name, char **path); /* * diff --git a/commit-graph.c b/commit-graph.c index 801471a09802d9..c6d9c5c740e94d 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -2016,8 +2016,8 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx) odb_prepare_alternates(ctx->r->objects); for (source = ctx->r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_for_each_object(files->packed, &oi, add_packed_commits_oi, - ctx, &opts); + odb_source_for_each_object(&files->packed->base, &oi, add_packed_commits_oi, + ctx, &opts); } if (ctx->progress_done < ctx->approx_nr_objects) diff --git a/compat/mingw.c b/compat/mingw.c index 41e055f7de885e..5c049c1ca92f04 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3405,7 +3405,7 @@ int is_valid_win32_path(const char *path, int allow_literal_nul) const char *p = path; int preceding_space_or_period = 0, i = 0, periods = 0; - if (!protect_ntfs) + if (!repo_protect_ntfs(the_repository)) return 1; skip_dos_drive_prefix((char **)&path); diff --git a/connected.c b/connected.c index 7e269768327238..929b9bd28d6fab 100644 --- a/connected.c +++ b/connected.c @@ -11,6 +11,62 @@ #include "packfile.h" #include "promisor-remote.h" +static int promised_object_cb(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *payload) +{ + bool *found = payload; + *found = true; + return 1; +} + +/* + * For partial clones, we don't want to have to do a regular connectivity check + * because we have to enumerate and exclude all promisor objects (slow), and + * then the connectivity check itself becomes a no-op because in a partial + * clone every object is a promisor object. Instead, just make sure we + * received, in a promisor packfile, the objects pointed to by each wanted ref. + * + * Before checking for promisor packs, be sure we have the latest pack-files + * loaded into memory. + * + * Returns 1 when all object IDs have been found in promisor packs, in which + * case we're fully connected and thus done. Returns 0 when we have found + * objects in non-promisor packs, in which case we'll have to fall back to the + * rev-list-based connectivity checks. Returns a negative error code on error. + */ +static int check_connected_promisor(oid_iterate_fn fn, + void *cb_data, + const struct object_id **oid) +{ + struct odb_for_each_object_options opts = { + .flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY, + .prefix_hex_len = the_repository->hash_algo->hexsz, + }; + int err; + + odb_reprepare(the_repository->objects); + do { + bool found = false; + + opts.prefix = *oid; + + err = odb_for_each_object_ext(the_repository->objects, NULL, + promised_object_cb, &found, &opts); + if (err < 0) + return err; + + /* + * We have found an object that is not part of a promisor pack, + * and thus we cannot skip the full connectivity check. + */ + if (!found) + return 0; + } while ((*oid = fn(cb_data)) != NULL); + + return 1; +} + /* * If we feed all the commits we want to verify to this command * @@ -46,42 +102,16 @@ int check_connected(oid_iterate_fn fn, void *cb_data, } if (repo_has_promisor_remote(the_repository)) { - /* - * For partial clones, we don't want to have to do a regular - * connectivity check because we have to enumerate and exclude - * all promisor objects (slow), and then the connectivity check - * itself becomes a no-op because in a partial clone every - * object is a promisor object. Instead, just make sure we - * received, in a promisor packfile, the objects pointed to by - * each wanted ref. - * - * Before checking for promisor packs, be sure we have the - * latest pack-files loaded into memory. - */ - odb_reprepare(the_repository->objects); - do { - struct packed_git *p; - - repo_for_each_pack(the_repository, p) { - if (!p->pack_promisor) - continue; - if (find_pack_entry_one(oid, p)) - goto promisor_pack_found; - } - /* - * Fallback to rev-list with oid and the rest of the - * object IDs provided by fn. - */ - goto no_promisor_pack_found; -promisor_pack_found: - ; - } while ((oid = fn(cb_data)) != NULL); - if (opt->err_fd) - close(opt->err_fd); - return 0; + err = check_connected_promisor(fn, cb_data, &oid); + if (err) { + if (opt->err_fd) + close(opt->err_fd); + if (err > 0) + err = 0; + return err; + } } -no_promisor_pack_found: if (opt->shallow_file) { strvec_push(&rev_list.args, "--shallow-file"); strvec_push(&rev_list.args, opt->shallow_file); diff --git a/environment.c b/environment.c index ba2c60103ff51c..8f0c1c4f250193 100644 --- a/environment.c +++ b/environment.c @@ -46,7 +46,6 @@ int has_symlinks = 1; int minimum_abbrev = 4, default_abbrev = -1; int ignore_case; int assume_unchanged; -int is_bare_repository_cfg = -1; /* unspecified */ char *git_commit_encoding; char *git_log_output_encoding; char *apply_default_whitespace; @@ -74,12 +73,10 @@ unsigned long pack_size_limit_cfg; #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 #endif -int protect_hfs = PROTECT_HFS_DEFAULT; #ifndef PROTECT_NTFS_DEFAULT #define PROTECT_NTFS_DEFAULT 1 #endif -int protect_ntfs = PROTECT_NTFS_DEFAULT; /* * The character that begins a commented line in user-editable file @@ -92,9 +89,6 @@ int auto_comment_line_char; bool warn_on_auto_comment_char; #endif /* !WITH_BREAKING_CHANGES */ -/* This is set by setup_git_directory_gently() and/or git_default_config() */ -char *git_work_tree_cfg; - /* * Repository-local GIT_* environment variables; see environment.h for details. */ @@ -128,10 +122,24 @@ const char *getenv_safe(struct strvec *argv, const char *name) return argv->v[argv->nr - 1]; } -int is_bare_repository(void) +int is_bare_repository(struct repository *repo) { /* if core.bare is not 'false', let's see if there is a work tree */ - return is_bare_repository_cfg && !repo_get_work_tree(the_repository); + return repo->bare_cfg && !repo_get_work_tree(repo); +} + +int repo_protect_ntfs(struct repository *repo) +{ + return (repo && repo->initialized) ? + repo_config_values(repo)->protect_ntfs : + PROTECT_NTFS_DEFAULT; +} + +int repo_protect_hfs(struct repository *repo) +{ + return (repo && repo->initialized) ? + repo_config_values(repo)->protect_hfs : + PROTECT_HFS_DEFAULT; } int have_git_dir(void) @@ -337,7 +345,7 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.bare")) { - is_bare_repository_cfg = git_config_bool(var, value); + the_repository->bare_cfg = git_config_bool(var, value); return 0; } @@ -533,12 +541,12 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.protecthfs")) { - protect_hfs = git_config_bool(var, value); + cfg->protect_hfs = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.protectntfs")) { - protect_ntfs = git_config_bool(var, value); + cfg->protect_ntfs = git_config_bool(var, value); return 0; } @@ -716,6 +724,8 @@ void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; + cfg->protect_hfs = PROTECT_HFS_DEFAULT; + cfg->protect_ntfs = PROTECT_NTFS_DEFAULT; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; cfg->check_stat = 1; diff --git a/environment.h b/environment.h index 6f182869558395..8aaedcfea36c47 100644 --- a/environment.h +++ b/environment.h @@ -98,6 +98,8 @@ struct repo_config_values { int precomposed_unicode; int core_sparse_checkout_cone; int warn_on_object_refname_ambiguity; + int protect_hfs; + int protect_ntfs; /* section "sparse" config values */ int sparse_expect_files_outside_of_patterns; @@ -133,8 +135,18 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +/* + * Getters for the `protect_hfs` and `protect_ntfs` fields of `struct repo_config_values`. + * They check `repo->initialized` to prevent calling `repo_config_values()` + * before the repository setup is fully complete or in non-git environments. + */ +int repo_protect_hfs(struct repository *repo); +int repo_protect_ntfs(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); +int is_bare_repository(struct repository *repo); + /* * TODO: All the below state either explicitly or implicitly relies on * `the_repository`. We should eventually get rid of these and make the @@ -157,10 +169,6 @@ void repo_config_values_init(struct repo_config_values *cfg); */ int have_git_dir(void); -extern int is_bare_repository_cfg; -int is_bare_repository(void); -extern char *git_work_tree_cfg; - /* Environment bits from configuration mechanism */ extern int trust_executable_bit; extern int has_symlinks; @@ -171,9 +179,6 @@ extern char *apply_default_whitespace; extern char *apply_default_ignorewhitespace; extern unsigned long pack_size_limit_cfg; -extern int protect_hfs; -extern int protect_ntfs; - enum rebase_setup_type { AUTOREBASE_NEVER = 0, AUTOREBASE_LOCAL, diff --git a/git.c b/git.c index 36f08891ef5476..387eabe38c1951 100644 --- a/git.c +++ b/git.c @@ -255,7 +255,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { char *cwd = xgetcwd(); - is_bare_repository_cfg = 1; + startup_info->force_bare_repository = true; setenv(GIT_DIR_ENVIRONMENT, cwd, 0); free(cwd); setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); diff --git a/log-tree.c b/log-tree.c index 88b3019293b725..83a3c4bf9b16b9 100644 --- a/log-tree.c +++ b/log-tree.c @@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "commit-reach.h" +#include "commit-slab.h" #include "config.h" #include "diff.h" #include "diffcore.h" @@ -1089,6 +1090,96 @@ static int do_remerge_diff(struct rev_info *opt, return !opt->loginfo; } +/* Per-commit path storage for --follow across merges */ +define_commit_slab(follow_pathspec_slab, char *); + +static const char *pathspec_single_path(const struct pathspec *ps) +{ + if (ps->nr != 1) + return NULL; + return ps->items[0].match; +} + +static void set_pathspec_to_single_path(struct pathspec *ps, const char *path) +{ + const char *paths[2] = { path, NULL }; + + clear_pathspec(ps); + parse_pathspec(ps, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, + PATHSPEC_LITERAL_PATH, "", paths); +} + +static void remember_follow_pathspec(struct rev_info *opt, + struct commit *c, const char *path) +{ + char **slot; + + if (!path) + return; + if (!opt->follow_pathspec_slab) { + opt->follow_pathspec_slab = xmalloc(sizeof(*opt->follow_pathspec_slab)); + init_follow_pathspec_slab(opt->follow_pathspec_slab); + } + slot = follow_pathspec_slab_at(opt->follow_pathspec_slab, c); + if (*slot && !strcmp(*slot, path)) + return; + free(*slot); + *slot = xstrdup(path); +} + +static const char *recall_follow_pathspec(struct rev_info *opt, + struct commit *c) +{ + char **slot; + + if (!opt->follow_pathspec_slab) + return NULL; + slot = follow_pathspec_slab_peek(opt->follow_pathspec_slab, c); + return slot ? *slot : NULL; +} + +static void free_follow_pathspec_slot(char **slot) +{ + FREE_AND_NULL(*slot); +} + +void release_follow_pathspec_slab(struct rev_info *opt) +{ + if (!opt->follow_pathspec_slab) + return; + deep_clear_follow_pathspec_slab(opt->follow_pathspec_slab, + free_follow_pathspec_slot); + FREE_AND_NULL(opt->follow_pathspec_slab); +} + +/* Compute a path to follow in parent, if there is one */ +static void propagate_follow_pathspec_to_parent(struct rev_info *opt, + struct commit *commit, + struct commit *parent) +{ + struct diff_options diff_opts; + const char *path; + + parse_commit_or_die(parent); + repo_diff_setup(opt->diffopt.repo, &diff_opts); + copy_pathspec(&diff_opts.pathspec, &opt->diffopt.pathspec); + diff_opts.flags.recursive = 1; + diff_opts.flags.follow_renames = 1; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&diff_opts); + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(commit), + "", &diff_opts); + + path = pathspec_single_path(&diff_opts.pathspec); + if (path) + remember_follow_pathspec(opt, parent, path); + + diff_queue_clear(&diff_queued_diff); + diff_free(&diff_opts); +} + /* * Show the diff of a commit. * @@ -1185,6 +1276,16 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) opt->loginfo = &log; opt->diffopt.no_free = 1; + /* Any recorded path for this commit? If so, restore it */ + if (opt->diffopt.flags.follow_renames) { + const char *stored = recall_follow_pathspec(opt, commit); + if (stored) { + const char *current = pathspec_single_path(&opt->diffopt.pathspec); + if (!current || strcmp(current, stored)) + set_pathspec_to_single_path(&opt->diffopt.pathspec, stored); + } + } + if (opt->track_linear && !opt->linear && !opt->reverse_output_stage) fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); shown = log_tree_diff(opt, commit, &log); @@ -1197,6 +1298,21 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); if (shown) show_diff_of_diff(opt); + + /* Record what path each parent of this commit should use */ + if (opt->diffopt.flags.follow_renames) { + struct commit_list *parents = get_saved_parents(opt, commit); + if (parents && parents->next) { + struct commit_list *p; + for (p = parents; p; p = p->next) + propagate_follow_pathspec_to_parent(opt, commit, + p->item); + } else if (parents) { + remember_follow_pathspec(opt, parents->item, + pathspec_single_path(&opt->diffopt.pathspec)); + } + } + opt->loginfo = NULL; maybe_flush_or_die(opt->diffopt.file, "stdout"); opt->diffopt.no_free = no_free; diff --git a/log-tree.h b/log-tree.h index 07924be8bcea5e..e8679b6c4aa3a3 100644 --- a/log-tree.h +++ b/log-tree.h @@ -26,6 +26,7 @@ struct decoration_options { int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); +void release_follow_pathspec_slab(struct rev_info *); void show_log(struct rev_info *opt); void format_decorations(struct strbuf *sb, const struct commit *commit, enum git_colorbool use_color, const struct decoration_options *opts); diff --git a/mailmap.c b/mailmap.c index 72b639e6021cf8..1386ab208cc57c 100644 --- a/mailmap.c +++ b/mailmap.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "environment.h" #include "string-list.h" @@ -219,10 +217,10 @@ int read_mailmap(struct repository *repo, struct string_list *map) map->strdup_strings = 1; map->cmp = namemap_cmp; - if (!mailmap_blob && is_bare_repository()) + if (!mailmap_blob && is_bare_repository(repo)) mailmap_blob = xstrdup("HEAD:.mailmap"); - if (!startup_info->have_repository || !is_bare_repository()) + if (!startup_info->have_repository || !is_bare_repository(repo)) err |= read_mailmap_file(map, ".mailmap", startup_info->have_repository ? MAILMAP_NOFOLLOW : 0); diff --git a/merge-ort.c b/merge-ort.c index 4f6273bd515789..c410a5d353234c 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -728,6 +728,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, strintmap_clear_func(&renames->deferred[i].possible_trivial_merges); strset_clear_func(&renames->deferred[i].target_dirs); renames->deferred[i].trivial_merges_okay = 1; /* 1 == maybe */ + free(renames->pairs[i].queue); + diff_queue_init(&renames->pairs[i]); } renames->cached_pairs_valid_side = 0; renames->dir_rename_mask = 0; @@ -1008,32 +1010,34 @@ static int traverse_trees_wrapper(struct index_state *istate, info->traverse_path = renames->callback_data_traverse_path; info->fn = old_fn; for (i = old_offset; i < renames->callback_data_nr; ++i) { - info->fn(n, - renames->callback_data[i].mask, - renames->callback_data[i].dirmask, - renames->callback_data[i].names, - info); + ret = info->fn(n, + renames->callback_data[i].mask, + renames->callback_data[i].dirmask, + renames->callback_data[i].names, + info); + if (ret < 0) + break; } renames->callback_data_nr = old_offset; free(renames->callback_data_traverse_path); renames->callback_data_traverse_path = old_callback_data_traverse_path; info->traverse_path = NULL; - return 0; + return ret < 0 ? ret : 0; } -static void setup_path_info(struct merge_options *opt, - struct string_list_item *result, - const char *current_dir_name, - int current_dir_name_len, - char *fullpath, /* we'll take over ownership */ - struct name_entry *names, - struct name_entry *merged_version, - unsigned is_null, /* boolean */ - unsigned df_conflict, /* boolean */ - unsigned filemask, - unsigned dirmask, - int resolved /* boolean */) +static int setup_path_info(struct merge_options *opt, + struct string_list_item *result, + const char *current_dir_name, + int current_dir_name_len, + char *fullpath, /* we'll take over ownership */ + struct name_entry *names, + struct name_entry *merged_version, + unsigned is_null, /* boolean */ + unsigned df_conflict, /* boolean */ + unsigned filemask, + unsigned dirmask, + int resolved /* boolean */) { /* result->util is void*, so mi is a convenience typed variable */ struct merged_info *mi; @@ -1077,9 +1081,11 @@ static void setup_path_info(struct merge_options *opt, */ mi->is_null = 1; } - strmap_put(&opt->priv->paths, fullpath, mi); + if (strmap_put(&opt->priv->paths, fullpath, mi)) + return error(_("tree has duplicate entries for '%s'"), fullpath); result->string = fullpath; result->util = mi; + return 0; } static void add_pair(struct merge_options *opt, @@ -1346,9 +1352,10 @@ static int collect_merge_info_callback(int n, */ if (side1_matches_mbase && side2_matches_mbase) { /* mbase, side1, & side2 all match; use mbase as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+0, mbase_null, 0 /* df_conflict */, - filemask, dirmask, 1 /* resolved */); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+0, mbase_null, 0 /* df_conflict */, + filemask, dirmask, 1 /* resolved */)) + return -1; /* Quit traversing */ return mask; } @@ -1360,9 +1367,10 @@ static int collect_merge_info_callback(int n, */ if (sides_match && filemask == 0x07) { /* use side1 (== side2) version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+1, side1_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+1, side1_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } @@ -1374,18 +1382,20 @@ static int collect_merge_info_callback(int n, */ if (side1_matches_mbase && filemask == 0x07) { /* use side2 version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+2, side2_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+2, side2_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } /* Similar to above but swapping sides 1 and 2 */ if (side2_matches_mbase && filemask == 0x07) { /* use side1 version as resolution */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, names+1, side1_null, 0, - filemask, dirmask, 1); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, names+1, side1_null, 0, + filemask, dirmask, 1)) + return -1; /* Quit traversing */ return mask; } @@ -1409,8 +1419,9 @@ static int collect_merge_info_callback(int n, * unconflict some more cases, but that comes later so all we can * do now is record the different non-null file hashes.) */ - setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, - names, NULL, 0, df_conflict, filemask, dirmask, 0); + if (setup_path_info(opt, &pi, dirname, info->pathlen, fullpath, + names, NULL, 0, df_conflict, filemask, dirmask, 0)) + return -1; /* Quit traversing */ ci = pi.util; VERIFY_CI(ci); @@ -1738,7 +1749,6 @@ static int collect_merge_info(struct merge_options *opt, setup_traverse_info(&info, opt->priv->toplevel_dir); info.fn = collect_merge_info_callback; info.data = opt; - info.show_all_errors = 1; if (repo_parse_tree(opt->repo, merge_base) < 0 || repo_parse_tree(opt->repo, side1) < 0 || diff --git a/meson.build b/meson.build index 3247697f74aae1..ca235801cf4848 100644 --- a/meson.build +++ b/meson.build @@ -406,6 +406,7 @@ libgit_sources = [ 'odb/source-files.c', 'odb/source-inmemory.c', 'odb/source-loose.c', + 'odb/source-packed.c', 'odb/streaming.c', 'odb/transaction.c', 'oid-array.c', @@ -421,6 +422,7 @@ libgit_sources = [ 'pack-revindex.c', 'pack-write.c', 'packfile.c', + 'packfile-list.c', 'pager.c', 'parallel-checkout.c', 'parse.c', diff --git a/midx-write.c b/midx-write.c index 19e1cd10b7dee4..8c1837f6df4671 100644 --- a/midx-write.c +++ b/midx-write.c @@ -25,9 +25,9 @@ #define NO_PREFERRED_PACK (~((uint32_t)0)) extern int midx_checksum_valid(struct multi_pack_index *m); -extern void clear_midx_files_ext(struct odb_source *source, const char *ext, +extern void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash); -extern void clear_incremental_midx_files_ext(struct odb_source *source, +extern void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes); extern int cmp_idx_or_pack_name(const char *idx_or_pack_name, @@ -119,7 +119,7 @@ struct write_midx_context { struct string_list *to_include; struct repository *repo; - struct odb_source *source; + struct odb_source_packed *source; }; static uint32_t midx_pack_perm(struct write_midx_context *ctx, @@ -1107,7 +1107,7 @@ static int link_midx_to_chain(struct multi_pack_index *m) return ret; } -static void clear_midx_files(struct odb_source *source, +static void clear_midx_files(struct odb_source_packed *source, const struct strvec *hashes, unsigned incremental) { /* @@ -1237,7 +1237,7 @@ static int midx_hashcmp(const struct multi_pack_index *a, } struct write_midx_opts { - struct odb_source *source; /* non-optional */ + struct odb_source_packed *source; /* non-optional */ struct string_list *packs_to_include; struct string_list *packs_to_drop; @@ -1253,7 +1253,7 @@ struct write_midx_opts { static int write_midx_internal(struct write_midx_opts *opts) { - struct repository *r = opts->source->odb->repo; + struct repository *r = opts->source->base.odb->repo; struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; uint32_t start_pack; @@ -1301,7 +1301,7 @@ static int write_midx_internal(struct write_midx_opts *opts) if (ctx.incremental) strbuf_addf(&midx_name, "%s/pack/multi-pack-index.d/tmp_midx_XXXXXX", - opts->source->path); + opts->source->base.path); else get_midx_filename(opts->source, &midx_name); if (safe_create_leading_directories(r, midx_name.buf)) @@ -1396,7 +1396,7 @@ static int write_midx_internal(struct write_midx_opts *opts) fill_packs_from_midx_range(&ctx, bitmap_order); } else { ctx.to_include = opts->packs_to_include; - for_each_file_in_pack_dir(opts->source->path, add_pack_to_midx, &ctx); + for_each_file_in_pack_dir(opts->source->base.path, add_pack_to_midx, &ctx); } stop_progress(&ctx.progress); @@ -1847,7 +1847,7 @@ static int write_midx_internal(struct write_midx_opts *opts) return result; } -int write_midx_file(struct odb_source *source, +int write_midx_file(struct odb_source_packed *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) @@ -1862,7 +1862,7 @@ int write_midx_file(struct odb_source *source, return write_midx_internal(&opts); } -int write_midx_file_only(struct odb_source *source, +int write_midx_file_only(struct odb_source_packed *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, @@ -1881,7 +1881,7 @@ int write_midx_file_only(struct odb_source *source, return write_midx_internal(&opts); } -int write_midx_file_compact(struct odb_source *source, +int write_midx_file_compact(struct odb_source_packed *source, struct multi_pack_index *from, struct multi_pack_index *to, const char *incremental_base, @@ -1898,7 +1898,7 @@ int write_midx_file_compact(struct odb_source *source, return write_midx_internal(&opts); } -int expire_midx_packs(struct odb_source *source, unsigned flags) +int expire_midx_packs(struct odb_source_packed *source, unsigned flags) { uint32_t i, *count, result = 0; struct string_list packs_to_drop = STRING_LIST_INIT_DUP; @@ -1915,7 +1915,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags) if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - source->odb->repo, + source->base.odb->repo, _("Counting referenced objects"), m->num_objects); for (i = 0; i < m->num_objects; i++) { @@ -1927,7 +1927,7 @@ int expire_midx_packs(struct odb_source *source, unsigned flags) if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - source->odb->repo, + source->base.odb->repo, _("Finding and deleting unreferenced packfiles"), m->num_packs); for (i = 0; i < m->num_packs; i++) { @@ -2085,9 +2085,9 @@ static void fill_included_packs_batch(struct repository *r, free(pack_info); } -int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) +int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; int result = 0; uint32_t i, packs_to_repack = 0; unsigned char *include_pack; @@ -2131,7 +2131,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) strvec_push(&cmd.args, "pack-objects"); - strvec_pushf(&cmd.args, "%s/pack/pack", source->path); + strvec_pushf(&cmd.args, "%s/pack/pack", source->base.path); if (delta_base_offset) strvec_push(&cmd.args, "--delta-base-offset"); diff --git a/midx.c b/midx.c index efbfbb13f4106a..cc6b94f9dd4fba 100644 --- a/midx.c +++ b/midx.c @@ -17,9 +17,9 @@ #define MIDX_PACK_ERROR ((void *)(intptr_t)-1) int midx_checksum_valid(struct multi_pack_index *m); -void clear_midx_files_ext(struct odb_source *source, const char *ext, +void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash); -void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes); int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *idx_name); @@ -27,25 +27,25 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *midx_get_checksum_hex(const struct multi_pack_index *m) { return hash_to_hex_algop(midx_get_checksum_hash(m), - m->source->odb->repo->hash_algo); + m->source->base.odb->repo->hash_algo); } const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m) { - return m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz; + return m->data + m->data_len - m->source->base.odb->repo->hash_algo->rawsz; } -void get_midx_filename(struct odb_source *source, struct strbuf *out) +void get_midx_filename(struct odb_source_packed *source, struct strbuf *out) { get_midx_filename_ext(source, out, NULL, NULL); } -void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, +void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out, const unsigned char *hash, const char *ext) { - strbuf_addf(out, "%s/pack/multi-pack-index", source->path); + strbuf_addf(out, "%s/pack/multi-pack-index", source->base.path); if (ext) - strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); + strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext); } static int midx_read_oid_fanout(const unsigned char *chunk_start, @@ -99,17 +99,16 @@ static int midx_read_object_offsets(const unsigned char *chunk_start, return 0; } -struct multi_pack_index *get_multi_pack_index(struct odb_source *source) +struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source) { - struct odb_source_files *files = odb_source_files_downcast(source); - packfile_store_prepare(files->packed); - return files->packed->midx; + odb_source_packed_prepare(source); + return source->midx; } -static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source, +static struct multi_pack_index *load_multi_pack_index_one(struct odb_source_packed *source, const char *midx_name) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; struct multi_pack_index *m = NULL; int fd; struct stat st; @@ -234,23 +233,23 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou return NULL; } -void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf) +void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *buf) { - strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->path); + strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->base.path); } -void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf) +void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *buf) { get_midx_chain_dirname(source, buf); strbuf_addstr(buf, "/multi-pack-index-chain"); } -void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, +void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf, const unsigned char *hash, const char *ext) { get_midx_chain_dirname(source, buf); strbuf_addf(buf, "/multi-pack-index-%s.%s", - hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); + hash_to_hex_algop(hash, source->base.odb->repo->hash_algo), ext); } static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo, @@ -306,11 +305,11 @@ static int add_midx_to_chain(struct multi_pack_index *midx, return 1; } -static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source, +static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source_packed *source, int fd, struct stat *st, int *incomplete_chain) { - const struct git_hash_algo *hash_algo = source->odb->repo->hash_algo; + const struct git_hash_algo *hash_algo = source->base.odb->repo->hash_algo; struct multi_pack_index *midx_chain = NULL; struct strbuf buf = STRBUF_INIT; int valid = 1; @@ -362,7 +361,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source, return midx_chain; } -static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source) +static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source_packed *source) { struct strbuf chain_file = STRBUF_INIT; struct stat st; @@ -370,7 +369,8 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s struct multi_pack_index *m = NULL; get_midx_chain_filename(source, &chain_file); - if (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) { + if (open_multi_pack_index_chain(source->base.odb->repo->hash_algo, + chain_file.buf, &fd, &st)) { int incomplete; /* ownership of fd is taken over by load function */ m = load_midx_chain_fd_st(source, fd, &st, &incomplete); @@ -380,7 +380,7 @@ static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *s return m; } -struct multi_pack_index *load_multi_pack_index(struct odb_source *source) +struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source) { struct strbuf midx_name = STRBUF_INIT; struct multi_pack_index *m; @@ -456,7 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m, int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id) { - struct odb_source_files *files = odb_source_files_downcast(m->source); + struct odb_source_packed *packed = m->source; struct strbuf pack_name = STRBUF_INIT; struct packed_git *p; @@ -467,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m, if (m->packs[pack_int_id]) return 0; - strbuf_addf(&pack_name, "%s/pack/%s", files->base.path, + strbuf_addf(&pack_name, "%s/pack/%s", packed->base.path, m->pack_names[pack_int_id]); - p = packfile_store_load_pack(files->packed, - pack_name.buf, files->base.local); + p = packfile_store_load_pack(packed, + pack_name.buf, packed->base.local); strbuf_release(&pack_name); if (!p) { @@ -523,7 +523,7 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m, { int ret = bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup, - m->source->odb->repo->hash_algo->rawsz, + m->source->base.odb->repo->hash_algo->rawsz, result); if (result) *result += m->num_objects_in_base; @@ -554,7 +554,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid, n = midx_for_object(&m, n); oidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n), - m->source->odb->repo->hash_algo); + m->source->base.odb->repo->hash_algo); return oid; } @@ -734,26 +734,25 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id) return 0; } -int prepare_multi_pack_index_one(struct odb_source *source) +int prepare_multi_pack_index_one(struct odb_source_packed *source) { - struct odb_source_files *files = odb_source_files_downcast(source); - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; prepare_repo_settings(r); if (!r->settings.core_multi_pack_index) return 0; - if (files->packed->midx) + if (source->midx) return 1; - files->packed->midx = load_multi_pack_index(source); + source->midx = load_multi_pack_index(source); - return !!files->packed->midx; + return !!source->midx; } int midx_checksum_valid(struct multi_pack_index *m) { - return hashfile_checksum_valid(m->source->odb->repo->hash_algo, + return hashfile_checksum_valid(m->source->base.odb->repo->hash_algo, m->data, m->data_len); } @@ -776,7 +775,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS die_errno(_("failed to remove %s"), full_path); } -void clear_midx_files_ext(struct odb_source *source, const char *ext, +void clear_midx_files_ext(struct odb_source_packed *source, const char *ext, const char *keep_hash) { struct clear_midx_data data = { @@ -793,12 +792,12 @@ void clear_midx_files_ext(struct odb_source *source, const char *ext, strbuf_release(&buf); } - for_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data); + for_each_file_in_pack_dir(source->base.path, clear_midx_file_ext, &data); strset_clear(&data.keep); } -void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source_packed *source, const char *ext, const struct strvec *keep_hashes) { struct clear_midx_data data = { @@ -817,7 +816,7 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext } } - for_each_file_in_pack_subdir(source->path, "multi-pack-index.d", + for_each_file_in_pack_subdir(source->base.path, "multi-pack-index.d", clear_midx_file_ext, &data); strbuf_release(&buf); @@ -826,26 +825,28 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext void clear_midx_file(struct repository *r) { + struct odb_source_files *files; struct strbuf midx = STRBUF_INIT; - get_midx_filename(r->objects->sources, &midx); - if (r->objects) { struct odb_source *source; for (source = r->objects->sources; source; source = source->next) { - struct odb_source_files *files = odb_source_files_downcast(source); + files = odb_source_files_downcast(source); if (files->packed->midx) close_midx(files->packed->midx); files->packed->midx = NULL; } } + files = odb_source_files_downcast(r->objects->sources); + get_midx_filename(files->packed, &midx); + if (remove_path(midx.buf)) die(_("failed to clear multi-pack-index at %s"), midx.buf); - clear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL); - clear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL); + clear_midx_files_ext(files->packed, MIDX_EXT_BITMAP, NULL); + clear_midx_files_ext(files->packed, MIDX_EXT_REV, NULL); strbuf_release(&midx); } @@ -853,28 +854,27 @@ void clear_midx_file(struct repository *r) void clear_incremental_midx_files(struct repository *r, const struct strvec *keep_hashes) { - struct odb_source *source = r->objects->sources; + struct odb_source_files *files; + struct odb_source *source; struct strbuf chain = STRBUF_INIT; - get_midx_chain_filename(source, &chain); - - for (; source; source = source->next) { - struct odb_source_files *files = odb_source_files_downcast(source); + for (source = r->objects->sources; source; source = source->next) { + files = odb_source_files_downcast(source); if (files->packed->midx) close_midx(files->packed->midx); files->packed->midx = NULL; } + files = odb_source_files_downcast(r->objects->sources); + get_midx_chain_filename(files->packed, &chain); + if (!keep_hashes && remove_path(chain.buf)) die(_("failed to clear multi-pack-index chain at %s"), chain.buf); - clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, - keep_hashes); - clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_REV, - keep_hashes); - clear_incremental_midx_files_ext(r->objects->sources, MIDX_EXT_MIDX, - keep_hashes); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_BITMAP, keep_hashes); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_REV, keep_hashes); + clear_incremental_midx_files_ext(files->packed, MIDX_EXT_MIDX, keep_hashes); strbuf_release(&chain); } @@ -918,9 +918,9 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b) display_progress(progress, _n); \ } while (0) -int verify_midx_file(struct odb_source *source, unsigned flags) +int verify_midx_file(struct odb_source_packed *source, unsigned flags) { - struct repository *r = source->odb->repo; + struct repository *r = source->base.odb->repo; struct pair_pos_vs_id *pairs = NULL; uint32_t i; struct progress *progress = NULL; diff --git a/midx.h b/midx.h index 63853a03a47fd1..939c18e5885e6f 100644 --- a/midx.h +++ b/midx.h @@ -37,7 +37,7 @@ struct strvec; "GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL" struct multi_pack_index { - struct odb_source *source; + struct odb_source_packed *source; const unsigned char *data; size_t data_len; @@ -92,16 +92,16 @@ struct multi_pack_index { const char *midx_get_checksum_hex(const struct multi_pack_index *m) /* static buffer */; const unsigned char *midx_get_checksum_hash(const struct multi_pack_index *m); -void get_midx_filename(struct odb_source *source, struct strbuf *out); -void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, +void get_midx_filename(struct odb_source_packed *source, struct strbuf *out); +void get_midx_filename_ext(struct odb_source_packed *source, struct strbuf *out, const unsigned char *hash, const char *ext); -void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out); -void get_midx_chain_filename(struct odb_source *source, struct strbuf *out); -void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, +void get_midx_chain_dirname(struct odb_source_packed *source, struct strbuf *out); +void get_midx_chain_filename(struct odb_source_packed *source, struct strbuf *out); +void get_split_midx_filename_ext(struct odb_source_packed *source, struct strbuf *buf, const unsigned char *hash, const char *ext); -struct multi_pack_index *get_multi_pack_index(struct odb_source *source); -struct multi_pack_index *load_multi_pack_index(struct odb_source *source); +struct multi_pack_index *get_multi_pack_index(struct odb_source_packed *source); +struct multi_pack_index *load_multi_pack_index(struct odb_source_packed *source); int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id); struct packed_git *nth_midxed_pack(struct multi_pack_index *m, uint32_t pack_int_id); @@ -123,22 +123,22 @@ int midx_contains_pack(struct multi_pack_index *m, int midx_layer_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id); -int prepare_multi_pack_index_one(struct odb_source *source); +int prepare_multi_pack_index_one(struct odb_source_packed *source); /* * Variant of write_midx_file which writes a MIDX containing only the packs * specified in packs_to_include. */ -int write_midx_file(struct odb_source *source, +int write_midx_file(struct odb_source_packed *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags); -int write_midx_file_only(struct odb_source *source, +int write_midx_file_only(struct odb_source_packed *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, const char *incremental_base, unsigned flags); -int write_midx_file_compact(struct odb_source *source, +int write_midx_file_compact(struct odb_source_packed *source, struct multi_pack_index *from, struct multi_pack_index *to, const char *incremental_base, @@ -146,9 +146,9 @@ int write_midx_file_compact(struct odb_source *source, void clear_midx_file(struct repository *r); void clear_incremental_midx_files(struct repository *r, const struct strvec *keep_hashes); -int verify_midx_file(struct odb_source *source, unsigned flags); -int expire_midx_packs(struct odb_source *source, unsigned flags); -int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags); +int verify_midx_file(struct odb_source_packed *source, unsigned flags); +int expire_midx_packs(struct odb_source_packed *source, unsigned flags); +int midx_repack(struct odb_source_packed *source, size_t batch_size, unsigned flags); void close_midx(struct multi_pack_index *m); diff --git a/object-file.c b/object-file.c index e3d92bbda25f0a..6453b1d6fa7d1b 100644 --- a/object-file.c +++ b/object-file.c @@ -316,9 +316,9 @@ int parse_loose_header(const char *hdr, struct object_info *oi) } static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c, - const void *buf, unsigned long len, + const void *buf, size_t len, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { algo->init_fn(c); git_hash_update(c, hdr, *hdrlen); @@ -327,16 +327,16 @@ static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_c } void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { struct git_hash_ctx c; /* Generate the header */ *hdrlen = format_object_header(hdr, *hdrlen, type, len); - /* Sha1.. */ + /* Hash (function pointers) computation */ hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); } @@ -472,11 +472,11 @@ int finalize_object_file_flags(struct repository *repo, } void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, + size_t len, enum object_type type, struct object_id *oid) { char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); + size_t hdrlen = sizeof(hdr); write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); } diff --git a/object-file.h b/object-file.h index 528c4e6e697f87..4c87cd160bb58a 100644 --- a/object-file.h +++ b/object-file.h @@ -131,12 +131,12 @@ int finalize_object_file_flags(struct repository *repo, enum finalize_object_file_flags flags); void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, + size_t len, enum object_type type, struct object_id *oid); void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, - char *hdr, int *hdrlen); + char *hdr, size_t *hdrlen); int write_loose_object(struct odb_source_loose *loose, const struct object_id *oid, char *hdr, int hdrlen, const void *buf, unsigned long len, diff --git a/odb/source-files.c b/odb/source-files.c index 5bdd0429225397..bbd1784b337c6d 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -29,7 +29,7 @@ static void odb_source_files_free(struct odb_source *source) struct odb_source_files *files = odb_source_files_downcast(source); chdir_notify_unregister(NULL, odb_source_files_reparent, files); odb_source_free(&files->loose->base); - packfile_store_free(files->packed); + odb_source_free(&files->packed->base); odb_source_release(&files->base); free(files); } @@ -38,14 +38,14 @@ static void odb_source_files_close(struct odb_source *source) { struct odb_source_files *files = odb_source_files_downcast(source); odb_source_close(&files->loose->base); - packfile_store_close(files->packed); + odb_source_close(&files->packed->base); } static void odb_source_files_reprepare(struct odb_source *source) { struct odb_source_files *files = odb_source_files_downcast(source); odb_source_reprepare(&files->loose->base); - packfile_store_reprepare(files->packed); + odb_source_reprepare(&files->packed->base); } static int odb_source_files_read_object_info(struct odb_source *source, @@ -55,7 +55,7 @@ static int odb_source_files_read_object_info(struct odb_source *source, { struct odb_source_files *files = odb_source_files_downcast(source); - if (!packfile_store_read_object_info(files->packed, oid, oi, flags) || + if (!odb_source_read_object_info(&files->packed->base, oid, oi, flags) || !odb_source_read_object_info(&files->loose->base, oid, oi, flags)) return 0; @@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out, const struct object_id *oid) { struct odb_source_files *files = odb_source_files_downcast(source); - if (!packfile_store_read_object_stream(out, files->packed, oid) || + if (!odb_source_read_object_stream(out, &files->packed->base, oid) || !odb_source_read_object_stream(out, &files->loose->base, oid)) return 0; return -1; @@ -88,7 +88,7 @@ static int odb_source_files_for_each_object(struct odb_source *source, return ret; } - ret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts); + ret = odb_source_for_each_object(&files->packed->base, request, cb, cb_data, opts); if (ret) return ret; @@ -103,7 +103,7 @@ static int odb_source_files_count_objects(struct odb_source *source, unsigned long count; int ret; - ret = packfile_store_count_objects(files->packed, flags, &count); + ret = odb_source_count_objects(&files->packed->base, flags, &count); if (ret < 0) goto out; @@ -133,7 +133,7 @@ static int odb_source_files_find_abbrev_len(struct odb_source *source, unsigned len = min_len; int ret; - ret = packfile_store_find_abbrev_len(files->packed, oid, len, &len); + ret = odb_source_find_abbrev_len(&files->packed->base, oid, len, &len); if (ret < 0) goto out; @@ -152,14 +152,14 @@ static int odb_source_files_freshen_object(struct odb_source *source, const struct object_id *oid) { struct odb_source_files *files = odb_source_files_downcast(source); - if (packfile_store_freshen_object(files->packed, oid) || + if (odb_source_freshen_object(&files->packed->base, oid) || odb_source_freshen_object(&files->loose->base, oid)) return 1; return 0; } static int odb_source_files_write_object(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid, @@ -269,7 +269,7 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb, CALLOC_ARRAY(files, 1); odb_source_init(&files->base, odb, ODB_SOURCE_FILES, path, local); files->loose = odb_source_loose_new(odb, path, local); - files->packed = packfile_store_new(&files->base); + files->packed = odb_source_packed_new(odb, path, local); files->base.free = odb_source_files_free; files->base.close = odb_source_files_close; diff --git a/odb/source-files.h b/odb/source-files.h index 23a3b4e04b1218..d7ac3c1c81d892 100644 --- a/odb/source-files.h +++ b/odb/source-files.h @@ -4,7 +4,7 @@ #include "odb/source.h" struct odb_source_loose; -struct packfile_store; +struct odb_source_packed; /* * The files object database source uses a combination of loose objects and @@ -13,7 +13,7 @@ struct packfile_store; struct odb_source_files { struct odb_source base; struct odb_source_loose *loose; - struct packfile_store *packed; + struct odb_source_packed *packed; }; /* Allocate and initialize a new object source. */ diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c index e004566d768b01..7f1b6f46363f2e 100644 --- a/odb/source-inmemory.c +++ b/odb/source-inmemory.c @@ -227,7 +227,7 @@ static int odb_source_inmemory_count_objects(struct odb_source *source, } static int odb_source_inmemory_write_object(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid UNUSED, diff --git a/odb/source-loose.c b/odb/source-loose.c index 66e6bb8d3f8004..6211348a4d3561 100644 --- a/odb/source-loose.c +++ b/odb/source-loose.c @@ -583,7 +583,7 @@ static int odb_source_loose_freshen_object(struct odb_source *source, } static int odb_source_loose_write_object(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid_in, enum odb_write_object_flags flags) @@ -593,7 +593,7 @@ static int odb_source_loose_write_object(struct odb_source *source, const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; struct object_id compat_oid; char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); + size_t hdrlen = sizeof(hdr); /* Generate compat_oid */ if (compat) { diff --git a/odb/source-packed.c b/odb/source-packed.c new file mode 100644 index 00000000000000..06b31dd7435d70 --- /dev/null +++ b/odb/source-packed.c @@ -0,0 +1,788 @@ +#include "git-compat-util.h" +#include "abspath.h" +#include "chdir-notify.h" +#include "dir.h" +#include "git-zlib.h" +#include "mergesort.h" +#include "midx.h" +#include "odb/source-packed.h" +#include "odb/streaming.h" +#include "packfile.h" + +static int find_pack_entry(struct odb_source_packed *store, + const struct object_id *oid, + struct pack_entry *e) +{ + struct packfile_list_entry *l; + + odb_source_packed_prepare(store); + if (store->midx && fill_midx_entry(store->midx, oid, e)) + return 1; + + for (l = store->packs.head; l; l = l->next) { + struct packed_git *p = l->pack; + + if (!p->multi_pack_index && packfile_fill_entry(p, oid, e)) { + if (!store->skip_mru_updates) + packfile_list_prepend(&store->packs, p); + return 1; + } + } + + return 0; +} + +static int odb_source_packed_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + int ret; + + /* + * In case the first read didn't surface the object, we have to reload + * packfiles. This may cause us to discover new packfiles that have + * been added since the last time we have prepared the packfile store. + */ + if (flags & OBJECT_INFO_SECOND_READ) + odb_source_reprepare(source); + + if (!find_pack_entry(packed, oid, &e)) + return 1; + + /* + * We know that the caller doesn't actually need the + * information below, so return early. + */ + if (!oi) + return 0; + + ret = packed_object_info(e.p, e.offset, oi); + if (ret < 0) { + mark_bad_packed_object(e.p, oid); + return -1; + } + + return 0; +} + +static int odb_source_packed_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + + if (!find_pack_entry(packed, oid, &e)) + return -1; + + return packfile_read_object_stream(out, oid, e.p, e.offset); +} + +struct odb_source_packed_for_each_object_wrapper_data { + struct odb_source_packed *store; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int odb_source_packed_for_each_object_wrapper(const struct object_id *oid, + struct packed_git *pack, + uint32_t index_pos, + void *cb_data) +{ + struct odb_source_packed_for_each_object_wrapper_data *data = cb_data; + + if (data->request) { + off_t offset = nth_packed_object_offset(pack, index_pos); + struct object_info oi = *data->request; + + if (packed_object_info_with_index_pos(pack, offset, + &index_pos, &oi) < 0) { + mark_bad_packed_object(pack, oid); + return -1; + } + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) +{ + do { + if (*a != *b) + return 0; + a++; + b++; + len -= 2; + } while (len > 1); + if (len) + if ((*a ^ *b) & 0xf0) + return 0; + return 1; +} + +static bool should_exclude_pack(struct packed_git *p, enum odb_for_each_object_flags flags) +{ + if ((flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) && + !p->pack_promisor) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && + p->pack_keep_in_core) + return true; + if ((flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && + p->pack_keep) + return true; + return false; +} + +static int for_each_prefixed_object_in_midx( + struct odb_source_packed *store, + struct multi_pack_index *m, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + bool pack_errors = false; + int ret; + + for (; m; m = m->base_midx) { + uint32_t num, i, first = 0; + int len = opts->prefix_hex_len > m->source->base.odb->repo->hash_algo->hexsz ? + m->source->base.odb->repo->hash_algo->hexsz : opts->prefix_hex_len; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; + + bsearch_one_midx(opts->prefix, m, &first); + + /* + * At this point, "first" is the location of the lowest + * object with an object name that could match "opts->prefix". + * See if we have 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + const struct object_id *current = NULL; + struct object_id oid; + + current = nth_midxed_object_oid(&oid, m, i); + + if (!match_hash(len, opts->prefix->hash, current->hash)) + break; + + if (opts->flags) { + uint32_t pack_id = nth_midxed_pack_int_id(m, i); + struct packed_git *pack; + + if (prepare_midx_pack(m, pack_id)) { + pack_errors = true; + continue; + } + + pack = nth_midxed_pack(m, pack_id); + if (should_exclude_pack(pack, opts->flags)) + continue; + } + + if (data->request) { + struct object_info oi = *data->request; + + ret = odb_source_read_object_info(&store->base, current, + &oi, 0); + if (ret) + goto out; + + ret = data->cb(&oid, &oi, data->cb_data); + if (ret) + goto out; + } else { + ret = data->cb(&oid, NULL, data->cb_data); + if (ret) + goto out; + } + } + } + + ret = 0; + +out: + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int for_each_prefixed_object_in_pack( + struct odb_source_packed *store, + struct packed_git *p, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + uint32_t num, i, first = 0; + int len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ? + p->repo->hash_algo->hexsz : opts->prefix_hex_len; + int ret; + + num = p->num_objects; + bsearch_pack(opts->prefix, p, &first); + + /* + * At this point, "first" is the location of the lowest object + * with an object name that could match "bin_pfx". See if we have + * 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + struct object_id oid; + + nth_packed_object_id(&oid, p, i); + if (!match_hash(len, opts->prefix->hash, oid.hash)) + break; + + if (data->request) { + struct object_info oi = *data->request; + + ret = odb_source_read_object_info(&store->base, &oid, &oi, 0); + if (ret) + goto out; + + ret = data->cb(&oid, &oi, data->cb_data); + if (ret) + goto out; + } else { + ret = data->cb(&oid, NULL, data->cb_data); + if (ret) + goto out; + } + } + + ret = 0; + +out: + return ret; +} + +static int odb_source_packed_for_each_prefixed_object( + struct odb_source_packed *store, + const struct odb_for_each_object_options *opts, + struct odb_source_packed_for_each_object_wrapper_data *data) +{ + struct packfile_list_entry *e; + struct multi_pack_index *m; + bool pack_errors = false; + int ret; + + store->skip_mru_updates = true; + + m = get_multi_pack_index(store); + if (m) { + ret = for_each_prefixed_object_in_midx(store, m, opts, data); + if (ret) + goto out; + } + + for (e = packfile_store_get_packs(store); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (should_exclude_pack(e->pack, opts->flags)) + continue; + + if (open_pack_index(e->pack)) { + pack_errors = true; + continue; + } + + if (!e->pack->num_objects) + continue; + + ret = for_each_prefixed_object_in_pack(store, e->pack, opts, data); + if (ret) + goto out; + } + + ret = 0; + +out: + store->skip_mru_updates = false; + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int odb_source_packed_for_each_object(struct odb_source *source, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct odb_source_packed_for_each_object_wrapper_data data = { + .store = packed, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + struct packfile_list_entry *e; + int pack_errors = 0, ret; + + if (opts->prefix) + return odb_source_packed_for_each_prefixed_object(packed, opts, &data); + + packed->skip_mru_updates = true; + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + struct packed_git *p = e->pack; + + if (should_exclude_pack(p, opts->flags)) + continue; + + if (open_pack_index(p)) { + pack_errors = 1; + continue; + } + + ret = for_each_object_in_pack(p, odb_source_packed_for_each_object_wrapper, + &data, opts->flags); + if (ret) + goto out; + } + + ret = 0; + +out: + packed->skip_mru_updates = false; + + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int odb_source_packed_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags UNUSED, + unsigned long *out) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct packfile_list_entry *e; + struct multi_pack_index *m; + unsigned long count = 0; + int ret; + + m = get_multi_pack_index(packed); + if (m) + count += m->num_objects + m->num_objects_in_base; + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack)) { + ret = -1; + goto out; + } + + count += e->pack->num_objects; + } + + *out = count; + ret = 0; + +out: + return ret; +} + +static int extend_abbrev_len(const struct object_id *a, + const struct object_id *b, + unsigned *out) +{ + unsigned len = oid_common_prefix_hexlen(a, b); + if (len != hash_algos[a->algo].hexsz && len >= *out) + *out = len + 1; + return 0; +} + +static void find_abbrev_len_for_midx(struct multi_pack_index *m, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + unsigned len = min_len; + + for (; m; m = m->base_midx) { + int match = 0; + uint32_t num, first = 0; + struct object_id found_oid; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; + match = bsearch_one_midx(oid, m, &first); + + /* + * first is now the position in the packfile where we + * would insert the object ID if it does not exist (or the + * position of the object ID if it does exist). Hence, we + * consider a maximum of two objects nearby for the + * abbreviation length. + */ + + if (!match) { + if (nth_midxed_object_oid(&found_oid, m, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (nth_midxed_object_oid(&found_oid, m, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (nth_midxed_object_oid(&found_oid, m, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + } + + *out = len; +} + +static void find_abbrev_len_for_pack(struct packed_git *p, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + int match; + uint32_t num, first = 0; + struct object_id found_oid; + unsigned len = min_len; + + num = p->num_objects; + match = bsearch_pack(oid, p, &first); + + /* + * first is now the position in the packfile where we would insert + * the object ID if it does not exist (or the position of mad->hash if + * it does exist). Hence, we consider a maximum of two objects + * nearby for the abbreviation length. + */ + if (!match) { + if (!nth_packed_object_id(&found_oid, p, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (!nth_packed_object_id(&found_oid, p, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (!nth_packed_object_id(&found_oid, p, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + + *out = len; +} + +static int odb_source_packed_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct packfile_list_entry *e; + struct multi_pack_index *m; + + m = get_multi_pack_index(packed); + if (m) + find_abbrev_len_for_midx(m, oid, min_len, &min_len); + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack) || !e->pack->num_objects) + continue; + + find_abbrev_len_for_pack(e->pack, oid, min_len, &min_len); + } + + *out = min_len; + return 0; +} + +static int odb_source_packed_freshen_object(struct odb_source *source, + const struct object_id *oid) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct pack_entry e; + + if (!find_pack_entry(packed, oid, &e)) + return 0; + if (e.p->is_cruft) + return 0; + if (e.p->freshened) + return 1; + if (utime(e.p->pack_name, NULL)) + return 0; + e.p->freshened = 1; + + return 1; +} + +static int odb_source_packed_write_object(struct odb_source *source UNUSED, + const void *buf UNUSED, + size_t len UNUSED, + enum object_type type UNUSED, + struct object_id *oid UNUSED, + struct object_id *compat_oid UNUSED, + unsigned flags UNUSED) +{ + return error("packed backend cannot write objects"); +} + +static int odb_source_packed_write_object_stream(struct odb_source *source UNUSED, + struct odb_write_stream *stream UNUSED, + size_t len UNUSED, + struct object_id *oid UNUSED) +{ + return error("packed backend cannot write object streams"); +} + +static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED, + struct odb_transaction **out UNUSED) +{ + return error("packed backend cannot begin transactions"); +} + +static int odb_source_packed_read_alternates(struct odb_source *source UNUSED, + struct strvec *out UNUSED) +{ + return 0; +} + +static int odb_source_packed_write_alternate(struct odb_source *source UNUSED, + const char *alternate UNUSED) +{ + return error("packed backend cannot write alternates"); +} + +void (*report_garbage)(unsigned seen_bits, const char *path); + +static void report_helper(const struct string_list *list, + int seen_bits, int first, int last) +{ + if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX)) + return; + + for (; first < last; first++) + report_garbage(seen_bits, list->items[first].string); +} + +static void report_pack_garbage(struct string_list *list) +{ + int baselen = -1, first = 0, seen_bits = 0; + + if (!report_garbage) + return; + + string_list_sort(list); + + for (size_t i = 0; i < list->nr; i++) { + const char *path = list->items[i].string; + if (baselen != -1 && + strncmp(path, list->items[first].string, baselen)) { + report_helper(list, seen_bits, first, i); + baselen = -1; + seen_bits = 0; + } + if (baselen == -1) { + const char *dot = strrchr(path, '.'); + if (!dot) { + report_garbage(PACKDIR_FILE_GARBAGE, path); + continue; + } + baselen = dot - path + 1; + first = i; + } + if (!strcmp(path + baselen, "pack")) + seen_bits |= 1; + else if (!strcmp(path + baselen, "idx")) + seen_bits |= 2; + } + report_helper(list, seen_bits, first, list->nr); +} + +struct prepare_pack_data { + struct odb_source_packed *source; + struct string_list *garbage; +}; + +static void prepare_pack(const char *full_name, size_t full_name_len, + const char *file_name, void *_data) +{ + struct prepare_pack_data *data = (struct prepare_pack_data *)_data; + size_t base_len = full_name_len; + + if (strip_suffix_mem(full_name, &base_len, ".idx") && + !(data->source->midx && + midx_contains_pack(data->source->midx, file_name))) { + char *trimmed_path = xstrndup(full_name, full_name_len); + packfile_store_load_pack(data->source, + trimmed_path, data->source->base.local); + free(trimmed_path); + } + + if (!report_garbage) + return; + + if (!strcmp(file_name, "multi-pack-index") || + !strcmp(file_name, "multi-pack-index.d")) + return; + if (starts_with(file_name, "multi-pack-index") && + (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev"))) + return; + if (ends_with(file_name, ".idx") || + ends_with(file_name, ".rev") || + ends_with(file_name, ".pack") || + ends_with(file_name, ".bitmap") || + ends_with(file_name, ".keep") || + ends_with(file_name, ".promisor") || + ends_with(file_name, ".mtimes")) + string_list_append(data->garbage, full_name); + else + report_garbage(PACKDIR_FILE_GARBAGE, full_name); +} + +static void prepare_packed_git_one(struct odb_source_packed *source) +{ + struct string_list garbage = STRING_LIST_INIT_DUP; + struct prepare_pack_data data = { + .source = source, + .garbage = &garbage, + }; + + for_each_file_in_pack_dir(source->base.path, prepare_pack, &data); + + report_pack_garbage(data.garbage); + string_list_clear(data.garbage, 0); +} + +DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next); + +static int sort_pack(const struct packfile_list_entry *a, + const struct packfile_list_entry *b) +{ + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack->pack_local - b->pack->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->pack->mtime < b->pack->mtime) + return 1; + else if (a->pack->mtime == b->pack->mtime) + return 0; + return -1; +} + +void odb_source_packed_prepare(struct odb_source_packed *source) +{ + if (source->initialized) + return; + + prepare_multi_pack_index_one(source); + prepare_packed_git_one(source); + + sort_packs(&source->packs.head, sort_pack); + for (struct packfile_list_entry *e = source->packs.head; e; e = e->next) + if (!e->next) + source->packs.tail = e; + + source->initialized = true; +} + +static void odb_source_packed_reprepare(struct odb_source *source) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + packed->initialized = false; + odb_source_packed_prepare(packed); +} + +static void odb_source_packed_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *cb_data) +{ + struct odb_source_packed *packed = cb_data; + char *path = reparent_relative_path(old_cwd, new_cwd, + packed->base.path); + free(packed->base.path); + packed->base.path = path; +} + +static void odb_source_packed_close(struct odb_source *source) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + + for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) { + if (e->pack->do_not_close) + BUG("want to close pack marked 'do-not-close'"); + close_pack(e->pack); + } + if (packed->midx) + close_midx(packed->midx); + packed->midx = NULL; +} + +static void odb_source_packed_free(struct odb_source *source) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + + chdir_notify_unregister(NULL, odb_source_packed_reparent, packed); + + for (struct packfile_list_entry *e = packed->packs.head; e; e = e->next) + free(e->pack); + packfile_list_clear(&packed->packs); + + strmap_clear(&packed->packs_by_path, 0); + odb_source_release(&packed->base); + free(packed); +} + +struct odb_source_packed *odb_source_packed_new(struct object_database *odb, + const char *path, + bool local) +{ + struct odb_source_packed *packed; + + CALLOC_ARRAY(packed, 1); + odb_source_init(&packed->base, odb, ODB_SOURCE_PACKED, path, local); + strmap_init(&packed->packs_by_path); + + packed->base.free = odb_source_packed_free; + packed->base.close = odb_source_packed_close; + packed->base.reprepare = odb_source_packed_reprepare; + packed->base.read_object_info = odb_source_packed_read_object_info; + packed->base.read_object_stream = odb_source_packed_read_object_stream; + packed->base.for_each_object = odb_source_packed_for_each_object; + packed->base.count_objects = odb_source_packed_count_objects; + packed->base.find_abbrev_len = odb_source_packed_find_abbrev_len; + packed->base.freshen_object = odb_source_packed_freshen_object; + packed->base.write_object = odb_source_packed_write_object; + packed->base.write_object_stream = odb_source_packed_write_object_stream; + packed->base.begin_transaction = odb_source_packed_begin_transaction; + packed->base.read_alternates = odb_source_packed_read_alternates; + packed->base.write_alternate = odb_source_packed_write_alternate; + + if (!is_absolute_path(path)) + chdir_notify_register(NULL, odb_source_packed_reparent, packed); + + return packed; +} diff --git a/odb/source-packed.h b/odb/source-packed.h new file mode 100644 index 00000000000000..f0724b204c9223 --- /dev/null +++ b/odb/source-packed.h @@ -0,0 +1,94 @@ +#ifndef ODB_SOURCE_PACKED_H +#define ODB_SOURCE_PACKED_H + +#include "odb/source.h" +#include "packfile-list.h" +#include "strmap.h" + +/* + * A store that manages packfiles for a given object database. + */ +struct odb_source_packed { + struct odb_source base; + + /* + * The list of packfiles in the order in which they have been most + * recently used. + */ + struct packfile_list packs; + + /* + * Cache of packfiles which are marked as "kept", either because there + * is an on-disk ".keep" file or because they are marked as "kept" in + * memory. + * + * Should not be accessed directly, but via + * `packfile_store_get_kept_pack_cache()`. The list of packs gets + * invalidated when the stored flags and the flags passed to + * `packfile_store_get_kept_pack_cache()` mismatch. + */ + struct { + struct packed_git **packs; + unsigned flags; + } kept_cache; + + /* The multi-pack index that belongs to this specific packfile store. */ + struct multi_pack_index *midx; + + /* + * A map of packfile names to packed_git structs for tracking which + * packs have been loaded already. + */ + struct strmap packs_by_path; + + /* + * Whether packfiles have already been populated with this store's + * packs. + */ + bool initialized; + + /* + * Usually, packfiles will be reordered to the front of the `packs` + * list whenever an object is looked up via them. This has the effect + * that packs that contain a lot of accessed objects will be located + * towards the front. + * + * This is usually desirable, but there are exceptions. One exception + * is when the looking up multiple objects in a loop for each packfile. + * In that case, we may easily end up with an infinite loop as the + * packfiles get reordered to the front repeatedly. + * + * Setting this field to `true` thus disables these reorderings. + */ + bool skip_mru_updates; +}; + +/* + * Allocate and initialize a new empty packfile store for the given object + * database. + */ +struct odb_source_packed *odb_source_packed_new(struct object_database *odb, + const char *path, + bool local); + +/* + * Cast the given object database source to the packed backend. This will cause + * a BUG in case the source doesn't use this backend. + */ +static inline struct odb_source_packed *odb_source_packed_downcast(struct odb_source *source) +{ + if (source->type != ODB_SOURCE_PACKED) + BUG("trying to downcast source of type '%d' to packed", source->type); + return container_of(source, struct odb_source_packed, base); +} + +/* + * Prepare the source by loading packfiles and multi-pack indices for + * all alternates. This becomes a no-op if the source is already prepared. + * + * It shouldn't typically be necessary to call this function directly, as + * functions that access the source know to prepare it. + */ +void odb_source_packed_prepare(struct odb_source_packed *source); + +#endif diff --git a/odb/source.h b/odb/source.h index 2192a101b8ab08..8767708c9c769c 100644 --- a/odb/source.h +++ b/odb/source.h @@ -17,6 +17,9 @@ enum odb_source_type { /* The "loose" backend that uses loose objects, only. */ ODB_SOURCE_LOOSE, + /* The "packed" backend that uses packfiles. */ + ODB_SOURCE_PACKED, + /* The "in-memory" backend that stores objects in memory. */ ODB_SOURCE_INMEMORY, }; @@ -199,7 +202,7 @@ struct odb_source { * return 0 on success, a negative error code otherwise. */ int (*write_object)(struct odb_source *source, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid, diff --git a/pack-bitmap.c b/pack-bitmap.c index e8a82945cc319e..83eb47a28ba9de 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -238,7 +238,7 @@ static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos) static struct repository *bitmap_repo(struct bitmap_index *bitmap_git) { if (bitmap_is_midx(bitmap_git)) - return bitmap_git->midx->source->odb->repo; + return bitmap_git->midx->source->base.odb->repo; return bitmap_git->pack->repo; } @@ -711,7 +711,8 @@ static int open_midx_bitmap(struct repository *r, odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - struct multi_pack_index *midx = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(source); + struct multi_pack_index *midx = get_multi_pack_index(files->packed); if (midx && !open_midx_bitmap_1(bitmap_git, midx)) ret = 0; } @@ -3399,7 +3400,8 @@ int verify_bitmap_files(struct repository *r) odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(source); + struct multi_pack_index *m = get_multi_pack_index(files->packed); char *midx_bitmap_name; if (!m) diff --git a/pack-revindex.c b/pack-revindex.c index 1b67863606a75f..62387ae6320181 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -383,13 +383,13 @@ int load_midx_revindex(struct multi_pack_index *m) * not want to accidentally call munmap() in the middle of the * MIDX. */ - trace2_data_string("load_midx_revindex", m->source->odb->repo, + trace2_data_string("load_midx_revindex", m->source->base.odb->repo, "source", "midx"); m->revindex_data = (const uint32_t *)m->chunk_revindex; return 0; } - trace2_data_string("load_midx_revindex", m->source->odb->repo, + trace2_data_string("load_midx_revindex", m->source->base.odb->repo, "source", "rev"); if (m->has_chain) @@ -401,7 +401,7 @@ int load_midx_revindex(struct multi_pack_index *m) midx_get_checksum_hash(m), MIDX_EXT_REV); - ret = load_revindex_from_disk(m->source->odb->repo->hash_algo, + ret = load_revindex_from_disk(m->source->base.odb->repo->hash_algo, revindex_name.buf, m->num_objects, &m->revindex_map, diff --git a/packfile-list.c b/packfile-list.c new file mode 100644 index 00000000000000..01fb913abf78fc --- /dev/null +++ b/packfile-list.c @@ -0,0 +1,86 @@ +#include "git-compat-util.h" +#include "packfile.h" +#include "packfile-list.h" + +void packfile_list_clear(struct packfile_list *list) +{ + struct packfile_list_entry *e, *next; + + for (e = list->head; e; e = next) { + next = e->next; + free(e); + } + + list->head = list->tail = NULL; +} + +static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list, + struct packed_git *pack) +{ + struct packfile_list_entry *e, *prev; + + for (e = list->head, prev = NULL; e; prev = e, e = e->next) { + if (e->pack != pack) + continue; + + if (prev) + prev->next = e->next; + if (list->head == e) + list->head = e->next; + if (list->tail == e) + list->tail = prev; + + return e; + } + + return NULL; +} + +void packfile_list_remove(struct packfile_list *list, struct packed_git *pack) +{ + free(packfile_list_remove_internal(list, pack)); +} + +void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack) +{ + struct packfile_list_entry *entry; + + entry = packfile_list_remove_internal(list, pack); + if (!entry) { + entry = xmalloc(sizeof(*entry)); + entry->pack = pack; + } + entry->next = list->head; + + list->head = entry; + if (!list->tail) + list->tail = entry; +} + +void packfile_list_append(struct packfile_list *list, struct packed_git *pack) +{ + struct packfile_list_entry *entry; + + entry = packfile_list_remove_internal(list, pack); + if (!entry) { + entry = xmalloc(sizeof(*entry)); + entry->pack = pack; + } + entry->next = NULL; + + if (list->tail) { + list->tail->next = entry; + list->tail = entry; + } else { + list->head = list->tail = entry; + } +} + +struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, + const struct object_id *oid) +{ + for (; packs; packs = packs->next) + if (find_pack_entry_one(oid, packs->pack)) + return packs->pack; + return NULL; +} diff --git a/packfile-list.h b/packfile-list.h new file mode 100644 index 00000000000000..1b05e2aa36de08 --- /dev/null +++ b/packfile-list.h @@ -0,0 +1,28 @@ +#ifndef PACKFILE_LIST_H +#define PACKFILE_LIST_H + +struct object_id; + +struct packfile_list { + struct packfile_list_entry *head, *tail; +}; + +struct packfile_list_entry { + struct packfile_list_entry *next; + struct packed_git *pack; +}; + +void packfile_list_clear(struct packfile_list *list); +void packfile_list_remove(struct packfile_list *list, struct packed_git *pack); +void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack); +void packfile_list_append(struct packfile_list *list, struct packed_git *pack); + +/* + * Find the pack within the "packs" list whose index contains the object + * "oid". For general object lookups, you probably don't want this; use + * find_pack_entry() instead. + */ +struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, + const struct object_id *oid); + +#endif diff --git a/packfile.c b/packfile.c index 78c389e6f35e22..1d1b23b6cc782f 100644 --- a/packfile.c +++ b/packfile.c @@ -8,7 +8,6 @@ #include "pack.h" #include "repository.h" #include "dir.h" -#include "mergesort.h" #include "packfile.h" #include "delta.h" #include "hash-lookup.h" @@ -48,89 +47,6 @@ static size_t pack_mapped; #define SZ_FMT PRIuMAX static inline uintmax_t sz_fmt(size_t s) { return s; } -void packfile_list_clear(struct packfile_list *list) -{ - struct packfile_list_entry *e, *next; - - for (e = list->head; e; e = next) { - next = e->next; - free(e); - } - - list->head = list->tail = NULL; -} - -static struct packfile_list_entry *packfile_list_remove_internal(struct packfile_list *list, - struct packed_git *pack) -{ - struct packfile_list_entry *e, *prev; - - for (e = list->head, prev = NULL; e; prev = e, e = e->next) { - if (e->pack != pack) - continue; - - if (prev) - prev->next = e->next; - if (list->head == e) - list->head = e->next; - if (list->tail == e) - list->tail = prev; - - return e; - } - - return NULL; -} - -void packfile_list_remove(struct packfile_list *list, struct packed_git *pack) -{ - free(packfile_list_remove_internal(list, pack)); -} - -void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack) -{ - struct packfile_list_entry *entry; - - entry = packfile_list_remove_internal(list, pack); - if (!entry) { - entry = xmalloc(sizeof(*entry)); - entry->pack = pack; - } - entry->next = list->head; - - list->head = entry; - if (!list->tail) - list->tail = entry; -} - -void packfile_list_append(struct packfile_list *list, struct packed_git *pack) -{ - struct packfile_list_entry *entry; - - entry = packfile_list_remove_internal(list, pack); - if (!entry) { - entry = xmalloc(sizeof(*entry)); - entry->pack = pack; - } - entry->next = NULL; - - if (list->tail) { - list->tail->next = entry; - list->tail = entry; - } else { - list->head = list->tail = entry; - } -} - -struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, - const struct object_id *oid) -{ - for (; packs; packs = packs->next) - if (find_pack_entry_one(oid, packs->pack)) - return packs->pack; - return NULL; -} - void pack_report(struct repository *repo) { fprintf(stderr, @@ -859,7 +775,7 @@ struct packed_git *add_packed_git(struct repository *r, const char *path, return p; } -void packfile_store_add_pack(struct packfile_store *store, +void packfile_store_add_pack(struct odb_source_packed *store, struct packed_git *pack) { if (pack->pack_fd != -1) @@ -869,7 +785,7 @@ void packfile_store_add_pack(struct packfile_store *store, strmap_put(&store->packs_by_path, pack->pack_name, pack); } -struct packed_git *packfile_store_load_pack(struct packfile_store *store, +struct packed_git *packfile_store_load_pack(struct odb_source_packed *store, const char *idx_path, int local) { struct strbuf key = STRBUF_INIT; @@ -885,7 +801,7 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store, p = strmap_get(&store->packs_by_path, key.buf); if (!p) { - p = add_packed_git(store->source->odb->repo, idx_path, + p = add_packed_git(store->base.odb->repo, idx_path, strlen(idx_path), local); if (p) packfile_store_add_pack(store, p); @@ -895,52 +811,6 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store, return p; } -void (*report_garbage)(unsigned seen_bits, const char *path); - -static void report_helper(const struct string_list *list, - int seen_bits, int first, int last) -{ - if (seen_bits == (PACKDIR_FILE_PACK|PACKDIR_FILE_IDX)) - return; - - for (; first < last; first++) - report_garbage(seen_bits, list->items[first].string); -} - -static void report_pack_garbage(struct string_list *list) -{ - int i, baselen = -1, first = 0, seen_bits = 0; - - if (!report_garbage) - return; - - string_list_sort(list); - - for (i = 0; i < list->nr; i++) { - const char *path = list->items[i].string; - if (baselen != -1 && - strncmp(path, list->items[first].string, baselen)) { - report_helper(list, seen_bits, first, i); - baselen = -1; - seen_bits = 0; - } - if (baselen == -1) { - const char *dot = strrchr(path, '.'); - if (!dot) { - report_garbage(PACKDIR_FILE_GARBAGE, path); - continue; - } - baselen = dot - path + 1; - first = i; - } - if (!strcmp(path + baselen, "pack")) - seen_bits |= 1; - else if (!strcmp(path + baselen, "idx")) - seen_bits |= 2; - } - report_helper(list, seen_bits, first, list->nr); -} - void for_each_file_in_pack_subdir(const char *objdir, const char *subdir, each_file_in_pack_dir_fn fn, @@ -983,116 +853,9 @@ void for_each_file_in_pack_dir(const char *objdir, for_each_file_in_pack_subdir(objdir, NULL, fn, data); } -struct prepare_pack_data { - struct odb_source *source; - struct string_list *garbage; -}; - -static void prepare_pack(const char *full_name, size_t full_name_len, - const char *file_name, void *_data) -{ - struct prepare_pack_data *data = (struct prepare_pack_data *)_data; - struct odb_source_files *files = odb_source_files_downcast(data->source); - size_t base_len = full_name_len; - - if (strip_suffix_mem(full_name, &base_len, ".idx") && - !(files->packed->midx && - midx_contains_pack(files->packed->midx, file_name))) { - char *trimmed_path = xstrndup(full_name, full_name_len); - packfile_store_load_pack(files->packed, - trimmed_path, data->source->local); - free(trimmed_path); - } - - if (!report_garbage) - return; - - if (!strcmp(file_name, "multi-pack-index") || - !strcmp(file_name, "multi-pack-index.d")) - return; - if (starts_with(file_name, "multi-pack-index") && - (ends_with(file_name, ".bitmap") || ends_with(file_name, ".rev"))) - return; - if (ends_with(file_name, ".idx") || - ends_with(file_name, ".rev") || - ends_with(file_name, ".pack") || - ends_with(file_name, ".bitmap") || - ends_with(file_name, ".keep") || - ends_with(file_name, ".promisor") || - ends_with(file_name, ".mtimes")) - string_list_append(data->garbage, full_name); - else - report_garbage(PACKDIR_FILE_GARBAGE, full_name); -} - -static void prepare_packed_git_one(struct odb_source *source) +struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store) { - struct string_list garbage = STRING_LIST_INIT_DUP; - struct prepare_pack_data data = { - .source = source, - .garbage = &garbage, - }; - - for_each_file_in_pack_dir(source->path, prepare_pack, &data); - - report_pack_garbage(data.garbage); - string_list_clear(data.garbage, 0); -} - -DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next); - -static int sort_pack(const struct packfile_list_entry *a, - const struct packfile_list_entry *b) -{ - int st; - - /* - * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, - * remote ones could be on a network mounted filesystem. - * Favor local ones for these reasons. - */ - st = a->pack->pack_local - b->pack->pack_local; - if (st) - return -st; - - /* - * Younger packs tend to contain more recent objects, - * and more recent objects tend to get accessed more - * often. - */ - if (a->pack->mtime < b->pack->mtime) - return 1; - else if (a->pack->mtime == b->pack->mtime) - return 0; - return -1; -} - -void packfile_store_prepare(struct packfile_store *store) -{ - if (store->initialized) - return; - - prepare_multi_pack_index_one(store->source); - prepare_packed_git_one(store->source); - - sort_packs(&store->packs.head, sort_pack); - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) - if (!e->next) - store->packs.tail = e; - - store->initialized = true; -} - -void packfile_store_reprepare(struct packfile_store *store) -{ - store->initialized = false; - packfile_store_prepare(store); -} - -struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store) -{ - packfile_store_prepare(store); + odb_source_packed_prepare(store); if (store->midx) { struct multi_pack_index *m = store->midx; @@ -1103,37 +866,6 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor return store->packs.head; } -int packfile_store_count_objects(struct packfile_store *store, - enum odb_count_objects_flags flags UNUSED, - unsigned long *out) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - unsigned long count = 0; - int ret; - - m = get_multi_pack_index(store->source); - if (m) - count += m->num_objects + m->num_objects_in_base; - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - if (open_pack_index(e->pack)) { - ret = -1; - goto out; - } - - count += e->pack->num_objects; - } - - *out = count; - ret = 0; - -out: - return ret; -} - unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep) { @@ -1592,8 +1324,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, hashmap_add(&delta_base_cache, &ent->ent); } -static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset, - uint32_t *maybe_index_pos, struct object_info *oi) +int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset, + uint32_t *maybe_index_pos, struct object_info *oi) { struct pack_window *w_curs = NULL; size_t size; @@ -2118,9 +1850,9 @@ int is_pack_valid(struct packed_git *p) return !open_packed_git(p); } -static int fill_pack_entry(const struct object_id *oid, - struct pack_entry *e, - struct packed_git *p) +int packfile_fill_entry(struct packed_git *p, + const struct object_id *oid, + struct pack_entry *e) { off_t offset; @@ -2146,81 +1878,7 @@ static int fill_pack_entry(const struct object_id *oid, return 1; } -static int find_pack_entry(struct packfile_store *store, - const struct object_id *oid, - struct pack_entry *e) -{ - struct packfile_list_entry *l; - - packfile_store_prepare(store); - if (store->midx && fill_midx_entry(store->midx, oid, e)) - return 1; - - for (l = store->packs.head; l; l = l->next) { - struct packed_git *p = l->pack; - - if (!p->multi_pack_index && fill_pack_entry(oid, e, p)) { - if (!store->skip_mru_updates) - packfile_list_prepend(&store->packs, p); - return 1; - } - } - - return 0; -} - -int packfile_store_freshen_object(struct packfile_store *store, - const struct object_id *oid) -{ - struct pack_entry e; - if (!find_pack_entry(store, oid, &e)) - return 0; - if (e.p->is_cruft) - return 0; - if (e.p->freshened) - return 1; - if (utime(e.p->pack_name, NULL)) - return 0; - e.p->freshened = 1; - return 1; -} - -int packfile_store_read_object_info(struct packfile_store *store, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags) -{ - struct pack_entry e; - int ret; - - /* - * In case the first read didn't surface the object, we have to reload - * packfiles. This may cause us to discover new packfiles that have - * been added since the last time we have prepared the packfile store. - */ - if (flags & OBJECT_INFO_SECOND_READ) - packfile_store_reprepare(store); - - if (!find_pack_entry(store, oid, &e)) - return 1; - - /* - * We know that the caller doesn't actually need the - * information below, so return early. - */ - if (!oi) - return 0; - - ret = packed_object_info(e.p, e.offset, oi); - if (ret < 0) { - mark_bad_packed_object(e.p, oid); - return -1; - } - - return 0; -} - -static void maybe_invalidate_kept_pack_cache(struct packfile_store *store, +static void maybe_invalidate_kept_pack_cache(struct odb_source_packed *store, unsigned flags) { if (!store->kept_cache.packs) @@ -2231,7 +1889,7 @@ static void maybe_invalidate_kept_pack_cache(struct packfile_store *store, store->kept_cache.flags = 0; } -struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store, +struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store, unsigned flags) { maybe_invalidate_kept_pack_cache(store, flags); @@ -2272,14 +1930,12 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *st int has_object_pack(struct repository *r, const struct object_id *oid) { struct odb_source *source; - struct pack_entry e; odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); - int ret = find_pack_entry(files->packed, oid, &e); - if (ret) - return ret; + if (!odb_source_read_object_info(&files->packed->base, oid, NULL, 0)) + return 1; } return 0; @@ -2299,7 +1955,7 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid, for (; *cache; cache++) { struct packed_git *p = *cache; - if (fill_pack_entry(oid, &e, p)) + if (packfile_fill_entry(p, oid, &e)) return 1; } } @@ -2351,373 +2007,6 @@ int for_each_object_in_pack(struct packed_git *p, return r; } -struct packfile_store_for_each_object_wrapper_data { - struct packfile_store *store; - const struct object_info *request; - odb_for_each_object_cb cb; - void *cb_data; -}; - -static int packfile_store_for_each_object_wrapper(const struct object_id *oid, - struct packed_git *pack, - uint32_t index_pos, - void *cb_data) -{ - struct packfile_store_for_each_object_wrapper_data *data = cb_data; - - if (data->request) { - off_t offset = nth_packed_object_offset(pack, index_pos); - struct object_info oi = *data->request; - - if (packed_object_info_with_index_pos(pack, offset, - &index_pos, &oi) < 0) { - mark_bad_packed_object(pack, oid); - return -1; - } - - return data->cb(oid, &oi, data->cb_data); - } else { - return data->cb(oid, NULL, data->cb_data); - } -} - -static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) -{ - do { - if (*a != *b) - return 0; - a++; - b++; - len -= 2; - } while (len > 1); - if (len) - if ((*a ^ *b) & 0xf0) - return 0; - return 1; -} - -static int for_each_prefixed_object_in_midx( - struct packfile_store *store, - struct multi_pack_index *m, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - int ret; - - for (; m; m = m->base_midx) { - uint32_t num, i, first = 0; - int len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ? - m->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - - bsearch_one_midx(opts->prefix, m, &first); - - /* - * At this point, "first" is the location of the lowest - * object with an object name that could match "opts->prefix". - * See if we have 0, 1 or more objects that actually match(es). - */ - for (i = first; i < num; i++) { - const struct object_id *current = NULL; - struct object_id oid; - - current = nth_midxed_object_oid(&oid, m, i); - - if (!match_hash(len, opts->prefix->hash, current->hash)) - break; - - if (data->request) { - struct object_info oi = *data->request; - - ret = packfile_store_read_object_info(store, current, - &oi, 0); - if (ret) - goto out; - - ret = data->cb(&oid, &oi, data->cb_data); - if (ret) - goto out; - } else { - ret = data->cb(&oid, NULL, data->cb_data); - if (ret) - goto out; - } - } - } - - ret = 0; - -out: - return ret; -} - -static int for_each_prefixed_object_in_pack( - struct packfile_store *store, - struct packed_git *p, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - uint32_t num, i, first = 0; - int len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ? - p->repo->hash_algo->hexsz : opts->prefix_hex_len; - int ret; - - num = p->num_objects; - bsearch_pack(opts->prefix, p, &first); - - /* - * At this point, "first" is the location of the lowest object - * with an object name that could match "bin_pfx". See if we have - * 0, 1 or more objects that actually match(es). - */ - for (i = first; i < num; i++) { - struct object_id oid; - - nth_packed_object_id(&oid, p, i); - if (!match_hash(len, opts->prefix->hash, oid.hash)) - break; - - if (data->request) { - struct object_info oi = *data->request; - - ret = packfile_store_read_object_info(store, &oid, &oi, 0); - if (ret) - goto out; - - ret = data->cb(&oid, &oi, data->cb_data); - if (ret) - goto out; - } else { - ret = data->cb(&oid, NULL, data->cb_data); - if (ret) - goto out; - } - } - - ret = 0; - -out: - return ret; -} - -static int packfile_store_for_each_prefixed_object( - struct packfile_store *store, - const struct odb_for_each_object_options *opts, - struct packfile_store_for_each_object_wrapper_data *data) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - bool pack_errors = false; - int ret; - - if (opts->flags) - BUG("flags unsupported"); - - store->skip_mru_updates = true; - - m = get_multi_pack_index(store->source); - if (m) { - ret = for_each_prefixed_object_in_midx(store, m, opts, data); - if (ret) - goto out; - } - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - - if (open_pack_index(e->pack)) { - pack_errors = true; - continue; - } - - if (!e->pack->num_objects) - continue; - - ret = for_each_prefixed_object_in_pack(store, e->pack, opts, data); - if (ret) - goto out; - } - - ret = 0; - -out: - store->skip_mru_updates = false; - if (!ret && pack_errors) - ret = -1; - return ret; -} - -int packfile_store_for_each_object(struct packfile_store *store, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts) -{ - struct packfile_store_for_each_object_wrapper_data data = { - .store = store, - .request = request, - .cb = cb, - .cb_data = cb_data, - }; - struct packfile_list_entry *e; - int pack_errors = 0, ret; - - if (opts->prefix) - return packfile_store_for_each_prefixed_object(store, opts, &data); - - store->skip_mru_updates = true; - - for (e = packfile_store_get_packs(store); e; e = e->next) { - struct packed_git *p = e->pack; - - if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) && - !p->pack_promisor) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && - p->pack_keep_in_core) - continue; - if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && - p->pack_keep) - continue; - if (open_pack_index(p)) { - pack_errors = 1; - continue; - } - - ret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper, - &data, opts->flags); - if (ret) - goto out; - } - - ret = 0; - -out: - store->skip_mru_updates = false; - - if (!ret && pack_errors) - ret = -1; - return ret; -} - -static int extend_abbrev_len(const struct object_id *a, - const struct object_id *b, - unsigned *out) -{ - unsigned len = oid_common_prefix_hexlen(a, b); - if (len != hash_algos[a->algo].hexsz && len >= *out) - *out = len + 1; - return 0; -} - -static void find_abbrev_len_for_midx(struct multi_pack_index *m, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - unsigned len = min_len; - - for (; m; m = m->base_midx) { - int match = 0; - uint32_t num, first = 0; - struct object_id found_oid; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - match = bsearch_one_midx(oid, m, &first); - - /* - * first is now the position in the packfile where we - * would insert the object ID if it does not exist (or the - * position of the object ID if it does exist). Hence, we - * consider a maximum of two objects nearby for the - * abbreviation length. - */ - - if (!match) { - if (nth_midxed_object_oid(&found_oid, m, first)) - extend_abbrev_len(&found_oid, oid, &len); - } else if (first < num - 1) { - if (nth_midxed_object_oid(&found_oid, m, first + 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - if (first > 0) { - if (nth_midxed_object_oid(&found_oid, m, first - 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - } - - *out = len; -} - -static void find_abbrev_len_for_pack(struct packed_git *p, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - int match; - uint32_t num, first = 0; - struct object_id found_oid; - unsigned len = min_len; - - num = p->num_objects; - match = bsearch_pack(oid, p, &first); - - /* - * first is now the position in the packfile where we would insert - * the object ID if it does not exist (or the position of mad->hash if - * it does exist). Hence, we consider a maximum of two objects - * nearby for the abbreviation length. - */ - if (!match) { - if (!nth_packed_object_id(&found_oid, p, first)) - extend_abbrev_len(&found_oid, oid, &len); - } else if (first < num - 1) { - if (!nth_packed_object_id(&found_oid, p, first + 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - if (first > 0) { - if (!nth_packed_object_id(&found_oid, p, first - 1)) - extend_abbrev_len(&found_oid, oid, &len); - } - - *out = len; -} - -int packfile_store_find_abbrev_len(struct packfile_store *store, - const struct object_id *oid, - unsigned min_len, - unsigned *out) -{ - struct packfile_list_entry *e; - struct multi_pack_index *m; - - m = get_multi_pack_index(store->source); - if (m) - find_abbrev_len_for_midx(m, oid, min_len, &min_len); - - for (e = packfile_store_get_packs(store); e; e = e->next) { - if (e->pack->multi_pack_index) - continue; - if (open_pack_index(e->pack) || !e->pack->num_objects) - continue; - - find_abbrev_len_for_pack(e->pack, oid, min_len, &min_len); - } - - *out = min_len; - return 0; -} - struct add_promisor_object_data { struct repository *repo; struct oidset *set; @@ -2818,37 +2107,6 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l return 0; } -struct packfile_store *packfile_store_new(struct odb_source *source) -{ - struct packfile_store *store; - CALLOC_ARRAY(store, 1); - store->source = source; - strmap_init(&store->packs_by_path); - return store; -} - -void packfile_store_free(struct packfile_store *store) -{ - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) - free(e->pack); - packfile_list_clear(&store->packs); - - strmap_clear(&store->packs_by_path, 0); - free(store); -} - -void packfile_store_close(struct packfile_store *store) -{ - for (struct packfile_list_entry *e = store->packs.head; e; e = e->next) { - if (e->pack->do_not_close) - BUG("want to close pack marked 'do-not-close'"); - close_pack(e->pack); - } - if (store->midx) - close_midx(store->midx); - store->midx = NULL; -} - struct odb_packed_read_stream { struct odb_read_stream base; struct packed_git *pack; @@ -2972,15 +2230,3 @@ int packfile_read_object_stream(struct odb_read_stream **out, return 0; } - -int packfile_store_read_object_stream(struct odb_read_stream **out, - struct packfile_store *store, - const struct object_id *oid) -{ - struct pack_entry e; - - if (!find_pack_entry(store, oid, &e)) - return -1; - - return packfile_read_object_stream(out, oid, e.p, e.offset); -} diff --git a/packfile.h b/packfile.h index defb6f442cca09..2329a697014a66 100644 --- a/packfile.h +++ b/packfile.h @@ -5,9 +5,10 @@ #include "object.h" #include "odb.h" #include "odb/source-files.h" +#include "odb/source-packed.h" #include "oidset.h" +#include "packfile-list.h" #include "repository.h" -#include "strmap.h" /* in odb.h */ struct object_info; @@ -54,133 +55,18 @@ struct packed_git { char pack_name[FLEX_ARRAY]; /* more */ }; -struct packfile_list { - struct packfile_list_entry *head, *tail; -}; - -struct packfile_list_entry { - struct packfile_list_entry *next; - struct packed_git *pack; -}; - -void packfile_list_clear(struct packfile_list *list); -void packfile_list_remove(struct packfile_list *list, struct packed_git *pack); -void packfile_list_prepend(struct packfile_list *list, struct packed_git *pack); -void packfile_list_append(struct packfile_list *list, struct packed_git *pack); - -/* - * Find the pack within the "packs" list whose index contains the object - * "oid". For general object lookups, you probably don't want this; use - * find_pack_entry() instead. - */ -struct packed_git *packfile_list_find_oid(struct packfile_list_entry *packs, - const struct object_id *oid); - -/* - * A store that manages packfiles for a given object database. - */ -struct packfile_store { - struct odb_source *source; - - /* - * The list of packfiles in the order in which they have been most - * recently used. - */ - struct packfile_list packs; - - /* - * Cache of packfiles which are marked as "kept", either because there - * is an on-disk ".keep" file or because they are marked as "kept" in - * memory. - * - * Should not be accessed directly, but via - * `packfile_store_get_kept_pack_cache()`. The list of packs gets - * invalidated when the stored flags and the flags passed to - * `packfile_store_get_kept_pack_cache()` mismatch. - */ - struct { - struct packed_git **packs; - unsigned flags; - } kept_cache; - - /* The multi-pack index that belongs to this specific packfile store. */ - struct multi_pack_index *midx; - - /* - * A map of packfile names to packed_git structs for tracking which - * packs have been loaded already. - */ - struct strmap packs_by_path; - - /* - * Whether packfiles have already been populated with this store's - * packs. - */ - bool initialized; - - /* - * Usually, packfiles will be reordered to the front of the `packs` - * list whenever an object is looked up via them. This has the effect - * that packs that contain a lot of accessed objects will be located - * towards the front. - * - * This is usually desirable, but there are exceptions. One exception - * is when the looking up multiple objects in a loop for each packfile. - * In that case, we may easily end up with an infinite loop as the - * packfiles get reordered to the front repeatedly. - * - * Setting this field to `true` thus disables these reorderings. - */ - bool skip_mru_updates; -}; - -/* - * Allocate and initialize a new empty packfile store for the given object - * database source. - */ -struct packfile_store *packfile_store_new(struct odb_source *source); - -/* - * Free the packfile store and all its associated state. All packfiles - * tracked by the store will be closed. - */ -void packfile_store_free(struct packfile_store *store); - -/* - * Close all packfiles associated with this store. The packfiles won't be - * free'd, so they can be re-opened at a later point in time. - */ -void packfile_store_close(struct packfile_store *store); - -/* - * Prepare the packfile store by loading packfiles and multi-pack indices for - * all alternates. This becomes a no-op if the store is already prepared. - * - * It shouldn't typically be necessary to call this function directly, as - * functions that access the store know to prepare it. - */ -void packfile_store_prepare(struct packfile_store *store); - -/* - * Clear the packfile caches and try to look up any new packfiles that have - * appeared since last preparing the packfiles store. - * - * This function must be called under the `odb_read_lock()`. - */ -void packfile_store_reprepare(struct packfile_store *store); - /* * Add the pack to the store so that contained objects become accessible via * the store. This moves ownership into the store. */ -void packfile_store_add_pack(struct packfile_store *store, +void packfile_store_add_pack(struct odb_source_packed *store, struct packed_git *pack); /* * Get all packs managed by the given store, including packfiles that are * referenced by multi-pack indices. */ -struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store); +struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store); struct repo_for_each_pack_data { struct odb_source *source; @@ -238,54 +124,26 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data * ((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \ repo_for_each_pack_data_next(&eack_pack_data)) -int packfile_store_read_object_stream(struct odb_read_stream **out, - struct packfile_store *store, - const struct object_id *oid); - -/* - * Try to read the object identified by its ID from the object store and - * populate the object info with its data. Returns 1 in case the object was - * not found, 0 if it was and read successfully, and a negative error code in - * case the object was corrupted. - */ -int packfile_store_read_object_info(struct packfile_store *store, - const struct object_id *oid, - struct object_info *oi, - enum object_info_flags flags); - /* * Open the packfile and add it to the store if it isn't yet known. Returns * either the newly opened packfile or the preexisting packfile. Returns a * `NULL` pointer in case the packfile could not be opened. */ -struct packed_git *packfile_store_load_pack(struct packfile_store *store, +struct packed_git *packfile_store_load_pack(struct odb_source_packed *store, const char *idx_path, int local); -int packfile_store_freshen_object(struct packfile_store *store, - const struct object_id *oid); - enum kept_pack_type { KEPT_PACK_ON_DISK = (1 << 0), KEPT_PACK_IN_CORE = (1 << 1), KEPT_PACK_IN_CORE_OPEN = (1 << 2), }; -/* - * Count the number objects contained in the given packfile store. If - * successful, the number of objects will be written to the `out` pointer. - * - * Return 0 on success, a negative error code otherwise. - */ -int packfile_store_count_objects(struct packfile_store *store, - enum odb_count_objects_flags flags, - unsigned long *out); - /* * Retrieve the cache of kept packs from the given packfile store. Accepts a * combination of `kept_pack_type` flags. The cache is computed on demand and * will be recomputed whenever the flags change. */ -struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store, +struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store, unsigned flags); struct pack_window { @@ -356,26 +214,6 @@ int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn, void *data, enum odb_for_each_object_flags flags); -/* - * Iterate through all packed objects in the given packfile store and invoke - * the callback function for each of them. If an object info request is given, - * then the object info will be read for every individual object and passed to - * the callback as if `packfile_store_read_object_info()` was called for the - * object. - * - * The flags parameter is a combination of `odb_for_each_object_flags`. - */ -int packfile_store_for_each_object(struct packfile_store *store, - const struct object_info *request, - odb_for_each_object_cb cb, - void *cb_data, - const struct odb_for_each_object_options *opts); - -int packfile_store_find_abbrev_len(struct packfile_store *store, - const struct object_id *oid, - unsigned min_len, - unsigned *out); - /* A hook to report invalid files in pack directory */ #define PACKDIR_FILE_PACK 1 #define PACKDIR_FILE_IDX 2 @@ -454,6 +292,10 @@ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n); */ off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *); +int packfile_fill_entry(struct packed_git *p, + const struct object_id *oid, + struct pack_entry *e); + int is_pack_valid(struct packed_git *); void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, size_t *); @@ -480,6 +322,8 @@ extern int do_check_packed_object_crc; */ int packed_object_info(struct packed_git *pack, off_t offset, struct object_info *); +int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_offset, + uint32_t *maybe_index_pos, struct object_info *oi); void mark_bad_packed_object(struct packed_git *, const struct object_id *); const struct packed_git *has_packed_and_bad(struct repository *, const struct object_id *); diff --git a/path.c b/path.c index d7e17bf17404de..c3a709a9284b7f 100644 --- a/path.c +++ b/path.c @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename) return NULL; } +void format_path(struct strbuf *dest, const char *path, + const char *prefix, enum path_format format) +{ + strbuf_reset(dest); + + switch (format) { + case PATH_FORMAT_UNMODIFIED: + strbuf_addstr(dest, path); + break; + + case PATH_FORMAT_RELATIVE: { + struct strbuf relative_buf = STRBUF_INIT; + struct strbuf real_path = STRBUF_INIT; + struct strbuf real_prefix = STRBUF_INIT; + char *cwd = NULL; + + /* + * We don't ever produce a relative path if prefix is NULL, + * so set the prefix to the current directory so that we can + * produce a relative path whenever possible. + */ + if (!prefix) + prefix = cwd = xgetcwd(); + + if (!is_absolute_path(path)) { + strbuf_realpath_forgiving(&real_path, path, 1); + path = real_path.buf; + } + if (!is_absolute_path(prefix)) { + strbuf_realpath_forgiving(&real_prefix, prefix, 1); + prefix = real_prefix.buf; + } + + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf)); + + strbuf_release(&relative_buf); + strbuf_release(&real_path); + strbuf_release(&real_prefix); + free(cwd); + break; + } + + case PATH_FORMAT_RELATIVE_IF_SHARED: { + struct strbuf relative_buf = STRBUF_INIT; + + /* + * If we're using RELATIVE_IF_SHARED mode, then we want an + * absolute path unless the two share a common prefix, so don't + * default the prefix to the current working directory. Doing so + * would cause a relative path to always be produced if possible. + */ + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf)); + strbuf_release(&relative_buf); + break; + } + + case PATH_FORMAT_CANONICAL: + /* + * strbuf_realpath_forgiving inherently resets the destination + * buffer, safely aligning with our replace semantics. + */ + strbuf_realpath_forgiving(dest, path, 1); + break; + + default: + BUG("unknown path_format value %d", format); + } +} + REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG") REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG") REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR") diff --git a/path.h b/path.h index 4c2958a9037179..7e7408dd05fce3 100644 --- a/path.h +++ b/path.h @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path); int safe_create_file_with_leading_directories(struct repository *repo, const char *path); +/** + * The formatting strategy to apply when writing a path into a buffer. + */ +enum path_format { + /* Output the path exactly as-is without any modifications. */ + PATH_FORMAT_UNMODIFIED, + + /* Output a path relative to the provided directory prefix. */ + PATH_FORMAT_RELATIVE, + + /* Output a relative path only if the path shares a root with the prefix. */ + PATH_FORMAT_RELATIVE_IF_SHARED, + + /* Output a fully resolved, absolute canonical path. */ + PATH_FORMAT_CANONICAL +}; + +/** + * Format a path according to the specified formatting strategy and store + * the result in the given strbuf, replacing any existing contents. + * + * `dest` : The string buffer to store the formatted path into. + * `path` : The path string that needs to be formatted. + * `prefix` : The directory prefix to calculate relative offsets against. + * Pass NULL to default to the current working directory where applicable. + * `format` : The formatting behavior rule to execute. + */ +void format_path(struct strbuf *dest, const char *path, + const char *prefix, enum path_format format); + # ifdef USE_THE_REPOSITORY_VARIABLE # include "strbuf.h" # include "repository.h" diff --git a/promisor-remote.c b/promisor-remote.c index f34856d499f621..43505d1e1ac8fd 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -12,7 +12,9 @@ #include "packfile.h" #include "environment.h" #include "url.h" +#include "urlmatch.h" #include "version.h" +#include "wildmatch.h" struct promisor_remote_config { struct promisor_remote *promisors; @@ -434,13 +436,14 @@ static struct string_list *fields_stored(void) * Struct for promisor remotes involved in the "promisor-remote" * protocol capability. * - * Except for "name", each in this struct and its - * should correspond (either on the client side or on the server side) - * to a "remote.." config variable set to where - * "" is a promisor remote name. + * Except for "name" and "local_name", each in this struct + * and its should correspond (either on the client side or on + * the server side) to a "remote.." config variable set + * to where "" is a promisor remote name. */ struct promisor_info { - const char *name; + const char *name; /* name the server advertised */ + const char *local_name; /* name used locally (may be auto-generated) */ const char *url; const char *filter; const char *token; @@ -449,6 +452,7 @@ struct promisor_info { static void promisor_info_free(struct promisor_info *p) { free((char *)p->name); + free((char *)p->local_name); free((char *)p->url); free((char *)p->filter); free((char *)p->token); @@ -462,6 +466,11 @@ static void promisor_info_list_clear(struct string_list *list) string_list_clear(list, 0); } +static const char *promisor_info_local_name(struct promisor_info *p) +{ + return p->local_name ? p->local_name : p->name; +} + static void set_one_field(struct promisor_info *p, const char *field, const char *value) { @@ -650,9 +659,351 @@ static bool has_control_char(const char *s) return false; } -static int should_accept_remote(enum accept_promisor accept, +struct allowed_url { + char *remote_name; + char *url_pattern; + struct url_info pattern_info; +}; + +static void allowed_url_free(void *util, const char *str UNUSED) +{ + struct allowed_url *allowed = util; + + if (!allowed) + return; + + /* Depending on prefix, free either remote_name or url_pattern */ + free(allowed->remote_name ? allowed->remote_name : allowed->url_pattern); + free(allowed->pattern_info.url); + free(allowed); +} + +static struct allowed_url *valid_accept_url(const char *url) +{ + char *dup, *p; + struct allowed_url *allowed; + + if (!url) + return NULL; + + dup = xstrdup(url); + p = strchr(dup, '='); + if (p) { + *p = '\0'; + if (!valid_remote_name(dup)) { + warning(_("invalid remote name '%s' before '=' sign " + "in '%s' from promisor.acceptFromServerUrl config"), + dup, url); + free(dup); + return NULL; + } + p++; + } else { + p = dup; + } + + if (has_control_char(p)) { + warning(_("invalid url pattern '%s' " + "in '%s' from promisor.acceptFromServerUrl config"), p, url); + free(dup); + return NULL; + } + + allowed = xmalloc(sizeof(*allowed)); + allowed->remote_name = (p == dup) ? NULL : dup; + allowed->url_pattern = p; + allowed->pattern_info.url = url_normalize_pattern(p, &allowed->pattern_info); + if (!allowed->pattern_info.url) { + warning(_("invalid url pattern '%s' " + "in '%s' from promisor.acceptFromServerUrl config"), p, url); + free(dup); + free(allowed); + return NULL; + } + + return allowed; +} + +static void load_accept_from_server_url(struct repository *repo, + struct string_list *accept_urls) +{ + const struct string_list *config_urls; + + if (!repo_config_get_string_multi(repo, "promisor.acceptfromserverurl", &config_urls)) { + struct string_list_item *item; + + for_each_string_list_item(item, config_urls) { + struct allowed_url *allowed = valid_accept_url(item->string); + if (allowed) { + struct string_list_item *new; + new = string_list_append(accept_urls, item->string); + new->util = allowed; + } + } + } +} + +static bool match_pattern_url(const char *pat, size_t pat_len, + const char *url, size_t url_len) +{ + char *p_str = xstrndup(pat, pat_len); + char *u_str = xstrndup(url, url_len); + bool res = !wildmatch(p_str, u_str, 0); + + free(p_str); + free(u_str); + + return res; +} + +static bool match_one_url(const struct url_info *pi, const struct url_info *ui) +{ + const char *pat = pi->url; + const char *url = ui->url; + + /* + * Schemes must match exactly. They are case-folded by + * url_normalize(), so strncmp() suffices. + */ + if (pi->scheme_len != ui->scheme_len || strncmp(pat, url, pi->scheme_len)) + return false; + + /* + * Ports must match exactly. url_normalize() strips default + * ports (like 443 for https), so length and content + * comparisons are sufficient. + */ + if (pi->port_len != ui->port_len || + strncmp(pat + pi->port_off, url + ui->port_off, pi->port_len)) + return false; + + /* + * Match host and path separately to prevent a '*' in the host + * portion of the pattern from matching across the '/' + * boundary into the path. + */ + + return match_pattern_url(pat + pi->host_off, pi->host_len, + url + ui->host_off, ui->host_len) && + match_pattern_url(pat + pi->path_off, pi->path_len, + url + ui->path_off, ui->path_len); +} + +static struct allowed_url *url_matches_accept_list( + struct string_list *accept_urls, const char *url) +{ + struct string_list_item *item; + struct url_info url_info; + + url_info.url = url_normalize(url, &url_info); + + if (!url_info.url) + return NULL; + + for_each_string_list_item(item, accept_urls) { + struct allowed_url *allowed = item->util; + + if (match_one_url(&allowed->pattern_info, &url_info)) { + free(url_info.url); + return allowed; + } + } + + free(url_info.url); + return NULL; +} + +/* + * Sanitize the buffer to make it a valid remote name coming from the + * server by: + * + * - replacing any non alphanumeric character with a '-' + * - stripping any leading '-', + * - condensing multiple '-' into one, + * - prepending "promisor-auto-", + * - validating the result. + */ +static int sanitize_remote_name(struct strbuf *buf, const char *url) +{ + char prev = '-'; + for (size_t i = 0; i < buf->len; ) { + if (!isalnum(buf->buf[i])) + buf->buf[i] = '-'; + if (prev == '-' && buf->buf[i] == '-') { + strbuf_remove(buf, i, 1); + } else { + prev = buf->buf[i]; + i++; + } + } + + strbuf_strip_suffix(buf, "-"); + + if (!buf->len) { + warning(_("couldn't generate a valid remote name from " + "advertised url '%s', ignoring this remote"), url); + return -1; + } + + strbuf_insertstr(buf, 0, "promisor-auto-"); + + if (!valid_remote_name(buf->buf)) { + warning(_("generated remote name '%s' from advertised url '%s' " + "is invalid, ignoring this remote"), buf->buf, url); + return -1; + } + + return 0; +} + +static char *promisor_remote_name_from_url(const char *url) +{ + struct url_info url_info = { 0 }; + char *normalized = url_normalize(url, &url_info); + struct strbuf buf = STRBUF_INIT; + + if (!normalized) { + warning(_("couldn't normalize advertised url '%s', " + "ignoring this remote"), url); + return NULL; + } + + if (url_info.host_len) { + strbuf_add(&buf, normalized + url_info.host_off, url_info.host_len); + strbuf_addch(&buf, '-'); + } + + if (url_info.port_len) { + strbuf_add(&buf, normalized + url_info.port_off, url_info.port_len); + strbuf_addch(&buf, '-'); + } + + if (url_info.path_len) { + strbuf_add(&buf, normalized + url_info.path_off, url_info.path_len); + strbuf_trim_trailing_dir_sep(&buf); + strbuf_strip_suffix(&buf, ".git"); + } + + free(normalized); + + if (sanitize_remote_name(&buf, url)) { + strbuf_release(&buf); + return NULL; + } + + return strbuf_detach(&buf, NULL); +} + +static void configure_auto_promisor_remote(struct repository *repo, + const char *name, + const char *url, + const char *advertised_as, + bool reuse) +{ + char *key; + + if (!reuse) { + fprintf(stderr, _("Auto-creating promisor remote '%s' for URL '%s'\n"), + name, url); + + key = xstrfmt("remote.%s.url", name); + repo_config_set_gently(repo, key, url); + free(key); + } + + /* NB: when reusing, this promotes an existing non-promisor remote */ + key = xstrfmt("remote.%s.promisor", name); + repo_config_set_gently(repo, key, "true"); + free(key); + + if (advertised_as) { + key = xstrfmt("remote.%s.advertisedAs", name); + repo_config_set_gently(repo, key, advertised_as); + free(key); + } +} + +#define MAX_REMOTES_WITH_SIMILAR_NAMES 20 + +/* Return the allocated local name, or NULL on failure */ +static char *handle_matching_allowed_url(struct repository *repo, + char *allowed_name, + const char *remote_url, + const char *remote_name) +{ + char *name; + char *basename = allowed_name ? + xstrdup(allowed_name) : + promisor_remote_name_from_url(remote_url); + int i = 0; + bool reuse = false; + + if (!basename) + return NULL; + + name = xstrdup(basename); + + while (i < MAX_REMOTES_WITH_SIMILAR_NAMES) { + char *url_key = xstrfmt("remote.%s.url", name); + const char *existing_url; + int exists = !repo_config_get_string_tmp(repo, url_key, &existing_url); + + free(url_key); + + if (!exists) + break; /* Free to use */ + + if (!strcmp(existing_url, remote_url)) { + reuse = true; + break; /* Same URL, so safe to reuse */ + } + + i++; + free(name); + name = xstrfmt("%s-%d", basename, i); + } + + if (i < MAX_REMOTES_WITH_SIMILAR_NAMES) { + configure_auto_promisor_remote(repo, name, + remote_url, remote_name, + reuse); + } else { + warning(_("too many remotes accepted with name like '%s-X', " + "ignoring this remote"), basename); + FREE_AND_NULL(name); + } + + free(basename); + return name; +} + +static int should_accept_new_remote_url(struct repository *repo, + struct string_list *accept_urls, + struct promisor_info *advertised) +{ + struct allowed_url *allowed = url_matches_accept_list(accept_urls, + advertised->url); + if (allowed) { + char *name = handle_matching_allowed_url(repo, + allowed->remote_name, + advertised->url, + advertised->name); + if (name) { + free((char *)advertised->local_name); + advertised->local_name = name; + return 1; + } + } + + return 0; +} + +static int should_accept_remote(struct repository *repo, + enum accept_promisor accept, struct promisor_info *advertised, - struct string_list *config_info) + struct string_list *accept_urls, + struct string_list *config_info, + bool *reload_config) { struct promisor_info *p; struct string_list_item *item; @@ -664,23 +1015,34 @@ static int should_accept_remote(enum accept_promisor accept, "this remote should have been rejected earlier", remote_name); - if (accept == ACCEPT_ALL) - return all_fields_match(advertised, config_info, NULL); - /* Get config info for that promisor remote */ item = string_list_lookup(config_info, remote_name); - if (!item) + if (!item) { /* We don't know about that remote */ + + int res = should_accept_new_remote_url(repo, accept_urls, advertised); + if (res) { + *reload_config = true; + return res; + } + + if (accept == ACCEPT_ALL) + return all_fields_match(advertised, config_info, NULL); return 0; + } p = item->util; - if (accept == ACCEPT_KNOWN_NAME) + /* Known remote in the allowlist? */ + if (!strcmp(p->url, remote_url) && url_matches_accept_list(accept_urls, remote_url)) return all_fields_match(advertised, config_info, p); - if (accept != ACCEPT_KNOWN_URL) - BUG("Unhandled 'enum accept_promisor' value '%d'", accept); + if (accept == ACCEPT_ALL) + return all_fields_match(advertised, config_info, NULL); + + if (accept == ACCEPT_KNOWN_NAME) + return all_fields_match(advertised, config_info, p); if (strcmp(p->url, remote_url)) { warning(_("known remote named '%s' but with URL '%s' instead of '%s', " @@ -689,7 +1051,13 @@ static int should_accept_remote(enum accept_promisor accept, return 0; } - return all_fields_match(advertised, config_info, p); + if (accept == ACCEPT_KNOWN_URL) + return all_fields_match(advertised, config_info, p); + + if (accept != ACCEPT_NONE) + BUG("Unhandled 'enum accept_promisor' value '%d'", accept); + + return 0; } static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) @@ -829,7 +1197,7 @@ static bool promisor_store_advertised_fields(struct promisor_info *advertised, { struct promisor_info *p; struct string_list_item *item; - const char *remote_name = advertised->name; + const char *remote_name = promisor_info_local_name(advertised); bool reload_config = false; if (!(store_info->store_filter || store_info->store_token)) @@ -894,8 +1262,12 @@ static void filter_promisor_remote(struct repository *repo, struct string_list_item *item; bool reload_config = false; enum accept_promisor accept = accept_from_server(repo); + struct string_list accept_urls = STRING_LIST_INIT_DUP; + + /* Load and validate the acceptFromServerUrl config */ + load_accept_from_server_url(repo, &accept_urls); - if (accept == ACCEPT_NONE) + if (accept == ACCEPT_NONE && !accept_urls.nr) return; /* Parse remote info received */ @@ -915,7 +1287,8 @@ static void filter_promisor_remote(struct repository *repo, string_list_sort(&config_info); } - if (should_accept_remote(accept, advertised, &config_info)) { + if (should_accept_remote(repo, accept, advertised, &accept_urls, + &config_info, &reload_config)) { if (!store_info) store_info = store_info_new(repo); if (promisor_store_advertised_fields(advertised, store_info)) @@ -927,6 +1300,7 @@ static void filter_promisor_remote(struct repository *repo, } } + string_list_clear_func(&accept_urls, allowed_url_free); promisor_info_list_clear(&config_info); string_list_clear(&remote_info, 0); store_info_free(store_info); @@ -937,7 +1311,8 @@ static void filter_promisor_remote(struct repository *repo, /* Apply accepted remotes to the stable repo state */ for_each_string_list_item(item, accepted_remotes) { struct promisor_info *info = item->util; - struct promisor_remote *r = repo_promisor_remote_find(repo, info->name); + const char *remote_name = promisor_info_local_name(info); + struct promisor_remote *r = repo_promisor_remote_find(repo, remote_name); if (r) { r->accepted = 1; diff --git a/read-cache.c b/read-cache.c index 21ca58beeaa28c..7c1cdcf696094f 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1002,7 +1002,7 @@ static enum verify_path_result verify_path_internal(const char *path, return PATH_OK; if (is_dir_sep(c)) { inside: - if (protect_hfs) { + if (repo_protect_hfs(the_repository)) { if (is_hfs_dotgit(path)) return PATH_INVALID; @@ -1011,7 +1011,7 @@ static enum verify_path_result verify_path_internal(const char *path, return PATH_INVALID; } } - if (protect_ntfs) { + if (repo_protect_ntfs(the_repository)) { #if defined GIT_WINDOWS_NATIVE || defined __CYGWIN__ if (c == '\\') return PATH_INVALID; @@ -1035,7 +1035,8 @@ static enum verify_path_result verify_path_internal(const char *path, if (c == '\0') return S_ISDIR(mode) ? PATH_DIR_WITH_SEP : PATH_INVALID; - } else if (c == '\\' && protect_ntfs) { + } else if (c == '\\' && + repo_protect_ntfs(the_repository)) { if (is_ntfs_dotgit(path)) return PATH_INVALID; if (S_ISLNK(mode)) { diff --git a/ref-filter.c b/ref-filter.c index 8ba91c72a11d39..284796c49b2986 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -3316,15 +3316,14 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for if (prefix) { struct ref_iterator *iter; + struct ref_store *store = get_main_ref_store(the_repository); - iter = refs_ref_iterator_begin(get_main_ref_store(the_repository), - "", NULL, 0, 0); - - if (filter->start_after) + if (filter->start_after) { + iter = refs_ref_iterator_begin(store, "", NULL, 0, 0); ret = start_ref_iterator_after(iter, filter->start_after); - else - ret = ref_iterator_seek(iter, prefix, - REF_ITERATOR_SEEK_SET_PREFIX); + } else { + iter = refs_ref_iterator_begin(store, prefix, NULL, 0, 0); + } if (!ret) ret = do_for_each_ref_iterator(iter, fn, cb_data); diff --git a/refs.c b/refs.c index d3caa9a633503f..1d2463789167c9 100644 --- a/refs.c +++ b/refs.c @@ -1053,6 +1053,15 @@ static char *normalize_reflog_message(const char *msg) return strbuf_detach(&sb, NULL); } +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value) +{ + if (value && !strcasecmp(value, "always")) + return LOG_REFS_ALWAYS; + else if (git_config_bool("core.logallrefupdates", value)) + return LOG_REFS_NORMAL; + return LOG_REFS_NONE; +} + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname) { @@ -2327,7 +2336,6 @@ static struct ref_store *ref_store_init(struct repository *repo, struct ref_store *refs; struct ref_store_init_options opts = { .access_flags = flags, - .log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo), }; be = find_ref_storage_backend(format); @@ -2351,15 +2359,22 @@ void ref_store_release(struct ref_store *ref_store) struct ref_store *get_main_ref_store(struct repository *r) { + static bool initializing; + if (r->refs_private) return r->refs_private; if (!r->gitdir) BUG("attempting to get main_ref_store outside of repository"); + if (initializing) + BUG("initialization of main ref store is recursing"); + initializing = true; r->refs_private = ref_store_init(r, r->ref_storage_format, r->gitdir, REF_STORE_ALL_CAPS); r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); + initializing = false; + return r->refs_private; } diff --git a/refs.h b/refs.h index 71d5c186d044bb..a381022c77065a 100644 --- a/refs.h +++ b/refs.h @@ -146,6 +146,15 @@ enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs, int refs_ref_exists(struct ref_store *refs, const char *refname); +enum log_refs_config { + LOG_REFS_UNSET = -1, + LOG_REFS_NONE = 0, + LOG_REFS_NORMAL, + LOG_REFS_ALWAYS +}; + +enum log_refs_config refs_parse_log_all_ref_updates_config(const char *value); + int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, const char *refname); diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787127d..3df56c25c8c585 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -84,12 +84,21 @@ struct files_ref_store { unsigned int store_flags; char *gitcommondir; - enum log_refs_config log_all_ref_updates; - int prefer_symlink_refs; - struct ref_cache *loose; - struct ref_store *packed_ref_store; + + /* + * Options used when writing references. These are parsed from the + * config lazily on first use via `files_ref_store_write_options()` so + * that we don't have to access the configuration when initializing the + * ref store. Do not access these fields directly, but use the accessor + * instead. + */ + struct files_ref_store_write_options { + enum log_refs_config log_all_ref_updates; + int prefer_symlink_refs; + bool initialized; + } write_opts_lazy_loaded; }; static void clear_loose_ref_cache(struct files_ref_store *refs) @@ -100,6 +109,52 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) } } +static void files_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct files_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir); + free(refs->gitcommondir); + refs->gitcommondir = tmp; +} + +static int files_ref_store_config(const char *var, const char *value, + const struct config_context *ctx UNUSED, + void *payload) +{ + struct files_ref_store_write_options *opts = payload; + + if (!strcmp(var, "core.prefersymlinkrefs")) { + opts->prefer_symlink_refs = git_config_bool(var, value); + } else if (!strcmp(var, "core.logallrefupdates")) { + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); + } + + return 0; +} + +static const struct files_ref_store_write_options *files_ref_store_write_options(struct files_ref_store *refs) +{ + struct files_ref_store_write_options *opts = &refs->write_opts_lazy_loaded; + + if (opts->initialized) + return opts; + + opts->log_all_ref_updates = LOG_REFS_UNSET; + repo_config(refs->base.repo, files_ref_store_config, opts); + + opts->initialized = true; + return opts; +} + /* * Create a new submodule ref cache and add it to the internal * set of caches. @@ -124,13 +179,8 @@ static struct ref_store *files_ref_store_init(struct repository *repo, refs->packed_ref_store = packed_ref_store_init(repo, NULL, refs->gitcommondir, opts); refs->store_flags = opts->access_flags; - refs->log_all_ref_updates = opts->log_all_ref_updates; - - repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); - chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); - chdir_notify_reparent("files-backend $GIT_COMMONDIR", - &refs->gitcommondir); + chdir_notify_register(NULL, files_ref_store_reparent, refs); strbuf_release(&refdir); @@ -182,6 +232,7 @@ static void files_ref_store_release(struct ref_store *ref_store) free(refs->gitcommondir); ref_store_release(refs->packed_ref_store); free(refs->packed_ref_store); + chdir_notify_unregister(NULL, files_ref_store_reparent, refs); } static void files_reflog_path(struct files_ref_store *refs, @@ -1860,12 +1911,12 @@ static int log_ref_setup(struct files_ref_store *refs, const char *refname, int force_create, int *logfd, struct strbuf *err) { - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates; + enum log_refs_config log_refs_cfg = files_ref_store_write_options(refs)->log_all_ref_updates; struct strbuf logfile_sb = STRBUF_INIT; char *logfile; if (log_refs_cfg == LOG_REFS_UNSET) - log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; + log_refs_cfg = is_bare_repository(the_repository) ? LOG_REFS_NONE : LOG_REFS_NORMAL; files_reflog_path(refs, &logfile_sb, refname); logfile = strbuf_detach(&logfile_sb, NULL); @@ -3271,6 +3322,7 @@ static int files_transaction_finish(struct ref_store *ref_store, { struct files_ref_store *refs = files_downcast(ref_store, 0, "ref_transaction_finish"); + const struct files_ref_store_write_options *write_opts = files_ref_store_write_options(refs); size_t i; int ret = 0; struct strbuf sb = STRBUF_INIT; @@ -3310,7 +3362,7 @@ static int files_transaction_finish(struct ref_store *ref_store, * We try creating a symlink, if that succeeds we continue to the * next update. If not, we try and create a regular symref. */ - if (update->new_target && refs->prefer_symlink_refs) + if (update->new_target && write_opts->prefer_symlink_refs) /* * By using the `NOT_CONSTANT()` trick, we can avoid * errors by `clang`'s `-Wunreachable` logic that would diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 0acde48c452513..499cb55dface4f 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +static void packed_ref_store_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct packed_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->path); + free(refs->path); + refs->path = tmp; +} + /* * Since packed-refs is only stored in the common dir, don't parse the * payload and rely on the files-backend to set 'gitdir' correctly. @@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo, strbuf_addf(&sb, "%s/packed-refs", gitdir); refs->path = strbuf_detach(&sb, NULL); - chdir_notify_reparent("packed-refs", &refs->path); + chdir_notify_register(NULL, packed_ref_store_reparent, refs); return ref_store; } @@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store) clear_snapshot(refs); rollback_lock_file(&refs->lock); delete_tempfile(&refs->tempfile); + chdir_notify_unregister(NULL, packed_ref_store_reparent, refs); free(refs->path); } diff --git a/refs/refs-internal.h b/refs/refs-internal.h index a08d58900eb644..c3ac7b556f3716 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -406,12 +406,6 @@ struct ref_store; struct ref_store_init_options { /* The kind of operations that the ref_store is allowed to perform. */ unsigned int access_flags; - - /* - * Denotes under what conditions reflogs should be created when updating - * references. - */ - enum log_refs_config log_all_ref_updates; }; /* diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 4ae22922de558b..212408c769c5e6 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "../git-compat-util.h" #include "../abspath.h" #include "../chdir-notify.h" @@ -48,9 +46,9 @@ static void reftable_backend_on_reload(void *payload) static int reftable_backend_init(struct reftable_backend *be, const char *path, - const struct reftable_write_options *_opts) + const struct reftable_stack_options *_opts) { - struct reftable_write_options opts = *_opts; + struct reftable_stack_options opts = *_opts; opts.on_reload = reftable_backend_on_reload; opts.on_reload_payload = be; return reftable_new_stack(&be->stack, path, &opts); @@ -140,10 +138,22 @@ struct reftable_ref_store { * is populated lazily when we try to resolve `worktrees/$worktree` refs. */ struct strmap worktree_backends; - struct reftable_write_options write_options; + struct reftable_stack_options stack_options; + + /* + * Options used when writing to or compacting the reftable stacks. + * These are parsed from the configuration lazily on first use via + * `reftable_be_write_options()` so that we don't have to access the + * configuration when initializing the ref store. Do not access these + * fields directly, but use the accessor instead. + */ + struct reftable_be_write_options { + struct reftable_write_options opts; + enum log_refs_config log_all_ref_updates; + bool initialized; + } write_opts_lazy_loaded; unsigned int store_flags; - enum log_refs_config log_all_ref_updates; int err; }; @@ -190,7 +200,7 @@ static int backend_for_worktree(struct reftable_backend **out, CALLOC_ARRAY(*out, 1); store->err = ret = reftable_backend_init(*out, worktree_dir.buf, - &store->write_options); + &store->stack_options); if (ret < 0) { free(*out); goto out; @@ -284,26 +294,6 @@ static int backend_for(struct reftable_backend **out, return ret; } -static int should_write_log(struct reftable_ref_store *refs, const char *refname) -{ - enum log_refs_config log_refs_cfg = refs->log_all_ref_updates; - if (log_refs_cfg == LOG_REFS_UNSET) - log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; - - switch (log_refs_cfg) { - case LOG_REFS_NONE: - return refs_reflog_exists(&refs->base, refname); - case LOG_REFS_ALWAYS: - return 1; - case LOG_REFS_NORMAL: - if (should_autocreate_reflog(log_refs_cfg, refname)) - return 1; - return refs_reflog_exists(&refs->base, refname); - default: - BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg); - } -} - static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split) { const char *tz_begin; @@ -332,39 +322,88 @@ static void fill_reftable_log_record(struct reftable_log_record *log, const stru static int reftable_be_config(const char *var, const char *value, const struct config_context *ctx, - void *_opts) + void *payload) { - struct reftable_write_options *opts = _opts; + struct reftable_ref_store *refs = payload; + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded; if (!strcmp(var, "reftable.blocksize")) { unsigned long block_size = git_config_ulong(var, value, ctx->kvi); if (block_size > 16777215) die("reftable block size cannot exceed 16MB"); - opts->block_size = block_size; + opts->opts.block_size = block_size; } else if (!strcmp(var, "reftable.restartinterval")) { unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi); if (restart_interval > UINT16_MAX) die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX); - opts->restart_interval = restart_interval; + opts->opts.restart_interval = restart_interval; } else if (!strcmp(var, "reftable.indexobjects")) { - opts->skip_index_objects = !git_config_bool(var, value); + opts->opts.skip_index_objects = !git_config_bool(var, value); } else if (!strcmp(var, "reftable.geometricfactor")) { unsigned long factor = git_config_ulong(var, value, ctx->kvi); if (factor > UINT8_MAX) die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX); - opts->auto_compaction_factor = factor; + opts->opts.auto_compaction_factor = factor; } else if (!strcmp(var, "reftable.locktimeout")) { int64_t lock_timeout = git_config_int64(var, value, ctx->kvi); if (lock_timeout > LONG_MAX) die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX); if (lock_timeout < 0 && lock_timeout != -1) die("reftable lock timeout does not support negative values other than -1"); - opts->lock_timeout_ms = lock_timeout; + opts->opts.lock_timeout_ms = lock_timeout; + } else if (!strcmp(var, "core.logallrefupdates")) { + opts->log_all_ref_updates = refs_parse_log_all_ref_updates_config(value); } return 0; } +static const struct reftable_be_write_options *reftable_be_write_options(struct reftable_ref_store *refs) +{ + struct reftable_be_write_options *opts = &refs->write_opts_lazy_loaded; + mode_t mask; + + if (opts->initialized) + return opts; + + mask = umask(0); + umask(mask); + + opts->opts.default_permissions = calc_shared_perm(refs->base.repo, 0666 & ~mask); + opts->opts.disable_auto_compact = + !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); + opts->opts.lock_timeout_ms = 100; + opts->log_all_ref_updates = LOG_REFS_UNSET; + + repo_config(refs->base.repo, reftable_be_config, refs); + + /* + * It is somewhat unfortunate that we have to mirror the default block + * size of the reftable library here. But given that the write options + * wouldn't be updated by the library here, and given that we require + * the proper block size to trim reflog message so that they fit, we + * must set up a proper value here. + */ + if (!opts->opts.block_size) + opts->opts.block_size = 4096; + + opts->initialized = true; + return opts; +} + +static void reftable_be_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *payload) +{ + struct reftable_ref_store *refs = payload; + char *tmp; + + tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir); + free(refs->base.gitdir); + refs->base.gitdir = tmp; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *payload, const char *gitdir, @@ -375,45 +414,24 @@ static struct ref_store *reftable_be_init(struct repository *repo, struct strbuf refdir = STRBUF_INIT; struct strbuf path = STRBUF_INIT; bool is_worktree; - mode_t mask; - - mask = umask(0); - umask(mask); refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir, &ref_common_dir); base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); - refs->log_all_ref_updates = opts->log_all_ref_updates; refs->store_flags = opts->access_flags; switch (repo->hash_algo->format_id) { case GIT_SHA1_FORMAT_ID: - refs->write_options.hash_id = REFTABLE_HASH_SHA1; + refs->stack_options.hash_id = REFTABLE_HASH_SHA1; break; case GIT_SHA256_FORMAT_ID: - refs->write_options.hash_id = REFTABLE_HASH_SHA256; + refs->stack_options.hash_id = REFTABLE_HASH_SHA256; break; default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); } - refs->write_options.default_permissions = calc_shared_perm(repo, 0666 & ~mask); - refs->write_options.disable_auto_compact = - !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); - refs->write_options.lock_timeout_ms = 100; - - repo_config(repo, reftable_be_config, &refs->write_options); - - /* - * It is somewhat unfortunate that we have to mirror the default block - * size of the reftable library here. But given that the write options - * wouldn't be updated by the library here, and given that we require - * the proper block size to trim reflog message so that they fit, we - * must set up a proper value here. - */ - if (!refs->write_options.block_size) - refs->write_options.block_size = 4096; /* * Set up the main reftable stack that is hosted in GIT_COMMON_DIR. @@ -426,7 +444,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, } strbuf_addstr(&path, "/reftable"); refs->err = reftable_backend_init(&refs->main_backend, path.buf, - &refs->write_options); + &refs->stack_options); if (refs->err) goto done; @@ -442,12 +460,12 @@ static struct ref_store *reftable_be_init(struct repository *repo, strbuf_addstr(&refdir, "/reftable"); refs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf, - &refs->write_options); + &refs->stack_options); if (refs->err) goto done; } - chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + chdir_notify_register(NULL, reftable_be_reparent, refs); done: assert(refs->err != REFTABLE_API_ERROR); @@ -474,6 +492,7 @@ static void reftable_be_release(struct ref_store *ref_store) free(be); } strmap_clear(&refs->worktree_backends, 0); + chdir_notify_unregister(NULL, reftable_be_reparent, refs); } static int reftable_be_create_on_disk(struct ref_store *ref_store, @@ -981,6 +1000,7 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out, struct reftable_addition *addition; ret = reftable_stack_new_addition(&addition, be->stack, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret) { if (ret == REFTABLE_LOCK_ERROR) @@ -1419,6 +1439,26 @@ static int transaction_update_cmp(const void *a, const void *b) return strcmp(update_a->update->refname, update_b->update->refname); } +static int should_write_log(struct reftable_ref_store *refs, const char *refname) +{ + enum log_refs_config log_refs_cfg = reftable_be_write_options(refs)->log_all_ref_updates; + if (log_refs_cfg == LOG_REFS_UNSET) + log_refs_cfg = is_bare_repository(refs->base.repo) ? LOG_REFS_NONE : LOG_REFS_NORMAL; + + switch (log_refs_cfg) { + case LOG_REFS_NONE: + return refs_reflog_exists(&refs->base, refname); + case LOG_REFS_ALWAYS: + return 1; + case LOG_REFS_NORMAL: + if (should_autocreate_reflog(log_refs_cfg, refname)) + return 1; + return refs_reflog_exists(&refs->base, refname); + default: + BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg); + } +} + static int write_transaction_table(struct reftable_writer *writer, void *cb_data) { struct write_transaction_table_arg *arg = cb_data; @@ -1553,7 +1593,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ); log->value.update.message = - xstrndup(u->msg, arg->refs->write_options.block_size / 2); + xstrndup(u->msg, reftable_be_write_options(arg->refs)->opts.block_size / 2); } } @@ -1669,9 +1709,9 @@ static int reftable_be_optimize(struct ref_store *ref_store, stack = refs->main_backend.stack; if (opts->flags & REFS_OPTIMIZE_AUTO) - ret = reftable_stack_auto_compact(stack); + ret = reftable_stack_auto_compact(stack, &reftable_be_write_options(refs)->opts); else - ret = reftable_stack_compact_all(stack, NULL); + ret = reftable_stack_compact_all(stack, &reftable_be_write_options(refs)->opts, NULL); if (ret < 0) { ret = error(_("unable to compact stack: %s"), reftable_error_str(ret)); @@ -1705,8 +1745,8 @@ static int reftable_be_optimize_required(struct ref_store *ref_store, if (opts->flags & REFS_OPTIMIZE_AUTO) use_heuristics = true; - return reftable_stack_compaction_required(stack, use_heuristics, - required); + return reftable_stack_compaction_required(stack, &reftable_be_write_options(refs)->opts, + use_heuristics, required); } struct write_create_symref_arg { @@ -1825,7 +1865,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = deletion_ts; logs[logs_nr].value.update.message = - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2); memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; @@ -1864,7 +1904,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = creation_ts; logs[logs_nr].value.update.message = - xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + xstrndup(arg->logmsg, reftable_be_write_options(arg->refs)->opts.block_size / 2); memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; @@ -1963,6 +2003,7 @@ static int reftable_be_rename_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -1993,6 +2034,7 @@ static int reftable_be_copy_ref(struct ref_store *ref_store, if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2358,6 +2400,7 @@ static int reftable_be_create_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); done: @@ -2430,6 +2473,7 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store, arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); assert(ret != REFTABLE_API_ERROR); @@ -2552,6 +2596,7 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, goto done; ret = reftable_stack_new_addition(&add, be->stack, + &reftable_be_write_options(refs)->opts, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret < 0) goto done; diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index 5f7be573fabf8e..11f9963f4f1e9f 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -26,11 +26,29 @@ */ struct reftable_stack; +/* Options related to opening a stack. */ +struct reftable_stack_options { + /* + * 4-byte identifier ("sha1", "s256") of the hash. Defaults to SHA1 if + * unset. + */ + enum reftable_hash hash_id; + + /* + * Callback function to execute whenever the stack is being reloaded. + * This can be used e.g. to discard cached information that relies on + * the old stack's data. The payload data will be passed as argument to + * the callback. + */ + void (*on_reload)(void *payload); + void *on_reload_payload; +}; + /* open a new reftable stack. The tables along with the table list will be * stored in 'dir'. Typically, this should be .git/reftables. */ int reftable_new_stack(struct reftable_stack **dest, const char *dir, - const struct reftable_write_options *opts); + const struct reftable_stack_options *opts); /* returns the update_index at which a next table should be written. */ uint64_t reftable_stack_next_update_index(struct reftable_stack *st); @@ -52,6 +70,7 @@ enum { */ int reftable_stack_new_addition(struct reftable_addition **dest, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags); /* Adds a reftable to transaction. */ @@ -77,7 +96,9 @@ void reftable_addition_destroy(struct reftable_addition *add); int reftable_stack_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *write_arg), - void *write_arg, unsigned flags); + void *write_arg, + const struct reftable_write_options *opts, + unsigned flags); struct reftable_iterator; @@ -122,6 +143,7 @@ struct reftable_log_expiry_config { /* compacts all reftables into a giant table. Expire reflog entries if config is * non-NULL */ int reftable_stack_compact_all(struct reftable_stack *st, + const struct reftable_write_options *opts, struct reftable_log_expiry_config *config); /* @@ -132,11 +154,13 @@ int reftable_stack_compact_all(struct reftable_stack *st, * compacted to maintain geometric progression. */ int reftable_stack_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, bool use_heuristics, bool *required); /* heuristically compact unbalanced table stack. */ -int reftable_stack_auto_compact(struct reftable_stack *st); +int reftable_stack_auto_compact(struct reftable_stack *st, + const struct reftable_write_options *opts); /* delete stale .ref tables. */ int reftable_stack_clean(struct reftable_stack *st); diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index a66db415c87637..6ff4ddfc600461 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -28,11 +28,6 @@ struct reftable_write_options { /* how often to write complete keys in each block. */ uint16_t restart_interval; - /* 4-byte identifier ("sha1", "s256") of the hash. - * Defaults to SHA1 if unset - */ - enum reftable_hash hash_id; - /* Default mode for creating files. If unset, use 0666 (+umask) */ unsigned int default_permissions; @@ -60,15 +55,6 @@ struct reftable_write_options { * negative value will cause us to block indefinitely. */ long lock_timeout_ms; - - /* - * Callback function to execute whenever the stack is being reloaded. - * This can be used e.g. to discard cached information that relies on - * the old stack's data. The payload data will be passed as argument to - * the callback. - */ - void (*on_reload)(void *payload); - void *on_reload_payload; }; /* reftable_block_stats holds statistics for a single block type */ @@ -114,7 +100,8 @@ struct reftable_writer; int reftable_writer_new(struct reftable_writer **out, ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, const struct reftable_write_options *opts); + void *writer_arg, enum reftable_hash hash_id, + const struct reftable_write_options *opts); /* * Set the range of update indices for the records we will add. When writing a diff --git a/reftable/stack.c b/reftable/stack.c index 1fba96ddb3366f..ab129267089f29 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -501,10 +501,10 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, } int reftable_new_stack(struct reftable_stack **dest, const char *dir, - const struct reftable_write_options *_opts) + const struct reftable_stack_options *_opts) { struct reftable_buf list_file_name = REFTABLE_BUF_INIT; - struct reftable_write_options opts = { 0 }; + struct reftable_stack_options opts = { 0 }; struct reftable_stack *p; int err; @@ -629,6 +629,7 @@ int reftable_stack_reload(struct reftable_stack *st) struct reftable_addition { struct reftable_flock tables_list_lock; struct reftable_stack *stack; + struct reftable_write_options opts; char **new_tables; size_t new_tables_len, new_tables_cap; @@ -657,6 +658,7 @@ static void reftable_addition_close(struct reftable_addition *add) static int reftable_stack_init_addition(struct reftable_addition *add, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags) { struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; @@ -664,15 +666,17 @@ static int reftable_stack_init_addition(struct reftable_addition *add, memset(add, 0, sizeof(*add)); add->stack = st; + if (opts) + add->opts = *opts; err = flock_acquire(&add->tables_list_lock, st->list_file, - st->opts.lock_timeout_ms); + add->opts.lock_timeout_ms); if (err < 0) goto done; - if (st->opts.default_permissions) { + if (add->opts.default_permissions) { if (chmod(add->tables_list_lock.path, - st->opts.default_permissions) < 0) { + add->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -702,12 +706,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add, static int stack_try_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *arg), - void *arg, unsigned flags) + void *arg, + const struct reftable_write_options *opts, + unsigned flags) { struct reftable_addition add; int err; - err = reftable_stack_init_addition(&add, st, flags); + err = reftable_stack_init_addition(&add, st, opts, flags); if (err < 0) goto done; @@ -723,9 +729,11 @@ static int stack_try_add(struct reftable_stack *st, int reftable_stack_add(struct reftable_stack *st, int (*write)(struct reftable_writer *wr, void *arg), - void *arg, unsigned flags) + void *arg, + const struct reftable_write_options *opts, + unsigned flags) { - int err = stack_try_add(st, write, arg, flags); + int err = stack_try_add(st, write, arg, opts, flags); if (err < 0) { if (err == REFTABLE_OUTDATED_ERROR) { /* Ignore error return, we want to propagate @@ -810,7 +818,7 @@ int reftable_addition_commit(struct reftable_addition *add) if (err) goto done; - if (!add->stack->opts.disable_auto_compact) { + if (!add->opts.disable_auto_compact) { /* * Auto-compact the stack to keep the number of tables in * control. It is possible that a concurrent writer is already @@ -820,7 +828,7 @@ int reftable_addition_commit(struct reftable_addition *add) * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`. * Both of these errors are benign, so we simply ignore them. */ - err = reftable_stack_auto_compact(add->stack); + err = reftable_stack_auto_compact(add->stack, &add->opts); if (err < 0 && err != REFTABLE_LOCK_ERROR && err != REFTABLE_OUTDATED_ERROR) goto done; @@ -834,6 +842,7 @@ int reftable_addition_commit(struct reftable_addition *add) int reftable_stack_new_addition(struct reftable_addition **dest, struct reftable_stack *st, + const struct reftable_write_options *opts, unsigned int flags) { int err; @@ -842,7 +851,7 @@ int reftable_stack_new_addition(struct reftable_addition **dest, if (!*dest) return REFTABLE_OUT_OF_MEMORY_ERROR; - err = reftable_stack_init_addition(*dest, st, flags); + err = reftable_stack_init_addition(*dest, st, opts, flags); if (err) { reftable_free(*dest); *dest = NULL; @@ -862,7 +871,7 @@ int reftable_addition_add(struct reftable_addition *add, struct reftable_writer *wr = NULL; struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; struct fd_writer writer = { - .opts = &add->stack->opts, + .opts = &add->opts, }; int err = 0; @@ -883,9 +892,9 @@ int reftable_addition_add(struct reftable_addition *add, err = tmpfile_from_pattern(&tab_file, temp_tab_file_name.buf); if (err < 0) goto done; - if (add->stack->opts.default_permissions) { + if (add->opts.default_permissions) { if (chmod(tab_file.path, - add->stack->opts.default_permissions)) { + add->opts.default_permissions)) { err = REFTABLE_IO_ERROR; goto done; } @@ -893,7 +902,7 @@ int reftable_addition_add(struct reftable_addition *add, writer.fd = tab_file.fd; err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, - &writer, &add->stack->opts); + &writer, add->stack->opts.hash_id, &add->opts); if (err < 0) goto done; @@ -1066,13 +1075,14 @@ static int stack_write_compact(struct reftable_stack *st, static int stack_compact_locked(struct reftable_stack *st, size_t first, size_t last, struct reftable_log_expiry_config *config, + const struct reftable_write_options *opts, struct reftable_tmpfile *tab_file_out) { struct reftable_buf next_name = REFTABLE_BUF_INIT; struct reftable_buf tab_file_path = REFTABLE_BUF_INIT; struct reftable_writer *wr = NULL; struct fd_writer writer= { - .opts = &st->opts, + .opts = opts, }; struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; int err = 0; @@ -1094,15 +1104,15 @@ static int stack_compact_locked(struct reftable_stack *st, if (err < 0) goto done; - if (st->opts.default_permissions && - chmod(tab_file.path, st->opts.default_permissions) < 0) { + if (opts->default_permissions && + chmod(tab_file.path, opts->default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } writer.fd = tab_file.fd; err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, - &writer, &st->opts); + &writer, st->opts.hash_id, opts); if (err < 0) goto done; @@ -1150,6 +1160,7 @@ enum stack_compact_range_flags { static int stack_compact_range(struct reftable_stack *st, size_t first, size_t last, struct reftable_log_expiry_config *expiry, + const struct reftable_write_options *opts, unsigned int flags) { struct reftable_buf tables_list_buf = REFTABLE_BUF_INIT; @@ -1175,7 +1186,7 @@ static int stack_compact_range(struct reftable_stack *st, * Hold the lock so that we can read "tables.list" and lock all tables * which are part of the user-specified range. */ - err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); + err = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms); if (err < 0) goto done; @@ -1274,7 +1285,7 @@ static int stack_compact_range(struct reftable_stack *st, * these tables may end up with an empty new table in case tombstones * end up cancelling out all refs in that range. */ - err = stack_compact_locked(st, first, last, expiry, &new_table); + err = stack_compact_locked(st, first, last, expiry, opts, &new_table); if (err < 0) { if (err != REFTABLE_EMPTY_TABLE_ERROR) goto done; @@ -1286,13 +1297,13 @@ static int stack_compact_range(struct reftable_stack *st, * "tables.list". We'll then replace the compacted range of tables with * the new table. */ - err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); + err = flock_acquire(&tables_list_lock, st->list_file, opts->lock_timeout_ms); if (err < 0) goto done; - if (st->opts.default_permissions) { + if (opts->default_permissions) { if (chmod(tables_list_lock.path, - st->opts.default_permissions) < 0) { + opts->default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -1513,10 +1524,16 @@ static int stack_compact_range(struct reftable_stack *st, } int reftable_stack_compact_all(struct reftable_stack *st, + const struct reftable_write_options *opts, struct reftable_log_expiry_config *config) { + struct reftable_write_options opts_default = { 0 }; size_t last = st->merged->tables_len ? st->merged->tables_len - 1 : 0; - return stack_compact_range(st, 0, last, config, 0); + + if (!opts) + opts = &opts_default; + + return stack_compact_range(st, 0, last, config, opts, 0); } static int segment_size(struct segment *s) @@ -1601,6 +1618,7 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, } static int stack_segments_for_compaction(struct reftable_stack *st, + const struct reftable_write_options *opts, struct segment *seg) { int version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; @@ -1615,13 +1633,14 @@ static int stack_segments_for_compaction(struct reftable_stack *st, sizes[i] = st->tables[i]->size - overhead; *seg = suggest_compaction_segment(sizes, st->merged->tables_len, - st->opts.auto_compaction_factor); + opts->auto_compaction_factor); reftable_free(sizes); return 0; } static int update_segment_if_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, struct segment *seg, bool use_geometric, bool *required) @@ -1638,7 +1657,7 @@ static int update_segment_if_compaction_required(struct reftable_stack *st, return 0; } - err = stack_segments_for_compaction(st, seg); + err = stack_segments_for_compaction(st, opts, seg); if (err) return err; @@ -1647,27 +1666,40 @@ static int update_segment_if_compaction_required(struct reftable_stack *st, } int reftable_stack_compaction_required(struct reftable_stack *st, + const struct reftable_write_options *opts, bool use_heuristics, bool *required) { + struct reftable_write_options opts_default = { 0 }; struct segment seg; - return update_segment_if_compaction_required(st, &seg, use_heuristics, - required); + + if (!opts) + opts = &opts_default; + + return update_segment_if_compaction_required(st, opts, &seg, + use_heuristics, required); } -int reftable_stack_auto_compact(struct reftable_stack *st) +int reftable_stack_auto_compact(struct reftable_stack *st, + const struct reftable_write_options *opts) { + struct reftable_write_options opts_default = { 0 }; struct segment seg; bool required; int err; - err = update_segment_if_compaction_required(st, &seg, true, &required); + if (!opts) + opts = &opts_default; + + err = update_segment_if_compaction_required(st, opts, &seg, true, + &required); if (err) return err; if (required) return stack_compact_range(st, seg.start, seg.end - 1, - NULL, STACK_COMPACT_RANGE_BEST_EFFORT); + NULL, opts, + STACK_COMPACT_RANGE_BEST_EFFORT); return 0; } @@ -1807,7 +1839,7 @@ static int reftable_stack_clean_locked(struct reftable_stack *st) int reftable_stack_clean(struct reftable_stack *st) { struct reftable_addition *add = NULL; - int err = reftable_stack_new_addition(&add, st, 0); + int err = reftable_stack_new_addition(&add, st, NULL, 0); if (err < 0) { goto done; } diff --git a/reftable/stack.h b/reftable/stack.h index bc28f2998ac027..f7901e6c6f5a01 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -20,7 +20,7 @@ struct reftable_stack { char *reftable_dir; - struct reftable_write_options opts; + struct reftable_stack_options opts; struct reftable_table **tables; size_t tables_len; diff --git a/reftable/writer.c b/reftable/writer.c index 0133b649759bcf..f850e9d599e387 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -80,9 +80,6 @@ static void options_set_defaults(struct reftable_write_options *opts) opts->restart_interval = 16; } - if (opts->hash_id == 0) { - opts->hash_id = REFTABLE_HASH_SHA1; - } if (opts->block_size == 0) { opts->block_size = DEFAULT_BLOCK_SIZE; } @@ -90,7 +87,7 @@ static void options_set_defaults(struct reftable_write_options *opts) static int writer_version(struct reftable_writer *w) { - return (w->opts.hash_id == 0 || w->opts.hash_id == REFTABLE_HASH_SHA1) ? + return (w->hash_id == 0 || w->hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; } @@ -107,7 +104,7 @@ static int writer_write_header(struct reftable_writer *w, uint8_t *dest) if (writer_version(w) == 2) { uint32_t hash_id; - switch (w->opts.hash_id) { + switch (w->hash_id) { case REFTABLE_HASH_SHA1: hash_id = REFTABLE_FORMAT_ID_SHA1; break; @@ -134,7 +131,7 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) reftable_buf_reset(&w->last_key); ret = block_writer_init(&w->block_writer_data, typ, w->block, w->opts.block_size, block_start, - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (ret < 0) return ret; @@ -147,7 +144,9 @@ static int writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) int reftable_writer_new(struct reftable_writer **out, ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, const struct reftable_write_options *_opts) + void *writer_arg, + enum reftable_hash hash_id, + const struct reftable_write_options *_opts) { struct reftable_write_options opts = {0}; struct reftable_writer *wp; @@ -162,6 +161,9 @@ int reftable_writer_new(struct reftable_writer **out, if (opts.block_size >= (1 << 24)) return REFTABLE_API_ERROR; + if (!hash_id) + hash_id = REFTABLE_HASH_SHA1; + reftable_buf_init(&wp->block_writer_data.last_key); reftable_buf_init(&wp->last_key); reftable_buf_init(&wp->scratch); @@ -173,6 +175,7 @@ int reftable_writer_new(struct reftable_writer **out, wp->write = writer_func; wp->write_arg = writer_arg; wp->opts = opts; + wp->hash_id = hash_id; wp->flush = flush_func; writer_reinit_block_writer(wp, REFTABLE_BLOCK_TYPE_REF); @@ -367,7 +370,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, if (!w->opts.skip_index_objects && reftable_ref_record_val1(ref)) { reftable_buf_reset(&w->scratch); err = reftable_buf_add(&w->scratch, (char *)reftable_ref_record_val1(ref), - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (err < 0) goto out; @@ -379,7 +382,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, if (!w->opts.skip_index_objects && reftable_ref_record_val2(ref)) { reftable_buf_reset(&w->scratch); err = reftable_buf_add(&w->scratch, reftable_ref_record_val2(ref), - hash_size(w->opts.hash_id)); + hash_size(w->hash_id)); if (err < 0) goto out; diff --git a/reftable/writer.h b/reftable/writer.h index 9f53610b27a161..c08fc413e19367 100644 --- a/reftable/writer.h +++ b/reftable/writer.h @@ -27,6 +27,7 @@ struct reftable_writer { uint64_t next; uint64_t min_update_index, max_update_index; struct reftable_write_options opts; + enum reftable_hash hash_id; /* memory buffer for writing */ uint8_t *block; diff --git a/remote.c b/remote.c index 00723b385e1d52..e6c52c850cac63 100644 --- a/remote.c +++ b/remote.c @@ -2315,6 +2315,8 @@ static void format_branch_comparison(struct strbuf *sb, bool up_to_date, int ours, int theirs, const char *branch_name, + const char *push_remote_name, + const char *push_branch_name, enum ahead_behind_flags abf, unsigned flags) { @@ -2350,9 +2352,15 @@ static void format_branch_comparison(struct strbuf *sb, "and can be fast-forwarded.\n", theirs), branch_name, theirs); - if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) - strbuf_addstr(sb, - _(" (use \"git pull\" to update your local branch)\n")); + if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) { + if (push_remote_name && push_branch_name) + strbuf_addf(sb, + _(" (use \"git pull %s %s\" to update your local branch)\n"), + push_remote_name, push_branch_name); + else + strbuf_addstr(sb, + _(" (use \"git pull\" to update your local branch)\n")); + } } else { strbuf_addf(sb, Q_("Your branch and '%s' have diverged,\n" @@ -2363,9 +2371,15 @@ static void format_branch_comparison(struct strbuf *sb, "respectively.\n", ours + theirs), branch_name, ours, theirs); - if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) - strbuf_addstr(sb, - _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n")); + if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) { + if (push_remote_name && push_branch_name) + strbuf_addf(sb, + _(" (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"), + push_remote_name, push_branch_name); + else + strbuf_addstr(sb, + _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n")); + } } } @@ -2403,6 +2417,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, int ours, theirs, cmp; int is_upstream, is_push; unsigned flags = 0; + const char *push_remote_name = NULL; + const char *push_branch_name = NULL; full_ref = resolve_compare_branch(branch, branches.items[i].string); @@ -2446,11 +2462,27 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, if (is_upstream) flags |= ENABLE_ADVICE_PULL; - if (is_push) - flags |= ENABLE_ADVICE_PUSH; if (show_divergence_advice && is_upstream) flags |= ENABLE_ADVICE_DIVERGENCE; + if (is_push) { + flags |= ENABLE_ADVICE_PUSH; + if (!upstream_ref || strcmp(upstream_ref, full_ref)) { + push_remote_name = pushremote_for_branch(branch, NULL); + if (push_remote_name && + skip_prefix(full_ref, "refs/remotes/", &push_branch_name) && + skip_prefix(push_branch_name, push_remote_name, &push_branch_name) && + *push_branch_name == '/') { + push_branch_name++; + flags |= ENABLE_ADVICE_PULL; + } else { + push_remote_name = NULL; + } + } else { + flags |= ENABLE_ADVICE_PULL; + } + } format_branch_comparison(sb, !cmp, ours, theirs, short_ref, + push_remote_name, push_branch_name, abf, flags); reported = 1; diff --git a/remote.h b/remote.h index 54b17e4b028b5b..72a54d84ad511d 100644 --- a/remote.h +++ b/remote.h @@ -62,12 +62,14 @@ struct remote_state { void remote_state_clear(struct remote_state *remote_state); struct remote_state *remote_state_new(void); - enum follow_remote_head_settings { - FOLLOW_REMOTE_NEVER = -1, - FOLLOW_REMOTE_CREATE = 0, - FOLLOW_REMOTE_WARN = 1, - FOLLOW_REMOTE_ALWAYS = 2, - }; +#define BUILTIN_FOLLOW_REMOTE_HEAD_DFLT FOLLOW_REMOTE_CREATE +enum follow_remote_head_settings { + FOLLOW_REMOTE_UNCONFIGURED = 0, + FOLLOW_REMOTE_NEVER, + FOLLOW_REMOTE_CREATE, + FOLLOW_REMOTE_WARN, + FOLLOW_REMOTE_ALWAYS, +}; struct remote { struct hashmap_entry ent; diff --git a/repack-geometry.c b/repack-geometry.c index 2064683dcfe1e1..15b34129506d2a 100644 --- a/repack-geometry.c +++ b/repack-geometry.c @@ -32,7 +32,8 @@ void pack_geometry_init(struct pack_geometry *geometry, { struct packed_git *p; struct strbuf buf = STRBUF_INIT; - struct multi_pack_index *m = get_multi_pack_index(existing->source); + struct odb_source_files *files = odb_source_files_downcast(existing->source); + struct multi_pack_index *m = get_multi_pack_index(files->packed); repo_for_each_pack(existing->repo, p) { if (geometry->midx_layer_threshold_set && m && diff --git a/repack-midx.c b/repack-midx.c index b6b1de718058da..7c7c3620e50b7d 100644 --- a/repack-midx.c +++ b/repack-midx.c @@ -557,13 +557,14 @@ static void repack_make_midx_append_plan(struct repack_write_midx_opts *opts, struct midx_compaction_step **steps_p, size_t *steps_nr_p) { + struct odb_source_files *files = odb_source_files_downcast(opts->existing->source); struct multi_pack_index *m; struct midx_compaction_step *steps = NULL; struct midx_compaction_step *step; size_t steps_nr = 0, steps_alloc = 0; odb_reprepare(opts->existing->repo->objects); - m = get_multi_pack_index(opts->existing->source); + m = get_multi_pack_index(files->packed); if (opts->names->nr) { struct strbuf buf = STRBUF_INIT; @@ -606,6 +607,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts, struct midx_compaction_step **steps_p, size_t *steps_nr_p) { + struct odb_source_files *files = odb_source_files_downcast(opts->existing->source); struct multi_pack_index *m; struct midx_compaction_step *steps = NULL; struct midx_compaction_step step = { 0 }; @@ -618,7 +620,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts, opts->existing->repo); odb_reprepare(opts->existing->repo->objects); - m = get_multi_pack_index(opts->existing->source); + m = get_multi_pack_index(files->packed); for (i = 0; m && i < m->num_packs + m->num_packs_in_base; i++) { if (prepare_midx_pack(m, i)) { @@ -938,6 +940,7 @@ static int repack_make_midx_compaction_plan(struct repack_write_midx_opts *opts, static int write_midx_incremental(struct repack_write_midx_opts *opts) { + struct odb_source_files *files = odb_source_files_downcast(opts->existing->source); struct midx_compaction_step *steps = NULL; struct strbuf lock_name = STRBUF_INIT; struct lock_file lf; @@ -946,7 +949,7 @@ static int write_midx_incremental(struct repack_write_midx_opts *opts) size_t i; int ret = 0; - get_midx_chain_filename(opts->existing->source, &lock_name); + get_midx_chain_filename(files->packed, &lock_name); if (safe_create_leading_directories(opts->existing->repo, lock_name.buf)) die_errno(_("unable to create leading directories of %s"), diff --git a/repack.c b/repack.c index 571dabb665ee9b..d2aa58e13484fd 100644 --- a/repack.c +++ b/repack.c @@ -59,10 +59,10 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name, bool wrote_incremental_midx) { struct strbuf buf = STRBUF_INIT; - struct odb_source *source = repo->objects->sources; - struct multi_pack_index *m = get_multi_pack_index(source); + struct odb_source_files *files = odb_source_files_downcast(repo->objects->sources); + struct multi_pack_index *m = get_multi_pack_index(files->packed); strbuf_addf(&buf, "%s.pack", base_name); - if (m && source->local && midx_contains_pack(m, buf.buf)) { + if (m && files->base.local && midx_contains_pack(m, buf.buf)) { clear_midx_file(repo); if (!wrote_incremental_midx) clear_incremental_midx_files(repo, NULL); diff --git a/repo-settings.c b/repo-settings.c index 208e09ff17fcee..f3be3b8c5a3d09 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -177,22 +177,6 @@ void repo_settings_set_big_file_threshold(struct repository *repo, unsigned long repo->settings.big_file_threshold = value; } -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo) -{ - const char *value; - - if (!repo_config_get_string_tmp(repo, "core.logallrefupdates", &value)) { - if (value && !strcasecmp(value, "always")) - return LOG_REFS_ALWAYS; - else if (git_config_bool("core.logallrefupdates", value)) - return LOG_REFS_NORMAL; - else - return LOG_REFS_NONE; - } - - return LOG_REFS_UNSET; -} - int repo_settings_get_warn_ambiguous_refs(struct repository *repo) { prepare_repo_settings(repo); diff --git a/repo-settings.h b/repo-settings.h index cad9c3f0cc15f3..e5253ead025c83 100644 --- a/repo-settings.h +++ b/repo-settings.h @@ -16,13 +16,6 @@ enum fetch_negotiation_setting { FETCH_NEGOTIATION_NOOP, }; -enum log_refs_config { - LOG_REFS_UNSET = -1, - LOG_REFS_NONE = 0, - LOG_REFS_NORMAL, - LOG_REFS_ALWAYS -}; - struct repo_settings { int initialized; @@ -86,8 +79,6 @@ struct repo_settings { void prepare_repo_settings(struct repository *r); void repo_settings_clear(struct repository *r); -/* Read the value for "core.logAllRefUpdates". */ -enum log_refs_config repo_settings_get_log_all_ref_updates(struct repository *repo); /* Read the value for "core.warnAmbiguousRefs". */ int repo_settings_get_warn_ambiguous_refs(struct repository *repo); /* Read the value for "core.hooksPath". */ diff --git a/repository.c b/repository.c index 187dd471c4e607..73d80bcffdf5c2 100644 --- a/repository.c +++ b/repository.c @@ -73,6 +73,7 @@ void initialize_repository(struct repository *repo) ALLOC_ARRAY(repo->index, 1); index_state_init(repo->index, repo); repo->check_deprecated_config = true; + repo->bare_cfg = -1; repo_config_values_init(&repo->config_values_private_); /* @@ -421,6 +422,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->remote_state); } + if (repo->refs_private) { + ref_store_release(repo->refs_private); + FREE_AND_NULL(repo->refs_private); + } + strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e) ref_store_release(e->value); strmap_clear(&repo->submodule_ref_stores, 1); diff --git a/repository.h b/repository.h index 36e2db26332c0e..7d649e32e7fad3 100644 --- a/repository.h +++ b/repository.h @@ -117,6 +117,13 @@ struct repository { bool worktree_initialized; bool worktree_config_is_bogus; + /* + * Whether the repository is bare, as set by "core.bare" config or + * inferred during repository discovery. -1 means unset/unknown, 0 + * means non-bare, 1 means bare. + */ + int bare_cfg; + /* * Path from the root of the top-level superproject down to this * repository. This is only non-NULL if the repository is initialized diff --git a/revision.c b/revision.c index e91d7e1f11356a..0c95edef5947fa 100644 --- a/revision.c +++ b/revision.c @@ -26,6 +26,7 @@ #include "decorate.h" #include "string-list.h" #include "line-log.h" +#include "log-tree.h" #include "mailmap.h" #include "commit-slab.h" #include "cache-tree.h" @@ -3304,6 +3305,7 @@ void release_revisions(struct rev_info *revs) line_log_free(revs); oidset_clear(&revs->missing_commits); release_revisions_bloom_keyvecs(revs); + release_follow_pathspec_slab(revs); } static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child) diff --git a/revision.h b/revision.h index 00c392be37195d..569b3fa1cb5a33 100644 --- a/revision.h +++ b/revision.h @@ -66,6 +66,7 @@ struct repository; struct rev_info; struct string_list; struct saved_parents; +struct follow_pathspec_slab; struct bloom_keyvec; struct bloom_filter_settings; struct option; @@ -363,6 +364,9 @@ struct rev_info { /* copies of the parent lists, for --full-diff display */ struct saved_parents *saved_parents_slab; + /* per-commit pathspec for --follow across merges */ + struct follow_pathspec_slab *follow_pathspec_slab; + struct commit_list *previous_parents; struct commit_list *ancestry_path_bottoms; const char *break_bar; diff --git a/sequencer.c b/sequencer.c index 57855b0066ac98..0fe8fed6c3e51d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2627,6 +2627,27 @@ static int is_command(enum todo_command command, const char **bol) return 0; } +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd) +{ + const char *s = *p; + + for (int i = 0; i < TODO_COMMENT; i++) + if (is_command(i, p)) { + *cmd = i; + return true; + } + + if (starts_with(s, comment_line_str)) { + *cmd = TODO_COMMENT; + return true; + } else if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n') || !s[0]) { + *cmd = TODO_COMMENT; + return true; + } + + return false; +} + static int check_label_or_ref_arg(enum todo_command command, const char *arg) { switch (command) { @@ -2716,30 +2737,24 @@ static int parse_insn_line(struct repository *r, struct replay_opts *opts, { struct object_id commit_oid; char *end_of_object_name; - int i, saved, status, padding; + int saved, status, padding; item->flags = 0; /* left-trim */ bol += strspn(bol, " \t"); - if (bol == eol || *bol == '\r' || starts_with_mem(bol, eol - bol, comment_line_str)) { - item->command = TODO_COMMENT; + if (!sequencer_parse_todo_command(&bol, &item->command)) + return error(_("invalid command '%.*s'"), + (int)strcspn(bol, " \t\r\n"), bol); + + if (item->command == TODO_COMMENT) { item->commit = NULL; item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } - for (i = 0; i < TODO_COMMENT; i++) - if (is_command(i, &bol)) { - item->command = i; - break; - } - if (i >= TODO_COMMENT) - return error(_("invalid command '%.*s'"), - (int)strcspn(bol, " \t\r\n"), bol); - /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); bol += padding; diff --git a/sequencer.h b/sequencer.h index 3164bd437d6a22..64a9c7fb1beccd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -265,6 +265,14 @@ int read_author_script(const char *path, char **name, char **email, char **date, int write_basic_state(struct replay_opts *opts, const char *head_name, struct commit *onto, const struct object_id *orig_head); void sequencer_post_commit_cleanup(struct repository *r, int verbose); + +/* + * Try to parse the todo command pointed to by *p. On success sets cmd, + * advances p and returns true. On failure returns false, leaves p and + * cmd unchanged. + */ +bool sequencer_parse_todo_command(const char **p, enum todo_command *cmd); + int sequencer_get_last_command(struct repository* r, enum replay_action *action); int sequencer_determine_whence(struct repository *r, enum commit_whence *whence); diff --git a/setup.c b/setup.c index b4652651dfd454..0de56a074f7c15 100644 --- a/setup.c +++ b/setup.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -795,14 +794,23 @@ static int check_repository_format_gently(const char *gitdir, has_common = 0; } - if (!has_common) { - if (candidate->is_bare != -1) { - is_bare_repository_cfg = candidate->is_bare; - } - if (candidate->work_tree) { - free(git_work_tree_cfg); - git_work_tree_cfg = xstrdup(candidate->work_tree); - } + if (startup_info->force_bare_repository) { + candidate->is_bare = 1; + FREE_AND_NULL(candidate->work_tree); + } else if (has_common) { + /* + * When sharing a common dir with another repository (e.g. a + * linked worktree), do not let this repository's config + * dictate bareness; it is inherited from the main worktree. + */ + candidate->is_bare = -1; + + /* + * Furthermore, "core.worktree" is supposed to be ignored when + * we have a commondir configured, unless it comes from the + * per-worktree configuration. + */ + FREE_AND_NULL(candidate->work_tree); } return 0; @@ -917,7 +925,7 @@ int verify_repository_format(const struct repository_format *format, return 0; } -void read_gitfile_error_die(int error_code, const char *path, const char *dir) +void read_gitfile_error_die(int error_code, const char *path) { switch (error_code) { case READ_GITFILE_ERR_NOT_A_FILE: @@ -937,7 +945,8 @@ void read_gitfile_error_die(int error_code, const char *path, const char *dir) case READ_GITFILE_ERR_NO_PATH: die(_("no path in gitfile: %s"), path); case READ_GITFILE_ERR_NOT_A_REPO: - die(_("not a git repository: %s"), dir); + die(_("gitfile does not point to a valid repository: %s"), + path); default: BUG("unknown error code"); } @@ -1028,7 +1037,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) if (return_error_code) *return_error_code = error_code; else if (error_code) - read_gitfile_error_die(error_code, path, dir); + read_gitfile_error_die(error_code, path); free(buf); return error_code ? NULL : path; @@ -1141,8 +1150,8 @@ static const char *setup_explicit_git_dir(struct repository *repo, /* #3, #7, #11, #15, #19, #23, #27, #31 (see t1510) */ if (work_tree_env) set_git_work_tree(repo, work_tree_env); - else if (is_bare_repository_cfg > 0) { - if (git_work_tree_cfg) { + else if (repo_fmt->is_bare > 0) { + if (repo_fmt->work_tree) { /* #22.2, #30 */ warning("core.bare and core.worktree do not make sense"); repo->worktree_config_is_bogus = true; @@ -1153,15 +1162,15 @@ static const char *setup_explicit_git_dir(struct repository *repo, free(gitfile); return NULL; } - else if (git_work_tree_cfg) { /* #6, #14 */ - if (is_absolute_path(git_work_tree_cfg)) - set_git_work_tree(repo, git_work_tree_cfg); + else if (repo_fmt->work_tree) { /* #6, #14 */ + if (is_absolute_path(repo_fmt->work_tree)) + set_git_work_tree(repo, repo_fmt->work_tree); else { char *core_worktree; if (chdir(gitdirenv)) die_errno(_("cannot chdir to '%s'"), gitdirenv); - if (chdir(git_work_tree_cfg)) - die_errno(_("cannot chdir to '%s'"), git_work_tree_cfg); + if (chdir(repo_fmt->work_tree)) + die_errno(_("cannot chdir to '%s'"), repo_fmt->work_tree); core_worktree = xgetcwd(); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); @@ -1214,7 +1223,7 @@ static const char *setup_discovered_git_dir(struct repository *repo, return NULL; /* --work-tree is set without --git-dir; use discovered one */ - if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { + if (getenv(GIT_WORK_TREE_ENVIRONMENT) || repo_fmt->work_tree) { char *to_free = NULL; const char *ret; @@ -1228,7 +1237,7 @@ static const char *setup_discovered_git_dir(struct repository *repo, } /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */ - if (is_bare_repository_cfg > 0) { + if (repo_fmt->is_bare > 0) { set_git_dir(repo, gitdir, (offset != cwd->len)); if (chdir(cwd->buf)) die_errno(_("cannot come back to cwd")); @@ -1264,7 +1273,7 @@ static const char *setup_bare_git_dir(struct repository *repo, setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); /* --work-tree is set without --git-dir; use discovered one */ - if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { + if (getenv(GIT_WORK_TREE_ENVIRONMENT) || repo_fmt->work_tree) { static const char *gitdir; gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset); @@ -1629,7 +1638,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, return GIT_DIR_INVALID_GITFILE; default: if (die_on_error) - read_gitfile_error_die(error_code, dir->buf, NULL); + read_gitfile_error_die(error_code, dir->buf); else return GIT_DIR_INVALID_GITFILE; } @@ -1765,6 +1774,7 @@ int apply_repository_format(struct repository *repo, alternate_object_directories = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT)); } + repo->bare_cfg = format->is_bare; repo_set_hash_algo(repo, format->hash_algo); repo->objects = odb_new(repo, object_directory, alternate_object_directories); @@ -1788,32 +1798,6 @@ int apply_repository_format(struct repository *repo, return 0; } -/* - * Check the repository format version in the path found in repo_get_git_dir(repo), - * and die if it is a version we don't understand. Generally one would - * set_git_dir() before calling this, and use it only for "are we in a valid - * repo?". - * - * If successful and fmt is not NULL, fill fmt with data. - */ -static void check_and_apply_repository_format(struct repository *repo, - struct repository_format *fmt, - enum apply_repository_format_flags flags) -{ - struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; - struct strbuf err = STRBUF_INIT; - - if (!fmt) - fmt = &repo_fmt; - - check_repository_format_gently(repo_get_git_dir(repo), fmt, NULL); - if (apply_repository_format(repo, fmt, flags, &err) < 0) - die("%s", err.buf); - startup_info->have_repository = 1; - - clear_repository_format(&repo_fmt); -} - const char *enter_repo(struct repository *repo, const char *path, unsigned flags) { static struct strbuf validated_path = STRBUF_INIT; @@ -1887,9 +1871,17 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags } if (is_git_directory(".")) { + struct repository_format fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + set_git_dir(repo, ".", 0); - check_and_apply_repository_format(repo, NULL, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); + check_repository_format_gently(".", &fmt, NULL); + if (apply_repository_format(repo, &fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; + + clear_repository_format(&fmt); + strbuf_release(&err); return path; } @@ -1924,7 +1916,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; - const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -2050,6 +2041,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) if (startup_info->have_repository) { struct strbuf err = STRBUF_INIT; + const char *ref_backend_uri; + + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *format; + + free(repo_fmt.ref_storage_payload); + + parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload); + repo_fmt.ref_storage_format = ref_storage_format_by_name(format); + if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), format); + + free(format); + } if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) @@ -2075,25 +2085,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } - /* - * The env variable should override the repository config - * for 'extensions.refStorage'. - */ - ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); - if (ref_backend_uri) { - char *backend, *payload; - enum ref_storage_format format; - - parse_reference_uri(ref_backend_uri, &backend, &payload); - format = ref_storage_format_by_name(backend); - if (format == REF_STORAGE_FORMAT_UNKNOWN) - die(_("unknown ref storage format: '%s'"), backend); - repo_set_ref_storage_format(repo, format, payload); - - free(backend); - free(payload); - } - setup_original_cwd(repo); strbuf_release(&dir); @@ -2574,7 +2565,7 @@ static int create_default_files(struct repository *repo, repo_settings_set_shared_repository(repo, init_shared_repository); - is_bare_repository_cfg = !work_tree; + repo->bare_cfg = !work_tree; /* * We would have created the above under user's umask -- under @@ -2600,13 +2591,17 @@ static int create_default_files(struct repository *repo, } repo_config_set(repo, "core.filemode", filemode ? "true" : "false"); - if (is_bare_repository()) + if (is_bare_repository(repo)) repo_config_set(repo, "core.bare", "true"); else { + const char *value; + repo_config_set(repo, "core.bare", "false"); + /* allow template config file to override the default */ - if (repo_settings_get_log_all_ref_updates(repo) == LOG_REFS_UNSET) + if (repo_config_get_string_tmp(repo, "core.logallrefupdates", &value)) repo_config_set(repo, "core.logallrefupdates", "true"); + if (needs_work_tree_config(original_git_dir, work_tree)) repo_config_set(repo, "core.worktree", work_tree); } @@ -2728,8 +2723,7 @@ static int read_default_format_config(const char *key, const char *value, return ret; } -static void repository_format_configure(struct repository *repo, - struct repository_format *repo_fmt, +static void repository_format_configure(struct repository_format *repo_fmt, int hash, enum ref_storage_format ref_format) { struct default_format_config cfg = { @@ -2766,7 +2760,6 @@ static void repository_format_configure(struct repository *repo, } else if (cfg.hash != GIT_HASH_UNKNOWN) { repo_fmt->hash_algo = cfg.hash; } - repo_set_hash_algo(repo, repo_fmt->hash_algo); env = getenv("GIT_DEFAULT_REF_FORMAT"); if (repo_fmt->version >= 0 && @@ -2804,9 +2797,6 @@ static void repository_format_configure(struct repository *repo, free(backend); } - - repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format, - repo_fmt->ref_storage_payload); } int init_db(struct repository *repo, @@ -2820,6 +2810,7 @@ int init_db(struct repository *repo, int exist_ok = flags & INIT_DB_EXIST_OK; char *original_git_dir = real_pathdup(git_dir, 1); struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; if (real_git_dir) { struct stat st; @@ -2846,10 +2837,11 @@ int init_db(struct repository *repo, * config file, so this will not fail. What we are catching * is an attempt to reinitialize new repository with an old tool. */ - check_and_apply_repository_format(repo, &repo_fmt, - APPLY_REPOSITORY_FORMAT_HONOR_ENV); - - repository_format_configure(repo, &repo_fmt, hash, ref_storage_format); + check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL); + repository_format_configure(&repo_fmt, hash, ref_storage_format); + if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0) + die("%s", err.buf); + startup_info->have_repository = 1; /* * Ensure `core.hidedotfiles` is processed. This must happen after we @@ -2904,6 +2896,7 @@ int init_db(struct repository *repo, } clear_repository_format(&repo_fmt); + strbuf_release(&err); free(original_git_dir); return 0; } diff --git a/setup.h b/setup.h index 705d1d6ff79685..dcb654562a426b 100644 --- a/setup.h +++ b/setup.h @@ -38,7 +38,7 @@ int is_nonbare_repository_dir(struct strbuf *path); #define READ_GITFILE_ERR_TOO_LARGE 8 #define READ_GITFILE_ERR_MISSING 9 #define READ_GITFILE_ERR_IS_A_DIR 10 -void read_gitfile_error_die(int error_code, const char *path, const char *dir); +void read_gitfile_error_die(int error_code, const char *path); const char *read_gitfile_gently(const char *path, int *return_error_code); #define read_gitfile(path) read_gitfile_gently((path), NULL) const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); @@ -292,6 +292,12 @@ enum sharedrepo { int git_config_perm(const char *var, const char *value); struct startup_info { + /* + * Whether the user is asking us to treat the repository as bare via + * `git --bare`, even if it's not. + */ + bool force_bare_repository; + int have_repository; const char *prefix; const char *original_cwd; diff --git a/sha1dc_git.c b/sha1dc_git.c index 9b675a046ee699..fe58d7962a30c9 100644 --- a/sha1dc_git.c +++ b/sha1dc_git.c @@ -27,10 +27,9 @@ void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) /* * Same as SHA1DCUpdate, but adjust types to match git's usual interface. */ -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, size_t len) { const char *data = vdata; - /* We expect an unsigned long, but sha1dc only takes an int */ while (len > INT_MAX) { SHA1DCUpdate(ctx, data, INT_MAX); data += INT_MAX; diff --git a/sha1dc_git.h b/sha1dc_git.h index f6f880cabea382..0bcf1aa84b7241 100644 --- a/sha1dc_git.h +++ b/sha1dc_git.h @@ -15,7 +15,7 @@ void git_SHA1DCInit(SHA1_CTX *); #endif void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, size_t len); #define platform_SHA_IS_SHA1DC /* used by "test-tool sha1-is-sha1dc" */ diff --git a/submodule.c b/submodule.c index fd91201a92d7b0..93d03610726b8a 100644 --- a/submodule.c +++ b/submodule.c @@ -2579,7 +2579,7 @@ void absorb_git_dir_into_superproject(const char *path, if (err_code != READ_GITFILE_ERR_NOT_A_REPO) /* We don't know what broke here. */ - read_gitfile_error_die(err_code, path, NULL); + read_gitfile_error_die(err_code, path); /* * Maybe populated, but no git directory was found? diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 15eb44485cda3d..f77b3f9d70fe0d 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -250,6 +250,7 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv) double m[3][2], v[3][2]; uint64_t cumul; double cumul2; + int ntfs, hfs; if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) { file_mode = 0120000; @@ -276,8 +277,13 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv) names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' '))); } - for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) - for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) { + if (!the_repository->gitdir) + the_repository->gitdir = xstrdup(".git"); + + for (ntfs = 0; ntfs < 2; ntfs++) + for (hfs = 0; hfs < 2; hfs++) { + repo_config_values(the_repository)->protect_ntfs = ntfs; + repo_config_values(the_repository)->protect_hfs = hfs; cumul = 0; cumul2 = 0; for (i = 0; i < repetitions; i++) { @@ -285,18 +291,18 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv) for (j = 0; j < nr; j++) verify_path(names[j], file_mode); end = getnanotime(); - printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6); + printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", ntfs, hfs, (end-begin) / (double)1e6); cumul += end - begin; cumul2 += (end - begin) * (end - begin); } - m[protect_ntfs][protect_hfs] = cumul / (double)repetitions; - v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]); - printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6); + m[ntfs][hfs] = cumul / (double)repetitions; + v[ntfs][hfs] = my_sqrt(cumul2 / (double)repetitions - m[ntfs][hfs] * m[ntfs][hfs]); + printf("mean: %lfms, stddev: %lfms\n", m[ntfs][hfs] / (double)1e6, v[ntfs][hfs] / (double)1e6); } - for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) - for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) - printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]); + for (ntfs = 0; ntfs < 2; ntfs++) + for (hfs = 0; hfs < 2; hfs++) + printf("ntfs=%d/hfs=%d: %lf%% slower\n", ntfs, hfs, (m[ntfs][hfs] - m[0][0]) * 100 / m[0][0]); return 0; } diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index 790000fb26c270..fb16ec0176d7b4 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -13,13 +13,16 @@ static struct multi_pack_index *setup_midx(const char *object_dir) { + struct odb_source_files *files; struct odb_source *source; setup_git_directory(the_repository); source = odb_find_source(the_repository->objects, object_dir); if (!source) source = odb_add_to_alternates_memory(the_repository->objects, object_dir); - return load_multi_pack_index(source); + files = odb_source_files_downcast(source); + + return load_multi_pack_index(files->packed); } static int read_midx_file(const char *object_dir, const char *checksum, @@ -70,7 +73,7 @@ static int read_midx_file(const char *object_dir, const char *checksum, for (i = 0; i < m->num_packs; i++) printf("%s\n", m->pack_names[i]); - printf("object-dir: %s\n", m->source->path); + printf("object-dir: %s\n", m->source->base.path); if (show_objects) { struct object_id oid; diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index b16c0722c84aa8..fc49fafc3466f4 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -174,7 +174,7 @@ static int dump_table(struct reftable_merged_table *mt) static int dump_stack(const char *stackdir, uint32_t hash_id) { struct reftable_stack *stack = NULL; - struct reftable_write_options opts = { .hash_id = hash_id }; + struct reftable_stack_options opts = { .hash_id = hash_id }; struct reftable_merged_table *merged = NULL; int err = reftable_new_stack(&stack, stackdir, &opts); diff --git a/t/meson.build b/t/meson.build index 3219264fe7d497..7c3c070426dc9e 100644 --- a/t/meson.build +++ b/t/meson.build @@ -125,6 +125,7 @@ integration_tests = [ 't0090-cache-tree.sh', 't0091-bugreport.sh', 't0092-diagnose.sh', + 't0093-verify-cache-df-gap.sh', 't0095-bloom.sh', 't0100-previous.sh', 't0101-at-syntax.sh', @@ -576,6 +577,7 @@ integration_tests = [ 't4215-log-skewed-merges.sh', 't4216-log-bloom.sh', 't4217-log-limit.sh', + 't4219-log-follow-merge.sh', 't4252-am-options.sh', 't4253-am-keep-cr-dos.sh', 't4254-am-corrupt.sh', diff --git a/t/perf/p5311-pack-bitmaps-fetch.sh b/t/perf/p5311-pack-bitmaps-fetch.sh index 047efb995d647b..505062162270b4 100755 --- a/t/perf/p5311-pack-bitmaps-fetch.sh +++ b/t/perf/p5311-pack-bitmaps-fetch.sh @@ -4,16 +4,22 @@ test_description='performance of fetches from bitmapped packs' . ./perf-lib.sh test_fetch_bitmaps () { + argv=$1 + export argv + test_expect_success 'setup test directory' ' rm -fr * .git ' test_perf_default_repo - test_expect_success 'create bitmapped server repo' ' + test_expect_success "create bitmapped server repo ${argv:+($argv)}" ' git config pack.writebitmaps true && - git config pack.writeBitmapLookupTable '"$1"' && - git repack -ad + git repack -adF $argv + ' + + test_size "size of bitmapped pack ${argv:+($argv)}" ' + test_file_size .git/objects/pack/pack-*.pack ' # simulate a fetch from a repository that last fetched N days ago, for @@ -21,7 +27,7 @@ test_fetch_bitmaps () { # and assume the first entry in the chain that is N days older than the current # HEAD is where the HEAD would have been then. for days in 1 2 4 8 16 32 64 128; do - title=$(printf '%10s' "($days days)") + title=$(printf '%10s' "($days days${argv:+, $argv})") test_expect_success "setup revs from $days days ago" ' now=$(git log -1 --format=%ct HEAD) && then=$(($now - ($days * 86400))) && @@ -32,7 +38,7 @@ test_fetch_bitmaps () { } >revs ' - test_perf "server $title (lookup=$1)" ' + test_perf "server $title" ' git pack-objects --stdout --revs \ --thin --delta-base-offset \ tmp.pack @@ -42,13 +48,15 @@ test_fetch_bitmaps () { test_file_size tmp.pack ' - test_perf "client $title (lookup=$1)" ' + test_perf "client $title" ' git index-pack --stdin --fix-thin /dev/null + done + ) +" + +test_perf "branch --remotes (many unrelated refs)" " + ( + cd scoped && + for i in \$(test_seq $test_iteration_count); do + git branch --remotes --format='%(refname)' >/dev/null + done + ) +" + +test_perf "tag (many unrelated refs)" " + ( + cd scoped && + for i in \$(test_seq $test_iteration_count); do + git tag --format='%(refname)' >/dev/null + done + ) +" + test_done diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index dfbcdddbcc1f0f..6356e9ec7272a2 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -27,7 +27,7 @@ test_expect_success 'bad setup: invalid .git file format' ' test_expect_success 'bad setup: invalid .git file path' ' echo "gitdir: $REAL.not" >.git && test_must_fail git rev-parse 2>.err && - test_grep "not a git repository" .err + test_grep "gitfile does not point to a valid repository" .err ' test_expect_success 'final setup + check rev-parse --git-dir' ' diff --git a/t/t0093-direct-index-write.pl b/t/t0093-direct-index-write.pl new file mode 100644 index 00000000000000..2881a3ebb21dcd --- /dev/null +++ b/t/t0093-direct-index-write.pl @@ -0,0 +1,38 @@ +#!/usr/bin/perl +# +# Build a v2 index file from entries listed on stdin. +# Each line: "octalmode hex-oid name" +# Output: binary index written to stdout. +# +# This bypasses all D/F safety checks in add_index_entry(), simulating +# what happens when code uses ADD_CACHE_JUST_APPEND to bulk-load entries. +use strict; +use warnings; +use Digest::SHA qw(sha1 sha256); + +my $hash_algo = $ENV{'GIT_DEFAULT_HASH'} || 'sha1'; +my $hash_func = $hash_algo eq 'sha256' ? \&sha256 : \&sha1; + +my @entries; +while (my $line = ) { + chomp $line; + my ($mode, $oid_hex, $name) = split(/ /, $line, 3); + push @entries, [$mode, $oid_hex, $name]; +} + +my $body = "DIRC" . pack("NN", 2, scalar @entries); + +for my $ent (@entries) { + my ($mode, $oid_hex, $name) = @{$ent}; + # 10 x 32-bit stat fields (zeroed), with mode in position 7 + my $stat = pack("N10", 0, 0, 0, 0, 0, 0, oct($mode), 0, 0, 0); + my $oid = pack("H*", $oid_hex); + my $flags = pack("n", length($name) & 0xFFF); + my $entry = $stat . $oid . $flags . $name . "\0"; + # Pad to 8-byte boundary + while (length($entry) % 8) { $entry .= "\0"; } + $body .= $entry; +} + +binmode STDOUT; +print $body . $hash_func->($body); diff --git a/t/t0093-verify-cache-df-gap.sh b/t/t0093-verify-cache-df-gap.sh new file mode 100755 index 00000000000000..0b6829d805269d --- /dev/null +++ b/t/t0093-verify-cache-df-gap.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +test_description='verify_cache() must catch non-adjacent D/F conflicts + +Ensure that verify_cache() can complain about bad entries like: + + docs <-- submodule + docs-internal/... <-- sorts here because "-" < "/" + docs/... <-- D/F conflict with "docs" above, not adjacent + +In order to test verify_cache, we directly construct a corrupt index +(bypassing the D/F safety checks in add_index_entry) and verify that +write-tree rejects it. +' + +. ./test-lib.sh + +if ! test_have_prereq PERL +then + skip_all='skipping verify_cache D/F tests; Perl not available' + test_done +fi + +# Build a v2 index from entries on stdin, bypassing D/F checks. +# Each line: "octalmode hex-oid name" (entries must be pre-sorted). +build_corrupt_index () { + perl "$TEST_DIRECTORY/t0093-direct-index-write.pl" >"$1" +} + +test_expect_success 'setup objects' ' + test_commit base && + BLOB=$(git rev-parse HEAD:base.t) && + SUB_COMMIT=$(git rev-parse HEAD) +' + +test_expect_success 'adjacent D/F conflict is caught by verify_cache' ' + cat >index-entries <<-EOF && + 0160000 $SUB_COMMIT docs + 0100644 $BLOB docs/requirements.txt + EOF + build_corrupt_index .git/index err && + test_grep "You have both docs and docs/requirements.txt" err +' + +test_expect_success 'non-adjacent D/F conflict is caught by verify_cache' ' + cat >index-entries <<-EOF && + 0160000 $SUB_COMMIT docs + 0100644 $BLOB docs-internal/README.md + 0100644 $BLOB docs/requirements.txt + EOF + build_corrupt_index .git/index err && + test_grep "You have both docs and docs/requirements.txt" err +' + +test_done diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index 74bfa2e9ba060d..bbbf6fa4228cd7 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -519,4 +519,25 @@ test_expect_success 'symref transaction supports false symlink config' ' test_cmp expect actual ' +test_expect_success SYMLINKS,!MINGW,!WITH_BREAKING_CHANGES 'core.preferSymlinkRefs can be set up via onbranch condition' ' + test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" && + test_when_finished "rm -f .git/include" && + git update-ref refs/heads/new @ && + cat >.git/include <<-\EOF && + [core] + preferSymlinkRefs = true + EOF + test_config includeIf.onbranch:"$(git branch --show-current)".path \ + "$(pwd)/.git/include" && + cat >stdin <<-EOF && + start + symref-create TEST_SYMREF_HEAD refs/heads/new + prepare + commit + EOF + git update-ref --no-deref --stdin .git/include <<-\EOF && + [reftable] + blockSize = 123 + EOF + git config includeIf.onbranch:master.path "$(pwd)/.git/include" && + git refs optimize && + test-tool dump-reftable -b .git/reftable/*.ref >stats && + test_grep "block_size: 123" stats + ) +' + test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index de076293b62a76..4bc82dd9682954 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -49,6 +49,9 @@ test_expect_success 'setup' ' example sha1:ddd3f836d3e3fbb7ae289aa9ae83536f76956399 example sha256:b44fe1fe65589848253737db859bd490453510719d7424daab03daf0767b85ae + + large5GB sha1:0be2be10a4c8764f32c4bf372a98edc731a4b204 + large5GB sha256:dc18ca621300c8d3cfa505a275641ebab00de189859e022a975056882d313e64 EOF ' @@ -258,4 +261,40 @@ test_expect_success '--stdin outside of repository (uses default hash)' ' test_cmp expect actual ' +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT \ + 'files over 4GB hash literally' ' + test-tool genzeros $((5*1024*1024*1024)) >big && + test_oid large5GB >expect && + git hash-object --stdin --literally actual && + test_cmp expect actual +' + +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT \ + 'files over 4GB hash correctly via --stdin' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + git hash-object --stdin actual && + test_cmp expect actual +' + +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT \ + 'files over 4GB hash correctly' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + git hash-object -- big >actual && + test_cmp expect actual +' + +# This clean filter does nothing, other than excercising the interface. +# We ensure that cleaning doesn't mangle large files on 64-bit Windows. +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT \ + 'hash filtered files over 4GB correctly' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + test_config filter.null-filter.clean "cat" && + echo "big filter=null-filter" >.gitattributes && + git hash-object -- big >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index f059d63e38055f..c7c814d8585e19 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -178,6 +178,18 @@ test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' test_must_fail git reflog exists $outside ' +test_expect_success 'core.logAllRefUpdates can be set up via onbranch condition' ' + test_when_finished "git update-ref -d $outside" && + test_when_finished "rm -f .git/include" && + cat >.git/include <<-\EOF && + [core] + logAllRefUpdates = always + EOF + test_config includeIf.onbranch:main.path "$(pwd)/.git/include" && + git update-ref $outside $A && + git reflog exists $outside +' + test_expect_success "create $m (by HEAD)" ' git update-ref HEAD $A && test $A = $(git show-ref -s --verify $m) diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index 39bb77dda0c327..ae8c22c81749ba 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -155,4 +155,62 @@ test_expect_success 'git repo info -h shows only repo info usage' ' test_grep ! "git repo structure" actual ' +# Helper function to test path keys in both absolute and relative formats. +# $1: label for the test +# $2: field_name (e.g., commondir) +# $3: expected_dir (the directory name, e.g., .git or custom-common) +# $4: init_command (extra setup like exporting env vars) +test_repo_info_path () { + label=$1 + field_name=$2 + expected_dir=$3 + init_command=$4 + + test_expect_success "absolute: $label" ' + test_when_finished "rm -rf repo" && + git init repo && + ( + mkdir -p repo/sub && + cd repo/sub && + ROOT="$(test-tool path-utils real_path ..)" && export ROOT && + eval "$init_command" && + echo "path.$field_name.absolute=$ROOT/$expected_dir" >expect && + git repo info "path.$field_name.absolute" >actual && + test_cmp expect actual + ) + ' + + test_expect_success "relative: $label" ' + test_when_finished "rm -rf repo" && + git init repo && + ( + mkdir -p repo/sub && + cd repo/sub && + ROOT="$(test-tool path-utils real_path ..)" && export ROOT && + eval "$init_command" && + echo "path.$field_name.relative=../$expected_dir" >expect && + git repo info "path.$field_name.relative" >actual && + test_cmp expect actual + ) + ' +} + +test_repo_info_path 'commondir standard' 'commondir' '.git' + +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \ + 'custom-common' \ + 'GIT_COMMON_DIR="$ROOT/custom-common" && export GIT_COMMON_DIR && + GIT_DIR="../.git" && export GIT_DIR && + git init --bare "$ROOT/custom-common"' + +test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \ + '.git' \ + 'GIT_DIR="../.git" && export GIT_DIR' + +test_repo_info_path 'gitdir standard' 'gitdir' '.git' + +test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ + '.git' \ + 'GIT_DIR="../.git" && export GIT_DIR' + test_done diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index f0bbc476ff8f4d..9ae06b63db891b 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -244,7 +244,7 @@ testrebase () { git rebase --quit && test_when_finished git stash drop && test_path_is_missing $dotest/autostash && - ! grep dirty file3 && + test_path_is_missing file3 && git stash show -p >actual && test_cmp expect actual && git reset --hard && diff --git a/t/t4219-log-follow-merge.sh b/t/t4219-log-follow-merge.sh new file mode 100755 index 00000000000000..e370f829556f71 --- /dev/null +++ b/t/t4219-log-follow-merge.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='Test --follow follows renames across merges' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +test_expect_success 'setup subtree-merged repository' ' + git init inner && + echo inner >inner/inner.txt && + git -C inner add inner.txt && + git -C inner commit -m "inner init" && + + git init outer && + echo outer >outer/outer.txt && + git -C outer add outer.txt && + git -C outer commit -m "outer init" && + + git -C outer fetch ../inner master && + git -C outer merge -s ours --no-commit --allow-unrelated-histories \ + FETCH_HEAD && + git -C outer read-tree --prefix=inner/ -u FETCH_HEAD && + git -C outer commit -m "Merge inner repo into inner/ subdirectory" +' + +test_expect_success '--follow finds the pre-merge commit through a subtree merge' ' + git -C outer log --follow --pretty=tformat:%s inner/inner.txt >actual && + echo "inner init" >expect && + test_cmp expect actual +' + +test_expect_success 'setup merge of two branches that both renamed a file to README' ' + git init foo && + mkdir foo/foo && + echo "foo readme" >foo/foo/README && + git -C foo add foo/README && + git -C foo commit -m "add foo README" && + + git -C foo mv foo/README README && + git -C foo commit -m "promote foo README to toplevel" && + + echo "foo c" >foo/foo.c && + git -C foo add foo.c && + git -C foo commit -m "add foo C impl" && + + git init bar && + mkdir bar/bar && + echo "bar readme" >bar/bar/README && + git -C bar add bar/README && + git -C bar commit -m "add bar README" && + + git -C bar mv bar/README README && + git -C bar commit -m "promote bar README to toplevel" && + + echo "bar c" >bar/bar.c && + git -C bar add bar.c && + git -C bar commit -m "add bar C impl" && + + git -C foo fetch ../bar master && + git -C foo merge -s ours --no-commit --allow-unrelated-histories \ + FETCH_HEAD && + git -C foo checkout FETCH_HEAD -- bar.c && + git -C foo commit -m "merge bar into foo" +' + +test_expect_success '--follow follows renames across both sides of a merge' ' + git -C foo log --follow --pretty=tformat:%s README >actual && + sort actual >actual.sorted && + cat >expect <<-\EOF && + add bar README + add foo README + promote bar README to toplevel + promote foo README to toplevel + EOF + test_cmp expect actual.sorted +' + +test_expect_success 'setup diamond with renames on both sides of a fork' ' + git init diamond && + test_lines="line 1\nline 2\nline 3\nline 4\nline 5\n" && + + printf "$test_lines" >diamond/path0 && + git -C diamond add path0 && + git -C diamond commit -m "A: add path0" && + + git -C diamond checkout -b upper && + printf "line 1\nline 2\nline 3 modified by B\nline 4\nline 5\n" \ + >diamond/path0 && + git -C diamond commit -am "B: modify path0 on upper" && + git -C diamond mv path0 path1 && + git -C diamond commit -m "X: rename path0 to path1" && + + git -C diamond checkout -b lower master && + printf "line 1\nline 2\nline 3 modified by C\nline 4\nline 5\n" \ + >diamond/path0 && + git -C diamond commit -am "C: modify path0 on lower" && + git -C diamond mv path0 path2 && + git -C diamond commit -m "Y: rename path0 to path2" && + + git -C diamond checkout upper && + git -C diamond merge -s ours --no-commit lower && + git -C diamond rm path1 && + printf "line 1\nline 2\nline 3 merged\nline 4\nline 5\n" \ + >diamond/path && + git -C diamond add path && + git -C diamond commit -m "M: merge with rename to path" && + + printf "line 1\nline 2\nline 3 merged again\nline 4\nline 5\n" \ + >diamond/path && + git -C diamond commit -am "Z: modify path" +' + +test_expect_success '--follow follows renames through a fork in a single history' ' + git -C diamond log --follow --pretty=tformat:%s path >actual && + sort actual >actual.sorted && + cat >expect <<-\EOF && + A: add path0 + B: modify path0 on upper + C: modify path0 on lower + X: rename path0 to path1 + Y: rename path0 to path2 + Z: modify path + EOF + test_cmp expect actual.sorted +' + +test_done diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index efeb71593bf7f6..e24347848ee9d4 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -577,6 +577,42 @@ test_bitmap_cases sane_unset GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL +test_expect_success 'path-walk repack can write and use bitmap indexes' ' + test_when_finished "rm -rf path-walk-bitmap" && + git init path-walk-bitmap && + ( + cd path-walk-bitmap && + test_commit first && + test_commit second && + test_commit third && + + git repack -a -d -b --path-walk && + git rev-list --test-bitmap --use-bitmap-index HEAD && + + git rev-parse HEAD >in && + + git rev-list --objects --no-object-names HEAD >expect.raw && + sort expect.raw >expect && + + for reuse in true false + do + : >trace.txt && + + GIT_TRACE2_EVENT="$(pwd)/trace.txt" \ + git -c pack.allowPackReuse=$reuse pack-objects \ + --stdout --revs --path-walk --use-bitmap-index \ + out.pack && + test_grep "\"category\":\"bitmap\",\"key\":\"bitmap/hits\"" trace.txt && + + git index-pack out.pack && + + list_packed_objects out.idx >actual.raw && + sort actual.raw >actual && + test_cmp expect actual || return 1 + done + ) +' + test_expect_success 'incremental repack fails when bitmaps are requested' ' test_commit more-1 && test_must_fail git repack -d 2>err && diff --git a/t/t5320-delta-islands.sh b/t/t5320-delta-islands.sh index 2c961c70963051..9b28344a0a3750 100755 --- a/t/t5320-delta-islands.sh +++ b/t/t5320-delta-islands.sh @@ -53,6 +53,35 @@ test_expect_success 'separate islands disallows delta' ' ! is_delta_base $two $one ' +test_expect_success 'path-walk island repack respects islands' ' + GIT_TRACE2_EVENT="$(pwd)/trace.path-walk-islands" \ + git -c "pack.island=refs/heads/(.*)" repack -adfi \ + --path-walk 2>err && + test_region pack-objects path-walk trace.path-walk-islands && + test_grep ! "cannot use --delta-islands with --path-walk" err && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'path-walk island bitmap repack respects islands' ' + GIT_TRACE2_EVENT="$(pwd)/trace.path-walk-island-bitmap" \ + git -c "pack.island=refs/heads/(.*)" repack -a -d -f -i -b \ + --path-walk 2>err && + test_region pack-objects path-walk trace.path-walk-island-bitmap && + test_path_is_file .git/objects/pack/*.bitmap && + git rev-list --test-bitmap --use-bitmap-index one && + test_grep ! "cannot use --delta-islands with --path-walk" err && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'path-walk same island allows delta' ' + GIT_TRACE2_EVENT="$(pwd)/trace.path-walk-same-island" \ + git -c "pack.island=refs/heads" repack -adfi --path-walk && + test_region pack-objects path-walk trace.path-walk-same-island && + is_delta_base $one $two +' + test_expect_success 'same island allows delta' ' git -c "pack.island=refs/heads" repack -adfi && is_delta_base $one $two diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index eca9a973b5cb16..6f0ae1bdd7981b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -140,6 +140,16 @@ test_expect_success "fetch test remote HEAD change" ' ) ' +test_expect_success "fetch test default followRemoteHEAD never" ' + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two fetch.followRemoteHEAD "never" && + GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch && + # Confirm that we do not even ask for HEAD when we are + # not going to act on it. + test_grep ! "ref-prefix HEAD" trace.out && + test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD +' + test_expect_success "fetch test followRemoteHEAD never" ' git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && test_config -C two remote.origin.followRemoteHEAD "never" && @@ -150,6 +160,21 @@ test_expect_success "fetch test followRemoteHEAD never" ' test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD ' +test_expect_success "fetch test default followRemoteHEAD warn no change" ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two fetch.followRemoteHEAD "warn" && + git -C two fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have ${SQ}other${SQ} locally." >expect && + test_cmp expect output && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" +' + test_expect_success "fetch test followRemoteHEAD warn no change" ' git -C two rev-parse --verify refs/remotes/origin/other && git -C two remote set-head origin other && @@ -165,6 +190,17 @@ test_expect_success "fetch test followRemoteHEAD warn no change" ' test "z$head" = "z$branch" ' +test_expect_success "fetch test default followRemoteHEAD warn create" ' + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two fetch.followRemoteHEAD "warn" && + git -C two rev-parse --verify refs/remotes/origin/main && + output=$(git -C two fetch) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" +' + test_expect_success "fetch test followRemoteHEAD warn create" ' git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && test_config -C two remote.origin.followRemoteHEAD "warn" && @@ -176,6 +212,18 @@ test_expect_success "fetch test followRemoteHEAD warn create" ' test "z$head" = "z$branch" ' +test_expect_success "fetch test default followRemoteHEAD warn detached" ' + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + git -C two update-ref refs/remotes/origin/HEAD HEAD && + HEAD=$(git -C two log --pretty="%H") && + test_config -C two fetch.followRemoteHEAD "warn" && + git -C two fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have a detached HEAD pointing to" \ + "${SQ}${HEAD}${SQ} locally." >expect && + test_cmp expect output +' + test_expect_success "fetch test followRemoteHEAD warn detached" ' git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && git -C two update-ref refs/remotes/origin/HEAD HEAD && @@ -188,6 +236,19 @@ test_expect_success "fetch test followRemoteHEAD warn detached" ' test_cmp expect output ' +test_expect_success "fetch test default followRemoteHEAD warn quiet" ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two fetch.followRemoteHEAD "warn" && + output=$(git -C two fetch --quiet) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" +' + test_expect_success "fetch test followRemoteHEAD warn quiet" ' git -C two rev-parse --verify refs/remotes/origin/other && git -C two remote set-head origin other && @@ -229,6 +290,18 @@ test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is di test "z$head" = "z$branch" ' +test_expect_success "fetch test default followRemoteHEAD always" ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two fetch.followRemoteHEAD "always" && + git -C two fetch && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" +' + test_expect_success "fetch test followRemoteHEAD always" ' git -C two rev-parse --verify refs/remotes/origin/other && git -C two remote set-head origin other && @@ -241,6 +314,28 @@ test_expect_success "fetch test followRemoteHEAD always" ' test "z$head" = "z$branch" ' +test_expect_success 'per-remote followRemoteHEAD takes priority over fetch default' ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two fetch.followRemoteHEAD "never" && + test_config -C two remote.origin.followRemoteHEAD "always" && + git -C two fetch && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" +' + +test_expect_success 'default followRemoteHEAD does not kick in with refspecs' ' + git -C two remote set-head origin other && + test_config -C two fetch.followRemoteHEAD always && + git -C two fetch origin refs/heads/main:refs/remotes/origin/main && + echo refs/remotes/origin/other >expect && + git -C two symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual +' + test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' git -C two remote set-head origin other && test_config -C two remote.origin.followRemoteHEAD always && @@ -250,7 +345,18 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' test_cmp expect actual ' +test_expect_success 'default followRemoteHEAD create does not overwrite dangling symref' ' + test_when_finished "git -C two remote remove custom-head" && + git -C two remote add -m does-not-exist custom-head ../one && + test_config -C two fetch.followRemoteHEAD create && + git -C two fetch custom-head && + echo refs/remotes/custom-head/does-not-exist >expect && + git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual && + test_cmp expect actual +' + test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' ' + test_when_finished "git -C two remote remove custom-head" && git -C two remote add -m does-not-exist custom-head ../one && test_config -C two remote.custom-head.followRemoteHEAD create && git -C two fetch custom-head && diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 1c2805accac636..905052072db7c1 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -97,6 +97,30 @@ test_expect_success 'partial fetch inherits filter settings' ' test_line_count = 5 observed ' +test_expect_success 'partial fetch does not spawn rev-list connectivity check' ' + test_when_finished "rm -rf connectivity-remote connectivity-client" && + git init connectivity-remote && + test_commit -C connectivity-remote one && + git -C connectivity-remote config uploadpack.allowfilter 1 && + git -C connectivity-remote config uploadpack.allowanysha1inwant 1 && + + git clone --no-checkout --filter=blob:none \ + "file://$(pwd)/connectivity-remote" connectivity-client && + + # When doing a partial fetch where all tips are part of a promisor pack + # we want to skip the connectivity check, as these objects are allowed + # to not be fully connected. + test_commit -C connectivity-remote two && + GIT_TRACE2_EVENT="$(pwd)/partial.trace" git -C connectivity-client fetch origin && + test_subcommand_flex ! git rev-list --objects --stdin .advertisedAs" == "lop". + git -C client config get --all --show-names --regexp \ + "remote\..*\.advertisedas" >full_names && + test_line_count = 1 full_names && + REMOTE_NAME=$(sed "s/^remote\.\(.*\)\.advertisedas .*$/\1/" full_names) && + + # Check ".url" and ".promisor" values + printf "%s\n" "$TRASH_DIRECTORY_URL/lop" "true" >expect && + git -C client config "remote.$REMOTE_NAME.url" >actual && + git -C client config "remote.$REMOTE_NAME.promisor" >>actual && + test_cmp expect actual && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with named URL allowlisted and no pre-configured remote" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + GIT_NO_LAZY_FETCH=0 git clone \ + -c promisor.acceptfromserver=None \ + -c promisor.acceptFromServerUrl="cdn=$ENCODED_TRASH_DIRECTORY_URL/*" \ + --no-local --filter="blob:limit=5k" server client && + + # Check that a remote has been auto-created with the right "cdn" name and fields. + printf "%s\n" "$TRASH_DIRECTORY_URL/lop" "true" "lop" >expect && + git -C client config "remote.cdn.url" >actual && + git -C client config "remote.cdn.promisor" >>actual && + git -C client config "remote.cdn.advertisedAs" >>actual && + test_cmp expect actual && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with URL allowlisted but colliding name" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + GIT_NO_LAZY_FETCH=0 git clone -c remote.cdn.promisor=true \ + -c remote.cdn.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.cdn.url="https://example.com/cdn" \ + -c promisor.acceptfromserver=None \ + -c promisor.acceptFromServerUrl="cdn=$ENCODED_TRASH_DIRECTORY_URL/*" \ + --no-local --filter="blob:limit=5k" server client && + + # Check that a remote has been auto-created with the right "cdn-1" name and fields. + printf "%s\n" "$TRASH_DIRECTORY_URL/lop" "true" "lop" >expect && + git -C client config "remote.cdn-1.url" >actual && + git -C client config "remote.cdn-1.promisor" >>actual && + git -C client config "remote.cdn-1.advertisedAs" >>actual && + test_cmp expect actual && + + # Check that the original "cdn" remote was not overwritten. + printf "%s\n" "https://example.com/cdn" "true" >expect && + git -C client config "remote.cdn.url" >actual && + git -C client config "remote.cdn.promisor" >>actual && + test_cmp expect actual && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with URL allowlisted and reusable remote" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.cdn.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.cdn.url="$TRASH_DIRECTORY_URL/lop" \ + -c promisor.acceptfromserver=None \ + -c promisor.acceptFromServerUrl="cdn=$ENCODED_TRASH_DIRECTORY_URL/*" \ + --no-local --filter="blob:limit=5k" server client && + + # Check that the existing "cdn" remote has been properly updated. + printf "%s\n" "$TRASH_DIRECTORY_URL/lop" "true" "lop" "+refs/heads/*:refs/remotes/lop/*" >expect && + git -C client config "remote.cdn.url" >actual && + git -C client config "remote.cdn.promisor" >>actual && + git -C client config "remote.cdn.advertisedAs" >>actual && + git -C client config "remote.cdn.fetch" >>actual && + test_cmp expect actual && + + # Check that no new "cdn-1" remote has been created. + test_must_fail git -C client config "remote.cdn-1.url" && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with invalid promisor.acceptFromServerUrl" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + # As "bad name" contains a space, which is not a valid remote name, + # the pattern should be rejected with a warning and no remote created. + GIT_NO_LAZY_FETCH=0 git clone \ + -c promisor.acceptfromserver=None \ + -c "promisor.acceptFromServerUrl=bad name=https://example.com/*" \ + --no-local --filter="blob:limit=5k" server client 2>err && + + # Check that a warning was emitted + test_grep "invalid remote name '\''bad name'\''" err && + + # Check that no remote was auto-created + test_must_fail git -C client config get --regexp "remote\..*\.advertisedas" && + + # Check that the largest object is not missing on the server + check_missing_objects server 0 "" && + + # Reinitialize server so that the largest object is missing again + initialize_server 1 "$oid" +' + test_expect_success "clone with promisor.sendFields" ' git -C server config promisor.advertise true && test_when_finished "rm -rf client" && diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 0242b5bf7abedb..91cbb8775d0a5a 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo test_cmp expect actual ' +test_expect_success 'status.compareBranches behind both upstream and push' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature13 upstream/main && + (cd test && advance work13) && + git -C test push origin && + git -C test branch --set-upstream-to upstream/ahead && + git -C test reset --hard HEAD^ && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature13 + Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull" to update your local branch) + + Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull origin feature13" to update your local branch) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with remapped push and behind push branch' ' + test_config -C test remote.pushDefault origin && + test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 && + test_config -C test status.compareBranches "@{push}" && + git -C test checkout -b feature14 upstream/main && + (cd test && advance work14) && + git -C test push && + git -C test reset --hard HEAD^ && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature14 + Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull origin remapped14" to update your local branch) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with behind push branch and no upstream' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{push}" && + git -C test checkout --no-track -b feature15 upstream/main && + (cd test && advance work15) && + git -C test push origin && + git -C test reset --hard HEAD^ && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature15 + Your branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull origin feature15" to update your local branch) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' ' + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature16 origin/main && + (cd test && advance work16) && + git -C test push origin HEAD:main && + git -C test reset --hard HEAD^ && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature16 + Your branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull" to update your local branch) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' ' + test_config -C test push.default current && + test_config -C test remote.imported.url ../. && + test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" && + test_config -C test branch.feature17.pushRemote imported && + test_config -C test status.compareBranches "@{push}" && + git -C test fetch imported && + git -C test checkout --no-track -b feature17 refs/imported/imported/main && + (cd test && advance work17) && + git -C test push imported HEAD:feature17 && + git -C test fetch imported && + git -C test reset --hard HEAD^ && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature17 + Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + test_done diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index e18d5a227d54f7..81b645bb3bdc5b 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh @@ -1525,4 +1525,58 @@ test_expect_success 'submodule/directory preliminary conflict' ' ) ' +# Testcase: submodule/directory conflict with duplicate tree entries +# One side has a path as a gitlink (submodule). The other side replaces +# the gitlink with a directory. A third-party tool creates a tree on the +# submodule side that has *both* a gitlink and a tree entry for the same +# path (adding a file inside the submodule path ignoring that there's a +# gitlink there). collect_merge_info_callback() should detect the +# duplicate and abort rather than silently corrupting its bookkeeping. + +test_expect_success 'duplicate tree entries trigger an error' ' + test_when_finished "rm -rf duplicate-entry" && + git init duplicate-entry && + ( + cd duplicate-entry && + + # Base commit: "docs" is a gitlink (submodule) + empty_tree=$(git mktree file.txt && + git add file.txt && + git commit -m base && + + # side1: remove the gitlink, replace with a directory + git checkout -b side1 && + git rm --cached docs && + mkdir -p docs && + echo hello >docs/requirements.txt && + git add docs/requirements.txt && + git commit -m "side1: submodule to directory" && + + # side2: keep the gitlink but craft a tree that also + # contains a tree entry for "docs" (simulating a tool + # that adds files inside a submodule path without + # removing the gitlink first). + git checkout main && + git checkout -b side2 && + blob_oid=$(echo world | git hash-object -w --stdin) && + docs_tree=$(printf "100644 blob %s\trequirements.txt\n" \ + "$blob_oid" | git mktree) && + cur_tree=$(git rev-parse HEAD^{tree}) && + git cat-file -p $cur_tree >tree-listing && + printf "040000 tree %s\tdocs\n" "$docs_tree" >>tree-listing && + new_tree=$(git mktree err && + test_grep "duplicate entries" err + ) +' + test_done diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh index 8cc86522b27d9b..69a17a9d139b83 100755 --- a/t/t7450-bad-git-dotfiles.sh +++ b/t/t7450-bad-git-dotfiles.sh @@ -350,7 +350,7 @@ test_expect_success 'git dirs of sibling submodules must not be nested' ' test_expect_success 'submodule git dir nesting detection must work with parallel cloning' ' test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err && cat err && - grep -E "(already exists|is inside git dir|not a git repository)" err && + grep -E "(already exists|is inside git dir|does not point to a valid repository)" err && { test_path_is_missing .git/modules/hippo/HEAD || test_path_is_missing .git/modules/hippo/hooks/HEAD diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 08e82f79140841..aca4b6d33262df 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -224,7 +224,7 @@ test_expect_success 'status when splitting a commit' ' COMMIT3=$(git rev-parse --short split_commit) && test_commit four_split main.txt four && COMMIT4=$(git rev-parse --short split_commit) && - FAKE_LINES="1 edit 2 3" && + FAKE_LINES="reword 1 edit 2 fixup_-C 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~3) && @@ -233,10 +233,10 @@ test_expect_success 'status when splitting a commit' ' cat >expected <todo <<-EOF && + edit several_edits^^ # two_edits + edit several_edits^ # three_edits + merge $(git rev-parse main) $(git rev-parse several_edits) + EOF + ( + set_replace_editor todo && + git rebase -i HEAD~3 + ) && git commit --amend -m "c" && git rebase --continue && git commit --amend -m "d" && @@ -477,7 +483,7 @@ Last commands done (2 commands done): edit $COMMIT2 # two_edits edit $COMMIT3 # three_edits Next command to do (1 remaining command): - pick $COMMIT4 # four_edits + merge $(git rev-parse --short main) $COMMIT4 (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) @@ -525,14 +531,21 @@ EOF test_expect_success 'status: (split first edit) second edit and split' ' git reset --hard several_edits && - FAKE_LINES="edit 1 edit 2 3" && - export FAKE_LINES && test_when_finished "git rebase --abort" && COMMIT2=$(git rev-parse --short several_edits^^) && COMMIT3=$(git rev-parse --short several_edits^) && COMMIT4=$(git rev-parse --short several_edits) && + cat >todo <<-EOF && + edit several_edits^^ # two_edits + edit several_edits^ # three_edits + reset $(git rev-parse main) + merge -C several_edits topic # title + EOF ONTO=$(git rev-parse --short HEAD~3) && - git rebase -i HEAD~3 && + ( + set_replace_editor todo && + git rebase -i HEAD~3 + ) && git reset HEAD^ && git add main.txt && git commit --amend -m "f" && @@ -543,8 +556,9 @@ interactive rebase in progress; onto $ONTO Last commands done (2 commands done): edit $COMMIT2 # two_edits edit $COMMIT3 # three_edits -Next command to do (1 remaining command): - pick $COMMIT4 # four_edits +Next commands to do (2 remaining commands): + reset $(git rev-parse --short main) + merge -C $COMMIT4 topic # title (use "git rebase --edit-todo" to view and edit) You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (Once your working directory is clean, run "git rebase --continue") @@ -563,14 +577,21 @@ EOF test_expect_success 'status: (split first edit) second edit and amend' ' git reset --hard several_edits && - FAKE_LINES="edit 1 edit 2 3" && - export FAKE_LINES && test_when_finished "git rebase --abort" && + git branch cafe main && COMMIT2=$(git rev-parse --short several_edits^^) && COMMIT3=$(git rev-parse --short several_edits^) && - COMMIT4=$(git rev-parse --short several_edits) && + cat >todo <<-EOF && + edit several_edits^^ # two_edits + edit several_edits^ # three_edits + update-ref refs/heads/main + reset cafe + EOF ONTO=$(git rev-parse --short HEAD~3) && - git rebase -i HEAD~3 && + ( + set_replace_editor todo && + git rebase -i HEAD~3 + ) && git reset HEAD^ && git add main.txt && git commit --amend -m "g" && @@ -581,8 +602,9 @@ interactive rebase in progress; onto $ONTO Last commands done (2 commands done): edit $COMMIT2 # two_edits edit $COMMIT3 # three_edits -Next command to do (1 remaining command): - pick $COMMIT4 # four_edits +Next commands to do (2 remaining commands): + update-ref refs/heads/main + reset cafe (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) diff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c index fdb5b11a2034f2..19a3ac8b809410 100644 --- a/t/unit-tests/lib-reftable.c +++ b/t/unit-tests/lib-reftable.c @@ -25,11 +25,12 @@ static int strbuf_writer_flush(void *arg UNUSED) } struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf, + enum reftable_hash hash_id, struct reftable_write_options *opts) { struct reftable_writer *writer; int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush, - buf, opts); + buf, hash_id, opts); cl_assert(!ret); return writer; } @@ -39,6 +40,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t nrefs, struct reftable_log_record *logs, size_t nlogs, + enum reftable_hash hash_id, struct reftable_write_options *_opts) { struct reftable_write_options opts = { 0 }; @@ -66,7 +68,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, min = ui; } - writer = cl_reftable_strbuf_writer(buf, &opts); + writer = cl_reftable_strbuf_writer(buf, hash_id, &opts); ret = reftable_writer_set_limits(writer, min, max); cl_assert(!ret); @@ -88,7 +90,7 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t off = i * (opts.block_size ? opts.block_size : DEFAULT_BLOCK_SIZE); if (!off) - off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1); + off = header_size(hash_id == REFTABLE_HASH_SHA256 ? 2 : 1); cl_assert(buf->buf[off] == 'r'); } diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h index d7e6d3136f5d2f..caf443d147e1ed 100644 --- a/t/unit-tests/lib-reftable.h +++ b/t/unit-tests/lib-reftable.h @@ -10,6 +10,7 @@ struct reftable_buf; void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id); struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf, + enum reftable_hash hash_id, struct reftable_write_options *opts); void cl_reftable_write_to_buf(struct reftable_buf *buf, @@ -17,4 +18,5 @@ void cl_reftable_write_to_buf(struct reftable_buf *buf, size_t nrecords, struct reftable_log_record *logs, size_t nlogs, + enum reftable_hash hash_id, struct reftable_write_options *opts); diff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c index 54cb7fc2a71465..21232c1e4f7455 100644 --- a/t/unit-tests/u-reftable-merged.c +++ b/t/unit-tests/u-reftable-merged.c @@ -34,7 +34,8 @@ merged_table_from_records(struct reftable_ref_record **refs, cl_assert(*source != NULL); for (size_t i = 0; i < n; i++) { - cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts); + cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, + REFTABLE_HASH_SHA1, &opts); block_source_from_buf(&(*source)[i], &buf[i]); err = reftable_table_new(&(*tables)[i], &(*source)[i], @@ -357,7 +358,8 @@ merged_table_from_log_records(struct reftable_log_record **logs, cl_assert(*source != NULL); for (size_t i = 0; i < n; i++) { - cl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts); + cl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], + REFTABLE_HASH_SHA1, &opts); block_source_from_buf(&(*source)[i], &buf[i]); err = reftable_table_new(&(*tables)[i], &(*source)[i], @@ -487,7 +489,8 @@ void test_reftable_merged__default_write_opts(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_ref_record rec = { .refname = (char *) "master", .update_index = 1, diff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c index 4d8c4be5f14010..5794b460c6020d 100644 --- a/t/unit-tests/u-reftable-readwrite.c +++ b/t/unit-tests/u-reftable-readwrite.c @@ -48,7 +48,6 @@ static void write_table(char ***names, struct reftable_buf *buf, int N, { struct reftable_write_options opts = { .block_size = block_size, - .hash_id = hash_id, }; struct reftable_ref_record *refs; struct reftable_log_record *logs; @@ -78,7 +77,7 @@ static void write_table(char ***names, struct reftable_buf *buf, int N, logs[i].value.update.message = (char *) "message"; } - cl_reftable_write_to_buf(buf, refs, N, logs, N, &opts); + cl_reftable_write_to_buf(buf, refs, N, logs, N, hash_id, &opts); reftable_free(refs); reftable_free(logs); @@ -103,6 +102,7 @@ void test_reftable_readwrite__log_buffer_size(void) .message = (char *) "commit: 9\n", } } }; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); /* This tests buffer extension for log compression. Must use a random @@ -143,6 +143,7 @@ void test_reftable_readwrite__log_overflow(void) }, }; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); memset(msg, 'x', sizeof(msg) - 1); @@ -157,6 +158,7 @@ void test_reftable_readwrite__log_write_limits(void) struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_log_record log = { .refname = (char *)"refs/head/master", @@ -202,7 +204,9 @@ void test_reftable_readwrite__log_write_read(void) struct reftable_table *table; struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); const struct reftable_stats *stats = NULL; int N = 2, i; char **names; @@ -299,6 +303,7 @@ void test_reftable_readwrite__log_zlib_corruption(void) struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); const struct reftable_stats *stats = NULL; char message[100] = { 0 }; @@ -531,6 +536,7 @@ static void t_table_refs_for(int indexed) struct reftable_block_source source = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, &opts); struct reftable_iterator it = { 0 }; int N = 50, j, i; @@ -622,7 +628,9 @@ void test_reftable_readwrite__write_empty_table(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_block_source source = { 0 }; struct reftable_table *table = NULL; struct reftable_ref_record rec = { 0 }; @@ -660,7 +668,9 @@ void test_reftable_readwrite__write_object_id_min_length(void) .block_size = 75, }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -691,7 +701,9 @@ void test_reftable_readwrite__write_object_id_length(void) .block_size = 75, }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .update_index = 1, .value_type = REFTABLE_REF_VAL1, @@ -721,7 +733,9 @@ void test_reftable_readwrite__write_empty_key(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record ref = { .refname = (char *) "", .update_index = 1, @@ -740,7 +754,9 @@ void test_reftable_readwrite__write_key_order(void) { struct reftable_write_options opts = { 0 }; struct reftable_buf buf = REFTABLE_BUF_INIT; - struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts); + struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, + REFTABLE_HASH_SHA1, + &opts); struct reftable_ref_record refs[2] = { { .refname = (char *) "b", @@ -787,7 +803,8 @@ void test_reftable_readwrite__write_multiple_indices(void) int i; int err; - writer = cl_reftable_strbuf_writer(&writer_buf, &opts); + writer = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1, + &opts); reftable_writer_set_limits(writer, 1, 1); for (i = 0; i < 100; i++) { struct reftable_ref_record ref = { @@ -861,7 +878,8 @@ void test_reftable_readwrite__write_multi_level_index(void) struct reftable_table *table; int err; - writer = cl_reftable_strbuf_writer(&writer_buf, &opts); + writer = cl_reftable_strbuf_writer(&writer_buf, REFTABLE_HASH_SHA1, + &opts); reftable_writer_set_limits(writer, 1, 1); for (size_t i = 0; i < 200; i++) { struct reftable_ref_record ref = { diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c index b8110cdeee664b..e6c163594013d7 100644 --- a/t/unit-tests/u-reftable-stack.c +++ b/t/unit-tests/u-reftable-stack.c @@ -111,10 +111,9 @@ static int write_test_ref(struct reftable_writer *wr, void *arg) static void write_n_ref_tables(struct reftable_stack *st, size_t n) { - int disable_auto_compact; - - disable_auto_compact = st->opts.disable_auto_compact; - st->opts.disable_auto_compact = 1; + struct reftable_write_options opts = { + .disable_auto_compact = 1, + }; for (size_t i = 0; i < n; i++) { struct reftable_ref_record ref = { @@ -128,10 +127,8 @@ static void write_n_ref_tables(struct reftable_stack *st, cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1); cl_assert_equal_i(reftable_stack_add(st, - &write_test_ref, &ref, 0), 0); + &write_test_ref, &ref, &opts, 0), 0); } - - st->opts.disable_auto_compact = disable_auto_compact; } struct write_log_arg { @@ -168,10 +165,10 @@ void test_reftable_stack__add_one(void) struct stat stat_result = { 0 }; int err; - err = reftable_new_stack(&st, dir, &opts); + err = reftable_new_stack(&st, dir, NULL); cl_assert(!err); - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); cl_assert(!err); err = reftable_stack_read_ref(st, ref.refname, &dest); @@ -210,7 +207,6 @@ void test_reftable_stack__add_one(void) void test_reftable_stack__uptodate(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL; struct reftable_stack *st2 = NULL; char *dir = get_tmp_dir(__LINE__); @@ -232,15 +228,15 @@ void test_reftable_stack__uptodate(void) /* simulate multi-process access to the same stack by creating two stacks for the same directory. */ - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st1, write_test_ref, - &ref1, 0), 0); + &ref1, NULL, 0), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, 0), REFTABLE_OUTDATED_ERROR); + &ref2, NULL, 0), REFTABLE_OUTDATED_ERROR); cl_assert_equal_i(reftable_stack_reload(st2), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2, 0), 0); + &ref2, NULL, 0), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); clear_dir(dir); @@ -249,7 +245,6 @@ void test_reftable_stack__uptodate(void) void test_reftable_stack__transaction_api(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_addition *add = NULL; @@ -261,11 +256,11 @@ void test_reftable_stack__transaction_api(void) }; struct reftable_ref_record dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st, NULL, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -306,7 +301,7 @@ void test_reftable_stack__transaction_with_reload(void) cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); - cl_assert_equal_i(reftable_stack_new_addition(&add, st1, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st1, NULL, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[0]), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -317,9 +312,9 @@ void test_reftable_stack__transaction_with_reload(void) * create the addition and lock the stack by default, but allow the * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set. */ - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, 0), + cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, 0), REFTABLE_OUTDATED_ERROR); - cl_assert_equal_i(reftable_stack_new_addition(&add, st2, + cl_assert_equal_i(reftable_stack_new_addition(&add, st2, NULL, REFTABLE_STACK_NEW_ADDITION_RELOAD), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &refs[1]), 0); @@ -342,12 +337,11 @@ void test_reftable_stack__transaction_with_reload(void) void test_reftable_stack__transaction_api_performs_auto_compaction(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = {0}; struct reftable_addition *add = NULL; struct reftable_stack *st = NULL; size_t n = 20; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (size_t i = 0; i <= n; i++) { struct reftable_ref_record ref = { @@ -356,6 +350,9 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void) .value.symref = (char *) "master", }; char name[100]; + struct reftable_write_options write_opts = { + .disable_auto_compact = (i != n), + }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); ref.refname = name; @@ -365,10 +362,8 @@ void test_reftable_stack__transaction_api_performs_auto_compaction(void) * we can ensure that we indeed honor this setting and have * better control over when exactly auto compaction runs. */ - st->opts.disable_auto_compact = i != n; - cl_assert_equal_i(reftable_stack_new_addition(&add, - st, 0), 0); + st, &write_opts, 0), 0); cl_assert_equal_i(reftable_addition_add(add, write_test_ref, &ref), 0); cl_assert_equal_i(reftable_addition_commit(add), 0); @@ -398,15 +393,14 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) .value_type = REFTABLE_REF_VAL1, .value.val1 = {0x01}, }; - struct reftable_write_options opts = { 0 }; struct reftable_stack *st; struct reftable_buf table_path = REFTABLE_BUF_INIT; char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, NULL, 0), 0); cl_assert_equal_i(st->merged->tables_len, 1); cl_assert_equal_i(st->stats.attempts, 0); cl_assert_equal_i(st->stats.failures, 0); @@ -424,7 +418,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) write_file_buf(table_path.buf, "", 0); ref.update_index = 2; - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, NULL, 0); cl_assert(!err); cl_assert_equal_i(st->merged->tables_len, 2); cl_assert_equal_i(st->stats.attempts, 1); @@ -443,7 +437,6 @@ static int write_error(struct reftable_writer *wr UNUSED, void *arg) void test_reftable_stack__update_index_check(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record ref1 = { .refname = (char *) "name1", @@ -458,11 +451,11 @@ void test_reftable_stack__update_index_check(void) .value.symref = (char *) "master", }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref1, 0), 0); + &ref1, NULL, 0), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref2, 0), REFTABLE_API_ERROR); + &ref2, NULL, 0), REFTABLE_API_ERROR); reftable_stack_destroy(st); clear_dir(dir); } @@ -470,14 +463,13 @@ void test_reftable_stack__update_index_check(void) void test_reftable_stack__lock_failure(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int i; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) cl_assert_equal_i(reftable_stack_add(st, write_error, - &i, 0), i); + &i, NULL, 0), i); reftable_stack_destroy(st); clear_dir(dir); @@ -499,7 +491,7 @@ void test_reftable_stack__add(void) size_t i, N = ARRAY_SIZE(refs); int err = 0; - err = reftable_new_stack(&st, dir, &opts); + err = reftable_new_stack(&st, dir, NULL); cl_assert(!err); for (i = 0; i < N; i++) { @@ -521,7 +513,7 @@ void test_reftable_stack__add(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], &opts, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -529,10 +521,10 @@ void test_reftable_stack__add(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, &opts, 0), 0); } - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, &opts, NULL), 0); for (i = 0; i < N; i++) { struct reftable_ref_record dest = { 0 }; @@ -584,7 +576,6 @@ void test_reftable_stack__add(void) void test_reftable_stack__iterator(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_ref_record refs[10] = { 0 }; @@ -593,7 +584,7 @@ void test_reftable_stack__iterator(void) size_t N = ARRAY_SIZE(refs), i; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i < N; i++) { refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i); @@ -613,7 +604,7 @@ void test_reftable_stack__iterator(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], NULL, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -622,7 +613,7 @@ void test_reftable_stack__iterator(void) }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } reftable_stack_init_ref_iterator(st, &it); @@ -669,9 +660,6 @@ void test_reftable_stack__iterator(void) void test_reftable_stack__log_normalize(void) { - struct reftable_write_options opts = { - 0, - }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_log_record input = { @@ -693,15 +681,15 @@ void test_reftable_stack__log_normalize(void) .update_index = 1, }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); input.value.update.message = (char *) "one\ntwo"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), REFTABLE_API_ERROR); + &arg, NULL, 0), REFTABLE_API_ERROR); input.value.update.message = (char *) "one"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "one\n"); @@ -709,7 +697,7 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "two\n"; arg.update_index = 2; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "two\n"); @@ -723,7 +711,6 @@ void test_reftable_stack__log_normalize(void) void test_reftable_stack__tombstone(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record refs[2] = { 0 }; struct reftable_log_record logs[2] = { 0 }; @@ -731,7 +718,7 @@ void test_reftable_stack__tombstone(void) struct reftable_ref_record dest = { 0 }; struct reftable_log_record log_dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); /* even entries add the refs, odd entries delete them. */ for (i = 0; i < N; i++) { @@ -760,7 +747,7 @@ void test_reftable_stack__tombstone(void) } for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i], 0), 0); + &refs[i], NULL, 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -768,7 +755,7 @@ void test_reftable_stack__tombstone(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } cl_assert_equal_i(reftable_stack_read_ref(st, "branch", @@ -779,7 +766,7 @@ void test_reftable_stack__tombstone(void) &log_dest), 1); reftable_log_record_release(&log_dest); - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0); cl_assert_equal_i(reftable_stack_read_ref(st, "branch", &dest), 1); cl_assert_equal_i(reftable_stack_read_log(st, "branch", @@ -799,7 +786,6 @@ void test_reftable_stack__tombstone(void) void test_reftable_stack__hash_id(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record ref = { @@ -808,15 +794,14 @@ void test_reftable_stack__hash_id(void) .value.symref = (char *) "target", .update_index = 1, }; - struct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 }; + struct reftable_stack_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 }; struct reftable_stack *st32 = NULL; - struct reftable_write_options opts_default = { 0 }; struct reftable_stack *st_default = NULL; struct reftable_ref_record dest = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, NULL, 0), 0); /* can't read it with the wrong hash ID. */ cl_assert_equal_i(reftable_new_stack(&st32, dir, @@ -824,7 +809,7 @@ void test_reftable_stack__hash_id(void) /* check that we can read it back with default opts too. */ cl_assert_equal_i(reftable_new_stack(&st_default, dir, - &opts_default), 0); + NULL), 0); cl_assert_equal_i(reftable_stack_read_ref(st_default, "master", &dest), 0); cl_assert(reftable_ref_record_equal(&ref, &dest, @@ -855,7 +840,6 @@ void test_reftable_stack__suggest_compaction_segment_nothing(void) void test_reftable_stack__reflog_expire(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_log_record logs[20] = { 0 }; size_t i, N = ARRAY_SIZE(logs) - 1; @@ -864,7 +848,7 @@ void test_reftable_stack__reflog_expire(void) }; struct reftable_log_record log = { 0 }; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 1; i <= N; i++) { char buf[256]; @@ -885,18 +869,18 @@ void test_reftable_stack__reflog_expire(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg, 0), 0); + &arg, NULL, 0), 0); } - cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); - cl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0); cl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname, &log), 1); cl_assert_equal_i(reftable_stack_read_log(st, logs[11].refname, &log), 0); expiry.min_update_index = 15; - cl_assert_equal_i(reftable_stack_compact_all(st, &expiry), 0); + cl_assert_equal_i(reftable_stack_compact_all(st, NULL, &expiry), 0); cl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname, &log), 1); cl_assert_equal_i(reftable_stack_read_log(st, logs[16].refname, @@ -918,15 +902,14 @@ static int write_nothing(struct reftable_writer *wr, void *arg UNUSED) void test_reftable_stack__empty_add(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); struct reftable_stack *st2 = NULL; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); cl_assert_equal_i(reftable_stack_add(st, write_nothing, - NULL, 0), 0); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); + NULL, NULL, 0), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); clear_dir(dir); reftable_stack_destroy(st); reftable_stack_destroy(st2); @@ -952,7 +935,7 @@ void test_reftable_stack__auto_compaction(void) size_t i, N = 100; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i < N; i++) { char name[100]; @@ -964,10 +947,10 @@ void test_reftable_stack__auto_compaction(void) }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, write_test_ref, &ref, 0); + err = reftable_stack_add(st, write_test_ref, &ref, &opts, 0); cl_assert(!err); - err = reftable_stack_auto_compact(st); + err = reftable_stack_auto_compact(st, &opts); cl_assert(!err); cl_assert(i < 2 || st->merged->tables_len < 2 * fastlogN(i, 2)); } @@ -989,7 +972,7 @@ void test_reftable_stack__auto_compaction_factor(void) size_t N = 100; int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (size_t i = 0; i < N; i++) { char name[20]; @@ -1000,7 +983,7 @@ void test_reftable_stack__auto_compaction_factor(void) }; xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, &write_test_ref, &ref, 0); + err = reftable_stack_add(st, &write_test_ref, &ref, &opts, 0); cl_assert(!err); cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); @@ -1020,7 +1003,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 5); cl_assert_equal_i(st->merged->tables_len, 5); @@ -1042,7 +1025,7 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) * would in theory compact all tables, due to the preexisting lock we * only compact the newest two tables. */ - err = reftable_stack_auto_compact(st); + err = reftable_stack_auto_compact(st, &opts); cl_assert(!err); cl_assert_equal_i(st->stats.failures, 0); cl_assert_equal_i(st->merged->tables_len, 4); @@ -1054,12 +1037,11 @@ void test_reftable_stack__auto_compaction_with_locked_tables(void) void test_reftable_stack__add_performs_auto_compaction(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); size_t i, n = 20; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); for (i = 0; i <= n; i++) { struct reftable_ref_record ref = { @@ -1067,6 +1049,9 @@ void test_reftable_stack__add_performs_auto_compaction(void) .value_type = REFTABLE_REF_SYMREF, .value.symref = (char *) "master", }; + struct reftable_write_options write_opts = { + .disable_auto_compact = (i != n), + }; bool required = false; char buf[128]; @@ -1075,20 +1060,18 @@ void test_reftable_stack__add_performs_auto_compaction(void) * we can ensure that we indeed honor this setting and have * better control over when exactly auto compaction runs. */ - st->opts.disable_auto_compact = i != n; - snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i); ref.refname = buf; cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref, 0), 0); + &ref, &write_opts, 0), 0); /* * The stack length should grow continuously for all runs where * auto compaction is disabled. When enabled, we should merge * all tables in the stack. */ - cl_assert_equal_i(reftable_stack_compaction_required(st, true, &required), 0); + cl_assert_equal_i(reftable_stack_compaction_required(st, NULL, true, &required), 0); if (i != n) { cl_assert_equal_i(st->merged->tables_len, i + 1); if (i < 1) @@ -1115,7 +1098,7 @@ void test_reftable_stack__compaction_with_locked_tables(void) char *dir = get_tmp_dir(__LINE__); int err; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 3); cl_assert_equal_i(st->merged->tables_len, 3); @@ -1131,7 +1114,7 @@ void test_reftable_stack__compaction_with_locked_tables(void) * Compaction is expected to fail given that we were not able to * compact all tables. */ - err = reftable_stack_compact_all(st, NULL); + err = reftable_stack_compact_all(st, &opts, NULL); cl_assert_equal_i(err, REFTABLE_LOCK_ERROR); cl_assert_equal_i(st->stats.failures, 1); cl_assert_equal_i(st->merged->tables_len, 3); @@ -1143,15 +1126,14 @@ void test_reftable_stack__compaction_with_locked_tables(void) void test_reftable_stack__compaction_concurrent(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL; char *dir = get_tmp_dir(__LINE__); - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 3); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); - cl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); @@ -1171,20 +1153,19 @@ static void unclean_stack_close(struct reftable_stack *st) void test_reftable_stack__compaction_concurrent_clean(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL; char *dir = get_tmp_dir(__LINE__); - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 3); - cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); - cl_assert_equal_i(reftable_stack_compact_all(st1, NULL), 0); + cl_assert_equal_i(reftable_new_stack(&st2, dir, NULL), 0); + cl_assert_equal_i(reftable_stack_compact_all(st1, NULL, NULL), 0); unclean_stack_close(st1); unclean_stack_close(st2); - cl_assert_equal_i(reftable_new_stack(&st3, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st3, dir, NULL), 0); cl_assert_equal_i(reftable_stack_clean(st3), 0); cl_assert_equal_i(count_dir_entries(dir), 2); @@ -1197,7 +1178,6 @@ void test_reftable_stack__compaction_concurrent_clean(void) void test_reftable_stack__read_across_reload(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; @@ -1205,17 +1185,17 @@ void test_reftable_stack__read_across_reload(void) int err; /* Create a first stack and set up an iterator for it. */ - cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st1, dir, NULL), 0); write_n_ref_tables(st1, 2); cl_assert_equal_i(st1->merged->tables_len, 2); reftable_stack_init_ref_iterator(st1, &it); cl_assert_equal_i(reftable_iterator_seek_ref(&it, ""), 0); /* Set up a second stack for the same directory and compact it. */ - err = reftable_new_stack(&st2, dir, &opts); + err = reftable_new_stack(&st2, dir, NULL); cl_assert(!err); cl_assert_equal_i(st2->merged->tables_len, 2); - err = reftable_stack_compact_all(st2, NULL); + err = reftable_stack_compact_all(st2, NULL, NULL); cl_assert(!err); cl_assert_equal_i(st2->merged->tables_len, 1); @@ -1244,7 +1224,6 @@ void test_reftable_stack__read_across_reload(void) void test_reftable_stack__reload_with_missing_table(void) { - struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_ref_record rec = { 0 }; struct reftable_iterator it = { 0 }; @@ -1253,7 +1232,7 @@ void test_reftable_stack__reload_with_missing_table(void) int err; /* Create a first stack and set up an iterator for it. */ - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); write_n_ref_tables(st, 2); cl_assert_equal_i(st->merged->tables_len, 2); reftable_stack_init_ref_iterator(st, &it); @@ -1320,11 +1299,11 @@ void test_reftable_stack__invalid_limit_updates(void) char *dir = get_tmp_dir(__LINE__); struct reftable_stack *st = NULL; - cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); + cl_assert_equal_i(reftable_new_stack(&st, dir, NULL), 0); reftable_addition_destroy(add); - cl_assert_equal_i(reftable_stack_new_addition(&add, st, 0), 0); + cl_assert_equal_i(reftable_stack_new_addition(&add, st, &opts, 0), 0); /* * write_limits_after_ref also updates the update indexes after adding diff --git a/t/unit-tests/u-reftable-table.c b/t/unit-tests/u-reftable-table.c index 14fae8b199c13c..fae478ee044c48 100644 --- a/t/unit-tests/u-reftable-table.c +++ b/t/unit-tests/u-reftable-table.c @@ -22,7 +22,8 @@ void test_reftable_table__seek_once(void) struct reftable_buf buf = REFTABLE_BUF_INIT; int ret; - cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL); + cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, + REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); @@ -64,7 +65,7 @@ void test_reftable_table__reseek(void) int ret; cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), - NULL, 0, NULL); + NULL, 0, REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); @@ -147,7 +148,8 @@ void test_reftable_table__block_iterator(void) (uintmax_t) i); } - cl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL); + cl_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, + REFTABLE_HASH_SHA1, NULL); block_source_from_buf(&source, &buf); ret = reftable_table_new(&table, &source, "name"); diff --git a/transport.c b/transport.c index 0f5ec302472d53..fc144f0aedabd9 100644 --- a/transport.c +++ b/transport.c @@ -1482,7 +1482,7 @@ int transport_push(struct repository *r, if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | TRANSPORT_RECURSE_SUBMODULES_ONLY)) && - !is_bare_repository()) { + !is_bare_repository(the_repository)) { struct ref *ref = remote_refs; struct oid_array commits = OID_ARRAY_INIT; @@ -1509,7 +1509,7 @@ int transport_push(struct repository *r, if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) || ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | TRANSPORT_RECURSE_SUBMODULES_ONLY)) && - !pretend)) && !is_bare_repository()) { + !pretend)) && !is_bare_repository(the_repository)) { struct ref *ref = remote_refs; struct string_list needs_pushing = STRING_LIST_INIT_DUP; struct oid_array commits = OID_ARRAY_INIT; diff --git a/urlmatch.c b/urlmatch.c index bf8cce6de9d8da..20bc2d009cd0dd 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -112,7 +112,7 @@ static int match_host(const struct url_info *url_info, return (!url_len && !pat_len); } -static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs) +static char *url_normalize_1(const char *url, struct url_info *out_info, bool allow_globs) { /* * Normalize NUL-terminated url using the following rules: @@ -438,7 +438,12 @@ static char *url_normalize_1(const char *url, struct url_info *out_info, char al char *url_normalize(const char *url, struct url_info *out_info) { - return url_normalize_1(url, out_info, 0); + return url_normalize_1(url, out_info, false); +} + +char *url_normalize_pattern(const char *url, struct url_info *out_info) +{ + return url_normalize_1(url, out_info, true); } char *url_parse(const char *url_orig, struct url_info *out_info) @@ -704,7 +709,7 @@ int urlmatch_config_entry(const char *var, const char *value, struct url_info norm_info; config_url = xmemdupz(key, dot - key); - norm_url = url_normalize_1(config_url, &norm_info, 1); + norm_url = url_normalize_1(config_url, &norm_info, true); if (norm_url) retval = match_urls(url, &norm_info, &matched); else if (collect->fallback_match_fn) diff --git a/urlmatch.h b/urlmatch.h index 6b3ce428582da3..db1a335e728ea3 100644 --- a/urlmatch.h +++ b/urlmatch.h @@ -37,6 +37,18 @@ struct url_info { char *url_normalize(const char *, struct url_info *); char *url_parse(const char *, struct url_info *); +/* + * Like url_normalize(), but also allows '*' glob characters in the host + * portion. Use this when normalizing URL patterns from user configuration. + * + * Note that '*' is a valid path character per RFC 3986 (as a sub-delim), + * so glob patterns using '*' in the path are also accepted. + * + * Returns a newly allocated normalized string and fills out_info if + * non-NULL, or NULL if the pattern is invalid. + */ +char *url_normalize_pattern(const char *url, struct url_info *out_info); + struct urlmatch_item { size_t hostmatch_len; size_t pathmatch_len; diff --git a/worktree.c b/worktree.c index 97eddc39166953..30125827fd39ed 100644 --- a/worktree.c +++ b/worktree.c @@ -123,8 +123,8 @@ static struct worktree *get_main_worktree(int skip_reading_head) worktree->repo = the_repository; worktree->path = strbuf_detach(&worktree_path, NULL); worktree->is_current = is_current_worktree(worktree); - worktree->is_bare = (is_bare_repository_cfg == 1) || - is_bare_repository() || + worktree->is_bare = (the_repository->bare_cfg == 1) || + is_bare_repository(the_repository) || /* * When in a secondary worktree we have to also verify if the main * worktree is bare in $commondir/config.worktree. diff --git a/wt-status.c b/wt-status.c index b17372390cf96c..58461e02f886ae 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1365,35 +1365,139 @@ static int split_commit_in_progress(struct wt_status *s) return split_in_progress; } +/* + * If the whitespace-delimited token starting at or just after *pp + * is a hex object id that is longer than its default abbreviation, + * abbreviate it in-place, shrinking `line` accordingly. On return + * *pp points one past the (possibly abbreviated) token. Leaves both + * `line` and *pp-advanced-past-the-token unchanged in all other cases + * (non-hex token, label name, unresolvable, or a refname that happens + * to consist only of hex digits). + */ +static void abbrev_oid_in_line(struct repository *r, struct strbuf *scratch, + struct strbuf *line, bool maybe_label, char **pp) +{ + char *p = *pp; + char *end_of_object_name, saved; + const char *abbrev; + struct object_id oid; + bool have_oid; + + p += strspn(p, " \t"); + end_of_object_name = p + strcspn(p, " \t"); + /* + * For "merge" and "reset" the object name may be a label or + * ref rather than a hex object id. Only abbreviate the object + * name if it is a hex object id. + */ + for (const char *q = p; q < end_of_object_name; q++) { + if (!isxdigit(*q)) + goto out; + } + if (maybe_label) { + strbuf_reset(scratch); + strbuf_addf(scratch, "refs/rewritten/%.*s", + (int)(end_of_object_name - p), p); + if (refs_ref_exists(get_main_ref_store(r), scratch->buf)) + goto out; /* object name was a label */ + } + saved = *end_of_object_name; + *end_of_object_name = '\0'; + have_oid = !repo_get_oid(r, p, &oid); + *end_of_object_name = saved; + if (!have_oid) + goto out; /* invalid object name */ + abbrev = repo_find_unique_abbrev(r, &oid, DEFAULT_ABBREV); + if (!starts_with(p, abbrev)) + goto out; /* object name was a refname containing only xdigits */ + p += strlen(abbrev); + strbuf_remove(line, p - line->buf, end_of_object_name - p); + end_of_object_name = p; +out: + *pp = end_of_object_name; +} + +/* Skip "[ \t]*(-[cC])?", returns true if "-c/-C" was skipped. */ +static bool skip_dash_c(char **pp) +{ + bool ret; + char *p = *pp; + + p += strspn(p, " \t"); + ret = skip_prefix(p, "-C", &p) || skip_prefix(p, "-c", &p); + *pp = p; + + return ret; +} + /* * Turn * "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message" * into * "pick d6a2f03 some message" * - * The function assumes that the line does not contain useless spaces - * before or after the command. + * Returns false on comment lines, true otherwise */ -static void abbrev_oid_in_line(struct repository *r, struct strbuf *line) +static bool format_todo_line(struct repository *r, struct strbuf *line) { - struct string_list split = STRING_LIST_INIT_DUP; - struct object_id oid; + enum todo_command cmd; + struct strbuf scratch = STRBUF_INIT; + char *p = line->buf; - if (starts_with(line->buf, "exec ") || - starts_with(line->buf, "x ") || - starts_with(line->buf, "label ") || - starts_with(line->buf, "l ")) - return; + if (!sequencer_parse_todo_command((const char **)&p, &cmd)) + return true; /* keep invalid lines */ + + switch (cmd) { + case TODO_COMMENT: + return false; + + case TODO_MERGE: { + /* + * The argument to -C cannot be a label, but the parents + * can be labels. + */ + bool maybe_label = !skip_dash_c(&p); + + while (true) { + p += strspn(p, " \t"); + if (!p[0] || (p[0] == '#' && (!p[1] || isspace(p[1])))) + break; + abbrev_oid_in_line(r, &scratch, line, maybe_label, &p); + maybe_label = true; + } + break; + } - if ((2 <= string_list_split(&split, line->buf, " ", 2)) && - !repo_get_oid(r, split.items[1].string, &oid)) { - strbuf_reset(line); - strbuf_addf(line, "%s ", split.items[0].string); - strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV); - for (size_t i = 2; i < split.nr; i++) - strbuf_addf(line, " %s", split.items[i].string); + case TODO_FIXUP: + skip_dash_c(&p); + /* fallthrough */ + case TODO_DROP: + case TODO_EDIT: + case TODO_PICK: + case TODO_REVERT: + case TODO_REWORD: + case TODO_SQUASH: + abbrev_oid_in_line(r, &scratch, line, false, &p); + break; + + case TODO_RESET: + abbrev_oid_in_line(r, &scratch, line, true, &p); + break; + /* + * Avoid "default" and instead list all the other commands so + * that -Wswitch (which is included in -Wall) warns if a new + * command is added without handling it in this function. + */ + case TODO_BREAK: + case TODO_EXEC: + case TODO_LABEL: + case TODO_NOOP: + case TODO_UPDATE_REF: + break; } - string_list_clear(&split, 0); + + strbuf_release(&scratch); + return true; } static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines) @@ -1411,13 +1515,9 @@ static int read_rebase_todolist(struct repository *r, const char *fname, struct repo_git_path_replace(r, &buf, "%s", fname)); } while (!strbuf_getline_lf(&buf, f)) { - if (starts_with(buf.buf, comment_line_str)) - continue; strbuf_trim(&buf); - if (!buf.len) - continue; - abbrev_oid_in_line(r, &buf); - string_list_append(lines, buf.buf); + if (format_todo_line(r, &buf)) + string_list_append(lines, buf.buf); } fclose(f);