diff options
253 files changed, 5340 insertions, 2871 deletions
diff --git a/Documentation/DecisionMaking.txt b/Documentation/DecisionMaking.txt new file mode 100644 index 0000000000..dbb4c1f569 --- /dev/null +++ b/Documentation/DecisionMaking.txt @@ -0,0 +1,74 @@ +Decision-Making Process in the Git Project +========================================== + +Introduction +------------ +This document describes the current decision-making process in the Git +project. It is a descriptive rather than prescriptive doc; that is, we want to +describe how things work in practice rather than explicitly recommending any +particular process or changes to the current process. + +Here we document how the project makes decisions for discussions +(with or without patches), in scale larger than an individual patch +series (which is fully covered by the SubmittingPatches document). + + +Larger Discussions (with patches) +--------------------------------- +As with discussions on an individual patch series, starting a larger-scale +discussion often begins by sending a patch or series to the list. This might +take the form of an initial design doc, with implementation following in later +iterations of the series (for example, +link:https://lore.kernel.org/git/0169ce6fb9ccafc089b74ae406db0d1a8ff8ac65.1688165272.git.steadmon@google.com/[adding unit tests] or +link:https://lore.kernel.org/git/20200420235310.94493-1-emilyshaffer@google.com/[config-based hooks]), +or it might include a full implementation from the beginning. +In either case, discussion progresses the same way for an individual patch series, +until consensus is reached or the topic is dropped. + + +Larger Discussions (without patches) +------------------------------------ +Occasionally, larger discussions might occur without an associated patch series. +These may be very large-scale technical decisions that are beyond the scope of +even a single large patch series, or they may be more open-ended, +policy-oriented discussions (examples: +link:https://lore.kernel.org/git/ZZ77NQkSuiRxRDwt@nand.local/[introducing Rust] +or link:https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/[improving submodule UX]). +In either case, discussion progresses as described above for general patch series. + +For larger discussions without a patch series or other concrete implementation, +it may be hard to judge when consensus has been reached, as there are not any +official guidelines. If discussion stalls at this point, it may be helpful to +restart discussion with an RFC patch series (such as a partial, unfinished +implementation or proof of concept) that can be more easily debated. + +When consensus is reached that it is a good idea, the original +proposer is expected to coordinate the effort to make it happen, +with help from others who were involved in the discussion, as +needed. + +For decisions that require code changes, it is often the case that the original +proposer will follow up with a patch series, although it is also common for +other interested parties to provide an implementation (or parts of the +implementation, for very large changes). + +For non-technical decisions such as community norms or processes, it is up to +the community as a whole to implement and sustain agreed-upon changes. +The project leadership committe (PLC) may help the implementation of +policy decisions. + + +Other Discussion Venues +----------------------- +Occasionally decision proposals are presented off-list, e.g. at the semi-regular +Contributors' Summit. While higher-bandwidth face-to-face discussion is often +useful for quickly reaching consensus among attendees, generally we expect to +summarize the discussion in notes that can later be presented on-list. For an +example, see the thread +link:https://lore.kernel.org/git/AC2EB721-2979-43FD-922D-C5076A57F24B@jramsay.com.au/[Notes +from Git Contributor Summit, Los Angeles (April 5, 2020)] by James Ramsay. + +We prefer that "official" discussion happens on the list so that the full +community has opportunity to engage in discussion. This also means that the +mailing list archives contain a more-or-less complete history of project +discussions and decisions. diff --git a/Documentation/Makefile b/Documentation/Makefile index 3f2383a12c..a04da672c6 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -103,6 +103,7 @@ SP_ARTICLES += howto/coordinate-embargoed-releases API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) +TECH_DOCS += DecisionMaking TECH_DOCS += ReviewingGuidelines TECH_DOCS += MyFirstContribution TECH_DOCS += MyFirstObjectWalk diff --git a/Documentation/RelNotes/2.39.5.txt b/Documentation/RelNotes/2.39.5.txt new file mode 100644 index 0000000000..97c0185de4 --- /dev/null +++ b/Documentation/RelNotes/2.39.5.txt @@ -0,0 +1,26 @@ +Git v2.39.5 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.39.4 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.40.3.txt b/Documentation/RelNotes/2.40.3.txt new file mode 100644 index 0000000000..6ca088ec86 --- /dev/null +++ b/Documentation/RelNotes/2.40.3.txt @@ -0,0 +1,26 @@ +Git v2.40.3 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.40.2 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.41.2.txt b/Documentation/RelNotes/2.41.2.txt new file mode 100644 index 0000000000..f94afde8c2 --- /dev/null +++ b/Documentation/RelNotes/2.41.2.txt @@ -0,0 +1,26 @@ +Git v2.41.2 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.41.1 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.42.3.txt b/Documentation/RelNotes/2.42.3.txt new file mode 100644 index 0000000000..bfe3ba5629 --- /dev/null +++ b/Documentation/RelNotes/2.42.3.txt @@ -0,0 +1,26 @@ +Git v2.42.3 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.42.2 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.43.5.txt b/Documentation/RelNotes/2.43.5.txt new file mode 100644 index 0000000000..236b234b06 --- /dev/null +++ b/Documentation/RelNotes/2.43.5.txt @@ -0,0 +1,26 @@ +Git v2.43.5 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.43.4 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.44.2.txt b/Documentation/RelNotes/2.44.2.txt new file mode 100644 index 0000000000..76700f0b73 --- /dev/null +++ b/Documentation/RelNotes/2.44.2.txt @@ -0,0 +1,26 @@ +Git v2.44.2 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.44.1 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.45.2.txt b/Documentation/RelNotes/2.45.2.txt new file mode 100644 index 0000000000..13429e6491 --- /dev/null +++ b/Documentation/RelNotes/2.45.2.txt @@ -0,0 +1,26 @@ +Git v2.45.2 Release Notes +========================= + +In preparing security fixes for four CVEs, we made overly aggressive +"defense in depth" changes that broke legitimate use cases like 'git +lfs' and 'git annex.' This release is to revert these misguided, if +well-intentioned, changes that were shipped in 2.45.1 and were not +direct security fixes. + +Jeff King (5): + send-email: drop FakeTerm hack + send-email: avoid creating more than one Term::ReadLine object + ci: drop mention of BREW_INSTALL_PACKAGES variable + ci: avoid bare "gcc" for osx-gcc job + ci: stop installing "gcc-13" for osx-gcc + +Johannes Schindelin (6): + hook: plug a new memory leak + init: use the correct path of the templates directory again + Revert "core.hooksPath: add some protection while cloning" + tests: verify that `clone -c core.hooksPath=/dev/null` works again + clone: drop the protections where hooks aren't run + Revert "Add a helper function to compare file contents" + +Junio C Hamano (1): + Revert "fsck: warn about symlink pointing inside a gitdir" diff --git a/Documentation/RelNotes/2.46.0.txt b/Documentation/RelNotes/2.46.0.txt index ff3ce813da..6321ddc319 100644 --- a/Documentation/RelNotes/2.46.0.txt +++ b/Documentation/RelNotes/2.46.0.txt @@ -40,6 +40,27 @@ UI, Workflows & Features * Updates to symbolic refs can now be made as a part of ref transaction. + * The trailer API has been reshuffled a bit. + + * Terminology to call various ref-like things are getting + straightened out. + + * The command line completion script (in contrib/) has been adjusted + to the recent update to "git config" that adopted subcommand based + UI. + + * The knobs to tweak how reftable files are written have been made + available as configuration variables. + + * When "git push" notices that the commit at the tip of the ref on + the other side it is about to overwrite does not exist locally, it + used to first try fetching it if the local repository is a partial + clone. The command has been taught not to do so and immediately + fail instead. + + * The promisor.quiet configuration knob can be set to true to make + lazy fetching from promisor remotes silent. + Performance, Internal Implementation, Development Support etc. @@ -62,7 +83,6 @@ Performance, Internal Implementation, Development Support etc. to "the_index" with references to its .index member. * Git-GUI has a new maintainer, Johannes Sixt. - (merge e18ad8eb26 jc/git-gui-maintainer-update later to maint). * The "test-tool" has been taught to run testsuite tests in parallel, bypassing the need to use the "prove" tool. @@ -74,6 +94,46 @@ Performance, Internal Implementation, Development Support etc. primary ref_store by forcing the callers to pass a ref_store as an argument. + * Code clean-up to reduce inter-function communication inside + builtin/config.c done via the use of global variables. + + * The pack bitmap code saw some clean-up to prepare for a follow-up topic. + + * Preliminary code clean-up for "git send-email". + + * The default "creation-factor" used by "git format-patch" has been + raised to make it more aggressively find matching commits. + + * Before discovering the repository details, We used to assume SHA-1 + as the "default" hash function, which has been corrected. Hopefully + this will smoke out codepaths that rely on such an unwarranted + assumptions. + + * The project decision making policy has been documented. + + * The strcmp-offset tests have been rewritten using the unit test + framework. + + * "git add -p" learned to complain when an answer with more than one + letter is given to a prompt that expects a single letter answer. + + * The alias-expanded command lines are logged to the trace output. + + * A new test was added to ensure git commands that are designed to + run outside repositories do work. + + * Basic unit tests for reftable have been reimplemented under the + unit test framework. + + * A pair of test helpers that essentially are unit tests on hash + algorithms have been rewritten using the unit-tests framework. + + * A test helper that essentially is unit tests on the "decorate" + logic has been rewritten using the unit-tests framework. + + * Many memory leaks in the sparse-checkout code paths have been + plugged. + Fixes since v2.45 ----------------- @@ -116,20 +176,17 @@ Fixes since v2.45 * Tests that try to corrupt in-repository files in chunked format did not work well on macOS due to its broken "mv", which has been worked around. - (merge 861dc19ba8 jc/test-workaround-broken-mv later to maint). * The maximum size of attribute files is enforced more consistently. (merge c793f9cb08 tb/attr-limits later to maint). * Unbreak CI jobs so that we do not attempt to use Python 2 that has been removed from the platform. - (merge 5ca0c455f1 ps/ci-python-2-deprecation later to maint). * Git 2.43 started using the tree of HEAD as the source of attributes in a bare repository, which has severe performance implications. For now, revert the change, without ripping out a more explicit support for the attr.tree configuration variable. - (merge 51441e6460 jc/no-default-attr-tree-in-bare later to maint). * The "--exit-code" option of "git diff" command learned to work with the "--ext-diff" option. @@ -138,13 +195,54 @@ Fixes since v2.45 * Windows CI running in GitHub Actions started complaining about the order of arguments given to calloc(); the imported regex code uses the wrong order almost consistently, which has been corrected. - (merge f01301aabe jc/compat-regex-calloc-fix later to maint). + + * Expose "name conflict" error when a ref creation fails due to D/F + conflict in the ref namespace, to improve an error message given by + "git fetch". + (merge 9339fca23e it/refs-name-conflict later to maint). + + * The SubmittingPatches document now refers folks to manpages + translation project. + + * The documentation for "git diff --name-only" has been clarified + that it is about showing the names in the post-image tree. + (merge 4986662cbc jc/doc-diff-name-only later to maint). + + * The credential helper that talks with osx keychain learned to avoid + storing back the authentication material it just got received from + the keychain. + (merge e1ab45b2da kn/osxkeychain-skip-idempotent-store later to maint). + + * The chainlint script (invoked during "make test") did nothing when + it failed to detect the number of available CPUs. It now falls + back to 1 CPU to avoid the problem. + (merge 2e7e9205be es/chainlint-ncores-fix later to maint). + + * Revert overly aggressive "layered defence" that went into 2.45.1 + and friends, which broke "git-lfs", "git-annex", and other use + cases, so that we can rebuild necessary counterparts in the open. + + * "git init" in an already created directory, when the user + configuration has includeif.onbranch, started to fail recently, + which has been corrected. + (merge 407997c1dd ps/fix-reinit-includeif-onbranch later to maint). + + * Memory leaks in "git mv" has been plugged. + + * The safe.directory configuration knob has been updated to + optionally allow leading path matches. + (merge 313eec177a jc/safe-directory-leading-path later to maint). + + * An overly large ".gitignore" files are now rejected silently. + (merge e7c3d1ddba jk/cap-exclude-file-size later to maint). * Other code cleanup, docfix, build fix, etc. - (merge 4cf6e7bf5e jt/doc-submitting-rerolled-series later to maint). (merge a5a4cb7b27 rs/diff-parseopts-cleanup later to maint). - (merge 395c130fd8 ma/win32-unix-domain-socket later to maint). - (merge 7df2405b38 jk/ci-macos-gcc13-fix later to maint). (merge 55702c543e fa/p4-error later to maint). (merge 2566a77774 vd/doc-merge-tree-x-option later to maint). (merge b64b0df9da ds/scalar-reconfigure-all-fix later to maint). + (merge c81ffcff83 dm/update-index-doc-fix later to maint). + (merge fc0202b0e9 dg/fetch-pack-code-cleanup later to maint). + (merge 7150f140f9 mt/t0211-typofix later to maint). + (merge d424488901 jc/rev-parse-fatal-doc later to maint). + (merge 36d900d2b0 rs/difftool-env-simplify later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 0690ae2140..d8a8caa791 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -7,6 +7,73 @@ Here are some guidelines for contributing back to this project. There is also a link:MyFirstContribution.html[step-by-step tutorial] available which covers many of these same guidelines. +[[patch-flow]] +=== A typical life cycle of a patch series + +To help us understand the reason behind various guidelines given later +in the document, first let's understand how the life cycle of a +typical patch series for this project goes. + +. You come up with an itch. You code it up. You do not need any + pre-authorization from the project to do so. ++ +Your patches will be reviewed by other contributors on the mailing +list, and the reviews will be done to assess the merit of various +things, like the general idea behind your patch (including "is it +solving a problem worth solving in the first place?"), the reason +behind the design of the solution, and the actual implementation. +The guidelines given here are there to help your patches by making +them easier to understand by the reviewers. + +. You send the patches to the list and cc people who may need to know + about the change. Your goal is *not* necessarily to convince others + that what you are building is good. Your goal is to get help in + coming up with a solution for the "itch" that is better than what + you can build alone. ++ +The people who may need to know are the ones who worked on the code +you are touching. These people happen to be the ones who are +most likely to be knowledgeable enough to help you, but +they have no obligation to help you (i.e. you ask them for help, +you don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would +help you find out who they are. + +. 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. + +. Polish, refine, and re-send your patches to the list and to the people + who spent their time to improve your patch. Go back to step (2). + +. While the above iterations improve your patches, the maintainer may + pick the patches up from the list and queue them to the `seen` + branch, in order to make it easier for people to play with it + without having to pick up and apply the patches to their trees + themselves. Being in `seen` has no other meaning. Specifically, it + does not mean the patch was "accepted" in any way. + +. When the discussion reaches a consensus that the latest iteration of + the patches are in good enough shape, the maintainer includes the + topic in the "What's cooking" report that are sent out a few times a + week to the mailing list, marked as "Will merge to 'next'." This + decision is primarily made by the maintainer with help from those + who participated in the review discussion. + +. After the patches are merged to the 'next' branch, the discussion + can still continue to further improve them by adding more patches on + top, but by the time a topic gets merged to 'next', it is expected + that everybody agrees that the scope and the basic direction of the + topic are appropriate, so such an incremental updates are limited to + small corrections and polishing. After a topic cooks for some time + (like 7 calendar days) in 'next' without needing further tweaks on + top, it gets merged to the 'master' branch and wait to become part + of the next major release. + +In the following sections, many techniques and conventions are listed +to help your patches get reviewed effectively in such a life cycle. + + [[choose-starting-point]] === Choose a starting point. @@ -192,8 +259,9 @@ reasons: which case, they can explain why they extend your code to cover files, too). -The goal of your log message is to convey the _why_ behind your -change to help future developers. +The goal of your log message is to convey the _why_ behind your change +to help future developers. The reviewers will also make sure that +your proposed log message will serve this purpose well. The first line of the commit message should be a short description (50 characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]), @@ -540,6 +608,85 @@ patch, format it as "multipart/signed", not a text/plain message that starts with `-----BEGIN PGP SIGNED MESSAGE-----`. That is not a text/plain, it's something else. +=== Handling Conflicts and Iterating Patches + +When revising changes made to your patches, it's important to +acknowledge the possibility of conflicts with other ongoing topics. To +navigate these potential conflicts effectively, follow the recommended +steps outlined below: + +. Build on a suitable base branch, see the <<choose-starting-point, section above>>, +and format-patch the series. If you are doing "rebase -i" in-place to +update from the previous round, this will reuse the previous base so +(2) and (3) may become trivial. + +. Find the base of where the last round was queued ++ + $ mine='kn/ref-transaction-symref' + $ git checkout "origin/seen^{/^Merge branch '$mine'}...master" + +. Apply your format-patch result. There are two cases +.. Things apply cleanly and tests fine. Go to (4). +.. Things apply cleanly but does not build or test fails, or things do +not apply cleanly. ++ +In the latter case, you have textual or semantic conflicts coming from +the difference between the old base and the base you used to build in +(1). Identify what caused the breakages (e.g., a topic or two may have +merged since the base used by (2) until the base used by (1)). ++ +Check out the latest 'origin/master' (which may be newer than the base +used by (2)), "merge --no-ff" the topics you newly depend on in there, +and use the result of the merge(s) as the base, rebuild the series and +test again. Run format-patch from the last such merges to the tip of +your topic. If you did ++ + $ git checkout origin/master + $ git merge --no-ff --into-name kn/ref-transaction-symref fo/obar + $ git merge --no-ff --into-name kn/ref-transaction-symref ba/zqux + ... rebuild the topic ... ++ +Then you'd just format your topic above these "preparing the ground" +merges, e.g. ++ + $ git format-patch "HEAD^{/^Merge branch 'ba/zqux'}"..HEAD ++ +Do not forget to write in the cover letter you did this, including the +topics you have in your base on top of 'master'. Then go to (4). + +. Make a trial merge of your topic into 'next' and 'seen', e.g. ++ + $ git checkout --detach 'origin/seen' + $ git revert -m 1 <the merge of the previous iteration into seen> + $ git merge kn/ref-transaction-symref ++ +The "revert" is needed if the previous iteration of your topic is +already in 'seen' (like in this case). You could choose to rebuild +master..origin/seen from scratch while excluding your previous +iteration, which may emulate what happens on the maintainers end more +closely. ++ +This trial merge may conflict. It is primarily to see what conflicts +_other_ topics may have with your topic. In other words, you do not +have to depend on it to make your topic work on 'master'. It may +become the job of the other topic owners to resolve conflicts if your +topic goes to 'next' before theirs. ++ +Make a note on what conflict you saw in the cover letter. You do not +necessarily have to resolve them, but it would be a good opportunity to +learn what others are doing in related areas. ++ + $ git checkout --detach 'origin/next' + $ git merge kn/ref-transaction-symref ++ +This is to see what conflicts your topic has with other topics that are +already cooking. This should not conflict if (3)-2 prepared a base on +top of updated master plus dependent topics taken from 'next'. Unless +the context is severe (one way to tell is try the same trial merge with +your old iteration, which may conflict in a similar way), expect that it +will be handled on maintainers end (if it gets unmanageable, I'll ask to +rebase when I receive your patches). + == Subsystems with dedicated maintainers Some parts of the system have dedicated maintainers with their own @@ -562,54 +709,12 @@ repositories. Patches to these parts should be based on their trees. -[[patch-flow]] -== An ideal patch flow - -Here is an ideal patch flow for this project the current maintainer -suggests to the contributors: - -. You come up with an itch. You code it up. - -. Send it to the list and cc people who may need to know about - the change. -+ -The people who may need to know are the ones whose code you -are butchering. These people happen to be the ones who are -most likely to be knowledgeable enough to help you, but -they have no obligation to help you (i.e. you ask for help, -don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would -help you find out who they are. - -. You get comments and suggestions for improvements. You may - even get them in an "on top of your change" patch form. - -. Polish, refine, and re-send to the list and the people who - spend their time to improve your patch. Go back to step (2). - -. The list forms consensus that the last round of your patch is - good. Send it to the maintainer and cc the list. - -. A topic branch is created with the patch and is merged to `next`, - and cooked further and eventually graduates to `master`. - -In any time between the (2)-(3) cycle, the maintainer may pick it up -from the list and queue it to `seen`, in order to make it easier for -people to play with it without having to pick up and apply the patch to -their trees themselves. - -[[patch-status]] -== Know the status of your patch after submission +- The "Git documentation translations" project, led by Jean-Noël + Avila, translates our documentation pages. Their work products are + maintained separately from this project, not as part of our tree: -* You can use Git itself to find out when your patch is merged in - master. `git pull --rebase` will automatically skip already-applied - patches, and will let you know. This works only if you rebase on top - of the branch in which your patch has been merged (i.e. it will not - tell you if your patch is merged in `seen` if you rebase on top of - master). + https://github.com/jnavila/git-manpages-l10n/ -* Read the Git mailing list, the maintainer regularly posts messages - entitled "What's cooking in git.git" giving - the status of various proposed changes. == GitHub CI[[GHCI]] diff --git a/Documentation/config.txt b/Documentation/config.txt index 6f649c997c..2d2d06d7ee 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -488,6 +488,8 @@ include::config/pager.txt[] include::config/pretty.txt[] +include::config/promisor.txt[] + include::config/protocol.txt[] include::config/pull.txt[] @@ -498,6 +500,8 @@ include::config/rebase.txt[] include::config/receive.txt[] +include::config/reftable.txt[] + include::config/remote.txt[] include::config/remotes.txt[] diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt index 01df96fab3..2c5db0ad84 100644 --- a/Documentation/config/alias.txt +++ b/Documentation/config/alias.txt @@ -21,8 +21,23 @@ If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command. For example, defining `alias.new = !gitk --all --not ORIG_HEAD`, the invocation `git new` is equivalent to running the shell command -`gitk --all --not ORIG_HEAD`. Note that shell commands will be -executed from the top-level directory of a repository, which may -not necessarily be the current directory. -`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix` -from the original current directory. See linkgit:git-rev-parse[1]. +`gitk --all --not ORIG_HEAD`. Note: ++ +* Shell commands will be executed from the top-level directory of a + repository, which may not necessarily be the current directory. +* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix` + from the original current directory. See linkgit:git-rev-parse[1]. +* Shell command aliases always receive any extra arguments provided to + the Git command-line as positional arguments. +** Care should be taken if your shell alias is a "one-liner" script + with multiple commands (e.g. in a pipeline), references multiple + arguments, or is otherwise not able to handle positional arguments + added at the end. For example: `alias.cmd = "!echo $1 | grep $2"` + called as `git cmd 1 2` will be executed as 'echo $1 | grep $2 + 1 2', which is not what you want. +** A convenient way to deal with this is to write your script + operations in an inline function that is then called with any + arguments from the command-line. For example `alias.cmd = "!c() { + echo $1 | grep $2 ; }; c" will correctly execute the prior example. +** Setting `GIT_TRACE=1` can help you debug the command being run for + your alias. diff --git a/Documentation/config/promisor.txt b/Documentation/config/promisor.txt new file mode 100644 index 0000000000..98c5cb2ec2 --- /dev/null +++ b/Documentation/config/promisor.txt @@ -0,0 +1,3 @@ +promisor.quiet:: + If set to "true" assume `--quiet` when fetching additional + objects for a partial clone. diff --git a/Documentation/config/reftable.txt b/Documentation/config/reftable.txt new file mode 100644 index 0000000000..0515727977 --- /dev/null +++ b/Documentation/config/reftable.txt @@ -0,0 +1,48 @@ +reftable.blockSize:: + The size in bytes used by the reftable backend when writing blocks. + The block size is determined by the writer, and does not have to be a + power of 2. The block size must be larger than the longest reference + name or log entry used in the repository, as references cannot span + blocks. ++ +Powers of two that are friendly to the virtual memory system or +filesystem (such as 4kB or 8kB) are recommended. Larger sizes (64kB) can +yield better compression, with a possible increased cost incurred by +readers during access. ++ +The largest block size is `16777215` bytes (15.99 MiB). The default value is +`4096` bytes (4kB). A value of `0` will use the default value. + +reftable.restartInterval:: + The interval at which to create restart points. The reftable backend + determines the restart points at file creation. Every 16 may be + more suitable for smaller block sizes (4k or 8k), every 64 for larger + block sizes (64k). ++ +More frequent restart points reduces prefix compression and increases +space consumed by the restart table, both of which increase file size. ++ +Less frequent restart points makes prefix compression more effective, +decreasing overall file size, with increased penalties for readers +walking through more records after the binary search step. ++ +A maximum of `65535` restart points per block is supported. ++ +The default value is to create restart points every 16 records. A value of `0` +will use the default value. + +reftable.indexObjects:: + Whether the reftable backend shall write object blocks. Object blocks + are a reverse mapping of object ID to the references pointing to them. ++ +The default value is `true`. + +reftable.geometricFactor:: + Whenever the reftable backend appends a new table to the stack, it + performs auto compaction to ensure that there is only a handful of + tables. The backend does this by ensuring that tables form a geometric + sequence regarding the respective sizes of each table. ++ +By default, the geometric sequence uses a factor of 2, meaning that for any +table, the next-biggest table must at least be twice as big. A maximum factor +of 256 is supported. diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt index 577df40223..2d45c98b12 100644 --- a/Documentation/config/safe.txt +++ b/Documentation/config/safe.txt @@ -44,7 +44,8 @@ string `*`. This will allow all repositories to be treated as if their directory was listed in the `safe.directory` list. If `safe.directory=*` is set in system config and you want to re-enable this protection, then initialize your list with an empty value before listing the repositories -that you deem safe. +that you deem safe. Giving a directory with `/*` appended to it will +allow access to all repositories under the named directory. + As explained, Git only allows you to access repositories owned by yourself, i.e. the user who is running Git, by default. When Git diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 0e9456957e..c7df20e571 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -329,12 +329,13 @@ explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). --name-only:: - Show only names of changed files. The file names are often encoded in UTF-8. + Show only the name of each changed file in the post-image tree. + The file names are often encoded in UTF-8. For more information see the discussion about encoding in the linkgit:git-log[1] manual page. --name-status:: - Show only names and status of changed files. See the description + Show only the name(s) and status of each changed file. See the description of the `--diff-filter` option on what the status letters mean. Just like `--name-only` the file names are often encoded in UTF-8. diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt index 5edc06c658..f643585a34 100644 --- a/Documentation/fsck-msgids.txt +++ b/Documentation/fsck-msgids.txt @@ -164,18 +164,6 @@ `nullSha1`:: (WARN) Tree contains entries pointing to a null sha1. -`symlinkPointsToGitDir`:: - (WARN) Symbolic link points inside a gitdir. - -`symlinkTargetBlob`:: - (ERROR) A non-blob found instead of a symbolic link's target. - -`symlinkTargetLength`:: - (WARN) Symbolic link target longer than maximum path length. - -`symlinkTargetMissing`:: - (ERROR) Unable to read symbolic link target's blob. - `treeNotSorted`:: (ERROR) A tree is not properly sorted. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 369af2c4a7..8708b31593 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -357,6 +357,11 @@ material (this may change in the future). between the previous and current series of patches by adjusting the creation/deletion cost fudge factor. See linkgit:git-range-diff[1]) for details. ++ +Defaults to 999 (the linkgit:git-range-diff[1] uses 60), as the use +case is to show comparison with an older iteration of the same +topic and the tool should find more correspondence between the two +sets of patches. --notes[=<ref>]:: --no-notes:: diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index f9d5a35fa0..dc12d38534 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -18,8 +18,15 @@ Many Git porcelainish commands take a mixture of flags (i.e. parameters that begin with a dash '-') and parameters meant for the underlying 'git rev-list' command they use internally and flags and parameters for the other commands they use -downstream of 'git rev-list'. This command is used to -distinguish between them. +downstream of 'git rev-list'. The primary purpose of this command +is to allow calling programs to distinguish between them. There are +a few other operation modes that have nothing to do with the above +"help parse command line options". + +Unless otherwise specified, most of the options and operation modes +require you to run this command inside a git repository or a working +tree that is under the control of a git repository, and will give you +a fatal error otherwise. OPTIONS @@ -32,11 +39,15 @@ Each of these options must appear first on the command line. --parseopt:: Use 'git rev-parse' in option parsing mode (see PARSEOPT section below). + The command in this mode can be used outside a repository or + a working tree controlled by a repository. --sq-quote:: Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE section below). In contrast to the `--sq` option below, this mode only does quoting. Nothing else is done to command input. + The command in this mode can be used outside a repository or + a working tree controlled by a repository. Options for --parseopt ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 8c47890a6a..7128aed540 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -25,6 +25,7 @@ SYNOPSIS [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] [-z] [--stdin] [--index-version <n>] + [--show-index-version] [--verbose] [--] [<file>...] @@ -793,7 +793,6 @@ TEST_BUILTINS_OBJS += test-dump-fsmonitor.o TEST_BUILTINS_OBJS += test-dump-split-index.o TEST_BUILTINS_OBJS += test-dump-untracked-cache.o TEST_BUILTINS_OBJS += test-env-helper.o -TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-example-tap.o TEST_BUILTINS_OBJS += test-find-pack.o TEST_BUILTINS_OBJS += test-fsmonitor-client.o @@ -839,7 +838,6 @@ TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o TEST_BUILTINS_OBJS += test-simple-ipc.o -TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o @@ -1335,10 +1333,16 @@ THIRD_PARTY_SOURCES += compat/regex/% THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1dc/% -UNIT_TEST_PROGRAMS += t-mem-pool -UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-ctype +UNIT_TEST_PROGRAMS += t-example-decorate +UNIT_TEST_PROGRAMS += t-hash +UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-prio-queue +UNIT_TEST_PROGRAMS += t-reftable-basics +UNIT_TEST_PROGRAMS += t-strbuf +UNIT_TEST_PROGRAMS += t-strcmp-offset +UNIT_TEST_PROGRAMS += t-strvec +UNIT_TEST_PROGRAMS += t-trailer UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o @@ -2671,7 +2675,6 @@ REFTABLE_OBJS += reftable/stack.o REFTABLE_OBJS += reftable/tree.o REFTABLE_OBJS += reftable/writer.o -REFTABLE_TEST_OBJS += reftable/basics_test.o REFTABLE_TEST_OBJS += reftable/block_test.o REFTABLE_TEST_OBJS += reftable/dump.o REFTABLE_TEST_OBJS += reftable/merged_test.o diff --git a/add-patch.c b/add-patch.c index 2252895c28..814de57c4a 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1227,6 +1227,7 @@ static int prompt_yesno(struct add_p_state *s, const char *prompt) fflush(stdout); if (read_single_character(s) == EOF) return -1; + /* do not limit to 1-byte input to allow 'no' etc. */ switch (tolower(s->answer.buf[0])) { case 'n': return 0; case 'y': return 1; @@ -1510,6 +1511,12 @@ static int patch_update_file(struct add_p_state *s, if (!s->answer.len) continue; ch = tolower(s->answer.buf[0]); + + /* 'g' takes a hunk number and '/' takes a regexp */ + if (s->answer.len != 1 && (ch != 'g' && ch != '/')) { + err(s, _("Only one letter is expected, got '%s'"), s->answer.buf); + continue; + } if (ch == 'y') { hunk->use = USE_HUNK; soft_increment: @@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value, return 0; if (data->alias) { - if (!strcasecmp(p, data->alias)) - return git_config_string((const char **)&data->v, + if (!strcasecmp(p, data->alias)) { + FREE_AND_NULL(data->v); + return git_config_string(&data->v, key, value); + } } else if (data->list) { string_list_append(data->list, p); } @@ -25,7 +25,7 @@ #include "tree-walk.h" #include "object-name.h" -const char *git_attr_tree; +char *git_attr_tree; const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; @@ -1205,15 +1205,16 @@ static void collect_some_attrs(struct index_state *istate, } static const char *default_attr_source_tree_object_name; -static int ignore_bad_attr_tree; void set_git_attr_source(const char *tree_object_name) { default_attr_source_tree_object_name = xstrdup(tree_object_name); } -static void compute_default_attr_source(struct object_id *attr_source) +static int compute_default_attr_source(struct object_id *attr_source) { + int ignore_bad_attr_tree = 0; + if (!default_attr_source_tree_object_name) default_attr_source_tree_object_name = getenv(GIT_ATTR_SOURCE_ENVIRONMENT); @@ -1222,22 +1223,34 @@ static void compute_default_attr_source(struct object_id *attr_source) ignore_bad_attr_tree = 1; } - if (!default_attr_source_tree_object_name || !is_null_oid(attr_source)) - return; + if (!default_attr_source_tree_object_name) + return 0; + + if (!startup_info->have_repository) { + if (!ignore_bad_attr_tree) + die(_("cannot use --attr-source or GIT_ATTR_SOURCE without repo")); + return 0; + } if (repo_get_oid_treeish(the_repository, default_attr_source_tree_object_name, - attr_source) && !ignore_bad_attr_tree) - die(_("bad --attr-source or GIT_ATTR_SOURCE")); + attr_source)) { + if (!ignore_bad_attr_tree) + die(_("bad --attr-source or GIT_ATTR_SOURCE")); + return 0; + } + + return 1; } static struct object_id *default_attr_source(void) { static struct object_id attr_source; + static int has_attr_source = -1; - if (is_null_oid(&attr_source)) - compute_default_attr_source(&attr_source); - if (is_null_oid(&attr_source)) + if (has_attr_source < 0) + has_attr_source = compute_default_attr_source(&attr_source); + if (!has_attr_source) return NULL; return &attr_source; } @@ -236,6 +236,6 @@ const char *git_attr_global_file(void); /* Return whether the system gitattributes file is enabled and should be used. */ int git_attr_system_is_enabled(void); -extern const char *git_attr_tree; +extern char *git_attr_tree; #endif /* ATTR_H */ diff --git a/builtin/apply.c b/builtin/apply.c index 861a01910c..d623c52f78 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "gettext.h" #include "repository.h" +#include "hash.h" #include "apply.h" static const char * const apply_usage[] = { @@ -18,6 +19,15 @@ int cmd_apply(int argc, const char **argv, const char *prefix) if (init_apply_state(&state, the_repository, prefix)) exit(128); + /* + * We could to redo the "apply.c" machinery to make this + * arbitrary fallback unnecessary, but it is dubious that it + * is worth the effort. + * cf. https://lore.kernel.org/git/xmqqcypfcmn4.fsf@gitster.g/ + */ + if (!the_hash_algo) + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + argc = apply_parse_options(argc, argv, &state, &force_apply, &options, apply_usage); diff --git a/builtin/blame.c b/builtin/blame.c index 6bc7aa6085..e09ff0155a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -718,7 +718,7 @@ static int git_blame_config(const char *var, const char *value, return 0; } if (!strcmp(var, "blame.ignorerevsfile")) { - const char *str; + char *str; int ret; ret = git_config_pathname(&str, var, value); @@ -915,7 +915,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct range_set ranges; unsigned int range_i; long anchor; - const int hexsz = the_hash_algo->hexsz; long num_lines = 0; const char *str_usage = cmd_is_annotate ? annotate_usage : blame_usage; const char **opt_usage = cmd_is_annotate ? annotate_opt_usage : blame_opt_usage; @@ -973,11 +972,11 @@ parse_done: } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev && abbrev < hexsz) + if (0 < abbrev && abbrev < (int)the_hash_algo->hexsz) /* one more abbrev length is needed for the boundary commit */ abbrev++; else if (!abbrev) - abbrev = hexsz; + abbrev = the_hash_algo->hexsz; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); diff --git a/builtin/bundle.c b/builtin/bundle.c index 3ad11dc5d0..d5d41a8f67 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -140,6 +140,11 @@ static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { builtin_bundle_verify_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ + if (!startup_info->have_repository) { + ret = error(_("need a repository to verify a bundle")); + goto cleanup; + } + if ((bundle_fd = open_bundle(bundle_file, &header, &name)) < 0) { ret = 1; goto cleanup; diff --git a/builtin/checkout.c b/builtin/checkout.c index f90a4ca4b7..3cf44b4683 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1275,12 +1275,12 @@ static void setup_new_branch_info_and_source_tree( } } -static const char *parse_remote_branch(const char *arg, - struct object_id *rev, - int could_be_checkout_paths) +static char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) { int num_matches = 0; - const char *remote = unique_tracking_name(arg, rev, &num_matches); + char *remote = unique_tracking_name(arg, rev, &num_matches); if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n" @@ -1316,6 +1316,7 @@ static int parse_branchname_arg(int argc, const char **argv, const char **new_branch = &opts->new_branch; int argcount = 0; const char *arg; + char *remote = NULL; int dash_dash_pos; int has_dash_dash = 0; int i; @@ -1416,8 +1417,8 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim = 0; if (recover_with_dwim) { - const char *remote = parse_remote_branch(arg, rev, - could_be_checkout_paths); + remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { *new_branch = arg; arg = remote; @@ -1459,6 +1460,7 @@ static int parse_branchname_arg(int argc, const char **argv, argc--; } + free(remote); return argcount; } diff --git a/builtin/clone.c b/builtin/clone.c index e808e02017..b89ca925b1 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -972,8 +972,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int hash_algo; enum ref_storage_format ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; const int do_not_override_repo_unix_permissions = -1; - const char *template_dir; - char *template_dir_dup = NULL; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; @@ -993,13 +991,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); - xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */); - template_dir = get_template_dir(option_template); - if (*template_dir && !is_absolute_path(template_dir)) - template_dir = template_dir_dup = - absolute_pathdup(template_dir); - xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1); - if (option_depth || option_since || option_not.nr) deepen = 1; if (option_single_branch == -1) @@ -1161,7 +1152,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * repository, and reference backends may persist that information into * their on-disk data structures. */ - init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, ref_storage_format, NULL, do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); @@ -1553,7 +1544,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) free(dir); free(path); free(repo_to_free); - free(template_dir_dup); + UNLEAK(repo); junk_mode = JUNK_LEAVE_ALL; transport_ls_refs_options_release(&transport_ls_refs_options); diff --git a/builtin/commit.c b/builtin/commit.c index 78bfae2164..f53e7e86ff 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -107,7 +107,7 @@ static enum { } commit_style; static const char *logfile, *force_author; -static const char *template_file; +static char *template_file; /* * The _message variables are commit names from which to take * the commit message and/or authorship. @@ -133,7 +133,7 @@ static struct strvec trailer_args = STRVEC_INIT; * is specified explicitly. */ static enum commit_msg_cleanup_mode cleanup_mode; -static const char *cleanup_arg; +static char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; diff --git a/builtin/config.c b/builtin/config.c index 80aa9d8a66..20a0b64090 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -62,56 +62,66 @@ static const char *const builtin_config_edit_usage[] = { NULL }; -static char *key; -static regex_t *key_regexp; -static const char *value_pattern; -static regex_t *regexp; -static int show_keys; -static int omit_values; -static int use_key_regexp; -static int do_all; -static int do_not_match; -static char delim = '='; -static char key_delim = ' '; -static char term = '\n'; - -static parse_opt_subcommand_fn *subcommand; -static int use_global_config, use_system_config, use_local_config; -static int use_worktree_config; -static struct git_config_source given_config_source; -static int actions, type; -static char *default_value; -static int end_nul; -static int respect_includes_opt = -1; -static struct config_options config_options; -static int show_origin; -static int show_scope; -static int fixed_value; -static const char *comment_arg; - -#define ACTION_GET (1<<0) -#define ACTION_GET_ALL (1<<1) -#define ACTION_GET_REGEXP (1<<2) -#define ACTION_REPLACE_ALL (1<<3) -#define ACTION_ADD (1<<4) -#define ACTION_UNSET (1<<5) -#define ACTION_UNSET_ALL (1<<6) -#define ACTION_RENAME_SECTION (1<<7) -#define ACTION_REMOVE_SECTION (1<<8) -#define ACTION_LIST (1<<9) -#define ACTION_EDIT (1<<10) -#define ACTION_SET (1<<11) -#define ACTION_SET_ALL (1<<12) -#define ACTION_GET_COLOR (1<<13) -#define ACTION_GET_COLORBOOL (1<<14) -#define ACTION_GET_URLMATCH (1<<15) - -/* - * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than - * one line of output and which should therefore be paged. - */ -#define PAGING_ACTIONS (ACTION_LIST | ACTION_GET_ALL | \ - ACTION_GET_REGEXP | ACTION_GET_URLMATCH) +#define CONFIG_LOCATION_OPTIONS(opts) \ + OPT_GROUP(N_("Config file location")), \ + OPT_BOOL(0, "global", &opts.use_global_config, N_("use global config file")), \ + OPT_BOOL(0, "system", &opts.use_system_config, N_("use system config file")), \ + OPT_BOOL(0, "local", &opts.use_local_config, N_("use repository config file")), \ + OPT_BOOL(0, "worktree", &opts.use_worktree_config, N_("use per-worktree config file")), \ + OPT_STRING('f', "file", &opts.source.file, N_("file"), N_("use given config file")), \ + OPT_STRING(0, "blob", &opts.source.blob, N_("blob-id"), N_("read config from given blob object")) + +struct config_location_options { + struct git_config_source source; + struct config_options options; + char *file_to_free; + int use_global_config; + int use_system_config; + int use_local_config; + int use_worktree_config; + int respect_includes_opt; +}; +#define CONFIG_LOCATION_OPTIONS_INIT { \ + .respect_includes_opt = -1, \ +} + +#define CONFIG_TYPE_OPTIONS(type) \ + OPT_GROUP(N_("Type")), \ + OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \ + OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \ + OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \ + OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \ + OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \ + OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \ + OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE) + +#define CONFIG_DISPLAY_OPTIONS(opts) \ + OPT_GROUP(N_("Display options")), \ + OPT_BOOL('z', "null", &opts.end_nul, N_("terminate values with NUL byte")), \ + OPT_BOOL(0, "name-only", &opts.omit_values, N_("show variable names only")), \ + OPT_BOOL(0, "show-origin", &opts.show_origin, N_("show origin of config (file, standard input, blob, command line)")), \ + OPT_BOOL(0, "show-scope", &opts.show_scope, N_("show scope of config (worktree, local, global, system, command)")), \ + OPT_BOOL(0, "show-names", &opts.show_keys, N_("show config keys in addition to their values")), \ + CONFIG_TYPE_OPTIONS(opts.type) + +struct config_display_options { + int end_nul; + int omit_values; + int show_origin; + int show_scope; + int show_keys; + int type; + char *default_value; + /* Populated via `display_options_init()`. */ + int term; + int delim; + int key_delim; +}; +#define CONFIG_DISPLAY_OPTIONS_INIT { \ + .term = '\n', \ + .delim = '=', \ + .key_delim = ' ', \ +} #define TYPE_BOOL 1 #define TYPE_INT 2 @@ -125,8 +135,6 @@ static const char *comment_arg; { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ PARSE_OPT_NONEG, option_parse_type, (i) } -static NORETURN void usage_builtin_config(void); - static int option_parse_type(const struct option *opt, const char *arg, int unset) { @@ -171,7 +179,7 @@ static int option_parse_type(const struct option *opt, const char *arg, * --type=int'. */ error(_("only one type at a time")); - usage_builtin_config(); + exit(129); } *to_type = new_type; @@ -187,27 +195,29 @@ static void check_argc(int argc, int min, int max) else error(_("wrong number of arguments, should be from %d to %d"), min, max); - usage_builtin_config(); + exit(129); } -static void show_config_origin(const struct key_value_info *kvi, +static void show_config_origin(const struct config_display_options *opts, + const struct key_value_info *kvi, struct strbuf *buf) { - const char term = end_nul ? '\0' : '\t'; + const char term = opts->end_nul ? '\0' : '\t'; strbuf_addstr(buf, config_origin_type_name(kvi->origin_type)); strbuf_addch(buf, ':'); - if (end_nul) + if (opts->end_nul) strbuf_addstr(buf, kvi->filename ? kvi->filename : ""); else quote_c_style(kvi->filename ? kvi->filename : "", buf, NULL, 0); strbuf_addch(buf, term); } -static void show_config_scope(const struct key_value_info *kvi, +static void show_config_scope(const struct config_display_options *opts, + const struct key_value_info *kvi, struct strbuf *buf) { - const char term = end_nul ? '\0' : '\t'; + const char term = opts->end_nul ? '\0' : '\t'; const char *scope = config_scope_name(kvi->scope); strbuf_addstr(buf, N_(scope)); @@ -216,24 +226,25 @@ static void show_config_scope(const struct key_value_info *kvi, static int show_all_config(const char *key_, const char *value_, const struct config_context *ctx, - void *cb UNUSED) + void *cb) { + const struct config_display_options *opts = cb; const struct key_value_info *kvi = ctx->kvi; - if (show_origin || show_scope) { + if (opts->show_origin || opts->show_scope) { struct strbuf buf = STRBUF_INIT; - if (show_scope) - show_config_scope(kvi, &buf); - if (show_origin) - show_config_origin(kvi, &buf); + if (opts->show_scope) + show_config_scope(opts, kvi, &buf); + if (opts->show_origin) + show_config_origin(opts, kvi, &buf); /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); } - if (!omit_values && value_) - printf("%s%c%s%c", key_, delim, value_, term); + if (!opts->omit_values && value_) + printf("%s%c%s%c", key_, opts->delim, value_, opts->term); else - printf("%s%c", key_, term); + printf("%s%c", key_, opts->term); return 0; } @@ -243,26 +254,27 @@ struct strbuf_list { int alloc; }; -static int format_config(struct strbuf *buf, const char *key_, +static int format_config(const struct config_display_options *opts, + struct strbuf *buf, const char *key_, const char *value_, const struct key_value_info *kvi) { - if (show_scope) - show_config_scope(kvi, buf); - if (show_origin) - show_config_origin(kvi, buf); - if (show_keys) + if (opts->show_scope) + show_config_scope(opts, kvi, buf); + if (opts->show_origin) + show_config_origin(opts, kvi, buf); + if (opts->show_keys) strbuf_addstr(buf, key_); - if (!omit_values) { - if (show_keys) - strbuf_addch(buf, key_delim); + if (!opts->omit_values) { + if (opts->show_keys) + strbuf_addch(buf, opts->key_delim); - if (type == TYPE_INT) + if (opts->type == TYPE_INT) strbuf_addf(buf, "%"PRId64, git_config_int64(key_, value_ ? value_ : "", kvi)); - else if (type == TYPE_BOOL) + else if (opts->type == TYPE_BOOL) strbuf_addstr(buf, git_config_bool(key_, value_) ? "true" : "false"); - else if (type == TYPE_BOOL_OR_INT) { + else if (opts->type == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key_, value_, kvi, &is_bool); @@ -270,24 +282,24 @@ static int format_config(struct strbuf *buf, const char *key_, strbuf_addstr(buf, v ? "true" : "false"); else strbuf_addf(buf, "%d", v); - } else if (type == TYPE_BOOL_OR_STR) { + } else if (opts->type == TYPE_BOOL_OR_STR) { int v = git_parse_maybe_bool(value_); if (v < 0) strbuf_addstr(buf, value_); else strbuf_addstr(buf, v ? "true" : "false"); - } else if (type == TYPE_PATH) { - const char *v; + } else if (opts->type == TYPE_PATH) { + char *v; if (git_config_pathname(&v, key_, value_) < 0) return -1; strbuf_addstr(buf, v); free((char *)v); - } else if (type == TYPE_EXPIRY_DATE) { + } else if (opts->type == TYPE_EXPIRY_DATE) { timestamp_t t; if (git_config_expiry_date(&t, key_, value_) < 0) return -1; strbuf_addf(buf, "%"PRItime, t); - } else if (type == TYPE_COLOR) { + } else if (opts->type == TYPE_COLOR) { char v[COLOR_MAXLEN]; if (git_config_color(v, key_, value_) < 0) return -1; @@ -296,43 +308,73 @@ static int format_config(struct strbuf *buf, const char *key_, strbuf_addstr(buf, value_); } else { /* Just show the key name; back out delimiter */ - if (show_keys) + if (opts->show_keys) strbuf_setlen(buf, buf->len - 1); } } - strbuf_addch(buf, term); + strbuf_addch(buf, opts->term); return 0; } +#define GET_VALUE_ALL (1 << 0) +#define GET_VALUE_KEY_REGEXP (1 << 1) + +struct collect_config_data { + const struct config_display_options *display_opts; + struct strbuf_list *values; + const char *value_pattern; + const char *key; + regex_t *regexp; + regex_t *key_regexp; + int do_not_match; + unsigned get_value_flags; + unsigned flags; +}; + static int collect_config(const char *key_, const char *value_, const struct config_context *ctx, void *cb) { - struct strbuf_list *values = cb; + struct collect_config_data *data = cb; + struct strbuf_list *values = data->values; const struct key_value_info *kvi = ctx->kvi; - if (!use_key_regexp && strcmp(key_, key)) + if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) && + strcmp(key_, data->key)) return 0; - if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) + if ((data->get_value_flags & GET_VALUE_KEY_REGEXP) && + regexec(data->key_regexp, key_, 0, NULL, 0)) return 0; - if (fixed_value && strcmp(value_pattern, (value_?value_:""))) + if ((data->flags & CONFIG_FLAGS_FIXED_VALUE) && + strcmp(data->value_pattern, (value_?value_:""))) return 0; - if (regexp != NULL && - (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) + if (data->regexp && + (data->do_not_match ^ !!regexec(data->regexp, (value_?value_:""), 0, NULL, 0))) return 0; ALLOC_GROW(values->items, values->nr + 1, values->alloc); strbuf_init(&values->items[values->nr], 0); - return format_config(&values->items[values->nr++], key_, value_, kvi); + return format_config(data->display_opts, &values->items[values->nr++], + key_, value_, kvi); } -static int get_value(const char *key_, const char *regex_, unsigned flags) +static int get_value(const struct config_location_options *opts, + const struct config_display_options *display_opts, + const char *key_, const char *regex_, + unsigned get_value_flags, unsigned flags) { int ret = CONFIG_GENERIC_ERROR; struct strbuf_list values = {NULL}; + struct collect_config_data data = { + .display_opts = display_opts, + .values = &values, + .get_value_flags = get_value_flags, + .flags = flags, + }; + char *key = NULL; int i; - if (use_key_regexp) { + if (get_value_flags & GET_VALUE_KEY_REGEXP) { char *tl; /* @@ -349,10 +391,10 @@ static int get_value(const char *key_, const char *regex_, unsigned flags) for (tl = key; *tl && *tl != '.'; tl++) *tl = tolower(*tl); - key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(key_regexp, key, REG_EXTENDED)) { + data.key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); + if (regcomp(data.key_regexp, key, REG_EXTENDED)) { error(_("invalid key pattern: %s"), key_); - FREE_AND_NULL(key_regexp); + FREE_AND_NULL(data.key_regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } @@ -361,30 +403,32 @@ static int get_value(const char *key_, const char *regex_, unsigned flags) ret = CONFIG_INVALID_KEY; goto free_strings; } + + data.key = key; } if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE)) - value_pattern = regex_; + data.value_pattern = regex_; else if (regex_) { if (regex_[0] == '!') { - do_not_match = 1; + data.do_not_match = 1; regex_++; } - regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(regexp, regex_, REG_EXTENDED)) { + data.regexp = (regex_t*)xmalloc(sizeof(regex_t)); + if (regcomp(data.regexp, regex_, REG_EXTENDED)) { error(_("invalid pattern: %s"), regex_); - FREE_AND_NULL(regexp); + FREE_AND_NULL(data.regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } } - config_with_options(collect_config, &values, - &given_config_source, the_repository, - &config_options); + config_with_options(collect_config, &data, + &opts->source, the_repository, + &opts->options); - if (!values.nr && default_value) { + if (!values.nr && display_opts->default_value) { struct key_value_info kvi = KVI_INIT; struct strbuf *item; @@ -392,16 +436,17 @@ static int get_value(const char *key_, const char *regex_, unsigned flags) ALLOC_GROW(values.items, values.nr + 1, values.alloc); item = &values.items[values.nr++]; strbuf_init(item, 0); - if (format_config(item, key_, default_value, &kvi) < 0) + if (format_config(display_opts, item, key_, + display_opts->default_value, &kvi) < 0) die(_("failed to format default config value: %s"), - default_value); + display_opts->default_value); } ret = !values.nr; for (i = 0; i < values.nr; i++) { struct strbuf *buf = values.items + i; - if (do_all || i == values.nr - 1) + if ((get_value_flags & GET_VALUE_ALL) || i == values.nr - 1) fwrite(buf->buf, 1, buf->len, stdout); strbuf_release(buf); } @@ -409,20 +454,20 @@ static int get_value(const char *key_, const char *regex_, unsigned flags) free_strings: free(key); - if (key_regexp) { - regfree(key_regexp); - free(key_regexp); + if (data.key_regexp) { + regfree(data.key_regexp); + free(data.key_regexp); } - if (regexp) { - regfree(regexp); - free(regexp); + if (data.regexp) { + regfree(data.regexp); + free(data.regexp); } return ret; } static char *normalize_value(const char *key, const char *value, - struct key_value_info *kvi) + int type, struct key_value_info *kvi) { if (!value) return NULL; @@ -473,97 +518,113 @@ static char *normalize_value(const char *key, const char *value, BUG("cannot normalize type %d", type); } -static int get_color_found; -static const char *get_color_slot; -static const char *get_colorbool_slot; -static char parsed_color[COLOR_MAXLEN]; +struct get_color_config_data { + int get_color_found; + const char *get_color_slot; + char parsed_color[COLOR_MAXLEN]; +}; static int git_get_color_config(const char *var, const char *value, const struct config_context *ctx UNUSED, - void *cb UNUSED) + void *cb) { - if (!strcmp(var, get_color_slot)) { + struct get_color_config_data *data = cb; + + if (!strcmp(var, data->get_color_slot)) { if (!value) config_error_nonbool(var); - if (color_parse(value, parsed_color) < 0) + if (color_parse(value, data->parsed_color) < 0) return -1; - get_color_found = 1; + data->get_color_found = 1; } return 0; } -static void get_color(const char *var, const char *def_color) +static void get_color(const struct config_location_options *opts, + const char *var, const char *def_color) { - get_color_slot = var; - get_color_found = 0; - parsed_color[0] = '\0'; - config_with_options(git_get_color_config, NULL, - &given_config_source, the_repository, - &config_options); - - if (!get_color_found && def_color) { - if (color_parse(def_color, parsed_color) < 0) + struct get_color_config_data data = { + .get_color_slot = var, + .parsed_color[0] = '\0', + }; + + config_with_options(git_get_color_config, &data, + &opts->source, the_repository, + &opts->options); + + if (!data.get_color_found && def_color) { + if (color_parse(def_color, data.parsed_color) < 0) die(_("unable to parse default color value")); } - fputs(parsed_color, stdout); + fputs(data.parsed_color, stdout); } -static int get_colorbool_found; -static int get_diff_color_found; -static int get_color_ui_found; +struct get_colorbool_config_data { + int get_colorbool_found; + int get_diff_color_found; + int get_color_ui_found; + const char *get_colorbool_slot; +}; + static int git_get_colorbool_config(const char *var, const char *value, const struct config_context *ctx UNUSED, - void *data UNUSED) + void *cb) { - if (!strcmp(var, get_colorbool_slot)) - get_colorbool_found = git_config_colorbool(var, value); + struct get_colorbool_config_data *data = cb; + + if (!strcmp(var, data->get_colorbool_slot)) + data->get_colorbool_found = git_config_colorbool(var, value); else if (!strcmp(var, "diff.color")) - get_diff_color_found = git_config_colorbool(var, value); + data->get_diff_color_found = git_config_colorbool(var, value); else if (!strcmp(var, "color.ui")) - get_color_ui_found = git_config_colorbool(var, value); + data->get_color_ui_found = git_config_colorbool(var, value); return 0; } -static int get_colorbool(const char *var, int print) +static int get_colorbool(const struct config_location_options *opts, + const char *var, int print) { - get_colorbool_slot = var; - get_colorbool_found = -1; - get_diff_color_found = -1; - get_color_ui_found = -1; - config_with_options(git_get_colorbool_config, NULL, - &given_config_source, the_repository, - &config_options); - - if (get_colorbool_found < 0) { - if (!strcmp(get_colorbool_slot, "color.diff")) - get_colorbool_found = get_diff_color_found; - if (get_colorbool_found < 0) - get_colorbool_found = get_color_ui_found; + struct get_colorbool_config_data data = { + .get_colorbool_slot = var, + .get_colorbool_found = -1, + .get_diff_color_found = -1, + .get_color_ui_found = -1, + }; + + config_with_options(git_get_colorbool_config, &data, + &opts->source, the_repository, + &opts->options); + + if (data.get_colorbool_found < 0) { + if (!strcmp(data.get_colorbool_slot, "color.diff")) + data.get_colorbool_found = data.get_diff_color_found; + if (data.get_colorbool_found < 0) + data.get_colorbool_found = data.get_color_ui_found; } - if (get_colorbool_found < 0) + if (data.get_colorbool_found < 0) /* default value if none found in config */ - get_colorbool_found = GIT_COLOR_AUTO; + data.get_colorbool_found = GIT_COLOR_AUTO; - get_colorbool_found = want_color(get_colorbool_found); + data.get_colorbool_found = want_color(data.get_colorbool_found); if (print) { - printf("%s\n", get_colorbool_found ? "true" : "false"); + printf("%s\n", data.get_colorbool_found ? "true" : "false"); return 0; } else - return get_colorbool_found ? 0 : 1; + return data.get_colorbool_found ? 0 : 1; } -static void check_write(void) +static void check_write(const struct git_config_source *source) { - if (!given_config_source.file && !startup_info->have_repository) + if (!source->file && !startup_info->have_repository) die(_("not in a git directory")); - if (given_config_source.use_stdin) + if (source->use_stdin) die(_("writing to stdin is not supported")); - if (given_config_source.blob) + if (source->blob) die(_("writing config blobs is not supported")); } @@ -600,10 +661,13 @@ static int urlmatch_collect_fn(const char *var, const char *value, return 0; } -static int get_urlmatch(const char *var, const char *url) +static int get_urlmatch(const struct config_location_options *opts, + const struct config_display_options *_display_opts, + const char *var, const char *url) { int ret; char *section_tail; + struct config_display_options display_opts = *_display_opts; struct string_list_item *item; struct urlmatch_config config = URLMATCH_CONFIG_INIT; struct string_list values = STRING_LIST_INIT_DUP; @@ -620,15 +684,15 @@ static int get_urlmatch(const char *var, const char *url) if (section_tail) { *section_tail = '\0'; config.key = section_tail + 1; - show_keys = 0; + display_opts.show_keys = 0; } else { config.key = NULL; - show_keys = 1; + display_opts.show_keys = 1; } config_with_options(urlmatch_config_entry, &config, - &given_config_source, the_repository, - &config_options); + &opts->source, the_repository, + &opts->options); ret = !values.nr; @@ -636,7 +700,7 @@ static int get_urlmatch(const char *var, const char *url) struct urlmatch_current_candidate_value *matched = item->util; struct strbuf buf = STRBUF_INIT; - format_config(&buf, item->string, + format_config(&display_opts, &buf, item->string, matched->value_is_null ? NULL : matched->value.buf, &matched->kvi); fwrite(buf.buf, 1, buf.len, stdout); @@ -666,34 +730,39 @@ static char *default_user_config(void) return strbuf_detach(&buf, NULL); } -static void handle_config_location(const char *prefix) +static void location_options_init(struct config_location_options *opts, + const char *prefix) { - if (use_global_config + use_system_config + use_local_config + - use_worktree_config + - !!given_config_source.file + !!given_config_source.blob > 1) { + if (!opts->source.file) + opts->source.file = opts->file_to_free = + xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); + + if (opts->use_global_config + opts->use_system_config + + opts->use_local_config + opts->use_worktree_config + + !!opts->source.file + !!opts->source.blob > 1) { error(_("only one config file at a time")); - usage_builtin_config(); + exit(129); } if (!startup_info->have_repository) { - if (use_local_config) + if (opts->use_local_config) die(_("--local can only be used inside a git repository")); - if (given_config_source.blob) + if (opts->source.blob) die(_("--blob can only be used inside a git repository")); - if (use_worktree_config) + if (opts->use_worktree_config) die(_("--worktree can only be used inside a git repository")); } - if (given_config_source.file && - !strcmp(given_config_source.file, "-")) { - given_config_source.file = NULL; - given_config_source.use_stdin = 1; - given_config_source.scope = CONFIG_SCOPE_COMMAND; + if (opts->source.file && + !strcmp(opts->source.file, "-")) { + opts->source.file = NULL; + opts->source.use_stdin = 1; + opts->source.scope = CONFIG_SCOPE_COMMAND; } - if (use_global_config) { - given_config_source.file = git_global_config(); - if (!given_config_source.file) + if (opts->use_global_config) { + opts->source.file = opts->file_to_free = git_global_config(); + if (!opts->source.file) /* * It is unknown if HOME/.gitconfig exists, so * we do not know if we should write to XDG @@ -701,17 +770,18 @@ static void handle_config_location(const char *prefix) * is set and points at a sane location. */ die(_("$HOME not set")); - given_config_source.scope = CONFIG_SCOPE_GLOBAL; - } else if (use_system_config) { - given_config_source.file = git_system_config(); - given_config_source.scope = CONFIG_SCOPE_SYSTEM; - } else if (use_local_config) { - given_config_source.file = git_pathdup("config"); - given_config_source.scope = CONFIG_SCOPE_LOCAL; - } else if (use_worktree_config) { + opts->source.scope = CONFIG_SCOPE_GLOBAL; + } else if (opts->use_system_config) { + opts->source.file = opts->file_to_free = git_system_config(); + opts->source.scope = CONFIG_SCOPE_SYSTEM; + } else if (opts->use_local_config) { + opts->source.file = opts->file_to_free = git_pathdup("config"); + opts->source.scope = CONFIG_SCOPE_LOCAL; + } else if (opts->use_worktree_config) { struct worktree **worktrees = get_worktrees(); if (the_repository->repository_format_worktree_config) - given_config_source.file = git_pathdup("config.worktree"); + opts->source.file = opts->file_to_free = + git_pathdup("config.worktree"); else if (worktrees[0] && worktrees[1]) die(_("--worktree cannot be used with multiple " "working trees unless the config\n" @@ -719,145 +789,102 @@ static void handle_config_location(const char *prefix) "Please read \"CONFIGURATION FILE\"\n" "section in \"git help worktree\" for details")); else - given_config_source.file = git_pathdup("config"); - given_config_source.scope = CONFIG_SCOPE_LOCAL; + opts->source.file = opts->file_to_free = + git_pathdup("config"); + opts->source.scope = CONFIG_SCOPE_LOCAL; free_worktrees(worktrees); - } else if (given_config_source.file) { - if (!is_absolute_path(given_config_source.file) && prefix) - given_config_source.file = - prefix_filename(prefix, given_config_source.file); - given_config_source.scope = CONFIG_SCOPE_COMMAND; - } else if (given_config_source.blob) { - given_config_source.scope = CONFIG_SCOPE_COMMAND; + } else if (opts->source.file) { + if (!is_absolute_path(opts->source.file) && prefix) + opts->source.file = opts->file_to_free = + prefix_filename(prefix, opts->source.file); + opts->source.scope = CONFIG_SCOPE_COMMAND; + } else if (opts->source.blob) { + opts->source.scope = CONFIG_SCOPE_COMMAND; } - if (respect_includes_opt == -1) - config_options.respect_includes = !given_config_source.file; + if (opts->respect_includes_opt == -1) + opts->options.respect_includes = !opts->source.file; else - config_options.respect_includes = respect_includes_opt; + opts->options.respect_includes = opts->respect_includes_opt; if (startup_info->have_repository) { - config_options.commondir = get_git_common_dir(); - config_options.git_dir = get_git_dir(); + opts->options.commondir = get_git_common_dir(); + opts->options.git_dir = get_git_dir(); } } -static void handle_nul(void) { - if (end_nul) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; - } +static void location_options_release(struct config_location_options *opts) +{ + free(opts->file_to_free); } -#define CONFIG_LOCATION_OPTIONS \ - OPT_GROUP(N_("Config file location")), \ - OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), \ - OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), \ - OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), \ - OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), \ - OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), \ - OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")) - -#define CONFIG_TYPE_OPTIONS \ - OPT_GROUP(N_("Type")), \ - OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \ - OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \ - OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \ - OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \ - OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \ - OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \ - OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE) - -#define CONFIG_DISPLAY_OPTIONS \ - OPT_GROUP(N_("Display options")), \ - OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), \ - OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), \ - OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), \ - OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")) - -static struct option builtin_config_options[] = { - CONFIG_LOCATION_OPTIONS, - OPT_GROUP(N_("Action")), - OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET), - OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL), - OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP), - OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), - OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL), - OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), - OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET), - OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL), - OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), - OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), - OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST), - OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), - OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR), - OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL), - CONFIG_TYPE_OPTIONS, - CONFIG_DISPLAY_OPTIONS, - OPT_GROUP(N_("Other")), - OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")), - OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")), - OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")), - OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), - OPT_END(), -}; - -static NORETURN void usage_builtin_config(void) +static void display_options_init(struct config_display_options *opts) { - usage_with_options(builtin_config_usage, builtin_config_options); + if (opts->end_nul) { + opts->term = '\0'; + opts->delim = '\n'; + opts->key_delim = '\n'; + } } static int cmd_config_list(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; + struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, - CONFIG_DISPLAY_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), + CONFIG_DISPLAY_OPTIONS(display_opts), OPT_GROUP(N_("Other")), - OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), + OPT_BOOL(0, "includes", &location_opts.respect_includes_opt, + N_("respect include directives on lookup")), OPT_END(), }; argc = parse_options(argc, argv, prefix, opts, builtin_config_list_usage, 0); check_argc(argc, 0, 0); - handle_config_location(prefix); - handle_nul(); + location_options_init(&location_opts, prefix); + display_options_init(&display_opts); setup_auto_pager("config", 1); - if (config_with_options(show_all_config, NULL, - &given_config_source, the_repository, - &config_options) < 0) { - if (given_config_source.file) + if (config_with_options(show_all_config, &display_opts, + &location_opts.source, the_repository, + &location_opts.options) < 0) { + if (location_opts.source.file) die_errno(_("unable to read config file '%s'"), - given_config_source.file); + location_opts.source.file); else die(_("error processing config file(s)")); } + location_options_release(&location_opts); return 0; } static int cmd_config_get(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; + struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT; const char *value_pattern = NULL, *url = NULL; int flags = 0; + unsigned get_value_flags = 0; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, - CONFIG_TYPE_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), OPT_GROUP(N_("Filter options")), - OPT_BOOL(0, "all", &do_all, N_("return all values for multi-valued config options")), - OPT_BOOL(0, "regexp", &use_key_regexp, N_("interpret the name as a regular expression")), + OPT_BIT(0, "all", &get_value_flags, N_("return all values for multi-valued config options"), GET_VALUE_ALL), + OPT_BIT(0, "regexp", &get_value_flags, N_("interpret the name as a regular expression"), GET_VALUE_KEY_REGEXP), OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")), OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE), OPT_STRING(0, "url", &url, N_("URL"), N_("show config matching the given URL")), - CONFIG_DISPLAY_OPTIONS, - OPT_BOOL(0, "show-names", &show_keys, N_("show config keys in addition to their values")), + CONFIG_DISPLAY_OPTIONS(display_opts), OPT_GROUP(N_("Other")), - OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), - OPT_STRING(0, "default", &default_value, N_("value"), N_("use default value when missing entry")), + OPT_BOOL(0, "includes", &location_opts.respect_includes_opt, + N_("respect include directives on lookup")), + OPT_STRING(0, "default", &display_opts.default_value, + N_("value"), N_("use default value when missing entry")), OPT_END(), }; + int ret; argc = parse_options(argc, argv, prefix, opts, builtin_config_get_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -865,29 +892,38 @@ static int cmd_config_get(int argc, const char **argv, const char *prefix) if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern) die(_("--fixed-value only applies with 'value-pattern'")); - if (default_value && (do_all || url)) + if (display_opts.default_value && + ((get_value_flags & GET_VALUE_ALL) || url)) die(_("--default= cannot be used with --all or --url=")); - if (url && (do_all || use_key_regexp || value_pattern)) + if (url && ((get_value_flags & GET_VALUE_ALL) || + (get_value_flags & GET_VALUE_KEY_REGEXP) || + value_pattern)) die(_("--url= cannot be used with --all, --regexp or --value")); - handle_config_location(prefix); - handle_nul(); + location_options_init(&location_opts, prefix); + display_options_init(&display_opts); setup_auto_pager("config", 1); if (url) - return get_urlmatch(argv[0], url); - return get_value(argv[0], value_pattern, flags); + ret = get_urlmatch(&location_opts, &display_opts, argv[0], url); + else + ret = get_value(&location_opts, &display_opts, argv[0], value_pattern, + get_value_flags, flags); + + location_options_release(&location_opts); + return ret; } static int cmd_config_set(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; const char *value_pattern = NULL, *comment_arg = NULL; char *comment = NULL; - int flags = 0, append = 0; + int flags = 0, append = 0, type = 0; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, - CONFIG_TYPE_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), + CONFIG_TYPE_OPTIONS(type), OPT_GROUP(N_("Filter")), OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE), OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")), @@ -903,7 +939,6 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage, PARSE_OPT_STOP_AT_NON_OPTION); - check_write(); check_argc(argc, 2, 2); if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern) @@ -915,22 +950,24 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix) comment = git_config_prepare_comment_string(comment_arg); - handle_config_location(prefix); + location_options_init(&location_opts, prefix); + check_write(&location_opts.source); - value = normalize_value(argv[0], argv[1], &default_kvi); + value = normalize_value(argv[0], argv[1], type, &default_kvi); if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern) { - ret = git_config_set_multivar_in_file_gently(given_config_source.file, + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, argv[0], value, value_pattern, comment, flags); } else { - ret = git_config_set_in_file_gently(given_config_source.file, + ret = git_config_set_in_file_gently(location_opts.source.file, argv[0], comment, value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" " Use a regexp, --add or --replace-all to change %s."), argv[0]); } + location_options_release(&location_opts); free(comment); free(value); return ret; @@ -938,101 +975,114 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix) static int cmd_config_unset(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; const char *value_pattern = NULL; int flags = 0; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), OPT_GROUP(N_("Filter")), OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE), OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")), OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE), OPT_END(), }; + int ret; argc = parse_options(argc, argv, prefix, opts, builtin_config_unset_usage, PARSE_OPT_STOP_AT_NON_OPTION); - check_write(); check_argc(argc, 1, 1); if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern) die(_("--fixed-value only applies with 'value-pattern'")); - handle_config_location(prefix); + location_options_init(&location_opts, prefix); + check_write(&location_opts.source); if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern) - return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, value_pattern, - NULL, flags); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, + argv[0], NULL, value_pattern, + NULL, flags); else - return git_config_set_in_file_gently(given_config_source.file, argv[0], - NULL, NULL); + ret = git_config_set_in_file_gently(location_opts.source.file, argv[0], + NULL, NULL); + + location_options_release(&location_opts); + return ret; } static int cmd_config_rename_section(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), OPT_END(), }; int ret; argc = parse_options(argc, argv, prefix, opts, builtin_config_rename_section_usage, PARSE_OPT_STOP_AT_NON_OPTION); - check_write(); check_argc(argc, 2, 2); - handle_config_location(prefix); + location_options_init(&location_opts, prefix); + check_write(&location_opts.source); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_rename_section_in_file(location_opts.source.file, argv[0], argv[1]); if (ret < 0) - return ret; + goto out; else if (!ret) die(_("no such section: %s"), argv[0]); + ret = 0; - return 0; +out: + location_options_release(&location_opts); + return ret; } static int cmd_config_remove_section(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), OPT_END(), }; int ret; argc = parse_options(argc, argv, prefix, opts, builtin_config_remove_section_usage, PARSE_OPT_STOP_AT_NON_OPTION); - check_write(); check_argc(argc, 1, 1); - handle_config_location(prefix); + location_options_init(&location_opts, prefix); + check_write(&location_opts.source); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_rename_section_in_file(location_opts.source.file, argv[0], NULL); if (ret < 0) - return ret; + goto out; else if (!ret) die(_("no such section: %s"), argv[0]); + ret = 0; - return 0; +out: + location_options_release(&location_opts); + return ret; } -static int show_editor(void) +static int show_editor(struct config_location_options *opts) { char *config_file; - if (!given_config_source.file && !startup_info->have_repository) + if (!opts->source.file && !startup_info->have_repository) die(_("not in a git directory")); - if (given_config_source.use_stdin) + if (opts->source.use_stdin) die(_("editing stdin is not supported")); - if (given_config_source.blob) + if (opts->source.blob) die(_("editing blobs is not supported")); git_config(git_default_config, NULL); - config_file = given_config_source.file ? - xstrdup(given_config_source.file) : + config_file = opts->source.file ? + xstrdup(opts->source.file) : git_pathdup("config"); - if (use_global_config) { + if (opts->use_global_config) { int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd >= 0) { char *content = default_user_config(); @@ -1051,66 +1101,90 @@ static int show_editor(void) static int cmd_config_edit(int argc, const char **argv, const char *prefix) { + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; struct option opts[] = { - CONFIG_LOCATION_OPTIONS, + CONFIG_LOCATION_OPTIONS(location_opts), OPT_END(), }; + int ret; argc = parse_options(argc, argv, prefix, opts, builtin_config_edit_usage, 0); - check_write(); check_argc(argc, 0, 0); - handle_config_location(prefix); + location_options_init(&location_opts, prefix); + check_write(&location_opts.source); - return show_editor(); + ret = show_editor(&location_opts); + location_options_release(&location_opts); + return ret; } -static struct option builtin_subcommand_options[] = { - OPT_SUBCOMMAND("list", &subcommand, cmd_config_list), - OPT_SUBCOMMAND("get", &subcommand, cmd_config_get), - OPT_SUBCOMMAND("set", &subcommand, cmd_config_set), - OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset), - OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section), - OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section), - OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit), - OPT_END(), -}; - -int cmd_config(int argc, const char **argv, const char *prefix) +static int cmd_config_actions(int argc, const char **argv, const char *prefix) { + enum { + ACTION_GET = (1<<0), + ACTION_GET_ALL = (1<<1), + ACTION_GET_REGEXP = (1<<2), + ACTION_REPLACE_ALL = (1<<3), + ACTION_ADD = (1<<4), + ACTION_UNSET = (1<<5), + ACTION_UNSET_ALL = (1<<6), + ACTION_RENAME_SECTION = (1<<7), + ACTION_REMOVE_SECTION = (1<<8), + ACTION_LIST = (1<<9), + ACTION_EDIT = (1<<10), + ACTION_SET = (1<<11), + ACTION_SET_ALL = (1<<12), + ACTION_GET_COLOR = (1<<13), + ACTION_GET_COLORBOOL = (1<<14), + ACTION_GET_URLMATCH = (1<<15), + }; + struct config_location_options location_opts = CONFIG_LOCATION_OPTIONS_INIT; + struct config_display_options display_opts = CONFIG_DISPLAY_OPTIONS_INIT; + const char *comment_arg = NULL; + int actions = 0; + unsigned flags = 0; + struct option opts[] = { + CONFIG_LOCATION_OPTIONS(location_opts), + OPT_GROUP(N_("Action")), + OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET), + OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL), + OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP), + OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), + OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL), + OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), + OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET), + OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL), + OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), + OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), + OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST), + OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), + OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR), + OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL), + CONFIG_DISPLAY_OPTIONS(display_opts), + OPT_GROUP(N_("Other")), + OPT_STRING(0, "default", &display_opts.default_value, + N_("value"), N_("with --get, use default value when missing entry")), + OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")), + OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE), + OPT_BOOL(0, "includes", &location_opts.respect_includes_opt, + N_("respect include directives on lookup")), + OPT_END(), + }; char *value = NULL, *comment = NULL; - int flags = 0; int ret = 0; struct key_value_info default_kvi = KVI_INIT; - given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); - - /* - * This is somewhat hacky: we first parse the command line while - * keeping all args intact in order to determine whether a subcommand - * has been specified. If so, we re-parse it a second time, but this - * time we drop KEEP_ARGV0. This is so that we don't munge the command - * line in case no subcommand was given, which would otherwise confuse - * us when parsing the legacy-style modes that don't use subcommands. - */ - argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage, - PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT); - if (subcommand) { - argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage, - PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT); - return subcommand(argc, argv, prefix); - } - - argc = parse_options(argc, argv, prefix, builtin_config_options, + argc = parse_options(argc, argv, prefix, opts, builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - handle_config_location(prefix); - handle_nul(); + location_options_init(&location_opts, prefix); + display_options_init(&display_opts); - if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) { + if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && display_opts.type) { error(_("--get-color and variable type are incoherent")); - usage_builtin_config(); + exit(129); } if (actions == 0) @@ -1119,34 +1193,35 @@ int cmd_config(int argc, const char **argv, const char *prefix) case 2: actions = ACTION_SET; break; case 3: actions = ACTION_SET_ALL; break; default: - usage_builtin_config(); + error(_("no action specified")); + exit(129); } - if (omit_values && + if (display_opts.omit_values && !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { error(_("--name-only is only applicable to --list or --get-regexp")); - usage_builtin_config(); + exit(129); } - if (show_origin && !(actions & + if (display_opts.show_origin && !(actions & (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) { error(_("--show-origin is only applicable to --get, --get-all, " "--get-regexp, and --list")); - usage_builtin_config(); + exit(129); } - if (default_value && !(actions & ACTION_GET)) { + if (display_opts.default_value && !(actions & ACTION_GET)) { error(_("--default is only applicable to --get")); - usage_builtin_config(); + exit(129); } if (comment_arg && !(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) { error(_("--comment is only applicable to add/set/replace operations")); - usage_builtin_config(); + exit(129); } /* check usage of --fixed-value */ - if (fixed_value) { + if (flags & CONFIG_FLAGS_FIXED_VALUE) { int allowed_usage = 0; switch (actions) { @@ -1175,123 +1250,125 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (!allowed_usage) { error(_("--fixed-value only applies with 'value-pattern'")); - usage_builtin_config(); + exit(129); } - - flags |= CONFIG_FLAGS_FIXED_VALUE; } comment = git_config_prepare_comment_string(comment_arg); - if (actions & PAGING_ACTIONS) + /* + * The following actions may produce more than one line of output and + * should therefore be paged. + */ + if (actions & (ACTION_LIST | ACTION_GET_ALL | ACTION_GET_REGEXP | ACTION_GET_URLMATCH)) setup_auto_pager("config", 1); if (actions == ACTION_LIST) { check_argc(argc, 0, 0); - if (config_with_options(show_all_config, NULL, - &given_config_source, the_repository, - &config_options) < 0) { - if (given_config_source.file) + if (config_with_options(show_all_config, &display_opts, + &location_opts.source, the_repository, + &location_opts.options) < 0) { + if (location_opts.source.file) die_errno(_("unable to read config file '%s'"), - given_config_source.file); + location_opts.source.file); else die(_("error processing config file(s)")); } } else if (actions == ACTION_EDIT) { - ret = show_editor(); + ret = show_editor(&location_opts); } else if (actions == ACTION_SET) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1], &default_kvi); - ret = git_config_set_in_file_gently(given_config_source.file, argv[0], comment, value); + value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi); + ret = git_config_set_in_file_gently(location_opts.source.file, argv[0], comment, value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" " Use a regexp, --add or --replace-all to change %s."), argv[0]); } else if (actions == ACTION_SET_ALL) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1], &default_kvi); - ret = git_config_set_multivar_in_file_gently(given_config_source.file, + value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, argv[0], value, argv[2], comment, flags); } else if (actions == ACTION_ADD) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1], &default_kvi); - ret = git_config_set_multivar_in_file_gently(given_config_source.file, + value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, argv[0], value, CONFIG_REGEX_NONE, comment, flags); } else if (actions == ACTION_REPLACE_ALL) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1], &default_kvi); - ret = git_config_set_multivar_in_file_gently(given_config_source.file, + value = normalize_value(argv[0], argv[1], display_opts.type, &default_kvi); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, argv[0], value, argv[2], comment, flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); - return get_value(argv[0], argv[1], flags); + ret = get_value(&location_opts, &display_opts, argv[0], argv[1], + 0, flags); } else if (actions == ACTION_GET_ALL) { - do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1], flags); + ret = get_value(&location_opts, &display_opts, argv[0], argv[1], + GET_VALUE_ALL, flags); } else if (actions == ACTION_GET_REGEXP) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; + display_opts.show_keys = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1], flags); + ret = get_value(&location_opts, &display_opts, argv[0], argv[1], + GET_VALUE_ALL|GET_VALUE_KEY_REGEXP, flags); } else if (actions == ACTION_GET_URLMATCH) { check_argc(argc, 2, 2); - return get_urlmatch(argv[0], argv[1]); + ret = get_urlmatch(&location_opts, &display_opts, argv[0], argv[1]); } else if (actions == ACTION_UNSET) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 1, 2); if (argc == 2) - return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], - NULL, flags); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, + argv[0], NULL, argv[1], + NULL, flags); else - return git_config_set_in_file_gently(given_config_source.file, - argv[0], NULL, NULL); + ret = git_config_set_in_file_gently(location_opts.source.file, + argv[0], NULL, NULL); } else if (actions == ACTION_UNSET_ALL) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 1, 2); - return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], - NULL, flags | CONFIG_FLAGS_MULTI_REPLACE); + ret = git_config_set_multivar_in_file_gently(location_opts.source.file, + argv[0], NULL, argv[1], + NULL, flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_RENAME_SECTION) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 2, 2); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_rename_section_in_file(location_opts.source.file, argv[0], argv[1]); if (ret < 0) - return ret; + goto out; else if (!ret) die(_("no such section: %s"), argv[0]); else ret = 0; } else if (actions == ACTION_REMOVE_SECTION) { - check_write(); + check_write(&location_opts.source); check_argc(argc, 1, 1); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_rename_section_in_file(location_opts.source.file, argv[0], NULL); if (ret < 0) - return ret; + goto out; else if (!ret) die(_("no such section: %s"), argv[0]); else @@ -1299,16 +1376,51 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_GET_COLOR) { check_argc(argc, 1, 2); - get_color(argv[0], argv[1]); + get_color(&location_opts, argv[0], argv[1]); } else if (actions == ACTION_GET_COLORBOOL) { check_argc(argc, 1, 2); if (argc == 2) color_stdout_is_tty = git_config_bool("command line", argv[1]); - return get_colorbool(argv[0], argc == 2); + ret = get_colorbool(&location_opts, argv[0], argc == 2); } +out: + location_options_release(&location_opts); free(comment); free(value); return ret; } + +int cmd_config(int argc, const char **argv, const char *prefix) +{ + parse_opt_subcommand_fn *subcommand = NULL; + struct option subcommand_opts[] = { + OPT_SUBCOMMAND("list", &subcommand, cmd_config_list), + OPT_SUBCOMMAND("get", &subcommand, cmd_config_get), + OPT_SUBCOMMAND("set", &subcommand, cmd_config_set), + OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset), + OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section), + OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section), + OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit), + OPT_END(), + }; + + /* + * This is somewhat hacky: we first parse the command line while + * keeping all args intact in order to determine whether a subcommand + * has been specified. If so, we re-parse it a second time, but this + * time we drop KEEP_ARGV0. This is so that we don't munge the command + * line in case no subcommand was given, which would otherwise confuse + * us when parsing the legacy-style modes that don't use subcommands. + */ + argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage, + PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT); + if (subcommand) { + argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage, + PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT); + return subcommand(argc, argv, prefix); + } + + return cmd_config_actions(argc, argv, prefix); +} diff --git a/builtin/credential.c b/builtin/credential.c index 5100d441f2..b72e76dd9a 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -39,5 +39,7 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) } else { usage(usage_msg); } + + credential_clear(&c); return 0; } diff --git a/builtin/diff.c b/builtin/diff.c index efc37483b3..9b6cdabe15 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -465,6 +465,15 @@ int cmd_diff(int argc, const char **argv, const char *prefix) no_index = DIFF_NO_INDEX_IMPLICIT; } + /* + * When operating outside of a Git repository we need to have a hash + * algorithm at hand so that we can generate the blob hashes. We + * default to SHA1 here, but may eventually want to change this to be + * configurable via a command line option. + */ + if (nongit) + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); prefix = precompose_argv_prefix(argc, argv, prefix); diff --git a/builtin/difftool.c b/builtin/difftool.c index a130faae4f..a1794b7eed 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -674,19 +674,15 @@ finish: static int run_file_diff(int prompt, const char *prefix, struct child_process *child) { - const char *env[] = { - "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, - NULL - }; - + strvec_push(&child->env, "GIT_PAGER="); + strvec_push(&child->env, "GIT_EXTERNAL_DIFF=git-difftool--helper"); if (prompt > 0) - env[2] = "GIT_DIFFTOOL_PROMPT=true"; + strvec_push(&child->env, "GIT_DIFFTOOL_PROMPT=true"); else if (!prompt) - env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + strvec_push(&child->env, "GIT_DIFFTOOL_NO_PROMPT=true"); child->git_cmd = 1; child->dir = prefix; - strvec_pushv(&child->env, env); return run_command(child); } diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 82ca6d2bfd..c767414a0c 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -123,6 +123,9 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) else prefix = setup_git_directory_gently(&nongit); + if (nongit && !the_hash_algo) + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + if (vpath && prefix) { vpath_free = prefix_filename(prefix, vpath); vpath = vpath_free; diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 8768bfea3c..1d969494cf 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts, LIST_HEAD(head); struct strbuf sb = STRBUF_INIT; struct strbuf trailer_block = STRBUF_INIT; - struct trailer_info info; + struct trailer_info *info; FILE *outfile = stdout; trailer_config_init(); @@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts, if (opts->in_place) outfile = create_in_place_tempfile(file); - parse_trailers(opts, &info, sb.buf, &head); + info = parse_trailers(opts, sb.buf, &head); /* Print the lines before the trailers */ if (!opts->only_trailers) - fwrite(sb.buf, 1, info.trailer_block_start, outfile); + fwrite(sb.buf, 1, trailer_block_start(info), outfile); - if (!opts->only_trailers && !info.blank_line_before_trailer) + if (!opts->only_trailers && !blank_line_before_trailer_block(info)) fprintf(outfile, "\n"); @@ -178,8 +178,8 @@ static void interpret_trailers(const struct process_trailer_options *opts, /* Print the lines after the trailers as is */ if (!opts->only_trailers) - fwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile); - trailer_info_release(&info); + fwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile); + trailer_info_release(info); if (opts->in_place) if (rename_tempfile(&trailers_tempfile, file)) diff --git a/builtin/log.c b/builtin/log.c index b17dd8b40a..78a247d8a9 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -48,22 +48,8 @@ #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 #define FORMAT_PATCH_NAME_MAX_DEFAULT 64 -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode = NULL; - -static int default_abbrev_commit; -static int default_show_root = 1; -static int default_follow; -static int default_show_signature; -static int default_encode_email_headers = 1; -static int decoration_style; -static int decoration_given; -static int use_mailmap_config = 1; static unsigned int force_in_body_from; static int stdout_mboxrd; -static const char *fmt_patch_subject_prefix = "PATCH"; -static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; -static const char *fmt_pretty; static int format_no_prefix; static const char * const builtin_log_usage[] = { @@ -111,6 +97,39 @@ static int parse_decoration_style(const char *value) return -1; } +struct log_config { + int default_abbrev_commit; + int default_show_root; + int default_follow; + int default_show_signature; + int default_encode_email_headers; + int decoration_style; + int decoration_given; + int use_mailmap_config; + char *fmt_patch_subject_prefix; + int fmt_patch_name_max; + char *fmt_pretty; + char *default_date_mode; +}; + +static void log_config_init(struct log_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + cfg->default_show_root = 1; + cfg->default_encode_email_headers = 1; + cfg->use_mailmap_config = 1; + cfg->fmt_patch_subject_prefix = xstrdup("PATCH"); + cfg->fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; + cfg->decoration_style = auto_decoration_style(); +} + +static void log_config_release(struct log_config *cfg) +{ + free(cfg->default_date_mode); + free(cfg->fmt_pretty); + free(cfg->fmt_patch_subject_prefix); +} + static int use_default_decoration_filter = 1; static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; @@ -127,20 +146,22 @@ static int clear_decorations_callback(const struct option *opt UNUSED, return 0; } -static int decorate_callback(const struct option *opt UNUSED, const char *arg, +static int decorate_callback(const struct option *opt, const char *arg, int unset) { + struct log_config *cfg = opt->value; + if (unset) - decoration_style = 0; + cfg->decoration_style = 0; else if (arg) - decoration_style = parse_decoration_style(arg); + cfg->decoration_style = parse_decoration_style(arg); else - decoration_style = DECORATE_SHORT_REFS; + cfg->decoration_style = DECORATE_SHORT_REFS; - if (decoration_style < 0) + if (cfg->decoration_style < 0) die(_("invalid --decorate option: %s"), arg); - decoration_given = 1; + cfg->decoration_given = 1; return 0; } @@ -160,32 +181,26 @@ static int log_line_range_callback(const struct option *option, const char *arg, return 0; } -static void init_log_defaults(void) +static void cmd_log_init_defaults(struct rev_info *rev, + struct log_config *cfg) { - init_diff_ui_defaults(); - - decoration_style = auto_decoration_style(); -} - -static void cmd_log_init_defaults(struct rev_info *rev) -{ - if (fmt_pretty) - get_commit_format(fmt_pretty, rev); - if (default_follow) + if (cfg->fmt_pretty) + get_commit_format(cfg->fmt_pretty, rev); + if (cfg->default_follow) rev->diffopt.flags.default_follow_renames = 1; rev->verbose_header = 1; init_diffstat_widths(&rev->diffopt); rev->diffopt.flags.recursive = 1; rev->diffopt.flags.allow_textconv = 1; - rev->abbrev_commit = default_abbrev_commit; - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - rev->patch_name_max = fmt_patch_name_max; - rev->show_signature = default_show_signature; - rev->encode_email_headers = default_encode_email_headers; + rev->abbrev_commit = cfg->default_abbrev_commit; + rev->show_root_diff = cfg->default_show_root; + rev->subject_prefix = cfg->fmt_patch_subject_prefix; + rev->patch_name_max = cfg->fmt_patch_name_max; + rev->show_signature = cfg->default_show_signature; + rev->encode_email_headers = cfg->default_encode_email_headers; - if (default_date_mode) - parse_date_format(default_date_mode, &rev->date_mode); + if (cfg->default_date_mode) + parse_date_format(cfg->default_date_mode, &rev->date_mode); } static void set_default_decoration_filter(struct decoration_filter *decoration_filter) @@ -233,7 +248,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f } static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) + struct rev_info *rev, struct setup_revision_opt *opt, + struct log_config *cfg) { struct userformat_want w; int quiet = 0, source = 0, mailmap; @@ -258,7 +274,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, N_("pattern"), N_("only decorate refs that match <pattern>")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, N_("pattern"), N_("do not decorate refs that match <pattern>")), - OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), + OPT_CALLBACK_F(0, "decorate", cfg, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "range:file", N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"), @@ -269,7 +285,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, line_cb.rev = rev; line_cb.prefix = prefix; - mailmap = use_mailmap_config; + mailmap = cfg->use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | @@ -314,8 +330,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, * "log --pretty=raw" is special; ignore UI oriented * configuration variables such as decoration. */ - if (!decoration_given) - decoration_style = 0; + if (!cfg->decoration_given) + cfg->decoration_style = 0; if (!rev->abbrev_commit_given) rev->abbrev_commit = 0; } @@ -326,24 +342,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, * Disable decoration loading if the format will not * show them anyway. */ - decoration_style = 0; - } else if (!decoration_style) { + cfg->decoration_style = 0; + } else if (!cfg->decoration_style) { /* * If we are going to show them, make sure we do load * them here, but taking care not to override a * specific style set by config or --decorate. */ - decoration_style = DECORATE_SHORT_REFS; + cfg->decoration_style = DECORATE_SHORT_REFS; } } - if (decoration_style || rev->simplify_by_decoration) { + if (cfg->decoration_style || rev->simplify_by_decoration) { set_default_decoration_filter(&decoration_filter); - if (decoration_style) + if (cfg->decoration_style) rev->show_decorations = 1; - load_ref_decorations(&decoration_filter, decoration_style); + load_ref_decorations(&decoration_filter, cfg->decoration_style); } if (rev->line_level_traverse) @@ -353,16 +369,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } static void cmd_log_init(int argc, const char **argv, const char *prefix, - struct rev_info *rev, struct setup_revision_opt *opt) -{ - cmd_log_init_defaults(rev); - cmd_log_init_finish(argc, argv, prefix, rev, opt); -} - -static int cmd_log_deinit(int ret, struct rev_info *rev) + struct rev_info *rev, struct setup_revision_opt *opt, + struct log_config *cfg) { - release_revisions(rev); - return ret; + cmd_log_init_defaults(rev, cfg); + cmd_log_init_finish(argc, argv, prefix, rev, opt, cfg); } /* @@ -566,30 +577,37 @@ static int cmd_log_walk(struct rev_info *rev) static int git_log_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { + struct log_config *cfg = cb; const char *slot_name; - if (!strcmp(var, "format.pretty")) - return git_config_string(&fmt_pretty, var, value); - if (!strcmp(var, "format.subjectprefix")) - return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.pretty")) { + FREE_AND_NULL(cfg->fmt_pretty); + return git_config_string(&cfg->fmt_pretty, var, value); + } + if (!strcmp(var, "format.subjectprefix")) { + FREE_AND_NULL(cfg->fmt_patch_subject_prefix); + return git_config_string(&cfg->fmt_patch_subject_prefix, var, value); + } if (!strcmp(var, "format.filenamemaxlength")) { - fmt_patch_name_max = git_config_int(var, value, ctx->kvi); + cfg->fmt_patch_name_max = git_config_int(var, value, ctx->kvi); return 0; } if (!strcmp(var, "format.encodeemailheaders")) { - default_encode_email_headers = git_config_bool(var, value); + cfg->default_encode_email_headers = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.abbrevcommit")) { - default_abbrev_commit = git_config_bool(var, value); + cfg->default_abbrev_commit = git_config_bool(var, value); return 0; } - if (!strcmp(var, "log.date")) - return git_config_string(&default_date_mode, var, value); + if (!strcmp(var, "log.date")) { + FREE_AND_NULL(cfg->default_date_mode); + return git_config_string(&cfg->default_date_mode, var, value); + } if (!strcmp(var, "log.decorate")) { - decoration_style = parse_decoration_style(value); - if (decoration_style < 0) - decoration_style = 0; /* maybe warn? */ + cfg->decoration_style = parse_decoration_style(value); + if (cfg->decoration_style < 0) + cfg->decoration_style = 0; /* maybe warn? */ return 0; } if (!strcmp(var, "log.diffmerges")) { @@ -598,21 +616,21 @@ static int git_log_config(const char *var, const char *value, return diff_merges_config(value); } if (!strcmp(var, "log.showroot")) { - default_show_root = git_config_bool(var, value); + cfg->default_show_root = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.follow")) { - default_follow = git_config_bool(var, value); + cfg->default_follow = git_config_bool(var, value); return 0; } if (skip_prefix(var, "color.decorate.", &slot_name)) return parse_decorate_color_config(var, slot_name, value); if (!strcmp(var, "log.mailmap")) { - use_mailmap_config = git_config_bool(var, value); + cfg->use_mailmap_config = git_config_bool(var, value); return 0; } if (!strcmp(var, "log.showsignature")) { - default_show_signature = git_config_bool(var, value); + cfg->default_show_signature = git_config_bool(var, value); return 0; } @@ -621,11 +639,14 @@ static int git_log_config(const char *var, const char *value, int cmd_whatchanged(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); @@ -635,10 +656,15 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; - cmd_log_init(argc, argv, prefix, &rev, &opt); + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + + ret = cmd_log_walk(&rev); + + release_revisions(&rev); + log_config_release(&cfg); + return ret; } static void show_tagger(const char *buf, struct rev_info *rev) @@ -733,14 +759,16 @@ static void show_setup_revisions_tweak(struct rev_info *rev) int cmd_show(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; unsigned int i; struct setup_revision_opt opt; struct pathspec match_all; int ret = 0; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); if (the_repository->gitdir) { prepare_repo_settings(the_repository); @@ -759,10 +787,14 @@ int cmd_show(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.tweak = show_setup_revisions_tweak; - cmd_log_init(argc, argv, prefix, &rev, &opt); + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); - if (!rev.no_walk) - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + if (!rev.no_walk) { + ret = cmd_log_walk(&rev); + release_revisions(&rev); + log_config_release(&cfg); + return ret; + } rev.diffopt.no_free = 1; for (i = 0; i < rev.pending.nr && !ret; i++) { @@ -832,8 +864,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.diffopt.no_free = 0; diff_free(&rev.diffopt); + release_revisions(&rev); + log_config_release(&cfg); - return cmd_log_deinit(ret, &rev); + return ret; } /* @@ -841,11 +875,14 @@ int cmd_show(int argc, const char **argv, const char *prefix) */ int cmd_log_reflog(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); init_reflog_walk(&rev.reflog_info); @@ -854,14 +891,18 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; - cmd_log_init_defaults(&rev); + cmd_log_init_defaults(&rev, &cfg); rev.abbrev_commit = 1; rev.commit_format = CMIT_FMT_ONELINE; rev.use_terminator = 1; rev.always_show_header = 1; - cmd_log_init_finish(argc, argv, prefix, &rev, &opt); + cmd_log_init_finish(argc, argv, prefix, &rev, &opt, &cfg); - return cmd_log_deinit(cmd_log_walk(&rev), &rev); + ret = cmd_log_walk(&rev); + + release_revisions(&rev); + log_config_release(&cfg); + return ret; } static void log_setup_revisions_tweak(struct rev_info *rev) @@ -876,11 +917,14 @@ static void log_setup_revisions_tweak(struct rev_info *rev) int cmd_log(int argc, const char **argv, const char *prefix) { + struct log_config cfg; struct rev_info rev; struct setup_revision_opt opt; + int ret; - init_log_defaults(); - git_config(git_log_config, NULL); + log_config_init(&cfg); + init_diff_ui_defaults(); + git_config(git_log_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); @@ -890,42 +934,17 @@ int cmd_log(int argc, const char **argv, const char *prefix) opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; opt.tweak = log_setup_revisions_tweak; - cmd_log_init(argc, argv, prefix, &rev, &opt); - return cmd_log_deinit(cmd_log_walk(&rev), &rev); -} - -/* format-patch */ - -static const char *fmt_patch_suffix = ".patch"; -static int numbered = 0; -static int auto_number = 1; - -static char *default_attach = NULL; - -static struct string_list extra_hdr = STRING_LIST_INIT_NODUP; -static struct string_list extra_to = STRING_LIST_INIT_NODUP; -static struct string_list extra_cc = STRING_LIST_INIT_NODUP; + cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); -static void add_header(const char *value) -{ - struct string_list_item *item; - int len = strlen(value); - while (len && value[len - 1] == '\n') - len--; - - if (!strncasecmp(value, "to: ", 4)) { - item = string_list_append(&extra_to, value + 4); - len -= 4; - } else if (!strncasecmp(value, "cc: ", 4)) { - item = string_list_append(&extra_cc, value + 4); - len -= 4; - } else { - item = string_list_append(&extra_hdr, value); - } + ret = cmd_log_walk(&rev); - item->string[len] = '\0'; + release_revisions(&rev); + log_config_release(&cfg); + return ret; } +/* format-patch */ + enum cover_setting { COVER_UNSET, COVER_OFF, @@ -952,17 +971,61 @@ enum auto_base_setting { AUTO_BASE_WHEN_ABLE }; -static enum thread_level thread; -static int do_signoff; -static enum auto_base_setting auto_base; -static char *from; -static const char *signature = git_version_string; -static const char *signature_file; -static enum cover_setting config_cover_letter; -static const char *config_output_directory; -static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; -static int show_notes; -static struct display_notes_opt notes_opt; +struct format_config { + struct log_config log; + enum thread_level thread; + int do_signoff; + enum auto_base_setting auto_base; + char *base_commit; + char *from; + char *signature; + char *signature_file; + enum cover_setting config_cover_letter; + char *config_output_directory; + enum cover_from_description cover_from_description_mode; + int show_notes; + struct display_notes_opt notes_opt; + int numbered_cmdline_opt; + int numbered; + int auto_number; + char *default_attach; + struct string_list extra_hdr; + struct string_list extra_to; + struct string_list extra_cc; + int keep_subject; + int subject_prefix; + struct strbuf sprefix; + char *fmt_patch_suffix; +}; + +static void format_config_init(struct format_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + log_config_init(&cfg->log); + cfg->cover_from_description_mode = COVER_FROM_MESSAGE; + cfg->auto_number = 1; + string_list_init_dup(&cfg->extra_hdr); + string_list_init_dup(&cfg->extra_to); + string_list_init_dup(&cfg->extra_cc); + strbuf_init(&cfg->sprefix, 0); + cfg->fmt_patch_suffix = xstrdup(".patch"); +} + +static void format_config_release(struct format_config *cfg) +{ + log_config_release(&cfg->log); + free(cfg->base_commit); + free(cfg->from); + free(cfg->signature); + free(cfg->signature_file); + free(cfg->config_output_directory); + free(cfg->default_attach); + string_list_clear(&cfg->extra_hdr, 0); + string_list_clear(&cfg->extra_to, 0); + string_list_clear(&cfg->extra_cc, 0); + strbuf_release(&cfg->sprefix); + free(cfg->fmt_patch_suffix); +} static enum cover_from_description parse_cover_from_description(const char *arg) { @@ -980,27 +1043,51 @@ static enum cover_from_description parse_cover_from_description(const char *arg) die(_("%s: invalid cover from description mode"), arg); } +static void add_header(struct format_config *cfg, const char *value) +{ + struct string_list_item *item; + int len = strlen(value); + while (len && value[len - 1] == '\n') + len--; + + if (!strncasecmp(value, "to: ", 4)) { + item = string_list_append(&cfg->extra_to, value + 4); + len -= 4; + } else if (!strncasecmp(value, "cc: ", 4)) { + item = string_list_append(&cfg->extra_cc, value + 4); + len -= 4; + } else { + item = string_list_append(&cfg->extra_hdr, value); + } + + item->string[len] = '\0'; +} + static int git_format_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { + struct format_config *cfg = cb; + if (!strcmp(var, "format.headers")) { if (!value) die(_("format.headers without value")); - add_header(value); + add_header(cfg, value); return 0; } - if (!strcmp(var, "format.suffix")) - return git_config_string(&fmt_patch_suffix, var, value); + if (!strcmp(var, "format.suffix")) { + FREE_AND_NULL(cfg->fmt_patch_suffix); + return git_config_string(&cfg->fmt_patch_suffix, var, value); + } if (!strcmp(var, "format.to")) { if (!value) return config_error_nonbool(var); - string_list_append(&extra_to, value); + string_list_append(&cfg->extra_to, value); return 0; } if (!strcmp(var, "format.cc")) { if (!value) return config_error_nonbool(var); - string_list_append(&extra_cc, value); + string_list_append(&cfg->extra_cc, value); return 0; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") || @@ -1009,69 +1096,76 @@ static int git_format_config(const char *var, const char *value, } if (!strcmp(var, "format.numbered")) { if (value && !strcasecmp(value, "auto")) { - auto_number = 1; + cfg->auto_number = 1; return 0; } - numbered = git_config_bool(var, value); - auto_number = auto_number && numbered; + cfg->numbered = git_config_bool(var, value); + cfg->auto_number = cfg->auto_number && cfg->numbered; return 0; } if (!strcmp(var, "format.attach")) { - if (value && *value) - default_attach = xstrdup(value); - else if (value && !*value) - FREE_AND_NULL(default_attach); - else - default_attach = xstrdup(git_version_string); + if (value && *value) { + FREE_AND_NULL(cfg->default_attach); + cfg->default_attach = xstrdup(value); + } else if (value && !*value) { + FREE_AND_NULL(cfg->default_attach); + } else { + FREE_AND_NULL(cfg->default_attach); + cfg->default_attach = xstrdup(git_version_string); + } return 0; } if (!strcmp(var, "format.thread")) { if (value && !strcasecmp(value, "deep")) { - thread = THREAD_DEEP; + cfg->thread = THREAD_DEEP; return 0; } if (value && !strcasecmp(value, "shallow")) { - thread = THREAD_SHALLOW; + cfg->thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; + cfg->thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { - do_signoff = git_config_bool(var, value); + cfg->do_signoff = git_config_bool(var, value); return 0; } - if (!strcmp(var, "format.signature")) - return git_config_string(&signature, var, value); - if (!strcmp(var, "format.signaturefile")) - return git_config_pathname(&signature_file, var, value); + if (!strcmp(var, "format.signature")) { + FREE_AND_NULL(cfg->signature); + return git_config_string(&cfg->signature, var, value); + } + if (!strcmp(var, "format.signaturefile")) { + FREE_AND_NULL(cfg->signature_file); + return git_config_pathname(&cfg->signature_file, var, value); + } if (!strcmp(var, "format.coverletter")) { if (value && !strcasecmp(value, "auto")) { - config_cover_letter = COVER_AUTO; + cfg->config_cover_letter = COVER_AUTO; return 0; } - config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; + cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; return 0; } - if (!strcmp(var, "format.outputdirectory")) - return git_config_string(&config_output_directory, var, value); + if (!strcmp(var, "format.outputdirectory")) { + FREE_AND_NULL(cfg->config_output_directory); + return git_config_string(&cfg->config_output_directory, var, value); + } if (!strcmp(var, "format.useautobase")) { if (value && !strcasecmp(value, "whenAble")) { - auto_base = AUTO_BASE_WHEN_ABLE; + cfg->auto_base = AUTO_BASE_WHEN_ABLE; return 0; } - auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; + cfg->auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; return 0; } if (!strcmp(var, "format.from")) { int b = git_parse_maybe_bool(value); - free(from); + FREE_AND_NULL(cfg->from); if (b < 0) - from = xstrdup(value); + cfg->from = xstrdup(value); else if (b) - from = xstrdup(git_committer_info(IDENT_NO_DATE)); - else - from = NULL; + cfg->from = xstrdup(git_committer_info(IDENT_NO_DATE)); return 0; } if (!strcmp(var, "format.forceinbodyfrom")) { @@ -1081,15 +1175,15 @@ static int git_format_config(const char *var, const char *value, if (!strcmp(var, "format.notes")) { int b = git_parse_maybe_bool(value); if (b < 0) - enable_ref_display_notes(¬es_opt, &show_notes, value); + enable_ref_display_notes(&cfg->notes_opt, &cfg->show_notes, value); else if (b) - enable_default_display_notes(¬es_opt, &show_notes); + enable_default_display_notes(&cfg->notes_opt, &cfg->show_notes); else - disable_display_notes(¬es_opt, &show_notes); + disable_display_notes(&cfg->notes_opt, &cfg->show_notes); return 0; } if (!strcmp(var, "format.coverfromdescription")) { - cover_from_description_mode = parse_cover_from_description(value); + cfg->cover_from_description_mode = parse_cover_from_description(value); return 0; } if (!strcmp(var, "format.mboxrd")) { @@ -1110,7 +1204,7 @@ static int git_format_config(const char *var, const char *value, if (!strcmp(var, "diff.noprefix")) return 0; - return git_log_config(var, value, ctx, cb); + return git_log_config(var, value, ctx, &cfg->log); } static const char *output_directory = NULL; @@ -1198,7 +1292,7 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id = strbuf_detach(&buf, NULL); } -static void print_signature(FILE *file) +static void print_signature(const char *signature, FILE *file) { if (!signature || !*signature) return; @@ -1268,14 +1362,15 @@ static void prepare_cover_text(struct pretty_print_context *pp, const char *branch_name, struct strbuf *sb, const char *encoding, - int need_8bit_cte) + int need_8bit_cte, + const struct format_config *cfg) { const char *subject = "*** SUBJECT HERE ***"; const char *body = "*** BLURB HERE ***"; struct strbuf description_sb = STRBUF_INIT; struct strbuf subject_sb = STRBUF_INIT; - if (cover_from_description_mode == COVER_FROM_NONE) + if (cfg->cover_from_description_mode == COVER_FROM_NONE) goto do_pp; if (description_file && *description_file) @@ -1285,13 +1380,13 @@ static void prepare_cover_text(struct pretty_print_context *pp, if (!description_sb.len) goto do_pp; - if (cover_from_description_mode == COVER_FROM_SUBJECT || - cover_from_description_mode == COVER_FROM_AUTO) + if (cfg->cover_from_description_mode == COVER_FROM_SUBJECT || + cfg->cover_from_description_mode == COVER_FROM_AUTO) body = format_subject(&subject_sb, description_sb.buf, " "); - if (cover_from_description_mode == COVER_FROM_MESSAGE || - (cover_from_description_mode == COVER_FROM_AUTO && - subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + if (cfg->cover_from_description_mode == COVER_FROM_MESSAGE || + (cfg->cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) body = description_sb.buf; else subject = subject_sb.buf; @@ -1328,7 +1423,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, int nr, struct commit **list, const char *description_file, const char *branch_name, - int quiet) + int quiet, + const struct format_config *cfg) { const char *committer; struct shortlog log; @@ -1367,7 +1463,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.encode_email_headers = rev->encode_email_headers; pp_user_info(&pp, NULL, &sb, committer, encoding); prepare_cover_text(&pp, description_file, branch_name, &sb, - encoding, need_8bit_cte); + encoding, need_8bit_cte, cfg); fprintf(rev->diffopt.file, "%s\n", sb.buf); free(pp.after_subject); @@ -1468,29 +1564,30 @@ static const char * const builtin_format_patch_usage[] = { NULL }; -static int keep_subject = 0; +struct keep_callback_data { + struct format_config *cfg; + struct rev_info *revs; +}; static int keep_callback(const struct option *opt, const char *arg, int unset) { + struct keep_callback_data *data = opt->value; BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - ((struct rev_info *)opt->value)->total = -1; - keep_subject = 1; + data->revs->total = -1; + data->cfg->keep_subject = 1; return 0; } -static int subject_prefix = 0; - static int subject_prefix_callback(const struct option *opt, const char *arg, int unset) { - struct strbuf *sprefix; + struct format_config *cfg = opt->value; BUG_ON_OPT_NEG(unset); - sprefix = opt->value; - subject_prefix = 1; - strbuf_reset(sprefix); - strbuf_addstr(sprefix, arg); + cfg->subject_prefix = 1; + strbuf_reset(&cfg->sprefix); + strbuf_addstr(&cfg->sprefix, arg); return 0; } @@ -1507,15 +1604,14 @@ static int rfc_callback(const struct option *opt, const char *arg, return 0; } -static int numbered_cmdline_opt = 0; - static int numbered_callback(const struct option *opt, const char *arg, int unset) { + struct format_config *cfg = opt->value; BUG_ON_OPT_ARG(arg); - *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; + cfg->numbered = cfg->numbered_cmdline_opt = unset ? 0 : 1; if (unset) - auto_number = 0; + cfg->auto_number = 0; return 0; } @@ -1539,13 +1635,14 @@ static int output_directory_callback(const struct option *opt, const char *arg, static int thread_callback(const struct option *opt, const char *arg, int unset) { - enum thread_level *thread = (enum thread_level *)opt->value; + struct format_config *cfg = opt->value; + if (unset) - *thread = THREAD_UNSET; + cfg->thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) - *thread = THREAD_SHALLOW; + cfg->thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) - *thread = THREAD_DEEP; + cfg->thread = THREAD_DEEP; /* * Please update _git_formatpatch() in git-completion.bash * when you add new options. @@ -1581,15 +1678,17 @@ static int inline_callback(const struct option *opt, const char *arg, int unset) return 0; } -static int header_callback(const struct option *opt UNUSED, const char *arg, +static int header_callback(const struct option *opt, const char *arg, int unset) { + struct format_config *cfg = opt->value; + if (unset) { - string_list_clear(&extra_hdr, 0); - string_list_clear(&extra_to, 0); - string_list_clear(&extra_cc, 0); + string_list_clear(&cfg->extra_hdr, 0); + string_list_clear(&cfg->extra_to, 0); + string_list_clear(&cfg->extra_cc, 0); } else { - add_header(arg); + add_header(cfg, arg); } return 0; } @@ -1611,17 +1710,17 @@ static int from_callback(const struct option *opt, const char *arg, int unset) static int base_callback(const struct option *opt, const char *arg, int unset) { - const char **base_commit = opt->value; + struct format_config *cfg = opt->value; if (unset) { - auto_base = AUTO_BASE_NEVER; - *base_commit = NULL; + cfg->auto_base = AUTO_BASE_NEVER; + FREE_AND_NULL(cfg->base_commit); } else if (!strcmp(arg, "auto")) { - auto_base = AUTO_BASE_ALWAYS; - *base_commit = NULL; + cfg->auto_base = AUTO_BASE_ALWAYS; + FREE_AND_NULL(cfg->base_commit); } else { - auto_base = AUTO_BASE_NEVER; - *base_commit = arg; + cfg->auto_base = AUTO_BASE_NEVER; + cfg->base_commit = xstrdup(arg); } return 0; } @@ -1632,7 +1731,7 @@ struct base_tree_info { struct object_id *patch_id; }; -static struct commit *get_base_commit(const char *base_commit, +static struct commit *get_base_commit(const struct format_config *cfg, struct commit **list, int total) { @@ -1640,9 +1739,9 @@ static struct commit *get_base_commit(const char *base_commit, struct commit **rev; int i = 0, rev_nr = 0, auto_select, die_on_failure, ret; - switch (auto_base) { + switch (cfg->auto_base) { case AUTO_BASE_NEVER: - if (base_commit) { + if (cfg->base_commit) { auto_select = 0; die_on_failure = 1; } else { @@ -1652,11 +1751,11 @@ static struct commit *get_base_commit(const char *base_commit, break; case AUTO_BASE_ALWAYS: case AUTO_BASE_WHEN_ABLE: - if (base_commit) { + if (cfg->base_commit) { BUG("requested automatic base selection but a commit was provided"); } else { auto_select = 1; - die_on_failure = auto_base == AUTO_BASE_ALWAYS; + die_on_failure = cfg->auto_base == AUTO_BASE_ALWAYS; } break; default: @@ -1664,9 +1763,9 @@ static struct commit *get_base_commit(const char *base_commit, } if (!auto_select) { - base = lookup_commit_reference_by_name(base_commit); + base = lookup_commit_reference_by_name(cfg->base_commit); if (!base) - die(_("unknown commit %s"), base_commit); + die(_("unknown commit %s"), cfg->base_commit); } else { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); @@ -1884,6 +1983,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, int cmd_format_patch(int argc, const char **argv, const char *prefix) { + struct format_config cfg; struct commit *commit; struct commit **list = NULL; struct rev_info rev; @@ -1908,7 +2008,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *cover_from_description_arg = NULL; char *description_file = NULL; char *branch_name = NULL; - char *base_commit = NULL; struct base_tree_info bases; struct commit *base; int show_progress = 0; @@ -1919,18 +2018,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf rdiff1 = STRBUF_INIT; struct strbuf rdiff2 = STRBUF_INIT; struct strbuf rdiff_title = STRBUF_INIT; - struct strbuf sprefix = STRBUF_INIT; const char *rfc = NULL; int creation_factor = -1; + const char *signature = git_version_string; + const char *signature_file_arg = NULL; + struct keep_callback_data keep_callback_data = { + .cfg = &cfg, + .revs = &rev, + }; + const char *fmt_patch_suffix = NULL; const struct option builtin_format_patch_options[] = { - OPT_CALLBACK_F('n', "numbered", &numbered, NULL, + OPT_CALLBACK_F('n', "numbered", &cfg, NULL, N_("use [PATCH n/m] even with a single patch"), PARSE_OPT_NOARG, numbered_callback), - OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL, + OPT_CALLBACK_F('N', "no-numbered", &cfg, NULL, N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), - OPT_BOOL('s', "signoff", &do_signoff, N_("add a Signed-off-by trailer")), + OPT_BOOL('s', "signoff", &cfg.do_signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, @@ -1943,7 +2048,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"), N_("mark the series as Nth re-roll")), - OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, + OPT_INTEGER(0, "filename-max-length", &cfg.log.fmt_patch_name_max, N_("max length of output filename")), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add <rfc> (default 'RFC') before 'PATCH'"), @@ -1953,13 +2058,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("generate parts of a cover letter based on a branch's description")), OPT_FILENAME(0, "description-file", &description_file, N_("use branch description from file")), - OPT_CALLBACK_F(0, "subject-prefix", &sprefix, N_("prefix"), + OPT_CALLBACK_F(0, "subject-prefix", &cfg, N_("prefix"), N_("use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback), OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in <dir>"), PARSE_OPT_NONEG, output_directory_callback), - OPT_CALLBACK_F('k', "keep-subject", &rev, NULL, + OPT_CALLBACK_F('k', "keep-subject", &keep_callback_data, NULL, N_("don't strip/add [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff, @@ -1972,11 +2077,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show patch format instead of default (patch + stat)"), 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Messaging")), - OPT_CALLBACK(0, "add-header", NULL, N_("header"), + OPT_CALLBACK(0, "add-header", &cfg, N_("header"), N_("add email header"), header_callback), - OPT_STRING_LIST(0, "to", &extra_to, N_("email"), N_("add To: header")), - OPT_STRING_LIST(0, "cc", &extra_cc, N_("email"), N_("add Cc: header")), - OPT_CALLBACK_F(0, "from", &from, N_("ident"), + OPT_STRING_LIST(0, "to", &cfg.extra_to, N_("email"), N_("add To: header")), + OPT_STRING_LIST(0, "cc", &cfg.extra_cc, N_("email"), N_("add Cc: header")), + OPT_CALLBACK_F(0, "from", &cfg.from, N_("ident"), N_("set From address to <ident> (or committer ident if absent)"), PARSE_OPT_OPTARG, from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), @@ -1988,15 +2093,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, inline_callback), - OPT_CALLBACK_F(0, "thread", &thread, N_("style"), + OPT_CALLBACK_F(0, "thread", &cfg, N_("style"), N_("enable message threading, styles: shallow, deep"), PARSE_OPT_OPTARG, thread_callback), OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), - OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"), + OPT_CALLBACK_F(0, "base", &cfg, N_("base-commit"), N_("add prerequisite tree info to the patch series"), 0, base_callback), - OPT_FILENAME(0, "signature-file", &signature_file, + OPT_FILENAME(0, "signature-file", &signature_file_arg, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_BOOL(0, "progress", &show_progress, @@ -2013,20 +2118,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_END() }; - extra_hdr.strdup_strings = 1; - extra_to.strdup_strings = 1; - extra_cc.strdup_strings = 1; - - init_log_defaults(); - init_display_notes(¬es_opt); - git_config(git_format_config, NULL); + format_config_init(&cfg); + init_diff_ui_defaults(); + init_display_notes(&cfg.notes_opt); + git_config(git_format_config, &cfg); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); - rev.show_notes = show_notes; - memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); + rev.show_notes = cfg.show_notes; + memcpy(&rev.notes_opt, &cfg.notes_opt, sizeof(cfg.notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; - rev.encode_email_headers = default_encode_email_headers; + rev.encode_email_headers = cfg.log.default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; @@ -2037,12 +2139,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; - strbuf_addstr(&sprefix, fmt_patch_subject_prefix); + strbuf_addstr(&cfg.sprefix, cfg.log.fmt_patch_subject_prefix); if (format_no_prefix) diff_set_noprefix(&rev.diffopt); - if (default_attach) { - rev.mime_boundary = default_attach; + if (cfg.default_attach) { + rev.mime_boundary = cfg.default_attach; rev.no_inline = 1; } @@ -2058,60 +2160,63 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from; + if (!fmt_patch_suffix) + fmt_patch_suffix = cfg.fmt_patch_suffix; + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ - if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) - fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); + if (cfg.log.fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) + cfg.log.fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); if (cover_from_description_arg) - cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + cfg.cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); if (rfc && rfc[0]) { - subject_prefix = 1; + cfg.subject_prefix = 1; if (rfc[0] == '-') - strbuf_addf(&sprefix, " %s", rfc + 1); + strbuf_addf(&cfg.sprefix, " %s", rfc + 1); else - strbuf_insertf(&sprefix, 0, "%s ", rfc); + strbuf_insertf(&cfg.sprefix, 0, "%s ", rfc); } if (reroll_count) { - strbuf_addf(&sprefix, " v%s", reroll_count); + strbuf_addf(&cfg.sprefix, " v%s", reroll_count); rev.reroll_count = reroll_count; } - rev.subject_prefix = sprefix.buf; + rev.subject_prefix = cfg.sprefix.buf; - for (i = 0; i < extra_hdr.nr; i++) { - strbuf_addstr(&buf, extra_hdr.items[i].string); + for (i = 0; i < cfg.extra_hdr.nr; i++) { + strbuf_addstr(&buf, cfg.extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); } - if (extra_to.nr) + if (cfg.extra_to.nr) strbuf_addstr(&buf, "To: "); - for (i = 0; i < extra_to.nr; i++) { + for (i = 0; i < cfg.extra_to.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_to.items[i].string); - if (i + 1 < extra_to.nr) + strbuf_addstr(&buf, cfg.extra_to.items[i].string); + if (i + 1 < cfg.extra_to.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } - if (extra_cc.nr) + if (cfg.extra_cc.nr) strbuf_addstr(&buf, "Cc: "); - for (i = 0; i < extra_cc.nr; i++) { + for (i = 0; i < cfg.extra_cc.nr; i++) { if (i) strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_cc.items[i].string); - if (i + 1 < extra_cc.nr) + strbuf_addstr(&buf, cfg.extra_cc.items[i].string); + if (i + 1 < cfg.extra_cc.nr) strbuf_addch(&buf, ','); strbuf_addch(&buf, '\n'); } rev.extra_headers = to_free = strbuf_detach(&buf, NULL); - if (from) { - if (split_ident_line(&rev.from_ident, from, strlen(from))) - die(_("invalid ident line: %s"), from); + if (cfg.from) { + if (split_ident_line(&rev.from_ident, cfg.from, strlen(cfg.from))) + die(_("invalid ident line: %s"), cfg.from); } if (start_number < 0) @@ -2122,14 +2227,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * and it would conflict with --keep-subject (-k) from the * command line, reset "numbered". */ - if (numbered && keep_subject && !numbered_cmdline_opt) - numbered = 0; + if (cfg.numbered && cfg.keep_subject && !cfg.numbered_cmdline_opt) + cfg.numbered = 0; - if (numbered && keep_subject) + if (cfg.numbered && cfg.keep_subject) die(_("options '%s' and '%s' cannot be used together"), "-n", "-k"); - if (keep_subject && subject_prefix) + if (cfg.keep_subject && cfg.subject_prefix) die(_("options '%s' and '%s' cannot be used together"), "--subject-prefix/--rfc", "-k"); - rev.preserve_subject = keep_subject; + rev.preserve_subject = cfg.keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) @@ -2156,7 +2261,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; rev.zero_commit = zero_commit; - rev.patch_name_max = fmt_patch_name_max; + rev.patch_name_max = cfg.log.fmt_patch_name_max; if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary = 1; @@ -2177,7 +2282,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int saved; if (!output_directory) - output_directory = config_output_directory; + output_directory = cfg.config_output_directory; output_directory = set_outdir(prefix, output_directory); if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) @@ -2275,14 +2380,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) goto done; total = nr; if (cover_letter == -1) { - if (config_cover_letter == COVER_AUTO) + if (cfg.config_cover_letter == COVER_AUTO) cover_letter = (total > 1); else - cover_letter = (config_cover_letter == COVER_ON); + cover_letter = (cfg.config_cover_letter == COVER_ON); } - if (!keep_subject && auto_number && (total > 1 || cover_letter)) - numbered = 1; - if (numbered) + if (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter)) + cfg.numbered = 1; + if (cfg.numbered) rev.total = total + start_number - 1; if (idiff_prev.nr) { @@ -2296,7 +2401,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (creation_factor < 0) - creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; + creation_factor = CREATION_FACTOR_FOR_THE_SAME_SERIES; else if (!rdiff_prev) die(_("the option '%s' requires '%s'"), "--creation-factor", "--range-diff"); @@ -2314,27 +2419,40 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) _("Range-diff against v%d:")); } + /* + * The order of precedence is: + * + * 1. The `--signature` and `--no-signature` options. + * 2. The `--signature-file` option. + * 3. The `format.signature` config. + * 4. The `format.signatureFile` config. + * 5. Default `git_version_string`. + */ if (!signature) { ; /* --no-signature inhibits all signatures */ } else if (signature && signature != git_version_string) { ; /* non-default signature already set */ - } else if (signature_file) { + } else if (signature_file_arg || (cfg.signature_file && !cfg.signature)) { struct strbuf buf = STRBUF_INIT; + const char *signature_file = signature_file_arg ? + signature_file_arg : cfg.signature_file; if (strbuf_read_file(&buf, signature_file, 128) < 0) die_errno(_("unable to read signature file '%s'"), signature_file); signature = strbuf_detach(&buf, NULL); + } else if (cfg.signature) { + signature = cfg.signature; } memset(&bases, 0, sizeof(bases)); - base = get_base_commit(base_commit, list, nr); + base = get_base_commit(&cfg, list, nr); if (base) { reset_revision_walk(); clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); } - if (in_reply_to || thread || cover_letter) { + if (in_reply_to || cfg.thread || cover_letter) { rev.ref_message_ids = xmalloc(sizeof(*rev.ref_message_ids)); string_list_init_dup(rev.ref_message_ids); } @@ -2345,19 +2463,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { - if (thread) + if (cfg.thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, !!output_directory, - origin, nr, list, description_file, branch_name, quiet); + origin, nr, list, description_file, branch_name, quiet, &cfg); print_bases(&bases, rev.diffopt.file); - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); total++; start_number--; /* interdiff/range-diff in cover-letter; omit from patches */ rev.idiff_oid1 = NULL; rev.rdiff1 = NULL; } - rev.add_signoff = do_signoff; + rev.add_signoff = cfg.do_signoff; if (show_progress) progress = start_delayed_progress(_("Generating patches"), total); @@ -2367,7 +2485,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) commit = list[nr]; rev.nr = total - nr + (start_number - 1); /* Make the second and subsequent mails replies to the first */ - if (thread) { + if (cfg.thread) { /* Have we already had a message ID? */ if (rev.message_id) { /* @@ -2391,7 +2509,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * letter is a reply to the * --in-reply-to, if specified. */ - if (thread == THREAD_SHALLOW + if (cfg.thread == THREAD_SHALLOW && rev.ref_message_ids->nr > 0 && (!cover_letter || rev.nr > 1)) free(rev.message_id); @@ -2424,7 +2542,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) mime_boundary_leader, rev.mime_boundary); else - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); } if (output_directory) fclose(rev.diffopt.file); @@ -2432,9 +2550,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) stop_progress(&progress); free(list); free(branch_name); - string_list_clear(&extra_to, 0); - string_list_clear(&extra_cc, 0); - string_list_clear(&extra_hdr, 0); if (ignore_if_in_upstream) free_patch_ids(&ids); @@ -2444,13 +2559,14 @@ done: strbuf_release(&rdiff1); strbuf_release(&rdiff2); strbuf_release(&rdiff_title); - strbuf_release(&sprefix); free(to_free); free(rev.message_id); if (rev.ref_message_ids) string_list_clear(rev.ref_message_ids, 0); free(rev.ref_message_ids); - return cmd_log_deinit(0, &rev); + release_revisions(&rev); + format_config_release(&cfg); + return 0; } static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) diff --git a/builtin/merge.c b/builtin/merge.c index e4bd65eeba..daed2d4e1e 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -100,7 +100,7 @@ static struct strategy all_strategy[] = { { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, }; -static const char *pull_twohead, *pull_octopus; +static char *pull_twohead, *pull_octopus; enum ff_type { FF_NO, @@ -110,7 +110,7 @@ enum ff_type { static enum ff_type fast_forward = FF_ALLOW; -static const char *cleanup_arg; +static char *cleanup_arg; static enum commit_msg_cleanup_mode cleanup_mode; static int option_parse_message(const struct option *opt, diff --git a/builtin/mv.c b/builtin/mv.c index 74aa9746aa..6c69033c5f 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -20,6 +20,7 @@ #include "read-cache-ll.h" #include "repository.h" #include "setup.h" +#include "strvec.h" #include "submodule.h" #include "entry.h" @@ -38,45 +39,35 @@ enum update_mode { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_prefix_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static void internal_prefix_pathspec(struct strvec *out, + const char *prefix, + const char **pathspec, + int count, unsigned flags) { - int i; - const char **result; int prefixlen = prefix ? strlen(prefix) : 0; - ALLOC_ARRAY(result, count + 1); /* Create an intermediate copy of the pathspec based on the flags */ - for (i = 0; i < count; i++) { - int length = strlen(pathspec[i]); - int to_copy = length; - char *it; + for (int i = 0; i < count; i++) { + size_t length = strlen(pathspec[i]); + size_t to_copy = length; + const char *maybe_basename; + char *trimmed, *prefixed_path; + while (!(flags & KEEP_TRAILING_SLASH) && to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - it = xmemdupz(pathspec[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else { - result[i] = it; - } - } - result[count] = NULL; + trimmed = xmemdupz(pathspec[i], to_copy); + maybe_basename = (flags & DUP_BASENAME) ? basename(trimmed) : trimmed; + prefixed_path = prefix_path(prefix, prefixlen, maybe_basename); + strvec_push(out, prefixed_path); - /* Prefix the pathspec and free the old intermediate strings */ - for (i = 0; i < count; i++) { - const char *match = prefix_path(prefix, prefixlen, result[i]); - free((char *) result[i]); - result[i] = match; + free(prefixed_path); + free(trimmed); } - - return result; } -static const char *add_slash(const char *path) +static char *add_slash(const char *path) { size_t len = strlen(path); if (len && path[len - 1] != '/') { @@ -86,32 +77,34 @@ static const char *add_slash(const char *path) with_slash[len] = 0; return with_slash; } - return path; + return xstrdup(path); } #define SUBMODULE_WITH_GITDIR ((const char *)1) -static void prepare_move_submodule(const char *src, int first, - const char **submodule_gitfile) +static const char *submodule_gitfile_path(const char *src, int first) { struct strbuf submodule_dotgit = STRBUF_INIT; + const char *path; + if (!S_ISGITLINK(the_repository->index->cache[first]->ce_mode)) die(_("Directory %s is in index and no submodule?"), src); if (!is_staging_gitmodules_ok(the_repository->index)) die(_("Please stage your changes to .gitmodules or stash them to proceed")); + strbuf_addf(&submodule_dotgit, "%s/.git", src); - *submodule_gitfile = read_gitfile(submodule_dotgit.buf); - if (*submodule_gitfile) - *submodule_gitfile = xstrdup(*submodule_gitfile); - else - *submodule_gitfile = SUBMODULE_WITH_GITDIR; + + path = read_gitfile(submodule_dotgit.buf); strbuf_release(&submodule_dotgit); + if (path) + return path; + return SUBMODULE_WITH_GITDIR; } static int index_range_of_same_dir(const char *src, int length, int *first_p, int *last_p) { - const char *src_w_slash = add_slash(src); + char *src_w_slash = add_slash(src); int first, last, len_w_slash = length + 1; first = index_name_pos(the_repository->index, src_w_slash, len_w_slash); @@ -124,8 +117,8 @@ static int index_range_of_same_dir(const char *src, int length, if (strncmp(path, src_w_slash, len_w_slash)) break; } - if (src_w_slash != src) - free((char *)src_w_slash); + + free(src_w_slash); *first_p = first; *last_p = last; return last - first; @@ -141,7 +134,7 @@ static int index_range_of_same_dir(const char *src, int length, static int empty_dir_has_sparse_contents(const char *name) { int ret = 0; - const char *with_slash = add_slash(name); + char *with_slash = add_slash(name); int length = strlen(with_slash); int pos = index_name_pos(the_repository->index, with_slash, length); @@ -159,11 +152,32 @@ static int empty_dir_has_sparse_contents(const char *name) } free_return: - if (with_slash != name) - free((char *)with_slash); + free(with_slash); return ret; } +static void remove_empty_src_dirs(const char **src_dir, size_t src_dir_nr) +{ + size_t i; + struct strbuf a_src_dir = STRBUF_INIT; + + for (i = 0; i < src_dir_nr; i++) { + int dummy; + strbuf_addstr(&a_src_dir, src_dir[i]); + /* + * if entries under a_src_dir are all moved away, + * recursively remove a_src_dir to cleanup + */ + if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, + &dummy, &dummy) < 1) { + remove_dir_recursively(&a_src_dir, 0); + } + strbuf_reset(&a_src_dir); + } + + strbuf_release(&a_src_dir); +} + int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; @@ -177,18 +191,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; - const char **source, **destination, **dest_path, **submodule_gitfile; - const char *dst_w_slash; - const char **src_dir = NULL; - int src_dir_nr = 0, src_dir_alloc = 0; - struct strbuf a_src_dir = STRBUF_INIT; + struct strvec sources = STRVEC_INIT; + struct strvec dest_paths = STRVEC_INIT; + struct strvec destinations = STRVEC_INIT; + struct strvec submodule_gitfiles_to_free = STRVEC_INIT; + const char **submodule_gitfiles; + char *dst_w_slash = NULL; + struct strvec src_dir = STRVEC_INIT; enum update_mode *modes, dst_mode = 0; struct stat st, dest_st; - struct string_list src_for_dst = STRING_LIST_INIT_NODUP; + struct string_list src_for_dst = STRING_LIST_INIT_DUP; struct lock_file lock_file = LOCK_INIT; struct cache_entry *ce; - struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; - struct string_list dirty_paths = STRING_LIST_INIT_NODUP; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_DUP; + struct string_list dirty_paths = STRING_LIST_INIT_DUP; + int ret; git_config(git_default_config, NULL); @@ -201,7 +218,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("index file corrupt")); - source = internal_prefix_pathspec(prefix, argv, argc, 0); + internal_prefix_pathspec(&sources, prefix, argv, argc, 0); CALLOC_ARRAY(modes, argc); /* @@ -212,45 +229,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); - dst_w_slash = add_slash(dest_path[0]); - submodule_gitfile = xcalloc(argc, sizeof(char *)); + internal_prefix_pathspec(&dest_paths, prefix, argv + argc, 1, flags); + dst_w_slash = add_slash(dest_paths.v[0]); + submodule_gitfiles = xcalloc(argc, sizeof(char *)); - if (dest_path[0][0] == '\0') + if (dest_paths.v[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); - else if (!lstat(dest_path[0], &st) && - S_ISDIR(st.st_mode)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); + internal_prefix_pathspec(&destinations, dest_paths.v[0], argv, argc, DUP_BASENAME); + else if (!lstat(dest_paths.v[0], &st) && S_ISDIR(st.st_mode)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + } else if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && + empty_dir_has_sparse_contents(dst_w_slash)) { + internal_prefix_pathspec(&destinations, dst_w_slash, argv, argc, DUP_BASENAME); + dst_mode = SKIP_WORKTREE_DIR; + } else if (argc != 1) { + die(_("destination '%s' is not a directory"), dest_paths.v[0]); } else { - if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && - empty_dir_has_sparse_contents(dst_w_slash)) { - destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); - dst_mode = SKIP_WORKTREE_DIR; - } else if (argc != 1) { - die(_("destination '%s' is not a directory"), dest_path[0]); - } else { - destination = dest_path; - /* - * <destination> is a file outside of sparse-checkout - * cone. Insist on cone mode here for backward - * compatibility. We don't want dst_mode to be assigned - * for a file when the repo is using no-cone mode (which - * is deprecated at this point) sparse-checkout. As - * SPARSE here is only considering cone-mode situation. - */ - if (!path_in_cone_mode_sparse_checkout(destination[0], the_repository->index)) - dst_mode = SPARSE; - } - } - if (dst_w_slash != dest_path[0]) { - free((char *)dst_w_slash); - dst_w_slash = NULL; + strvec_pushv(&destinations, dest_paths.v); + + /* + * <destination> is a file outside of sparse-checkout + * cone. Insist on cone mode here for backward + * compatibility. We don't want dst_mode to be assigned + * for a file when the repo is using no-cone mode (which + * is deprecated at this point) sparse-checkout. As + * SPARSE here is only considering cone-mode situation. + */ + if (!path_in_cone_mode_sparse_checkout(destinations.v[0], the_repository->index)) + dst_mode = SPARSE; } /* Checking */ for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; int length; const char *bad = NULL; int skip_sparse = 0; @@ -265,12 +276,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix) pos = index_name_pos(the_repository->index, src, length); if (pos < 0) { - const char *src_w_slash = add_slash(src); + char *src_w_slash = add_slash(src); if (!path_in_sparse_checkout(src_w_slash, the_repository->index) && empty_dir_has_sparse_contents(src)) { + free(src_w_slash); modes[i] |= SKIP_WORKTREE_DIR; goto dir_check; } + free(src_w_slash); /* only error if existence is expected. */ if (!(modes[i] & SPARSE)) bad = _("bad source"); @@ -310,12 +323,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check: if (S_ISDIR(st.st_mode)) { - int j, dst_len, n; + char *dst_with_slash; + size_t dst_with_slash_len; + int j, n; int first = index_name_pos(the_repository->index, src, length), last; if (first >= 0) { - prepare_move_submodule(src, first, - submodule_gitfile + i); + const char *path = submodule_gitfile_path(src, first); + if (path != SUBMODULE_WITH_GITDIR) + path = strvec_push(&submodule_gitfiles_to_free, path); + submodule_gitfiles[i] = path; goto act_on_entry; } else if (index_range_of_same_dir(src, length, &first, &last) < 1) { @@ -326,28 +343,31 @@ dir_check: /* last - first >= 1 */ modes[i] |= WORKING_DIRECTORY; - ALLOC_GROW(src_dir, src_dir_nr + 1, src_dir_alloc); - src_dir[src_dir_nr++] = src; + strvec_push(&src_dir, src); n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); + REALLOC_ARRAY(submodule_gitfiles, n); - dst = add_slash(dst); - dst_len = strlen(dst); + dst_with_slash = add_slash(dst); + dst_with_slash_len = strlen(dst_with_slash); for (j = 0; j < last - first; j++) { const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); + char *prefixed_path = prefix_path(dst_with_slash, dst_with_slash_len, path + length + 1); + + strvec_push(&sources, path); + strvec_push(&destinations, prefixed_path); + memset(modes + argc + j, 0, sizeof(enum update_mode)); modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; + submodule_gitfiles[argc + j] = NULL; + + free(prefixed_path); } + + free(dst_with_slash); argc += last - first; goto act_on_entry; } @@ -428,23 +448,25 @@ act_on_entry: remove_entry: if (--argc > 0) { int n = argc - i; - MOVE_ARRAY(source + i, source + i + 1, n); - MOVE_ARRAY(destination + i, destination + i + 1, n); + strvec_remove(&sources, i); + strvec_remove(&destinations, i); MOVE_ARRAY(modes + i, modes + i + 1, n); - MOVE_ARRAY(submodule_gitfile + i, - submodule_gitfile + i + 1, n); + MOVE_ARRAY(submodule_gitfiles + i, + submodule_gitfiles + i + 1, n); i--; } } if (only_match_skip_worktree.nr) { advise_on_updating_sparse_paths(&only_match_skip_worktree); - if (!ignore_errors) - return 1; + if (!ignore_errors) { + ret = 1; + goto out; + } } for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; + const char *src = sources.v[i], *dst = destinations.v[i]; enum update_mode mode = modes[i]; int pos; int sparse_and_dirty = 0; @@ -464,12 +486,12 @@ remove_entry: continue; die_errno(_("renaming '%s' failed"), src); } - if (submodule_gitfile[i]) { + if (submodule_gitfiles[i]) { if (!update_path_in_gitmodules(src, dst)) gitmodules_modified = 1; - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + if (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR) connect_work_tree_and_git_dir(dst, - submodule_gitfile[i], + submodule_gitfiles[i], 1); } @@ -535,25 +557,7 @@ remove_entry: } } - /* - * cleanup the empty src_dirs - */ - for (i = 0; i < src_dir_nr; i++) { - int dummy; - strbuf_addstr(&a_src_dir, src_dir[i]); - /* - * if entries under a_src_dir are all moved away, - * recursively remove a_src_dir to cleanup - */ - if (index_range_of_same_dir(a_src_dir.buf, a_src_dir.len, - &dummy, &dummy) < 1) { - remove_dir_recursively(&a_src_dir, 0); - } - strbuf_reset(&a_src_dir); - } - - strbuf_release(&a_src_dir); - free(src_dir); + remove_empty_src_dirs(src_dir.v, src_dir.nr); if (dirty_paths.nr) advise_on_moving_dirty_path(&dirty_paths); @@ -565,11 +569,19 @@ remove_entry: COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + ret = 0; + +out: + strvec_clear(&src_dir); + free(dst_w_slash); string_list_clear(&src_for_dst, 0); string_list_clear(&dirty_paths, 0); - UNLEAK(source); - UNLEAK(dest_path); - free(submodule_gitfile); + string_list_clear(&only_match_skip_worktree, 0); + strvec_clear(&sources); + strvec_clear(&dest_paths); + strvec_clear(&destinations); + strvec_clear(&submodule_gitfiles_to_free); + free(submodule_gitfiles); free(modes); - return 0; + return ret; } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 62ddf41f84..638f5c57f0 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1315,6 +1315,7 @@ static void write_pack_file(void) if (!pack_to_stdout) { struct stat st; struct strbuf tmpname = STRBUF_INIT; + struct bitmap_writer bitmap_writer; char *idx_tmp_name = NULL; /* @@ -1340,8 +1341,9 @@ static void write_pack_file(void) hash_to_hex(hash)); if (write_bitmap_index) { - bitmap_writer_set_checksum(hash); - bitmap_writer_build_type_index( + bitmap_writer_init(&bitmap_writer); + bitmap_writer_set_checksum(&bitmap_writer, hash); + bitmap_writer_build_type_index(&bitmap_writer, &to_pack, written_list, nr_written); } @@ -1359,12 +1361,17 @@ static void write_pack_file(void) strbuf_addstr(&tmpname, "bitmap"); stop_progress(&progress_state); - bitmap_writer_show_progress(progress); - bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1); - if (bitmap_writer_build(&to_pack) < 0) + bitmap_writer_show_progress(&bitmap_writer, + progress); + bitmap_writer_select_commits(&bitmap_writer, + indexed_commits, + indexed_commits_nr); + if (bitmap_writer_build(&bitmap_writer, &to_pack) < 0) die(_("failed to write bitmap index")); - bitmap_writer_finish(written_list, nr_written, + bitmap_writer_finish(&bitmap_writer, + written_list, nr_written, tmpname.buf, write_bitmap_options); + bitmap_writer_free(&bitmap_writer); write_bitmap_index = 0; strbuf_setlen(&tmpname, tmpname_len); } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 3894d2b970..583099cacf 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -5,6 +5,7 @@ #include "hash.h" #include "hex.h" #include "parse-options.h" +#include "setup.h" static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { @@ -237,6 +238,18 @@ int cmd_patch_id(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_patch_id_options, patch_id_usage, 0); + /* + * We rely on `the_hash_algo` to compute patch IDs. This is dubious as + * it means that the hash algorithm now depends on the object hash of + * the repository, even though git-patch-id(1) clearly defines that + * patch IDs always use SHA1. + * + * NEEDSWORK: This hack should be removed in favor of converting + * the code that computes patch IDs to always use SHA1. + */ + if (!the_hash_algo) + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + generate_id_list(opts ? opts > 1 : config.stable, opts ? opts == 3 : config.verbatim); return 0; diff --git a/builtin/rebase.c b/builtin/rebase.c index 0466d9414a..14d4f0a5e6 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -83,7 +83,7 @@ static const char *action_names[] = { struct rebase_options { enum rebase_type type; enum empty_type empty; - const char *default_backend; + char *default_backend; const char *state_dir; struct commit *upstream; const char *upstream_name; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index be8969a84a..01c1f04ece 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -88,7 +88,7 @@ static struct strbuf push_cert = STRBUF_INIT; static struct object_id push_cert_oid; static struct signature_check sigcheck; static const char *push_cert_nonce; -static const char *cert_nonce_seed; +static char *cert_nonce_seed; static struct strvec hidden_refs = STRVEC_INIT; static const char *NONCE_UNSOLICITED = "UNSOLICITED"; @@ -168,13 +168,13 @@ static int receive_pack_config(const char *var, const char *value, } if (strcmp(var, "receive.fsck.skiplist") == 0) { - const char *path; + char *path; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&fsck_msg_types, "%cskiplist=%s", fsck_msg_types.len ? ',' : '=', path); - free((char *)path); + free(path); return 0; } diff --git a/builtin/repack.c b/builtin/repack.c index 58ad82dd97..f0317fa94a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -48,10 +48,10 @@ static const char incremental_bitmap_conflict_error[] = N_( ); struct pack_objects_args { - const char *window; - const char *window_memory; - const char *depth; - const char *threads; + char *window; + char *window_memory; + char *depth; + char *threads; unsigned long max_pack_size; int no_reuse_delta; int no_reuse_object; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 2db047fff4..1e2919fd81 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -691,7 +691,6 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *name = NULL; struct object_context unused; struct strbuf buf = STRBUF_INIT; - const int hexsz = the_hash_algo->hexsz; int seen_end_of_options = 0; enum format_type format = FORMAT_DEFAULT; @@ -867,8 +866,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) abbrev = strtoul(arg, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; - else if (hexsz <= abbrev) - abbrev = hexsz; + else if ((int)the_hash_algo->hexsz <= abbrev) + abbrev = the_hash_algo->hexsz; continue; } if (!strcmp(arg, "--sq")) { diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 3c7cd2d6ef..d4daf31e22 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -435,7 +435,7 @@ parse_done: usage_with_options(shortlog_usage, options); } - if (setup_revisions(argc, argv, &rev, NULL) != 1) { + if (!nongit && setup_revisions(argc, argv, &rev, NULL) != 1) { error(_("unrecognized argument: %s"), argv[1]); usage_with_options(shortlog_usage, options); } diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 0f52e25249..3f2bfce8fa 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -96,10 +96,11 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix) printf("\n"); } - return 0; + string_list_clear(&sl, 0); + } else { + write_patterns_to_file(stdout, &pl); } - write_patterns_to_file(stdout, &pl); clear_pattern_list(&pl); return 0; @@ -205,11 +206,13 @@ static int update_working_directory(struct pattern_list *pl) struct unpack_trees_options o; struct lock_file lock_file = LOCK_INIT; struct repository *r = the_repository; + struct pattern_list *old_pl; /* If no branch has been checked out, there are no updates to make. */ if (is_index_unborn(r->index)) return UPDATE_SPARSITY_SUCCESS; + old_pl = r->index->sparse_checkout_patterns; r->index->sparse_checkout_patterns = pl; memset(&o, 0, sizeof(o)); @@ -241,7 +244,12 @@ static int update_working_directory(struct pattern_list *pl) clean_tracked_sparse_directories(r); - r->index->sparse_checkout_patterns = NULL; + if (r->index->sparse_checkout_patterns != pl) { + clear_pattern_list(r->index->sparse_checkout_patterns); + FREE_AND_NULL(r->index->sparse_checkout_patterns); + } + r->index->sparse_checkout_patterns = old_pl; + return result; } @@ -311,6 +319,8 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl) fprintf(fp, "%s/\n", pattern); free(pattern); } + + string_list_clear(&sl, 0); } static int write_patterns_and_update(struct pattern_list *pl) @@ -440,7 +450,6 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix) char *sparse_filename; int res; struct object_id oid; - struct strbuf pattern = STRBUF_INIT; static struct option builtin_sparse_checkout_init_options[] = { OPT_BOOL(0, "cone", &init_opts.cone_mode, @@ -471,6 +480,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix) /* If we already have a sparse-checkout file, use it. */ if (res >= 0) { free(sparse_filename); + clear_pattern_list(&pl); return update_working_directory(NULL); } @@ -491,10 +501,10 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix) return 0; } - strbuf_addstr(&pattern, "/*"); - add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); - strbuf_addstr(&pattern, "!/*/"); - add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + free(sparse_filename); + + add_pattern("/*", empty_base, 0, &pl, 0); + add_pattern("!/*/", empty_base, 0, &pl, 0); pl.use_cone_patterns = init_opts.cone_mode; return write_patterns_and_update(&pl); @@ -513,6 +523,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat char *slash = strrchr(e->pattern, '/'); char *oldpattern = e->pattern; size_t newlen; + struct pattern_entry *dup; if (!slash || slash == e->pattern) break; @@ -523,8 +534,14 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat e->pattern = xstrndup(oldpattern, newlen); hashmap_entry_init(&e->ent, fspathhash(e->pattern)); - if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL)) + dup = hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL); + if (!dup) { hashmap_add(&pl->parent_hashmap, &e->ent); + } else { + free(e->pattern); + free(e); + e = dup; + } } } @@ -581,15 +598,15 @@ static void add_patterns_from_input(struct pattern_list *pl, strbuf_to_cone_pattern(&line, pl); } } + strbuf_release(&line); } else { if (file) { struct strbuf line = STRBUF_INIT; - while (!strbuf_getline(&line, file)) { - size_t len; - char *buf = strbuf_detach(&line, &len); - add_pattern(buf, empty_base, 0, pl, 0); - } + while (!strbuf_getline(&line, file)) + add_pattern(line.buf, empty_base, 0, pl, 0); + + strbuf_release(&line); } else { for (i = 0; i < argc; i++) add_pattern(argv[i], empty_base, 0, pl, 0); @@ -891,7 +908,6 @@ static int sparse_checkout_disable(int argc, const char **argv, OPT_END(), }; struct pattern_list pl; - struct strbuf match_all = STRBUF_INIT; /* * We do not exit early if !core_apply_sparse_checkout; due to the @@ -917,8 +933,7 @@ static int sparse_checkout_disable(int argc, const char **argv, pl.use_cone_patterns = 0; core_apply_sparse_checkout = 1; - strbuf_addstr(&match_all, "/*"); - add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0); + add_pattern("/*", empty_base, 0, &pl, 0); prepare_repo_settings(the_repository); the_repository->settings.sparse_index = 0; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 897f19868e..880ab4456e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -256,11 +256,9 @@ static void module_list_active(struct module_list *list) static char *get_up_path(const char *path) { - int i; struct strbuf sb = STRBUF_INIT; - for (i = count_slashes(path); i; i--) - strbuf_addstr(&sb, "../"); + strbuf_addstrings(&sb, "../", count_slashes(path)); /* * Check if 'path' ends with slash or not diff --git a/builtin/worktree.c b/builtin/worktree.c index 893e973871..1d51e54fcd 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -736,16 +736,14 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote) return 1; } -static const char *dwim_branch(const char *path, const char **new_branch) +static char *dwim_branch(const char *path, char **new_branch) { int n; int branch_exists; const char *s = worktree_basename(path, &n); - const char *branchname = xstrndup(s, n); + char *branchname = xstrndup(s, n); struct strbuf ref = STRBUF_INIT; - UNLEAK(branchname); - branch_exists = !strbuf_check_branch_ref(&ref, branchname) && refs_ref_exists(get_main_ref_store(the_repository), ref.buf); @@ -756,8 +754,7 @@ static const char *dwim_branch(const char *path, const char **new_branch) *new_branch = branchname; if (guess_remote) { struct object_id oid; - const char *remote = - unique_tracking_name(*new_branch, &oid, NULL); + char *remote = unique_tracking_name(*new_branch, &oid, NULL); return remote; } return NULL; @@ -769,6 +766,8 @@ static int add(int ac, const char **av, const char *prefix) const char *new_branch_force = NULL; char *path; const char *branch; + char *branch_to_free = NULL; + char *new_branch_to_free = NULL; const char *new_branch = NULL; const char *opt_track = NULL; const char *lock_reason = NULL; @@ -859,16 +858,17 @@ static int add(int ac, const char **av, const char *prefix) opts.orphan = dwim_orphan(&opts, !!opt_track, 0); } else if (ac < 2) { /* DWIM: Guess branch name from path. */ - const char *s = dwim_branch(path, &new_branch); + char *s = dwim_branch(path, &new_branch_to_free); if (s) - branch = s; + branch = branch_to_free = s; + new_branch = new_branch_to_free; /* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1); } else if (ac == 2) { struct object_id oid; struct commit *commit; - const char *remote; + char *remote; commit = lookup_commit_reference_by_name(branch); if (!commit) { @@ -923,6 +923,8 @@ static int add(int ac, const char **av, const char *prefix) ret = add_worktree(path, branch, &opts); free(path); + free(branch_to_free); + free(new_branch_to_free); return ret; } diff --git a/checkout.c b/checkout.c index 4256e71a7c..cfaea4bd10 100644 --- a/checkout.c +++ b/checkout.c @@ -45,8 +45,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data) return 0; } -const char *unique_tracking_name(const char *name, struct object_id *oid, - int *dwim_remotes_matched) +char *unique_tracking_name(const char *name, struct object_id *oid, + int *dwim_remotes_matched) { struct tracking_name_data cb_data = TRACKING_NAME_DATA_INIT; const char *default_remote = NULL; diff --git a/checkout.h b/checkout.h index 3c514a5ab4..ba15a13fb3 100644 --- a/checkout.h +++ b/checkout.h @@ -8,8 +8,8 @@ * tracking branch. Return the name of the remote if such a branch * exists, NULL otherwise. */ -const char *unique_tracking_name(const char *name, - struct object_id *oid, - int *dwim_remotes_matched); +char *unique_tracking_name(const char *name, + struct object_id *oid, + int *dwim_remotes_matched); #endif /* CHECKOUT_H */ diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 2e7688ae8b..6ec0f85972 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -27,7 +27,7 @@ alpine-*) apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ pcre2-dev python3 musl-libintl perl-utils ncurses \ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null + bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null ;; fedora-*) dnf -yq update >/dev/null && @@ -42,7 +42,7 @@ ubuntu-*) language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE mkdir --parents "$CUSTOM_PATH" diff --git a/commit-reach.c b/commit-reach.c index 8f9b008f87..384aee1ab3 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -1106,6 +1106,10 @@ void ahead_behind(struct repository *r, /* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks(r, PARENT2 | STALE); + while (prio_queue_peek(&queue)) { + struct commit *c = prio_queue_get(&queue); + free_bit_array(c); + } clear_bit_arrays(&bit_arrays); clear_prio_queue(&queue); } @@ -125,7 +125,7 @@ struct config_include_data { config_fn_t fn; void *data; const struct config_options *opts; - struct git_config_source *config_source; + const struct git_config_source *config_source; struct repository *repo; /* @@ -1338,7 +1338,7 @@ int git_config_bool(const char *name, const char *value) return v; } -int git_config_string(const char **dest, const char *var, const char *value) +int git_config_string(char **dest, const char *var, const char *value) { if (!value) return config_error_nonbool(var); @@ -1346,7 +1346,7 @@ int git_config_string(const char **dest, const char *var, const char *value) return 0; } -int git_config_pathname(const char **dest, const char *var, const char *value) +int git_config_pathname(char **dest, const char *var, const char *value) { if (!value) return config_error_nonbool(var); @@ -1414,20 +1414,13 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.attributesfile")) + if (!strcmp(var, "core.attributesfile")) { + FREE_AND_NULL(git_attributes_file); return git_config_pathname(&git_attributes_file, var, value); + } if (!strcmp(var, "core.hookspath")) { - if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL && - git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0)) - die(_("active `core.hooksPath` found in the local " - "repository config:\n\t%s\nFor security " - "reasons, this is disallowed by default.\nIf " - "this is intentional and the hook should " - "actually be run, please\nrun the command " - "again with " - "`GIT_CLONE_PROTECTION_ACTIVE=false`"), - value); + FREE_AND_NULL(git_hooks_path); return git_config_pathname(&git_hooks_path, var, value); } @@ -1564,8 +1557,10 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.checkroundtripencoding")) + if (!strcmp(var, "core.checkroundtripencoding")) { + FREE_AND_NULL(check_roundtrip_encoding); return git_config_string(&check_roundtrip_encoding, var, value); + } if (!strcmp(var, "core.notesref")) { if (!value) @@ -1574,8 +1569,10 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.editor")) + if (!strcmp(var, "core.editor")) { + FREE_AND_NULL(editor_program); return git_config_string(&editor_program, var, value); + } if (!strcmp(var, "core.commentchar") || !strcmp(var, "core.commentstring")) { @@ -1593,11 +1590,13 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.askpass")) + if (!strcmp(var, "core.askpass")) { + FREE_AND_NULL(askpass_program); return git_config_string(&askpass_program, var, value); + } if (!strcmp(var, "core.excludesfile")) { - free((char *)excludes_file); + FREE_AND_NULL(excludes_file); return git_config_pathname(&excludes_file, var, value); } @@ -1700,11 +1699,15 @@ static int git_default_sparse_config(const char *var, const char *value) static int git_default_i18n_config(const char *var, const char *value) { - if (!strcmp(var, "i18n.commitencoding")) + if (!strcmp(var, "i18n.commitencoding")) { + FREE_AND_NULL(git_commit_encoding); return git_config_string(&git_commit_encoding, var, value); + } - if (!strcmp(var, "i18n.logoutputencoding")) + if (!strcmp(var, "i18n.logoutputencoding")) { + FREE_AND_NULL(git_log_output_encoding); return git_config_string(&git_log_output_encoding, var, value); + } /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -1777,10 +1780,15 @@ static int git_default_push_config(const char *var, const char *value) static int git_default_mailmap_config(const char *var, const char *value) { - if (!strcmp(var, "mailmap.file")) + if (!strcmp(var, "mailmap.file")) { + FREE_AND_NULL(git_mailmap_file); return git_config_pathname(&git_mailmap_file, var, value); - if (!strcmp(var, "mailmap.blob")) + } + + if (!strcmp(var, "mailmap.blob")) { + FREE_AND_NULL(git_mailmap_blob); return git_config_string(&git_mailmap_blob, var, value); + } /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -1788,8 +1796,10 @@ static int git_default_mailmap_config(const char *var, const char *value) static int git_default_attr_config(const char *var, const char *value) { - if (!strcmp(var, "attr.tree")) + if (!strcmp(var, "attr.tree")) { + FREE_AND_NULL(git_attr_tree); return git_config_string(&git_attr_tree, var, value); + } /* * Add other attribute related config variables here and to @@ -2117,7 +2127,7 @@ static int do_git_config_sequence(const struct config_options *opts, } int config_with_options(config_fn_t fn, void *data, - struct git_config_source *config_source, + const struct git_config_source *config_source, struct repository *repo, const struct config_options *opts) { @@ -2416,7 +2426,7 @@ int git_configset_get_string(struct config_set *set, const char *key, char **des { const char *value; if (!git_configset_get_value(set, key, &value, NULL)) - return git_config_string((const char **)dest, key, value); + return git_config_string(dest, key, value); else return 1; } @@ -2494,7 +2504,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d return 1; } -int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest) +int git_configset_get_pathname(struct config_set *set, const char *key, char **dest) { const char *value; if (!git_configset_get_value(set, key, &value, NULL)) @@ -2639,7 +2649,7 @@ int repo_config_get_maybe_bool(struct repository *repo, } int repo_config_get_pathname(struct repository *repo, - const char *key, const char **dest) + const char *key, char **dest) { int ret; git_config_check_init(repo); @@ -2738,7 +2748,7 @@ int git_config_get_maybe_bool(const char *key, int *dest) return repo_config_get_maybe_bool(the_repository, key, dest); } -int git_config_get_pathname(const char *key, const char **dest) +int git_config_get_pathname(const char *key, char **dest) { return repo_config_get_pathname(the_repository, key, dest); } @@ -232,7 +232,7 @@ void git_config(config_fn_t fn, void *); * sets `opts.respect_includes` to `1` by default. */ int config_with_options(config_fn_t fn, void *, - struct git_config_source *config_source, + const struct git_config_source *config_source, struct repository *repo, const struct config_options *opts); @@ -280,13 +280,13 @@ int git_config_bool(const char *, const char *); * Allocates and copies the value string into the `dest` parameter; if no * string is given, prints an error message and returns -1. */ -int git_config_string(const char **, const char *, const char *); +int git_config_string(char **, const char *, const char *); /** * Similar to `git_config_string`, but expands `~` or `~user` into the * user's home directory when found at the beginning of the path. */ -int git_config_pathname(const char **, const char *, const char *); +int git_config_pathname(char **, const char *, const char *); int git_config_expiry_date(timestamp_t *, const char *, const char *); int git_config_color(char *, const char *, const char *); @@ -541,7 +541,7 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lon int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); -int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); +int git_configset_get_pathname(struct config_set *cs, const char *key, char **dest); /* Functions for reading a repository's config */ struct repository; @@ -577,7 +577,7 @@ int repo_config_get_bool_or_int(struct repository *repo, int repo_config_get_maybe_bool(struct repository *repo, const char *key, int *dest); int repo_config_get_pathname(struct repository *repo, - const char *key, const char **dest); + const char *key, char **dest); /* * Functions for reading protected config. By definition, protected @@ -687,7 +687,7 @@ int git_config_get_maybe_bool(const char *key, int *dest); * Similar to `git_config_get_string`, but expands `~` or `~user` into * the user's home directory when found at the beginning of the path. */ -int git_config_get_pathname(const char *key, const char **dest); +int git_config_get_pathname(const char *key, char **dest); int git_config_get_index_threads(int *dest); int git_config_get_split_index(void); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 5c0ddeb3d4..60a22d619a 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2989,22 +2989,42 @@ __git_complete_config_variable_name_and_value () _git_config () { - case "$prev" in - --get|--get-all|--unset|--unset-all) - __gitcomp_nl "$(__git_config_get_set_variables)" + local subcommands subcommand + + __git_resolve_builtins "config" + + subcommands="$___git_resolved_builtins" + subcommand="$(__git_find_subcommand "$subcommands")" + + if [ -z "$subcommand" ] + then + __gitcomp "$subcommands" return - ;; - *.*) - __git_complete_config_variable_value + fi + + case "$cur" in + --*) + __gitcomp_builtin "config_$subcommand" return ;; esac - case "$cur" in - --*) - __gitcomp_builtin config + + case "$subcommand" in + get) + __gitcomp_nl "$(__git_config_get_set_variables)" ;; - *) - __git_complete_config_variable_name + set) + case "$prev" in + *.*) + __git_complete_config_variable_value + ;; + *) + __git_complete_config_variable_name + ;; + esac + ;; + unset) + __gitcomp_nl "$(__git_config_get_set_variables)" ;; esac } diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index 6a40917b1e..6ce22a28ed 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -12,6 +12,7 @@ static CFStringRef username; static CFDataRef password; static CFDataRef password_expiry_utc; static CFDataRef oauth_refresh_token; +static int state_seen; static void clear_credential(void) { @@ -171,6 +172,9 @@ static OSStatus find_internet_password(void) CFRelease(item); + write_item("capability[]", "state", strlen("state")); + write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1")); + out: CFRelease(attrs); @@ -284,6 +288,9 @@ static OSStatus add_internet_password(void) CFDictionaryRef attrs; OSStatus result; + if (state_seen) + return errSecSuccess; + /* Only store complete credentials */ if (!protocol || !host || !username || !password) return -1; @@ -395,6 +402,10 @@ static void read_credential(void) oauth_refresh_token = CFDataCreate(kCFAllocatorDefault, (UInt8 *)v, strlen(v)); + else if (!strcmp(buf, "state[]")) { + if (!strcmp(v, "osxkeychain:seen=1")) + state_seen = 1; + } /* * Ignore other lines; we don't know what they mean, but * this future-proofs us when later versions of git do @@ -414,6 +425,9 @@ int main(int argc, const char **argv) if (!argv[1]) die("%s", usage); + if (open(argv[0], O_RDONLY | O_EXLOCK) == -1) + die("failed to lock %s", argv[0]); + read_credential(); if (!strcmp(argv[1], "get")) @@ -345,30 +345,32 @@ static int check_roundtrip(const char *enc_name) * space separated encodings (eg. "UTF-16, ASCII, CP1125"). * Search for the given encoding in that string. */ - const char *found = strcasestr(check_roundtrip_encoding, enc_name); + const char *encoding = check_roundtrip_encoding ? + check_roundtrip_encoding : "SHIFT-JIS"; + const char *found = strcasestr(encoding, enc_name); const char *next; int len; if (!found) return 0; next = found + strlen(enc_name); - len = strlen(check_roundtrip_encoding); + len = strlen(encoding); return (found && ( /* - * check that the found encoding is at the - * beginning of check_roundtrip_encoding or - * that it is prefixed with a space or comma + * Check that the found encoding is at the beginning of + * encoding or that it is prefixed with a space or + * comma. */ - found == check_roundtrip_encoding || ( + found == encoding || ( (isspace(found[-1]) || found[-1] == ',') ) ) && ( /* - * check that the found encoding is at the - * end of check_roundtrip_encoding or - * that it is suffixed with a space or comma + * Check that the found encoding is at the end of + * encoding or that it is suffixed with a space + * or comma. */ - next == check_roundtrip_encoding + len || ( - next < check_roundtrip_encoding + len && + next == encoding + len || ( + next < encoding + len && (isspace(next[0]) || next[0] == ',') ) )); @@ -979,9 +981,9 @@ done: static struct convert_driver { const char *name; struct convert_driver *next; - const char *smudge; - const char *clean; - const char *process; + char *smudge; + char *clean; + char *process; int required; } *user_convert, **user_convert_tail; @@ -92,7 +92,7 @@ void convert_attrs(struct index_state *istate, struct conv_attrs *ca, const char *path); extern enum eol core_eol; -extern const char *check_roundtrip_encoding; +extern char *check_roundtrip_encoding; const char *get_cached_convert_stats_ascii(struct index_state *istate, const char *path); const char *get_wt_convert_stats_ascii(const char *path); @@ -70,61 +70,3 @@ int copy_file_with_time(const char *dst, const char *src, int mode) return copy_times(dst, src); return status; } - -static int do_symlinks_match(const char *path1, const char *path2) -{ - struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT; - int ret = 0; - - if (!strbuf_readlink(&buf1, path1, 0) && - !strbuf_readlink(&buf2, path2, 0)) - ret = !strcmp(buf1.buf, buf2.buf); - - strbuf_release(&buf1); - strbuf_release(&buf2); - return ret; -} - -int do_files_match(const char *path1, const char *path2) -{ - struct stat st1, st2; - int fd1 = -1, fd2 = -1, ret = 1; - char buf1[8192], buf2[8192]; - - if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 || - fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) { - if (fd1 < 0 && errno == ELOOP) - /* maybe this is a symbolic link? */ - return do_symlinks_match(path1, path2); - ret = 0; - } else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 || - fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) { - ret = 0; - } - - if (ret) - /* to match, neither must be executable, or both */ - ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111); - - if (ret) - ret = st1.st_size == st2.st_size; - - while (ret) { - ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1)); - ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2)); - - if (len1 < 0 || len2 < 0 || len1 != len2) - ret = 0; /* read error or different file size */ - else if (!len1) /* len2 is also 0; hit EOF on both */ - break; /* ret is still true */ - else - ret = !memcmp(buf1, buf2, len1); - } - - if (fd1 >= 0) - close(fd1); - if (fd2 >= 0) - close(fd2); - - return ret; -} @@ -7,18 +7,4 @@ int copy_fd(int ifd, int ofd); int copy_file(const char *dst, const char *src, int mode); int copy_file_with_time(const char *dst, const char *src, int mode); -/* - * Compare the file mode and contents of two given files. - * - * If both files are actually symbolic links, the function returns 1 if the link - * targets are identical or 0 if they are not. - * - * If any of the two files cannot be accessed or in case of read failures, this - * function returns 0. - * - * If the file modes and contents are identical, the function returns 1, - * otherwise it returns 0. - */ -int do_files_match(const char *path1, const char *path2); - #endif /* COPY_H */ diff --git a/decorate.h b/decorate.h index cdeb17c9df..08af658d34 100644 --- a/decorate.h +++ b/decorate.h @@ -3,7 +3,7 @@ /* * A data structure that associates Git objects to void pointers. See - * t/helper/test-example-decorate.c for a demonstration of how to use these + * t/unit-tests/t-example-decorate.c for a demonstration of how to use these * functions. */ diff --git a/delta-islands.c b/delta-islands.c index 4ac3c10551..89d51b72e3 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -313,7 +313,7 @@ struct island_load_data { size_t nr; size_t alloc; }; -static const char *core_island_name; +static char *core_island_name; static void free_config_regexes(struct island_load_data *ild) { @@ -56,14 +56,14 @@ static int diff_color_moved_default; static int diff_color_moved_ws_default; static int diff_context_default = 3; static int diff_interhunk_context_default; -static const char *diff_word_regex_cfg; -static const char *external_diff_cmd_cfg; -static const char *diff_order_file_cfg; +static char *diff_word_regex_cfg; +static char *external_diff_cmd_cfg; +static char *diff_order_file_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static int diff_no_prefix; -static const char *diff_src_prefix = "a/"; -static const char *diff_dst_prefix = "b/"; +static char *diff_src_prefix; +static char *diff_dst_prefix; static int diff_relative; static int diff_stat_name_width; static int diff_stat_graph_width; @@ -411,9 +411,11 @@ int git_diff_ui_config(const char *var, const char *value, return 0; } if (!strcmp(var, "diff.srcprefix")) { + FREE_AND_NULL(diff_src_prefix); return git_config_string(&diff_src_prefix, var, value); } if (!strcmp(var, "diff.dstprefix")) { + FREE_AND_NULL(diff_dst_prefix); return git_config_string(&diff_dst_prefix, var, value); } if (!strcmp(var, "diff.relative")) { @@ -3433,8 +3435,8 @@ void diff_set_noprefix(struct diff_options *options) void diff_set_default_prefix(struct diff_options *options) { - options->a_prefix = diff_src_prefix; - options->b_prefix = diff_dst_prefix; + options->a_prefix = diff_src_prefix ? diff_src_prefix : "a/"; + options->b_prefix = diff_dst_prefix ? diff_dst_prefix : "b/"; } struct userdiff_driver *get_textconv(struct repository *r, @@ -5371,8 +5373,8 @@ static int diff_opt_default_prefix(const struct option *opt, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(optarg); - diff_src_prefix = "a/"; - diff_dst_prefix = "b/"; + FREE_AND_NULL(diff_src_prefix); + FREE_AND_NULL(diff_dst_prefix); diff_set_default_prefix(options); return 0; } @@ -30,6 +30,13 @@ #include "symlinks.h" #include "trace2.h" #include "tree.h" +#include "hex.h" + + /* + * The maximum size of a pattern/exclude file. If the file exceeds this size + * we will ignore it. + */ +#define PATTERN_MAX_FILE_SIZE (100 * 1024 * 1024) /* * Tells read_directory_recursive how a file or directory should be treated. @@ -726,6 +733,17 @@ static char *dup_and_filter_pattern(const char *pattern) return result; } +static void clear_pattern_entry_hashmap(struct hashmap *map) +{ + struct hashmap_iter iter; + struct pattern_entry *entry; + + hashmap_for_each_entry(map, &iter, entry, ent) { + free(entry->pattern); + } + hashmap_clear_and_free(map, struct pattern_entry, ent); +} + static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given) { struct pattern_entry *translated; @@ -799,6 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern if (given->patternlen > 2 && !strcmp(given->pattern + given->patternlen - 2, "/*")) { + struct pattern_entry *old; + if (!(given->flags & PATTERN_FLAG_NEGATIVE)) { /* Not a cone pattern. */ warning(_("unrecognized pattern: '%s'"), given->pattern); @@ -824,7 +844,11 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern } hashmap_add(&pl->parent_hashmap, &translated->ent); - hashmap_remove(&pl->recursive_hashmap, &translated->ent, &data); + old = hashmap_remove_entry(&pl->recursive_hashmap, translated, ent, &data); + if (old) { + free(old->pattern); + free(old); + } free(data); return; } @@ -855,8 +879,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern clear_hashmaps: warning(_("disabling cone pattern matching")); - hashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent); - hashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent); + clear_pattern_entry_hashmap(&pl->recursive_hashmap); + clear_pattern_entry_hashmap(&pl->parent_hashmap); pl->use_cone_patterns = 0; } @@ -908,12 +932,7 @@ void add_pattern(const char *string, const char *base, int nowildcardlen; parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen); - if (flags & PATTERN_FLAG_MUSTBEDIR) { - FLEXPTR_ALLOC_MEM(pattern, pattern, string, patternlen); - } else { - pattern = xmalloc(sizeof(*pattern)); - pattern->pattern = string; - } + FLEX_ALLOC_MEM(pattern, pattern, string, patternlen); pattern->patternlen = patternlen; pattern->nowildcardlen = nowildcardlen; pattern->base = base; @@ -955,9 +974,8 @@ void clear_pattern_list(struct pattern_list *pl) for (i = 0; i < pl->nr; i++) free(pl->patterns[i]); free(pl->patterns); - free(pl->filebuf); - hashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent); - hashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent); + clear_pattern_entry_hashmap(&pl->recursive_hashmap); + clear_pattern_entry_hashmap(&pl->parent_hashmap); memset(pl, 0, sizeof(*pl)); } @@ -1148,7 +1166,14 @@ static int add_patterns(const char *fname, const char *base, int baselen, } } + if (size > PATTERN_MAX_FILE_SIZE) { + warning("ignoring excessively large pattern file: %s", fname); + free(buf); + return -1; + } + add_patterns_from_buffer(buf, size, base, baselen, pl); + free(buf); return 0; } @@ -1156,16 +1181,15 @@ static int add_patterns_from_buffer(char *buf, size_t size, const char *base, int baselen, struct pattern_list *pl) { + char *orig = buf; int i, lineno = 1; char *entry; hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); - pl->filebuf = buf; - if (skip_utf8_bom(&buf, size)) - size -= buf - pl->filebuf; + size -= buf - orig; entry = buf; @@ -1204,7 +1228,15 @@ int add_patterns_from_blob_to_list( if (r != 1) return r; + if (size > PATTERN_MAX_FILE_SIZE) { + warning("ignoring excessively large pattern blob: %s", + oid_to_hex(oid)); + free(buf); + return -1; + } + add_patterns_from_buffer(buf, size, base, baselen, pl); + free(buf); return 0; } @@ -62,7 +62,6 @@ struct path_pattern { */ struct pattern_list *pl; - const char *pattern; int patternlen; int nowildcardlen; const char *base; @@ -74,6 +73,8 @@ struct path_pattern { * and from -1 decrementing for patterns from CLI args. */ int srcpos; + + char pattern[FLEX_ARRAY]; }; /* used for hashmaps for cone patterns */ @@ -94,9 +95,6 @@ struct pattern_list { int nr; int alloc; - /* remember pointer to exclude file contents so we can free() */ - char *filebuf; - /* origin of list, e.g. path to filename, or descriptive string */ const char *src; diff --git a/environment.c b/environment.c index a73ba9c12c..701d515135 100644 --- a/environment.c +++ b/environment.c @@ -42,12 +42,12 @@ int is_bare_repository_cfg = -1; /* unspecified */ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int repository_format_precious_objects; -const char *git_commit_encoding; -const char *git_log_output_encoding; +char *git_commit_encoding; +char *git_log_output_encoding; char *apply_default_whitespace; char *apply_default_ignorewhitespace; -const char *git_attributes_file; -const char *git_hooks_path; +char *git_attributes_file; +char *git_hooks_path; int zlib_compression_level = Z_BEST_SPEED; int pack_compression_level = Z_DEFAULT_COMPRESSION; int fsync_object_files = -1; @@ -58,13 +58,13 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 96 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; -const char *editor_program; -const char *askpass_program; -const char *excludes_file; +char *editor_program; +char *askpass_program; +char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; -const char *check_roundtrip_encoding = "SHIFT-JIS"; +char *check_roundtrip_encoding; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; diff --git a/environment.h b/environment.h index 0b2d457f07..e9f01d4d11 100644 --- a/environment.h +++ b/environment.h @@ -131,8 +131,8 @@ extern int warn_ambiguous_refs; extern int warn_on_object_refname_ambiguity; extern char *apply_default_whitespace; extern char *apply_default_ignorewhitespace; -extern const char *git_attributes_file; -extern const char *git_hooks_path; +extern char *git_attributes_file; +extern char *git_hooks_path; extern int zlib_compression_level; extern int pack_compression_level; extern size_t packed_git_window_size; @@ -224,12 +224,12 @@ int odb_pack_keep(const char *name); const char *get_log_output_encoding(void); const char *get_commit_output_encoding(void); -extern const char *git_commit_encoding; -extern const char *git_log_output_encoding; +extern char *git_commit_encoding; +extern char *git_log_output_encoding; -extern const char *editor_program; -extern const char *askpass_program; -extern const char *excludes_file; +extern char *editor_program; +extern char *askpass_program; +extern char *excludes_file; /* * Should we print an ellipsis after an abbreviated SHA-1 value diff --git a/fetch-pack.c b/fetch-pack.c index 8e8f3bba32..eba9e420ea 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -733,11 +733,6 @@ static void mark_alternate_complete(struct fetch_negotiator *negotiator UNUSED, mark_complete(&obj->oid); } -struct loose_object_iter { - struct oidset *loose_object_set; - struct ref *refs; -}; - /* * Mark recent commits available locally and reachable from a local ref as * COMPLETE. @@ -1865,13 +1860,13 @@ static int fetch_pack_config_cb(const char *var, const char *value, const char *msg_id; if (strcmp(var, "fetch.fsck.skiplist") == 0) { - const char *path; + char *path ; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&fsck_msg_types, "%cskiplist=%s", fsck_msg_types.len ? ',' : '=', path); - free((char *)path); + free(path); return 0; } @@ -658,8 +658,6 @@ static int fsck_tree(const struct object_id *tree_oid, retval += report(options, tree_oid, OBJ_TREE, FSCK_MSG_MAILMAP_SYMLINK, ".mailmap is a symlink"); - oidset_insert(&options->symlink_targets_found, - entry_oid); } if ((backslash = strchr(name, '\\'))) { @@ -1168,56 +1166,6 @@ static int fsck_blob(const struct object_id *oid, const char *buf, } } - if (oidset_contains(&options->symlink_targets_found, oid)) { - const char *ptr = buf; - const struct object_id *reported = NULL; - - oidset_insert(&options->symlink_targets_done, oid); - - if (!buf || size > PATH_MAX) { - /* - * A missing buffer here is a sign that the caller found the - * blob too gigantic to load into memory. Let's just consider - * that an error. - */ - return report(options, oid, OBJ_BLOB, - FSCK_MSG_SYMLINK_TARGET_LENGTH, - "symlink target too long"); - } - - while (!reported && ptr) { - const char *p = ptr; - char c, *slash = strchrnul(ptr, '/'); - char *backslash = memchr(ptr, '\\', slash - ptr); - - c = *slash; - *slash = '\0'; - - while (!reported && backslash) { - *backslash = '\0'; - if (is_ntfs_dotgit(p)) - ret |= report(options, reported = oid, OBJ_BLOB, - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, - "symlink target points to git dir"); - *backslash = '\\'; - p = backslash + 1; - backslash = memchr(p, '\\', slash - p); - } - if (!reported && is_ntfs_dotgit(p)) - ret |= report(options, reported = oid, OBJ_BLOB, - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, - "symlink target points to git dir"); - - if (!reported && is_hfs_dotgit(ptr)) - ret |= report(options, reported = oid, OBJ_BLOB, - FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR, - "symlink target points to git dir"); - - *slash = c; - ptr = c ? slash + 1 : NULL; - } - } - return ret; } @@ -1316,10 +1264,6 @@ int fsck_finish(struct fsck_options *options) FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB, options, ".gitattributes"); - ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done, - FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB, - options, "<symlink-target>"); - return ret; } @@ -1330,13 +1274,13 @@ int git_fsck_config(const char *var, const char *value, const char *msg_id; if (strcmp(var, "fsck.skiplist") == 0) { - const char *path; + char *path; struct strbuf sb = STRBUF_INIT; if (git_config_pathname(&path, var, value)) return 1; strbuf_addf(&sb, "skiplist=%s", path); - free((char *)path); + free(path); fsck_set_msg_types(options, sb.buf); strbuf_release(&sb); return 0; @@ -64,8 +64,6 @@ enum fsck_msg_type { FUNC(GITATTRIBUTES_LARGE, ERROR) \ FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \ FUNC(GITATTRIBUTES_BLOB, ERROR) \ - FUNC(SYMLINK_TARGET_MISSING, ERROR) \ - FUNC(SYMLINK_TARGET_BLOB, ERROR) \ /* warnings */ \ FUNC(EMPTY_NAME, WARN) \ FUNC(FULL_PATHNAME, WARN) \ @@ -76,8 +74,6 @@ enum fsck_msg_type { FUNC(ZERO_PADDED_FILEMODE, WARN) \ FUNC(NUL_IN_COMMIT, WARN) \ FUNC(LARGE_PATHNAME, WARN) \ - FUNC(SYMLINK_TARGET_LENGTH, WARN) \ - FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \ /* infos (reported as warnings, but ignored by default) */ \ FUNC(BAD_FILEMODE, INFO) \ FUNC(GITMODULES_PARSE, INFO) \ @@ -145,8 +141,6 @@ struct fsck_options { struct oidset gitmodules_done; struct oidset gitattributes_found; struct oidset gitattributes_done; - struct oidset symlink_targets_found; - struct oidset symlink_targets_done; kh_oid_map_t *object_names; }; @@ -156,8 +150,6 @@ struct fsck_options { .gitmodules_done = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \ - .symlink_targets_found = OIDSET_INIT, \ - .symlink_targets_done = OIDSET_INIT, \ .error_func = fsck_error_function \ } #define FSCK_OPTIONS_STRICT { \ @@ -166,8 +158,6 @@ struct fsck_options { .gitmodules_done = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \ - .symlink_targets_found = OIDSET_INIT, \ - .symlink_targets_done = OIDSET_INIT, \ .error_func = fsck_error_function, \ } #define FSCK_OPTIONS_MISSING_GITMODULES { \ @@ -176,8 +166,6 @@ struct fsck_options { .gitmodules_done = OIDSET_INIT, \ .gitattributes_found = OIDSET_INIT, \ .gitattributes_done = OIDSET_INIT, \ - .symlink_targets_found = OIDSET_INIT, \ - .symlink_targets_done = OIDSET_INIT, \ .error_func = fsck_error_cb_print_missing_gitmodules, \ } diff --git a/fsmonitor-settings.c b/fsmonitor-settings.c index a6a9e6bc19..e818583420 100644 --- a/fsmonitor-settings.c +++ b/fsmonitor-settings.c @@ -103,6 +103,7 @@ static struct fsmonitor_settings *alloc_settings(void) static void lookup_fsmonitor_settings(struct repository *r) { const char *const_str; + char *to_free = NULL; int bool_value; if (r->settings.fsmonitor) @@ -129,8 +130,9 @@ static void lookup_fsmonitor_settings(struct repository *r) break; case -1: /* config value set to an arbitrary string */ - if (repo_config_get_pathname(r, "core.fsmonitor", &const_str)) + if (repo_config_get_pathname(r, "core.fsmonitor", &to_free)) return; /* should not happen */ + const_str = to_free; break; default: /* should not happen */ @@ -141,6 +143,7 @@ static void lookup_fsmonitor_settings(struct repository *r) fsm_settings__set_hook(r, const_str); else fsm_settings__set_disabled(r); + free(to_free); } enum fsmonitor_mode fsm_settings__get_mode(struct repository *r) diff --git a/git-send-email.perl b/git-send-email.perl index 821b2b3a13..f0be4b4560 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1664,9 +1664,11 @@ EOF $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; } if ($quiet) { - printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject); + printf($dry_run ? __("Dry-Sent %s") : __("Sent %s"), $subject); + print "\n"; } else { - print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n")); + print($dry_run ? __("Dry-OK. Log says:") : __("OK. Log says:")); + print "\n"; if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; @@ -1686,10 +1688,11 @@ EOF print $header, "\n"; if ($smtp) { print __("Result: "), $smtp->code, ' ', - ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; + ($smtp->message =~ /\n([^\n]+\n)$/s); } else { - print __("Result: OK\n"); + print __("Result: OK"); } + print "\n"; } return 1; diff --git a/gpg-interface.c b/gpg-interface.c index 1ff94266d2..5193223714 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -27,12 +27,14 @@ static void gpg_interface_lazy_init(void) } static char *configured_signing_key; -static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file; +static char *ssh_default_key_command; +static char *ssh_allowed_signers; +static char *ssh_revocation_file; static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED; struct gpg_format { const char *name; - const char *program; + char *program; const char **verify_args; const char **sigs; int (*verify_signed_buffer)(struct signature_check *sigc, @@ -9,31 +9,6 @@ #include "strbuf.h" #include "environment.h" #include "setup.h" -#include "copy.h" - -static int identical_to_template_hook(const char *name, const char *path) -{ - const char *env = getenv("GIT_CLONE_TEMPLATE_DIR"); - const char *template_dir = get_template_dir(env && *env ? env : NULL); - struct strbuf template_path = STRBUF_INIT; - int found_template_hook, ret; - - strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name); - found_template_hook = access(template_path.buf, X_OK) >= 0; -#ifdef STRIP_EXTENSION - if (!found_template_hook) { - strbuf_addstr(&template_path, STRIP_EXTENSION); - found_template_hook = access(template_path.buf, X_OK) >= 0; - } -#endif - if (!found_template_hook) - return 0; - - ret = do_files_match(template_path.buf, path); - - strbuf_release(&template_path); - return ret; -} const char *find_hook(const char *name) { @@ -70,14 +45,6 @@ const char *find_hook(const char *name) } return NULL; } - if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) && - !identical_to_template_hook(name, path.buf)) - die(_("active `%s` hook found during `git clone`:\n\t%s\n" - "For security reasons, this is disallowed by default.\n" - "If this is intentional and the hook should actually " - "be run, please\nrun the command again with " - "`GIT_CLONE_PROTECTION_ACTIVE=false`"), - name, path.buf); return path.buf; } @@ -38,11 +38,11 @@ char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; static int curl_ssl_try; -static const char *curl_http_version = NULL; -static const char *ssl_cert; -static const char *ssl_cert_type; -static const char *ssl_cipherlist; -static const char *ssl_version; +static char *curl_http_version; +static char *ssl_cert; +static char *ssl_cert_type; +static char *ssl_cipherlist; +static char *ssl_version; static struct { const char *name; long ssl_version; @@ -59,23 +59,23 @@ static struct { { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, #endif }; -static const char *ssl_key; -static const char *ssl_key_type; -static const char *ssl_capath; -static const char *curl_no_proxy; +static char *ssl_key; +static char *ssl_key_type; +static char *ssl_capath; +static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY -static const char *ssl_pinnedkey; +static char *ssl_pinnedkey; #endif -static const char *ssl_cainfo; +static char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; -static const char *curl_http_proxy; -static const char *http_proxy_authmethod; +static char *curl_http_proxy; +static char *http_proxy_authmethod; -static const char *http_proxy_ssl_cert; -static const char *http_proxy_ssl_key; -static const char *http_proxy_ssl_ca_info; +static char *http_proxy_ssl_cert; +static char *http_proxy_ssl_key; +static char *http_proxy_ssl_ca_info; static struct credential proxy_cert_auth = CREDENTIAL_INIT; static int proxy_ssl_cert_password_required; @@ -95,7 +95,7 @@ static struct { */ }; #ifdef CURLGSSAPI_DELEGATION_FLAG -static const char *curl_deleg; +static char *curl_deleg; static struct { const char *name; long curl_deleg_param; @@ -108,11 +108,11 @@ static struct { static struct credential proxy_auth = CREDENTIAL_INIT; static const char *curl_proxyuserpwd; -static const char *curl_cookie_file; +static char *curl_cookie_file; static int curl_save_cookies; struct credential http_auth = CREDENTIAL_INIT; static int http_proactive_auth; -static const char *user_agent; +static char *user_agent; static int curl_empty_auth = -1; enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL; @@ -592,10 +592,10 @@ static void init_curl_http_auth(CURL *result) } /* *var must be free-able */ -static void var_override(const char **var, char *value) +static void var_override(char **var, char *value) { if (value) { - free((void *)*var); + free(*var); *var = xstrdup(value); } } @@ -1233,11 +1233,13 @@ static CURL *get_curl_handle(void) return result; } -static void set_from_env(const char **var, const char *envname) +static void set_from_env(char **var, const char *envname) { const char *val = getenv(envname); - if (val) - *var = val; + if (val) { + FREE_AND_NULL(*var); + *var = xstrdup(val); + } } void http_init(struct remote *remote, const char *url, int proactive_auth) diff --git a/imap-send.c b/imap-send.c index c0130d0e02..185104d019 100644 --- a/imap-send.c +++ b/imap-send.c @@ -70,16 +70,16 @@ static char *next_arg(char **); struct imap_server_conf { const char *name; - const char *tunnel; - const char *host; + char *tunnel; + char *host; int port; - const char *folder; - const char *user; - const char *pass; + char *folder; + char *user; + char *pass; int use_ssl; int ssl_verify; int use_html; - const char *auth_method; + char *auth_method; }; static struct imap_server_conf server = { @@ -1543,6 +1543,7 @@ int cmd_main(int argc, const char **argv) } if (all_msgs.len == 0) { + strbuf_release(&all_msgs); fprintf(stderr, "nothing to send\n"); return 1; } diff --git a/json-writer.c b/json-writer.c index 005c820aa4..25b9201f9c 100644 --- a/json-writer.c +++ b/json-writer.c @@ -46,10 +46,7 @@ static void append_quoted_string(struct strbuf *out, const char *in) static void indent_pretty(struct json_writer *jw) { - int k; - - for (k = 0; k < jw->open_stack.len; k++) - strbuf_addstr(&jw->json, " "); + strbuf_addstrings(&jw->json, " ", jw->open_stack.len); } /* @@ -6,8 +6,8 @@ #include "object-store-ll.h" #include "setup.h" -const char *git_mailmap_file; -const char *git_mailmap_blob; +char *git_mailmap_file; +char *git_mailmap_blob; struct mailmap_info { char *name; @@ -3,8 +3,8 @@ struct string_list; -extern const char *git_mailmap_file; -extern const char *git_mailmap_blob; +extern char *git_mailmap_file; +extern char *git_mailmap_blob; int read_mailmap(struct string_list *map); void clear_mailmap(struct string_list *map); diff --git a/merge-ll.c b/merge-ll.c index bf1077ae09..e29b15fa4a 100644 --- a/merge-ll.c +++ b/merge-ll.c @@ -27,9 +27,9 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *, struct ll_merge_driver { const char *name; - const char *description; + char *description; ll_merge_fn fn; - const char *recursive; + char *recursive; struct ll_merge_driver *next; char *cmdline; }; @@ -268,7 +268,7 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, * merge.default and merge.driver configuration items */ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; +static char *default_ll_merge; static int read_merge_config(const char *var, const char *value, const struct config_context *ctx UNUSED, diff --git a/mergetools/vimdiff b/mergetools/vimdiff index 734d15a03b..f8ad6b35d4 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -325,7 +325,7 @@ gen_cmd () { fi # If this is a single window diff with all the buffers - if ! echo "$tab" | grep ",\|/" >/dev/null + if ! echo "$tab" | grep -E ",|/" >/dev/null then CMD="$CMD | silent execute 'bufdo diffthis'" fi diff --git a/midx-write.c b/midx-write.c index 86173abdb9..55a6b63bac 100644 --- a/midx-write.c +++ b/midx-write.c @@ -100,6 +100,18 @@ struct write_midx_context { struct string_list *to_include; }; +static int should_include_pack(const struct write_midx_context *ctx, + const char *file_name, + int exclude_from_midx) +{ + if (exclude_from_midx && ctx->m && midx_contains_pack(ctx->m, file_name)) + return 0; + if (ctx->to_include && !string_list_has_string(ctx->to_include, + file_name)) + return 0; + return 1; +} + static void add_pack_to_midx(const char *full_path, size_t full_path_len, const char *file_name, void *data) { @@ -108,29 +120,11 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, if (ends_with(file_name, ".idx")) { display_progress(ctx->progress, ++ctx->pack_paths_checked); - /* - * Note that at most one of ctx->m and ctx->to_include are set, - * so we are testing midx_contains_pack() and - * string_list_has_string() independently (guarded by the - * appropriate NULL checks). - * - * We could support passing to_include while reusing an existing - * MIDX, but don't currently since the reuse process drags - * forward all packs from an existing MIDX (without checking - * whether or not they appear in the to_include list). - * - * If we added support for that, these next two conditional - * should be performed independently (likely checking - * to_include before the existing MIDX). - */ - if (ctx->m && midx_contains_pack(ctx->m, file_name)) - return; - else if (ctx->to_include && - !string_list_has_string(ctx->to_include, file_name)) + + if (!should_include_pack(ctx, file_name, 1)) return; ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); - p = add_packed_git(full_path, full_path_len, 0); if (!p) { warning(_("failed to add packfile '%s'"), @@ -299,21 +293,16 @@ static void midx_fanout_add_pack_fanout(struct midx_fanout *fanout, * Copy only the de-duplicated entries (selected by most-recent modified time * of a packfile containing the object). */ -static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, - struct pack_info *info, - uint32_t nr_packs, - size_t *nr_objects, - int preferred_pack) +static void compute_sorted_entries(struct write_midx_context *ctx, + uint32_t start_pack) { uint32_t cur_fanout, cur_pack, cur_object; size_t alloc_objects, total_objects = 0; struct midx_fanout fanout = { 0 }; - struct pack_midx_entry *deduplicated_entries = NULL; - uint32_t start_pack = m ? m->num_packs : 0; - for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) + for (cur_pack = start_pack; cur_pack < ctx->nr; cur_pack++) total_objects = st_add(total_objects, - info[cur_pack].p->num_objects); + ctx->info[cur_pack].p->num_objects); /* * As we de-duplicate by fanout value, we expect the fanout @@ -323,26 +312,26 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, alloc_objects = fanout.alloc = total_objects > 3200 ? total_objects / 200 : 16; ALLOC_ARRAY(fanout.entries, fanout.alloc); - ALLOC_ARRAY(deduplicated_entries, alloc_objects); - *nr_objects = 0; + ALLOC_ARRAY(ctx->entries, alloc_objects); + ctx->entries_nr = 0; for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) { fanout.nr = 0; - if (m) - midx_fanout_add_midx_fanout(&fanout, m, cur_fanout, - preferred_pack); + if (ctx->m) + midx_fanout_add_midx_fanout(&fanout, ctx->m, cur_fanout, + ctx->preferred_pack_idx); - for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) { - int preferred = cur_pack == preferred_pack; + for (cur_pack = start_pack; cur_pack < ctx->nr; cur_pack++) { + int preferred = cur_pack == ctx->preferred_pack_idx; midx_fanout_add_pack_fanout(&fanout, - info, cur_pack, + ctx->info, cur_pack, preferred, cur_fanout); } - if (-1 < preferred_pack && preferred_pack < start_pack) - midx_fanout_add_pack_fanout(&fanout, info, - preferred_pack, 1, + if (-1 < ctx->preferred_pack_idx && ctx->preferred_pack_idx < start_pack) + midx_fanout_add_pack_fanout(&fanout, ctx->info, + ctx->preferred_pack_idx, 1, cur_fanout); midx_fanout_sort(&fanout); @@ -356,17 +345,16 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, &fanout.entries[cur_object].oid)) continue; - ALLOC_GROW(deduplicated_entries, st_add(*nr_objects, 1), + ALLOC_GROW(ctx->entries, st_add(ctx->entries_nr, 1), alloc_objects); - memcpy(&deduplicated_entries[*nr_objects], + memcpy(&ctx->entries[ctx->entries_nr], &fanout.entries[cur_object], sizeof(struct pack_midx_entry)); - (*nr_objects)++; + ctx->entries_nr++; } } free(fanout.entries); - return deduplicated_entries; } static int write_midx_pack_names(struct hashfile *f, void *data) @@ -799,6 +787,7 @@ static int write_midx_bitmap(const char *midx_name, { int ret, i; uint16_t options = 0; + struct bitmap_writer writer; struct pack_idx_entry **index; char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash)); @@ -820,8 +809,10 @@ static int write_midx_bitmap(const char *midx_name, for (i = 0; i < pdata->nr_objects; i++) index[i] = &pdata->objects[i].idx; - bitmap_writer_show_progress(flags & MIDX_PROGRESS); - bitmap_writer_build_type_index(pdata, index, pdata->nr_objects); + bitmap_writer_init(&writer); + bitmap_writer_show_progress(&writer, flags & MIDX_PROGRESS); + bitmap_writer_build_type_index(&writer, pdata, index, + pdata->nr_objects); /* * bitmap_writer_finish expects objects in lex order, but pack_order @@ -839,17 +830,19 @@ static int write_midx_bitmap(const char *midx_name, for (i = 0; i < pdata->nr_objects; i++) index[pack_order[i]] = &pdata->objects[i].idx; - bitmap_writer_select_commits(commits, commits_nr, -1); - ret = bitmap_writer_build(pdata); + bitmap_writer_select_commits(&writer, commits, commits_nr); + ret = bitmap_writer_build(&writer, pdata); if (ret < 0) goto cleanup; - bitmap_writer_set_checksum(midx_hash); - bitmap_writer_finish(index, pdata->nr_objects, bitmap_name, options); + bitmap_writer_set_checksum(&writer, midx_hash); + bitmap_writer_finish(&writer, index, pdata->nr_objects, bitmap_name, + options); cleanup: free(index); free(bitmap_name); + bitmap_writer_free(&writer); trace2_region_leave("midx", "write_midx_bitmap", the_repository); @@ -881,6 +874,43 @@ cleanup: return result; } +static int fill_packs_from_midx(struct write_midx_context *ctx, + const char *preferred_pack_name, uint32_t flags) +{ + uint32_t i; + + for (i = 0; i < ctx->m->num_packs; i++) { + if (!should_include_pack(ctx, ctx->m->pack_names[i], 0)) + continue; + + ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); + + if (flags & MIDX_WRITE_REV_INDEX || preferred_pack_name) { + /* + * If generating a reverse index, need to have + * packed_git's loaded to compare their + * mtimes and object count. + * + * + * If a preferred pack is specified, need to + * have packed_git's loaded to ensure the chosen + * preferred pack has a non-zero object count. + */ + if (prepare_midx_pack(the_repository, ctx->m, i)) + return error(_("could not load pack")); + + if (open_pack_index(ctx->m->packs[i])) + die(_("could not open index for %s"), + ctx->m->packs[i]->pack_name); + } + + fill_pack_info(&ctx->info[ctx->nr++], ctx->m->packs[i], + ctx->m->pack_names[i], i); + } + + return 0; +} + static int write_midx_internal(const char *object_dir, struct string_list *packs_to_include, struct string_list *packs_to_drop, @@ -890,7 +920,7 @@ static int write_midx_internal(const char *object_dir, { struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; - uint32_t i; + uint32_t i, start_pack; struct hashfile *f = NULL; struct lock_file lk; struct write_midx_context ctx = { 0 }; @@ -907,15 +937,7 @@ static int write_midx_internal(const char *object_dir, die_errno(_("unable to create leading directories of %s"), midx_name.buf); - if (!packs_to_include) { - /* - * Only reference an existing MIDX when not filtering which - * packs to include, since all packs and objects are copied - * blindly from an existing MIDX if one is present. - */ - ctx.m = lookup_multi_pack_index(the_repository, object_dir); - } - + ctx.m = lookup_multi_pack_index(the_repository, object_dir); if (ctx.m && !midx_checksum_valid(ctx.m)) { warning(_("ignoring existing multi-pack-index; checksum mismatch")); ctx.m = NULL; @@ -924,42 +946,23 @@ static int write_midx_internal(const char *object_dir, ctx.nr = 0; ctx.alloc = ctx.m ? ctx.m->num_packs : 16; ctx.info = NULL; + ctx.to_include = packs_to_include; ALLOC_ARRAY(ctx.info, ctx.alloc); - if (ctx.m) { - for (i = 0; i < ctx.m->num_packs; i++) { - ALLOC_GROW(ctx.info, ctx.nr + 1, ctx.alloc); - - if (flags & MIDX_WRITE_REV_INDEX) { - /* - * If generating a reverse index, need to have - * packed_git's loaded to compare their - * mtimes and object count. - */ - if (prepare_midx_pack(the_repository, ctx.m, i)) { - error(_("could not load pack")); - result = 1; - goto cleanup; - } - - if (open_pack_index(ctx.m->packs[i])) - die(_("could not open index for %s"), - ctx.m->packs[i]->pack_name); - } - - fill_pack_info(&ctx.info[ctx.nr++], ctx.m->packs[i], - ctx.m->pack_names[i], i); - } + if (ctx.m && fill_packs_from_midx(&ctx, preferred_pack_name, + flags) < 0) { + result = 1; + goto cleanup; } + start_pack = ctx.nr; + ctx.pack_paths_checked = 0; if (flags & MIDX_PROGRESS) ctx.progress = start_delayed_progress(_("Adding packfiles to multi-pack-index"), 0); else ctx.progress = NULL; - ctx.to_include = packs_to_include; - for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx); stop_progress(&ctx.progress); @@ -1049,8 +1052,7 @@ static int write_midx_internal(const char *object_dir, } } - ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr, - ctx.preferred_pack_idx); + compute_sorted_entries(&ctx, start_pack); ctx.large_offsets_needed = 0; for (i = 0; i < ctx.entries_nr; i++) { @@ -25,13 +25,15 @@ const unsigned char *get_midx_checksum(struct multi_pack_index *m) void get_midx_filename(struct strbuf *out, const char *object_dir) { - strbuf_addf(out, "%s/pack/multi-pack-index", object_dir); + get_midx_filename_ext(out, object_dir, NULL, NULL); } -void get_midx_rev_filename(struct strbuf *out, struct multi_pack_index *m) +void get_midx_filename_ext(struct strbuf *out, const char *object_dir, + const unsigned char *hash, const char *ext) { - get_midx_filename(out, m->object_dir); - strbuf_addf(out, "-%s.rev", hash_to_hex(get_midx_checksum(m))); + strbuf_addf(out, "%s/pack/multi-pack-index", object_dir); + if (ext) + strbuf_addf(out, "-%s.%s", hash_to_hex(hash), ext); } static int midx_read_oid_fanout(const unsigned char *chunk_start, @@ -74,9 +74,13 @@ struct multi_pack_index { #define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3) #define MIDX_WRITE_BITMAP_LOOKUP_TABLE (1 << 4) +#define MIDX_EXT_REV "rev" +#define MIDX_EXT_BITMAP "bitmap" + const unsigned char *get_midx_checksum(struct multi_pack_index *m); void get_midx_filename(struct strbuf *out, const char *object_dir); -void get_midx_rev_filename(struct strbuf *out, struct multi_pack_index *m); +void get_midx_filename_ext(struct strbuf *out, const char *object_dir, + const unsigned char *hash, const char *ext); struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local); int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id); @@ -81,6 +81,7 @@ void object_array_init(struct object_array *array); * reflog.c: 10--12 * builtin/show-branch.c: 0-------------------------------------------26 * builtin/unpack-objects.c: 2021 + * pack-bitmap.h: 22 */ #define FLAG_BITS 28 diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c index fe15e2c225..75e668a057 100644 --- a/oss-fuzz/fuzz-commit-graph.c +++ b/oss-fuzz/fuzz-commit-graph.c @@ -19,6 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) * touching the disk to keep the individual fuzz-test cases as fast as * possible. */ + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); the_repository->settings.commit_graph_generation_version = 2; the_repository->settings.commit_graph_read_changed_paths = 1; g = parse_commit_graph(&the_repository->settings, (void *)data, size); diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index c6c8f94cc5..6cae670412 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -27,43 +27,53 @@ struct bitmapped_commit { uint32_t commit_pos; }; -struct bitmap_writer { - struct ewah_bitmap *commits; - struct ewah_bitmap *trees; - struct ewah_bitmap *blobs; - struct ewah_bitmap *tags; +void bitmap_writer_init(struct bitmap_writer *writer) +{ + memset(writer, 0, sizeof(struct bitmap_writer)); +} + +void bitmap_writer_free(struct bitmap_writer *writer) +{ + uint32_t i; - kh_oid_map_t *bitmaps; - struct packing_data *to_pack; + if (!writer) + return; - struct bitmapped_commit *selected; - unsigned int selected_nr, selected_alloc; + ewah_free(writer->commits); + ewah_free(writer->trees); + ewah_free(writer->blobs); + ewah_free(writer->tags); - struct progress *progress; - int show_progress; - unsigned char pack_checksum[GIT_MAX_RAWSZ]; -}; + kh_destroy_oid_map(writer->bitmaps); -static struct bitmap_writer writer; + for (i = 0; i < writer->selected_nr; i++) { + struct bitmapped_commit *bc = &writer->selected[i]; + if (bc->write_as != bc->bitmap) + ewah_free(bc->write_as); + ewah_free(bc->bitmap); + } + free(writer->selected); +} -void bitmap_writer_show_progress(int show) +void bitmap_writer_show_progress(struct bitmap_writer *writer, int show) { - writer.show_progress = show; + writer->show_progress = show; } /** * Build the initial type index for the packfile or multi-pack-index */ -void bitmap_writer_build_type_index(struct packing_data *to_pack, +void bitmap_writer_build_type_index(struct bitmap_writer *writer, + struct packing_data *to_pack, struct pack_idx_entry **index, uint32_t index_nr) { uint32_t i; - writer.commits = ewah_new(); - writer.trees = ewah_new(); - writer.blobs = ewah_new(); - writer.tags = ewah_new(); + writer->commits = ewah_new(); + writer->trees = ewah_new(); + writer->blobs = ewah_new(); + writer->tags = ewah_new(); ALLOC_ARRAY(to_pack->in_pack_pos, to_pack->nr_objects); for (i = 0; i < index_nr; ++i) { @@ -88,19 +98,19 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack, switch (real_type) { case OBJ_COMMIT: - ewah_set(writer.commits, i); + ewah_set(writer->commits, i); break; case OBJ_TREE: - ewah_set(writer.trees, i); + ewah_set(writer->trees, i); break; case OBJ_BLOB: - ewah_set(writer.blobs, i); + ewah_set(writer->blobs, i); break; case OBJ_TAG: - ewah_set(writer.tags, i); + ewah_set(writer->tags, i); break; default: @@ -115,23 +125,26 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack, * Compute the actual bitmaps */ -static inline void push_bitmapped_commit(struct commit *commit) +static inline void push_bitmapped_commit(struct bitmap_writer *writer, + struct commit *commit) { - if (writer.selected_nr >= writer.selected_alloc) { - writer.selected_alloc = (writer.selected_alloc + 32) * 2; - REALLOC_ARRAY(writer.selected, writer.selected_alloc); + if (writer->selected_nr >= writer->selected_alloc) { + writer->selected_alloc = (writer->selected_alloc + 32) * 2; + REALLOC_ARRAY(writer->selected, writer->selected_alloc); } - writer.selected[writer.selected_nr].commit = commit; - writer.selected[writer.selected_nr].bitmap = NULL; - writer.selected[writer.selected_nr].flags = 0; + writer->selected[writer->selected_nr].commit = commit; + writer->selected[writer->selected_nr].bitmap = NULL; + writer->selected[writer->selected_nr].write_as = NULL; + writer->selected[writer->selected_nr].flags = 0; - writer.selected_nr++; + writer->selected_nr++; } -static uint32_t find_object_pos(const struct object_id *oid, int *found) +static uint32_t find_object_pos(struct bitmap_writer *writer, + const struct object_id *oid, int *found) { - struct object_entry *entry = packlist_find(writer.to_pack, oid); + struct object_entry *entry = packlist_find(writer->to_pack, oid); if (!entry) { if (found) @@ -143,17 +156,17 @@ static uint32_t find_object_pos(const struct object_id *oid, int *found) if (found) *found = 1; - return oe_in_pack_pos(writer.to_pack, entry); + return oe_in_pack_pos(writer->to_pack, entry); } -static void compute_xor_offsets(void) +static void compute_xor_offsets(struct bitmap_writer *writer) { static const int MAX_XOR_OFFSET_SEARCH = 10; int i, next = 0; - while (next < writer.selected_nr) { - struct bitmapped_commit *stored = &writer.selected[next]; + while (next < writer->selected_nr) { + struct bitmapped_commit *stored = &writer->selected[next]; int best_offset = 0; struct ewah_bitmap *best_bitmap = stored->bitmap; @@ -166,7 +179,7 @@ static void compute_xor_offsets(void) break; test_xor = ewah_pool_new(); - ewah_xor(writer.selected[curr].bitmap, stored->bitmap, test_xor); + ewah_xor(writer->selected[curr].bitmap, stored->bitmap, test_xor); if (test_xor->buffer_size < best_bitmap->buffer_size) { if (best_bitmap != stored->bitmap) @@ -348,7 +361,8 @@ static void bitmap_builder_clear(struct bitmap_builder *bb) bb->commits_nr = bb->commits_alloc = 0; } -static int fill_bitmap_tree(struct bitmap *bitmap, +static int fill_bitmap_tree(struct bitmap_writer *writer, + struct bitmap *bitmap, struct tree *tree) { int found; @@ -360,7 +374,7 @@ static int fill_bitmap_tree(struct bitmap *bitmap, * If our bit is already set, then there is nothing to do. Both this * tree and all of its children will be set. */ - pos = find_object_pos(&tree->object.oid, &found); + pos = find_object_pos(writer, &tree->object.oid, &found); if (!found) return -1; if (bitmap_get(bitmap, pos)) @@ -375,12 +389,12 @@ static int fill_bitmap_tree(struct bitmap *bitmap, while (tree_entry(&desc, &entry)) { switch (object_type(entry.mode)) { case OBJ_TREE: - if (fill_bitmap_tree(bitmap, + if (fill_bitmap_tree(writer, bitmap, lookup_tree(the_repository, &entry.oid)) < 0) return -1; break; case OBJ_BLOB: - pos = find_object_pos(&entry.oid, &found); + pos = find_object_pos(writer, &entry.oid, &found); if (!found) return -1; bitmap_set(bitmap, pos); @@ -397,7 +411,8 @@ static int fill_bitmap_tree(struct bitmap *bitmap, static int reused_bitmaps_nr; -static int fill_bitmap_commit(struct bb_commit *ent, +static int fill_bitmap_commit(struct bitmap_writer *writer, + struct bb_commit *ent, struct commit *commit, struct prio_queue *queue, struct prio_queue *tree_queue, @@ -436,7 +451,7 @@ static int fill_bitmap_commit(struct bb_commit *ent, * Mark ourselves and queue our tree. The commit * walk ensures we cover all parents. */ - pos = find_object_pos(&c->object.oid, &found); + pos = find_object_pos(writer, &c->object.oid, &found); if (!found) return -1; bitmap_set(ent->bitmap, pos); @@ -444,7 +459,8 @@ static int fill_bitmap_commit(struct bb_commit *ent, repo_get_commit_tree(the_repository, c)); for (p = c->parents; p; p = p->next) { - pos = find_object_pos(&p->item->object.oid, &found); + pos = find_object_pos(writer, &p->item->object.oid, + &found); if (!found) return -1; if (!bitmap_get(ent->bitmap, pos)) { @@ -455,29 +471,31 @@ static int fill_bitmap_commit(struct bb_commit *ent, } while (tree_queue->nr) { - if (fill_bitmap_tree(ent->bitmap, + if (fill_bitmap_tree(writer, ent->bitmap, prio_queue_get(tree_queue)) < 0) return -1; } return 0; } -static void store_selected(struct bb_commit *ent, struct commit *commit) +static void store_selected(struct bitmap_writer *writer, + struct bb_commit *ent, struct commit *commit) { - struct bitmapped_commit *stored = &writer.selected[ent->idx]; + struct bitmapped_commit *stored = &writer->selected[ent->idx]; khiter_t hash_pos; int hash_ret; stored->bitmap = bitmap_to_ewah(ent->bitmap); - hash_pos = kh_put_oid_map(writer.bitmaps, commit->object.oid, &hash_ret); + hash_pos = kh_put_oid_map(writer->bitmaps, commit->object.oid, &hash_ret); if (hash_ret == 0) die("Duplicate entry when writing index: %s", oid_to_hex(&commit->object.oid)); - kh_value(writer.bitmaps, hash_pos) = stored; + kh_value(writer->bitmaps, hash_pos) = stored; } -int bitmap_writer_build(struct packing_data *to_pack) +int bitmap_writer_build(struct bitmap_writer *writer, + struct packing_data *to_pack) { struct bitmap_builder bb; size_t i; @@ -488,11 +506,12 @@ int bitmap_writer_build(struct packing_data *to_pack) uint32_t *mapping; int closed = 1; /* until proven otherwise */ - writer.bitmaps = kh_init_oid_map(); - writer.to_pack = to_pack; + writer->bitmaps = kh_init_oid_map(); + writer->to_pack = to_pack; - if (writer.show_progress) - writer.progress = start_progress("Building bitmaps", writer.selected_nr); + if (writer->show_progress) + writer->progress = start_progress("Building bitmaps", + writer->selected_nr); trace2_region_enter("pack-bitmap-write", "building_bitmaps_total", the_repository); @@ -502,23 +521,23 @@ int bitmap_writer_build(struct packing_data *to_pack) else mapping = NULL; - bitmap_builder_init(&bb, &writer, old_bitmap); + bitmap_builder_init(&bb, writer, old_bitmap); for (i = bb.commits_nr; i > 0; i--) { struct commit *commit = bb.commits[i-1]; struct bb_commit *ent = bb_data_at(&bb.data, commit); struct commit *child; int reused = 0; - if (fill_bitmap_commit(ent, commit, &queue, &tree_queue, + if (fill_bitmap_commit(writer, ent, commit, &queue, &tree_queue, old_bitmap, mapping) < 0) { closed = 0; break; } if (ent->selected) { - store_selected(ent, commit); + store_selected(writer, ent, commit); nr_stored++; - display_progress(writer.progress, nr_stored); + display_progress(writer->progress, nr_stored); } while ((child = pop_commit(&ent->reverse_edges))) { @@ -549,10 +568,10 @@ int bitmap_writer_build(struct packing_data *to_pack) trace2_data_intmax("pack-bitmap-write", the_repository, "building_bitmaps_reused", reused_bitmaps_nr); - stop_progress(&writer.progress); + stop_progress(&writer->progress); if (closed) - compute_xor_offsets(); + compute_xor_offsets(writer); return closed ? 0 : -1; } @@ -590,9 +609,9 @@ static int date_compare(const void *_a, const void *_b) return (long)b->date - (long)a->date; } -void bitmap_writer_select_commits(struct commit **indexed_commits, - unsigned int indexed_commits_nr, - int max_bitmaps) +void bitmap_writer_select_commits(struct bitmap_writer *writer, + struct commit **indexed_commits, + unsigned int indexed_commits_nr) { unsigned int i = 0, j, next; @@ -600,12 +619,12 @@ void bitmap_writer_select_commits(struct commit **indexed_commits, if (indexed_commits_nr < 100) { for (i = 0; i < indexed_commits_nr; ++i) - push_bitmapped_commit(indexed_commits[i]); + push_bitmapped_commit(writer, indexed_commits[i]); return; } - if (writer.show_progress) - writer.progress = start_progress("Selecting bitmap commits", 0); + if (writer->show_progress) + writer->progress = start_progress("Selecting bitmap commits", 0); for (;;) { struct commit *chosen = NULL; @@ -615,11 +634,6 @@ void bitmap_writer_select_commits(struct commit **indexed_commits, if (i + next >= indexed_commits_nr) break; - if (max_bitmaps > 0 && writer.selected_nr >= max_bitmaps) { - writer.selected_nr = max_bitmaps; - break; - } - if (next == 0) { chosen = indexed_commits[i]; } else { @@ -638,13 +652,13 @@ void bitmap_writer_select_commits(struct commit **indexed_commits, } } - push_bitmapped_commit(chosen); + push_bitmapped_commit(writer, chosen); i += next + 1; - display_progress(writer.progress, i); + display_progress(writer->progress, i); } - stop_progress(&writer.progress); + stop_progress(&writer->progress); } @@ -670,19 +684,18 @@ static const struct object_id *oid_access(size_t pos, const void *table) return &index[pos]->oid; } -static void write_selected_commits_v1(struct hashfile *f, - uint32_t *commit_positions, - off_t *offsets) +static void write_selected_commits_v1(struct bitmap_writer *writer, + struct hashfile *f, off_t *offsets) { int i; - for (i = 0; i < writer.selected_nr; ++i) { - struct bitmapped_commit *stored = &writer.selected[i]; + for (i = 0; i < writer->selected_nr; ++i) { + struct bitmapped_commit *stored = &writer->selected[i]; if (offsets) offsets[i] = hashfile_total(f); - hashwrite_be32(f, commit_positions[i]); + hashwrite_be32(f, stored->commit_pos); hashwrite_u8(f, stored->xor_offset); hashwrite_u8(f, stored->flags); @@ -692,29 +705,28 @@ static void write_selected_commits_v1(struct hashfile *f, static int table_cmp(const void *_va, const void *_vb, void *_data) { - uint32_t *commit_positions = _data; - uint32_t a = commit_positions[*(uint32_t *)_va]; - uint32_t b = commit_positions[*(uint32_t *)_vb]; + struct bitmap_writer *writer = _data; + struct bitmapped_commit *a = &writer->selected[*(uint32_t *)_va]; + struct bitmapped_commit *b = &writer->selected[*(uint32_t *)_vb]; - if (a > b) - return 1; - else if (a < b) + if (a->commit_pos < b->commit_pos) return -1; + else if (a->commit_pos > b->commit_pos) + return 1; return 0; } -static void write_lookup_table(struct hashfile *f, - uint32_t *commit_positions, +static void write_lookup_table(struct bitmap_writer *writer, struct hashfile *f, off_t *offsets) { uint32_t i; uint32_t *table, *table_inv; - ALLOC_ARRAY(table, writer.selected_nr); - ALLOC_ARRAY(table_inv, writer.selected_nr); + ALLOC_ARRAY(table, writer->selected_nr); + ALLOC_ARRAY(table_inv, writer->selected_nr); - for (i = 0; i < writer.selected_nr; i++) + for (i = 0; i < writer->selected_nr; i++) table[i] = i; /* @@ -722,17 +734,17 @@ static void write_lookup_table(struct hashfile *f, * bitmap corresponds to j'th bitmapped commit (among the selected * commits) in lex order of OIDs. */ - QSORT_S(table, writer.selected_nr, table_cmp, commit_positions); + QSORT_S(table, writer->selected_nr, table_cmp, writer); /* table_inv helps us discover that relationship (i'th bitmap * to j'th commit by j = table_inv[i]) */ - for (i = 0; i < writer.selected_nr; i++) + for (i = 0; i < writer->selected_nr; i++) table_inv[table[i]] = i; trace2_region_enter("pack-bitmap-write", "writing_lookup_table", the_repository); - for (i = 0; i < writer.selected_nr; i++) { - struct bitmapped_commit *selected = &writer.selected[table[i]]; + for (i = 0; i < writer->selected_nr; i++) { + struct bitmapped_commit *selected = &writer->selected[table[i]]; uint32_t xor_offset = selected->xor_offset; uint32_t xor_row; @@ -753,7 +765,7 @@ static void write_lookup_table(struct hashfile *f, xor_row = 0xffffffff; } - hashwrite_be32(f, commit_positions[table[i]]); + hashwrite_be32(f, writer->selected[table[i]].commit_pos); hashwrite_be64(f, (uint64_t)offsets[table[i]]); hashwrite_be32(f, xor_row); } @@ -775,12 +787,14 @@ static void write_hash_cache(struct hashfile *f, } } -void bitmap_writer_set_checksum(const unsigned char *sha1) +void bitmap_writer_set_checksum(struct bitmap_writer *writer, + const unsigned char *sha1) { - hashcpy(writer.pack_checksum, sha1); + hashcpy(writer->pack_checksum, sha1); } -void bitmap_writer_finish(struct pack_idx_entry **index, +void bitmap_writer_finish(struct bitmap_writer *writer, + struct pack_idx_entry **index, uint32_t index_nr, const char *filename, uint16_t options) @@ -789,7 +803,6 @@ void bitmap_writer_finish(struct pack_idx_entry **index, static uint16_t flags = BITMAP_OPT_FULL_DAG; struct strbuf tmp_file = STRBUF_INIT; struct hashfile *f; - uint32_t *commit_positions = NULL; off_t *offsets = NULL; uint32_t i; @@ -802,34 +815,32 @@ void bitmap_writer_finish(struct pack_idx_entry **index, memcpy(header.magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE)); header.version = htons(default_version); header.options = htons(flags | options); - header.entry_count = htonl(writer.selected_nr); - hashcpy(header.checksum, writer.pack_checksum); + header.entry_count = htonl(writer->selected_nr); + hashcpy(header.checksum, writer->pack_checksum); hashwrite(f, &header, sizeof(header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz); - dump_bitmap(f, writer.commits); - dump_bitmap(f, writer.trees); - dump_bitmap(f, writer.blobs); - dump_bitmap(f, writer.tags); + dump_bitmap(f, writer->commits); + dump_bitmap(f, writer->trees); + dump_bitmap(f, writer->blobs); + dump_bitmap(f, writer->tags); if (options & BITMAP_OPT_LOOKUP_TABLE) CALLOC_ARRAY(offsets, index_nr); - ALLOC_ARRAY(commit_positions, writer.selected_nr); - - for (i = 0; i < writer.selected_nr; i++) { - struct bitmapped_commit *stored = &writer.selected[i]; - int commit_pos = oid_pos(&stored->commit->object.oid, index, index_nr, oid_access); + for (i = 0; i < writer->selected_nr; i++) { + struct bitmapped_commit *stored = &writer->selected[i]; + int commit_pos = oid_pos(&stored->commit->object.oid, index, + index_nr, oid_access); if (commit_pos < 0) BUG(_("trying to write commit not in index")); - - commit_positions[i] = commit_pos; + stored->commit_pos = commit_pos; } - write_selected_commits_v1(f, commit_positions, offsets); + write_selected_commits_v1(writer, f, offsets); if (options & BITMAP_OPT_LOOKUP_TABLE) - write_lookup_table(f, commit_positions, offsets); + write_lookup_table(writer, f, offsets); if (options & BITMAP_OPT_HASH_CACHE) write_hash_cache(f, index, index_nr); @@ -844,6 +855,5 @@ void bitmap_writer_finish(struct pack_idx_entry **index, die_errno("unable to rename temporary bitmap file to '%s'", filename); strbuf_release(&tmp_file); - free(commit_positions); free(offsets); } diff --git a/pack-bitmap.c b/pack-bitmap.c index 35c5ef9d3c..fe8e8a51d3 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -309,9 +309,8 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) char *midx_bitmap_filename(struct multi_pack_index *midx) { struct strbuf buf = STRBUF_INIT; - - get_midx_filename(&buf, midx->object_dir); - strbuf_addf(&buf, "-%s.bitmap", hash_to_hex(get_midx_checksum(midx))); + get_midx_filename_ext(&buf, midx->object_dir, get_midx_checksum(midx), + MIDX_EXT_BITMAP); return strbuf_detach(&buf, NULL); } diff --git a/pack-bitmap.h b/pack-bitmap.h index c7dea13217..3091095f33 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -97,9 +97,29 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_i off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *); -void bitmap_writer_show_progress(int show); -void bitmap_writer_set_checksum(const unsigned char *sha1); -void bitmap_writer_build_type_index(struct packing_data *to_pack, +struct bitmap_writer { + struct ewah_bitmap *commits; + struct ewah_bitmap *trees; + struct ewah_bitmap *blobs; + struct ewah_bitmap *tags; + + kh_oid_map_t *bitmaps; + struct packing_data *to_pack; + + struct bitmapped_commit *selected; + unsigned int selected_nr, selected_alloc; + + struct progress *progress; + int show_progress; + unsigned char pack_checksum[GIT_MAX_RAWSZ]; +}; + +void bitmap_writer_init(struct bitmap_writer *writer); +void bitmap_writer_show_progress(struct bitmap_writer *writer, int show); +void bitmap_writer_set_checksum(struct bitmap_writer *writer, + const unsigned char *sha1); +void bitmap_writer_build_type_index(struct bitmap_writer *writer, + struct packing_data *to_pack, struct pack_idx_entry **index, uint32_t index_nr); uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git, @@ -109,13 +129,17 @@ int rebuild_bitmap(const uint32_t *reposition, struct bitmap *dest); struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git, struct commit *commit); -void bitmap_writer_select_commits(struct commit **indexed_commits, - unsigned int indexed_commits_nr, int max_bitmaps); -int bitmap_writer_build(struct packing_data *to_pack); -void bitmap_writer_finish(struct pack_idx_entry **index, +void bitmap_writer_select_commits(struct bitmap_writer *writer, + struct commit **indexed_commits, + unsigned int indexed_commits_nr); +int bitmap_writer_build(struct bitmap_writer *writer, + struct packing_data *to_pack); +void bitmap_writer_finish(struct bitmap_writer *writer, + struct pack_idx_entry **index, uint32_t index_nr, const char *filename, uint16_t options); +void bitmap_writer_free(struct bitmap_writer *writer); char *midx_bitmap_filename(struct multi_pack_index *midx); char *pack_bitmap_filename(struct packed_git *p); diff --git a/pack-revindex.c b/pack-revindex.c index a7624d8be8..fc63aa76a2 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -381,7 +381,8 @@ int load_midx_revindex(struct multi_pack_index *m) trace2_data_string("load_midx_revindex", the_repository, "source", "rev"); - get_midx_rev_filename(&revindex_name, m); + get_midx_filename_ext(&revindex_name, m->object_dir, + get_midx_checksum(m), MIDX_EXT_REV); ret = load_revindex_from_disk(revindex_name.buf, m->num_objects, @@ -13,7 +13,7 @@ int pager_use_color = 1; #endif static struct child_process pager_process; -static const char *pager_program; +static char *pager_program; /* Is the value coming back from term_columns() just a guess? */ static int term_columns_guessed; diff --git a/parse-options-cb.c b/parse-options-cb.c index bdc7fae497..d99d688d3c 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -7,6 +7,7 @@ #include "environment.h" #include "gettext.h" #include "object-name.h" +#include "setup.h" #include "string-list.h" #include "strvec.h" #include "oid-array.h" @@ -29,7 +30,7 @@ int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset) opt->long_name); if (v && v < MINIMUM_ABBREV) v = MINIMUM_ABBREV; - else if (v > the_hash_algo->hexsz) + else if (startup_info->have_repository && v > the_hash_algo->hexsz) v = the_hash_algo->hexsz; } *(int *)(opt->value) = v; @@ -5,7 +5,6 @@ #include "abspath.h" #include "environment.h" #include "gettext.h" -#include "hex.h" #include "repository.h" #include "strbuf.h" #include "string-list.h" @@ -647,58 +646,6 @@ void strbuf_git_common_path(struct strbuf *sb, va_end(args); } -int validate_headref(const char *path) -{ - struct stat st; - char buffer[256]; - const char *refname; - struct object_id oid; - int fd; - ssize_t len; - - if (lstat(path, &st) < 0) - return -1; - - /* Make sure it is a "refs/.." symlink */ - if (S_ISLNK(st.st_mode)) { - len = readlink(path, buffer, sizeof(buffer)-1); - if (len >= 5 && !memcmp("refs/", buffer, 5)) - return 0; - return -1; - } - - /* - * Anything else, just open it and try to see if it is a symbolic ref. - */ - fd = open(path, O_RDONLY); - if (fd < 0) - return -1; - len = read_in_full(fd, buffer, sizeof(buffer)-1); - close(fd); - - if (len < 0) - return -1; - buffer[len] = '\0'; - - /* - * Is it a symbolic ref? - */ - if (skip_prefix(buffer, "ref:", &refname)) { - while (isspace(*refname)) - refname++; - if (starts_with(refname, "refs/")) - return 0; - } - - /* - * Is this a detached HEAD? - */ - if (!get_oid_hex(buffer, &oid)) - return 0; - - return -1; -} - static struct passwd *getpw_str(const char *username, size_t len) { struct passwd *pw; @@ -173,7 +173,6 @@ const char *git_path_fetch_head(struct repository *r); const char *git_path_shallow(struct repository *r); int ends_with_path_components(const char *path, const char *components); -int validate_headref(const char *ref); int calc_shared_perm(int mode); int adjust_shared_perm(const char *path); @@ -62,7 +62,7 @@ static int git_pretty_formats_config(const char *var, const char *value, { struct cmt_fmt_map *commit_format = NULL; const char *name; - const char *fmt; + char *fmt; int i; if (!skip_prefix(var, "pretty.", &name)) @@ -93,13 +93,17 @@ static int git_pretty_formats_config(const char *var, const char *value, if (git_config_string(&fmt, var, value)) return -1; - if (skip_prefix(fmt, "format:", &fmt)) + if (skip_prefix(fmt, "format:", &commit_format->user_format)) { commit_format->is_tformat = 0; - else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%')) + } else if (skip_prefix(fmt, "tformat:", &commit_format->user_format)) { commit_format->is_tformat = 1; - else + } else if (strchr(fmt, '%')) { + commit_format->is_tformat = 1; + commit_format->user_format = fmt; + } else { commit_format->is_alias = 1; - commit_format->user_format = fmt; + commit_format->user_format = fmt; + } return 0; } diff --git a/promisor-remote.c b/promisor-remote.c index b414922c44..2ca7c2ae48 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -23,6 +23,7 @@ static int fetch_objects(struct repository *repo, struct child_process child = CHILD_PROCESS_INIT; int i; FILE *child_in; + int quiet; if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) { static int warning_shown; @@ -41,6 +42,8 @@ static int fetch_objects(struct repository *repo, "fetch", remote_name, "--no-tags", "--no-write-fetch-head", "--recurse-submodules=no", "--filter=blob:none", "--stdin", NULL); + if (!git_config_get_bool("promisor.quiet", &quiet) && quiet) + strvec_push(&child.args, "--quiet"); if (start_command(&child)) die(_("promisor-remote: unable to fork off fetch subprocess")); child_in = xfdopen(child.in, "w"); diff --git a/promisor-remote.h b/promisor-remote.h index 2cb9eda9ea..88cb599c39 100644 --- a/promisor-remote.h +++ b/promisor-remote.h @@ -13,7 +13,7 @@ struct object_id; */ struct promisor_remote { struct promisor_remote *next; - const char *partial_clone_filter; + char *partial_clone_filter; const char name[FLEX_ARRAY]; }; diff --git a/range-diff.h b/range-diff.h index 04ffe217be..2f69f6a434 100644 --- a/range-diff.h +++ b/range-diff.h @@ -6,6 +6,12 @@ #define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60 +/* + * A much higher value than the default, when we KNOW we are comparing + * the same series (e.g., used when format-patch calls range-diff). + */ +#define CREATION_FACTOR_FOR_THE_SAME_SERIES 999 + struct range_diff_options { int creation_factor; unsigned dual_color:1; diff --git a/refs/files-backend.c b/refs/files-backend.c index e663781199..4519b46171 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -831,8 +831,10 @@ retry: */ if (refs_verify_refname_available( refs->packed_ref_store, refname, - extras, NULL, err)) + extras, NULL, err)) { + ret = TRANSACTION_NAME_CONFLICT; goto error_return; + } } ret = 0; diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index da6b3162f3..9886fc67a4 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1,6 +1,7 @@ #include "../git-compat-util.h" #include "../abspath.h" #include "../chdir-notify.h" +#include "../config.h" #include "../dir.h" #include "../environment.h" #include "../gettext.h" @@ -16,7 +17,6 @@ #include "../reftable/reftable-record.h" #include "../reftable/reftable-error.h" #include "../reftable/reftable-iterator.h" -#include "../reftable/reftable-merged.h" #include "../setup.h" #include "../strmap.h" #include "parse.h" @@ -130,7 +130,7 @@ static struct reftable_stack *stack_for(struct reftable_ref_store *store, store->base.repo->commondir, wtname_buf.buf); store->err = reftable_new_stack(&stack, wt_dir.buf, - store->write_options); + &store->write_options); assert(store->err != REFTABLE_API_ERROR); strmap_put(&store->worktree_stacks, wtname_buf.buf, stack); } @@ -229,6 +229,34 @@ done: return ret; } +static int reftable_be_config(const char *var, const char *value, + const struct config_context *ctx, + void *_opts) +{ + struct reftable_write_options *opts = _opts; + + 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; + } 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; + } else if (!strcmp(var, "reftable.indexobjects")) { + 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; + } + + return 0; +} + static struct ref_store *reftable_be_init(struct repository *repo, const char *gitdir, unsigned int store_flags) @@ -244,12 +272,24 @@ static struct ref_store *reftable_be_init(struct repository *repo, base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable); strmap_init(&refs->worktree_stacks); refs->store_flags = store_flags; - refs->write_options.block_size = 4096; + refs->write_options.hash_id = repo->hash_algo->format_id; refs->write_options.default_permissions = calc_shared_perm(0666 & ~mask); refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); + git_config(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. * This stack contains both the shared and the main worktree refs. @@ -264,7 +304,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, } strbuf_addstr(&path, "/reftable"); refs->err = reftable_new_stack(&refs->main_stack, path.buf, - refs->write_options); + &refs->write_options); if (refs->err) goto done; @@ -281,7 +321,7 @@ static struct ref_store *reftable_be_init(struct repository *repo, strbuf_addf(&path, "%s/reftable", gitdir); refs->err = reftable_new_stack(&refs->worktree_stack, path.buf, - refs->write_options); + &refs->write_options); if (refs->err) goto done; } @@ -533,7 +573,6 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_ const char *prefix, int flags) { - struct reftable_merged_table *merged_table; struct reftable_ref_iterator *iter; int ret; @@ -553,9 +592,8 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_ if (ret) goto done; - merged_table = reftable_stack_merged_table(stack); - - ret = reftable_merged_table_seek_ref(merged_table, &iter->iter, prefix); + reftable_stack_init_ref_iterator(stack, &iter->iter); + ret = reftable_iterator_seek_ref(&iter->iter, prefix); if (ret) goto done; @@ -1083,8 +1121,6 @@ static int transaction_update_cmp(const void *a, const void *b) static int write_transaction_table(struct reftable_writer *writer, void *cb_data) { struct write_transaction_table_arg *arg = cb_data; - struct reftable_merged_table *mt = - reftable_stack_merged_table(arg->stack); uint64_t ts = reftable_stack_next_update_index(arg->stack); struct reftable_log_record *logs = NULL; struct ident_split committer_ident = {0}; @@ -1121,6 +1157,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; + reftable_stack_init_log_iterator(arg->stack, &it); + /* * When deleting refs we also delete all reflog entries * with them. While it is not strictly required to @@ -1130,7 +1168,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data * Unfortunately, we have no better way than to delete * all reflog entries one by one. */ - ret = reftable_merged_table_seek_log(mt, &it, u->refname); + ret = reftable_iterator_seek_log(&it, u->refname); while (ret == 0) { struct reftable_log_record *tombstone; @@ -1349,7 +1387,6 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) { struct write_copy_arg *arg = cb_data; uint64_t deletion_ts, creation_ts; - struct reftable_merged_table *mt = reftable_stack_merged_table(arg->stack); struct reftable_ref_record old_ref = {0}, refs[2] = {0}; struct reftable_log_record old_log = {0}, *logs = NULL; struct reftable_iterator it = {0}; @@ -1483,7 +1520,8 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) * copy over all log entries from the old reflog. Last but not least, * when renaming we also have to delete all the old reflog entries. */ - ret = reftable_merged_table_seek_log(mt, &it, arg->oldname); + reftable_stack_init_log_iterator(arg->stack, &it); + ret = reftable_iterator_seek_log(&it, arg->oldname); if (ret < 0) goto done; @@ -1689,7 +1727,6 @@ static struct ref_iterator_vtable reftable_reflog_iterator_vtable = { static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftable_ref_store *refs, struct reftable_stack *stack) { - struct reftable_merged_table *merged_table; struct reftable_reflog_iterator *iter; int ret; @@ -1706,9 +1743,8 @@ static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftabl if (ret < 0) goto done; - merged_table = reftable_stack_merged_table(stack); - - ret = reftable_merged_table_seek_log(merged_table, &iter->iter, ""); + reftable_stack_init_log_iterator(stack, &iter->iter); + ret = reftable_iterator_seek_log(&iter->iter, ""); if (ret < 0) goto done; @@ -1766,7 +1802,6 @@ static int reftable_be_for_each_reflog_ent_reverse(struct ref_store *ref_store, struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent_reverse"); struct reftable_stack *stack = stack_for(refs, refname, &refname); - struct reftable_merged_table *mt = NULL; struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; int ret; @@ -1774,8 +1809,8 @@ static int reftable_be_for_each_reflog_ent_reverse(struct ref_store *ref_store, if (refs->err < 0) return refs->err; - mt = reftable_stack_merged_table(stack); - ret = reftable_merged_table_seek_log(mt, &it, refname); + reftable_stack_init_log_iterator(stack, &it); + ret = reftable_iterator_seek_log(&it, refname); while (!ret) { ret = reftable_iterator_next_log(&it, &log); if (ret < 0) @@ -1803,7 +1838,6 @@ static int reftable_be_for_each_reflog_ent(struct ref_store *ref_store, struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent"); struct reftable_stack *stack = stack_for(refs, refname, &refname); - struct reftable_merged_table *mt = NULL; struct reftable_log_record *logs = NULL; struct reftable_iterator it = {0}; size_t logs_alloc = 0, logs_nr = 0, i; @@ -1812,8 +1846,8 @@ static int reftable_be_for_each_reflog_ent(struct ref_store *ref_store, if (refs->err < 0) return refs->err; - mt = reftable_stack_merged_table(stack); - ret = reftable_merged_table_seek_log(mt, &it, refname); + reftable_stack_init_log_iterator(stack, &it); + ret = reftable_iterator_seek_log(&it, refname); while (!ret) { struct reftable_log_record log = {0}; @@ -1850,7 +1884,6 @@ static int reftable_be_reflog_exists(struct ref_store *ref_store, struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "reflog_exists"); struct reftable_stack *stack = stack_for(refs, refname, &refname); - struct reftable_merged_table *mt = reftable_stack_merged_table(stack); struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; int ret; @@ -1863,7 +1896,8 @@ static int reftable_be_reflog_exists(struct ref_store *ref_store, if (ret < 0) goto done; - ret = reftable_merged_table_seek_log(mt, &it, refname); + reftable_stack_init_log_iterator(stack, &it); + ret = reftable_iterator_seek_log(&it, refname); if (ret < 0) goto done; @@ -1961,8 +1995,6 @@ struct write_reflog_delete_arg { static int write_reflog_delete_table(struct reftable_writer *writer, void *cb_data) { struct write_reflog_delete_arg *arg = cb_data; - struct reftable_merged_table *mt = - reftable_stack_merged_table(arg->stack); struct reftable_log_record log = {0}, tombstone = {0}; struct reftable_iterator it = {0}; uint64_t ts = reftable_stack_next_update_index(arg->stack); @@ -1970,12 +2002,14 @@ static int write_reflog_delete_table(struct reftable_writer *writer, void *cb_da reftable_writer_set_limits(writer, ts, ts); + reftable_stack_init_log_iterator(arg->stack, &it); + /* * In order to delete a table we need to delete all reflog entries one * by one. This is inefficient, but the reftable format does not have a * better marker right now. */ - ret = reftable_merged_table_seek_log(mt, &it, arg->refname); + ret = reftable_iterator_seek_log(&it, arg->refname); while (ret == 0) { ret = reftable_iterator_next_log(&it, &log); if (ret < 0) @@ -2112,7 +2146,6 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "reflog_expire"); struct reftable_stack *stack = stack_for(refs, refname, &refname); - struct reftable_merged_table *mt = reftable_stack_merged_table(stack); struct reftable_log_record *logs = NULL; struct reftable_log_record *rewritten = NULL; struct reftable_ref_record ref_record = {0}; @@ -2131,7 +2164,9 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, if (ret < 0) goto done; - ret = reftable_merged_table_seek_log(mt, &it, refname); + reftable_stack_init_log_iterator(stack, &it); + + ret = reftable_iterator_seek_log(&it, refname); if (ret < 0) goto done; diff --git a/reftable/basics_test.c b/reftable/basics_test.c deleted file mode 100644 index 997c4d9e01..0000000000 --- a/reftable/basics_test.c +++ /dev/null @@ -1,105 +0,0 @@ -/* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ - -#include "system.h" - -#include "basics.h" -#include "test_framework.h" -#include "reftable-tests.h" - -struct integer_needle_lesseq_args { - int needle; - int *haystack; -}; - -static int integer_needle_lesseq(size_t i, void *_args) -{ - struct integer_needle_lesseq_args *args = _args; - return args->needle <= args->haystack[i]; -} - -static void test_binsearch(void) -{ - int haystack[] = { 2, 4, 6, 8, 10 }; - struct { - int needle; - size_t expected_idx; - } testcases[] = { - {-9000, 0}, - {-1, 0}, - {0, 0}, - {2, 0}, - {3, 1}, - {4, 1}, - {7, 3}, - {9, 4}, - {10, 4}, - {11, 5}, - {9000, 5}, - }; - size_t i = 0; - - for (i = 0; i < ARRAY_SIZE(testcases); i++) { - struct integer_needle_lesseq_args args = { - .haystack = haystack, - .needle = testcases[i].needle, - }; - size_t idx; - - idx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args); - EXPECT(idx == testcases[i].expected_idx); - } -} - -static void test_names_length(void) -{ - char *a[] = { "a", "b", NULL }; - EXPECT(names_length(a) == 2); -} - -static void test_parse_names_normal(void) -{ - char in[] = "a\nb\n"; - char **out = NULL; - parse_names(in, strlen(in), &out); - EXPECT(!strcmp(out[0], "a")); - EXPECT(!strcmp(out[1], "b")); - EXPECT(!out[2]); - free_names(out); -} - -static void test_parse_names_drop_empty(void) -{ - char in[] = "a\n\n"; - char **out = NULL; - parse_names(in, strlen(in), &out); - EXPECT(!strcmp(out[0], "a")); - EXPECT(!out[1]); - free_names(out); -} - -static void test_common_prefix(void) -{ - struct strbuf s1 = STRBUF_INIT; - struct strbuf s2 = STRBUF_INIT; - strbuf_addstr(&s1, "abcdef"); - strbuf_addstr(&s2, "abc"); - EXPECT(common_prefix_size(&s1, &s2) == 3); - strbuf_release(&s1); - strbuf_release(&s2); -} - -int basics_test_main(int argc, const char *argv[]) -{ - RUN_TEST(test_common_prefix); - RUN_TEST(test_parse_names_normal); - RUN_TEST(test_parse_names_drop_empty); - RUN_TEST(test_binsearch); - RUN_TEST(test_names_length); - return 0; -} diff --git a/reftable/block.c b/reftable/block.c index 5942cb4053..00030eee06 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -326,9 +326,9 @@ int block_reader_first_key(const struct block_reader *br, struct strbuf *key) return 0; } -static uint32_t block_reader_restart_offset(const struct block_reader *br, int i) +static uint32_t block_reader_restart_offset(const struct block_reader *br, size_t idx) { - return get_be24(br->restart_bytes + 3 * i); + return get_be24(br->restart_bytes + 3 * idx); } void block_iter_seek_start(struct block_iter *it, const struct block_reader *br) diff --git a/reftable/block.h b/reftable/block.h index e91f3d2790..1c8f25ee6e 100644 --- a/reftable/block.h +++ b/reftable/block.h @@ -29,7 +29,7 @@ struct block_writer { uint32_t header_off; /* How often to restart keys. */ - int restart_interval; + uint16_t restart_interval; int hash_size; /* Offset of next uint8_t to write. */ diff --git a/reftable/constants.h b/reftable/constants.h index 5eee72c4c1..f6beb843eb 100644 --- a/reftable/constants.h +++ b/reftable/constants.h @@ -17,5 +17,6 @@ https://developers.google.com/open-source/licenses/bsd #define MAX_RESTARTS ((1 << 16) - 1) #define DEFAULT_BLOCK_SIZE 4096 +#define DEFAULT_GEOMETRIC_FACTOR 2 #endif diff --git a/reftable/dump.c b/reftable/dump.c index 26e0393c7d..41abbb8ecf 100644 --- a/reftable/dump.c +++ b/reftable/dump.c @@ -27,9 +27,9 @@ https://developers.google.com/open-source/licenses/bsd static int compact_stack(const char *stackdir) { struct reftable_stack *stack = NULL; - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; - int err = reftable_new_stack(&stack, stackdir, cfg); + int err = reftable_new_stack(&stack, stackdir, &opts); if (err < 0) goto done; @@ -48,6 +48,7 @@ static void print_help(void) printf("usage: dump [-cst] arg\n\n" "options: \n" " -c compact\n" + " -b dump blocks\n" " -t dump table\n" " -s dump stack\n" " -6 sha256 hash format\n" @@ -58,6 +59,7 @@ static void print_help(void) int reftable_dump_main(int argc, char *const *argv) { int err = 0; + int opt_dump_blocks = 0; int opt_dump_table = 0; int opt_dump_stack = 0; int opt_compact = 0; @@ -67,6 +69,8 @@ int reftable_dump_main(int argc, char *const *argv) for (; argc > 1; argv++, argc--) if (*argv[1] != '-') break; + else if (!strcmp("-b", argv[1])) + opt_dump_blocks = 1; else if (!strcmp("-t", argv[1])) opt_dump_table = 1; else if (!strcmp("-6", argv[1])) @@ -88,7 +92,9 @@ int reftable_dump_main(int argc, char *const *argv) arg = argv[1]; - if (opt_dump_table) { + if (opt_dump_blocks) { + err = reftable_reader_print_blocks(arg); + } else if (opt_dump_table) { err = reftable_reader_print_file(arg); } else if (opt_dump_stack) { err = reftable_stack_print_directory(arg, opt_hash_id); diff --git a/reftable/generic.c b/reftable/generic.c index b9f1c7c18a..28ae26145e 100644 --- a/reftable/generic.c +++ b/reftable/generic.c @@ -12,32 +12,66 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-iterator.h" #include "reftable-generic.h" -int reftable_table_seek_ref(struct reftable_table *tab, - struct reftable_iterator *it, const char *name) +void table_init_iter(struct reftable_table *tab, + struct reftable_iterator *it, + uint8_t typ) { - struct reftable_record rec = { .type = BLOCK_TYPE_REF, - .u.ref = { - .refname = (char *)name, - } }; - return tab->ops->seek_record(tab->table_arg, it, &rec); + + tab->ops->init_iter(tab->table_arg, it, typ); +} + +void reftable_table_init_ref_iter(struct reftable_table *tab, + struct reftable_iterator *it) +{ + table_init_iter(tab, it, BLOCK_TYPE_REF); +} + +void reftable_table_init_log_iter(struct reftable_table *tab, + struct reftable_iterator *it) +{ + table_init_iter(tab, it, BLOCK_TYPE_LOG); +} + +int reftable_iterator_seek_ref(struct reftable_iterator *it, + const char *name) +{ + struct reftable_record want = { + .type = BLOCK_TYPE_REF, + .u.ref = { + .refname = (char *)name, + }, + }; + return it->ops->seek(it->iter_arg, &want); +} + +int reftable_iterator_seek_log_at(struct reftable_iterator *it, + const char *name, uint64_t update_index) +{ + struct reftable_record want = { + .type = BLOCK_TYPE_LOG, + .u.log = { + .refname = (char *)name, + .update_index = update_index, + }, + }; + return it->ops->seek(it->iter_arg, &want); } -int reftable_table_seek_log(struct reftable_table *tab, - struct reftable_iterator *it, const char *name) +int reftable_iterator_seek_log(struct reftable_iterator *it, + const char *name) { - struct reftable_record rec = { .type = BLOCK_TYPE_LOG, - .u.log = { - .refname = (char *)name, - .update_index = ~((uint64_t)0), - } }; - return tab->ops->seek_record(tab->table_arg, it, &rec); + return reftable_iterator_seek_log_at(it, name, ~((uint64_t) 0)); } int reftable_table_read_ref(struct reftable_table *tab, const char *name, struct reftable_ref_record *ref) { struct reftable_iterator it = { NULL }; - int err = reftable_table_seek_ref(tab, &it, name); + int err; + + reftable_table_init_ref_iter(tab, &it); + + err = reftable_iterator_seek_ref(&it, name); if (err) goto done; @@ -62,10 +96,13 @@ int reftable_table_print(struct reftable_table *tab) { struct reftable_ref_record ref = { NULL }; struct reftable_log_record log = { NULL }; uint32_t hash_id = reftable_table_hash_id(tab); - int err = reftable_table_seek_ref(tab, &it, ""); - if (err < 0) { + int err; + + reftable_table_init_ref_iter(tab, &it); + + err = reftable_iterator_seek_ref(&it, ""); + if (err < 0) return err; - } while (1) { err = reftable_iterator_next_ref(&it, &ref); @@ -80,10 +117,12 @@ int reftable_table_print(struct reftable_table *tab) { reftable_iterator_destroy(&it); reftable_ref_record_release(&ref); - err = reftable_table_seek_log(tab, &it, ""); - if (err < 0) { + reftable_table_init_log_iter(tab, &it); + + err = reftable_iterator_seek_log(&it, ""); + if (err < 0) return err; - } + while (1) { err = reftable_iterator_next_log(&it, &log); if (err > 0) { @@ -152,11 +191,21 @@ int reftable_iterator_next_log(struct reftable_iterator *it, return err; } +int iterator_seek(struct reftable_iterator *it, struct reftable_record *want) +{ + return it->ops->seek(it->iter_arg, want); +} + int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) { return it->ops->next(it->iter_arg, rec); } +static int empty_iterator_seek(void *arg, struct reftable_record *want) +{ + return 0; +} + static int empty_iterator_next(void *arg, struct reftable_record *rec) { return 1; @@ -167,6 +216,7 @@ static void empty_iterator_close(void *arg) } static struct reftable_iterator_vtable empty_vtable = { + .seek = &empty_iterator_seek, .next = &empty_iterator_next, .close = &empty_iterator_close, }; diff --git a/reftable/generic.h b/reftable/generic.h index 98886a0640..8341fa570e 100644 --- a/reftable/generic.h +++ b/reftable/generic.h @@ -14,19 +14,24 @@ https://developers.google.com/open-source/licenses/bsd /* generic interface to reftables */ struct reftable_table_vtable { - int (*seek_record)(void *tab, struct reftable_iterator *it, - struct reftable_record *); + void (*init_iter)(void *tab, struct reftable_iterator *it, uint8_t typ); uint32_t (*hash_id)(void *tab); uint64_t (*min_update_index)(void *tab); uint64_t (*max_update_index)(void *tab); }; +void table_init_iter(struct reftable_table *tab, + struct reftable_iterator *it, + uint8_t typ); + struct reftable_iterator_vtable { + int (*seek)(void *iter_arg, struct reftable_record *want); int (*next)(void *iter_arg, struct reftable_record *rec); void (*close)(void *iter_arg); }; void iterator_set_empty(struct reftable_iterator *it); +int iterator_seek(struct reftable_iterator *it, struct reftable_record *want); int iterator_next(struct reftable_iterator *it, struct reftable_record *rec); #endif diff --git a/reftable/iter.c b/reftable/iter.c index aa9ac199b1..fddea31e51 100644 --- a/reftable/iter.c +++ b/reftable/iter.c @@ -23,6 +23,13 @@ static void filtering_ref_iterator_close(void *iter_arg) reftable_iterator_destroy(&fri->it); } +static int filtering_ref_iterator_seek(void *iter_arg, + struct reftable_record *want) +{ + struct filtering_ref_iterator *fri = iter_arg; + return iterator_seek(&fri->it, want); +} + static int filtering_ref_iterator_next(void *iter_arg, struct reftable_record *rec) { @@ -38,11 +45,11 @@ static int filtering_ref_iterator_next(void *iter_arg, if (fri->double_check) { struct reftable_iterator it = { NULL }; - err = reftable_table_seek_ref(&fri->tab, &it, - ref->refname); - if (err == 0) { + reftable_table_init_ref_iter(&fri->tab, &it); + + err = reftable_iterator_seek_ref(&it, ref->refname); + if (err == 0) err = reftable_iterator_next_ref(&it, ref); - } reftable_iterator_destroy(&it); @@ -73,6 +80,7 @@ static int filtering_ref_iterator_next(void *iter_arg, } static struct reftable_iterator_vtable filtering_ref_iterator_vtable = { + .seek = &filtering_ref_iterator_seek, .next = &filtering_ref_iterator_next, .close = &filtering_ref_iterator_close, }; @@ -119,6 +127,12 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) return 0; } +static int indexed_table_ref_iter_seek(void *p, struct reftable_record *want) +{ + BUG("seeking indexed table is not supported"); + return -1; +} + static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec) { struct indexed_table_ref_iter *it = p; @@ -175,6 +189,7 @@ int new_indexed_table_ref_iter(struct indexed_table_ref_iter **dest, } static struct reftable_iterator_vtable indexed_table_ref_iter_vtable = { + .seek = &indexed_table_ref_iter_seek, .next = &indexed_table_ref_iter_next, .close = &indexed_table_ref_iter_close, }; diff --git a/reftable/merged.c b/reftable/merged.c index 804fdc0de0..6adce44f4b 100644 --- a/reftable/merged.c +++ b/reftable/merged.c @@ -25,34 +25,25 @@ struct merged_subiter { struct merged_iter { struct merged_subiter *subiters; struct merged_iter_pqueue pq; - uint32_t hash_id; size_t stack_len; - uint8_t typ; int suppress_deletions; ssize_t advance_index; }; -static int merged_iter_init(struct merged_iter *mi) +static void merged_iter_init(struct merged_iter *mi, + struct reftable_merged_table *mt, + uint8_t typ) { - for (size_t i = 0; i < mi->stack_len; i++) { - struct pq_entry e = { - .index = i, - .rec = &mi->subiters[i].rec, - }; - int err; - - reftable_record_init(&mi->subiters[i].rec, mi->typ); - err = iterator_next(&mi->subiters[i].iter, - &mi->subiters[i].rec); - if (err < 0) - return err; - if (err > 0) - continue; + memset(mi, 0, sizeof(*mi)); + mi->advance_index = -1; + mi->suppress_deletions = mt->suppress_deletions; - merged_iter_pqueue_add(&mi->pq, &e); + REFTABLE_CALLOC_ARRAY(mi->subiters, mt->stack_len); + for (size_t i = 0; i < mt->stack_len; i++) { + reftable_record_init(&mi->subiters[i].rec, typ); + table_init_iter(&mt->stack[i], &mi->subiters[i].iter, typ); } - - return 0; + mi->stack_len = mt->stack_len; } static void merged_iter_close(void *p) @@ -83,6 +74,27 @@ static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx) return 0; } +static int merged_iter_seek(struct merged_iter *mi, struct reftable_record *want) +{ + int err; + + mi->advance_index = -1; + + for (size_t i = 0; i < mi->stack_len; i++) { + err = iterator_seek(&mi->subiters[i].iter, want); + if (err < 0) + return err; + if (err > 0) + continue; + + err = merged_iter_advance_subiter(mi, i); + if (err < 0) + return err; + } + + return 0; +} + static int merged_iter_next_entry(struct merged_iter *mi, struct reftable_record *rec) { @@ -148,6 +160,11 @@ static int merged_iter_next_entry(struct merged_iter *mi, return 0; } +static int merged_iter_seek_void(void *it, struct reftable_record *want) +{ + return merged_iter_seek(it, want); +} + static int merged_iter_next_void(void *p, struct reftable_record *rec) { struct merged_iter *mi = p; @@ -162,6 +179,7 @@ static int merged_iter_next_void(void *p, struct reftable_record *rec) } static struct reftable_iterator_vtable merged_iter_vtable = { + .seek = merged_iter_seek_void, .next = &merged_iter_next_void, .close = &merged_iter_close, }; @@ -227,81 +245,13 @@ reftable_merged_table_min_update_index(struct reftable_merged_table *mt) return mt->min; } -static int reftable_table_seek_record(struct reftable_table *tab, - struct reftable_iterator *it, - struct reftable_record *rec) -{ - return tab->ops->seek_record(tab->table_arg, it, rec); -} - -static int merged_table_seek_record(struct reftable_merged_table *mt, - struct reftable_iterator *it, - struct reftable_record *rec) -{ - struct merged_iter merged = { - .typ = reftable_record_type(rec), - .hash_id = mt->hash_id, - .suppress_deletions = mt->suppress_deletions, - .advance_index = -1, - }; - struct merged_iter *p; - int err; - - REFTABLE_CALLOC_ARRAY(merged.subiters, mt->stack_len); - for (size_t i = 0; i < mt->stack_len; i++) { - err = reftable_table_seek_record(&mt->stack[i], - &merged.subiters[merged.stack_len].iter, rec); - if (err < 0) - goto out; - if (!err) - merged.stack_len++; - } - - err = merged_iter_init(&merged); - if (err < 0) - goto out; - - p = reftable_malloc(sizeof(struct merged_iter)); - *p = merged; - iterator_from_merged_iter(it, p); - -out: - if (err < 0) - merged_iter_close(&merged); - return err; -} - -int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name) -{ - struct reftable_record rec = { - .type = BLOCK_TYPE_REF, - .u.ref = { - .refname = (char *)name, - }, - }; - return merged_table_seek_record(mt, it, &rec); -} - -int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name, uint64_t update_index) -{ - struct reftable_record rec = { .type = BLOCK_TYPE_LOG, - .u.log = { - .refname = (char *)name, - .update_index = update_index, - } }; - return merged_table_seek_record(mt, it, &rec); -} - -int reftable_merged_table_seek_log(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name) +void merged_table_init_iter(struct reftable_merged_table *mt, + struct reftable_iterator *it, + uint8_t typ) { - uint64_t max = ~((uint64_t)0); - return reftable_merged_table_seek_log_at(mt, it, name, max); + struct merged_iter *mi = reftable_malloc(sizeof(*mi)); + merged_iter_init(mi, mt, typ); + iterator_from_merged_iter(it, mi); } uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt) @@ -309,11 +259,11 @@ uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt) return mt->hash_id; } -static int reftable_merged_table_seek_void(void *tab, - struct reftable_iterator *it, - struct reftable_record *rec) +static void reftable_merged_table_init_iter_void(void *tab, + struct reftable_iterator *it, + uint8_t typ) { - return merged_table_seek_record(tab, it, rec); + merged_table_init_iter(tab, it, typ); } static uint32_t reftable_merged_table_hash_id_void(void *tab) @@ -332,7 +282,7 @@ static uint64_t reftable_merged_table_max_update_index_void(void *tab) } static struct reftable_table_vtable merged_table_vtable = { - .seek_record = reftable_merged_table_seek_void, + .init_iter = reftable_merged_table_init_iter_void, .hash_id = reftable_merged_table_hash_id_void, .min_update_index = reftable_merged_table_min_update_index_void, .max_update_index = reftable_merged_table_max_update_index_void, diff --git a/reftable/merged.h b/reftable/merged.h index 9db45c3196..2efe571da6 100644 --- a/reftable/merged.h +++ b/reftable/merged.h @@ -24,4 +24,10 @@ struct reftable_merged_table { uint64_t max; }; +struct reftable_iterator; + +void merged_table_init_iter(struct reftable_merged_table *mt, + struct reftable_iterator *it, + uint8_t typ); + #endif diff --git a/reftable/merged_test.c b/reftable/merged_test.c index 530fc82d1c..33a17efcde 100644 --- a/reftable/merged_test.c +++ b/reftable/merged_test.c @@ -12,6 +12,7 @@ https://developers.google.com/open-source/licenses/bsd #include "basics.h" #include "blocksource.h" +#include "constants.h" #include "reader.h" #include "record.h" #include "test_framework.h" @@ -145,7 +146,10 @@ static void test_merged_between(void) int i; struct reftable_ref_record ref = { NULL }; struct reftable_iterator it = { NULL }; - int err = reftable_merged_table_seek_ref(mt, &it, "a"); + int err; + + merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = reftable_iterator_seek_ref(&it, "a"); EXPECT_ERR(err); err = reftable_iterator_next_ref(&it, &ref); @@ -217,14 +221,15 @@ static void test_merged(void) struct reftable_reader **readers = NULL; struct reftable_merged_table *mt = merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3); - struct reftable_iterator it = { NULL }; - int err = reftable_merged_table_seek_ref(mt, &it, "a"); + int err; struct reftable_ref_record *out = NULL; size_t len = 0; size_t cap = 0; int i = 0; + merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = reftable_iterator_seek_ref(&it, "a"); EXPECT_ERR(err); EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID); EXPECT(reftable_merged_table_min_update_index(mt) == 1); @@ -348,14 +353,15 @@ static void test_merged_logs(void) struct reftable_reader **readers = NULL; struct reftable_merged_table *mt = merged_table_from_log_records( logs, &bs, &readers, sizes, bufs, 3); - struct reftable_iterator it = { NULL }; - int err = reftable_merged_table_seek_log(mt, &it, "a"); + int err; struct reftable_log_record *out = NULL; size_t len = 0; size_t cap = 0; int i = 0; + merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = reftable_iterator_seek_log(&it, "a"); EXPECT_ERR(err); EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID); EXPECT(reftable_merged_table_min_update_index(mt) == 1); @@ -377,7 +383,8 @@ static void test_merged_logs(void) GIT_SHA1_RAWSZ)); } - err = reftable_merged_table_seek_log_at(mt, &it, "a", 2); + merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = reftable_iterator_seek_log_at(&it, "a", 2); EXPECT_ERR(err); reftable_log_record_release(&out[0]); err = reftable_iterator_next_log(&it, &out[0]); diff --git a/reftable/reader.c b/reftable/reader.c index 481dff10d4..29c99e2269 100644 --- a/reftable/reader.c +++ b/reftable/reader.c @@ -224,8 +224,14 @@ struct table_iter { struct block_iter bi; int is_finished; }; -#define TABLE_ITER_INIT { \ - .bi = BLOCK_ITER_INIT \ + +static int table_iter_init(struct table_iter *ti, struct reftable_reader *r) +{ + struct block_iter bi = BLOCK_ITER_INIT; + memset(ti, 0, sizeof(*ti)); + ti->r = r; + ti->bi = bi; + return 0; } static int table_iter_next_in_block(struct table_iter *ti, @@ -363,50 +369,23 @@ static int table_iter_next(struct table_iter *ti, struct reftable_record *rec) } } -static int table_iter_next_void(void *ti, struct reftable_record *rec) -{ - return table_iter_next(ti, rec); -} - -static void table_iter_close_void(void *ti) -{ - table_iter_close(ti); -} - -static struct reftable_iterator_vtable table_iter_vtable = { - .next = &table_iter_next_void, - .close = &table_iter_close_void, -}; - -static void iterator_from_table_iter(struct reftable_iterator *it, - struct table_iter *ti) -{ - assert(!it->ops); - it->iter_arg = ti; - it->ops = &table_iter_vtable; -} - -static int reader_table_iter_at(struct reftable_reader *r, - struct table_iter *ti, uint64_t off, - uint8_t typ) +static int table_iter_seek_to(struct table_iter *ti, uint64_t off, uint8_t typ) { int err; - err = reader_init_block_reader(r, &ti->br, off, typ); + err = reader_init_block_reader(ti->r, &ti->br, off, typ); if (err != 0) return err; - ti->r = r; ti->typ = block_reader_type(&ti->br); ti->block_off = off; block_iter_seek_start(&ti->bi, &ti->br); return 0; } -static int reader_start(struct reftable_reader *r, struct table_iter *ti, - uint8_t typ, int index) +static int table_iter_seek_start(struct table_iter *ti, uint8_t typ, int index) { - struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); + struct reftable_reader_offsets *offs = reader_offsets_for(ti->r, typ); uint64_t off = offs->offset; if (index) { off = offs->index_offset; @@ -416,16 +395,16 @@ static int reader_start(struct reftable_reader *r, struct table_iter *ti, typ = BLOCK_TYPE_INDEX; } - return reader_table_iter_at(r, ti, off, typ); + return table_iter_seek_to(ti, off, typ); } -static int reader_seek_linear(struct table_iter *ti, - struct reftable_record *want) +static int table_iter_seek_linear(struct table_iter *ti, + struct reftable_record *want) { struct strbuf want_key = STRBUF_INIT; struct strbuf got_key = STRBUF_INIT; struct reftable_record rec; - int err = -1; + int err; reftable_record_init(&rec, reftable_record_type(want)); reftable_record_key(want, &want_key); @@ -499,9 +478,8 @@ done: return err; } -static int reader_seek_indexed(struct reftable_reader *r, - struct reftable_iterator *it, - struct reftable_record *rec) +static int table_iter_seek_indexed(struct table_iter *ti, + struct reftable_record *rec) { struct reftable_record want_index = { .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT } @@ -510,15 +488,9 @@ static int reader_seek_indexed(struct reftable_reader *r, .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT }, }; - struct table_iter index_iter = TABLE_ITER_INIT; - struct table_iter empty = TABLE_ITER_INIT; - struct table_iter next = TABLE_ITER_INIT; - int err = 0; + int err; reftable_record_key(rec, &want_index.u.idx.last_key); - err = reader_start(r, &index_iter, reftable_record_type(rec), 1); - if (err < 0) - goto done; /* * The index may consist of multiple levels, where each level may have @@ -526,7 +498,7 @@ static int reader_seek_indexed(struct reftable_reader *r, * highest layer that identifies the relevant index block as well as * the record inside that block that corresponds to our wanted key. */ - err = reader_seek_linear(&index_iter, &want_index); + err = table_iter_seek_linear(ti, &want_index); if (err < 0) goto done; @@ -552,123 +524,113 @@ static int reader_seek_indexed(struct reftable_reader *r, * all levels of the index only to find out that the key does * not exist. */ - err = table_iter_next(&index_iter, &index_result); + err = table_iter_next(ti, &index_result); if (err != 0) goto done; - err = reader_table_iter_at(r, &next, index_result.u.idx.offset, - 0); + err = table_iter_seek_to(ti, index_result.u.idx.offset, 0); if (err != 0) goto done; - err = block_iter_seek_key(&next.bi, &next.br, &want_index.u.idx.last_key); + err = block_iter_seek_key(&ti->bi, &ti->br, &want_index.u.idx.last_key); if (err < 0) goto done; - if (next.typ == reftable_record_type(rec)) { + if (ti->typ == reftable_record_type(rec)) { err = 0; break; } - if (next.typ != BLOCK_TYPE_INDEX) { + if (ti->typ != BLOCK_TYPE_INDEX) { err = REFTABLE_FORMAT_ERROR; - break; + goto done; } - - table_iter_close(&index_iter); - index_iter = next; - next = empty; - } - - if (err == 0) { - struct table_iter *malloced = reftable_calloc(1, sizeof(*malloced)); - *malloced = next; - next = empty; - iterator_from_table_iter(it, malloced); } done: - table_iter_close(&next); - table_iter_close(&index_iter); reftable_record_release(&want_index); reftable_record_release(&index_result); return err; } -static int reader_seek_internal(struct reftable_reader *r, - struct reftable_iterator *it, - struct reftable_record *rec) +static int table_iter_seek(struct table_iter *ti, + struct reftable_record *want) { - struct reftable_reader_offsets *offs = - reader_offsets_for(r, reftable_record_type(rec)); - uint64_t idx = offs->index_offset; - struct table_iter ti = TABLE_ITER_INIT, *p; + uint8_t typ = reftable_record_type(want); + struct reftable_reader_offsets *offs = reader_offsets_for(ti->r, typ); int err; - if (idx > 0) - return reader_seek_indexed(r, it, rec); - - err = reader_start(r, &ti, reftable_record_type(rec), 0); + err = table_iter_seek_start(ti, reftable_record_type(want), + !!offs->index_offset); if (err < 0) goto out; - err = reader_seek_linear(&ti, rec); - if (err < 0) + if (offs->index_offset) + err = table_iter_seek_indexed(ti, want); + else + err = table_iter_seek_linear(ti, want); + if (err) goto out; - REFTABLE_ALLOC_ARRAY(p, 1); - *p = ti; - iterator_from_table_iter(it, p); - out: - if (err) - table_iter_close(&ti); return err; } -static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it, - struct reftable_record *rec) +static int table_iter_seek_void(void *ti, struct reftable_record *want) { - uint8_t typ = reftable_record_type(rec); + return table_iter_seek(ti, want); +} - struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); - if (!offs->is_present) { - iterator_set_empty(it); - return 0; - } +static int table_iter_next_void(void *ti, struct reftable_record *rec) +{ + return table_iter_next(ti, rec); +} - return reader_seek_internal(r, it, rec); +static void table_iter_close_void(void *ti) +{ + table_iter_close(ti); } -int reftable_reader_seek_ref(struct reftable_reader *r, - struct reftable_iterator *it, const char *name) +static struct reftable_iterator_vtable table_iter_vtable = { + .seek = &table_iter_seek_void, + .next = &table_iter_next_void, + .close = &table_iter_close_void, +}; + +static void iterator_from_table_iter(struct reftable_iterator *it, + struct table_iter *ti) { - struct reftable_record rec = { - .type = BLOCK_TYPE_REF, - .u.ref = { - .refname = (char *)name, - }, - }; - return reader_seek(r, it, &rec); + assert(!it->ops); + it->iter_arg = ti; + it->ops = &table_iter_vtable; } -int reftable_reader_seek_log_at(struct reftable_reader *r, - struct reftable_iterator *it, const char *name, - uint64_t update_index) +static void reader_init_iter(struct reftable_reader *r, + struct reftable_iterator *it, + uint8_t typ) { - struct reftable_record rec = { .type = BLOCK_TYPE_LOG, - .u.log = { - .refname = (char *)name, - .update_index = update_index, - } }; - return reader_seek(r, it, &rec); + struct reftable_reader_offsets *offs = reader_offsets_for(r, typ); + + if (offs->is_present) { + struct table_iter *ti; + REFTABLE_ALLOC_ARRAY(ti, 1); + table_iter_init(ti, r); + iterator_from_table_iter(it, ti); + } else { + iterator_set_empty(it); + } } -int reftable_reader_seek_log(struct reftable_reader *r, - struct reftable_iterator *it, const char *name) +void reftable_reader_init_ref_iterator(struct reftable_reader *r, + struct reftable_iterator *it) { - uint64_t max = ~((uint64_t)0); - return reftable_reader_seek_log_at(r, it, name, max); + reader_init_iter(r, it, BLOCK_TYPE_REF); +} + +void reftable_reader_init_log_iterator(struct reftable_reader *r, + struct reftable_iterator *it) +{ + reader_init_iter(r, it, BLOCK_TYPE_LOG); } void reader_close(struct reftable_reader *r) @@ -719,7 +681,8 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r, struct indexed_table_ref_iter *itr = NULL; /* Look through the reverse index. */ - err = reader_seek(r, &oit, &want); + reader_init_iter(r, &oit, BLOCK_TYPE_OBJ); + err = iterator_seek(&oit, &want); if (err != 0) goto done; @@ -754,15 +717,15 @@ static int reftable_reader_refs_for_unindexed(struct reftable_reader *r, struct reftable_iterator *it, uint8_t *oid) { - struct table_iter ti_empty = TABLE_ITER_INIT; - struct table_iter *ti = reftable_calloc(1, sizeof(*ti)); + struct table_iter *ti; struct filtering_ref_iterator *filter = NULL; struct filtering_ref_iterator empty = FILTERING_REF_ITERATOR_INIT; int oid_len = hash_size(r->hash_id); int err; - *ti = ti_empty; - err = reader_start(r, ti, BLOCK_TYPE_REF, 0); + REFTABLE_ALLOC_ARRAY(ti, 1); + table_iter_init(ti, r); + err = table_iter_seek_start(ti, BLOCK_TYPE_REF, 0); if (err < 0) { reftable_free(ti); return err; @@ -800,10 +763,11 @@ uint64_t reftable_reader_min_update_index(struct reftable_reader *r) /* generic table interface. */ -static int reftable_reader_seek_void(void *tab, struct reftable_iterator *it, - struct reftable_record *rec) +static void reftable_reader_init_iter_void(void *tab, + struct reftable_iterator *it, + uint8_t typ) { - return reader_seek(tab, it, rec); + reader_init_iter(tab, it, typ); } static uint32_t reftable_reader_hash_id_void(void *tab) @@ -822,7 +786,7 @@ static uint64_t reftable_reader_max_update_index_void(void *tab) } static struct reftable_table_vtable reader_vtable = { - .seek_record = reftable_reader_seek_void, + .init_iter = reftable_reader_init_iter_void, .hash_id = reftable_reader_hash_id_void, .min_update_index = reftable_reader_min_update_index_void, .max_update_index = reftable_reader_max_update_index_void, @@ -856,3 +820,68 @@ done: reftable_reader_free(r); return err; } + +int reftable_reader_print_blocks(const char *tablename) +{ + struct { + const char *name; + int type; + } sections[] = { + { + .name = "ref", + .type = BLOCK_TYPE_REF, + }, + { + .name = "obj", + .type = BLOCK_TYPE_OBJ, + }, + { + .name = "log", + .type = BLOCK_TYPE_LOG, + }, + }; + struct reftable_block_source src = { 0 }; + struct reftable_reader *r = NULL; + struct table_iter ti = { 0 }; + size_t i; + int err; + + err = reftable_block_source_from_file(&src, tablename); + if (err < 0) + goto done; + + err = reftable_new_reader(&r, &src, tablename); + if (err < 0) + goto done; + + table_iter_init(&ti, r); + + printf("header:\n"); + printf(" block_size: %d\n", r->block_size); + + for (i = 0; i < ARRAY_SIZE(sections); i++) { + err = table_iter_seek_start(&ti, sections[i].type, 0); + if (err < 0) + goto done; + if (err > 0) + continue; + + printf("%s:\n", sections[i].name); + + while (1) { + printf(" - length: %u\n", ti.br.block_len); + printf(" restarts: %u\n", ti.br.restart_count); + + err = table_iter_next_block(&ti); + if (err < 0) + goto done; + if (err > 0) + break; + } + } + +done: + reftable_reader_free(r); + table_iter_close(&ti); + return err; +} diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c index a6dbd214c5..d99543bbd6 100644 --- a/reftable/readwrite_test.c +++ b/reftable/readwrite_test.c @@ -239,7 +239,9 @@ static void test_log_write_read(void) err = init_reader(&rd, &source, "file.log"); EXPECT_ERR(err); - err = reftable_reader_seek_ref(&rd, &it, names[N - 1]); + reftable_reader_init_ref_iterator(&rd, &it); + + err = reftable_iterator_seek_ref(&it, names[N - 1]); EXPECT_ERR(err); err = reftable_iterator_next_ref(&it, &ref); @@ -252,7 +254,9 @@ static void test_log_write_read(void) reftable_iterator_destroy(&it); reftable_ref_record_release(&ref); - err = reftable_reader_seek_log(&rd, &it, ""); + reftable_reader_init_log_iterator(&rd, &it); + + err = reftable_iterator_seek_log(&it, ""); EXPECT_ERR(err); i = 0; @@ -330,7 +334,8 @@ static void test_log_zlib_corruption(void) err = init_reader(&rd, &source, "file.log"); EXPECT_ERR(err); - err = reftable_reader_seek_log(&rd, &it, "refname"); + reftable_reader_init_log_iterator(&rd, &it); + err = reftable_iterator_seek_log(&it, "refname"); EXPECT(err == REFTABLE_ZLIB_ERROR); reftable_iterator_destroy(&it); @@ -358,7 +363,8 @@ static void test_table_read_write_sequential(void) err = init_reader(&rd, &source, "file.ref"); EXPECT_ERR(err); - err = reftable_reader_seek_ref(&rd, &it, ""); + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, ""); EXPECT_ERR(err); while (1) { @@ -412,7 +418,8 @@ static void test_table_read_api(void) err = init_reader(&rd, &source, "file.ref"); EXPECT_ERR(err); - err = reftable_reader_seek_ref(&rd, &it, names[0]); + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, names[0]); EXPECT_ERR(err); err = reftable_iterator_next_log(&it, &log); @@ -457,7 +464,8 @@ static void test_table_read_write_seek(int index, int hash_id) } for (i = 1; i < N; i++) { - int err = reftable_reader_seek_ref(&rd, &it, names[i]); + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, names[i]); EXPECT_ERR(err); err = reftable_iterator_next_ref(&it, &ref); EXPECT_ERR(err); @@ -472,7 +480,8 @@ static void test_table_read_write_seek(int index, int hash_id) strbuf_addstr(&pastLast, names[N - 1]); strbuf_addstr(&pastLast, "/"); - err = reftable_reader_seek_ref(&rd, &it, pastLast.buf); + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, pastLast.buf); if (err == 0) { struct reftable_ref_record ref = { NULL }; int err = reftable_iterator_next_ref(&it, &ref); @@ -576,7 +585,8 @@ static void test_table_refs_for(int indexed) rd.obj_offsets.is_present = 0; } - err = reftable_reader_seek_ref(&rd, &it, ""); + reftable_reader_init_ref_iterator(&rd, &it); + err = reftable_iterator_seek_ref(&it, ""); EXPECT_ERR(err); reftable_iterator_destroy(&it); @@ -639,7 +649,8 @@ static void test_write_empty_table(void) err = reftable_new_reader(&rd, &source, "filename"); EXPECT_ERR(err); - err = reftable_reader_seek_ref(rd, &it, ""); + reftable_reader_init_ref_iterator(rd, &it); + err = reftable_iterator_seek_ref(&it, ""); EXPECT_ERR(err); err = reftable_iterator_next_ref(&it, &rec); @@ -846,7 +857,8 @@ static void test_write_multiple_indices(void) * Seeking the log uses the log index now. In case there is any * confusion regarding indices we would notice here. */ - err = reftable_reader_seek_log(reader, &it, ""); + reftable_reader_init_log_iterator(reader, &it); + err = reftable_iterator_seek_log(&it, ""); EXPECT_ERR(err); reftable_iterator_destroy(&it); @@ -901,7 +913,8 @@ static void test_write_multi_level_index(void) /* * Seeking the last ref should work as expected. */ - err = reftable_reader_seek_ref(reader, &it, "refs/heads/199"); + reftable_reader_init_ref_iterator(reader, &it); + err = reftable_iterator_seek_ref(&it, "refs/heads/199"); EXPECT_ERR(err); reftable_iterator_destroy(&it); diff --git a/reftable/record_test.c b/reftable/record_test.c index c158ee79ff..58290bdba3 100644 --- a/reftable/record_test.c +++ b/reftable/record_test.c @@ -64,31 +64,6 @@ static void test_varint_roundtrip(void) } } -static void test_common_prefix(void) -{ - struct { - const char *a, *b; - int want; - } cases[] = { - { "abc", "ab", 2 }, - { "", "abc", 0 }, - { "abc", "abd", 2 }, - { "abc", "pqr", 0 }, - }; - - int i = 0; - for (i = 0; i < ARRAY_SIZE(cases); i++) { - struct strbuf a = STRBUF_INIT; - struct strbuf b = STRBUF_INIT; - strbuf_addstr(&a, cases[i].a); - strbuf_addstr(&b, cases[i].b); - EXPECT(common_prefix_size(&a, &b) == cases[i].want); - - strbuf_release(&a); - strbuf_release(&b); - } -} - static void set_hash(uint8_t *h, int j) { int i = 0; @@ -258,16 +233,6 @@ static void test_reftable_log_record_roundtrip(void) strbuf_release(&scratch); } -static void test_u24_roundtrip(void) -{ - uint32_t in = 0x112233; - uint8_t dest[3]; - uint32_t out; - put_be24(dest, in); - out = get_be24(dest); - EXPECT(in == out); -} - static void test_key_roundtrip(void) { uint8_t buffer[1024] = { 0 }; @@ -411,9 +376,7 @@ int record_test_main(int argc, const char *argv[]) RUN_TEST(test_reftable_ref_record_roundtrip); RUN_TEST(test_varint_roundtrip); RUN_TEST(test_key_roundtrip); - RUN_TEST(test_common_prefix); RUN_TEST(test_reftable_obj_record_roundtrip); RUN_TEST(test_reftable_index_record_roundtrip); - RUN_TEST(test_u24_roundtrip); return 0; } diff --git a/reftable/reftable-generic.h b/reftable/reftable-generic.h index d239751a77..65670ea093 100644 --- a/reftable/reftable-generic.h +++ b/reftable/reftable-generic.h @@ -21,11 +21,11 @@ struct reftable_table { void *table_arg; }; -int reftable_table_seek_log(struct reftable_table *tab, - struct reftable_iterator *it, const char *name); +void reftable_table_init_ref_iter(struct reftable_table *tab, + struct reftable_iterator *it); -int reftable_table_seek_ref(struct reftable_table *tab, - struct reftable_iterator *it, const char *name); +void reftable_table_init_log_iter(struct reftable_table *tab, + struct reftable_iterator *it); /* returns the hash ID from a generic reftable_table */ uint32_t reftable_table_hash_id(struct reftable_table *tab); diff --git a/reftable/reftable-iterator.h b/reftable/reftable-iterator.h index d3eee7af35..e3bf688d53 100644 --- a/reftable/reftable-iterator.h +++ b/reftable/reftable-iterator.h @@ -21,12 +21,33 @@ struct reftable_iterator { void *iter_arg; }; +/* + * Position the iterator at the ref record with given name such that the next + * call to `next_ref()` would yield the record. + */ +int reftable_iterator_seek_ref(struct reftable_iterator *it, + const char *name); + /* reads the next reftable_ref_record. Returns < 0 for error, 0 for OK and > 0: * end of iteration. */ int reftable_iterator_next_ref(struct reftable_iterator *it, struct reftable_ref_record *ref); +/* + * Position the iterator at the log record with given name and update index + * such that the next call to `next_log()` would yield the record. + */ +int reftable_iterator_seek_log_at(struct reftable_iterator *it, + const char *name, uint64_t update_index); + +/* + * Position the iterator at the newest log record with given name such that the + * next call to `next_log()` would yield the record. + */ +int reftable_iterator_seek_log(struct reftable_iterator *it, + const char *name); + /* reads the next reftable_log_record. Returns < 0 for error, 0 for OK and > 0: * end of iteration. */ diff --git a/reftable/reftable-merged.h b/reftable/reftable-merged.h index c91a2d83a2..14d5fc9f05 100644 --- a/reftable/reftable-merged.h +++ b/reftable/reftable-merged.h @@ -36,21 +36,6 @@ int reftable_new_merged_table(struct reftable_merged_table **dest, struct reftable_table *stack, size_t n, uint32_t hash_id); -/* returns an iterator positioned just before 'name' */ -int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name); - -/* returns an iterator for log entry, at given update_index */ -int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name, uint64_t update_index); - -/* like reftable_merged_table_seek_log_at but look for the newest entry. */ -int reftable_merged_table_seek_log(struct reftable_merged_table *mt, - struct reftable_iterator *it, - const char *name); - /* returns the max update_index covered by this merged table. */ uint64_t reftable_merged_table_max_update_index(struct reftable_merged_table *mt); diff --git a/reftable/reftable-reader.h b/reftable/reftable-reader.h index 4a4bc2fdf8..a32f31d648 100644 --- a/reftable/reftable-reader.h +++ b/reftable/reftable-reader.h @@ -36,48 +36,17 @@ struct reftable_table; int reftable_new_reader(struct reftable_reader **pp, struct reftable_block_source *src, const char *name); -/* reftable_reader_seek_ref returns an iterator where 'name' would be inserted - in the table. To seek to the start of the table, use name = "". - - example: - - struct reftable_reader *r = NULL; - int err = reftable_new_reader(&r, &src, "filename"); - if (err < 0) { ... } - struct reftable_iterator it = {0}; - err = reftable_reader_seek_ref(r, &it, "refs/heads/master"); - if (err < 0) { ... } - struct reftable_ref_record ref = {0}; - while (1) { - err = reftable_iterator_next_ref(&it, &ref); - if (err > 0) { - break; - } - if (err < 0) { - ..error handling.. - } - ..found.. - } - reftable_iterator_destroy(&it); - reftable_ref_record_release(&ref); -*/ -int reftable_reader_seek_ref(struct reftable_reader *r, - struct reftable_iterator *it, const char *name); +/* Initialize a reftable iterator for reading refs. */ +void reftable_reader_init_ref_iterator(struct reftable_reader *r, + struct reftable_iterator *it); + +/* Initialize a reftable iterator for reading logs. */ +void reftable_reader_init_log_iterator(struct reftable_reader *r, + struct reftable_iterator *it); /* returns the hash ID used in this table. */ uint32_t reftable_reader_hash_id(struct reftable_reader *r); -/* seek to logs for the given name, older than update_index. To seek to the - start of the table, use name = "". -*/ -int reftable_reader_seek_log_at(struct reftable_reader *r, - struct reftable_iterator *it, const char *name, - uint64_t update_index); - -/* seek to newest log entry for given name. */ -int reftable_reader_seek_log(struct reftable_reader *r, - struct reftable_iterator *it, const char *name); - /* closes and deallocates a reader. */ void reftable_reader_free(struct reftable_reader *); @@ -97,5 +66,7 @@ void reftable_table_from_reader(struct reftable_table *tab, /* print table onto stdout for debugging. */ int reftable_reader_print_file(const char *tablename); +/* print blocks onto stdout for debugging. */ +int reftable_reader_print_blocks(const char *tablename); #endif diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index 1b602dda58..09e97c9991 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -29,7 +29,7 @@ struct reftable_stack; * stored in 'dir'. Typically, this should be .git/reftables. */ int reftable_new_stack(struct reftable_stack **dest, const char *dir, - struct reftable_write_options config); + const struct reftable_write_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); @@ -66,6 +66,24 @@ int reftable_stack_add(struct reftable_stack *st, void *write_arg), void *write_arg); +struct reftable_iterator; + +/* + * Initialize an iterator for the merged tables contained in the stack that can + * be used to iterate through refs. The iterator is valid until the next reload + * or write. + */ +void reftable_stack_init_ref_iterator(struct reftable_stack *st, + struct reftable_iterator *it); + +/* + * Initialize an iterator for the merged tables contained in the stack that can + * be used to iterate through logs. The iterator is valid until the next reload + * or write. + */ +void reftable_stack_init_log_iterator(struct reftable_stack *st, + struct reftable_iterator *it); + /* returns the merged_table for seeking. This table is valid until the * next write or reload, and should not be closed or deleted. */ diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index b601a69a40..189b1f4144 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -28,7 +28,7 @@ struct reftable_write_options { unsigned skip_index_objects : 1; /* how often to write complete keys in each block. */ - int restart_interval; + uint16_t restart_interval; /* 4-byte identifier ("sha1", "s256") of the hash. * Defaults to SHA1 if unset @@ -45,6 +45,12 @@ struct reftable_write_options { /* boolean: Prevent auto-compaction of tables. */ unsigned disable_auto_compact : 1; + + /* + * Geometric sequence factor used by auto-compaction to decide which + * tables to compact. Defaults to 2 if unset. + */ + uint8_t auto_compaction_factor; }; /* reftable_block_stats holds statistics for a single block type */ @@ -88,7 +94,7 @@ struct reftable_stats { struct reftable_writer * reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, struct reftable_write_options *opts); + void *writer_arg, const struct reftable_write_options *opts); /* Set the range of update indices for the records we will add. When writing a table into a stack, the min should be at least diff --git a/reftable/stack.c b/reftable/stack.c index 984fd866d0..324cb27ce9 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -10,6 +10,7 @@ https://developers.google.com/open-source/licenses/bsd #include "../write-or-die.h" #include "system.h" +#include "constants.h" #include "merged.h" #include "reader.h" #include "reftable-error.h" @@ -54,15 +55,17 @@ static int reftable_fd_flush(void *arg) } int reftable_new_stack(struct reftable_stack **dest, const char *dir, - struct reftable_write_options config) + const struct reftable_write_options *_opts) { struct reftable_stack *p = reftable_calloc(1, sizeof(*p)); struct strbuf list_file_name = STRBUF_INIT; + struct reftable_write_options opts = {0}; int err = 0; - if (config.hash_id == 0) { - config.hash_id = GIT_SHA1_FORMAT_ID; - } + if (_opts) + opts = *_opts; + if (opts.hash_id == 0) + opts.hash_id = GIT_SHA1_FORMAT_ID; *dest = NULL; @@ -73,7 +76,7 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir, p->list_file = strbuf_detach(&list_file_name, NULL); p->list_fd = -1; p->reftable_dir = xstrdup(dir); - p->config = config; + p->opts = opts; err = reftable_stack_reload_maybe_reuse(p, 1); if (err < 0) { @@ -130,6 +133,20 @@ int read_lines(const char *filename, char ***namesp) return err; } +void reftable_stack_init_ref_iterator(struct reftable_stack *st, + struct reftable_iterator *it) +{ + merged_table_init_iter(reftable_stack_merged_table(st), + it, BLOCK_TYPE_REF); +} + +void reftable_stack_init_log_iterator(struct reftable_stack *st, + struct reftable_iterator *it) +{ + merged_table_init_iter(reftable_stack_merged_table(st), + it, BLOCK_TYPE_LOG); +} + struct reftable_merged_table * reftable_stack_merged_table(struct reftable_stack *st) { @@ -255,7 +272,7 @@ static int reftable_stack_reload_once(struct reftable_stack *st, char **names, /* success! */ err = reftable_new_merged_table(&new_merged, new_tables, - new_readers_len, st->config.hash_id); + new_readers_len, st->opts.hash_id); if (err < 0) goto done; @@ -576,8 +593,8 @@ static int reftable_stack_init_addition(struct reftable_addition *add, } goto done; } - if (st->config.default_permissions) { - if (chmod(add->lock_file->filename.buf, st->config.default_permissions) < 0) { + if (st->opts.default_permissions) { + if (chmod(add->lock_file->filename.buf, st->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -676,7 +693,7 @@ int reftable_addition_commit(struct reftable_addition *add) if (err) goto done; - if (!add->stack->config.disable_auto_compact) { + if (!add->stack->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 @@ -754,9 +771,9 @@ int reftable_addition_add(struct reftable_addition *add, err = REFTABLE_IO_ERROR; goto done; } - if (add->stack->config.default_permissions) { + if (add->stack->opts.default_permissions) { if (chmod(get_tempfile_path(tab_file), - add->stack->config.default_permissions)) { + add->stack->opts.default_permissions)) { err = REFTABLE_IO_ERROR; goto done; } @@ -764,7 +781,7 @@ int reftable_addition_add(struct reftable_addition *add, tab_fd = get_tempfile_fd(tab_file); wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, - &add->stack->config); + &add->stack->opts); err = write_table(wr, arg); if (err < 0) goto done; @@ -847,14 +864,14 @@ static int stack_compact_locked(struct reftable_stack *st, } tab_fd = get_tempfile_fd(tab_file); - if (st->config.default_permissions && - chmod(get_tempfile_path(tab_file), st->config.default_permissions) < 0) { + if (st->opts.default_permissions && + chmod(get_tempfile_path(tab_file), st->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, - &tab_fd, &st->config); + &tab_fd, &st->opts); err = stack_write_compact(st, wr, first, last, config); if (err < 0) goto done; @@ -902,13 +919,14 @@ static int stack_write_compact(struct reftable_stack *st, st->readers[last]->max_update_index); err = reftable_new_merged_table(&mt, subtabs, subtabs_len, - st->config.hash_id); + st->opts.hash_id); if (err < 0) { reftable_free(subtabs); goto done; } - err = reftable_merged_table_seek_ref(mt, &it, ""); + merged_table_init_iter(mt, &it, BLOCK_TYPE_REF); + err = reftable_iterator_seek_ref(&it, ""); if (err < 0) goto done; @@ -932,7 +950,8 @@ static int stack_write_compact(struct reftable_stack *st, } reftable_iterator_destroy(&it); - err = reftable_merged_table_seek_log(mt, &it, ""); + merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG); + err = reftable_iterator_seek_log(&it, ""); if (err < 0) goto done; @@ -1090,9 +1109,9 @@ static int stack_compact_range(struct reftable_stack *st, goto done; } - if (st->config.default_permissions) { + if (st->opts.default_permissions) { if (chmod(get_lock_file_path(&tables_list_lock), - st->config.default_permissions) < 0) { + st->opts.default_permissions) < 0) { err = REFTABLE_IO_ERROR; goto done; } @@ -1206,12 +1225,16 @@ static int segment_size(struct segment *s) return s->end - s->start; } -struct segment suggest_compaction_segment(uint64_t *sizes, size_t n) +struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, + uint8_t factor) { struct segment seg = { 0 }; uint64_t bytes; size_t i; + if (!factor) + factor = DEFAULT_GEOMETRIC_FACTOR; + /* * If there are no tables or only a single one then we don't have to * compact anything. The sequence is geometric by definition already. @@ -1243,7 +1266,7 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n) * 64, 32, 16, 8, 4, 3, 1 */ for (i = n - 1; i > 0; i--) { - if (sizes[i - 1] < sizes[i] * 2) { + if (sizes[i - 1] < sizes[i] * factor) { seg.end = i + 1; bytes = sizes[i]; break; @@ -1269,7 +1292,7 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n) uint64_t curr = bytes; bytes += sizes[i - 1]; - if (sizes[i - 1] < curr * 2) { + if (sizes[i - 1] < curr * factor) { seg.start = i - 1; seg.bytes = bytes; } @@ -1282,7 +1305,7 @@ static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st) { uint64_t *sizes = reftable_calloc(st->merged->stack_len, sizeof(*sizes)); - int version = (st->config.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2; + int version = (st->opts.hash_id == GIT_SHA1_FORMAT_ID) ? 1 : 2; int overhead = header_size(version) - 1; int i = 0; for (i = 0; i < st->merged->stack_len; i++) { @@ -1295,7 +1318,8 @@ int reftable_stack_auto_compact(struct reftable_stack *st) { uint64_t *sizes = stack_table_sizes_for_compaction(st); struct segment seg = - suggest_compaction_segment(sizes, st->merged->stack_len); + suggest_compaction_segment(sizes, st->merged->stack_len, + st->opts.auto_compaction_factor); reftable_free(sizes); if (segment_size(&seg) > 0) return stack_compact_range_stats(st, seg.start, seg.end - 1, @@ -1321,9 +1345,11 @@ int reftable_stack_read_ref(struct reftable_stack *st, const char *refname, int reftable_stack_read_log(struct reftable_stack *st, const char *refname, struct reftable_log_record *log) { - struct reftable_iterator it = { NULL }; - struct reftable_merged_table *mt = reftable_stack_merged_table(st); - int err = reftable_merged_table_seek_log(mt, &it, refname); + struct reftable_iterator it = {0}; + int err; + + reftable_stack_init_log_iterator(st, &it); + err = reftable_iterator_seek_log(&it, refname); if (err) goto done; @@ -1431,11 +1457,11 @@ done: int reftable_stack_print_directory(const char *stackdir, uint32_t hash_id) { struct reftable_stack *stack = NULL; - struct reftable_write_options cfg = { .hash_id = hash_id }; + struct reftable_write_options opts = { .hash_id = hash_id }; struct reftable_merged_table *merged = NULL; struct reftable_table table = { NULL }; - int err = reftable_new_stack(&stack, stackdir, cfg); + int err = reftable_new_stack(&stack, stackdir, &opts); if (err < 0) goto done; diff --git a/reftable/stack.h b/reftable/stack.h index d43efa4760..5b45cff4f7 100644 --- a/reftable/stack.h +++ b/reftable/stack.h @@ -20,7 +20,7 @@ struct reftable_stack { char *reftable_dir; - struct reftable_write_options config; + struct reftable_write_options opts; struct reftable_reader **readers; size_t readers_len; @@ -35,6 +35,7 @@ struct segment { uint64_t bytes; }; -struct segment suggest_compaction_segment(uint64_t *sizes, size_t n); +struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, + uint8_t factor); #endif diff --git a/reftable/stack_test.c b/reftable/stack_test.c index 7889f818d1..6b8136e8e2 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -102,29 +102,6 @@ static void test_read_file(void) (void) remove(fn); } -static void test_parse_names(void) -{ - char buf[] = "line\n"; - char **names = NULL; - parse_names(buf, strlen(buf), &names); - - EXPECT(NULL != names[0]); - EXPECT(0 == strcmp(names[0], "line")); - EXPECT(NULL == names[1]); - free_names(names); -} - -static void test_names_equal(void) -{ - char *a[] = { "a", "b", "c", NULL }; - char *b[] = { "a", "b", "d", NULL }; - char *c[] = { "a", "b", NULL }; - - EXPECT(names_equal(a, a)); - EXPECT(!names_equal(a, b)); - EXPECT(!names_equal(a, c)); -} - static int write_test_ref(struct reftable_writer *wr, void *arg) { struct reftable_ref_record *ref = arg; @@ -150,7 +127,7 @@ static void test_reftable_stack_add_one(void) char *dir = get_tmp_dir(__LINE__); struct strbuf scratch = STRBUF_INIT; int mask = umask(002); - struct reftable_write_options cfg = { + struct reftable_write_options opts = { .default_permissions = 0660, }; struct reftable_stack *st = NULL; @@ -163,7 +140,7 @@ static void test_reftable_stack_add_one(void) }; struct reftable_ref_record dest = { NULL }; struct stat stat_result = { 0 }; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st, &write_test_ref, &ref); @@ -186,7 +163,7 @@ static void test_reftable_stack_add_one(void) strbuf_addstr(&scratch, "/tables.list"); err = stat(scratch.buf, &stat_result); EXPECT(!err); - EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); + EXPECT((stat_result.st_mode & 0777) == opts.default_permissions); strbuf_reset(&scratch); strbuf_addstr(&scratch, dir); @@ -195,7 +172,7 @@ static void test_reftable_stack_add_one(void) strbuf_addstr(&scratch, st->readers[0]->name); err = stat(scratch.buf, &stat_result); EXPECT(!err); - EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); + EXPECT((stat_result.st_mode & 0777) == opts.default_permissions); #else (void) stat_result; #endif @@ -209,7 +186,7 @@ static void test_reftable_stack_add_one(void) static void test_reftable_stack_uptodate(void) { - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL; struct reftable_stack *st2 = NULL; char *dir = get_tmp_dir(__LINE__); @@ -232,10 +209,10 @@ static void test_reftable_stack_uptodate(void) /* simulate multi-process access to the same stack by creating two stacks for the same directory. */ - err = reftable_new_stack(&st1, dir, cfg); + err = reftable_new_stack(&st1, dir, &opts); EXPECT_ERR(err); - err = reftable_new_stack(&st2, dir, cfg); + err = reftable_new_stack(&st2, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st1, &write_test_ref, &ref1); @@ -257,8 +234,7 @@ static void test_reftable_stack_uptodate(void) static void test_reftable_stack_transaction_api(void) { char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err; struct reftable_addition *add = NULL; @@ -271,8 +247,7 @@ static void test_reftable_stack_transaction_api(void) }; struct reftable_ref_record dest = { NULL }; - - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); reftable_addition_destroy(add); @@ -301,12 +276,12 @@ static void test_reftable_stack_transaction_api(void) static void test_reftable_stack_transaction_api_performs_auto_compaction(void) { char *dir = get_tmp_dir(__LINE__); - struct reftable_write_options cfg = {0}; + struct reftable_write_options opts = {0}; struct reftable_addition *add = NULL; struct reftable_stack *st = NULL; int i, n = 20, err; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = 0; i <= n; i++) { @@ -325,7 +300,7 @@ static 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->config.disable_auto_compact = i != n; + st->opts.disable_auto_compact = i != n; err = reftable_stack_new_addition(&add, st); EXPECT_ERR(err); @@ -361,13 +336,13 @@ static void test_reftable_stack_auto_compaction_fails_gracefully(void) .value_type = REFTABLE_REF_VAL1, .value.val1 = {0x01}, }; - struct reftable_write_options cfg = {0}; + struct reftable_write_options opts = {0}; struct reftable_stack *st; struct strbuf table_path = STRBUF_INIT; char *dir = get_tmp_dir(__LINE__); int err; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st, write_test_ref, &ref); @@ -404,8 +379,7 @@ static int write_error(struct reftable_writer *wr, void *arg) static void test_reftable_stack_update_index_check(void) { char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err; struct reftable_ref_record ref1 = { @@ -421,7 +395,7 @@ static void test_reftable_stack_update_index_check(void) .value.symref = "master", }; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st, &write_test_ref, &ref1); @@ -436,12 +410,11 @@ static void test_reftable_stack_update_index_check(void) static void test_reftable_stack_lock_failure(void) { char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err, i; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) { err = reftable_stack_add(st, &write_error, &i); @@ -456,7 +429,7 @@ static void test_reftable_stack_add(void) { int i = 0; int err = 0; - struct reftable_write_options cfg = { + struct reftable_write_options opts = { .exact_log_message = 1, .default_permissions = 0660, .disable_auto_compact = 1, @@ -469,7 +442,7 @@ static void test_reftable_stack_add(void) struct stat stat_result; int N = ARRAY_SIZE(refs); - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = 0; i < N; i++) { @@ -528,7 +501,7 @@ static void test_reftable_stack_add(void) strbuf_addstr(&path, "/tables.list"); err = stat(path.buf, &stat_result); EXPECT(!err); - EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); + EXPECT((stat_result.st_mode & 0777) == opts.default_permissions); strbuf_reset(&path); strbuf_addstr(&path, dir); @@ -537,7 +510,7 @@ static void test_reftable_stack_add(void) strbuf_addstr(&path, st->readers[0]->name); err = stat(path.buf, &stat_result); EXPECT(!err); - EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions); + EXPECT((stat_result.st_mode & 0777) == opts.default_permissions); #else (void) stat_result; #endif @@ -555,7 +528,7 @@ static void test_reftable_stack_add(void) static void test_reftable_stack_log_normalize(void) { int err = 0; - struct reftable_write_options cfg = { + struct reftable_write_options opts = { 0, }; struct reftable_stack *st = NULL; @@ -579,7 +552,7 @@ static void test_reftable_stack_log_normalize(void) .update_index = 1, }; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); input.value.update.message = "one\ntwo"; @@ -612,8 +585,7 @@ static void test_reftable_stack_tombstone(void) { int i = 0; char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err; struct reftable_ref_record refs[2] = { { NULL } }; @@ -622,8 +594,7 @@ static void test_reftable_stack_tombstone(void) struct reftable_ref_record dest = { NULL }; struct reftable_log_record log_dest = { NULL }; - - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); /* even entries add the refs, odd entries delete them. */ @@ -691,8 +662,7 @@ static void test_reftable_stack_tombstone(void) static void test_reftable_stack_hash_id(void) { char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err; @@ -702,24 +672,24 @@ static void test_reftable_stack_hash_id(void) .value.symref = "target", .update_index = 1, }; - struct reftable_write_options cfg32 = { .hash_id = GIT_SHA256_FORMAT_ID }; + struct reftable_write_options opts32 = { .hash_id = GIT_SHA256_FORMAT_ID }; struct reftable_stack *st32 = NULL; - struct reftable_write_options cfg_default = { 0 }; + struct reftable_write_options opts_default = { 0 }; struct reftable_stack *st_default = NULL; struct reftable_ref_record dest = { NULL }; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st, &write_test_ref, &ref); EXPECT_ERR(err); /* can't read it with the wrong hash ID. */ - err = reftable_new_stack(&st32, dir, cfg32); + err = reftable_new_stack(&st32, dir, &opts32); EXPECT(err == REFTABLE_FORMAT_ERROR); - /* check that we can read it back with default config too. */ - err = reftable_new_stack(&st_default, dir, cfg_default); + /* check that we can read it back with default opts too. */ + err = reftable_new_stack(&st_default, dir, &opts_default); EXPECT_ERR(err); err = reftable_stack_read_ref(st_default, "master", &dest); @@ -736,7 +706,7 @@ static void test_suggest_compaction_segment(void) { uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 }; struct segment min = - suggest_compaction_segment(sizes, ARRAY_SIZE(sizes)); + suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2); EXPECT(min.start == 1); EXPECT(min.end == 10); } @@ -745,15 +715,14 @@ static void test_suggest_compaction_segment_nothing(void) { uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 }; struct segment result = - suggest_compaction_segment(sizes, ARRAY_SIZE(sizes)); + suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2); EXPECT(result.start == result.end); } static void test_reflog_expire(void) { char *dir = get_tmp_dir(__LINE__); - - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct reftable_log_record logs[20] = { { NULL } }; int N = ARRAY_SIZE(logs) - 1; @@ -764,8 +733,7 @@ static void test_reflog_expire(void) }; struct reftable_log_record log = { NULL }; - - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = 1; i <= N; i++) { @@ -828,21 +796,19 @@ static int write_nothing(struct reftable_writer *wr, void *arg) static void test_empty_add(void) { - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; int err; char *dir = get_tmp_dir(__LINE__); - struct reftable_stack *st2 = NULL; - - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); err = reftable_stack_add(st, &write_nothing, NULL); EXPECT_ERR(err); - err = reftable_new_stack(&st2, dir, cfg); + err = reftable_new_stack(&st2, dir, &opts); EXPECT_ERR(err); clear_dir(dir); reftable_stack_destroy(st); @@ -861,16 +827,15 @@ static int fastlog2(uint64_t sz) static void test_reftable_stack_auto_compaction(void) { - struct reftable_write_options cfg = { + struct reftable_write_options opts = { .disable_auto_compact = 1, }; struct reftable_stack *st = NULL; char *dir = get_tmp_dir(__LINE__); - int err, i; int N = 100; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = 0; i < N; i++) { @@ -900,13 +865,13 @@ static void test_reftable_stack_auto_compaction(void) static void test_reftable_stack_add_performs_auto_compaction(void) { - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st = NULL; struct strbuf refname = STRBUF_INIT; char *dir = get_tmp_dir(__LINE__); int err, i, n = 20; - err = reftable_new_stack(&st, dir, cfg); + err = reftable_new_stack(&st, dir, &opts); EXPECT_ERR(err); for (i = 0; i <= n; i++) { @@ -921,7 +886,7 @@ static 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->config.disable_auto_compact = i != n; + st->opts.disable_auto_compact = i != n; strbuf_reset(&refname); strbuf_addf(&refname, "branch-%04d", i); @@ -948,14 +913,13 @@ static void test_reftable_stack_add_performs_auto_compaction(void) static void test_reftable_stack_compaction_concurrent(void) { - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL; char *dir = get_tmp_dir(__LINE__); - int err, i; int N = 3; - err = reftable_new_stack(&st1, dir, cfg); + err = reftable_new_stack(&st1, dir, &opts); EXPECT_ERR(err); for (i = 0; i < N; i++) { @@ -972,7 +936,7 @@ static void test_reftable_stack_compaction_concurrent(void) EXPECT_ERR(err); } - err = reftable_new_stack(&st2, dir, cfg); + err = reftable_new_stack(&st2, dir, &opts); EXPECT_ERR(err); err = reftable_stack_compact_all(st1, NULL); @@ -998,14 +962,13 @@ static void unclean_stack_close(struct reftable_stack *st) static void test_reftable_stack_compaction_concurrent_clean(void) { - struct reftable_write_options cfg = { 0 }; + struct reftable_write_options opts = { 0 }; struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL; char *dir = get_tmp_dir(__LINE__); - int err, i; int N = 3; - err = reftable_new_stack(&st1, dir, cfg); + err = reftable_new_stack(&st1, dir, &opts); EXPECT_ERR(err); for (i = 0; i < N; i++) { @@ -1022,7 +985,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void) EXPECT_ERR(err); } - err = reftable_new_stack(&st2, dir, cfg); + err = reftable_new_stack(&st2, dir, &opts); EXPECT_ERR(err); err = reftable_stack_compact_all(st1, NULL); @@ -1031,7 +994,7 @@ static void test_reftable_stack_compaction_concurrent_clean(void) unclean_stack_close(st1); unclean_stack_close(st2); - err = reftable_new_stack(&st3, dir, cfg); + err = reftable_new_stack(&st3, dir, &opts); EXPECT_ERR(err); err = reftable_stack_clean(st3); @@ -1048,8 +1011,6 @@ static void test_reftable_stack_compaction_concurrent_clean(void) int stack_test_main(int argc, const char *argv[]) { RUN_TEST(test_empty_add); - RUN_TEST(test_names_equal); - RUN_TEST(test_parse_names); RUN_TEST(test_read_file); RUN_TEST(test_reflog_expire); RUN_TEST(test_reftable_stack_add); diff --git a/reftable/writer.c b/reftable/writer.c index 10eccaaa07..45b3e9ce1f 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -117,25 +117,26 @@ static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) w->block_writer->restart_interval = w->opts.restart_interval; } -static struct strbuf reftable_empty_strbuf = STRBUF_INIT; - struct reftable_writer * reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t), int (*flush_func)(void *), - void *writer_arg, struct reftable_write_options *opts) + void *writer_arg, const struct reftable_write_options *_opts) { struct reftable_writer *wp = reftable_calloc(1, sizeof(*wp)); + struct reftable_write_options opts = {0}; + + if (_opts) + opts = *_opts; + options_set_defaults(&opts); + if (opts.block_size >= (1 << 24)) + BUG("configured block size exceeds 16MB"); + strbuf_init(&wp->block_writer_data.last_key, 0); - options_set_defaults(opts); - if (opts->block_size >= (1 << 24)) { - /* TODO - error return? */ - abort(); - } - wp->last_key = reftable_empty_strbuf; - REFTABLE_CALLOC_ARRAY(wp->block, opts->block_size); + strbuf_init(&wp->last_key, 0); + REFTABLE_CALLOC_ARRAY(wp->block, opts.block_size); wp->write = writer_func; wp->write_arg = writer_arg; - wp->opts = *opts; + wp->opts = opts; wp->flush = flush_func; writer_reinit_block_writer(wp, BLOCK_TYPE_REF); diff --git a/remote-curl.c b/remote-curl.c index cae98384da..6008d7e87c 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -266,12 +266,23 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push) return list; } +/* + * Try to detect the hash algorithm used by the remote repository when using + * the dumb HTTP transport. As dumb transports cannot tell us the object hash + * directly have to derive it from the advertised ref lengths. + */ static const struct git_hash_algo *detect_hash_algo(struct discovery *heads) { const char *p = memchr(heads->buf, '\t', heads->len); int algo; + + /* + * In case the remote has no refs we have no way to reliably determine + * the object hash used by that repository. In that case we simply fall + * back to SHA1, which may or may not be correct. + */ if (!p) - return the_hash_algo; + return &hash_algos[GIT_HASH_SHA1]; algo = hash_algo_by_length((p - heads->buf) / 2); if (algo == GIT_HASH_UNKNOWN) @@ -295,6 +306,12 @@ static struct ref *parse_info_refs(struct discovery *heads) "is this a git repository?", transport_anonymize_url(url.buf)); + /* + * Set the repository's hash algo to whatever we have just detected. + * This ensures that we can correctly parse the remote references. + */ + repo_set_hash_algo(the_repository, hash_algo_by_ptr(options.hash_algo)); + data = heads->buf; start = NULL; mid = data; @@ -430,29 +430,29 @@ static int handle_config(const char *key, const char *value, else if (!strcmp(subkey, "prunetags")) remote->prune_tags = git_config_bool(key, value); else if (!strcmp(subkey, "url")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; add_url(remote, v); } else if (!strcmp(subkey, "pushurl")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; add_pushurl(remote, v); } else if (!strcmp(subkey, "push")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; refspec_append(&remote->push, v); - free((char *)v); + free(v); } else if (!strcmp(subkey, "fetch")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; refspec_append(&remote->fetch, v); - free((char *)v); + free(v); } else if (!strcmp(subkey, "receivepack")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; if (!remote->receivepack) @@ -460,7 +460,7 @@ static int handle_config(const char *key, const char *value, else error(_("more than one receivepack given, using the first")); } else if (!strcmp(subkey, "uploadpack")) { - const char *v; + char *v; if (git_config_string(&v, key, value)) return -1; if (!remote->uploadpack) @@ -473,10 +473,10 @@ static int handle_config(const char *key, const char *value, else if (!strcmp(value, "--tags")) remote->fetch_tags = 2; } else if (!strcmp(subkey, "proxy")) { - return git_config_string((const char **)&remote->http_proxy, + return git_config_string(&remote->http_proxy, key, value); } else if (!strcmp(subkey, "proxyauthmethod")) { - return git_config_string((const char **)&remote->http_proxy_authmethod, + return git_config_string(&remote->http_proxy_authmethod, key, value); } else if (!strcmp(subkey, "vcs")) { return git_config_string(&remote->foreign_vcs, key, value); @@ -1778,7 +1778,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, if (!reject_reason && !ref->deletion && !is_null_oid(&ref->old_oid)) { if (starts_with(ref->name, "refs/tags/")) reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS; - else if (!repo_has_object_file(the_repository, &ref->old_oid)) + else if (!repo_has_object_file_with_flags(the_repository, &ref->old_oid, OBJECT_INFO_SKIP_FETCH_OBJECT)) reject_reason = REF_STATUS_REJECT_FETCH_FIRST; else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1) || !lookup_commit_reference_gently(the_repository, &ref->new_oid, 1)) @@ -46,7 +46,7 @@ struct remote_state { struct hashmap branches_hash; struct branch *current_branch; - const char *pushremote_name; + char *pushremote_name; struct rewrites rewrites; struct rewrites rewrites_push; @@ -65,7 +65,7 @@ struct remote { int origin, configured_in_repo; - const char *foreign_vcs; + char *foreign_vcs; /* An array of all of the url_nr URLs configured for the remote */ const char **url; @@ -309,9 +309,9 @@ struct branch { const char *refname; /* The name of the remote listed in the configuration. */ - const char *remote_name; + char *remote_name; - const char *pushremote_name; + char *pushremote_name; /* An array of the "merge" lines in the configuration. */ const char **merge_name; diff --git a/repository.c b/repository.c index 166863f852..95d10cc4a0 100644 --- a/repository.c +++ b/repository.c @@ -20,6 +20,28 @@ static struct repository the_repo; struct repository *the_repository = &the_repo; +/* + * An escape hatch: if we hit a bug in the production code that fails + * to set an appropriate hash algorithm (most likely to happen when + * running outside a repository), we can tell the user who reported + * the crash to set the environment variable to "sha1" (all lowercase) + * to revert to the historical behaviour of defaulting to SHA-1. + */ +static void set_default_hash_algo(struct repository *repo) +{ + const char *hash_name; + int algo; + + hash_name = getenv("GIT_TEST_DEFAULT_HASH_ALGO"); + if (!hash_name) + return; + algo = hash_algo_by_name(hash_name); + if (algo == GIT_HASH_UNKNOWN) + return; + + repo_set_hash_algo(repo, algo); +} + void initialize_repository(struct repository *repo) { repo->objects = raw_object_store_new(); @@ -29,24 +51,26 @@ void initialize_repository(struct repository *repo) index_state_init(repo->index, repo); /* - * Unfortunately, we need to keep this hack around for the time being: - * - * - Not setting up the hash algorithm for `the_repository` leads to - * crashes because `the_hash_algo` is a macro that expands to - * `the_repository->hash_algo`. So if Git commands try to access - * `the_hash_algo` without a Git directory we crash. + * When a command runs inside a repository, it learns what + * hash algorithm is in use from the repository, but some + * commands are designed to work outside a repository, yet + * they want to access the_hash_algo, if only for the length + * of the hashed value to see if their input looks like a + * plausible hash value. * - * - Setting up the hash algorithm to be SHA1 by default breaks other - * commands when running with SHA256. + * We are in the process of identifying such code paths and + * giving them an appropriate default individually; any + * unconverted code paths that try to access the_hash_algo + * will thus fail. The end-users however have an escape hatch + * to set GIT_TEST_DEFAULT_HASH_ALGO environment variable to + * "sha1" to get back the old behaviour of defaulting to SHA-1. * - * This is another point in case why having global state is a bad idea. - * Eventually, we should remove this hack and stop setting the hash - * algorithm in this function altogether. Instead, it should only ever - * be set via our repository setup procedures. But that requires more - * work. + * This escape hatch is deliberately kept unadvertised, so + * that they see crashes and we can get a report before + * telling them about it. */ if (repo == the_repository) - repo_set_hash_algo(repo, GIT_HASH_SHA1); + set_default_hash_algo(repo); } static void expand_base_dir(char **out, const char *in, diff --git a/run-command.c b/run-command.c index 1b821042b4..31b20123d8 100644 --- a/run-command.c +++ b/run-command.c @@ -746,6 +746,8 @@ fail_pipe: goto end_of_spawn; } + trace_argv_printf(&argv.v[1], "trace: start_command:"); + if (pipe(notify_pipe)) notify_pipe[0] = notify_pipe[1] = -1; @@ -913,6 +915,7 @@ end_of_spawn: else if (cmd->use_shell) cmd->args.v = prepare_shell_cmd(&nargv, sargv); + trace_argv_printf(cmd->args.v, "trace: start_command:"); cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env.v, cmd->dir, fhin, fhout, fherr); @@ -361,16 +361,13 @@ static char *remote_default_branch(const char *url) static int delete_enlistment(struct strbuf *enlistment) { -#ifdef WIN32 struct strbuf parent = STRBUF_INIT; size_t offset; char *path_sep; -#endif if (unregister_dir()) return error(_("failed to unregister repository")); -#ifdef WIN32 /* * Change the current directory to one outside of the enlistment so * that we may delete everything underneath it. @@ -385,7 +382,6 @@ static int delete_enlistment(struct strbuf *enlistment) return res; } strbuf_release(&parent); -#endif if (have_fsmonitor_support() && stop_fsmonitor_daemon()) return error(_("failed to stop the FSMonitor daemon")); diff --git a/sequencer.c b/sequencer.c index dbd60d79b9..30513e87bf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -306,7 +306,7 @@ static int git_sequencer_config(const char *k, const char *v, } if (!opts->default_strategy && !strcmp(k, "pull.twohead")) { - int ret = git_config_string((const char**)&opts->default_strategy, k, v); + int ret = git_config_string(&opts->default_strategy, k, v); if (ret == 0) { /* * pull.twohead is allowed to be multi-valued; we only @@ -359,35 +359,32 @@ static const char *get_todo_path(const struct replay_opts *opts) static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, size_t ignore_footer) { - struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; - struct trailer_info info; - size_t i; + struct trailer_iterator iter; + size_t i = 0; int found_sob = 0, found_sob_last = 0; char saved_char; - opts.no_divider = 1; - if (ignore_footer) { saved_char = sb->buf[sb->len - ignore_footer]; sb->buf[sb->len - ignore_footer] = '\0'; } - trailer_info_get(&opts, sb->buf, &info); + trailer_iterator_init(&iter, sb->buf); if (ignore_footer) sb->buf[sb->len - ignore_footer] = saved_char; - if (info.trailer_block_start == info.trailer_block_end) - return 0; + while (trailer_iterator_advance(&iter)) { + i++; + if (sob && !strncmp(iter.raw, sob->buf, sob->len)) + found_sob = i; + } + trailer_iterator_release(&iter); - for (i = 0; i < info.trailer_nr; i++) - if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) { - found_sob = 1; - if (i == info.trailer_nr - 1) - found_sob_last = 1; - } + if (!i) + return 0; - trailer_info_release(&info); + found_sob_last = (int)i == found_sob; if (found_sob_last) return 3; @@ -4,6 +4,7 @@ #include "environment.h" #include "exec-cmd.h" #include "gettext.h" +#include "hex.h" #include "object-name.h" #include "refs.h" #include "repository.h" @@ -342,6 +343,58 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir) return ret; } +static int validate_headref(const char *path) +{ + struct stat st; + char buffer[256]; + const char *refname; + struct object_id oid; + int fd; + ssize_t len; + + if (lstat(path, &st) < 0) + return -1; + + /* Make sure it is a "refs/.." symlink */ + if (S_ISLNK(st.st_mode)) { + len = readlink(path, buffer, sizeof(buffer)-1); + if (len >= 5 && !memcmp("refs/", buffer, 5)) + return 0; + return -1; + } + + /* + * Anything else, just open it and try to see if it is a symbolic ref. + */ + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + len = read_in_full(fd, buffer, sizeof(buffer)-1); + close(fd); + + if (len < 0) + return -1; + buffer[len] = '\0'; + + /* + * Is it a symbolic ref? + */ + if (skip_prefix(buffer, "ref:", &refname)) { + while (isspace(*refname)) + refname++; + if (starts_with(refname, "refs/")) + return 0; + } + + /* + * Is this a detached HEAD? + */ + if (get_oid_hex_any(buffer, &oid) != GIT_HASH_UNKNOWN) + return 0; + + return -1; +} + /* * Test if it looks like we're at a git directory. * We want to see: @@ -1177,13 +1230,20 @@ static int safe_directory_cb(const char *key, const char *value, } else if (!strcmp(value, "*")) { data->is_safe = 1; } else { - const char *interpolated = NULL; - - if (!git_config_pathname(&interpolated, key, value) && - !fspathcmp(data->path, interpolated ? interpolated : value)) - data->is_safe = 1; - - free((char *)interpolated); + char *allowed = NULL; + + if (!git_config_pathname(&allowed, key, value)) { + const char *check = allowed ? allowed : value; + if (ends_with(check, "/*")) { + size_t len = strlen(check); + if (!fspathncmp(check, data->path, len - 1)) + data->is_safe = 1; + } else if (!fspathcmp(data->path, check)) { + data->is_safe = 1; + } + } + if (allowed != value) + free(allowed); } return 0; @@ -1822,7 +1882,7 @@ static int template_dir_cb(const char *key, const char *value, char *path = NULL; FREE_AND_NULL(data->path); - if (!git_config_pathname((const char **)&path, key, value)) + if (!git_config_pathname(&path, key, value)) data->path = path ? path : xstrdup(value); } @@ -2291,12 +2351,6 @@ int init_db(const char *git_dir, const char *real_git_dir, } startup_info->have_repository = 1; - /* Ensure `core.hidedotfiles` is processed */ - git_config(platform_core_config, NULL); - - safe_create_dir(git_dir, 0); - - /* Check to see if the repository version is right. * Note that a newly created repository does not have * config file, so this will not fail. What we are catching @@ -2307,9 +2361,6 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); validate_ref_storage_format(&repo_fmt, ref_storage_format); - reinit = create_default_files(template_dir, original_git_dir, - &repo_fmt, init_shared_repository); - /* * Now that we have set up both the hash algorithm and the ref storage * format we can update the repository's settings accordingly. @@ -2317,6 +2368,18 @@ int init_db(const char *git_dir, const char *real_git_dir, repo_set_hash_algo(the_repository, repo_fmt.hash_algo); repo_set_ref_storage_format(the_repository, repo_fmt.ref_storage_format); + /* + * Ensure `core.hidedotfiles` is processed. This must happen after we + * have set up the repository format such that we can evaluate + * includeIf conditions correctly in the case of re-initialization. + */ + git_config(platform_core_config, NULL); + + safe_create_dir(git_dir, 0); + + reinit = create_default_files(template_dir, original_git_dir, + &repo_fmt, init_shared_repository); + if (!(flags & INIT_DB_SKIP_REFDB)) create_reference_database(repo_fmt.ref_storage_format, initial_branch, flags & INIT_DB_QUIET); @@ -313,6 +313,15 @@ void strbuf_add(struct strbuf *sb, const void *data, size_t len) strbuf_setlen(sb, sb->len + len); } +void strbuf_addstrings(struct strbuf *sb, const char *s, size_t n) +{ + size_t len = strlen(s); + + strbuf_grow(sb, st_mult(len, n)); + for (size_t i = 0; i < n; i++) + strbuf_add(sb, s, len); +} + void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) { strbuf_grow(sb, sb2->len); @@ -691,8 +700,10 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term) int strbuf_appendwholeline(struct strbuf *sb, FILE *fp, int term) { struct strbuf line = STRBUF_INIT; - if (strbuf_getwholeline(&line, fp, term)) + if (strbuf_getwholeline(&line, fp, term)) { + strbuf_release(&line); return EOF; + } strbuf_addbuf(sb, &line); strbuf_release(&line); return 0; @@ -311,6 +311,11 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) } /** + * Add a NUL-terminated string the specified number of times to the buffer. + */ +void strbuf_addstrings(struct strbuf *sb, const char *s, size_t n); + +/** * Copy the contents of another buffer at the end of the current one. */ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); @@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items) strvec_push(array, *items); } +const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement) +{ + char *to_free; + if (idx >= array->nr) + BUG("index outside of array boundary"); + to_free = (char *) array->v[idx]; + array->v[idx] = xstrdup(replacement); + free(to_free); + return array->v[idx]; +} + +void strvec_remove(struct strvec *array, size_t idx) +{ + if (idx >= array->nr) + BUG("index outside of array boundary"); + free((char *)array->v[idx]); + memmove(array->v + idx, array->v + idx + 1, (array->nr - idx) * sizeof(char *)); + array->nr--; +} + void strvec_pop(struct strvec *array) { if (!array->nr) @@ -65,6 +65,19 @@ void strvec_pushl(struct strvec *, ...); void strvec_pushv(struct strvec *, const char **); /** + * Replace the value at the given index with a new value. The index must be + * valid. Returns a pointer to the inserted value. + */ +const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement); + +/* + * Remove the value at the given index. The remainder of the array will be + * moved to fill the resulting gap. The provided index must point into the + * array. + */ +void strvec_remove(struct strvec *array, size_t idx); + +/** * Remove the final element from the array. If there are no * elements in the array, do nothing. */ diff --git a/submodule-config.c b/submodule-config.c index 11428b4ada..ec45ea67b9 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -91,6 +91,8 @@ static void free_one_config(struct submodule_entry *entry) free((void *) entry->config->path); free((void *) entry->config->name); free((void *) entry->config->branch); + free((void *) entry->config->url); + free((void *) entry->config->ignore); free((void *) entry->config->update_strategy.command); free(entry->config); } diff --git a/t/chainlint.pl b/t/chainlint.pl index 556ee91a15..1bbd985b78 100755 --- a/t/chainlint.pl +++ b/t/chainlint.pl @@ -716,11 +716,25 @@ sub fd_colors { sub ncores { # Windows - return $ENV{NUMBER_OF_PROCESSORS} if exists($ENV{NUMBER_OF_PROCESSORS}); + if (exists($ENV{NUMBER_OF_PROCESSORS})) { + my $ncpu = $ENV{NUMBER_OF_PROCESSORS}; + return $ncpu > 0 ? $ncpu : 1; + } # Linux / MSYS2 / Cygwin / WSL - do { local @ARGV='/proc/cpuinfo'; return scalar(grep(/^processor[\s\d]*:/, <>)); } if -r '/proc/cpuinfo'; + if (open my $fh, '<', '/proc/cpuinfo') { + my $cpuinfo = do { local $/; <$fh> }; + close($fh); + if ($cpuinfo =~ /^n?cpus active\s*:\s*(\d+)/m) { + return $1 if $1 > 0; + } + my @matches = ($cpuinfo =~ /^(processor|CPU)[\s\d]*:/mg); + return @matches ? scalar(@matches) : 1; + } # macOS & BSD - return qx/sysctl -n hw.ncpu/ if $^O =~ /(?:^darwin$|bsd)/; + if ($^O =~ /(?:^darwin$|bsd)/) { + my $ncpu = qx/sysctl -n hw.ncpu/; + return $ncpu > 0 ? $ncpu : 1; + } return 1; } diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c deleted file mode 100644 index 8f59f6be4c..0000000000 --- a/t/helper/test-example-decorate.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "test-tool.h" -#include "git-compat-util.h" -#include "object.h" -#include "decorate.h" -#include "repository.h" - -int cmd__example_decorate(int argc UNUSED, const char **argv UNUSED) -{ - struct decoration n; - struct object_id one_oid = { {1} }; - struct object_id two_oid = { {2} }; - struct object_id three_oid = { {3} }; - struct object *one, *two, *three; - - int decoration_a, decoration_b; - - void *ret; - - int i, objects_noticed = 0; - - /* - * The struct must be zero-initialized. - */ - memset(&n, 0, sizeof(n)); - - /* - * Add 2 objects, one with a non-NULL decoration and one with a NULL - * decoration. - */ - one = lookup_unknown_object(the_repository, &one_oid); - two = lookup_unknown_object(the_repository, &two_oid); - ret = add_decoration(&n, one, &decoration_a); - if (ret) - BUG("when adding a brand-new object, NULL should be returned"); - ret = add_decoration(&n, two, NULL); - if (ret) - BUG("when adding a brand-new object, NULL should be returned"); - - /* - * When re-adding an already existing object, the old decoration is - * returned. - */ - ret = add_decoration(&n, one, NULL); - if (ret != &decoration_a) - BUG("when readding an already existing object, existing decoration should be returned"); - ret = add_decoration(&n, two, &decoration_b); - if (ret) - BUG("when readding an already existing object, existing decoration should be returned"); - - /* - * Lookup returns the added declarations, or NULL if the object was - * never added. - */ - ret = lookup_decoration(&n, one); - if (ret) - BUG("lookup should return added declaration"); - ret = lookup_decoration(&n, two); - if (ret != &decoration_b) - BUG("lookup should return added declaration"); - three = lookup_unknown_object(the_repository, &three_oid); - ret = lookup_decoration(&n, three); - if (ret) - BUG("lookup for unknown object should return NULL"); - - /* - * The user can also loop through all entries. - */ - for (i = 0; i < n.size; i++) { - if (n.entries[i].base) - objects_noticed++; - } - if (objects_noticed != 2) - BUG("should have 2 objects"); - - clear_decoration(&n, NULL); - - return 0; -} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 023ed2e1a7..bf0e23ed50 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -501,16 +501,6 @@ int cmd__path_utils(int argc, const char **argv) return !!res; } - if (argc == 4 && !strcmp(argv[1], "do_files_match")) { - int ret = do_files_match(argv[2], argv[3]); - - if (ret) - printf("equal\n"); - else - printf("different\n"); - return !ret; - } - fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index bae731669c..9160bc5da6 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -5,7 +5,6 @@ int cmd__reftable(int argc, const char **argv) { /* test from simple to complex. */ - basics_test_main(argc, argv); record_test_main(argc, argv); block_test_main(argc, argv); tree_test_main(argc, argv); diff --git a/t/helper/test-strcmp-offset.c b/t/helper/test-strcmp-offset.c deleted file mode 100644 index d8473cf2fc..0000000000 --- a/t/helper/test-strcmp-offset.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "test-tool.h" -#include "read-cache-ll.h" - -int cmd__strcmp_offset(int argc UNUSED, const char **argv) -{ - int result; - size_t offset; - - if (!argv[1] || !argv[2]) - die("usage: %s <string1> <string2>", argv[0]); - - result = strcmp_offset(argv[1], argv[2], &offset); - - /* - * Because different CRTs behave differently, only rely on signs - * of the result values. - */ - result = (result < 0 ? -1 : - result > 0 ? 1 : - 0); - printf("%d %"PRIuMAX"\n", result, (uintmax_t)offset); - return 0; -} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index f6fd0fe491..1304336fe8 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -29,7 +29,6 @@ static struct test_cmd cmds[] = { { "dump-split-index", cmd__dump_split_index }, { "dump-untracked-cache", cmd__dump_untracked_cache }, { "env-helper", cmd__env_helper }, - { "example-decorate", cmd__example_decorate }, { "example-tap", cmd__example_tap }, { "find-pack", cmd__find_pack }, { "fsmonitor-client", cmd__fsmonitor_client }, @@ -78,7 +77,6 @@ static struct test_cmd cmds[] = { { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, { "simple-ipc", cmd__simple_ipc }, - { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule", cmd__submodule }, { "submodule-config", cmd__submodule_config }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 868f33453c..93a125d21a 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -23,7 +23,6 @@ int cmd__dump_split_index(int argc, const char **argv); int cmd__dump_untracked_cache(int argc, const char **argv); int cmd__dump_reftable(int argc, const char **argv); int cmd__env_helper(int argc, const char **argv); -int cmd__example_decorate(int argc, const char **argv); int cmd__example_tap(int argc, const char **argv); int cmd__find_pack(int argc, const char **argv); int cmd__fsmonitor_client(int argc, const char **argv); @@ -71,7 +70,6 @@ int cmd__oid_array(int argc, const char **argv); int cmd__sha256(int argc, const char **argv); int cmd__sigchain(int argc, const char **argv); int cmd__simple_ipc(int argc, const char **argv); -int cmd__strcmp_offset(int argc, const char **argv); int cmd__string_list(int argc, const char **argv); int cmd__submodule(int argc, const char **argv); int cmd__submodule_config(int argc, const char **argv); diff --git a/t/t0001-init.sh b/t/t0001-init.sh index b131d665db..49e9bf77c6 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -584,14 +584,39 @@ test_expect_success 'init with --ref-format=files' ' test_cmp expect actual ' -test_expect_success 're-init with same format' ' - test_when_finished "rm -rf refformat" && - git init --ref-format=files refformat && - git init --ref-format=files refformat && - echo files >expect && - git -C refformat rev-parse --show-ref-format >actual && - test_cmp expect actual -' +backends="files reftable" +for from_format in $backends +do + test_expect_success "re-init with same format ($from_format)" ' + test_when_finished "rm -rf refformat" && + git init --ref-format=$from_format refformat && + git init --ref-format=$from_format refformat && + echo $from_format >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual + ' + + for to_format in $backends + do + if test "$from_format" = "$to_format" + then + continue + fi + + test_expect_success "re-init with different format fails ($from_format -> $to_format)" ' + test_when_finished "rm -rf refformat" && + git init --ref-format=$from_format refformat && + cat >expect <<-EOF && + fatal: attempt to reinitialize repository with different reference storage format + EOF + test_must_fail git init --ref-format=$to_format refformat 2>err && + test_cmp expect err && + echo $from_format >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual + ' + done +done test_expect_success 'init with --ref-format=garbage' ' test_when_finished "rm -rf refformat" && @@ -678,4 +703,64 @@ test_expect_success 'branch -m with the initial branch' ' test_cmp expect actual ' +test_expect_success 'init with includeIf.onbranch condition' ' + test_when_finished "rm -rf repo" && + git -c includeIf.onbranch:main.path=nonexistent init repo && + echo $GIT_DEFAULT_REF_FORMAT >expect && + git -C repo rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init with includeIf.onbranch condition with existing directory' ' + test_when_finished "rm -rf repo" && + mkdir repo && + git -c includeIf.onbranch:nonexistent.path=/does/not/exist init repo && + echo $GIT_DEFAULT_REF_FORMAT >expect && + git -C repo rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 're-init with includeIf.onbranch condition' ' + test_when_finished "rm -rf repo" && + git init repo && + git -c includeIf.onbranch:nonexistent.path=/does/not/exist init repo && + echo $GIT_DEFAULT_REF_FORMAT >expect && + git -C repo rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 're-init with includeIf.onbranch condition' ' + test_when_finished "rm -rf repo" && + git init repo && + git -c includeIf.onbranch:nonexistent.path=/does/not/exist init repo && + echo $GIT_DEFAULT_REF_FORMAT >expect && + git -C repo rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 're-init skips non-matching includeIf.onbranch' ' + test_when_finished "rm -rf repo config" && + cat >config <<-EOF && + [ + garbage + EOF + git init repo && + git -c includeIf.onbranch:nonexistent.path="$(test-tool path-utils absolute_path config)" init repo +' + +test_expect_success 're-init reads matching includeIf.onbranch' ' + test_when_finished "rm -rf repo config" && + cat >config <<-EOF && + [ + garbage + EOF + path="$(test-tool path-utils absolute_path config)" && + git init --initial-branch=branch repo && + cat >expect <<-EOF && + fatal: bad config line 1 in file $path + EOF + test_must_fail git -c includeIf.onbranch:branch.path="$path" init repo 2>err && + test_cmp expect err +' + test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 71f082836a..66ccb5889d 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -434,6 +434,21 @@ test_expect_success 'precedence of --attr-source, GIT_ATTR_SOURCE, then attr.tre ) ' +test_expect_success 'diff without repository with attr source' ' + mkdir -p "$TRASH_DIRECTORY/outside/nongit" && + ( + cd "$TRASH_DIRECTORY/outside/nongit" && + GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/outside" && + export GIT_CEILING_DIRECTORIES && + touch file && + cat >expect <<-EOF && + fatal: cannot use --attr-source or GIT_ATTR_SOURCE without repo + EOF + test_must_fail env GIT_ATTR_SOURCE=HEAD git grep --no-index foo file 2>err && + test_cmp expect err + ) +' + test_expect_success 'bare repository: with --source' ' ( cd bare.git && diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index 361446b2f4..02a18d4fdb 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -945,4 +945,12 @@ test_expect_success SYMLINKS 'symlinks not respected in-tree' ' test_grep "unable to access.*gitignore" err ' +test_expect_success EXPENSIVE 'large exclude file ignored in tree' ' + test_when_finished "rm .gitignore" && + dd if=/dev/zero of=.gitignore bs=101M count=1 && + git ls-files -o --exclude-standard 2>err && + echo "warning: ignoring excessively large pattern file: .gitignore" >expect && + test_cmp expect err +' + test_done diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 95568342be..854d59ec58 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -44,4 +44,15 @@ test_expect_success 'run-command formats empty args properly' ' test_cmp expect actual ' +test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' ' + cat >expect <<-EOF && + trace: start_command: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} arg + EOF + git config alias.echo "!echo \$*" && + env GIT_TRACE=1 git echo arg 2>output && + # redact platform differences + sed -n -e "s/^\(trace: start_command:\) .* -c /\1 SHELL -c /p" output >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0015-hash.sh b/t/t0015-hash.sh deleted file mode 100755 index 0a087a1983..0000000000 --- a/t/t0015-hash.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -test_description='test basic hash implementation' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -test_expect_success 'test basic SHA-1 hash values' ' - test-tool sha1 </dev/null >actual && - grep da39a3ee5e6b4b0d3255bfef95601890afd80709 actual && - printf "a" | test-tool sha1 >actual && - grep 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 actual && - printf "abc" | test-tool sha1 >actual && - grep a9993e364706816aba3e25717850c26c9cd0d89d actual && - printf "message digest" | test-tool sha1 >actual && - grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual && - printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual && - grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual && - perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | - test-tool sha1 >actual && - grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual && - printf "blob 0\0" | test-tool sha1 >actual && - grep e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 actual && - printf "blob 3\0abc" | test-tool sha1 >actual && - grep f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f actual && - printf "tree 0\0" | test-tool sha1 >actual && - grep 4b825dc642cb6eb9a060e54bf8d69288fbee4904 actual -' - -test_expect_success 'test basic SHA-256 hash values' ' - test-tool sha256 </dev/null >actual && - grep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 actual && - printf "a" | test-tool sha256 >actual && - grep ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb actual && - printf "abc" | test-tool sha256 >actual && - grep ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad actual && - printf "message digest" | test-tool sha256 >actual && - grep f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650 actual && - printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha256 >actual && - grep 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 actual && - # Try to exercise the chunking code by turning autoflush on. - perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | - test-tool sha256 >actual && - grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual && - perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | - test-tool sha256 >actual && - grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual && - printf "blob 0\0" | test-tool sha256 >actual && - grep 473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813 actual && - printf "blob 3\0abc" | test-tool sha256 >actual && - grep c1cf6e465077930e88dc5136641d402f72a229ddd996f627d60e9639eaba35a6 actual && - printf "tree 0\0" | test-tool sha256 >actual && - grep 6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 actual -' - -test_done diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh index fc14ba091c..f3a16859cc 100755 --- a/t/t0017-env-helper.sh +++ b/t/t0017-env-helper.sh @@ -91,9 +91,16 @@ test_expect_success 'test-tool env-helper reads config thanks to trace2' ' git config -l 2>err && grep "exceeded maximum include depth" err && + # This validates that the assumption that we attempt to + # read the configuration and fail very early in the start-up + # sequence (due to trace2 subsystem), even before we notice + # that the directory named with "test-tool -C" does not exist + # and die. It is a dubious thing to test, though. test_must_fail \ env HOME="$(pwd)/home" GIT_TEST_ENV_HELPER=true \ - test-tool -C cycle env-helper --type=bool --default=0 --exit-code GIT_TEST_ENV_HELPER 2>err && + test-tool -C no-such-directory \ + env-helper --type=bool --default=0 \ + --exit-code GIT_TEST_ENV_HELPER 2>err && grep "exceeded maximum include depth" err ' diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh index 11c3e8f28e..5fe61f1291 100755 --- a/t/t0033-safe-directory.sh +++ b/t/t0033-safe-directory.sh @@ -71,7 +71,22 @@ test_expect_success 'safe.directory=*, but is reset' ' expect_rejected_dir ' +test_expect_success 'safe.directory with matching glob' ' + git config --global --unset-all safe.directory && + p=$(pwd) && + git config --global safe.directory "${p%/*}/*" && + git status +' + +test_expect_success 'safe.directory with unmatching glob' ' + git config --global --unset-all safe.directory && + p=$(pwd) && + git config --global safe.directory "${p%/*}no/*" && + expect_rejected_dir +' + test_expect_success 'safe.directory in included file' ' + git config --global --unset-all safe.directory && cat >gitconfig-include <<-EOF && [safe] directory = "$(pwd)" diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 8bb2a8b453..45a773642f 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -176,6 +176,23 @@ test_expect_success 'long options' ' test_cmp expect output ' +test_expect_success 'abbreviate to something longer than SHA1 length' ' + cat >expect <<-EOF && + boolean: 0 + integer: 0 + magnitude: 0 + timestamp: 0 + string: (not set) + abbrev: 100 + verbose: -1 + quiet: 0 + dry run: no + file: (not set) + EOF + test-tool parse-options --abbrev=100 >output && + test_cmp expect output +' + test_expect_success 'missing required value' ' cat >expect <<-\EOF && error: switch `s'\'' requires a value diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 85686ee15d..0afa3d0d31 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -610,45 +610,4 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' test_cmp expect actual ' -test_expect_success 'do_files_match()' ' - test_seq 0 10 >0-10.txt && - test_seq -1 10 >-1-10.txt && - test_seq 1 10 >1-10.txt && - test_seq 1 9 >1-9.txt && - test_seq 0 8 >0-8.txt && - - test-tool path-utils do_files_match 0-10.txt 0-10.txt >out && - - assert_fails() { - test_must_fail \ - test-tool path-utils do_files_match "$1" "$2" >out && - grep different out - } && - - assert_fails 0-8.txt 1-9.txt && - assert_fails -1-10.txt 0-10.txt && - assert_fails 1-10.txt 1-9.txt && - assert_fails 1-10.txt .git && - assert_fails does-not-exist 1-10.txt && - - if test_have_prereq FILEMODE - then - cp 0-10.txt 0-10.x && - chmod a+x 0-10.x && - assert_fails 0-10.txt 0-10.x - fi && - - if test_have_prereq SYMLINKS - then - ln -sf 0-10.txt symlink && - ln -s 0-10.txt another-symlink && - ln -s over-the-ocean yet-another-symlink && - ln -s "$PWD/0-10.txt" absolute-symlink && - assert_fails 0-10.txt symlink && - test-tool path-utils do_files_match symlink another-symlink && - assert_fails symlink yet-another-symlink && - assert_fails symlink absolute-symlink - fi -' - test_done diff --git a/t/t0065-strcmp-offset.sh b/t/t0065-strcmp-offset.sh deleted file mode 100755 index 94e34c83ed..0000000000 --- a/t/t0065-strcmp-offset.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -test_description='Test strcmp_offset functionality' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -while read s1 s2 expect -do - test_expect_success "strcmp_offset($s1, $s2)" ' - echo "$expect" >expect && - test-tool strcmp-offset "$s1" "$s2" >actual && - test_cmp expect actual - ' -done <<-EOF -abc abc 0 3 -abc def -1 0 -abc abz -1 2 -abc abcdef -1 3 -EOF - -test_done diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh index 13ef69b92f..070fe7a5da 100755 --- a/t/t0211-trace2-perf.sh +++ b/t/t0211-trace2-perf.sh @@ -233,7 +233,7 @@ have_counter_event () { pattern="d0|${thread}|${event}||||${category}|name:${name} value:${value}" && - grep "${patern}" ${file} + grep "${pattern}" ${file} } test_expect_success 'global counter test/test1' ' diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 432f029d48..6a76b7fdbd 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='basic credential helper tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-credential.sh diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 88a66f0904..2c30c86e7b 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -3,6 +3,7 @@ test_description='partial clone' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh # missing promisor objects cause repacks which write bitmaps to fail GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 @@ -689,6 +690,67 @@ test_expect_success 'lazy-fetch when accessing object not in the_repository' ' ! grep "[?]$FILE_HASH" out ' +test_expect_success 'push should not fetch new commit objects' ' + rm -rf server client && + test_create_repo server && + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + test_commit -C server server1 && + + git clone --filter=blob:none "file://$(pwd)/server" client && + test_commit -C client client1 && + + test_commit -C server server2 && + COMMIT=$(git -C server rev-parse server2) && + + test_must_fail git -C client push 2>err && + grep "fetch first" err && + git -C client rev-list --objects --missing=print "$COMMIT" >objects && + grep "^[?]$COMMIT" objects +' + +test_expect_success 'setup for promisor.quiet tests' ' + rm -rf server && + test_create_repo server && + test_commit -C server foo && + git -C server rm foo.t && + git -C server commit -m remove && + git -C server config uploadpack.allowanysha1inwant 1 && + git -C server config uploadpack.allowfilter 1 +' + +test_expect_success TTY 'promisor.quiet=false shows progress messages' ' + rm -rf repo && + git clone --filter=blob:none "file://$(pwd)/server" repo && + git -C repo config promisor.quiet "false" && + + test_terminal git -C repo cat-file -p foo:foo.t 2>err && + + # Ensure that progress messages are written + grep "Receiving objects" err +' + +test_expect_success TTY 'promisor.quiet=true does not show progress messages' ' + rm -rf repo && + git clone --filter=blob:none "file://$(pwd)/server" repo && + git -C repo config promisor.quiet "true" && + + test_terminal git -C repo cat-file -p foo:foo.t 2>err && + + # Ensure that no progress messages are written + ! grep "Receiving objects" err +' + +test_expect_success TTY 'promisor.quiet=unconfigured shows progress messages' ' + rm -rf repo && + git clone --filter=blob:none "file://$(pwd)/server" repo && + + test_terminal git -C repo cat-file -p foo:foo.t 2>err && + + # Ensure that progress messages are written + grep "Receiving objects" err +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh index c98d501869..932bf2067d 100755 --- a/t/t0411-clone-from-partial.sh +++ b/t/t0411-clone-from-partial.sh @@ -2,6 +2,7 @@ test_description='check that local clone does not fetch from promisor remotes' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create evil repo' ' diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index a390cffc80..92f570313d 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -424,7 +424,7 @@ test_expect_success SYMLINKS 'git branch -m with symlinked .git/refs' ' test_when_finished "rm -rf subdir" && git init --bare subdir && - rm -rfv subdir/refs subdir/objects subdir/packed-refs && + rm -rf subdir/refs subdir/objects subdir/packed-refs && ln -s ../.git/refs subdir/refs && ln -s ../.git/objects subdir/objects && ln -s ../.git/packed-refs subdir/packed-refs && diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh index cc5bbfd732..b06c46999d 100755 --- a/t/t0610-reftable-basics.sh +++ b/t/t0610-reftable-basics.sh @@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_DEFAULT_REF_FORMAT=reftable export GIT_TEST_DEFAULT_REF_FORMAT +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh INVALID_OID=$(test_oid 001) diff --git a/t/t0611-reftable-httpd.sh b/t/t0611-reftable-httpd.sh index 5e05b9c1f2..2805995cc8 100755 --- a/t/t0611-reftable-httpd.sh +++ b/t/t0611-reftable-httpd.sh @@ -2,6 +2,7 @@ test_description='reftable HTTPD tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh new file mode 100755 index 0000000000..e2708e11d5 --- /dev/null +++ b/t/t0613-reftable-write-options.sh @@ -0,0 +1,286 @@ +#!/bin/sh + +test_description='reftable write options' + +GIT_TEST_DEFAULT_REF_FORMAT=reftable +export GIT_TEST_DEFAULT_REF_FORMAT +# Disable auto-compaction for all tests as we explicitly control repacking of +# refs. +GIT_TEST_REFTABLE_AUTOCOMPACTION=false +export GIT_TEST_REFTABLE_AUTOCOMPACTION +# Block sizes depend on the hash function, so we force SHA1 here. +GIT_TEST_DEFAULT_HASH=sha1 +export GIT_TEST_DEFAULT_HASH +# Block sizes also depend on the actual refs we write, so we force "master" to +# be the default initial branch name. +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +test_expect_success 'default write options' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + git pack-refs && + cat >expect <<-EOF && + header: + block_size: 4096 + ref: + - length: 129 + restarts: 2 + log: + - length: 262 + restarts: 2 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'disabled reflog writes no log blocks' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + git pack-refs && + cat >expect <<-EOF && + header: + block_size: 4096 + ref: + - length: 129 + restarts: 2 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'many refs results in multiple blocks' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + for i in $(test_seq 200) + do + printf "update refs/heads/branch-%d HEAD\n" "$i" || + return 1 + done >input && + git update-ref --stdin <input && + git pack-refs && + + cat >expect <<-EOF && + header: + block_size: 4096 + ref: + - length: 4049 + restarts: 11 + - length: 1136 + restarts: 3 + log: + - length: 4041 + restarts: 4 + - length: 4015 + restarts: 3 + - length: 4014 + restarts: 3 + - length: 4012 + restarts: 3 + - length: 3289 + restarts: 3 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'tiny block size leads to error' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + cat >expect <<-EOF && + error: unable to compact stack: entry too large + EOF + test_must_fail git -c reftable.blockSize=50 pack-refs 2>err && + test_cmp expect err + ) +' + +test_expect_success 'small block size leads to multiple ref blocks' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit B && + git -c reftable.blockSize=100 pack-refs && + + cat >expect <<-EOF && + header: + block_size: 100 + ref: + - length: 53 + restarts: 1 + - length: 74 + restarts: 1 + - length: 38 + restarts: 1 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'small block size fails with large reflog message' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + perl -e "print \"a\" x 500" >logmsg && + cat >expect <<-EOF && + fatal: update_ref failed for ref ${SQ}refs/heads/logme${SQ}: reftable: transaction failure: entry too large + EOF + test_must_fail git -c reftable.blockSize=100 \ + update-ref -m "$(cat logmsg)" refs/heads/logme HEAD 2>err && + test_cmp expect err + ) +' + +test_expect_success 'block size exceeding maximum supported size' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_commit B && + cat >expect <<-EOF && + fatal: reftable block size cannot exceed 16MB + EOF + test_must_fail git -c reftable.blockSize=16777216 pack-refs 2>err && + test_cmp expect err + ) +' + +test_expect_success 'restart interval at every single record' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + for i in $(test_seq 10) + do + printf "update refs/heads/branch-%d HEAD\n" "$i" || + return 1 + done >input && + git update-ref --stdin <input && + git -c reftable.restartInterval=1 pack-refs && + + cat >expect <<-EOF && + header: + block_size: 4096 + ref: + - length: 566 + restarts: 13 + log: + - length: 1393 + restarts: 12 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'restart interval exceeding maximum supported interval' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + cat >expect <<-EOF && + fatal: reftable block size cannot exceed 65535 + EOF + test_must_fail git -c reftable.restartInterval=65536 pack-refs 2>err && + test_cmp expect err + ) +' + +test_expect_success 'object index gets written by default with ref index' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + for i in $(test_seq 5) + do + printf "update refs/heads/branch-%d HEAD\n" "$i" || + return 1 + done >input && + git update-ref --stdin <input && + git -c reftable.blockSize=100 pack-refs && + + cat >expect <<-EOF && + header: + block_size: 100 + ref: + - length: 53 + restarts: 1 + - length: 95 + restarts: 1 + - length: 71 + restarts: 1 + - length: 80 + restarts: 1 + obj: + - length: 11 + restarts: 1 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'object index can be disabled' ' + test_config_global core.logAllRefUpdates false && + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + for i in $(test_seq 5) + do + printf "update refs/heads/branch-%d HEAD\n" "$i" || + return 1 + done >input && + git update-ref --stdin <input && + git -c reftable.blockSize=100 -c reftable.indexObjects=false pack-refs && + + cat >expect <<-EOF && + header: + block_size: 100 + ref: + - length: 53 + restarts: 1 + - length: 95 + restarts: 1 + - length: 71 + restarts: 1 + - length: 80 + restarts: 1 + EOF + test-tool dump-reftable -b .git/reftable/*.ref >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 64aea38486..d73a5cc237 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -260,4 +260,10 @@ test_expect_success '--literally with extra-long type' ' echo example | git hash-object -t $t --literally --stdin ' +test_expect_success '--stdin outside of repository (uses SHA-1)' ' + nongit git hash-object --stdin <hello >actual && + echo "$(test_oid --hash=sha1 hello)" >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index bfc90d4cf2..cf8b94ebed 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -2,6 +2,7 @@ test_description='read-tree can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index 3a14218b24..da0e7714d5 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_CREATE_REPO_NO_TEMPLATE=1 +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index ab3a105fff..8c5cd651b4 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -8,6 +8,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_SPLIT_INDEX=false export GIT_TEST_SPLIT_INDEX +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh list_files() { diff --git a/t/t1300-config.sh b/t/t1300-config.sh index f3c4d28e06..9de2d95f06 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -596,7 +596,8 @@ test_expect_success 'get bool variable with empty value' ' test_expect_success 'no arguments, but no crash' ' test_must_fail git config >output 2>&1 && - test_grep usage output + echo "error: no action specified" >expect && + test_cmp expect output ' cat > .git/config << EOF @@ -2834,6 +2835,12 @@ test_expect_success 'specifying multiple modes causes failure' ' test_cmp expect err ' +test_expect_success 'writing to stdin is rejected' ' + echo "fatal: writing to stdin is not supported" >expect && + test_must_fail git config ${mode_set} --file - foo.bar baz 2>err && + test_cmp expect err +' + done test_done diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 40d3c42618..53e5b290b9 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -7,6 +7,7 @@ test_description='Compatibility with $XDG_CONFIG_HOME/git/ files' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' ' diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh index f6dc83e2aa..ceeb7ac3a4 100755 --- a/t/t1350-config-hooks-path.sh +++ b/t/t1350-config-hooks-path.sh @@ -2,6 +2,7 @@ test_description='Test the core.hooksPath configuration variable' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up a pre-commit hook in core.hooksPath' ' @@ -41,4 +42,11 @@ test_expect_success 'git rev-parse --git-path hooks' ' test .git/custom-hooks/abc = "$(cat actual)" ' +test_expect_success 'core.hooksPath=/dev/null' ' + git clone -c core.hooksPath=/dev/null . no-templates && + value="$(git -C no-templates config --local core.hooksPath)" && + # The Bash used by Git for Windows rewrites `/dev/null` to `nul` + { test /dev/null = "$value" || test nul = "$value"; } +' + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ec3443cc87..bbee2783ab 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -4,6 +4,8 @@ # test_description='Test git update-ref and basic ref logging' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh Z=$ZERO_OID diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 98e9158bd2..67ebd81a4c 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -100,7 +100,7 @@ df_test() { printf "%s\n" "delete $delname" "create $addname $D" fi >commands && test_must_fail git update-ref --stdin <commands 2>output.err && - grep "fatal:\( cannot lock ref $SQ$addname$SQ:\)\? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err && + grep -E "fatal:( cannot lock ref $SQ$addname$SQ:)? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err && printf "%s\n" "$C $delref" >expected-refs && git for-each-ref --format="%(objectname) %(refname)" $prefix/r >actual-refs && test_cmp expected-refs actual-refs diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 173b4fafad..8a456b1142 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -1060,41 +1060,4 @@ test_expect_success 'fsck reports problems in current worktree index without fil test_cmp expect actual ' -test_expect_success 'fsck warning on symlink target with excessive length' ' - symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) && - test_when_finished "remove_object $symlink_target" && - tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) && - test_when_finished "remove_object $tree" && - cat >expected <<-EOF && - warning in blob $symlink_target: symlinkTargetLength: symlink target too long - EOF - git fsck --no-dangling >actual 2>&1 && - test_cmp expected actual -' - -test_expect_success 'fsck warning on symlink target pointing inside git dir' ' - gitdir=$(printf ".git" | git hash-object -w --stdin) && - ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) && - hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) && - inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) && - benign_target=$(printf "legit/config" | git hash-object -w --stdin) && - tree=$(printf "120000 blob %s\t%s\n" \ - $benign_target benign_target \ - $gitdir gitdir \ - $hfs_gitdir hfs_gitdir \ - $inside_gitdir inside_gitdir \ - $ntfs_gitdir ntfs_gitdir | - git mktree) && - for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree - do - test_when_finished "remove_object $o" || return 1 - done && - printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \ - $gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir | - sort >expected && - git fsck --no-dangling >actual 2>&1 && - sort actual >actual.sorted && - test_cmp expected actual.sorted -' - test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index a669e592f1..30c31918fd 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -304,4 +304,10 @@ test_expect_success 'rev-parse --bisect includes bad, excludes good' ' test_cmp expect actual ' +test_expect_success '--short= truncates to the actual hash length' ' + git rev-parse HEAD >expect && + git rev-parse --short=100 HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh new file mode 100755 index 0000000000..990a036582 --- /dev/null +++ b/t/t1517-outside-repo.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +test_description='check random commands outside repo' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'set up a non-repo directory and test file' ' + GIT_CEILING_DIRECTORIES=$(pwd) && + export GIT_CEILING_DIRECTORIES && + mkdir non-repo && + ( + cd non-repo && + # confirm that git does not find a repo + test_must_fail git rev-parse --git-dir + ) && + test_write_lines one two three four >nums && + git add nums && + cp nums nums.old && + test_write_lines five >>nums && + git diff >sample.patch +' + +test_expect_success 'compute a patch-id outside repository (uses SHA-1)' ' + nongit env GIT_DEFAULT_HASH=sha1 \ + git patch-id <sample.patch >patch-id.expect && + nongit \ + git patch-id <sample.patch >patch-id.actual && + test_cmp patch-id.expect patch-id.actual +' + +test_expect_success 'hash-object outside repository (uses SHA-1)' ' + nongit env GIT_DEFAULT_HASH=sha1 \ + git hash-object --stdin <sample.patch >hash.expect && + nongit \ + git hash-object --stdin <sample.patch >hash.actual && + test_cmp hash.expect hash.actual +' + +test_expect_success 'apply a patch outside repository' ' + ( + cd non-repo && + cp ../nums.old nums && + git apply ../sample.patch + ) && + test_cmp nums non-repo/nums +' + +test_expect_success 'grep outside repository' ' + git grep --cached two >expect && + ( + cd non-repo && + cp ../nums.old nums && + git grep --no-index two >../actual + ) && + test_cmp expect actual +' + +test_expect_success 'imap-send outside repository' ' + test_config_global imap.host imaps://localhost && + test_config_global imap.folder Drafts && + + echo nothing to send >expect && + test_must_fail git imap-send -v </dev/null 2>actual && + test_cmp expect actual && + + ( + cd non-repo && + test_must_fail git imap-send -v </dev/null 2>../actual + ) && + test_cmp expect actual +' + +test_expect_success 'check-ref-format outside repository' ' + git check-ref-format --branch refs/heads/xyzzy >expect && + nongit git check-ref-format --branch refs/heads/xyzzy >actual && + test_cmp expect actual +' + +test_expect_success 'diff outside repository' ' + echo one >one && + echo two >two && + test_must_fail git diff --no-index one two >expect.raw && + ( + cd non-repo && + cp ../one . && + cp ../two . && + test_must_fail git diff one two >../actual.raw + ) && + # outside repository diff falls back to SHA-1 but + # GIT_DEFAULT_HASH may be set to sha256 on the in-repo side. + sed -e "/^index /d" expect.raw >expect && + sed -e "/^index /d" actual.raw >actual && + test_cmp expect actual +' + +test_expect_success 'stripspace outside repository' ' + nongit git stripspace -s </dev/null +' + +test_expect_success 'remote-http outside repository' ' + test_must_fail git remote-http 2>actual && + test_grep "^error: remote-curl" actual && + ( + cd non-repo && + test_must_fail git remote-http 2>../actual + ) && + test_grep "^error: remote-curl" actual +' + +test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index a7b7263b35..ac4a5b2734 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -527,7 +527,7 @@ test_expect_success 'reading split index at alternate location' ' # ... and, for backwards compatibility, in the current GIT_DIR # as well. - mv -v ./reading-alternate-location/.git/sharedindex.* .git && + mv ./reading-alternate-location/.git/sharedindex.* .git && GIT_INDEX_FILE=./reading-alternate-location/.git/index \ git ls-files --cached >actual && test_cmp expect actual diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 1894ebeb0e..8b0234cf2d 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -185,19 +185,4 @@ test_expect_success 'stdin to hooks' ' test_cmp expect actual ' -test_expect_success 'clone protections' ' - test_config core.hooksPath "$(pwd)/my-hooks" && - mkdir -p my-hooks && - write_script my-hooks/test-hook <<-\EOF && - echo Hook ran $1 - EOF - - git hook run test-hook 2>err && - test_grep "Hook ran" err && - test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \ - git hook run test-hook 2>err && - test_grep "active .core.hooksPath" err && - test_grep ! "Hook ran" err -' - test_done diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index b2bdd1fcb4..3c1d663d94 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -2,6 +2,7 @@ test_description='checkout can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index a3b1449ef1..2caada3d83 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -4,6 +4,7 @@ test_description='checkout <branch> Ensures that checkout on an unborn branch does what the user expects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Is the current branch "refs/heads/$1"? diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh index c91c4db936..77b2346291 100755 --- a/t/t2060-switch.sh +++ b/t/t2060-switch.sh @@ -5,6 +5,7 @@ test_description='switch basic functionality' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh index 11018f37c7..1d7f605633 100755 --- a/t/t2405-worktree-submodule.sh +++ b/t/t2405-worktree-submodule.sh @@ -5,6 +5,7 @@ test_description='Combination of submodules and multiple worktrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh base_path=$(pwd -P) diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh index 61771eec83..f04bdc8c78 100755 --- a/t/t3007-ls-files-recurse-submodules.sh +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -6,6 +6,7 @@ This test verifies the recurse-submodules feature correctly lists files from submodules. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup directory structure and submodules' ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 758963b189..e627f08a17 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git branch display tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index fcc40d6fe1..22452ff84c 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -5,6 +5,7 @@ test_description='auto squash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index ba069dccbd..94ea88e384 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -2,6 +2,7 @@ test_description='rebase can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh . "$TEST_DIRECTORY"/lib-rebase.sh diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index f22d1ddead..9387a22a9e 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -5,6 +5,7 @@ test_description='cherry-pick can handle submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh index 8bfe3ed246..e178968b40 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -2,6 +2,7 @@ test_description='revert can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 98259e2ada..31ac31d4bc 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -8,6 +8,7 @@ test_description='Test of the various options to git rm.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Setup some files to be removed, some with funny characters diff --git a/t/t3602-rm-sparse-checkout.sh b/t/t3602-rm-sparse-checkout.sh index 08580fd3dc..fcdefba48c 100755 --- a/t/t3602-rm-sparse-checkout.sh +++ b/t/t3602-rm-sparse-checkout.sh @@ -2,6 +2,7 @@ test_description='git rm in sparse checked out working trees' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' " diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 28a95a775d..6624a4f7c0 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -160,6 +160,14 @@ test_expect_success 'revert works (commit)' ' grep "unchanged *+3/-0 file" output ' +test_expect_success 'reject multi-key input' ' + saved=$(git hash-object -w file) && + test_when_finished "git cat-file blob $saved >file" && + echo an extra line >>file && + test_write_lines aa | git add -p >actual && + test_grep "is expected, got ${SQ}aa${SQ}" actual +' + test_expect_success 'setup expected' ' cat >expected <<-\EOF EOF @@ -526,7 +534,7 @@ test_expect_success 'split hunk setup' ' test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test ' -test_expect_success 'goto hunk' ' +test_expect_success 'goto hunk 1 with "g 1"' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15 @@ -542,7 +550,20 @@ test_expect_success 'goto hunk' ' test_cmp expect actual.trimmed ' -test_expect_success 'navigate to hunk via regex' ' +test_expect_success 'goto hunk 1 with "g1"' ' + test_when_finished "git reset" && + tr _ " " >expect <<-EOF && + _10 + +15 + _20 + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + EOF + test_write_lines s y g1 | git add -p >actual && + tail -n 4 <actual >actual.trimmed && + test_cmp expect actual.trimmed +' + +test_expect_success 'navigate to hunk via regex /pattern' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@ @@ -556,6 +577,19 @@ test_expect_success 'navigate to hunk via regex' ' test_cmp expect actual.trimmed ' +test_expect_success 'navigate to hunk via regex / pattern' ' + test_when_finished "git reset" && + tr _ " " >expect <<-EOF && + _10 + +15 + _20 + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ + EOF + test_write_lines s y / 1,2 | git add -p >actual && + tail -n 4 <actual >actual.trimmed && + test_cmp expect actual.trimmed +' + test_expect_success 'split hunk "add -p (edit)"' ' # Split, say Edit and do nothing. Then: # diff --git a/t/t3906-stash-submodule.sh b/t/t3906-stash-submodule.sh index 0f7348ec21..0f61f01ef4 100755 --- a/t/t3906-stash-submodule.sh +++ b/t/t3906-stash-submodule.sh @@ -2,6 +2,7 @@ test_description='stash can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 49c042a38a..cd1931dd55 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -3,9 +3,9 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Test rename detection in diff engine. +test_description='Test rename detection in diff engine.' -' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 0c1502d4b0..8fc40e75eb 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -12,6 +12,7 @@ This test tries to verify the sanity of the --submodule option of git diff. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh index 2a2cf91352..e486493908 100755 --- a/t/t4043-diff-rename-binary.sh +++ b/t/t4043-diff-rename-binary.sh @@ -5,6 +5,7 @@ test_description='Move a binary file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index d489230df8..668f526303 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -9,6 +9,7 @@ This test tries to verify that add_submodule_odb works when the submodule was initialized previously but the checkout has since been removed. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index 97c6424cd5..8ce67442d9 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -10,6 +10,7 @@ test_description='Support for diff format verbose submodule difference in git di This test tries to verify the sanity of --submodule=diff option of git diff. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh index 697e86c0ff..f788428540 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -5,6 +5,7 @@ test_description='git apply -p handling.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t4137-apply-submodule.sh b/t/t4137-apply-submodule.sh index 07d5262537..ebd0d4ad17 100755 --- a/t/t4137-apply-submodule.sh +++ b/t/t4137-apply-submodule.sh @@ -2,6 +2,7 @@ test_description='git apply handling submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 60fe60d761..86c695eb0a 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -2022,7 +2022,7 @@ test_expect_success GPGSM 'log --graph --show-signature x509' ' test_expect_success GPGSSH 'log --graph --show-signature ssh' ' test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && git log --graph --show-signature -n1 signed-ssh >actual && - grep "${GOOD_SIGNATURE_TRUSTED}" actual + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual ' test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' ' diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index a7fa94ce0a..605faea0c7 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -310,4 +310,38 @@ test_expect_success 'patch-id handles diffs with one line of before/after' ' test_config patchid.stable true && calc_patch_id diffu1stable <diffu1 ' + +test_expect_failure 'patch-id computes same ID with different object hashes' ' + test_when_finished "rm -rf repo-sha1 repo-sha256" && + + cat >diff <<-\EOF && + diff --git a/bar b/bar + index bdaf90f..31051f6 100644 + --- a/bar + +++ b/bar + @@ -2 +2,2 @@ + b + +c + EOF + + git init --object-format=sha1 repo-sha1 && + git -C repo-sha1 patch-id <diff >patch-id-sha1 && + git init --object-format=sha256 repo-sha256 && + git -C repo-sha256 patch-id <diff >patch-id-sha256 && + test_cmp patch-id-sha1 patch-id-sha256 +' + +test_expect_success 'patch-id without repository' ' + cat >diff <<-\EOF && + diff --git a/bar b/bar + index bdaf90f..31051f6 100644 + --- a/bar + +++ b/bar + @@ -2 +2,2 @@ + b + +c + EOF + nongit git patch-id <diff +' + test_done diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 75216f19ce..7120030b5c 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of é diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index dd09134db0..10d2a6bf92 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -350,6 +350,29 @@ test_expect_success 'preferred packs must be non-empty' ' ) ' +test_expect_success 'preferred pack from existing MIDX without bitmaps' ' + git init preferred-without-bitmaps && + ( + cd preferred-without-bitmaps && + + test_commit one && + pack="$(git pack-objects --all $objdir/pack/pack </dev/null)" && + + git multi-pack-index write && + + # make another pack so that the subsequent MIDX write + # has something to do + test_commit two && + git repack -d && + + # write a new MIDX without bitmaps reusing the singular + # pack from the existing MIDX as the preferred pack in + # the new MIDX + git multi-pack-index write --preferred-pack=pack-$pack.pack + ) + +' + test_expect_success 'verify multi-pack-index success' ' git multi-pack-index verify --object-dir=$objdir ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 2af277be9a..3b3991ab86 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' ' test_cmp expect actual ' +test_expect_success 'branchname D/F conflict rejected with targeted error message' ' + git clone . df-conflict-error && + git branch dir_conflict && + ( + cd df-conflict-error && + git update-ref refs/remotes/origin/dir_conflict/file HEAD && + test_must_fail git fetch 2>err && + test_grep "error: some local refs could not be updated; try running" err && + test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err && + git pack-refs --all && + test_must_fail git fetch 2>err-packed && + test_grep "error: some local refs could not be updated; try running" err-packed && + test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed + ) +' + test_expect_success 'fetching a one-level ref works' ' test_commit extra && git reset --hard HEAD^ && diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 4c3b32785d..5f16cbc58d 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -55,6 +55,21 @@ test_expect_success 'list refs from outside any repository' ' test_cmp expect actual ' + +test_expect_success 'list detached HEAD from outside any repository' ' + git clone --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" \ + update-ref --no-deref HEAD refs/heads/main && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" update-server-info && + cat >expect <<-EOF && + $(git rev-parse main) HEAD + $(git rev-parse main) refs/heads/main + EOF + nongit git ls-remote "$HTTPD_URL/dumb/repo-detached.git" >actual && + test_cmp expect actual +' + test_expect_success 'create password-protected repository' ' mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" && cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 5d5caa3f58..4af796de67 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -2,6 +2,7 @@ test_description='test http auth header and credential helper interop' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index 9da5134614..bb35b87071 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh @@ -2,6 +2,7 @@ test_description="test fetching through http proxy" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5581-http-curl-verbose.sh b/t/t5581-http-curl-verbose.sh index cded79c16b..724f610054 100755 --- a/t/t5581-http-curl-verbose.sh +++ b/t/t5581-http-curl-verbose.sh @@ -4,6 +4,7 @@ test_description='test GIT_CURL_VERBOSE' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index deb1c282c7..cc0b953f14 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -788,57 +788,6 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe git clone --filter=blob:limit=0 "file://$(pwd)/server" client ' -test_expect_success 'clone with init.templatedir runs hooks' ' - git init tmpl/hooks && - write_script tmpl/hooks/post-checkout <<-EOF && - echo HOOK-RUN >&2 - echo I was here >hook.run - EOF - git -C tmpl/hooks add . && - test_tick && - git -C tmpl/hooks commit -m post-checkout && - - test_when_finished "git config --global --unset init.templateDir || :" && - test_when_finished "git config --unset init.templateDir || :" && - ( - sane_unset GIT_TEMPLATE_DIR && - NO_SET_GIT_TEMPLATE_DIR=t && - export NO_SET_GIT_TEMPLATE_DIR && - - git -c core.hooksPath="$(pwd)/tmpl/hooks" \ - clone tmpl/hooks hook-run-hookspath 2>err && - test_grep ! "active .* hook found" err && - test_path_is_file hook-run-hookspath/hook.run && - - git -c init.templateDir="$(pwd)/tmpl" \ - clone tmpl/hooks hook-run-config 2>err && - test_grep ! "active .* hook found" err && - test_path_is_file hook-run-config/hook.run && - - git clone --template=tmpl tmpl/hooks hook-run-option 2>err && - test_grep ! "active .* hook found" err && - test_path_is_file hook-run-option/hook.run && - - git config --global init.templateDir "$(pwd)/tmpl" && - git clone tmpl/hooks hook-run-global-config 2>err && - git config --global --unset init.templateDir && - test_grep ! "active .* hook found" err && - test_path_is_file hook-run-global-config/hook.run && - - # clone ignores local `init.templateDir`; need to create - # a new repository because we deleted `.git/` in the - # `setup` test case above - git init local-clone && - cd local-clone && - - git config init.templateDir "$(pwd)/../tmpl" && - git clone ../tmpl/hooks hook-run-local-config 2>err && - git config --unset init.templateDir && - test_grep ! "active .* hook found" err && - test_path_is_missing hook-run-local-config/hook.run - ) -' - . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 573eb97a0f..f1623b1c06 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -8,6 +8,7 @@ test_description='git rev-list --pretty=format test' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh index 82013fc903..3946e18089 100755 --- a/t/t6041-bisect-submodule.sh +++ b/t/t6041-bisect-submodule.sh @@ -2,6 +2,7 @@ test_description='bisect can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 43e1afd44c..0387f35a32 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -701,4 +701,16 @@ test_expect_success 'expand blob limit in protocol' ' grep "blob:limit=1024" trace ' +test_expect_success EXPENSIVE 'large sparse filter file ignored' ' + blob=$(dd if=/dev/zero bs=101M count=1 | + git hash-object -w --stdin) && + test_must_fail \ + git rev-list --all --objects --filter=sparse:oid=$blob 2>err && + cat >expect <<-EOF && + warning: ignoring excessively large pattern blob: $blob + fatal: unable to parse sparse filter data in $blob + EOF + test_cmp expect err +' + test_done diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh index 3de4ef6bd9..27d6efdc9a 100755 --- a/t/t6400-merge-df.sh +++ b/t/t6400-merge-df.sh @@ -7,6 +7,7 @@ test_description='Test merge with directory/file conflicts' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare repository' ' diff --git a/t/t6412-merge-large-rename.sh b/t/t6412-merge-large-rename.sh index ca018d11f5..d0863a8fb5 100755 --- a/t/t6412-merge-large-rename.sh +++ b/t/t6412-merge-large-rename.sh @@ -4,6 +4,7 @@ test_description='merging with large rename matrix' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh count() { diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh index b059475ed0..62f0180325 100755 --- a/t/t6426-merge-skip-unneeded-updates.sh +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -22,6 +22,7 @@ test_description="merge cases" # underscore notation is to differentiate different # files that might be renamed into each other's paths.) +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 0f39ed0d08..cb1c4ceef7 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -2,6 +2,7 @@ test_description="remember regular & dir renames in sequence of merges" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh index 8df67a0ef9..3594190af8 100755 --- a/t/t6438-submodule-directory-file-conflicts.sh +++ b/t/t6438-submodule-directory-file-conflicts.sh @@ -2,6 +2,7 @@ test_description='merge can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 879a6dce60..86258f9f43 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git mv in subdirs' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-data.sh diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh index 26582ae4e5..57969ce805 100755 --- a/t/t7002-mv-sparse-checkout.sh +++ b/t/t7002-mv-sparse-checkout.sh @@ -2,6 +2,7 @@ test_description='git mv in sparse working trees' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh setup_sparse_checkout () { diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 5fcf281dfb..b9822294fe 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -2,6 +2,7 @@ test_description='GIT_EDITOR, core.editor, and stuff' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh unset EDITOR VISUAL GIT_EDITOR diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 62d9f846ce..2add26d768 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -10,6 +10,7 @@ Documented tests for git reset' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh commit_msg () { diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh index a3e2413bc3..b0d3d93b0b 100755 --- a/t/t7112-reset-submodule.sh +++ b/t/t7112-reset-submodule.sh @@ -2,6 +2,7 @@ test_description='reset can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh index 5e3051da8b..dbbb3853dc 100755 --- a/t/t7417-submodule-path-url.sh +++ b/t/t7417-submodule-path-url.sh @@ -4,6 +4,7 @@ test_description='check handling of .gitmodule path with dash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh index ce64d8b137..479c8fdde1 100755 --- a/t/t7421-submodule-summary-add.sh +++ b/t/t7421-submodule-summary-add.sh @@ -10,6 +10,7 @@ while making sure to add submodules using `git submodule add` instead of `git add` as done in t7401. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t7423-submodule-symlinks.sh b/t/t7423-submodule-symlinks.sh index 3d3c7af3ce..f45d806201 100755 --- a/t/t7423-submodule-symlinks.sh +++ b/t/t7423-submodule-symlinks.sh @@ -2,6 +2,7 @@ test_description='check that submodule operations do not follow symlinks' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare' ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 5a771000c9..58699f8e4e 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -2526,7 +2526,7 @@ test_expect_success $PREREQ 'test forbidSendmailVariables behavior override' ' test_expect_success $PREREQ '--compose handles lowercase headers' ' write_script fake-editor <<-\EOF && - sed "s/^From:.*/from: edited-from@example.com/i" "$1" >"$1.tmp" && + sed "s/^[Ff][Rr][Oo][Mm]:.*/from: edited-from@example.com/" "$1" >"$1.tmp" && mv "$1.tmp" "$1" EOF clean_fake_sendmail && diff --git a/t/t9004-example.sh b/t/t9004-example.sh deleted file mode 100755 index 590aab0304..0000000000 --- a/t/t9004-example.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -test_description='check that example code compiles and runs' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -test_expect_success 'decorate' ' - test-tool example-decorate -' - -test_done diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index d3261e35b8..a34fd46ecc 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -38,7 +38,7 @@ test_expect_success 'setup svnrepo' ' # SVN 1.7 will truncate "not-a%40{0]" to just "not-a". # Look at what SVN wound up naming the branch and use that. # Be sure to escape the @ if it shows up. -non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p }') +non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p; }') test_expect_success 'test clone with funky branch names' ' git svn clone -s "$svnrepo/pr ject" project && diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 185248a4cd..01e1e8a8f7 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -4,7 +4,6 @@ test_description='git svn honors i18n.commitEncoding in config' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh compare_git_head_with () { diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh index b7f756b2b7..22d80b0be2 100755 --- a/t/t9139-git-svn-non-utf8-commitencoding.sh +++ b/t/t9139-git-svn-non-utf8-commitencoding.sh @@ -4,7 +4,6 @@ test_description='git svn refuses to dcommit non-UTF8 messages' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh # ISO-2022-JP can pass for valid UTF-8, so skipping that in this test diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index a44eabf0d8..3d4842164c 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -4,6 +4,7 @@ # test_description='Test export of commits to CVS' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh if ! test_have_prereq PERL; then diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index a34805acdc..a67e6abd49 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -12,6 +12,7 @@ repository using cvs CLI client via git-cvsserver server' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh marked_as () { diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 5680849218..41fcf3606b 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -4,6 +4,7 @@ test_description='git cvsimport basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh if ! test_have_prereq NOT_ROOT; then diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh index 116cddba3a..e007669495 100755 --- a/t/t9601-cvsimport-vendor-branch.sh +++ b/t/t9601-cvsimport-vendor-branch.sh @@ -35,6 +35,7 @@ test_description='git cvsimport handling of vendor branches' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9601 diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh index e5266c9a87..3768e3bd8c 100755 --- a/t/t9602-cvsimport-branches-tags.sh +++ b/t/t9602-cvsimport-branches-tags.sh @@ -7,6 +7,7 @@ test_description='git cvsimport handling of branches and tags' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9602 diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh index 19f38f78f2..2a387fdbaa 100755 --- a/t/t9603-cvsimport-patchsets.sh +++ b/t/t9603-cvsimport-patchsets.sh @@ -12,6 +12,8 @@ # bug. test_description='git cvsimport testing for correct patchset estimation' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9603 diff --git a/t/t9604-cvsimport-timestamps.sh b/t/t9604-cvsimport-timestamps.sh index 2d03259729..9cf0685d56 100755 --- a/t/t9604-cvsimport-timestamps.sh +++ b/t/t9604-cvsimport-timestamps.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git cvsimport timestamps' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh test_lazy_prereq POSIX_TIMEZONE ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 963f865f27..932d5ad759 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -73,7 +73,7 @@ _get_comp_words_by_ref () print_comp () { local IFS=$'\n' - echo "${COMPREPLY[*]}" > out + printf '%s\n' "${COMPREPLY[*]}" > out } run_completion () @@ -2742,30 +2742,58 @@ do ' done -test_expect_success 'git config - section' ' - test_completion "git config br" <<-\EOF +test_expect_success 'git config subcommand' ' + test_completion "git config " <<-\EOF + edit Z + get Z + list Z + remove-section Z + rename-section Z + set Z + unset Z + EOF +' + +test_expect_success 'git config subcommand options' ' + test_completion "git config get --show-" <<-\EOF + --show-names Z + --show-origin Z + --show-scope Z + EOF +' + +test_expect_success 'git config get' ' + test_when_finished "rm -f cfgfile" && + git config set --file cfgfile foo.bar baz && + test_completion "git config get --file cfgfile foo." <<-\EOF + foo.bar Z + EOF +' + +test_expect_success 'git config set - section' ' + test_completion "git config set br" <<-\EOF branch.Z browser.Z EOF ' -test_expect_success 'git config - section include, includeIf' ' - test_completion "git config inclu" <<-\EOF +test_expect_success 'git config set - section include, includeIf' ' + test_completion "git config set inclu" <<-\EOF include.Z includeIf.Z EOF ' -test_expect_success 'git config - variable name' ' - test_completion "git config log.d" <<-\EOF +test_expect_success 'git config set - variable name' ' + test_completion "git config set log.d" <<-\EOF log.date Z log.decorate Z log.diffMerges Z EOF ' -test_expect_success 'git config - variable name include' ' - test_completion "git config include.p" <<-\EOF +test_expect_success 'git config set - variable name include' ' + test_completion "git config set include.p" <<-\EOF include.path Z EOF ' @@ -2776,8 +2804,8 @@ test_expect_success 'setup for git config submodule tests' ' git submodule add ./sub ' -test_expect_success 'git config - variable name - submodule and __git_compute_first_level_config_vars_for_section' ' - test_completion "git config submodule." <<-\EOF +test_expect_success 'git config set - variable name - submodule and __git_compute_first_level_config_vars_for_section' ' + test_completion "git config set submodule." <<-\EOF submodule.active Z submodule.alternateErrorStrategy Z submodule.alternateLocation Z @@ -2788,8 +2816,8 @@ test_expect_success 'git config - variable name - submodule and __git_compute_fi EOF ' -test_expect_success 'git config - variable name - __git_compute_second_level_config_vars_for_section' ' - test_completion "git config submodule.sub." <<-\EOF +test_expect_success 'git config set - variable name - __git_compute_second_level_config_vars_for_section' ' + test_completion "git config set submodule.sub." <<-\EOF submodule.sub.url Z submodule.sub.update Z submodule.sub.branch Z @@ -2799,8 +2827,8 @@ test_expect_success 'git config - variable name - __git_compute_second_level_con EOF ' -test_expect_success 'git config - value' ' - test_completion "git config color.pager " <<-\EOF +test_expect_success 'git config set - value' ' + test_completion "git config set color.pager " <<-\EOF false Z true Z EOF diff --git a/t/unit-tests/t-example-decorate.c b/t/unit-tests/t-example-decorate.c new file mode 100644 index 0000000000..3c856a8cf2 --- /dev/null +++ b/t/unit-tests/t-example-decorate.c @@ -0,0 +1,80 @@ +#include "test-lib.h" +#include "object.h" +#include "decorate.h" +#include "repository.h" + +struct test_vars { + struct object *one, *two, *three; + struct decoration n; + int decoration_a, decoration_b; +}; + +static void t_add(struct test_vars *vars) +{ + void *ret = add_decoration(&vars->n, vars->one, &vars->decoration_a); + + if (!check(ret == NULL)) + test_msg("when adding a brand-new object, NULL should be returned"); + ret = add_decoration(&vars->n, vars->two, NULL); + if (!check(ret == NULL)) + test_msg("when adding a brand-new object, NULL should be returned"); +} + +static void t_readd(struct test_vars *vars) +{ + void *ret = add_decoration(&vars->n, vars->one, NULL); + + if (!check(ret == &vars->decoration_a)) + test_msg("when readding an already existing object, existing decoration should be returned"); + ret = add_decoration(&vars->n, vars->two, &vars->decoration_b); + if (!check(ret == NULL)) + test_msg("when readding an already existing object, existing decoration should be returned"); +} + +static void t_lookup(struct test_vars *vars) +{ + void *ret = lookup_decoration(&vars->n, vars->one); + + if (!check(ret == NULL)) + test_msg("lookup should return added declaration"); + ret = lookup_decoration(&vars->n, vars->two); + if (!check(ret == &vars->decoration_b)) + test_msg("lookup should return added declaration"); + ret = lookup_decoration(&vars->n, vars->three); + if (!check(ret == NULL)) + test_msg("lookup for unknown object should return NULL"); +} + +static void t_loop(struct test_vars *vars) +{ + int i, objects_noticed = 0; + + for (i = 0; i < vars->n.size; i++) { + if (vars->n.entries[i].base) + objects_noticed++; + } + if (!check_int(objects_noticed, ==, 2)) + test_msg("should have 2 objects"); +} + +int cmd_main(int argc UNUSED, const char **argv UNUSED) +{ + struct object_id one_oid = { { 1 } }, two_oid = { { 2 } }, three_oid = { { 3 } }; + struct test_vars vars = { 0 }; + + vars.one = lookup_unknown_object(the_repository, &one_oid); + vars.two = lookup_unknown_object(the_repository, &two_oid); + vars.three = lookup_unknown_object(the_repository, &three_oid); + + TEST(t_add(&vars), + "Add 2 objects, one with a non-NULL decoration and one with a NULL decoration."); + TEST(t_readd(&vars), + "When re-adding an already existing object, the old decoration is returned."); + TEST(t_lookup(&vars), + "Lookup returns the added declarations, or NULL if the object was never added."); + TEST(t_loop(&vars), "The user can also loop through all entries."); + + clear_decoration(&vars.n, NULL); + + return test_done(); +} diff --git a/t/unit-tests/t-hash.c b/t/unit-tests/t-hash.c new file mode 100644 index 0000000000..e9a78bf2c0 --- /dev/null +++ b/t/unit-tests/t-hash.c @@ -0,0 +1,84 @@ +#include "test-lib.h" +#include "hex.h" +#include "strbuf.h" + +static void check_hash_data(const void *data, size_t data_length, + const char *expected_hashes[]) +{ + if (!check(data != NULL)) { + test_msg("BUG: NULL data pointer provided"); + return; + } + + for (size_t i = 1; i < ARRAY_SIZE(hash_algos); i++) { + git_hash_ctx ctx; + unsigned char hash[GIT_MAX_HEXSZ]; + const struct git_hash_algo *algop = &hash_algos[i]; + + algop->init_fn(&ctx); + algop->update_fn(&ctx, data, data_length); + algop->final_fn(hash, &ctx); + + if (!check_str(hash_to_hex_algop(hash, algop), expected_hashes[i - 1])) + test_msg("result does not match with the expected for %s\n", hash_algos[i].name); + } +} + +/* Works with a NUL terminated string. Doesn't work if it should contain a NUL character. */ +#define TEST_HASH_STR(data, expected_sha1, expected_sha256) do { \ + const char *expected_hashes[] = { expected_sha1, expected_sha256 }; \ + TEST(check_hash_data(data, strlen(data), expected_hashes), \ + "SHA1 and SHA256 (%s) works", #data); \ + } while (0) + +/* Only works with a literal string, useful when it contains a NUL character. */ +#define TEST_HASH_LITERAL(literal, expected_sha1, expected_sha256) do { \ + const char *expected_hashes[] = { expected_sha1, expected_sha256 }; \ + TEST(check_hash_data(literal, (sizeof(literal) - 1), expected_hashes), \ + "SHA1 and SHA256 (%s) works", #literal); \ + } while (0) + +int cmd_main(int argc, const char **argv) +{ + struct strbuf aaaaaaaaaa_100000 = STRBUF_INIT; + struct strbuf alphabet_100000 = STRBUF_INIT; + + strbuf_addstrings(&aaaaaaaaaa_100000, "aaaaaaaaaa", 100000); + strbuf_addstrings(&alphabet_100000, "abcdefghijklmnopqrstuvwxyz", 100000); + + TEST_HASH_STR("", + "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + TEST_HASH_STR("a", + "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", + "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"); + TEST_HASH_STR("abc", + "a9993e364706816aba3e25717850c26c9cd0d89d", + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + TEST_HASH_STR("message digest", + "c12252ceda8be8994d5fa0290a47231c1d16aae3", + "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"); + TEST_HASH_STR("abcdefghijklmnopqrstuvwxyz", + "32d10c7b8cf96570ca04ce37f2a19d84240d3a89", + "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"); + TEST_HASH_STR(aaaaaaaaaa_100000.buf, + "34aa973cd4c4daa4f61eeb2bdbad27316534016f", + "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0"); + TEST_HASH_STR(alphabet_100000.buf, + "e7da7c55b3484fdf52aebec9cbe7b85a98f02fd4", + "e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35"); + TEST_HASH_LITERAL("blob 0\0", + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813"); + TEST_HASH_LITERAL("blob 3\0abc", + "f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f", + "c1cf6e465077930e88dc5136641d402f72a229ddd996f627d60e9639eaba35a6"); + TEST_HASH_LITERAL("tree 0\0", + "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321"); + + strbuf_release(&aaaaaaaaaa_100000); + strbuf_release(&alphabet_100000); + + return test_done(); +} diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c new file mode 100644 index 0000000000..529049af12 --- /dev/null +++ b/t/unit-tests/t-reftable-basics.c @@ -0,0 +1,160 @@ +/* +Copyright 2020 Google LLC + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +#include "test-lib.h" +#include "reftable/basics.h" + +struct integer_needle_lesseq_args { + int needle; + int *haystack; +}; + +static int integer_needle_lesseq(size_t i, void *_args) +{ + struct integer_needle_lesseq_args *args = _args; + return args->needle <= args->haystack[i]; +} + +static void test_binsearch(void) +{ + int haystack[] = { 2, 4, 6, 8, 10 }; + struct { + int needle; + size_t expected_idx; + } testcases[] = { + {-9000, 0}, + {-1, 0}, + {0, 0}, + {2, 0}, + {3, 1}, + {4, 1}, + {7, 3}, + {9, 4}, + {10, 4}, + {11, 5}, + {9000, 5}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) { + struct integer_needle_lesseq_args args = { + .haystack = haystack, + .needle = testcases[i].needle, + }; + size_t idx; + + idx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args); + check_int(idx, ==, testcases[i].expected_idx); + } +} + +static void test_names_length(void) +{ + char *a[] = { "a", "b", NULL }; + check_int(names_length(a), ==, 2); +} + +static void test_names_equal(void) +{ + char *a[] = { "a", "b", "c", NULL }; + char *b[] = { "a", "b", "d", NULL }; + char *c[] = { "a", "b", NULL }; + + check(names_equal(a, a)); + check(!names_equal(a, b)); + check(!names_equal(a, c)); +} + +static void test_parse_names_normal(void) +{ + char in1[] = "line\n"; + char in2[] = "a\nb\nc"; + char **out = NULL; + parse_names(in1, strlen(in1), &out); + check_str(out[0], "line"); + check(!out[1]); + free_names(out); + + parse_names(in2, strlen(in2), &out); + check_str(out[0], "a"); + check_str(out[1], "b"); + check_str(out[2], "c"); + check(!out[3]); + free_names(out); +} + +static void test_parse_names_drop_empty(void) +{ + char in[] = "a\n\nb\n"; + char **out = NULL; + parse_names(in, strlen(in), &out); + check_str(out[0], "a"); + /* simply '\n' should be dropped as empty string */ + check_str(out[1], "b"); + check(!out[2]); + free_names(out); +} + +static void test_common_prefix(void) +{ + struct strbuf a = STRBUF_INIT; + struct strbuf b = STRBUF_INIT; + struct { + const char *a, *b; + int want; + } cases[] = { + {"abcdef", "abc", 3}, + { "abc", "ab", 2 }, + { "", "abc", 0 }, + { "abc", "abd", 2 }, + { "abc", "pqr", 0 }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(cases); i++) { + strbuf_addstr(&a, cases[i].a); + strbuf_addstr(&b, cases[i].b); + check_int(common_prefix_size(&a, &b), ==, cases[i].want); + strbuf_reset(&a); + strbuf_reset(&b); + } + strbuf_release(&a); + strbuf_release(&b); +} + +static void test_u24_roundtrip(void) +{ + uint32_t in = 0x112233; + uint8_t dest[3]; + uint32_t out; + put_be24(dest, in); + out = get_be24(dest); + check_int(in, ==, out); +} + +static void test_u16_roundtrip(void) +{ + uint32_t in = 0xfef1; + uint8_t dest[3]; + uint32_t out; + put_be16(dest, in); + out = get_be16(dest); + check_int(in, ==, out); +} + +int cmd_main(int argc, const char *argv[]) +{ + TEST(test_common_prefix(), "common_prefix_size works"); + TEST(test_parse_names_normal(), "parse_names works for basic input"); + TEST(test_parse_names_drop_empty(), "parse_names drops empty string"); + TEST(test_binsearch(), "binary search with binsearch works"); + TEST(test_names_length(), "names_length retuns size of a NULL-terminated string array"); + TEST(test_names_equal(), "names_equal compares NULL-terminated string arrays"); + TEST(test_u24_roundtrip(), "put_be24 and get_be24 work"); + TEST(test_u16_roundtrip(), "put_be16 and get_be16 work"); + + return test_done(); +} diff --git a/t/unit-tests/t-strcmp-offset.c b/t/unit-tests/t-strcmp-offset.c new file mode 100644 index 0000000000..fe4c2706b1 --- /dev/null +++ b/t/unit-tests/t-strcmp-offset.c @@ -0,0 +1,35 @@ +#include "test-lib.h" +#include "read-cache-ll.h" + +static void check_strcmp_offset(const char *string1, const char *string2, + int expect_result, uintmax_t expect_offset) +{ + size_t offset; + int result = strcmp_offset(string1, string2, &offset); + + /* + * Because different CRTs behave differently, only rely on signs of the + * result values. + */ + result = (result < 0 ? -1 : + result > 0 ? 1 : + 0); + + check_int(result, ==, expect_result); + check_uint((uintmax_t)offset, ==, expect_offset); +} + +#define TEST_STRCMP_OFFSET(string1, string2, expect_result, expect_offset) \ + TEST(check_strcmp_offset(string1, string2, expect_result, \ + expect_offset), \ + "strcmp_offset(%s, %s) works", #string1, #string2) + +int cmd_main(int argc, const char **argv) +{ + TEST_STRCMP_OFFSET("abc", "abc", 0, 3); + TEST_STRCMP_OFFSET("abc", "def", -1, 0); + TEST_STRCMP_OFFSET("abc", "abz", -1, 2); + TEST_STRCMP_OFFSET("abc", "abcdef", -1, 3); + + return test_done(); +} diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c new file mode 100644 index 0000000000..d4615ab06d --- /dev/null +++ b/t/unit-tests/t-strvec.c @@ -0,0 +1,272 @@ +#include "test-lib.h" +#include "strbuf.h" +#include "strvec.h" + +#define check_strvec(vec, ...) \ + check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__) +LAST_ARG_MUST_BE_NULL +static void check_strvec_loc(const char *loc, struct strvec *vec, ...) +{ + va_list ap; + size_t nr = 0; + + va_start(ap, vec); + while (1) { + const char *str = va_arg(ap, const char *); + if (!str) + break; + + if (!check_uint(vec->nr, >, nr) || + !check_uint(vec->alloc, >, nr) || + !check_str(vec->v[nr], str)) { + struct strbuf msg = STRBUF_INIT; + strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr); + test_assert(loc, msg.buf, 0); + strbuf_release(&msg); + va_end(ap); + return; + } + + nr++; + } + va_end(ap); + + check_uint(vec->nr, ==, nr); + check_uint(vec->alloc, >=, nr); + check_pointer_eq(vec->v[nr], NULL); +} + +static void t_static_init(void) +{ + struct strvec vec = STRVEC_INIT; + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_dynamic_init(void) +{ + struct strvec vec; + strvec_init(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_clear(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_push(&vec, "foo"); + strvec_clear(&vec); + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); +} + +static void t_push(void) +{ + struct strvec vec = STRVEC_INIT; + + strvec_push(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + + strvec_push(&vec, "bar"); + check_strvec(&vec, "foo", "bar", NULL); + + strvec_clear(&vec); +} + +static void t_pushf(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushf(&vec, "foo: %d", 1); + check_strvec(&vec, "foo: 1", NULL); + strvec_clear(&vec); +} + +static void t_pushl(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_pushv(void) +{ + const char *strings[] = { + "foo", "bar", "baz", NULL, + }; + struct strvec vec = STRVEC_INIT; + + strvec_pushv(&vec, strings); + check_strvec(&vec, "foo", "bar", "baz", NULL); + + strvec_clear(&vec); +} + +static void t_replace_at_head(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 0, "replaced"); + check_strvec(&vec, "replaced", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_replace_at_tail(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 2, "replaced"); + check_strvec(&vec, "foo", "bar", "replaced", NULL); + strvec_clear(&vec); +} + +static void t_replace_in_between(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 1, "replaced"); + check_strvec(&vec, "foo", "replaced", "baz", NULL); + strvec_clear(&vec); +} + +static void t_replace_with_substring(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", NULL); + strvec_replace(&vec, 0, vec.v[0] + 1); + check_strvec(&vec, "oo", NULL); + strvec_clear(&vec); +} + +static void t_remove_at_head(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 0); + check_strvec(&vec, "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_remove_at_tail(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 2); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_remove_in_between(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 1); + check_strvec(&vec, "foo", "baz", NULL); + strvec_clear(&vec); +} + +static void t_pop_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pop(&vec); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_pop_non_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_pop(&vec); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_split_empty_string(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, ""); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_split_single_item(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + strvec_clear(&vec); +} + +static void t_split_multiple_items(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo bar baz"); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); +} + +static void t_split_whitespace_only(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, " \t\n"); + check_strvec(&vec, NULL); + strvec_clear(&vec); +} + +static void t_split_multiple_consecutive_whitespaces(void) +{ + struct strvec vec = STRVEC_INIT; + strvec_split(&vec, "foo\n\t bar"); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); +} + +static void t_detach(void) +{ + struct strvec vec = STRVEC_INIT; + const char **detached; + + strvec_push(&vec, "foo"); + + detached = strvec_detach(&vec); + check_str(detached[0], "foo"); + check_pointer_eq(detached[1], NULL); + + check_pointer_eq(vec.v, empty_strvec); + check_uint(vec.nr, ==, 0); + check_uint(vec.alloc, ==, 0); + + free((char *) detached[0]); + free(detached); +} + +int cmd_main(int argc, const char **argv) +{ + TEST(t_static_init(), "static initialization"); + TEST(t_dynamic_init(), "dynamic initialization"); + TEST(t_clear(), "clear"); + TEST(t_push(), "push"); + TEST(t_pushf(), "pushf"); + TEST(t_pushl(), "pushl"); + TEST(t_pushv(), "pushv"); + TEST(t_replace_at_head(), "replace at head"); + TEST(t_replace_in_between(), "replace in between"); + TEST(t_replace_at_tail(), "replace at tail"); + TEST(t_replace_with_substring(), "replace with substring"); + TEST(t_remove_at_head(), "remove at head"); + TEST(t_remove_in_between(), "remove in between"); + TEST(t_remove_at_tail(), "remove at tail"); + TEST(t_pop_empty_array(), "pop with empty array"); + TEST(t_pop_non_empty_array(), "pop with non-empty array"); + TEST(t_split_empty_string(), "split empty string"); + TEST(t_split_single_item(), "split single item"); + TEST(t_split_multiple_items(), "split multiple items"); + TEST(t_split_whitespace_only(), "split whitespace only"); + TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces"); + TEST(t_detach(), "detach"); + return test_done(); +} diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c new file mode 100644 index 0000000000..2ecca359d9 --- /dev/null +++ b/t/unit-tests/t-trailer.c @@ -0,0 +1,315 @@ +#include "test-lib.h" +#include "trailer.h" + +struct contents { + const char *raw; + const char *key; + const char *val; +}; + +static void t_trailer_iterator(const char *msg, size_t num_expected, + struct contents *contents) +{ + struct trailer_iterator iter; + size_t i = 0; + + trailer_iterator_init(&iter, msg); + while (trailer_iterator_advance(&iter)) { + if (num_expected) { + check_str(iter.raw, contents[i].raw); + check_str(iter.key.buf, contents[i].key); + check_str(iter.val.buf, contents[i].val); + } + i++; + } + trailer_iterator_release(&iter); + + check_uint(i, ==, num_expected); +} + +static void run_t_trailer_iterator(void) +{ + + static struct test_cases { + const char *name; + const char *msg; + size_t num_expected; + struct contents contents[10]; + } tc[] = { + { + "empty input", + "", + 0, + {{0}}, + }, + { + "no newline at beginning", + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 0, + {{0}}, + }, + { + "newline at beginning", + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 3, + { + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "without body text", + "subject: foo bar\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n", + 3, + { + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "with body text, without divider", + "my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n" + "Signed-off-by: x\n", + 4, + { + { + .raw = "Fixes: x\n", + .key = "Fixes", + .val = "x", + }, + { + .raw = "Acked-by: x\n", + .key = "Acked-by", + .val = "x", + }, + { + .raw = "Reviewed-by: x\n", + .key = "Reviewed-by", + .val = "x", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "with body text, without divider (second trailer block)", + "my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "Fixes: x\n" + "Acked-by: x\n" + "Reviewed-by: x\n" + "Signed-off-by: x\n" + "\n" + /* + * Because this is the last trailer block, it takes + * precedence over the first one encountered above. + */ + "Helped-by: x\n" + "Signed-off-by: x\n", + 2, + { + { + .raw = "Helped-by: x\n", + .key = "Helped-by", + .val = "x", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "with body text, with divider", + "my subject\n" + "\n" + "my body which is long\n" + "and contains some special\n" + "chars like : = ? !\n" + "hello\n" + "\n" + "---\n" + "\n" + /* + * This trailer still counts because the iterator + * always ignores the divider. + */ + "Signed-off-by: x\n", + 1, + { + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "with non-trailer lines in trailer block", + "subject: foo bar\n" + "\n" + /* + * Even though this trailer block has a non-trailer line + * in it, it's still a valid trailer block because it's + * at least 25% trailers and is Git-generated (see + * git_generated_prefixes[] in trailer.c). + */ + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "Signed-off-by: x\n", + /* + * Even though there is only really 1 real "trailer" + * (Signed-off-by), we still have 4 trailer objects + * because we still want to iterate through the entire + * block. + */ + 4, + { + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "not a trailer line\n", + .key = "not a trailer line", + .val = "", + }, + { + .raw = "Signed-off-by: x\n", + .key = "Signed-off-by", + .val = "x", + }, + { + 0 + }, + }, + }, + { + "with non-trailer lines (one too many) in trailer block", + "subject: foo bar\n" + "\n" + /* + * This block has only 20% trailers, so it's below the + * 25% threshold. + */ + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "not a trailer line\n" + "Signed-off-by: x\n", + 0, + {{0}}, + }, + { + "with non-trailer lines (only 1) in trailer block, but no Git-generated trailers", + "subject: foo bar\n" + "\n" + /* + * This block has only 1 non-trailer out of 10 (IOW, 90% + * trailers) but is not considered a trailer block + * because the 25% threshold only applies to cases where + * there was a Git-generated trailer. + */ + "Reviewed-by: x\n" + "Reviewed-by: x\n" + "Reviewed-by: x\n" + "Helped-by: x\n" + "Helped-by: x\n" + "Helped-by: x\n" + "Acked-by: x\n" + "Acked-by: x\n" + "Acked-by: x\n" + "not a trailer line\n", + 0, + {{0}}, + }, + }; + + for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) { + TEST(t_trailer_iterator(tc[i].msg, + tc[i].num_expected, + tc[i].contents), + "%s", tc[i].name); + } +} + +int cmd_main(int argc, const char **argv) +{ + run_t_trailer_iterator(); + return test_done(); +} diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c index 66d6980ffb..3c513ce59a 100644 --- a/t/unit-tests/test-lib.c +++ b/t/unit-tests/test-lib.c @@ -318,6 +318,19 @@ int check_bool_loc(const char *loc, const char *check, int ok) union test__tmp test__tmp[2]; +int check_pointer_eq_loc(const char *loc, const char *check, int ok, + const void *a, const void *b) +{ + int ret = test_assert(loc, check, ok); + + if (!ret) { + test_msg(" left: %p", a); + test_msg(" right: %p", b); + } + + return ret; +} + int check_int_loc(const char *loc, const char *check, int ok, intmax_t a, intmax_t b) { diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h index a8f07ae0b7..2de6d715d5 100644 --- a/t/unit-tests/test-lib.h +++ b/t/unit-tests/test-lib.h @@ -79,6 +79,18 @@ int check_bool_loc(const char *loc, const char *check, int ok); * Compare two integers. Prints a message with the two values if the * comparison fails. NB this is not thread safe. */ +#define check_pointer_eq(a, b) \ + (test__tmp[0].p = (a), test__tmp[1].p = (b), \ + check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \ + test__tmp[0].p == test__tmp[1].p, \ + test__tmp[0].p, test__tmp[1].p)) +int check_pointer_eq_loc(const char *loc, const char *check, int ok, + const void *a, const void *b); + +/* + * Compare two integers. Prints a message with the two values if the + * comparison fails. NB this is not thread safe. + */ #define check_int(a, op, b) \ (test__tmp[0].i = (a), test__tmp[1].i = (b), \ check_int_loc(TEST_LOCATION(), #a" "#op" "#b, \ @@ -136,6 +148,7 @@ union test__tmp { intmax_t i; uintmax_t u; char c; + const void *p; }; extern union test__tmp test__tmp[2]; @@ -11,6 +11,27 @@ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> */ +struct trailer_info { + /* + * True if there is a blank line before the location pointed to by + * trailer_block_start. + */ + int blank_line_before_trailer; + + /* + * Offsets to the trailer block start and end positions in the input + * string. If no trailer block is found, these are both set to the + * "true" end of the input (find_end_of_log_message()). + */ + size_t trailer_block_start, trailer_block_end; + + /* + * Array of trailers found. + */ + char **trailers; + size_t trailer_nr; +}; + struct conf_info { char *name; char *key; @@ -952,20 +973,72 @@ static void unfold_value(struct strbuf *val) strbuf_release(&out); } +static struct trailer_info *trailer_info_new(void) +{ + struct trailer_info *info = xcalloc(1, sizeof(*info)); + return info; +} + +static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts, + const char *str) +{ + struct trailer_info *info = trailer_info_new(); + size_t end_of_log_message = 0, trailer_block_start = 0; + struct strbuf **trailer_lines, **ptr; + char **trailer_strings = NULL; + size_t nr = 0, alloc = 0; + char **last = NULL; + + trailer_config_init(); + + end_of_log_message = find_end_of_log_message(str, opts->no_divider); + trailer_block_start = find_trailer_block_start(str, end_of_log_message); + + trailer_lines = strbuf_split_buf(str + trailer_block_start, + end_of_log_message - trailer_block_start, + '\n', + 0); + for (ptr = trailer_lines; *ptr; ptr++) { + if (last && isspace((*ptr)->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_attach(&sb, *last, strlen(*last), strlen(*last)); + strbuf_addbuf(&sb, *ptr); + *last = strbuf_detach(&sb, NULL); + continue; + } + ALLOC_GROW(trailer_strings, nr + 1, alloc); + trailer_strings[nr] = strbuf_detach(*ptr, NULL); + last = find_separator(trailer_strings[nr], separators) >= 1 + ? &trailer_strings[nr] + : NULL; + nr++; + } + strbuf_list_free(trailer_lines); + + info->blank_line_before_trailer = ends_with_blank_line(str, + trailer_block_start); + info->trailer_block_start = trailer_block_start; + info->trailer_block_end = end_of_log_message; + info->trailers = trailer_strings; + info->trailer_nr = nr; + + return info; +} + /* - * Parse trailers in "str", populating the trailer info and "head" + * Parse trailers in "str", populating the trailer info and "trailer_objects" * linked list structure. */ -void parse_trailers(const struct process_trailer_options *opts, - struct trailer_info *info, - const char *str, - struct list_head *head) +struct trailer_info *parse_trailers(const struct process_trailer_options *opts, + const char *str, + struct list_head *trailer_objects) { + struct trailer_info *info; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; size_t i; - trailer_info_get(opts, str, info); + info = trailer_info_get(opts, str); for (i = 0; i < info->trailer_nr; i++) { int separator_pos; @@ -978,17 +1051,19 @@ void parse_trailers(const struct process_trailer_options *opts, separator_pos); if (opts->unfold) unfold_value(&val); - add_trailer_item(head, + add_trailer_item(trailer_objects, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); } else if (!opts->only_trailers) { strbuf_addstr(&val, trailer); strbuf_strip_suffix(&val, "\n"); - add_trailer_item(head, + add_trailer_item(trailer_objects, NULL, strbuf_detach(&val, NULL)); } } + + return info; } void free_trailers(struct list_head *trailers) @@ -1000,48 +1075,19 @@ void free_trailers(struct list_head *trailers) } } -void trailer_info_get(const struct process_trailer_options *opts, - const char *str, - struct trailer_info *info) +size_t trailer_block_start(struct trailer_info *info) { - size_t end_of_log_message = 0, trailer_block_start = 0; - struct strbuf **trailer_lines, **ptr; - char **trailer_strings = NULL; - size_t nr = 0, alloc = 0; - char **last = NULL; - - trailer_config_init(); - - end_of_log_message = find_end_of_log_message(str, opts->no_divider); - trailer_block_start = find_trailer_block_start(str, end_of_log_message); + return info->trailer_block_start; +} - trailer_lines = strbuf_split_buf(str + trailer_block_start, - end_of_log_message - trailer_block_start, - '\n', - 0); - for (ptr = trailer_lines; *ptr; ptr++) { - if (last && isspace((*ptr)->buf[0])) { - struct strbuf sb = STRBUF_INIT; - strbuf_attach(&sb, *last, strlen(*last), strlen(*last)); - strbuf_addbuf(&sb, *ptr); - *last = strbuf_detach(&sb, NULL); - continue; - } - ALLOC_GROW(trailer_strings, nr + 1, alloc); - trailer_strings[nr] = strbuf_detach(*ptr, NULL); - last = find_separator(trailer_strings[nr], separators) >= 1 - ? &trailer_strings[nr] - : NULL; - nr++; - } - strbuf_list_free(trailer_lines); +size_t trailer_block_end(struct trailer_info *info) +{ + return info->trailer_block_end; +} - info->blank_line_before_trailer = ends_with_blank_line(str, - trailer_block_start); - info->trailer_block_start = trailer_block_start; - info->trailer_block_end = end_of_log_message; - info->trailers = trailer_strings; - info->trailer_nr = nr; +int blank_line_before_trailer_block(struct trailer_info *info) +{ + return info->blank_line_before_trailer; } void trailer_info_release(struct trailer_info *info) @@ -1050,6 +1096,7 @@ void trailer_info_release(struct trailer_info *info) for (i = 0; i < info->trailer_nr; i++) free(info->trailers[i]); free(info->trailers); + free(info); } void format_trailers(const struct process_trailer_options *opts, @@ -1117,21 +1164,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts, struct strbuf *out) { LIST_HEAD(trailer_objects); - struct trailer_info info; - - parse_trailers(opts, &info, msg, &trailer_objects); + struct trailer_info *info = parse_trailers(opts, msg, &trailer_objects); /* If we want the whole block untouched, we can take the fast path. */ if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator && !opts->key_only && !opts->value_only && !opts->key_value_separator) { - strbuf_add(out, msg + info.trailer_block_start, - info.trailer_block_end - info.trailer_block_start); + strbuf_add(out, msg + info->trailer_block_start, + info->trailer_block_end - info->trailer_block_start); } else format_trailers(opts, &trailer_objects, out); free_trailers(&trailer_objects); - trailer_info_release(&info); + trailer_info_release(info); } void trailer_iterator_init(struct trailer_iterator *iter, const char *msg) @@ -1140,23 +1185,21 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg) strbuf_init(&iter->key, 0); strbuf_init(&iter->val, 0); opts.no_divider = 1; - trailer_info_get(&opts, msg, &iter->internal.info); + iter->internal.info = trailer_info_get(&opts, msg); iter->internal.cur = 0; } int trailer_iterator_advance(struct trailer_iterator *iter) { - while (iter->internal.cur < iter->internal.info.trailer_nr) { - char *trailer = iter->internal.info.trailers[iter->internal.cur++]; - int separator_pos = find_separator(trailer, separators); - - if (separator_pos < 1) - continue; /* not a real trailer */ + if (iter->internal.cur < iter->internal.info->trailer_nr) { + char *line = iter->internal.info->trailers[iter->internal.cur++]; + int separator_pos = find_separator(line, separators); + iter->raw = line; strbuf_reset(&iter->key); strbuf_reset(&iter->val); parse_trailer(&iter->key, &iter->val, NULL, - trailer, separator_pos); + line, separator_pos); /* Always unfold values during iteration. */ unfold_value(&iter->val); return 1; @@ -1166,7 +1209,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter) void trailer_iterator_release(struct trailer_iterator *iter) { - trailer_info_release(&iter->internal.info); + trailer_info_release(iter->internal.info); strbuf_release(&iter->val); strbuf_release(&iter->key); } @@ -4,6 +4,7 @@ #include "list.h" #include "strbuf.h" +struct trailer_info; struct strvec; enum trailer_where { @@ -31,27 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value); int trailer_set_if_exists(enum trailer_if_exists *item, const char *value); int trailer_set_if_missing(enum trailer_if_missing *item, const char *value); -struct trailer_info { - /* - * True if there is a blank line before the location pointed to by - * trailer_block_start. - */ - int blank_line_before_trailer; - - /* - * Offsets to the trailer block start and end positions in the input - * string. If no trailer block is found, these are both set to the - * "true" end of the input (find_end_of_log_message()). - */ - size_t trailer_block_start, trailer_block_end; - - /* - * Array of trailers found. - */ - char **trailers; - size_t trailer_nr; -}; - /* * A list that represents newly-added trailers, such as those provided * with the --trailer command line option of git-interpret-trailers. @@ -91,15 +71,63 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head, void process_trailers_lists(struct list_head *head, struct list_head *arg_head); -void parse_trailers(const struct process_trailer_options *, - struct trailer_info *, - const char *str, - struct list_head *head); +/* + * Given some input string "str", return a pointer to an opaque trailer_info + * structure. Also populate the trailer_objects list with parsed trailer + * objects. Internally this calls trailer_info_get() to get the opaque pointer, + * but does some extra work to populate the trailer_objects linked list. + * + * The opaque trailer_info pointer can be used to check the position of the + * trailer block as offsets relative to the beginning of "str" in + * trailer_block_start() and trailer_block_end(). + * blank_line_before_trailer_block() returns 1 if there is a blank line just + * before the trailer block. All of these functions are useful for preserving + * the input before and after the trailer block, if we were to write out the + * original input (but with the trailer block itself modified); see + * builtin/interpret-trailers.c for an example. + * + * For iterating through the parsed trailer block (if you don't care about the + * position of the trailer block itself in the context of the larger string text + * from which it was parsed), please see trailer_iterator_init() which uses the + * trailer_info struct internally. + * + * Lastly, callers should call trailer_info_release() when they are done using + * the opaque pointer. + * + * NOTE: Callers should treat both trailer_info and trailer_objects as + * read-only items, because there is some overlap between the two (trailer_info + * has "char **trailers" string array, and trailer_objects will have the same + * data but as a linked list of trailer_item objects). This API does not perform + * any synchronization between the two. In the future we should be able to + * reduce the duplication and use just the linked list. + */ +struct trailer_info *parse_trailers(const struct process_trailer_options *, + const char *str, + struct list_head *trailer_objects); + +/* + * Return the offset of the start of the trailer block. That is, 0 is the start + * of the input ("str" in parse_trailers()) and some other positive number + * indicates how many bytes we have to skip over before we get to the beginning + * of the trailer block. + */ +size_t trailer_block_start(struct trailer_info *); + +/* + * Return the end of the trailer block, again relative to the start of the + * input. + */ +size_t trailer_block_end(struct trailer_info *); -void trailer_info_get(const struct process_trailer_options *, - const char *str, - struct trailer_info *); +/* + * Return 1 if the trailer block had an extra newline (blank line) just before + * it. + */ +int blank_line_before_trailer_block(struct trailer_info *); +/* + * Free trailer_info struct. + */ void trailer_info_release(struct trailer_info *info); void trailer_config_init(void); @@ -127,12 +155,19 @@ void format_trailers_from_commit(const struct process_trailer_options *, * trailer_iterator_release(&iter); */ struct trailer_iterator { + /* + * Raw line (e.g., "foo: bar baz") before being parsed as a trailer + * key/val pair as part of a trailer block (as the "key" and "val" + * fields below). If a line fails to parse as a trailer, then the "key" + * will be the entire line and "val" will be the empty string. + */ + const char *raw; struct strbuf key; struct strbuf val; /* private */ struct { - struct trailer_info info; + struct trailer_info *info; size_t cur; } internal; }; diff --git a/transport-helper.c b/transport-helper.c index 780fcaf529..9820947ab2 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -22,7 +22,7 @@ static int debug; struct helper_data { - const char *name; + char *name; struct child_process *helper; FILE *out; unsigned fetch : 1, @@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data; transport_take_over(transport, data->helper); fclose(data->out); + free(data->name); free(data); } @@ -253,6 +254,7 @@ static int disconnect_helper(struct transport *transport) close(data->helper->out); fclose(data->out); res = finish_command(data->helper); + FREE_AND_NULL(data->name); FREE_AND_NULL(data->helper); } return res; @@ -1297,7 +1299,7 @@ static struct transport_vtable vtable = { int transport_helper_init(struct transport *transport, const char *name) { struct helper_data *data = xcalloc(1, sizeof(*data)); - data->name = name; + data->name = xstrdup(name); transport_check_allowed(name); diff --git a/transport.c b/transport.c index 0ad04b77fd..83ddea8fbc 100644 --- a/transport.c +++ b/transport.c @@ -1176,6 +1176,7 @@ struct transport *transport_get(struct remote *remote, const char *url) int len = external_specification_len(url); char *handler = xmemdupz(url, len); transport_helper_init(ret, handler); + free(handler); } if (ret->smart_options) { diff --git a/upload-pack.c b/upload-pack.c index bbfb04c8bd..b726f7a57d 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -94,7 +94,7 @@ struct upload_pack_data { struct packet_writer writer; - const char *pack_objects_hook; + char *pack_objects_hook; unsigned stateless_rpc : 1; /* v0 only */ unsigned no_done : 1; /* v0 only */ diff --git a/userdiff.h b/userdiff.h index d726804c3e..cc8e5abfef 100644 --- a/userdiff.h +++ b/userdiff.h @@ -7,19 +7,19 @@ struct index_state; struct repository; struct userdiff_funcname { - const char *pattern; + char *pattern; int cflags; }; struct userdiff_driver { const char *name; - const char *external; - const char *algorithm; + char *external; + char *algorithm; int binary; struct userdiff_funcname funcname; - const char *word_regex; - const char *word_regex_multi_byte; - const char *textconv; + char *word_regex; + char *word_regex_multi_byte; + char *textconv; struct notes_cache *textconv_cache; int textconv_want_cache; }; |
