From: Junio C Hamano Date: Mon, 10 Oct 2022 17:08:39 +0000 (-0700) Subject: Merge branch 'jk/clone-allow-bare-and-o-together' X-Git-Tag: v2.39.0-rc0~134 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7aeb0d4c47878a04e19cffe2ddf20d642df56414;hp=3b910d6e2911a48a2a6945094f79dacd5367b799;p=thirdparty%2Fgit.git Merge branch 'jk/clone-allow-bare-and-o-together' "git clone" did not like to see the "--bare" and the "--origin" options used together without a good reason. * jk/clone-allow-bare-and-o-together: clone: allow "--bare" with "-o" --- diff --git a/.gitignore b/.gitignore index a452215764..62720c6135 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -/fuzz-commit-graph /fuzz_corpora -/fuzz-pack-headers -/fuzz-pack-idx /GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS @@ -53,6 +50,7 @@ /git-cvsimport /git-cvsserver /git-daemon +/git-diagnose /git-diff /git-diff-files /git-diff-index @@ -180,11 +178,14 @@ /git-verify-commit /git-verify-pack /git-verify-tag +/git-version /git-web--browse /git-whatchanged /git-worktree /git-write-tree +/scalar /git-core-*/?* +/git.res /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi /gitweb/static/gitweb.js @@ -225,7 +226,6 @@ *.hcc *.obj *.lib -*.res *.sln *.sp *.suo diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 4c756be517..cb7a367ea0 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -162,8 +162,6 @@ For shell scripts specifically (not exhaustive): - We do not use \{m,n\}; - - We do not use -E; - - We do not use ? or + (which are \{0,1\} and \{1,\} respectively in BRE) but that goes without saying as these are ERE elements not BRE (note that \? and \+ are not even part @@ -606,7 +604,7 @@ Writing Documentation: avoidance of gendered pronouns. - When it becomes awkward to stick to this style, prefer "you" when - addressing the the hypothetical user, and possibly "we" when + addressing the hypothetical user, and possibly "we" when discussing how the program might react to the user. E.g. You can use this option instead of --xyz, but we might remove diff --git a/Documentation/Makefile b/Documentation/Makefile index 4f801f4e4c..d47acb2e25 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -21,13 +21,25 @@ MAN1_TXT += $(filter-out \ MAN1_TXT += git.txt MAN1_TXT += gitk.txt MAN1_TXT += gitweb.txt +MAN1_TXT += scalar.txt # man5 / man7 guides (note: new guides should also be added to command-list.txt) MAN5_TXT += gitattributes.txt +MAN5_TXT += gitformat-bundle.txt +MAN5_TXT += gitformat-chunk.txt +MAN5_TXT += gitformat-commit-graph.txt +MAN5_TXT += gitformat-index.txt +MAN5_TXT += gitformat-pack.txt +MAN5_TXT += gitformat-signature.txt MAN5_TXT += githooks.txt MAN5_TXT += gitignore.txt MAN5_TXT += gitmailmap.txt MAN5_TXT += gitmodules.txt +MAN5_TXT += gitprotocol-capabilities.txt +MAN5_TXT += gitprotocol-common.txt +MAN5_TXT += gitprotocol-http.txt +MAN5_TXT += gitprotocol-pack.txt +MAN5_TXT += gitprotocol-v2.txt MAN5_TXT += gitrepository-layout.txt MAN5_TXT += gitweb.conf.txt @@ -51,6 +63,7 @@ HOWTO_TXT += $(wildcard howto/*.txt) DOC_DEP_TXT += $(wildcard *.txt) DOC_DEP_TXT += $(wildcard config/*.txt) +DOC_DEP_TXT += $(wildcard includes/*.txt) ifdef MAN_FILTER MAN_TXT = $(filter $(MAN_FILTER),$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)) @@ -90,31 +103,24 @@ 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 += ReviewingGuidelines TECH_DOCS += MyFirstContribution TECH_DOCS += MyFirstObjectWalk TECH_DOCS += SubmittingPatches TECH_DOCS += ToolsForGit TECH_DOCS += technical/bitmap-format -TECH_DOCS += technical/bundle-format -TECH_DOCS += technical/cruft-packs +TECH_DOCS += technical/bundle-uri TECH_DOCS += technical/hash-function-transition -TECH_DOCS += technical/http-protocol -TECH_DOCS += technical/index-format TECH_DOCS += technical/long-running-process-protocol TECH_DOCS += technical/multi-pack-index -TECH_DOCS += technical/pack-format TECH_DOCS += technical/pack-heuristics -TECH_DOCS += technical/pack-protocol TECH_DOCS += technical/parallel-checkout TECH_DOCS += technical/partial-clone -TECH_DOCS += technical/protocol-capabilities -TECH_DOCS += technical/protocol-common -TECH_DOCS += technical/protocol-v2 TECH_DOCS += technical/racy-git TECH_DOCS += technical/reftable +TECH_DOCS += technical/scalar TECH_DOCS += technical/send-pack-pipeline TECH_DOCS += technical/shallow -TECH_DOCS += technical/signature-format TECH_DOCS += technical/trivial-merge SP_ARTICLES += $(TECH_DOCS) SP_ARTICLES += technical/api-index @@ -290,6 +296,8 @@ cmds_txt = cmds-ancillaryinterrogators.txt \ cmds-synchingrepositories.txt \ cmds-synchelpers.txt \ cmds-guide.txt \ + cmds-developerinterfaces.txt \ + cmds-userinterfaces.txt \ cmds-purehelpers.txt \ cmds-foreignscminterface.txt diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index 1da15d9ad4..1a4be8ee0a 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -1160,7 +1160,7 @@ all named like `v2-000n-my-commit-subject.patch`. `-v2` will also format your patches by prefixing them with "[PATCH v2]" instead of "[PATCH]", and your range-diff will be prefaced with "Range-diff against v1". -Afer you run this command, `format-patch` will output the patches to the `psuh/` +After you run this command, `format-patch` will output the patches to the `psuh/` directory, alongside the v1 patches. Using a single directory makes it easy to refer to the old v1 patches while proofreading the v2 patches, but you will need to be careful to send out only the v2 patches. We will use a pattern like diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt index 8d9e85566e..eee513e86f 100644 --- a/Documentation/MyFirstObjectWalk.txt +++ b/Documentation/MyFirstObjectWalk.txt @@ -534,7 +534,7 @@ the arguments to `traverse_commit_list()`. - `void *show_data`: A context buffer which is passed in turn to `show_commit` and `show_object`. -In addition, `traverse_commit_list_filtered()` has an additional paramter: +In addition, `traverse_commit_list_filtered()` has an additional parameter: - `struct oidset *omitted`: A linked-list of object IDs which the provided filter caused to be omitted. diff --git a/Documentation/RelNotes/2.38.0.txt b/Documentation/RelNotes/2.38.0.txt new file mode 100644 index 0000000000..870581fc57 --- /dev/null +++ b/Documentation/RelNotes/2.38.0.txt @@ -0,0 +1,404 @@ +Git v2.38 Release Notes +======================= + +UI, Workflows & Features + + * "git remote show [-n] frotz" now pays attention to negative + pathspec. + + * "git push" sometimes performs poorly when reachability bitmaps are + used, even in a repository where other operations are helped by + bitmaps. The push.useBitmaps configuration variable is introduced + to allow disabling use of reachability bitmaps only for "git push". + + * "git grep -m" is a way to limit the hits shown per file. + + * "git merge-tree" learned a new mode where it takes two commits and + computes a tree that would result in the merge commit, if the + histories leading to these two commits were to be merged. + + * "git mv A B" in a sparsely populated working tree can be asked to + move a path between directories that are "in cone" (i.e. expected + to be materialized in the working tree) and "out of cone" + (i.e. expected to be hidden). The handling of such cases has been + improved. + + * Earlier, HTTP transport clients learned to tell the server side + what locale they are in by sending Accept-Language HTTP header, but + this was done only for some requests but not others. + + * Introduce a safe.barerepository configuration variable that + allows users to forbid discovery of bare repositories. + + * Various messages that come from the pack-bitmap codepaths have been + tweaked. + + * "git rebase -i" learns to update branches whose tip appear in the + rebased range with "--update-refs" option. + + * "git ls-files" learns the "--format" option to tweak its output. + + * "git cat-file" learned an option to use the mailmap when showing + commit and tag objects. + + * When "git merge" finds that it cannot perform a merge, it should + restore the working tree to the state before the command was + initiated, but in some corner cases it didn't. + + * Operating modes like "--batch" of "git cat-file" command learned to + take NUL-terminated input, instead of one-item-per-line. + + * "git rm" has become more aware of the sparse-index feature. + + * "git rev-list --disk-usage" learned to take an optional value + "human" to show the reported value in human-readable format, like + "3.40MiB". + + * The "diagnose" feature to create a zip archive for diagnostic + material has been lifted from "scalar" and made into a feature of + "git bugreport". + + * The namespaces used by "log --decorate" from "refs/" hierarchy by + default has been tightened. + + * "git rev-list --ancestry-path=C A..B" is a natural extension of + "git rev-list A..B"; instead of choosing a subset of A..B to those + that have ancestry relationship with A, it lets a subset with + ancestry relationship with C. + + * "scalar" now enables built-in fsmonitor on enlisted repositories, + when able. + + * The bash prompt (in contrib/) learned to optionally indicate when + the index is unmerged. + + * "git clone" command learned the "--bundle-uri" option to coordinate + with hosting sites the use of pre-prepared bundle files. + + * "git range-diff" learned to honor pathspec argument if given. + + * "git format-patch --from=" can be told to add an in-body + "From:" line even for commits that are authored by the given + with "--force-in-body-from" option. + + * The built-in fsmonitor refuses to work on a network mounted + repositories; a configuration knob for users to override this has + been introduced. + + * The "scalar" addition from Microsoft is now part of the core Git + installation. + + +Performance, Internal Implementation, Development Support etc. + + * Collection of what is referenced by objects in promisor packs have + been optimized to inspect these objects in the in-pack order. + + * Introduce a helper to see if a branch is already being worked on + (hence should not be newly checked out in a working tree), which + performs much better than the existing find_shared_symref() to + replace many uses of the latter. + + * Teach "git archive" to (optionally and then by default) avoid + spawning an external "gzip" process when creating ".tar.gz" (and + ".tgz") archives. + + * Allow large objects read from a packstream to be streamed into a + loose object file straight, without having to keep it in-core as a + whole. + + * Further preparation to turn git-submodule.sh into a builtin + continues. + + * Apply Coccinelle rule to turn raw memmove() into MOVE_ARRAY() cpp + macro, which would improve maintainability and readability. + + * Teach "make all" to build gitweb as well. + + * Tweak tests so that they still work when the "git init" template + did not create .git/info directory. + + * Add Coccinelle rules to detect the pattern of initializing and then + finalizing a structure without using it in between at all, which + happens after code restructuring and the compilers fail to + recognize as an unused variable. + + * The code to convert between GPG trust level strings and internal + constants we use to represent them have been cleaned up. + + * Support for libnettle as SHA256 implementation has been added. + + * The way "git multi-pack" uses parse-options API has been improved. + + * A Coccinelle rule (in contrib/) to encourage use of COPY_ARRAY + macro has been improved. + + * API tweak to make it easier to run fuzz testing on commit-graph parser. + + * Omit fsync-related trace2 entries when their values are all zero. + + * The codepath to write multi-pack index has been taught to release a + large chunk of memory that holds an array of objects in the packs, + as soon as it is done with the array, to reduce memory consumption. + + * Add a level of redirection to array allocation API in xdiff part, + to make it easier to share with the libgit2 project. + + * "git fetch" client logs the partial clone filter used in the trace2 + output. + + * The "bundle URI" design gets documented. + + * The common ancestor negotiation exchange during a "git fetch" + session now leaves trace log. + + * Test portability improvements. + (merge 4d1d843be7 mt/rot13-in-c later to maint). + + * The "subcommand" mode is introduced to parse-options API and update + the command line parser of Git commands with subcommands. + + * The pack bitmap file gained a bitmap-lookup table to speed up + locating the necessary bitmap for a given commit. + + * The assembly version of SHA-1 implementation for PPC has been + removed. + + * The server side that responds to "git fetch" and "git clone" + request has been optimized by allowing it to send objects in its + object store without recomputing and validating the object names. + + * Annotate function parameters that are not used (but cannot be + removed for structural reasons), to prepare us to later compile + with -Wunused warning turned on. + + * Share the text used to explain configuration variables used by "git + " in "git help " with the text from "git help config". + + * "git mv A B" in a sparsely populated working tree can be asked to + move a path from a directory that is "in cone" to another directory + that is "out of cone". Handling of such a case has been improved. + + * The chainlint script for our tests has been revamped. + + +Fixes since v2.37 +----------------- + + * Rewrite of "git add -i" in C that appeared in Git 2.25 didn't + correctly record a removed file to the index, which was fixed. + + * Certain diff options are currently ignored when combined-diff is + shown; mark them as incompatible with the feature. + + * Adjust technical/bitmap-format to be formatted by AsciiDoc, and + add some missing information to the documentation. + + * Fixes for tests when the source directory has unusual characters in + its path, e.g. whitespaces, double-quotes, etc. + + * "git mktree --missing" lazily fetched objects that are missing from + the local object store, which was totally unnecessary for the purpose + of creating the tree object(s) from its input. + + * Give _() markings to fatal/warning/usage: labels that are shown in + front of these messages. + + * References to commands-to-be-typed-literally in "git rebase" + documentation mark-up have been corrected. + + * In a non-bare repository, the behavior of Git when the + core.worktree configuration variable points at a directory that has + a repository as its subdirectory, regressed in Git 2.27 days. + + * Recent update to vimdiff layout code has been made more robust + against different end-user vim settings. + + * Plug various memory leaks, both in the main code and in test-tool + commands. + + * Fixes a long-standing corner case bug around directory renames in + the merge-ort strategy. + + * The resolve-undo information in the index was not protected against + GC, which has been corrected. + + * A corner case bug where lazily fetching objects from a promisor + remote resulted in infinite recursion has been corrected. + + * "git clone" from a repository with some ref whose HEAD is unborn + did not set the HEAD in the resulting repository correctly, which + has been corrected. + + * An earlier attempt to plug leaks placed a clean-up label to jump to + at a bogus place, which as been corrected. + + * Variable quoting fix in the vimdiff driver of "git mergetool" + + * "git shortlog -n" relied on the underlying qsort() to be stable, + which shouldn't have. Fixed. + + * A fix for a regression in test framework. + + * mkstemp() emulation on Windows has been improved. + + * Add missing documentation for "include" and "includeIf" features in + "git config" file format, which incidentally teaches the command + line completion to include them in its offerings. + + * Avoid "white/black-list" in documentation and code comments. + + * Workaround for a compiler warning against use of die() in + osx-keychain (in contrib/). + + * Workaround for a false positive compiler warning. + + * "git p4" working on UTF-16 files on Windows did not implement + CRLF-to-LF conversion correctly, which has been corrected. + + * "git p4" did not handle non-ASCII client name well, which has been + corrected. + + * "rerere-train" script (in contrib/) used to honor commit.gpgSign + while recreating the throw-away merges. + + * "git checkout" miscounted the paths it updated, which has been + corrected. + + * Fix for a bug that makes write-tree to fail to write out a + non-existent index as a tree, introduced in 2.37. + + * There was a bug in the codepath to upgrade generation information + in commit-graph from v1 to v2 format, which has been corrected. + + * Gitweb had legacy URL shortener that is specific to the way + projects hosted on kernel.org used to (but no longer) work, which + has been removed. + + * Fix build procedure for Windows that uses CMake so that it can pick + up the shell interpreter from local installation location. + + * Conditionally allow building Python interpreter on Windows + + * Fix to lstat() emulation on Windows. + + * Older gcc with -Wall complains about the universal zero initializer + "struct s = { 0 };" idiom, which makes developers' lives + inconvenient (as -Werror is enabled by DEVELOPER=YesPlease). The + build procedure has been tweaked to help these compilers. + + * Plug memory leaks in the failure code path in the "merge-ort" merge + strategy backend. + + * "git symbolic-ref symref non..sen..se" is now diagnosed as an error. + + * A follow-up fix to a fix for a regression in 2.36 around hooks. + + * Avoid repeatedly running getconf to ask libc version in the test + suite, and instead just as it once per script. + + * Platform-specific code that determines if a directory is OK to use + as a repository has been taught to report more details, especially + on Windows. + + * "vimdiff3" regression fix. + + * "git fsck" reads mode from tree objects but canonicalizes the mode + before passing it to the logic to check object sanity, which has + hid broken tree objects from the checking logic. This has been + corrected, but to help existing projects with broken tree objects + that they cannot fix retroactively, the severity of anomalies this + code detects has been demoted to "info" for now. + + * Fixes to sparse index compatibility work for "reset" and "checkout" + commands. + + * An earlier optimization discarded a tree-object buffer that is + still in use, which has been corrected. + + * Fix deadlocks between main Git process and subprocess spawned via + the pipe_command() API, that can kill "git add -p" that was + reimplemented in C recently. + + * The sequencer machinery translated messages left in the reflog by + mistake, which has been corrected. + + * xcalloc(), imitating calloc(), takes "number of elements of the + array", and "size of a single element", in this order. A call that + does not follow this ordering has been corrected. + + * The preload-index codepath made copies of pathspec to give to + multiple threads, which were left leaked. + + * Update the version of Ubuntu used for GitHub Actions CI from 18.04 + to 22.04. + + * The auto-stashed local changes created by "git merge --autostash" + was mixed into a conflicted state left in the working tree, which + has been corrected. + + * Multi-pack index got corrupted when preferred pack changed from one + pack to another in a certain way, which has been corrected. + (merge 99e4d084ff tb/midx-with-changing-preferred-pack-fix later to maint). + + * The clean-up of temporary files created via mks_tempfile_dt() was + racy and attempted to unlink() the leading directory when signals + are involved, which has been corrected. + (merge babe2e0559 rs/tempfile-cleanup-race-fix later to maint). + + * FreeBSD portability fix for "git maintenance" that spawns "crontab" + to schedule tasks. + (merge ee69e7884e bc/gc-crontab-fix later to maint). + + * Those who use diff-so-fancy as the diff-filter noticed a regression + or two in the code that parses the diff output in the built-in + version of "add -p", which has been corrected. + (merge 0a101676e5 js/add-p-diff-parsing-fix later to maint). + + * Segfault fix-up to an earlier fix to the topic to teach "git reset" + and "git checkout" work better in a sparse checkout. + (merge 037f8ea6d9 vd/sparse-reset-checkout-fixes later to maint). + + * "git diff --no-index A B" managed its the pathnames of its two + input files rather haphazardly, sometimes leaking them. The + command line argument processing has been straightened out to clean + it up. + (merge 2b43dd0eb5 rs/diff-no-index-cleanup later to maint). + + * "git rev-list --verify-objects" ought to inspect the contents of + objects and notice corrupted ones, but it didn't when the commit + graph is in use, which has been corrected. + (merge b27ccae34b jk/rev-list-verify-objects-fix later to maint). + + * More fixes to "add -p" + (merge 64ec8efb83 js/builtin-add-p-portability-fix later to maint). + + * The parser in the script interface to parse-options in "git + rev-parse" has been updated to diagnose a bogus input correctly. + (merge f20b9c36d0 ow/rev-parse-parseopt-fix later to maint). + + * The code that manages list-object-filter structure, used in partial + clones, leaked the instances, which has been plugged. + (merge 66eede4a37 jk/plug-list-object-filter-leaks later to maint). + + * Fix another UI regression in the reimplemented "add -p". + (merge f6f0ee247f rs/add-p-worktree-mode-prompt-fix later to maint). + + * "git fetch" over protocol v2 sent an incorrect ref prefix request + to the server and made "git pull" with configured fetch refspec + that does not cover the remote branch to merge with fail, which has + been corrected. + (merge 49ca2fba39 jk/proto-v2-ref-prefix-fix later to maint). + + * A result from opendir() was leaking in the commit-graph expiration + codepath, which has been plugged. + (merge 12f1ae5324 ml/commit-graph-expire-dir-leak-fix later to maint). + + * Just like we have coding guidelines, we now have guidelines for + reviewers. + (merge e01b851923 vd/doc-reviewing-guidelines later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge 77b9e85c0f vd/fix-perf-tests later to maint). + (merge 0682bc43f5 jk/test-crontab-fixes later to maint). + (merge b46dd1726c cc/doc-trailer-whitespace-rules later to maint). diff --git a/Documentation/RelNotes/2.39.0.txt b/Documentation/RelNotes/2.39.0.txt new file mode 100644 index 0000000000..3d27b72efc --- /dev/null +++ b/Documentation/RelNotes/2.39.0.txt @@ -0,0 +1,22 @@ +Git v2.39 Release Notes +======================= + +Performance, Internal Implementation, Development Support etc. + + * With a bit of header twiddling, use the native regexp library on + macOS instead of the compat/ one. + + * Prepare for GNU [ef]grep that throw warning of their uses. + + * Sources related to fuzz testing have been moved down to their own + directory. + + +Fixes since v2.38 +----------------- + + * The codepath that reads from the index v4 had unaligned memory + accesses, which has been corrected. + (merge 4a6ed30f96 vd/fix-unaligned-read-index-v4 later to maint). + + * Other code cleanup, docfix, build fix, etc. diff --git a/Documentation/ReviewingGuidelines.txt b/Documentation/ReviewingGuidelines.txt new file mode 100644 index 0000000000..0e323d5477 --- /dev/null +++ b/Documentation/ReviewingGuidelines.txt @@ -0,0 +1,162 @@ +Reviewing Patches in the Git Project +==================================== + +Introduction +------------ +The Git development community is a widely distributed, diverse, ever-changing +group of individuals. Asynchronous communication via the Git mailing list poses +unique challenges when reviewing or discussing patches. This document contains +some guiding principles and helpful tools you can use to make your reviews both +more efficient for yourself and more effective for other contributors. + +Note that none of the recommendations here are binding or in any way a +requirement of participation in the Git community. They are provided as a +resource to supplement your skills as a contributor. + +Principles +---------- + +Selecting patch(es) to review +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you are looking for a patch series in need of review, start by checking +latest "What's cooking in git.git" email +(https://lore.kernel.org/git/xmqqilm1yp3m.fsf@gitster.g/[example]). The "What's +cooking" emails & replies can be found using the query `s:"What's cooking"` on +the https://lore.kernel.org/git/[`lore.kernel.org` mailing list archive]; +alternatively, you can find the contents of the "What's cooking" email tracked +in `whats-cooking.txt` on the `todo` branch of Git. Topics tagged with "Needs +review" and those in the "[New Topics]" section are typically those that would +benefit the most from additional review. + +Patches can also be searched manually in the mailing list archive using a query +like `s:"PATCH" -s:"Re:"`. You can browse these results for topics relevant to +your expertise or interest. + +If you've already contributed to Git, you may also be CC'd in another +contributor's patch series. These are topics where the author feels that your +attention is warranted. This may be because their patch changes something you +wrote previously (making you a good judge of whether the new approach does or +doesn't work), or because you have the expertise to provide an exceptionally +helpful review. There is no requirement to review these patches but, in the +spirit of open source collaboration, you should strongly consider doing so. + +Reviewing patches +~~~~~~~~~~~~~~~~~ +While every contributor takes their own approach to reviewing patches, here are +some general pieces of advice to make your reviews as clear and helpful as +possible. The advice is broken into two rough categories: high-level reviewing +guidance, and concrete tips for interacting with patches on the mailing list. + +==== High-level guidance +- Remember to review the content of commit messages for correctness and clarity, + in addition to the code change in the patch's diff. The commit message of a + patch should accurately and fully explain the code change being made in the + diff. + +- Reviewing test coverage is an important - but easy to overlook - component of + reviews. A patch's changes may be covered by existing tests, or new tests may + be introduced to exercise new behavior. Checking out a patch or series locally + allows you to manually mutate lines of new & existing tests to verify expected + pass/fail behavior. You can use this information to verify proper coverage or + to suggest additional tests the author could add. + +- When providing a recommendation, be as clear as possible about whether you + consider it "blocking" (the code would be broken or otherwise made worse if an + issue isn't fixed) or "non-blocking" (the patch could be made better by taking + the recommendation, but acceptance of the series does not require it). + Non-blocking recommendations can be particularly ambiguous when they are + related to - but outside the scope of - a series ("nice-to-have"s), or when + they represent only stylistic differences between the author and reviewer. + +- When commenting on an issue, try to include suggestions for how the author + could fix it. This not only helps the author to understand and fix the issue, + it also deepens and improves your understanding of the topic. + +- Reviews do not need to exclusively point out problems. Feel free to "think out + loud" in your review: describe how you read & understood a complex section of + a patch, ask a question about something that confused you, point out something + you found exceptionally well-written, etc. In particular, uplifting feedback + goes a long way towards encouraging contributors to participate more actively + in the Git community. + +==== Performing your review +- Provide your review comments per-patch in a plaintext "Reply-All" email to the + relevant patch. Comments should be made inline, immediately below the relevant + section(s). + +- You may find that the limited context provided in the patch diff is sometimes + insufficient for a thorough review. In such cases, you can review patches in + your local tree by either applying patches with linkgit:git-am[1] or checking + out the associated branch from https://github.com/gitster/git once the series + is tracked there. + +- Large, complicated patch diffs are sometimes unavoidable, such as when they + refactor existing code. If you find such a patch difficult to parse, try + reviewing the diff produced with the `--color-moved` and/or + `--ignore-space-change` options. + +- If a patch is long, you are encouraged to delete parts of it that are + unrelated to your review from the email reply. Make sure to leave enough + context for readers to understand your comments! + +- If you cannot complete a full review of a series all at once, consider letting + the author know (on- or off-list) if/when you plan to review the rest of the + series. + +Completing a review +~~~~~~~~~~~~~~~~~~~ +Once each patch of a series is reviewed, the author (and/or other contributors) +may discuss the review(s). This may result in no changes being applied, or the +author will send a new version of their patch(es). + +After a series is rerolled in response to your or others' review, make sure to +re-review the updates. If you are happy with the state of the patch series, +explicitly indicate your approval (typically with a reply to the latest +version's cover letter). Optionally, you can let the author know that they can +add a "Reviewed-by: " trailer if they resubmit the reviewed patch verbatim +in a later iteration of the series. + +Finally, subsequent "What's cooking" emails may explicitly ask whether a +reviewed topic is ready for merging to the `next` branch (typically phrased +"Will merge to \'next\'?"). You can help the maintainer and author by responding +with a short description of the state of your (and others', if applicable) +review, including the links to the relevant thread(s). + +Terminology +----------- +nit: :: + Denotes a small issue that should be fixed, such as a typographical error + or mis-alignment of conditions in an `if()` statement. + +aside: :: +optional: :: +non-blocking: :: + Indicates to the reader that the following comment should not block the + acceptance of the patch or series. These are typically recommendations + related to code organization & style, or musings about topics related to + the patch in question, but beyond its scope. + +s///:: + Shorthand for "you wrote , but I think you meant ," usually + for misspellings or other typographical errors. The syntax is a reference + to "substitute" command commonly found in Unix tools such as `ed`, `sed`, + `vim`, and `perl`. + +cover letter:: + The "Patch 0" of a multi-patch series. This email describes the + high-level intent and structure of the patch series to readers on the + Git mailing list. It is also where the changelog notes and range-diff of + subsequent versions are provided by the author. ++ +On single-patch submissions, cover letter content is typically not sent as a +separate email. Instead, it is inserted between the end of the patch's commit +message (after the `---`) and the beginning of the diff. + +#leftoverbits:: + Used by either an author or a reviewer to describe features or suggested + changes that are out-of-scope of a given patch or series, but are relevant + to the topic for the sake of discussion. + +See Also +-------- +link:MyFirstContribution.html[MyFirstContribution] diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index af5da45d28..755a110bc4 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -10,7 +10,7 @@ sub format_one { $state = 0; open I, '<', "$name.txt" or die "No such file $name.txt"; while () { - if (/^git[a-z0-9-]*\(([0-9])\)$/) { + if (/^(?:git|scalar)[a-z0-9-]*\(([0-9])\)$/) { $mansection = $1; next; } diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index 88bc799cf3..37afbaf5a4 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -444,17 +444,32 @@ You probably do not need to adjust this value. Common unit suffixes of 'k', 'm', or 'g' are supported. core.bigFileThreshold:: - Files larger than this size are stored deflated, without - attempting delta compression. Storing large files without - delta compression avoids excessive memory usage, at the - slight expense of increased disk usage. Additionally files - larger than this size are always treated as binary. + The size of files considered "big", which as discussed below + changes the behavior of numerous git commands, as well as how + such files are stored within the repository. The default is + 512 MiB. Common unit suffixes of 'k', 'm', or 'g' are + supported. + -Default is 512 MiB on all platforms. This should be reasonable -for most projects as source code and other text files can still -be delta compressed, but larger binary media files won't be. +Files above the configured limit will be: + -Common unit suffixes of 'k', 'm', or 'g' are supported. +* Stored deflated in packfiles, without attempting delta compression. ++ +The default limit is primarily set with this use-case in mind. With it, +most projects will have their source code and other text files delta +compressed, but not larger binary media files. ++ +Storing large files without delta compression avoids excessive memory +usage, at the slight expense of increased disk usage. ++ +* Will be treated as if they were labeled "binary" (see + linkgit:gitattributes[5]). e.g. linkgit:git-log[1] and + linkgit:git-diff[1] will not compute diffs for files above this limit. ++ +* Will generally be streamed when written, which avoids excessive +memory usage, at the cost of some fixed overhead. Commands that make +use of this include linkgit:git-archive[1], +linkgit:git-fast-import[1], linkgit:git-index-pack[1], +linkgit:git-unpack-objects[1] and linkgit:git-fsck[1]. core.excludesFile:: Specifies the pathname to the file that contains patterns to diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt index 32f84838ac..35a7bf86d7 100644 --- a/Documentation/config/diff.txt +++ b/Documentation/config/diff.txt @@ -178,21 +178,6 @@ diff..cachetextconv:: Set this option to true to make the diff driver cache the text conversion outputs. See linkgit:gitattributes[5] for details. -diff.tool:: - Controls which diff tool is used by linkgit:git-difftool[1]. - This variable overrides the value configured in `merge.tool`. - The list below shows the valid built-in values. - Any other value is treated as a custom diff tool and requires - that a corresponding difftool..cmd variable is defined. - -diff.guitool:: - Controls which diff tool is used by linkgit:git-difftool[1] when - the -g/--gui flag is specified. This variable overrides the value - configured in `merge.guitool`. The list below shows the valid - built-in values. Any other value is treated as a custom diff tool - and requires that a corresponding difftool..cmd variable - is defined. - include::../mergetools-diff.txt[] diff.indentHeuristic:: diff --git a/Documentation/config/difftool.txt b/Documentation/config/difftool.txt index 6762594480..a3f8211210 100644 --- a/Documentation/config/difftool.txt +++ b/Documentation/config/difftool.txt @@ -1,6 +1,17 @@ -difftool..path:: - Override the path for the given tool. This is useful in case - your tool is not in the PATH. +diff.tool:: + Controls which diff tool is used by linkgit:git-difftool[1]. + This variable overrides the value configured in `merge.tool`. + The list below shows the valid built-in values. + Any other value is treated as a custom diff tool and requires + that a corresponding difftool..cmd variable is defined. + +diff.guitool:: + Controls which diff tool is used by linkgit:git-difftool[1] when + the -g/--gui flag is specified. This variable overrides the value + configured in `merge.guitool`. The list below shows the valid + built-in values. Any other value is treated as a custom diff tool + and requires that a corresponding difftool..cmd variable + is defined. difftool..cmd:: Specify the command to invoke the specified diff tool. @@ -9,6 +20,17 @@ difftool..cmd:: file containing the contents of the diff pre-image and 'REMOTE' is set to the name of the temporary file containing the contents of the diff post-image. ++ +See the `--tool=` option in linkgit:git-difftool[1] for more details. + +difftool..path:: + Override the path for the given tool. This is useful in case + your tool is not in the PATH. + +difftool.trustExitCode:: + Exit difftool if the invoked diff tool returns a non-zero exit status. ++ +See the `--trust-exit-code` option in linkgit:git-difftool[1] for more details. difftool.prompt:: Prompt before each invocation of the diff tool. diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt index fdbc06a4d2..c7303d8d9f 100644 --- a/Documentation/config/format.txt +++ b/Documentation/config/format.txt @@ -15,6 +15,10 @@ format.from:: different. If set to a non-boolean value, format-patch uses that value instead of your committer identity. Defaults to false. +format.forceInBodyFrom:: + Provides the default value for the `--[no-]force-in-body-from` + option to format-patch. Defaults to false. + format.numbered:: A boolean which can enable or disable sequence numbers in patch subjects. It defaults to "auto" which enables it only if there diff --git a/Documentation/config/grep.txt b/Documentation/config/grep.txt index 182edd813a..e521f20390 100644 --- a/Documentation/config/grep.txt +++ b/Documentation/config/grep.txt @@ -17,8 +17,11 @@ grep.extendedRegexp:: other than 'default'. grep.threads:: - Number of grep worker threads to use. - See `grep.threads` in linkgit:git-grep[1] for more information. + Number of grep worker threads to use. If unset (or set to 0), Git will + use as many threads as the number of logical cores available. + +grep.fullName:: + If set to true, enable `--full-name` option by default. grep.fallbackToNoIndex:: If set to true, fall back to git grep --no-index if git grep diff --git a/Documentation/config/log.txt b/Documentation/config/log.txt index 456eb07800..5f96cf87fb 100644 --- a/Documentation/config/log.txt +++ b/Documentation/config/log.txt @@ -7,6 +7,10 @@ log.date:: Set the default date-time mode for the 'log' command. Setting a value for log.date is similar to using 'git log''s `--date` option. See linkgit:git-log[1] for details. ++ +If the format is set to "auto:foo" and the pager is in use, format +"foo" will be the used for the date format. Otherwise "default" will +be used. log.decorate:: Print out the ref names of any commits that are shown by the log @@ -18,6 +22,11 @@ log.decorate:: names are shown. This is the same as the `--decorate` option of the `git log`. +log.initialDecorationSet:: + By default, `git log` only shows decorations for certain known ref + namespaces. If 'all' is specified, then show all refs as + decorations. + log.excludeDecoration:: Exclude the specified patterns from the log decorations. This is similar to the `--decorate-refs-exclude` command-line option, but @@ -25,9 +34,9 @@ log.excludeDecoration:: option. log.diffMerges:: - Set default diff format to be used for merge commits. See - `--diff-merges` in linkgit:git-log[1] for details. - Defaults to `separate`. + Set diff format to be used when `--diff-merges=on` is + specified, see `--diff-merges` in linkgit:git-log[1] for + details. Defaults to `separate`. log.follow:: If `true`, `git log` will act as if the `--follow` option was used when diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt index adeda0f24d..3d88fb0bad 100644 --- a/Documentation/config/lsrefs.txt +++ b/Documentation/config/lsrefs.txt @@ -1,7 +1,7 @@ lsrefs.unborn:: May be "advertise" (the default), "allow", or "ignore". If "advertise", the server will respond to the client sending "unborn" (as described in - protocol-v2.txt) and will advertise support for this feature during the + linkgit:gitprotocol-v2[5]) and will advertise support for this feature during the protocol v2 capability advertisement. "allow" is the same as "advertise" except that the server will not advertise support for this feature; this is useful for load-balanced servers that cannot be diff --git a/Documentation/config/notes.txt b/Documentation/config/notes.txt index aeef56d49a..c7c4811734 100644 --- a/Documentation/config/notes.txt +++ b/Documentation/config/notes.txt @@ -3,6 +3,9 @@ notes.mergeStrategy:: conflicts. Must be one of `manual`, `ours`, `theirs`, `union`, or `cat_sort_uniq`. Defaults to `manual`. See "NOTES MERGE STRATEGIES" section of linkgit:git-notes[1] for more information on each strategy. ++ +This setting can be overridden by passing the `--strategy` option to +linkgit:git-notes[1]. notes..mergeStrategy:: Which merge strategy to choose when doing a notes merge into @@ -11,28 +14,35 @@ notes..mergeStrategy:: linkgit:git-notes[1] for more information on the available strategies. notes.displayRef:: - The (fully qualified) refname from which to show notes when - showing commit messages. The value of this variable can be set - to a glob, in which case notes from all matching refs will be - shown. You may also specify this configuration variable - several times. A warning will be issued for refs that do not - exist, but a glob that does not match any refs is silently - ignored. + Which ref (or refs, if a glob or specified more than once), in + addition to the default set by `core.notesRef` or + `GIT_NOTES_REF`, to read notes from when showing commit + messages with the 'git log' family of commands. + This setting can be overridden with the `GIT_NOTES_DISPLAY_REF` environment variable, which must be a colon separated list of refs or globs. + +A warning will be issued for refs that do not exist, +but a glob that does not match any refs is silently ignored. ++ +This setting can be disabled by the `--no-notes` option to the 'git +log' family of commands, or by the `--notes=` option accepted by +those commands. ++ The effective value of "core.notesRef" (possibly overridden by GIT_NOTES_REF) is also implicitly added to the list of refs to be displayed. notes.rewrite.:: When rewriting commits with (currently `amend` or - `rebase`) and this variable is set to `true`, Git - automatically copies your notes from the original to the - rewritten commit. Defaults to `true`, but see - "notes.rewriteRef" below. + `rebase`), if this variable is `false`, git will not copy + notes from the original to the rewritten commit. Defaults to + `true`. See also "`notes.rewriteRef`" below. ++ +This setting can be overridden with the `GIT_NOTES_REWRITE_REF` +environment variable, which must be a colon separated list of refs or +globs. notes.rewriteMode:: When copying notes during a rewrite (see the @@ -46,14 +56,13 @@ environment variable. notes.rewriteRef:: When copying notes during a rewrite, specifies the (fully - qualified) ref whose notes should be copied. The ref may be a - glob, in which case notes in all matching refs will be copied. - You may also specify this configuration several times. + qualified) ref whose notes should be copied. May be a glob, + in which case notes in all matching refs will be copied. You + may also specify this configuration several times. + Does not have a default value; you must configure this variable to enable note rewriting. Set it to `refs/notes/commits` to enable rewriting for the default commit notes. + -This setting can be overridden with the `GIT_NOTES_REWRITE_REF` -environment variable, which must be a colon separated list of refs or -globs. +Can be overridden with the `GIT_NOTES_REWRITE_REF` environment variable. +See `notes.rewrite.` above for a further description of its format. diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt index ad7f73a1ea..53093d9996 100644 --- a/Documentation/config/pack.txt +++ b/Documentation/config/pack.txt @@ -164,9 +164,16 @@ When writing a multi-pack reachability bitmap, no new namehashes are computed; instead, any namehashes stored in an existing bitmap are permuted into their appropriate location when writing a new bitmap. +pack.writeBitmapLookupTable:: + When true, Git will include a "lookup table" section in the + bitmap index (if one is written). This table is used to defer + loading individual bitmaps as late as possible. This can be + beneficial in repositories that have relatively large bitmap + indexes. Defaults to false. + pack.writeReverseIndex:: When true, git will write a corresponding .rev file (see: - link:../technical/pack-format.html[Documentation/technical/pack-format.txt]) + linkgit:gitformat-pack[5]) for each new packfile that it writes in all places except for linkgit:git-fast-import[1] and in the bulk checkin mechanism. Defaults to false. diff --git a/Documentation/config/protocol.txt b/Documentation/config/protocol.txt index 756591d77b..5760381851 100644 --- a/Documentation/config/protocol.txt +++ b/Documentation/config/protocol.txt @@ -58,6 +58,6 @@ protocol.version:: * `1` - the original wire protocol with the addition of a version string in the initial response from the server. -* `2` - link:technical/protocol-v2.html[wire protocol version 2]. +* `2` - Wire protocol version 2, see linkgit:gitprotocol-v2[5]. -- diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt index e32801e6c9..7386fea225 100644 --- a/Documentation/config/push.txt +++ b/Documentation/config/push.txt @@ -137,3 +137,8 @@ push.negotiate:: server attempt to find commits in common. If "false", Git will rely solely on the server's ref advertisement to find commits in common. + +push.useBitmaps:: + If set to "false", disable use of bitmaps for "git push" even if + `pack.useBitmaps` is "true", without preventing other git operations + from using bitmaps. Default is true. diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index 8c979cb20f..f19bd0e040 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -21,6 +21,9 @@ rebase.autoStash:: `--autostash` options of linkgit:git-rebase[1]. Defaults to false. +rebase.updateRefs:: + If set to true enable `--update-refs` option by default. + rebase.missingCommitsCheck:: If set to "warn", git rebase -i will print a warning if some commits are removed (e.g. a line was deleted), however the diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt index fa02f3ccc5..bde7f31459 100644 --- a/Documentation/config/safe.txt +++ b/Documentation/config/safe.txt @@ -1,3 +1,22 @@ +safe.bareRepository:: + Specifies which bare repositories Git will work with. The currently + supported values are: ++ +* `all`: Git works with all bare repositories. This is the default. +* `explicit`: Git only works with bare repositories specified via + the top-level `--git-dir` command-line option, or the `GIT_DIR` + environment variable (see linkgit:git[1]). ++ +If you do not use bare repositories in your workflow, then it may be +beneficial to set `safe.bareRepository` to `explicit` in your global +config. This will protect you from attacks that involve cloning a +repository that contains a bare repository and running a Git command +within that directory. ++ +This config setting is only respected in protected configuration (see +<>). This prevents the untrusted repository from tampering with +this value. + safe.directory:: These config entries specify Git-tracked directories that are considered safe even if they are owned by someone other than the @@ -12,9 +31,9 @@ via `git config --add`. To reset the list of safe directories (e.g. to override any such directories specified in the system config), add a `safe.directory` entry with an empty value. + -This config setting is only respected when specified in a system or global -config, not when it is specified in a repository config, via the command -line option `-c safe.directory=`, or in environment variables. +This config setting is only respected in protected configuration (see +<>). This prevents the untrusted repository from tampering with this +value. + The value of this setting is interpolated, i.e. `~/` expands to a path relative to the home directory and `%(prefix)/` expands to a diff --git a/Documentation/config/sendemail.txt b/Documentation/config/sendemail.txt index 50baa5d6bf..51da7088a8 100644 --- a/Documentation/config/sendemail.txt +++ b/Documentation/config/sendemail.txt @@ -18,17 +18,49 @@ sendemail..*:: identity is selected, through either the command-line or `sendemail.identity`. +sendemail.multiEdit:: + If true (default), a single editor instance will be spawned to edit + files you have to edit (patches when `--annotate` is used, and the + summary when `--compose` is used). If false, files will be edited one + after the other, spawning a new editor each time. + +sendemail.confirm:: + Sets the default for whether to confirm before sending. Must be + one of 'always', 'never', 'cc', 'compose', or 'auto'. See `--confirm` + in the linkgit:git-send-email[1] documentation for the meaning of these + values. + sendemail.aliasesFile:: + To avoid typing long email addresses, point this to one or more + email aliases files. You must also supply `sendemail.aliasFileType`. + sendemail.aliasFileType:: + Format of the file(s) specified in sendemail.aliasesFile. Must be + one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus', or 'sendmail'. ++ +What an alias file in each format looks like can be found in +the documentation of the email program of the same name. The +differences and limitations from the standard formats are +described below: ++ +-- +sendmail;; +* Quoted aliases and quoted addresses are not supported: lines that + contain a `"` symbol are ignored. +* Redirection to a file (`/path/name`) or pipe (`|command`) is not + supported. +* File inclusion (`:include: /path/name`) is not supported. +* Warnings are printed on the standard error output for any + explicitly unsupported constructs, and any other lines that are not + recognized by the parser. +-- sendemail.annotate:: sendemail.bcc:: sendemail.cc:: sendemail.ccCmd:: sendemail.chainReplyTo:: -sendemail.confirm:: sendemail.envelopeSender:: sendemail.from:: -sendemail.multiEdit:: sendemail.signedoffbycc:: sendemail.smtpPass:: sendemail.suppresscc:: @@ -44,7 +76,9 @@ sendemail.thread:: sendemail.transferEncoding:: sendemail.validate:: sendemail.xmailer:: - See linkgit:git-send-email[1] for description. + These configuration variables all provide a default for + linkgit:git-send-email[1] command-line options. See its + documentation for details. sendemail.signedoffcc (deprecated):: Deprecated alias for `sendemail.signedoffbycc`. diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt index 7ed917f5fc..264812cca4 100644 --- a/Documentation/config/transfer.txt +++ b/Documentation/config/transfer.txt @@ -13,7 +13,7 @@ Note that this is currently limited to detecting credentials in You might want to enable this to prevent inadvertent credentials exposure, e.g. because: + -* The OS or system where you're running git may not provide way way or +* The OS or system where you're running git may not provide a way or otherwise allow you to configure the permissions of the configuration file where the username and/or password are stored. * Even if it does, having such data stored "at rest" might expose you diff --git a/Documentation/config/uploadpack.txt b/Documentation/config/uploadpack.txt index 32fad5bbe8..16264d82a7 100644 --- a/Documentation/config/uploadpack.txt +++ b/Documentation/config/uploadpack.txt @@ -49,9 +49,9 @@ uploadpack.packObjectsHook:: `pack-objects` to the hook, and expects a completed packfile on stdout. + -Note that this configuration variable is ignored if it is seen in the -repository-level config (this is a safety measure against fetching from -untrusted repositories). +Note that this configuration variable is only respected when it is specified +in protected configuration (see <>). This is a safety measure +against fetching from untrusted repositories. uploadpack.allowFilter:: If this option is set, `upload-pack` will support partial diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 9b37f35654..a030d33c6e 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -433,6 +433,13 @@ they will make the patch impossible to apply: * deleting context or removal lines * modifying the contents of context or removal lines +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/add.txt[] + SEE ALSO -------- linkgit:git-status[1] diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 09107fb106..326276e51c 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -112,10 +112,7 @@ default. You can use `--no-utf8` to override this. am.threeWay configuration variable. For more information, see am.threeWay in linkgit:git-config[1]. ---rerere-autoupdate:: ---no-rerere-autoupdate:: - Allow the rerere mechanism to update the index with the - result of auto-conflict resolution if possible. +include::rerere-options.txt[] --ignore-space-change:: --ignore-whitespace:: @@ -261,6 +258,13 @@ This command can run `applypatch-msg`, `pre-applypatch`, and `post-applypatch` hooks. See linkgit:githooks[5] for more information. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/am.txt[] + SEE ALSO -------- linkgit:git-apply[1]. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index b6d77f4206..1d478cbe9b 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -263,13 +263,9 @@ has no effect when `--index` or `--cached` is in use. CONFIGURATION ------------- -apply.ignoreWhitespace:: - Set to 'change' if you want changes in whitespace to be ignored by default. - Set to one of: no, none, never, false if you want changes in - whitespace to be significant. -apply.whitespace:: - When no `--whitespace` flag is given from the command - line, this configuration item is used as the default. +include::includes/cmd-config-section-all.txt[] + +include::config/apply.txt[] SUBMODULES ---------- diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 56989a2f34..60c040988b 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -34,10 +34,12 @@ OPTIONS ------- --format=:: - Format of the resulting archive: 'tar' or 'zip'. If this option + Format of the resulting archive. Possible values are `tar`, + `zip`, `tar.gz`, `tgz`, and any format defined using the + configuration option `tar..command`. If `--format` is not given, and the output file is specified, the format is - inferred from the filename if possible (e.g. writing to "foo.zip" - makes the output to be in the zip format). Otherwise the output + inferred from the filename if possible (e.g. writing to `foo.zip` + makes the output to be in the `zip` format). Otherwise the output format is `tar`. -l:: @@ -143,17 +145,16 @@ tar..command:: is executed using the shell with the generated tar file on its standard input, and should produce the final output on its standard output. Any compression-level options will be passed - to the command (e.g., "-9"). An output file with the same - extension as `` will be use this format if no other - format is given. + to the command (e.g., `-9`). + -The "tar.gz" and "tgz" formats are defined automatically and default to -`gzip -cn`. You may override them with custom commands. +The `tar.gz` and `tgz` formats are defined automatically and use the +magic command `git archive gzip` by default, which invokes an internal +implementation of gzip. tar..remote:: - If true, enable `` for use by remote clients via + If true, enable the format for use by remote clients via linkgit:git-upload-archive[1]. Defaults to false for - user-defined formats, but true for the "tar.gz" and "tgz" + user-defined formats, but true for the `tar.gz` and `tgz` formats. [[ATTRIBUTES]] diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index d7a46cc674..4400a17330 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -241,6 +241,12 @@ MAPPING AUTHORS See linkgit:gitmailmap[5]. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/blame.txt[] SEE ALSO -------- diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index ae82378349..12c5f84e3b 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -336,6 +336,10 @@ CONFIGURATION `--list` is used or implied. The default is to use a pager. See linkgit:git-config[1]. +include::includes/cmd-config-section-rest.txt[] + +include::config/branch.txt[] + EXAMPLES -------- diff --git a/Documentation/git-bugreport.txt b/Documentation/git-bugreport.txt index d8817bf3ce..eca726e579 100644 --- a/Documentation/git-bugreport.txt +++ b/Documentation/git-bugreport.txt @@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] 'git bugreport' [(-o | --output-directory) ] [(-s | --suffix) ] + [--diagnose[=]] DESCRIPTION ----------- @@ -31,6 +32,10 @@ The following information is captured automatically: - A list of enabled hooks - $SHELL +Additional information may be gathered into a separate zip archive using the +`--diagnose` option, and can be attached alongside the bugreport document to +provide additional context to readers. + This tool is invoked via the typical Git setup process, which means that in some cases, it might not be able to launch - for example, if a relevant config file is unreadable. In this kind of scenario, it may be helpful to manually gather @@ -49,6 +54,19 @@ OPTIONS named 'git-bugreport-'. This should take the form of a strftime(3) format string; the current local time will be used. +--no-diagnose:: +--diagnose[=]:: + Create a zip archive of supplemental information about the user's + machine, Git client, and repository state. The archive is written to the + same output directory as the bug report and is named + 'git-diagnostics-'. ++ +Without `mode` specified, the diagnostic archive will contain the default set of +statistics reported by `git diagnose`. An optional `mode` value may be specified +to change which information is included in the archive. See +linkgit:git-diagnose[1] for the list of valid values for `mode` and details +about their usage. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 7685b57045..18a022b4b4 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -42,7 +42,7 @@ BUNDLE FORMAT Bundles are `.pack` files (see linkgit:git-pack-objects[1]) with a header indicating what references are contained within the bundle. -Like the the packed archive format itself bundles can either be +Like the packed archive format itself bundles can either be self-contained, or be created using exclusions. See the "OBJECT PREREQUISITES" section below. @@ -56,10 +56,8 @@ using "thin packs", bundles created using exclusions are smaller in size. That they're "thin" under the hood is merely noted here as a curiosity, and as a reference to other documentation. -See link:technical/bundle-format.html[the `bundle-format` -documentation] for more details and the discussion of "thin pack" in -link:technical/pack-format.html[the pack format documentation] for -further details. +See linkgit:gitformat-bundle[5] for more details and the discussion of +"thin pack" in linkgit:gitformat-pack[5] for further details. OPTIONS ------- @@ -77,7 +75,7 @@ verify :: commits exist and are fully linked in the current repository. Then, 'git bundle' prints a list of missing commits, if any. Finally, information about additional capabilities, such as "object - filter", is printed. See "Capabilities" in link:technical/bundle-format.html + filter", is printed. See "Capabilities" in linkgit:gitformat-bundle[5] for more information. The exit code is zero for success, but will be nonzero if the bundle file is invalid. @@ -337,6 +335,11 @@ You can also see what references it offers: $ git ls-remote mybundle ---------------- +FILE FORMAT +----------- + +See linkgit:gitformat-bundle[5]. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 24a811f0ef..ec30b5c574 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -14,7 +14,7 @@ SYNOPSIS 'git cat-file' (-t | -s) [--allow-unknown-type] 'git cat-file' (--batch | --batch-check | --batch-command) [--batch-all-objects] [--buffer] [--follow-symlinks] [--unordered] - [--textconv | --filters] + [--textconv | --filters] [-z] 'git cat-file' (--textconv | --filters) [: | --path= ] @@ -63,6 +63,12 @@ OPTIONS or to ask for a "blob" with `` being a tag object that points at it. +--[no-]mailmap:: +--[no-]use-mailmap:: + Use mailmap file to map author, committer and tagger names + and email addresses to canonical real names and email addresses. + See linkgit:git-shortlog[1]. + --textconv:: Show the content as transformed by a textconv filter. In this case, `` has to be of the form `:`, or `:` in @@ -207,6 +213,11 @@ respectively print: /etc/passwd -- +-z:: + Only meaningful with `--batch`, `--batch-check`, or + `--batch-command`; input is NUL-delimited instead of + newline-delimited. + OUTPUT ------ diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 9f37e22e13..4cb9d555b4 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -600,6 +600,13 @@ $ edit frotz $ git add frotz ------------ +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/checkout.txt[] + SEE ALSO -------- linkgit:git-switch[1], diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 78dcc9171f..1e8ac9df60 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -156,10 +156,7 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. ---rerere-autoupdate:: ---no-rerere-autoupdate:: - Allow the rerere mechanism to update the index with the - result of auto-conflict resolution if possible. +include::rerere-options.txt[] SEQUENCER SUBCOMMANDS --------------------- diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index a7f309dff5..91742633fa 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -133,6 +133,13 @@ help:: Show brief usage of interactive git-clean. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/clean.txt[] + SEE ALSO -------- linkgit:gitignore[5] diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 632bd1348e..d6434d262d 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -323,6 +323,13 @@ or `--mirror` is given) for `host.xz:foo/.git`). Cloning into an existing directory is only allowed if the directory is empty. +--bundle-uri=:: + Before fetching from the remote, fetch a bundle from the given + `` and unbundle the data into the local repository. The refs + in the bundle will be stored under the hidden `refs/bundle/*` + namespace. This option is incompatible with `--depth`, + `--shallow-since`, and `--shallow-exclude`. + :git-clone: 1 include::urls.txt[] @@ -363,6 +370,15 @@ $ cd my-linux $ git clone --bare -l /home/proj/.git /pub/scm/proj.git ------------ +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/init.txt[] + +include::config/clone.txt[] + GIT --- diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt index 6cea9ab463..18431647a2 100644 --- a/Documentation/git-column.txt +++ b/Documentation/git-column.txt @@ -74,6 +74,13 @@ v2.4.3 v2.4.4 v2.4.5 v2.4.6 v2.4.7 v2.4.8 v2.4.9 ------------ +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/column.txt[] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt index e1f48c95b3..36fe56c2c7 100644 --- a/Documentation/git-commit-graph.txt +++ b/Documentation/git-commit-graph.txt @@ -142,6 +142,18 @@ $ git show-ref -s | git commit-graph write --stdin-commits $ git rev-parse HEAD | git commit-graph write --stdin-commits --append ------------------------------------------------ +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/commitgraph.txt[] + + +FILE FORMAT +----------- + +see linkgit:gitformat-commit-graph[5]. GIT --- diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 6c60bf98f9..225c6c9f2e 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -557,6 +557,10 @@ The editor used to edit the commit log message will be chosen from the `VISUAL` environment variable, or the `EDITOR` environment variable (in that order). See linkgit:git-var[1] for details. +include::includes/cmd-config-section-rest.txt[] + +include::config/commit.txt[] + HOOKS ----- This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`, diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 9376e39aef..7a2bcb2f6c 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -297,23 +297,20 @@ The default is to use a pager. FILES ----- -If not set explicitly with `--file`, there are four files where -'git config' will search for configuration options: +By default, 'git config' will read configuration options from multiple +files: $(prefix)/etc/gitconfig:: System-wide configuration file. $XDG_CONFIG_HOME/git/config:: - Second user-specific configuration file. If $XDG_CONFIG_HOME is not set - or empty, `$HOME/.config/git/config` will be used. Any single-valued - variable set in this file will be overwritten by whatever is in - `~/.gitconfig`. It is a good idea not to create this file if - you sometimes use older versions of Git, as support for this - file was added fairly recently. - ~/.gitconfig:: - User-specific configuration file. Also called "global" - configuration file. + User-specific configuration files. When the XDG_CONFIG_HOME environment + variable is not set or empty, $HOME/.config/ is used as + $XDG_CONFIG_HOME. ++ +These are also called "global" configuration files. If both files exist, both +files are read in the order given above. $GIT_DIR/config:: Repository specific configuration file. @@ -322,28 +319,80 @@ $GIT_DIR/config.worktree:: This is optional and is only searched when `extensions.worktreeConfig` is present in $GIT_DIR/config. -If no further options are given, all reading options will read all of these -files that are available. If the global or the system-wide configuration -file are not available they will be ignored. If the repository configuration -file is not available or readable, 'git config' will exit with a non-zero -error code. However, in neither case will an error message be issued. +You may also provide additional configuration parameters when running any +git command by using the `-c` option. See linkgit:git[1] for details. + +Options will be read from all of these files that are available. If the +global or the system-wide configuration files are missing or unreadable they +will be ignored. If the repository configuration file is missing or unreadable, +'git config' will exit with a non-zero error code. An error message is produced +if the file is unreadable, but not if it is missing. The files are read in the order given above, with last value found taking precedence over values read earlier. When multiple values are taken then all values of a key from all files will be used. -You may override individual configuration parameters when running any git -command by using the `-c` option. See linkgit:git[1] for details. - -All writing options will per default write to the repository specific +By default, options are only written to the repository specific configuration file. Note that this also affects options like `--replace-all` and `--unset`. *'git config' will only ever change one file at a time*. -You can override these rules using the `--global`, `--system`, -`--local`, `--worktree`, and `--file` command-line options; see -<> above. +You can limit which configuration sources are read from or written to by +specifying the path of a file with the `--file` option, or by specifying a +configuration scope with `--system`, `--global`, `--local`, or `--worktree`. +For more, see <> above. + +[[SCOPES]] +SCOPES +------ + +Each configuration source falls within a configuration scope. The scopes +are: + +system:: + $(prefix)/etc/gitconfig + +global:: + $XDG_CONFIG_HOME/git/config ++ +~/.gitconfig + +local:: + $GIT_DIR/config + +worktree:: + $GIT_DIR/config.worktree + +command:: + GIT_CONFIG_{COUNT,KEY,VALUE} environment variables (see <> + below) ++ +the `-c` option + +With the exception of 'command', each scope corresponds to a command line +option: `--system`, `--global`, `--local`, `--worktree`. + +When reading options, specifying a scope will only read options from the +files within that scope. When writing options, specifying a scope will write +to the files within that scope (instead of the repository specific +configuration file). See <> above for a complete description. + +Most configuration options are respected regardless of the scope it is +defined in, but some options are only respected in certain scopes. See the +respective option's documentation for the full details. + +Protected configuration +~~~~~~~~~~~~~~~~~~~~~~~ + +Protected configuration refers to the 'system', 'global', and 'command' scopes. +For security reasons, certain options are only respected when they are +specified in protected configuration, and ignored otherwise. +Git treats these scopes as if they are controlled by the user or a trusted +administrator. This is because an attacker who controls these scopes can do +substantial harm without using Git, so it is assumed that the user's environment +protects these scopes against attackers. +[[ENVIRONMENT]] ENVIRONMENT ----------- diff --git a/Documentation/git-diagnose.txt b/Documentation/git-diagnose.txt new file mode 100644 index 0000000000..3ec8cc7ad7 --- /dev/null +++ b/Documentation/git-diagnose.txt @@ -0,0 +1,65 @@ +git-diagnose(1) +================ + +NAME +---- +git-diagnose - Generate a zip archive of diagnostic information + +SYNOPSIS +-------- +[verse] +'git diagnose' [(-o | --output-directory) ] [(-s | --suffix) ] + [--mode=] + +DESCRIPTION +----------- +Collects detailed information about the user's machine, Git client, and +repository state and packages that information into a zip archive. The +generated archive can then, for example, be shared with the Git mailing list to +help debug an issue or serve as a reference for independent debugging. + +By default, the following information is captured in the archive: + + * 'git version --build-options' + * The path to the repository root + * The available disk space on the filesystem + * The name and size of each packfile, including those in alternate object + stores + * The total count of loose objects, as well as counts broken down by + `.git/objects` subdirectory + +Additional information can be collected by selecting a different diagnostic mode +using the `--mode` option. + +This tool differs from linkgit:git-bugreport[1] in that it collects much more +detailed information with a greater focus on reporting the size and data shape +of repository contents. + +OPTIONS +------- +-o :: +--output-directory :: + Place the resulting diagnostics archive in `` instead of the + current directory. + +-s :: +--suffix :: + Specify an alternate suffix for the diagnostics archive name, to create + a file named 'git-diagnostics-'. This should take the + form of a strftime(3) format string; the current local time will be + used. + +--mode=(stats|all):: + Specify the type of diagnostics that should be collected. The default behavior + of 'git diagnose' is equivalent to `--mode=stats`. ++ +The `--mode=all` option collects everything included in `--mode=stats`, as well +as copies of `.git`, `.git/hooks`, `.git/info`, `.git/logs`, and +`.git/objects/info` directories. This additional information may be sensitive, +as it can be used to reconstruct the full contents of the diagnosed repository. +Users should exercise caution when sharing an archive generated with +`--mode=all`. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 6236c75c9b..85ae6d6d08 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -213,6 +213,13 @@ $ git diff -R <2> rewrites (very expensive). <2> Output diff in reverse. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/diff.txt[] + SEE ALSO -------- diff(1), diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 143b0c49d7..9d14c3c9f0 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -113,33 +113,14 @@ instead. `--no-symlinks` is the default on Windows. See linkgit:git-diff[1] for the full list of supported options. -CONFIG VARIABLES ----------------- +CONFIGURATION +------------- 'git difftool' falls back to 'git mergetool' config variables when the difftool equivalents have not been defined. -diff.tool:: - The default diff tool to use. +include::includes/cmd-config-section-rest.txt[] -diff.guitool:: - The default diff tool to use when `--gui` is specified. - -difftool..path:: - Override the path for the given tool. This is useful in case - your tool is not in the PATH. - -difftool..cmd:: - Specify the command to invoke the specified diff tool. -+ -See the `--tool=` option above for more details. - -difftool.prompt:: - Prompt before each invocation of the diff tool. - -difftool.trustExitCode:: - Exit difftool if the invoked diff tool returns a non-zero exit status. -+ -See the `--trust-exit-code` option above for more details. +include::config/difftool.txt[] SEE ALSO -------- diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 39cfa05b28..8b5dd6add0 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -1564,6 +1564,13 @@ operator can use this facility to peek at the objects and refs from an import in progress, at the cost of some added running time and worse compression. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/fastimport.txt[] + SEE ALSO -------- linkgit:git-fast-export[1] diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index e9d364669a..63d9569e16 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -285,6 +285,13 @@ linkgit:git-gc[1]). include::transfer-data-leaks.txt[] +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/fetch.txt[] + BUGS ---- Using --recurse-submodules can only fetch new commits in submodules that are diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index be797d7a28..dfcc7da4c2 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -275,6 +275,17 @@ header). Note also that `git send-email` already handles this transformation for you, and this option should not be used if you are feeding the result to `git send-email`. +--[no-]force-in-body-from:: + With the e-mail sender specified via the `--from` option, by + default, an in-body "From:" to identify the real author of + the commit is added at the top of the commit log message if + the sender is different from the author. With this option, + the in-body "From:" is added even when the sender and the + author have the same name and address, which may help if the + mailing list software mangles the sender's identity. + Defaults to the value of the `format.forceInBodyFrom` + configuration variable. + --add-header=
:: Add an arbitrary header to the email headers. This is in addition to any configured headers, and may be used multiple times. diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 5088783dcc..29318ea957 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -107,6 +107,8 @@ care about this output and want to speed it up further. CONFIGURATION ------------- +include::includes/cmd-config-section-all.txt[] + include::config/fsck.txt[] DISCUSSION diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 0af7540a0c..a65c9aa62d 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -110,8 +110,7 @@ users and their repositories. CONFIGURATION ------------- -The below documentation is the same as what's found in -linkgit:git-config[1]: +include::includes/cmd-config-section-all.txt[] include::config/gc.txt[] diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 3d393fbac1..dabdbe8471 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -23,6 +23,7 @@ SYNOPSIS [--break] [--heading] [-p | --show-function] [-A ] [-B ] [-C ] [-W | --function-context] + [(-m | --max-count) ] [--threads ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] @@ -238,6 +239,14 @@ providing this option will cause it to die. `git diff` works out patch hunk headers (see 'Defining a custom hunk-header' in linkgit:gitattributes[5]). +-m :: +--max-count :: + Limit the amount of matches per file. When using the `-v` or + `--invert-match` option, the search stops after the specified + number of non-matches. A value of -1 will return unlimited + results (the default). A value of 0 will exit immediately with + a non-zero status. + --threads :: Number of grep worker threads to use. See `grep.threads` in 'CONFIGURATION' for more information. @@ -334,34 +343,9 @@ performance in this case, it might be desirable to use `--threads=1`. CONFIGURATION ------------- -grep.lineNumber:: - If set to true, enable `-n` option by default. - -grep.column:: - If set to true, enable the `--column` option by default. - -grep.patternType:: - Set the default matching behavior. Using a value of 'basic', 'extended', - 'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`, - `--fixed-strings`, or `--perl-regexp` option accordingly, while the - value 'default' will return to the default matching behavior. - -grep.extendedRegexp:: - If set to true, enable `--extended-regexp` option by default. This - option is ignored when the `grep.patternType` option is set to a value - other than 'default'. - -grep.threads:: - Number of grep worker threads to use. If unset (or set to 0), Git will - use as many threads as the number of logical cores available. - -grep.fullName:: - If set to true, enable `--full-name` option by default. - -grep.fallbackToNoIndex:: - If set to true, fall back to git grep --no-index if git grep - is executed outside of a git repository. Defaults to false. +include::includes/cmd-config-section-all.txt[] +include::config/grep.txt[] GIT --- diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index 239c68db45..2b0b5e390d 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -9,14 +9,16 @@ SYNOPSIS -------- [verse] 'git help' [-a|--all] [--[no-]verbose] [--[no-]external-commands] [--[no-]aliases] -'git help' [[-i|--info] [-m|--man] [-w|--web]] [|] +'git help' [[-i|--info] [-m|--man] [-w|--web]] [|] 'git help' [-g|--guides] 'git help' [-c|--config] +'git help' [--user-interfaces] +'git help' [--developer-interfaces] DESCRIPTION ----------- -With no options and no '' or '' given, the synopsis of the 'git' +With no options and no '' or '' given, the synopsis of the 'git' command and a list of the most commonly used Git commands are printed on the standard output. @@ -26,8 +28,8 @@ printed on the standard output. If the option `--guides` or `-g` is given, a list of the Git concept guides is also printed on the standard output. -If a command, or a guide, is given, a manual page for that command or -guide is brought up. The 'man' program is used by default for this +If a command or other documentation is given, the relevant manual page +will be brought up. The 'man' program is used by default for this purpose, but this can be overridden by other options or configuration variables. @@ -69,6 +71,23 @@ OPTIONS --guides:: Prints a list of the Git concept guides on the standard output. +--user-interfaces:: + Prints a list of the repository, command and file interfaces + documentation on the standard output. ++ +In-repository file interfaces such as `.git/info/exclude` are +documented here (see linkgit:gitrepository-layout[5]), as well as +in-tree configuration such as `.mailmap` (see linkgit:gitmailmap[5]). ++ +This section of the documentation also covers general or widespread +user-interface conventions (e.g. linkgit:gitcli[7]), and +pseudo-configuration such as the file-based `.git/hooks/*` interface +described in linkgit:githooks[5]. + +--developer-interfaces:: + Print list of file formats, protocols and other developer + interfaces documentation on the standard output. + -i:: --info:: Display manual page for the command in the 'info' format. The diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 63cf498ce9..f7b1851514 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -54,6 +54,8 @@ CONFIGURATION To use the tool, `imap.folder` and either `imap.tunnel` or `imap.host` must be set to appropriate values. +include::includes/cmd-config-section-rest.txt[] + include::config/imap.txt[] EXAMPLES diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index ad921fe782..160dea1372 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -169,6 +169,13 @@ $ git commit <3> <2> Add all existing files to the index. <3> Record the pristine state as the first commit in the history. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/init.txt[] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 956a01d184..6d6197cd0a 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -60,10 +60,12 @@ non-whitespace lines before a line that starts with '---' (followed by a space or the end of the line). Such three minus signs start the patch part of the message. See also `--no-divider` below. -When reading trailers, there can be whitespaces after the -token, the separator and the value. There can also be whitespaces -inside the token and the value. The value may be split over multiple lines with -each subsequent line starting with whitespace, like the "folding" in RFC 822. +When reading trailers, there can be no whitespace before or inside the +token, but any number of regular space and tab characters are allowed +between the token and the separator. There can be whitespaces before, +inside or after the value. The value may be split over multiple lines +with each subsequent line starting with at least one whitespace, like +the "folding" in RFC 822. Note that 'trailers' do not follow and are not intended to follow many rules for RFC 822 headers. For example they do not follow diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 20e87cecf4..2a66cf8880 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -45,13 +45,23 @@ OPTIONS --decorate-refs=:: --decorate-refs-exclude=:: - If no `--decorate-refs` is given, pretend as if all refs were - included. For each candidate, do not use it for decoration if it + For each candidate reference, do not use it for decoration if it matches any patterns given to `--decorate-refs-exclude` or if it doesn't match any of the patterns given to `--decorate-refs`. The `log.excludeDecoration` config option allows excluding refs from the decorations, but an explicit `--decorate-refs` pattern will override a match in `log.excludeDecoration`. ++ +If none of these options or config settings are given, then references are +used as decoration if they match `HEAD`, `refs/heads/`, `refs/remotes/`, +`refs/stash/`, or `refs/tags/`. + +--clear-decorations:: + When specified, this option clears all previous `--decorate-refs` + or `--decorate-refs-exclude` options and relaxes the default + decoration filter to include all references. This option is + assumed if the config value `log.initialDecorationSet` is set to + `all`. --source:: Print out the ref name given on the command line by which each @@ -199,47 +209,11 @@ i18n.logOutputEncoding:: Defaults to the value of `i18n.commitEncoding` if set, and UTF-8 otherwise. -log.date:: - Default format for human-readable dates. (Compare the - `--date` option.) Defaults to "default", which means to write - dates like `Sat May 8 19:35:34 2010 -0500`. -+ -If the format is set to "auto:foo" and the pager is in use, format -"foo" will be the used for the date format. Otherwise "default" will -be used. - -log.follow:: - If `true`, `git log` will act as if the `--follow` option was used when - a single is given. This has the same limitations as `--follow`, - i.e. it cannot be used to follow multiple files and does not work well - on non-linear history. - -log.showRoot:: - If `false`, `git log` and related commands will not treat the - initial commit as a big creation event. Any root commits in - `git log -p` output would be shown without a diff attached. - The default is `true`. - -log.showSignature:: - If `true`, `git log` and related commands will act as if the - `--show-signature` option was passed to them. - -mailmap.*:: - See linkgit:git-shortlog[1]. - -notes.displayRef:: - Which refs, in addition to the default set by `core.notesRef` - or `GIT_NOTES_REF`, to read notes from when showing commit - messages with the `log` family of commands. See - linkgit:git-notes[1]. -+ -May be an unabbreviated ref name or a glob and may be specified -multiple times. A warning will be issued for refs that do not exist, -but a glob that does not match any refs is silently ignored. -+ -This setting can be disabled by the `--no-notes` option, -overridden by the `GIT_NOTES_DISPLAY_REF` environment variable, -and overridden by the `--notes=` option. +include::includes/cmd-config-section-rest.txt[] + +include::config/log.txt[] + +include::config/notes.txt[] GIT --- diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 0dabf3f0dd..d7986419c2 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -20,7 +20,7 @@ SYNOPSIS [--exclude-standard] [--error-unmatch] [--with-tree=] [--full-name] [--recurse-submodules] - [--abbrev[=]] [--] [...] + [--abbrev[=]] [--format=] [--] [...] DESCRIPTION ----------- @@ -192,6 +192,13 @@ followed by the ("attr/"). to the contained files. Sparse directories will be shown with a trailing slash, such as "x/" for a sparse directory "x". +--format=:: + A string that interpolates `%(fieldname)` from the result being shown. + It also interpolates `%%` to `%`, and `%xx` where `xx` are hex digits + interpolates to character with hex code `xx`; for example `%00` + interpolates to `\0` (NUL), `%09` to `\t` (TAB) and %0a to `\n` (LF). + --format cannot be combined with `-s`, `-o`, `-k`, `-t`, `--resolve-undo` + and `--eol`. \--:: Do not interpret any more arguments as options. @@ -223,6 +230,36 @@ quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). Using `-z` the filename is output verbatim and the line is terminated by a NUL byte. +It is possible to print in a custom format by using the `--format` +option, which is able to interpolate different fields using +a `%(fieldname)` notation. For example, if you only care about the +"objectname" and "path" fields, you can execute with a specific +"--format" like + + git ls-files --format='%(objectname) %(path)' + +FIELD NAMES +----------- +The way each path is shown can be customized by using the +`--format=` option, where the %(fieldname) in the + string for various aspects of the index entry are +interpolated. The following "fieldname" are understood: + +objectmode:: + The mode of the file which is recorded in the index. +objectname:: + The name of the file which is recorded in the index. +stage:: + The stage of the file which is recorded in the index. +eolinfo:index:: +eolinfo:worktree:: + The (see the description of the `--eol` option) of + the contents in the index or in the worktree for the path. +eolattr:: + The (see the description of the `--eol` option) + that applies to the path. +path:: + The pathname of the file which is recorded in the index. EXCLUDE PATTERNS ---------------- diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 3fcfd965fd..28060283c7 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -115,6 +115,13 @@ If no such configuration option has been set, `warn` will be used. :: The patch extracted from e-mail. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/mailinfo.txt[] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index e56bad28c6..9c630efe19 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -397,6 +397,13 @@ If you want to customize the background tasks, please rename the tasks so future calls to `git maintenance (start|stop)` do not overwrite your custom tasks. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/maintenance.txt[] + GIT --- diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt index 58731c1942..d6c356740e 100644 --- a/Documentation/git-merge-tree.txt +++ b/Documentation/git-merge-tree.txt @@ -3,26 +3,237 @@ git-merge-tree(1) NAME ---- -git-merge-tree - Show three-way merge without touching index +git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' +'git merge-tree' [--write-tree] [] +'git merge-tree' [--trivial-merge] (deprecated) +[[NEWMERGE]] DESCRIPTION ----------- -Reads three tree-ish, and output trivial merge results and -conflicting stages to the standard output. This is similar to -what three-way 'git read-tree -m' does, but instead of storing the -results in the index, the command outputs the entries to the -standard output. - -This is meant to be used by higher level scripts to compute -merge results outside of the index, and stuff the results back into the -index. For this reason, the output from the command omits -entries that match the tree. + +This command has a modern `--write-tree` mode and a deprecated +`--trivial-merge` mode. With the exception of the +<> section at the end, the rest of +this documentation describes modern `--write-tree` mode. + +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. + +The performed merge will use the same feature as the "real" +linkgit:git-merge[1], including: + + * three way content merges of individual files + * rename detection + * proper directory/file conflict handling + * recursive ancestor consolidation (i.e. when there is more than one + merge base, creating a virtual merge base by merging the merge bases) + * etc. + +After the merge completes, a new toplevel tree object is created. See +`OUTPUT` below for details. + +OPTIONS +------- + +-z:: + Do not quote filenames in the section, + and end each filename with a NUL character rather than + newline. Also begin the messages section with a NUL character + instead of a newline. See <> below for more information. + +--name-only:: + In the Conflicted file info section, instead of writing a list + of (mode, oid, stage, path) tuples to output for conflicted + files, just provide a list of filenames with conflicts (and + do not list filenames multiple times if they have multiple + conflicting stages). + +--[no-]messages:: + Write any informational messages such as "Auto-merging " + or CONFLICT notices to the end of stdout. If unspecified, the + default is to include these messages if there are merge + conflicts, and to omit them otherwise. + +--allow-unrelated-histories:: + merge-tree will by default error out if the two branches specified + share no common history. This flag can be given to override that + check and make the merge proceed anyway. + +[[OUTPUT]] +OUTPUT +------ + +For a successful merge, the output from git-merge-tree is simply one +line: + + + +Whereas for a conflicted merge, the output is by default of the form: + + + + + +These are discussed individually below. + +[[OIDTLT]] +OID of toplevel tree +~~~~~~~~~~~~~~~~~~~~ + +This is a tree object that represents what would be checked out in the +working tree at the end of `git merge`. If there were conflicts, then +files within this tree may have embedded conflict markers. This section +is always followed by a newline (or NUL if `-z` is passed). + +[[CFI]] +Conflicted file info +~~~~~~~~~~~~~~~~~~~~ + +This is a sequence of lines with the format + + + +The filename will be quoted as explained for the configuration +variable `core.quotePath` (see linkgit:git-config[1]). However, if +the `--name-only` option is passed, the mode, object, and stage will +be omitted. If `-z` is passed, the "lines" are terminated by a NUL +character instead of a newline character. + +[[IM]] +Informational messages +~~~~~~~~~~~~~~~~~~~~~~ + +This always starts with a blank line (or NUL if `-z` is passed) to +separate it from the previous sections, and then has free-form +messages about the merge, such as: + + * "Auto-merging " + * "CONFLICT (rename/delete): renamed...but deleted in..." + * "Failed to merge submodule ()" + * "Warning: cannot merge binary files: " + +Note that these free-form messages will never have a NUL character +in or between them, even if -z is passed. It is simply a large block +of text taking up the remainder of the output. + +EXIT STATUS +----------- + +For a successful, non-conflicted merge, the exit status is 0. When the +merge has conflicts, the exit status is 1. If the merge is not able to +complete (or start) due to some kind of error, the exit status is +something other than 0 or 1 (and the output is unspecified). + +USAGE NOTES +----------- + +This command is intended as low-level plumbing, similar to +linkgit:git-hash-object[1], linkgit:git-mktree[1], +linkgit:git-commit-tree[1], linkgit:git-write-tree[1], +linkgit:git-update-ref[1], and linkgit:git-mktag[1]. Thus, it can be +used as a part of a series of steps such as: + + NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2) + test $? -eq 0 || die "There were conflicts..." + NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) + git update-ref $BRANCH1 $NEWCOMMIT + +Note that when the exit status is non-zero, `NEWTREE` in this sequence +will contain a lot more output than just a tree. + +For conflicts, the output includes the same information that you'd get +with linkgit:git-merge[1]: + + * what would be written to the working tree (the + <>) + * the higher order stages that would be written to the index (the + <>) + * any messages that would have been printed to stdout (the + <>) + +MISTAKES TO AVOID +----------------- + +Do NOT look through the resulting toplevel tree to try to find which +files conflict; parse the <> section instead. +Not only would parsing an entire tree be horrendously slow in large +repositories, there are numerous types of conflicts not representable by +conflict markers (modify/delete, mode conflict, binary file changed on +both sides, file/directory conflicts, various rename conflict +permutations, etc.) + +Do NOT interpret an empty <> list as a clean +merge; check the exit status. A merge can have conflicts without having +individual files conflict (there are a few types of directory rename +conflicts that fall into this category, and others might also be added +in the future). + +Do NOT attempt to guess or make the user guess the conflict types from +the <> list. The information there is +insufficient to do so. For example: Rename/rename(1to2) conflicts (both +sides renamed the same file differently) will result in three different +file having higher order stages (but each only has one higher order +stage), with no way (short of the <> section) +to determine which three files are related. File/directory conflicts +also result in a file with exactly one higher order stage. +Possibly-involved-in-directory-rename conflicts (when +"merge.directoryRenames" is unset or set to "conflicts") also result in +a file with exactly one higher order stage. In all cases, the +<> section has the necessary info, though it +is not designed to be machine parseable. + +Do NOT assume that each paths from <>, and +the logical conflicts in the <> have a +one-to-one mapping, nor that there is a one-to-many mapping, nor a +many-to-one mapping. Many-to-many mappings exist, meaning that each +path can have many logical conflict types in a single merge, and each +logical conflict type can affect many paths. + +Do NOT assume all filenames listed in the <> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging ". + +AVOID taking the OIDS from the <> and +re-merging them to present the conflicts to the user. This will lose +information. Instead, look up the version of the file found within the +<> and show that instead. In particular, +the latter will have conflict markers annotated with the original +branch/commit being merged and, if renames were involved, the original +filename. While you could include the original branch/commit in the +conflict marker annotations when re-merging, the original filename is +not available from the <> and thus you would +be losing information that might help the user resolve the conflict. + +[[DEPMERGE]] +DEPRECATED DESCRIPTION +---------------------- + +Per the <> and unlike the rest of this +documentation, this section describes the deprecated `--trivial-merge` +mode. + +Other than the optional `--trivial-merge`, this mode accepts no +options. + +This mode reads three tree-ish, and outputs trivial merge results and +conflicting stages to the standard output in a semi-diff format. +Since this was designed for higher level scripts to consume and merge +the results back into the index, it omits entries that match +. The result of this second form is similar to what +three-way 'git read-tree -m' does, but instead of storing the results +in the index, the command outputs the entries to the standard output. + +This form not only has limited applicability (a trivial merge cannot +handle content merges of individual files, rename detection, proper +directory/file conflict handling, etc.), the output format is also +difficult to work with, and it will generally be less performant than +the first form even on successful merges (especially if working in +large repositories). GIT --- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 3125473cc1..2d6a1391c8 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -90,10 +90,7 @@ invocations. The automated message can include the branch description. If `--log` is specified, a shortlog of the commits being merged will be appended to the specified message. ---rerere-autoupdate:: ---no-rerere-autoupdate:: - Allow the rerere mechanism to update the index with the - result of auto-conflict resolution if possible. +include::rerere-options.txt[] --overwrite-ignore:: --no-overwrite-ignore:: @@ -386,13 +383,16 @@ include::merge-strategies.txt[] CONFIGURATION ------------- -include::config/merge.txt[] branch..mergeOptions:: Sets default options for merging into branch . The syntax and supported options are the same as those of 'git merge', but option values containing whitespace characters are currently not supported. +include::includes/cmd-config-section-rest.txt[] + +include::config/merge.txt[] + SEE ALSO -------- linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1], diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index f784027bc1..c44e205629 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -102,6 +102,9 @@ success of the resolution after the custom tool has exited. CONFIGURATION ------------- :git-mergetool: 1 + +include::includes/cmd-config-section-all.txt[] + include::config/mergetool.txt[] TEMPORARY FILES diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index c588fb91af..a48c3d5ea6 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -128,8 +128,8 @@ $ git multi-pack-index verify SEE ALSO -------- See link:technical/multi-pack-index.html[The Multi-Pack-Index Design -Document] and link:technical/pack-format.html[The Multi-Pack-Index -Format] for more information on the multi-pack-index feature. +Document] and linkgit:gitformat-pack[5] for more information on the +multi-pack-index feature and its file format. GIT diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 0a4200674c..efbc10f0f5 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -44,7 +44,7 @@ using the `--notes` option. Such notes are added as a patch commentary after a three dash separator line. To change which notes are shown by 'git log', see the -"notes.displayRef" configuration in linkgit:git-log[1]. +"notes.displayRef" discussion in <>. See the "notes.rewrite." configuration for a way to carry notes across commands that rewrite commits. @@ -307,6 +307,7 @@ with 'git log', so if you use such notes, you'll probably need to write some special-purpose tools to do something useful with them. +[[CONFIGURATION]] CONFIGURATION ------------- @@ -316,57 +317,9 @@ core.notesRef:: This setting can be overridden through the environment and command line. -notes.mergeStrategy:: - Which merge strategy to choose by default when resolving notes - conflicts. Must be one of `manual`, `ours`, `theirs`, `union`, or - `cat_sort_uniq`. Defaults to `manual`. See "NOTES MERGE STRATEGIES" - section above for more information on each strategy. -+ -This setting can be overridden by passing the `--strategy` option. - -notes..mergeStrategy:: - Which merge strategy to choose when doing a notes merge into - refs/notes/. This overrides the more general - "notes.mergeStrategy". See the "NOTES MERGE STRATEGIES" section above - for more information on each available strategy. - -notes.displayRef:: - Which ref (or refs, if a glob or specified more than once), in - addition to the default set by `core.notesRef` or - `GIT_NOTES_REF`, to read notes from when showing commit - messages with the 'git log' family of commands. - This setting can be overridden on the command line or by the - `GIT_NOTES_DISPLAY_REF` environment variable. - See linkgit:git-log[1]. - -notes.rewrite.:: - When rewriting commits with (currently `amend` or - `rebase`), if this variable is `false`, git will not copy - notes from the original to the rewritten commit. Defaults to - `true`. See also "`notes.rewriteRef`" below. -+ -This setting can be overridden by the `GIT_NOTES_REWRITE_REF` -environment variable. +include::includes/cmd-config-section-rest.txt[] -notes.rewriteMode:: - When copying notes during a rewrite, what to do if the target - commit already has a note. Must be one of `overwrite`, - `concatenate`, `cat_sort_uniq`, or `ignore`. Defaults to - `concatenate`. -+ -This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` -environment variable. - -notes.rewriteRef:: - When copying notes during a rewrite, specifies the (fully - qualified) ref whose notes should be copied. May be a glob, - in which case notes in all matching refs will be copied. You - may also specify this configuration several times. -+ -Does not have a default value; you must configure this variable to -enable note rewriting. -+ -Can be overridden with the `GIT_NOTES_REWRITE_REF` environment variable. +include::config/notes.txt[] ENVIRONMENT diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 2f25aa3a29..def7657ef9 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -692,6 +692,13 @@ a `git gc` command on the origin repository. include::transfer-data-leaks.txt[] +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/push.txt[] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt index fe350d7f40..0b393715d7 100644 --- a/Documentation/git-range-diff.txt +++ b/Documentation/git-range-diff.txt @@ -12,6 +12,7 @@ SYNOPSIS [--no-dual-color] [--creation-factor=] [--left-only | --right-only] ( | ... | ) + [[--] ...] DESCRIPTION ----------- @@ -19,6 +20,9 @@ DESCRIPTION This command shows the differences between two versions of a patch series, or more generally, two commit ranges (ignoring merge commits). +In the presence of `` arguments, these commit ranges are limited +accordingly. + To that end, it first finds pairs of commits from both commit ranges that correspond with each other. Two commits are said to correspond when the diff between their patches (i.e. the author information, the commit diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index a872ab0fbd..9cb8931c7a 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -376,10 +376,7 @@ See also INCOMPATIBLE OPTIONS below. + See also INCOMPATIBLE OPTIONS below. ---rerere-autoupdate:: ---no-rerere-autoupdate:: - Allow the rerere mechanism to update the index with the - result of auto-conflict resolution if possible. +include::rerere-options.txt[] -S[]:: --gpg-sign[=]:: @@ -612,6 +609,15 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the start would be overridden by the presence of `rebase.rescheduleFailedExec=true` configuration. +--update-refs:: +--no-update-refs:: + Automatically force-update any branches that point to commits that + are being rebased. Any branches that are checked out in a worktree + are not updated in this way. ++ +If the configuration variable `rebase.updateRefs` is set, then this option +can be used to override and disable this setting. + INCOMPATIBLE OPTIONS -------------------- @@ -635,6 +641,7 @@ are incompatible with the following options: * --empty= * --reapply-cherry-picks * --edit-todo + * --update-refs * --root when used in combination with --onto In addition, the following pairs of options are incompatible: @@ -1252,6 +1259,8 @@ merge cmake CONFIGURATION ------------- +include::includes/cmd-config-section-all.txt[] + include::config/rebase.txt[] include::config/sequencer.txt[] diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 8463fe9cf7..5016755efb 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -112,10 +112,7 @@ effect to your index in a row. Pass the merge strategy-specific option through to the merge strategy. See linkgit:git-merge[1] for details. ---rerere-autoupdate:: ---no-rerere-autoupdate:: - Allow the rerere mechanism to update the index with the - result of auto-conflict resolution if possible. +include::rerere-options.txt[] --reference:: Instead of starting the body of the log message with "This @@ -145,6 +142,13 @@ EXAMPLES changes. The revert only modifies the working tree and the index. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/revert.txt[] + SEE ALSO -------- linkgit:git-cherry-pick[1] diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 41cd8cb424..3290043053 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -456,41 +456,9 @@ Information CONFIGURATION ------------- -sendemail.aliasesFile:: - To avoid typing long email addresses, point this to one or more - email aliases files. You must also supply `sendemail.aliasFileType`. +include::includes/cmd-config-section-all.txt[] -sendemail.aliasFileType:: - Format of the file(s) specified in sendemail.aliasesFile. Must be - one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus', or 'sendmail'. -+ -What an alias file in each format looks like can be found in -the documentation of the email program of the same name. The -differences and limitations from the standard formats are -described below: -+ --- -sendmail;; -* Quoted aliases and quoted addresses are not supported: lines that - contain a `"` symbol are ignored. -* Redirection to a file (`/path/name`) or pipe (`|command`) is not - supported. -* File inclusion (`:include: /path/name`) is not supported. -* Warnings are printed on the standard error output for any - explicitly unsupported constructs, and any other lines that are not - recognized by the parser. --- - -sendemail.multiEdit:: - If true (default), a single editor instance will be spawned to edit - files you have to edit (patches when `--annotate` is used, and the - summary when `--compose` is used). If false, files will be edited one - after the other, spawning a new editor each time. - -sendemail.confirm:: - Sets the default for whether to confirm before sending. Must be - one of 'always', 'never', 'cc', 'compose', or 'auto'. See `--confirm` - in the previous section for the meaning of these values. +include::config/sendemail.txt[] EXAMPLES -------- diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 5cc2fcefba..e5ec6b467f 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -199,6 +199,13 @@ shows 10 reflog entries going back from the tip as of 1 hour ago. Without `--list`, the output also shows how these tips are topologically related with each other. +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/showbranch.txt[] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 6e15f47525..c5d7091828 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -382,6 +382,13 @@ grep commit | cut -d\ -f3 | xargs git log --merges --no-walk --grep=WIP ---------------------------------------------------------------- +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/stash.txt[] + SEE ALSO -------- diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt index bbcbdceb45..c60fc9c138 100644 --- a/Documentation/git-switch.txt +++ b/Documentation/git-switch.txt @@ -265,6 +265,13 @@ always create a new name for it (without switching away): $ git switch -c good-surprises ------------ +CONFIGURATION +------------- + +include::includes/cmd-config-section-all.txt[] + +include::config/checkout.txt[] + SEE ALSO -------- linkgit:git-checkout[1], diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 5ea2f2c60e..f4bb9c5daf 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -420,7 +420,7 @@ as `switch`, `pull`, `merge`) will avoid writing these files. However, these commands will sometimes write these files anyway in important cases such as conflicts during a merge or rebase. Git commands will also avoid treating the lack of such files as an -intentional deletion; for example `git add -u` will not not stage a +intentional deletion; for example `git add -u` will not stage a deletion for these files and `git commit -a` will not make a commit deleting them either. diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index 8f87b23ea8..b656b47567 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -39,10 +39,9 @@ OPTIONS --http-backend-info-refs:: Used by linkgit:git-http-backend[1] to serve up `$GIT_URL/info/refs?service=git-upload-pack` requests. See - "Smart Clients" in link:technical/http-protocol.html[the HTTP - transfer protocols] documentation and "HTTP Transport" in - link:technical/protocol-v2.html[the Git Wire Protocol, Version - 2] documentation. Also understood by + "Smart Clients" in linkgit:gitprotocol-http[5] and "HTTP + Transport" in the linkgit:gitprotocol-v2[5] + documentation. Also understood by linkgit:git-receive-pack[1]. :: diff --git a/Documentation/git.txt b/Documentation/git.txt index 47a6095ff4..0c15ef3a8e 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -339,6 +339,23 @@ The following documentation pages are guides about Git concepts. include::cmds-guide.txt[] +Repository, command and file interfaces +--------------------------------------- + +This documentation discusses repository and command interfaces which +users are expected to interact with directly. See `--user-formats` in +linkgit:git-help[1] for more details on the criteria. + +include::cmds-userinterfaces.txt[] + +File formats, protocols and other developer interfaces +------------------------------------------------------ + +This documentation discusses file formats, over-the-wire protocols and +other git developer interfaces. See `--developer-interfaces` in +linkgit:git-help[1]. + +include::cmds-developerinterfaces.txt[] Configuration Mechanism ----------------------- diff --git a/Documentation/technical/bundle-format.txt b/Documentation/gitformat-bundle.txt similarity index 79% rename from Documentation/technical/bundle-format.txt rename to Documentation/gitformat-bundle.txt index b9be8644cf..00e0a20e65 100644 --- a/Documentation/technical/bundle-format.txt +++ b/Documentation/gitformat-bundle.txt @@ -1,11 +1,33 @@ -= Git bundle v2 format +gitformat-bundle(5) +=================== -The Git bundle format is a format that represents both refs and Git objects. +NAME +---- +gitformat-bundle - The bundle file format + + +SYNOPSIS +-------- +[verse] +*.bundle +*.bdl + +DESCRIPTION +----------- + +The Git bundle format is a format that represents both refs and Git +objects. A bundle is a header in a format similar to +linkgit:git-show-ref[1] followed by a pack in *.pack format. -== Format +The format is created and read by the linkgit:git-bundle[1] command, +and supported by e.g. linkgit:git-fetch[1] and linkgit:git-clone[1]. + + +FORMAT +------ We will use ABNF notation to define the Git bundle format. See -protocol-common.txt for the details. +linkgit:gitprotocol-common[5] for the details. A v2 bundle looks like this: @@ -36,7 +58,9 @@ value = *(%01-09 / %0b-FF) pack = ... ; packfile ---- -== Semantics + +SEMANTICS +--------- A Git bundle consists of several parts. @@ -62,13 +86,15 @@ In the bundle format, there can be a comment following a prerequisite obj-id. This is a comment and it has no specific meaning. The writer of the bundle MAY put any string here. The reader of the bundle MUST ignore the comment. -=== Note on the shallow clone and a Git bundle +Note on the shallow clone and a Git bundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note that the prerequisites does not represent a shallow-clone boundary. The semantics of the prerequisites and the shallow-clone boundaries are different, and the Git bundle v2 format cannot represent a shallow clone repository. -== Capabilities +CAPABILITIES +------------ Because there is no opportunity for negotiation, unknown capabilities cause 'git bundle' to abort. @@ -79,3 +105,7 @@ bundle' to abort. * `filter` specifies an object filter as in the `--filter` option in linkgit:git-rev-list[1]. The resulting pack-file must be marked as a `.promisor` pack-file after it is unbundled. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/chunk-format.txt b/Documentation/gitformat-chunk.txt similarity index 89% rename from Documentation/technical/chunk-format.txt rename to Documentation/gitformat-chunk.txt index 593614fced..57202ede27 100644 --- a/Documentation/technical/chunk-format.txt +++ b/Documentation/gitformat-chunk.txt @@ -1,12 +1,25 @@ -Chunk-based file formats -======================== +gitformat-chunk(5) +================== + +NAME +---- +gitformat-chunk - Chunk-based file formats + +SYNOPSIS +-------- + +Used by linkgit:gitformat-commit-graph[5] and the "MIDX" format (see +the pack format documentation in linkgit:gitformat-pack[5]). + +DESCRIPTION +----------- Some file formats in Git use a common concept of "chunks" to describe sections of the file. This allows structured access to a large file by scanning a small "table of contents" for the remaining data. This common format is used by the `commit-graph` and `multi-pack-index` files. See -link:technical/pack-format.html[the `multi-pack-index` format] and -link:technical/commit-graph-format.html[the `commit-graph` format] for +the `multi-pack-index` format in linkgit:gitformat-pack[5] and +the `commit-graph` format in linkgit:gitformat-commit-graph[5] for how they use the chunks to describe structured data. A chunk-based file format begins with some header information custom to @@ -108,9 +121,13 @@ for future formats: * *commit-graph:* see `write_commit_graph_file()` and `parse_commit_graph()` in `commit-graph.c` for how the chunk-format API is used to write and parse the commit-graph file format documented in - link:technical/commit-graph-format.html[the commit-graph file format]. + the commit-graph file format in linkgit:gitformat-commit-graph[5]. * *multi-pack-index:* see `write_midx_internal()` and `load_multi_pack_index()` in `midx.c` for how the chunk-format API is used to write and parse the multi-pack-index file format documented in - link:technical/pack-format.html[the multi-pack-index file format]. + the multi-pack-index file format section of linkgit:gitformat-pack[5]. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/gitformat-commit-graph.txt similarity index 87% rename from Documentation/technical/commit-graph-format.txt rename to Documentation/gitformat-commit-graph.txt index 484b185ba9..7324665716 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/gitformat-commit-graph.txt @@ -1,5 +1,18 @@ -Git commit graph format -======================= +gitformat-commit-graph(5) +========================= + +NAME +---- +gitformat-commit-graph - Git commit graph format + +SYNOPSIS +-------- +[verse] +$GIT_DIR/objects/info/commit-graph +$GIT_DIR/objects/info/commit-graphs/* + +DESCRIPTION +----------- The Git commit graph stores a list of commit OIDs and some associated metadata, including: @@ -30,7 +43,7 @@ and hash type. All multi-byte numbers are in network byte order. -HEADER: +=== HEADER: 4-byte signature: The signature is: {'C', 'G', 'P', 'H'} @@ -52,7 +65,7 @@ HEADER: We infer the length (H*B) of the Base Graphs chunk from this value. -CHUNK LOOKUP: +=== CHUNK LOOKUP: (C + 1) * 12 bytes listing the table of contents for the chunks: First 4 bytes describe the chunk id. Value 0 is a terminating label. @@ -62,23 +75,23 @@ CHUNK LOOKUP: ID appears at most once. The CHUNK LOOKUP matches the table of contents from - link:technical/chunk-format.html[the chunk-based file format]. + the chunk-based file format, see linkgit:gitformat-chunk[5] The remaining data in the body is described one chunk at a time, and these chunks may be given in any order. Chunks are required unless otherwise specified. -CHUNK DATA: +=== CHUNK DATA: - OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes) +==== OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes) The ith entry, F[i], stores the number of OIDs with first byte at most i. Thus F[255] stores the total number of commits (N). - OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) +==== OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes) The OIDs for all commits in the graph, sorted in ascending order. - Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) +==== Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) * The first H bytes are for the OID of the root tree. * The next 8 bytes are for the positions of the first two parents of the ith commit. Stores value 0x70000000 if no parent in that @@ -93,7 +106,7 @@ CHUNK DATA: 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. - Generation Data (ID: {'G', 'D', 'A', '2' }) (N * 4 bytes) [Optional] +==== Generation Data (ID: {'G', 'D', 'A', '2' }) (N * 4 bytes) [Optional] * This list of 4-byte values store corrected commit date offsets for the commits, arranged in the same order as commit data chunk. * If the corrected commit date offset cannot be stored within 31 bits, @@ -104,7 +117,7 @@ CHUNK DATA: by compatible versions of Git and in case of split commit-graph chains, the topmost layer also has Generation Data chunk. - Generation Data Overflow (ID: {'G', 'D', 'O', '2' }) [Optional] +==== Generation Data Overflow (ID: {'G', 'D', 'O', '2' }) [Optional] * This list of 8-byte values stores the corrected commit date offsets for commits with corrected commit date offsets that cannot be stored within 31 bits. @@ -112,7 +125,7 @@ CHUNK DATA: chunk is present and atleast one corrected commit date offset cannot be stored within 31 bits. - Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] +==== Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data stores an array position within this list along with the most-significant bit @@ -120,14 +133,14 @@ CHUNK DATA: positions for the parents until reaching a value with the most-significant bit on. The other bits correspond to the position of the last parent. - Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional] +==== Bloom Filter Index (ID: {'B', 'I', 'D', 'X'}) (N * 4 bytes) [Optional] * The ith entry, BIDX[i], stores the number of bytes in all Bloom filters from commit 0 to commit i (inclusive) in lexicographic order. The Bloom filter for the i-th commit spans from BIDX[i-1] to BIDX[i] (plus header length), where BIDX[-1] is 0. * The BIDX chunk is ignored if the BDAT chunk is not present. - Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] +==== Bloom Filter Data (ID: {'B', 'D', 'A', 'T'}) [Optional] * It starts with header consisting of three unsigned 32-bit integers: - Version of the hash algorithm being used. We currently only support value 1 which corresponds to the 32-bit version of the murmur3 hash @@ -147,13 +160,13 @@ CHUNK DATA: of length one, with either all bits set to zero or one respectively. * The BDAT chunk is present if and only if BIDX is present. - Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional] +==== Base Graphs List (ID: {'B', 'A', 'S', 'E'}) [Optional] This list of H-byte hashes describe a set of B commit-graph files that form a commit-graph chain. The graph position for the ith commit in this file's OID Lookup chunk is equal to i plus the number of commits in all base graphs. If B is non-zero, this chunk must exist. -TRAILER: +=== TRAILER: H-byte HASH-checksum of all of the above. @@ -164,3 +177,7 @@ the number '2' in their chunk IDs because a previous version of Git wrote possibly erroneous data in these chunks with the IDs "GDAT" and "GDOV". By changing the IDs, newer versions of Git will silently ignore those older chunks and write the new information without trusting the incorrect data. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/index-format.txt b/Documentation/gitformat-index.txt similarity index 98% rename from Documentation/technical/index-format.txt rename to Documentation/gitformat-index.txt index f691c20ab0..015cb21bdc 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/gitformat-index.txt @@ -1,5 +1,19 @@ +gitformat-index(5) +================== + +NAME +---- +gitformat-index - Git index format + +SYNOPSIS +-------- +[verse] +$GIT_DIR/index + +DESCRIPTION +----------- + Git index format -================ == The Git index file has the following format @@ -125,7 +139,7 @@ Git index format entry is encoded as if the path name for the previous entry is an empty string). At the beginning of an entry, an integer N in the variable width encoding (the same encoding as the offset is encoded - for OFS_DELTA pack entries; see pack-format.txt) is stored, followed + for OFS_DELTA pack entries; see linkgit:gitformat-pack[5]) is stored, followed by a NUL-terminated string S. Removing N bytes from the end of the path name for the previous entry, and replacing it with the string S yields the path name for this entry. @@ -402,3 +416,7 @@ The remaining data of each directory block is grouped by type: with signature { 's', 'd', 'i', 'r' }. Like the split-index extension, tools should avoid interacting with a sparse index unless they understand this extension. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/pack-format.txt b/Documentation/gitformat-pack.txt similarity index 72% rename from Documentation/technical/pack-format.txt rename to Documentation/gitformat-pack.txt index b520aa9c45..e06af02f21 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/gitformat-pack.txt @@ -1,5 +1,30 @@ -Git pack format -=============== +gitformat-pack(5) +================= + +NAME +---- +gitformat-pack - Git pack format + + +SYNOPSIS +-------- +[verse] +$GIT_DIR/objects/pack/pack-*.{pack,idx} +$GIT_DIR/objects/pack/pack-*.rev +$GIT_DIR/objects/pack/pack-*.mtimes +$GIT_DIR/objects/pack/multi-pack-index + +DESCRIPTION +----------- + +The Git pack format is now Git stores most of its primary repository +data. Over the lietime af a repository loose objects (if any) and +smaller packs are consolidated into larger pack(s). See +linkgit:git-gc[1] and linkgit:git-pack-objects[1]. + +The pack format is also used over-the-wire, see +e.g. linkgit:gitprotocol-v2[5], as well as being a part of +other container formats in the case of linkgit:gitformat-bundle[5]. == Checksums and object IDs @@ -356,7 +381,7 @@ CHUNK LOOKUP: using the next chunk position if necessary.) The CHUNK LOOKUP matches the table of contents from - link:technical/chunk-format.html[the chunk-based file format]. + the chunk-based file format, see linkgit:gitformat-chunk[5]. The remaining data in the body is described one chunk at a time, and these chunks may be given in any order. Chunks are required unless @@ -482,3 +507,132 @@ packs arranged in MIDX order (with the preferred pack coming first). The MIDX's reverse index is stored in the optional 'RIDX' chunk within the MIDX itself. + +== cruft packs + +The cruft packs feature offer an alternative to Git's traditional mechanism of +removing unreachable objects. This document provides an overview of Git's +pruning mechanism, and how a cruft pack can be used instead to accomplish the +same. + +=== Background + +To remove unreachable objects from your repository, Git offers `git repack -Ad` +(see linkgit:git-repack[1]). Quoting from the documentation: + +---- +[...] unreachable objects in a previous pack become loose, unpacked objects, +instead of being left in the old pack. [...] loose unreachable objects will be +pruned according to normal expiry rules with the next 'git gc' invocation. +---- + +Unreachable objects aren't removed immediately, since doing so could race with +an incoming push which may reference an object which is about to be deleted. +Instead, those unreachable objects are stored as loose objects and stay that way +until they are older than the expiration window, at which point they are removed +by linkgit:git-prune[1]. + +Git must store these unreachable objects loose in order to keep track of their +per-object mtimes. If these unreachable objects were written into one big pack, +then either freshening that pack (because an object contained within it was +re-written) or creating a new pack of unreachable objects would cause the pack's +mtime to get updated, and the objects within it would never leave the expiration +window. Instead, objects are stored loose in order to keep track of the +individual object mtimes and avoid a situation where all cruft objects are +freshened at once. + +This can lead to undesirable situations when a repository contains many +unreachable objects which have not yet left the grace period. Having large +directories in the shards of `.git/objects` can lead to decreased performance in +the repository. But given enough unreachable objects, this can lead to inode +starvation and degrade the performance of the whole system. Since we +can never pack those objects, these repositories often take up a large amount of +disk space, since we can only zlib compress them, but not store them in delta +chains. + +=== Cruft packs + +A cruft pack eliminates the need for storing unreachable objects in a loose +state by including the per-object mtimes in a separate file alongside a single +pack containing all loose objects. + +A cruft pack is written by `git repack --cruft` when generating a new pack. +linkgit:git-pack-objects[1]'s `--cruft` option. Note that `git repack --cruft` +is a classic all-into-one repack, meaning that everything in the resulting pack is +reachable, and everything else is unreachable. Once written, the `--cruft` +option instructs `git repack` to generate another pack containing only objects +not packed in the previous step (which equates to packing all unreachable +objects together). This progresses as follows: + + 1. Enumerate every object, marking any object which is (a) not contained in a + kept-pack, and (b) whose mtime is within the grace period as a traversal + tip. + + 2. Perform a reachability traversal based on the tips gathered in the previous + step, adding every object along the way to the pack. + + 3. Write the pack out, along with a `.mtimes` file that records the per-object + timestamps. + +This mode is invoked internally by linkgit:git-repack[1] when instructed to +write a cruft pack. Crucially, the set of in-core kept packs is exactly the set +of packs which will not be deleted by the repack; in other words, they contain +all of the repository's reachable objects. + +When a repository already has a cruft pack, `git repack --cruft` typically only +adds objects to it. An exception to this is when `git repack` is given the +`--cruft-expiration` option, which allows the generated cruft pack to omit +expired objects instead of waiting for linkgit:git-gc[1] to expire those objects +later on. + +It is linkgit:git-gc[1] that is typically responsible for removing expired +unreachable objects. + +=== Caution for mixed-version environments + +Repositories that have cruft packs in them will continue to work with any older +version of Git. Note, however, that previous versions of Git which do not +understand the `.mtimes` file will use the cruft pack's mtime as the mtime for +all of the objects in it. In other words, do not expect older (pre-cruft pack) +versions of Git to interpret or even read the contents of the `.mtimes` file. + +Note that having mixed versions of Git GC-ing the same repository can lead to +unreachable objects never being completely pruned. This can happen under the +following circumstances: + + - An older version of Git running GC explodes the contents of an existing + cruft pack loose, using the cruft pack's mtime. + - A newer version running GC collects those loose objects into a cruft pack, + where the .mtime file reflects the loose object's actual mtimes, but the + cruft pack mtime is "now". + +Repeating this process will lead to unreachable objects not getting pruned as a +result of repeatedly resetting the objects' mtimes to the present time. + +If you are GC-ing repositories in a mixed version environment, consider omitting +the `--cruft` option when using linkgit:git-repack[1] and linkgit:git-gc[1], and +leaving the `gc.cruftPacks` configuration unset until all writers understand +cruft packs. + +=== Alternatives + +Notable alternatives to this design include: + + - The location of the per-object mtime data, and + - Storing unreachable objects in multiple cruft packs. + +On the location of mtime data, a new auxiliary file tied to the pack was chosen +to avoid complicating the `.idx` format. If the `.idx` format were ever to gain +support for optional chunks of data, it may make sense to consolidate the +`.mtimes` format into the `.idx` itself. + +Storing unreachable objects among multiple cruft packs (e.g., creating a new +cruft pack during each repacking operation including only unreachable objects +which aren't already stored in an earlier cruft pack) is significantly more +complicated to construct, and so aren't pursued here. The obvious drawback to +the current implementation is that the entire cruft pack must be re-written from +scratch. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/signature-format.txt b/Documentation/gitformat-signature.txt similarity index 96% rename from Documentation/technical/signature-format.txt rename to Documentation/gitformat-signature.txt index 166721be6f..a249869faf 100644 --- a/Documentation/technical/signature-format.txt +++ b/Documentation/gitformat-signature.txt @@ -1,7 +1,18 @@ -Git signature format -==================== +gitformat-signature(5) +====================== -== Overview +NAME +---- +gitformat-signature - Git cryptographic signature formats + +SYNOPSIS +-------- +[verse] +<[tag|commit] object header(s)> + + +DESCRIPTION +----------- Git uses cryptographic signatures in various places, currently objects (tags, commits, mergetags) and transactions (pushes). In every case, the command which @@ -200,3 +211,7 @@ Date: Wed Jun 15 09:13:29 2016 +0000 # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: D4BE 2231 1AD3 131E 5EDA 29A4 6109 2E85 B722 7189 ---- + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/gitprotocol-capabilities.txt similarity index 95% rename from Documentation/technical/protocol-capabilities.txt rename to Documentation/gitprotocol-capabilities.txt index 9dfade930d..0fb5ea0c1c 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/gitprotocol-capabilities.txt @@ -1,8 +1,20 @@ -Git Protocol Capabilities -========================= +gitprotocol-capabilities(5) +=========================== + +NAME +---- +gitprotocol-capabilities - Protocol v0 and v1 capabilities + +SYNOPSIS +-------- +[verse] + + +DESCRIPTION +----------- NOTE: this document describes capabilities for versions 0 and 1 of the pack -protocol. For version 2, please refer to the link:protocol-v2.html[protocol-v2] +protocol. For version 2, please refer to the linkgit:gitprotocol-v2[5] doc. Servers SHOULD support all capabilities defined in this document. @@ -77,7 +89,7 @@ interleaved with S-R-Q. multi_ack_detailed ------------------ This is an extension of multi_ack that permits client to better -understand the server's in-memory state. See pack-protocol.txt, +understand the server's in-memory state. See linkgit:gitprotocol-pack[5], section "Packfile Negotiation" for more information. no-done @@ -281,7 +293,7 @@ a packfile upload and reference update. If the pushing client requests this capability, after unpacking and updating references the server will respond with whether the packfile unpacked successfully and if each reference was updated successfully. If any of those were not -successful, it will send back an error message. See pack-protocol.txt +successful, it will send back an error message. See linkgit:gitprotocol-pack[5] for example messages. report-status-v2 @@ -292,7 +304,7 @@ adding new "option" directives in order to support reference rewritten by the "proc-receive" hook. The "proc-receive" hook may handle a command for a pseudo-reference which may create or update a reference with different name, new-oid, and old-oid. While the capability -'report-status' cannot report for such case. See pack-protocol.txt +'report-status' cannot report for such case. See linkgit:gitprotocol-pack[5] for details. delete-refs @@ -376,5 +388,9 @@ the server as well. Session IDs should be unique to a given process. They must fit within a packet-line, and must not contain non-printable or whitespace characters. The current implementation uses trace2 session IDs (see -link:api-trace2.html[api-trace2] for details), but this may change and users of -the session ID should not rely on this fact. +link:technical/api-trace2.html[api-trace2] for details), but this may change +and users of the session ID should not rely on this fact. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/protocol-common.txt b/Documentation/gitprotocol-common.txt similarity index 89% rename from Documentation/technical/protocol-common.txt rename to Documentation/gitprotocol-common.txt index ecedb34bba..1486651bd1 100644 --- a/Documentation/technical/protocol-common.txt +++ b/Documentation/gitprotocol-common.txt @@ -1,5 +1,20 @@ -Documentation Common to Pack and Http Protocols -=============================================== +gitprotocol-common(5) +===================== + +NAME +---- +gitprotocol-common - Things common to various protocols + +SYNOPSIS +-------- +[verse] + + +DESCRIPTION +----------- + +This document sets defines things common to various over-the-wire +protocols and file formats used in Git. ABNF Notation ------------- @@ -97,3 +112,7 @@ Examples (as C-style strings): "000bfoobar\n" "foobar\n" "0004" "" ---- + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/http-protocol.txt b/Documentation/gitprotocol-http.txt similarity index 97% rename from Documentation/technical/http-protocol.txt rename to Documentation/gitprotocol-http.txt index cc5126cfed..ccc13f0a40 100644 --- a/Documentation/technical/http-protocol.txt +++ b/Documentation/gitprotocol-http.txt @@ -1,5 +1,19 @@ -HTTP transfer protocols -======================= +gitprotocol-http(5) +=================== + +NAME +---- +gitprotocol-http - Git HTTP-based protocols + + +SYNOPSIS +-------- +[verse] + + + +DESCRIPTION +----------- Git supports two HTTP based transfer protocols. A "dumb" protocol which requires only a standard HTTP server on the server end of the @@ -222,7 +236,7 @@ smart server reply: S: 0000 The client may send Extra Parameters (see -Documentation/technical/pack-protocol.txt) as a colon-separated string +linkgit:gitprotocol-pack[5]) as a colon-separated string in the Git-Protocol HTTP header. Uses the `--http-backend-info-refs` option to @@ -512,11 +526,18 @@ the id obtained through ref discovery as old_id. TODO: Document this further. - -References +REFERENCES ---------- http://www.ietf.org/rfc/rfc1738.txt[RFC 1738: Uniform Resource Locators (URL)] http://www.ietf.org/rfc/rfc2616.txt[RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1] -link:technical/pack-protocol.html -link:technical/protocol-capabilities.html + +SEE ALSO +-------- + +linkgit:gitprotocol-pack[5] +linkgit:gitprotocol-capabilities[5] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/gitprotocol-pack.txt similarity index 98% rename from Documentation/technical/pack-protocol.txt rename to Documentation/gitprotocol-pack.txt index e13a2c064d..dd4108b7a3 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/gitprotocol-pack.txt @@ -1,11 +1,23 @@ -Packfile transfer protocols -=========================== +gitprotocol-pack(5) +=================== + +NAME +---- +gitprotocol-pack - How packs are transferred over-the-wire + +SYNOPSIS +-------- +[verse] + + +DESCRIPTION +----------- Git supports transferring data in packfiles over the ssh://, git://, http:// and file:// transports. There exist two sets of protocols, one for pushing data from a client to a server and another for fetching data from a server to a client. The three transports (ssh, git, file) use the same -protocol to transfer data. http is documented in http-protocol.txt. +protocol to transfer data. http is documented in linkgit:gitprotocol-http[5]. The processes invoked in the canonical Git implementation are 'upload-pack' on the server side and 'fetch-pack' on the client side for fetching data; @@ -18,7 +30,7 @@ pkt-line Format --------------- The descriptions below build on the pkt-line format described in -protocol-common.txt. When the grammar indicate `PKT-LINE(...)`, unless +linkgit:gitprotocol-common[5]. When the grammar indicate `PKT-LINE(...)`, unless otherwise noted the usual pkt-line LF rules apply: the sender SHOULD include a LF, but the receiver MUST NOT complain if it is not present. @@ -60,7 +72,7 @@ Each Extra Parameter takes the form of `=` or ``. Servers that receive any such Extra Parameters MUST ignore all unrecognized keys. Currently, the only Extra Parameter recognized is -"version" with a value of '1' or '2'. See protocol-v2.txt for more +"version" with a value of '1' or '2'. See linkgit:gitprotocol-v2[5] for more information on protocol version 2. Git Transport @@ -455,7 +467,7 @@ Now that the client and server have finished negotiation about what the minimal amount of data that needs to be sent to the client is, the server will construct and send the required data in packfile format. -See pack-format.txt for what the packfile itself actually looks like. +See linkgit:gitformat-pack[5] for what the packfile itself actually looks like. If 'side-band' or 'side-band-64k' capabilities have been specified by the client, the server will send the packfile data multiplexed. @@ -707,3 +719,7 @@ An example client/server communication might look like this: S: 0018ok refs/heads/debug\n S: 002ang refs/heads/master non-fast-forward\n ---- + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/gitprotocol-v2.txt similarity index 97% rename from Documentation/technical/protocol-v2.txt rename to Documentation/gitprotocol-v2.txt index 8a877d27e2..59bf41cefb 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/gitprotocol-v2.txt @@ -1,5 +1,17 @@ -Git Wire Protocol, Version 2 -============================ +gitprotocol-v2(5) +================= + +NAME +---- +gitprotocol-v2 - Git Wire Protocol, Version 2 + +SYNOPSIS +-------- +[verse] + + +DESCRIPTION +----------- This document presents a specification for a version 2 of Git's wire protocol. Protocol v2 will improve upon v1 in the following ways: @@ -26,8 +38,7 @@ Packet-Line Framing ------------------- All communication is done using packet-line framing, just as in v1. See -`Documentation/technical/pack-protocol.txt` and -`Documentation/technical/protocol-common.txt` for more information. +linkgit:gitprotocol-pack[5] and linkgit:gitprotocol-common[5] for more information. In protocol v2 these special packets will have the following semantics: @@ -42,7 +53,7 @@ Initial Client Request In general a client can request to speak protocol v2 by sending `version=2` through the respective side-channel for the transport being used which inevitably sets `GIT_PROTOCOL`. More information can be -found in `pack-protocol.txt` and `http-protocol.txt`, as well as the +found in linkgit:gitprotocol-pack[5] and linkgit:gitprotocol-http[5], as well as the `GIT_PROTOCOL` definition in `git.txt`. In all cases the response from the server is the capability advertisement. @@ -66,7 +77,7 @@ HTTP Transport ~~~~~~~~~~~~~~ When using the http:// or https:// transport a client makes a "smart" -info/refs request as described in `http-protocol.txt` and requests that +info/refs request as described in linkgit:gitprotocol-http[5] and requests that v2 be used by supplying "version=2" in the `Git-Protocol` header. C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0 @@ -533,8 +544,8 @@ the server as well. Session IDs should be unique to a given process. They must fit within a packet-line, and must not contain non-printable or whitespace characters. The current implementation uses trace2 session IDs (see -link:api-trace2.html[api-trace2] for details), but this may change and users of -the session ID should not rely on this fact. +link:technical/api-trace2.html[api-trace2] for details), but this may change +and users of the session ID should not rely on this fact. object-info ~~~~~~~~~~~ @@ -566,3 +577,7 @@ and associated requested information, each separated by a single space. attr = "size" obj-info = obj-id SP obj-size + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt index 6f1e269ae4..ed8da428c9 100644 --- a/Documentation/gitremote-helpers.txt +++ b/Documentation/gitremote-helpers.txt @@ -168,6 +168,9 @@ Supported commands: 'list', 'import'. Can guarantee that when a clone is requested, the received pack is self contained and is connected. +'get':: + Can use the 'get' command to download a file from a given URI. + If a helper advertises 'connect', Git will use it if possible and fall back to another capability if the helper requests so when connecting (see the 'connect' command under COMMANDS). @@ -418,6 +421,12 @@ Supported if the helper has the "connect" capability. + Supported if the helper has the "stateless-connect" capability. +'get' :: + Downloads the file from the given `` to the given ``. If + `.temp` exists, then Git assumes that the `.temp` file is a + partial download from a previous attempt and will resume the + download from that position. + If a fatal error occurs, the program writes the error message to stderr and exits. The caller should expect that a suitable error message has been printed if the child closes the connection without diff --git a/Documentation/howto/recover-corrupted-object-harder.txt b/Documentation/howto/recover-corrupted-object-harder.txt index 8994e2559e..5efb4fe81f 100644 --- a/Documentation/howto/recover-corrupted-object-harder.txt +++ b/Documentation/howto/recover-corrupted-object-harder.txt @@ -68,7 +68,7 @@ Note that the "object" file isn't fit for feeding straight to zlib; it has the git packed object header, which is variable-length. We want to strip that off so we can start playing with the zlib data directly. You can either work your way through it manually (the format is described in -link:../technical/pack-format.html[Documentation/technical/pack-format.txt]), +linkgit:gitformat-pack[5]), or you can walk through it in a debugger. I did the latter, creating a valid pack like: diff --git a/Documentation/includes/cmd-config-section-all.txt b/Documentation/includes/cmd-config-section-all.txt new file mode 100644 index 0000000000..296a239f2a --- /dev/null +++ b/Documentation/includes/cmd-config-section-all.txt @@ -0,0 +1,3 @@ +Everything below this line in this section is selectively included +from the linkgit:git-config[1] documentation. The content is the same +as what's found there: diff --git a/Documentation/includes/cmd-config-section-rest.txt b/Documentation/includes/cmd-config-section-rest.txt new file mode 100644 index 0000000000..b1e7682c1d --- /dev/null +++ b/Documentation/includes/cmd-config-section-rest.txt @@ -0,0 +1,3 @@ +Everything above this line in this section isn't included from the +linkgit:git-config[1] documentation. The content that follows is the +same as what's found there: diff --git a/Documentation/lint-man-section-order.perl b/Documentation/lint-man-section-order.perl index 425377dfeb..02408a0062 100755 --- a/Documentation/lint-man-section-order.perl +++ b/Documentation/lint-man-section-order.perl @@ -32,6 +32,9 @@ my %SECTIONS; 'SEE ALSO' => { order => $order++, }, + 'FILE FORMAT' => { + order => $order++, + }, 'GIT' => { required => 1, order => $order++, diff --git a/Documentation/rerere-options.txt b/Documentation/rerere-options.txt new file mode 100644 index 0000000000..c3321ddea2 --- /dev/null +++ b/Documentation/rerere-options.txt @@ -0,0 +1,9 @@ +--rerere-autoupdate:: +--no-rerere-autoupdate:: + After the rerere mechanism reuses a recorded resolution on + the current conflict to update the files in the working + tree, allow it to also update the index with the result of + resolution. `--no-rerere-autoupdate` is a good way to + double-check what `rerere` did and catch potential + mismerges, before committing the result to the index with a + separate `git add`. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 195e74eec6..1837509566 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -242,6 +242,7 @@ ifdef::git-rev-list[] to `/dev/null` as the output does not have to be formatted. --disk-usage:: +--disk-usage=human:: Suppress normal output; instead, print the sum of the bytes used for on-disk storage by the selected commits or objects. This is equivalent to piping the output into `git cat-file @@ -249,6 +250,8 @@ ifdef::git-rev-list[] faster (especially with `--use-bitmap-index`). See the `CAVEATS` section in linkgit:git-cat-file[1] for the limitations of what "on-disk storage" means. + With the optional value `human`, on-disk storage size is shown + in human-readable string(e.g. 12.24 Kib, 3.50 Mib). endif::git-rev-list[] --cherry-mark:: @@ -389,12 +392,14 @@ Default mode:: merges from the resulting history, as there are no selected commits contributing to this merge. ---ancestry-path:: +--ancestry-path[=]:: When given a range of commits to display (e.g. 'commit1..commit2' - or 'commit2 {caret}commit1'), only display commits that exist - directly on the ancestry chain between the 'commit1' and - 'commit2', i.e. commits that are both descendants of 'commit1', - and ancestors of 'commit2'. + or 'commit2 {caret}commit1'), only display commits in that range + that are ancestors of , descendants of , or + itself. If no commit is specified, use 'commit1' (the + excluded part of the range) as . Can be passed multiple + times; if so, a commit is included if it is any of the commits + given or if it is an ancestor or descendant of one of them. A more detailed explanation follows. @@ -568,11 +573,10 @@ Note the major differences in `N`, `P`, and `Q` over `--full-history`: There is another simplification mode available: ---ancestry-path:: - Limit the displayed commits to those directly on the ancestry - chain between the ``from'' and ``to'' commits in the given commit - range. I.e. only display commits that are ancestor of the ``to'' - commit and descendants of the ``from'' commit. +--ancestry-path[=]:: + Limit the displayed commits to those which are an ancestor of + , or which are a descendant of , or are + itself. + As an example use case, consider the following commit history: + @@ -604,6 +608,29 @@ option does. Applied to the 'D..M' range, it results in: \ L--M ----------------------------------------------------------------------- ++ +We can also use `--ancestry-path=D` instead of `--ancestry-path` which +means the same thing when applied to the 'D..M' range but is just more +explicit. ++ +If we instead are interested in a given topic within this range, and all +commits affected by that topic, we may only want to view the subset of +`D..M` which contain that topic in their ancestry path. So, using +`--ancestry-path=H D..M` for example would result in: ++ +----------------------------------------------------------------------- + E + \ + G---H---I---J + \ + L--M +----------------------------------------------------------------------- ++ +Whereas `--ancestry-path=K D..M` would result in ++ +----------------------------------------------------------------------- + K---------------L--M +----------------------------------------------------------------------- Before discussing another option, `--show-pulls`, we need to create a new example history. @@ -659,7 +686,7 @@ Here, the merge commits `O` and `P` contribute extra noise, as they did not actually contribute a change to `file.txt`. They only merged a topic that was based on an older version of `file.txt`. This is a common issue in repositories using a workflow where many contributors work in -parallel and merge their topic branches along a single trunk: manu +parallel and merge their topic branches along a single trunk: many unrelated merges appear in the `--full-history` results. When using the `--simplify-merges` option, the commits `O` and `P` diff --git a/contrib/scalar/scalar.txt b/Documentation/scalar.txt similarity index 93% rename from contrib/scalar/scalar.txt rename to Documentation/scalar.txt index c0425e0653..f33436c7f6 100644 --- a/contrib/scalar/scalar.txt +++ b/Documentation/scalar.txt @@ -3,7 +3,7 @@ scalar(1) NAME ---- -scalar - an opinionated repository management tool +scalar - A tool for managing large Git repositories SYNOPSIS -------- @@ -20,10 +20,9 @@ scalar delete DESCRIPTION ----------- -Scalar is an opinionated repository management tool. By creating new -repositories or registering existing repositories with Scalar, your Git -experience will speed up. Scalar sets advanced Git config settings, -maintains your repositories in the background, and helps reduce data sent +Scalar is a repository management tool that optimizes Git for use in large +repositories. Scalar improves performance by configuring advanced Git settings, +maintaining repositories in the background, and helping to reduce data sent across the network. An important Scalar concept is the enlistment: this is the top-level directory @@ -162,6 +161,6 @@ SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1]. -Scalar +GIT --- -Associated with the linkgit:git[1] suite +Part of the linkgit:git[1] suite diff --git a/Documentation/technical/api-error-handling.txt b/Documentation/technical/api-error-handling.txt index 70bf1d3e52..665c4960b4 100644 --- a/Documentation/technical/api-error-handling.txt +++ b/Documentation/technical/api-error-handling.txt @@ -46,7 +46,7 @@ parse-options.c. returns -1 after reporting the situation to the caller. These reports will be logged via the trace2 facility. See the "error" -event in link:api-trace2.txt[trace2 API]. +event in link:api-trace2.html[trace2 API]. Customizable error handlers --------------------------- diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index acfd5dc1d8..61fa6ee167 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -8,7 +8,8 @@ Basics ------ The argument vector `argv[]` may usually contain mandatory or optional -'non-option arguments', e.g. a filename or a branch, and 'options'. +'non-option arguments', e.g. a filename or a branch, 'options', and +'subcommands'. Options are optional arguments that start with a dash and that allow to change the behavior of a command. @@ -48,6 +49,33 @@ The parse-options API allows: option, e.g. `-a -b --option -- --this-is-a-file` indicates that `--this-is-a-file` must not be processed as an option. +Subcommands are special in a couple of ways: + +* Subcommands only have long form, and they have no double dash prefix, no + negated form, and no description, and they don't take any arguments, and + can't be abbreviated. + +* There must be exactly one subcommand among the arguments, or zero if the + command has a default operation mode. + +* All arguments following the subcommand are considered to be arguments of + the subcommand, and, conversely, arguments meant for the subcommand may + not precede the subcommand. + +Therefore, if the options array contains at least one subcommand and +`parse_options()` encounters the first dashless argument, it will either: + +* stop and return, if that dashless argument is a known subcommand, setting + `value` to the function pointer associated with that subcommand, storing + the name of the subcommand in argv[0], and leaving the rest of the + arguments unprocessed, or + +* stop and return, if it was invoked with the `PARSE_OPT_SUBCOMMAND_OPTIONAL` + flag and that dashless argument doesn't match any subcommands, leaving + `value` unchanged and the rest of the arguments unprocessed, or + +* show error and usage, and abort. + Steps to parse options ---------------------- @@ -90,8 +118,8 @@ Flags are the bitwise-or of: Keep the first argument, which contains the program name. It's removed from argv[] by default. -`PARSE_OPT_KEEP_UNKNOWN`:: - Keep unknown arguments instead of erroring out. This doesn't +`PARSE_OPT_KEEP_UNKNOWN_OPT`:: + Keep unknown options instead of erroring out. This doesn't work for all combinations of arguments as users might expect it to do. E.g. if the first argument in `--unknown --known` takes a value (which we can't know), the second one is @@ -101,6 +129,8 @@ Flags are the bitwise-or of: non-option, not as a value belonging to the unknown option, the parser early. That's why parse_options() errors out if both options are set. + Note that non-option arguments are always kept, even without + this flag. `PARSE_OPT_NO_INTERNAL_HELP`:: By default, parse_options() handles `-h`, `--help` and @@ -108,6 +138,13 @@ Flags are the bitwise-or of: turns it off and allows one to add custom handlers for these options, or to just leave them unknown. +`PARSE_OPT_SUBCOMMAND_OPTIONAL`:: + Don't error out when no subcommand is specified. + +Note that `PARSE_OPT_STOP_AT_NON_OPTION` is incompatible with subcommands; +while `PARSE_OPT_KEEP_DASHDASH` and `PARSE_OPT_KEEP_UNKNOWN_OPT` can only be +used with subcommands when combined with `PARSE_OPT_SUBCOMMAND_OPTIONAL`. + Data Structure -------------- @@ -236,10 +273,14 @@ There are some macros to easily define options: `OPT_CMDMODE(short, long, &int_var, description, enum_val)`:: Define an "operation mode" option, only one of which in the same group of "operating mode" options that share the same `int_var` - can be given by the user. `enum_val` is set to `int_var` when the + can be given by the user. `int_var` is set to `enum_val` when the option is used, but an error is reported if other "operating mode" option has already set its value to the same `int_var`. + In new commands consider using subcommands instead. +`OPT_SUBCOMMAND(long, &fn_ptr, subcommand_fn)`:: + Define a subcommand. `subcommand_fn` is put into `fn_ptr` when + this subcommand is used. The last element of the array must be `OPT_END()`. diff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt index d79ad323e6..d44ada98e7 100644 --- a/Documentation/technical/api-simple-ipc.txt +++ b/Documentation/technical/api-simple-ipc.txt @@ -78,7 +78,7 @@ client and an optional response message from the server. Both the client and server messages are unlimited in length and are terminated with a flush packet. -The pkt-line routines (Documentation/technical/protocol-common.txt) +The pkt-line routines (linkgit:gitprotocol-common[5]) are used to simplify buffer management during message generation, transmission, and reception. A flush packet is used to mark the end of the message. This allows the sender to incrementally generate and diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt index 77a150b30e..2afa28bb5a 100644 --- a/Documentation/technical/api-trace2.txt +++ b/Documentation/technical/api-trace2.txt @@ -717,6 +717,7 @@ The "exec_id" field is a command-unique id and is only useful if the { "event":"def_param", ... + "scope":"global", "param":"core.abbrev", "value":"7" } @@ -1207,6 +1208,45 @@ at offset 508. This example also shows that thread names are assigned in a racy manner as each thread starts and allocates TLS storage. +Config (def param) Events:: + + Dump "interesting" config values to trace2 log. ++ +We can optionally emit configuration events, see +`trace2.configparams` in linkgit:git-config[1] for how to enable +it. ++ +---------------- +$ git config --system color.ui never +$ git config --global color.ui always +$ git config --local color.ui auto +$ git config --list --show-scope | grep 'color.ui' +system color.ui=never +global color.ui=always +local color.ui=auto +---------------- ++ +Then, mark the config `color.ui` as "interesting" config with +`GIT_TRACE2_CONFIG_PARAMS`: ++ +---------------- +$ export GIT_TRACE2_PERF_BRIEF=1 +$ export GIT_TRACE2_PERF=~/log.perf +$ export GIT_TRACE2_CONFIG_PARAMS=color.ui +$ git version +... +$ cat ~/log.perf +d0 | main | version | | | | | ... +d0 | main | start | | 0.001642 | | | /usr/local/bin/git version +d0 | main | cmd_name | | | | | version (version) +d0 | main | def_param | | | | scope:system | color.ui:never +d0 | main | def_param | | | | scope:global | color.ui:always +d0 | main | def_param | | | | scope:local | color.ui:auto +d0 | main | data | r0 | 0.002100 | 0.002100 | fsync | fsync/writeout-only:0 +d0 | main | data | r0 | 0.002126 | 0.002126 | fsync | fsync/hardware-flush:0 +d0 | main | exit | | 0.000470 | | | code:0 +d0 | main | atexit | | 0.000477 | | | code:0 +---------------- == Future Work === Relationship to the Existing Trace Api (api-trace.txt) diff --git a/Documentation/technical/bitmap-format.txt b/Documentation/technical/bitmap-format.txt index a85f58f515..c2e652b71a 100644 --- a/Documentation/technical/bitmap-format.txt +++ b/Documentation/technical/bitmap-format.txt @@ -72,6 +72,17 @@ MIDXs, both the bit-cache and rev-cache extensions are required. pack/MIDX. The format and meaning of the name-hash is described below. + ** {empty} + BITMAP_OPT_LOOKUP_TABLE (0x10): ::: + If present, the end of the bitmap file contains a table + containing a list of `N` + triplets. The format and meaning of the table is described + below. ++ +NOTE: Unlike the xor_offset used to compress an individual bitmap, +`xor_row` stores an *absolute* index into the lookup table, not a location +relative to the current entry. + 4-byte entry count (network byte order): :: The total count of entries (bitmapped commits) in this bitmap index. @@ -216,3 +227,31 @@ Note that this hashing scheme is tied to the BITMAP_OPT_HASH_CACHE flag. If implementations want to choose a different hashing scheme, they are free to do so, but MUST allocate a new header flag (because comparing hashes made under two different schemes would be pointless). + +Commit lookup table +------------------- + +If the BITMAP_OPT_LOOKUP_TABLE flag is set, the last `N * (4 + 8 + 4)` +bytes (preceding the name-hash cache and trailing hash) of the `.bitmap` +file contains a lookup table specifying the information needed to get +the desired bitmap from the entries without parsing previous unnecessary +bitmaps. + +For a `.bitmap` containing `nr_entries` reachability bitmaps, the table +contains a list of `nr_entries` triplets +(sorted in the ascending order of `commit_pos`). The content of i'th +triplet is - + + * {empty} + commit_pos (4 byte integer, network byte order): :: + It stores the object position of a commit (in the midx or pack + index). + + * {empty} + offset (8 byte integer, network byte order): :: + The offset from which that commit's bitmap can be read. + + * {empty} + xor_row (4 byte integer, network byte order): :: + The position of the triplet whose bitmap is used to compress + this one, or `0xffffffff` if no such bitmap exists. diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.txt new file mode 100644 index 0000000000..18f2dedd40 --- /dev/null +++ b/Documentation/technical/bundle-uri.txt @@ -0,0 +1,572 @@ +Bundle URIs +=========== + +Git bundles are files that store a pack-file along with some extra metadata, +including a set of refs and a (possibly empty) set of necessary commits. See +linkgit:git-bundle[1] and linkgit:gitformat-bundle[5] for more information. + +Bundle URIs are locations where Git can download one or more bundles in +order to bootstrap the object database in advance of fetching the remaining +objects from a remote. + +One goal is to speed up clones and fetches for users with poor network +connectivity to the origin server. Another benefit is to allow heavy users, +such as CI build farms, to use local resources for the majority of Git data +and thereby reducing the load on the origin server. + +To enable the bundle URI feature, users can specify a bundle URI using +command-line options or the origin server can advertise one or more URIs +via a protocol v2 capability. + +Design Goals +------------ + +The bundle URI standard aims to be flexible enough to satisfy multiple +workloads. The bundle provider and the Git client have several choices in +how they create and consume bundle URIs. + +* Bundles can have whatever name the server desires. This name could refer + to immutable data by using a hash of the bundle contents. However, this + means that a new URI will be needed after every update of the content. + This might be acceptable if the server is advertising the URI (and the + server is aware of new bundles being generated) but would not be + ergonomic for users using the command line option. + +* The bundles could be organized specifically for bootstrapping full + clones, but could also be organized with the intention of bootstrapping + incremental fetches. The bundle provider must decide on one of several + organization schemes to minimize client downloads during incremental + fetches, but the Git client can also choose whether to use bundles for + either of these operations. + +* The bundle provider can choose to support full clones, partial clones, + or both. The client can detect which bundles are appropriate for the + repository's partial clone filter, if any. + +* The bundle provider can use a single bundle (for clones only), or a + list of bundles. When using a list of bundles, the provider can specify + whether or not the client needs _all_ of the bundle URIs for a full + clone, or if _any_ one of the bundle URIs is sufficient. This allows the + bundle provider to use different URIs for different geographies. + +* The bundle provider can organize the bundles using heuristics, such as + creation tokens, to help the client prevent downloading bundles it does + not need. When the bundle provider does not provide these heuristics, + the client can use optimizations to minimize how much of the data is + downloaded. + +* The bundle provider does not need to be associated with the Git server. + The client can choose to use the bundle provider without it being + advertised by the Git server. + +* The client can choose to discover bundle providers that are advertised + by the Git server. This could happen during `git clone`, during + `git fetch`, both, or neither. The user can choose which combination + works best for them. + +* The client can choose to configure a bundle provider manually at any + time. The client can also choose to specify a bundle provider manually + as a command-line option to `git clone`. + +Each repository is different and every Git server has different needs. +Hopefully the bundle URI feature is flexible enough to satisfy all needs. +If not, then the feature can be extended through its versioning mechanism. + +Server requirements +------------------- + +To provide a server-side implementation of bundle servers, no other parts +of the Git protocol are required. This allows server maintainers to use +static content solutions such as CDNs in order to serve the bundle files. + +At the current scope of the bundle URI feature, all URIs are expected to +be HTTP(S) URLs where content is downloaded to a local file using a `GET` +request to that URL. The server could include authentication requirements +to those requests with the aim of triggering the configured credential +helper for secure access. (Future extensions could use "file://" URIs or +SSH URIs.) + +Assuming a `200 OK` response from the server, the content at the URL is +inspected. First, Git attempts to parse the file as a bundle file of +version 2 or higher. If the file is not a bundle, then the file is parsed +as a plain-text file using Git's config parser. The key-value pairs in +that config file are expected to describe a list of bundle URIs. If +neither of these parse attempts succeed, then Git will report an error to +the user that the bundle URI provided erroneous data. + +Any other data provided by the server is considered erroneous. + +Bundle Lists +------------ + +The Git server can advertise bundle URIs using a set of `key=value` pairs. +A bundle URI can also serve a plain-text file in the Git config format +containing these same `key=value` pairs. In both cases, we consider this +to be a _bundle list_. The pairs specify information about the bundles +that the client can use to make decisions for which bundles to download +and which to ignore. + +A few keys focus on properties of the list itself. + +bundle.version:: + (Required) This value provides a version number for the bundle + list. If a future Git change enables a feature that needs the Git + client to react to a new key in the bundle list file, then this version + will increment. The only current version number is 1, and if any other + value is specified then Git will fail to use this file. + +bundle.mode:: + (Required) This value has one of two values: `all` and `any`. When `all` + is specified, then the client should expect to need all of the listed + bundle URIs that match their repository's requirements. When `any` is + specified, then the client should expect that any one of the bundle URIs + that match their repository's requirements will suffice. Typically, the + `any` option is used to list a number of different bundle servers + located in different geographies. + +bundle.heuristic:: + If this string-valued key exists, then the bundle list is designed to + work well with incremental `git fetch` commands. The heuristic signals + that there are additional keys available for each bundle that help + determine which subset of bundles the client should download. The only + heuristic currently planned is `creationToken`. + +The remaining keys include an `` segment which is a server-designated +name for each available bundle. The `` must contain only alphanumeric +and `-` characters. + +bundle..uri:: + (Required) This string value is the URI for downloading bundle ``. + If the URI begins with a protocol (`http://` or `https://`) then the URI + is absolute. Otherwise, the URI is interpreted as relative to the URI + used for the bundle list. If the URI begins with `/`, then that relative + path is relative to the domain name used for the bundle list. (This use + of relative paths is intended to make it easier to distribute a set of + bundles across a large number of servers or CDNs with different domain + names.) + +bundle..filter:: + This string value represents an object filter that should also appear in + the header of this bundle. The server uses this value to differentiate + different kinds of bundles from which the client can choose those that + match their object filters. + +bundle..creationToken:: + This value is a nonnegative 64-bit integer used for sorting the bundles + the list. This is used to download a subset of bundles during a fetch + when `bundle.heuristic=creationToken`. + +bundle..location:: + This string value advertises a real-world location from where the bundle + URI is served. This can be used to present the user with an option for + which bundle URI to use or simply as an informative indicator of which + bundle URI was selected by Git. This is only valuable when + `bundle.mode` is `any`. + +Here is an example bundle list using the Git config format: + + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "2022-02-09-1644442601-daily"] + uri = https://bundles.example.com/git/git/2022-02-09-1644442601-daily.bundle + creationToken = 1644442601 + + [bundle "2022-02-02-1643842562"] + uri = https://bundles.example.com/git/git/2022-02-02-1643842562.bundle + creationToken = 1643842562 + + [bundle "2022-02-09-1644442631-daily-blobless"] + uri = 2022-02-09-1644442631-daily-blobless.bundle + creationToken = 1644442631 + filter = blob:none + + [bundle "2022-02-02-1643842568-blobless"] + uri = /git/git/2022-02-02-1643842568-blobless.bundle + creationToken = 1643842568 + filter = blob:none + +This example uses `bundle.mode=all` as well as the +`bundle..creationToken` heuristic. It also uses the `bundle..filter` +options to present two parallel sets of bundles: one for full clones and +another for blobless partial clones. + +Suppose that this bundle list was found at the URI +`https://bundles.example.com/git/git/` and so the two blobless bundles have +the following fully-expanded URIs: + +* `https://bundles.example.com/git/git/2022-02-09-1644442631-daily-blobless.bundle` +* `https://bundles.example.com/git/git/2022-02-02-1643842568-blobless.bundle` + +Advertising Bundle URIs +----------------------- + +If a user knows a bundle URI for the repository they are cloning, then +they can specify that URI manually through a command-line option. However, +a Git host may want to advertise bundle URIs during the clone operation, +helping users unaware of the feature. + +The only thing required for this feature is that the server can advertise +one or more bundle URIs. This advertisement takes the form of a new +protocol v2 capability specifically for discovering bundle URIs. + +The client could choose an arbitrary bundle URI as an option _or_ select +the URI with best performance by some exploratory checks. It is up to the +bundle provider to decide if having multiple URIs is preferable to a +single URI that is geodistributed through server-side infrastructure. + +Cloning with Bundle URIs +------------------------ + +The primary need for bundle URIs is to speed up clones. The Git client +will interact with bundle URIs according to the following flow: + +1. The user specifies a bundle URI with the `--bundle-uri` command-line + option _or_ the client discovers a bundle list advertised by the + Git server. + +2. If the downloaded data from a bundle URI is a bundle, then the client + inspects the bundle headers to check that the prerequisite commit OIDs + are present in the client repository. If some are missing, then the + client delays unbundling until other bundles have been unbundled, + making those OIDs present. When all required OIDs are present, the + client unbundles that data using a refspec. The default refspec is + `+refs/heads/*:refs/bundles/*`, but this can be configured. These refs + are stored so that later `git fetch` negotiations can communicate the + bundled refs as `have`s, reducing the size of the fetch over the Git + protocol. To allow pruning refs from this ref namespace, Git may + introduce a numbered namespace (such as `refs/bundles//*`) such that + stale bundle refs can be deleted. + +3. If the file is instead a bundle list, then the client inspects the + `bundle.mode` to see if the list is of the `all` or `any` form. + + a. If `bundle.mode=all`, then the client considers all bundle + URIs. The list is reduced based on the `bundle..filter` options + matching the client repository's partial clone filter. Then, all + bundle URIs are requested. If the `bundle..creationToken` + heuristic is provided, then the bundles are downloaded in decreasing + order by the creation token, stopping when a bundle has all required + OIDs. The bundles can then be unbundled in increasing creation token + order. The client stores the latest creation token as a heuristic + for avoiding future downloads if the bundle list does not advertise + bundles with larger creation tokens. + + b. If `bundle.mode=any`, then the client can choose any one of the + bundle URIs to inspect. The client can use a variety of ways to + choose among these URIs. The client can also fallback to another URI + if the initial choice fails to return a result. + +Note that during a clone we expect that all bundles will be required, and +heuristics such as `bundle..creationToken` can be used to download +bundles in chronological order or in parallel. + +If a given bundle URI is a bundle list with a `bundle.heuristic` +value, then the client can choose to store that URI as its chosen bundle +URI. The client can then navigate directly to that URI during later `git +fetch` calls. + +When downloading bundle URIs, the client can choose to inspect the initial +content before committing to downloading the entire content. This may +provide enough information to determine if the URI is a bundle list or +a bundle. In the case of a bundle, the client may inspect the bundle +header to determine that all advertised tips are already in the client +repository and cancel the remaining download. + +Fetching with Bundle URIs +------------------------- + +When the client fetches new data, it can decide to fetch from bundle +servers before fetching from the origin remote. This could be done via a +command-line option, but it is more likely useful to use a config value +such as the one specified during the clone. + +The fetch operation follows the same procedure to download bundles from a +bundle list (although we do _not_ want to use parallel downloads here). We +expect that the process will end when all prerequisite commit OIDs in a +thin bundle are already in the object database. + +When using the `creationToken` heuristic, the client can avoid downloading +any bundles if their creation tokens are not larger than the stored +creation token. After fetching new bundles, Git updates this local +creation token. + +If the bundle provider does not provide a heuristic, then the client +should attempt to inspect the bundle headers before downloading the full +bundle data in case the bundle tips already exist in the client +repository. + +Error Conditions +---------------- + +If the Git client discovers something unexpected while downloading +information according to a bundle URI or the bundle list found at that +location, then Git can ignore that data and continue as if it was not +given a bundle URI. The remote Git server is the ultimate source of truth, +not the bundle URI. + +Here are a few example error conditions: + +* The client fails to connect with a server at the given URI or a connection + is lost without any chance to recover. + +* The client receives a 400-level response (such as `404 Not Found` or + `401 Not Authorized`). The client should use the credential helper to + find and provide a credential for the URI, but match the semantics of + Git's other HTTP protocols in terms of handling specific 400-level + errors. + +* The server reports any other failure response. + +* The client receives data that is not parsable as a bundle or bundle list. + +* A bundle includes a filter that does not match expectations. + +* The client cannot unbundle the bundles because the prerequisite commit OIDs + are not in the object database and there are no more bundles to download. + +There are also situations that could be seen as wasteful, but are not +error conditions: + +* The downloaded bundles contain more information than is requested by + the clone or fetch request. A primary example is if the user requests + a clone with `--single-branch` but downloads bundles that store every + reachable commit from all `refs/heads/*` references. This might be + initially wasteful, but perhaps these objects will become reachable by + a later ref update that the client cares about. + +* A bundle download during a `git fetch` contains objects already in the + object database. This is probably unavoidable if we are using bundles + for fetches, since the client will almost always be slightly ahead of + the bundle servers after performing its "catch-up" fetch to the remote + server. This extra work is most wasteful when the client is fetching + much more frequently than the server is computing bundles, such as if + the client is using hourly prefetches with background maintenance, but + the server is computing bundles weekly. For this reason, the client + should not use bundle URIs for fetch unless the server has explicitly + recommended it through a `bundle.heuristic` value. + +Example Bundle Provider organization +------------------------------------ + +The bundle URI feature is intentionally designed to be flexible to +different ways a bundle provider wants to organize the object data. +However, it can be helpful to have a complete organization model described +here so providers can start from that base. + +This example organization is a simplified model of what is used by the +GVFS Cache Servers (see section near the end of this document) which have +been beneficial in speeding up clones and fetches for very large +repositories, although using extra software outside of Git. + +The bundle provider deploys servers across multiple geographies. Each +server manages its own bundle set. The server can track a number of Git +repositories, but provides a bundle list for each based on a pattern. For +example, when mirroring a repository at `https:////` +the bundle server could have its bundle list available at +`https://///`. The origin Git server can +list all of these servers under the "any" mode: + + [bundle] + version = 1 + mode = any + + [bundle "eastus"] + uri = https://eastus.example.com/// + + [bundle "europe"] + uri = https://europe.example.com/// + + [bundle "apac"] + uri = https://apac.example.com/// + +This "list of lists" is static and only changes if a bundle server is +added or removed. + +Each bundle server manages its own set of bundles. The initial bundle list +contains only a single bundle, containing all of the objects received from +cloning the repository from the origin server. The list uses the +`creationToken` heuristic and a `creationToken` is made for the bundle +based on the server's timestamp. + +The bundle server runs regularly-scheduled updates for the bundle list, +such as once a day. During this task, the server fetches the latest +contents from the origin server and generates a bundle containing the +objects reachable from the latest origin refs, but not contained in a +previously-computed bundle. This bundle is added to the list, with care +that the `creationToken` is strictly greater than the previous maximum +`creationToken`. + +When the bundle list grows too large, say more than 30 bundles, then the +oldest "_N_ minus 30" bundles are combined into a single bundle. This +bundle's `creationToken` is equal to the maximum `creationToken` among the +merged bundles. + +An example bundle list is provided here, although it only has two daily +bundles and not a full list of 30: + + [bundle] + version = 1 + mode = all + heuristic = creationToken + + [bundle "2022-02-13-1644770820-daily"] + uri = https://eastus.example.com////2022-02-09-1644770820-daily.bundle + creationToken = 1644770820 + + [bundle "2022-02-09-1644442601-daily"] + uri = https://eastus.example.com////2022-02-09-1644442601-daily.bundle + creationToken = 1644442601 + + [bundle "2022-02-02-1643842562"] + uri = https://eastus.example.com////2022-02-02-1643842562.bundle + creationToken = 1643842562 + +To avoid storing and serving object data in perpetuity despite becoming +unreachable in the origin server, this bundle merge can be more careful. +Instead of taking an absolute union of the old bundles, instead the bundle +can be created by looking at the newer bundles and ensuring that their +necessary commits are all available in this merged bundle (or in another +one of the newer bundles). This allows "expiring" object data that is not +being used by new commits in this window of time. That data could be +reintroduced by a later push. + +The intention of this data organization has two main goals. First, initial +clones of the repository become faster by downloading precomputed object +data from a closer source. Second, `git fetch` commands can be faster, +especially if the client has not fetched for a few days. However, if a +client does not fetch for 30 days, then the bundle list organization would +cause redownloading a large amount of object data. + +One way to make this organization more useful to users who fetch frequently +is to have more frequent bundle creation. For example, bundles could be +created every hour, and then once a day those "hourly" bundles could be +merged into a "daily" bundle. The daily bundles are merged into the +oldest bundle after 30 days. + +It is recommended that this bundle strategy is repeated with the `blob:none` +filter if clients of this repository are expecting to use blobless partial +clones. This list of blobless bundles stays in the same list as the full +bundles, but uses the `bundle..filter` key to separate the two groups. +For very large repositories, the bundle provider may want to _only_ provide +blobless bundles. + +Implementation Plan +------------------- + +This design document is being submitted on its own as an aspirational +document, with the goal of implementing all of the mentioned client +features over the course of several patch series. Here is a potential +outline for submitting these features: + +1. Integrate bundle URIs into `git clone` with a `--bundle-uri` option. + This will include a new `git fetch --bundle-uri` mode for use as the + implementation underneath `git clone`. The initial version here will + expect a single bundle at the given URI. + +2. Implement the ability to parse a bundle list from a bundle URI and + update the `git fetch --bundle-uri` logic to properly distinguish + between `bundle.mode` options. Specifically design the feature so + that the config format parsing feeds a list of key-value pairs into the + bundle list logic. + +3. Create the `bundle-uri` protocol v2 command so Git servers can advertise + bundle URIs using the key-value pairs. Plug into the existing key-value + input to the bundle list logic. Allow `git clone` to discover these + bundle URIs and bootstrap the client repository from the bundle data. + (This choice is an opt-in via a config option and a command-line + option.) + +4. Allow the client to understand the `bundle.flag=forFetch` configuration + and the `bundle..creationToken` heuristic. When `git clone` + discovers a bundle URI with `bundle.flag=forFetch`, it configures the + client repository to check that bundle URI during later `git fetch ` + commands. + +5. Allow clients to discover bundle URIs during `git fetch` and configure + a bundle URI for later fetches if `bundle.flag=forFetch`. + +6. Implement the "inspect headers" heuristic to reduce data downloads when + the `bundle..creationToken` heuristic is not available. + +As these features are reviewed, this plan might be updated. We also expect +that new designs will be discovered and implemented as this feature +matures and becomes used in real-world scenarios. + +Related Work: Packfile URIs +--------------------------- + +The Git protocol already has a capability where the Git server can list +a set of URLs along with the packfile response when serving a client +request. The client is then expected to download the packfiles at those +locations in order to have a complete understanding of the response. + +This mechanism is used by the Gerrit server (implemented with JGit) and +has been effective at reducing CPU load and improving user performance for +clones. + +A major downside to this mechanism is that the origin server needs to know +_exactly_ what is in those packfiles, and the packfiles need to be available +to the user for some time after the server has responded. This coupling +between the origin and the packfile data is difficult to manage. + +Further, this implementation is extremely hard to make work with fetches. + +Related Work: GVFS Cache Servers +-------------------------------- + +The GVFS Protocol [2] is a set of HTTP endpoints designed independently of +the Git project before Git's partial clone was created. One feature of this +protocol is the idea of a "cache server" which can be colocated with build +machines or developer offices to transfer Git data without overloading the +central server. + +The endpoint that VFS for Git is famous for is the `GET /gvfs/objects/{oid}` +endpoint, which allows downloading an object on-demand. This is a critical +piece of the filesystem virtualization of that product. + +However, a more subtle need is the `GET /gvfs/prefetch?lastPackTimestamp=` +endpoint. Given an optional timestamp, the cache server responds with a list +of precomputed packfiles containing the commits and trees that were introduced +in those time intervals. + +The cache server computes these "prefetch" packfiles using the following +strategy: + +1. Every hour, an "hourly" pack is generated with a given timestamp. +2. Nightly, the previous 24 hourly packs are rolled up into a "daily" pack. +3. Nightly, all prefetch packs more than 30 days old are rolled up into + one pack. + +When a user runs `gvfs clone` or `scalar clone` against a repo with cache +servers, the client requests all prefetch packfiles, which is at most +`24 + 30 + 1` packfiles downloading only commits and trees. The client +then follows with a request to the origin server for the references, and +attempts to checkout that tip reference. (There is an extra endpoint that +helps get all reachable trees from a given commit, in case that commit +was not already in a prefetch packfile.) + +During a `git fetch`, a hook requests the prefetch endpoint using the +most-recent timestamp from a previously-downloaded prefetch packfile. +Only the list of packfiles with later timestamps are downloaded. Most +users fetch hourly, so they get at most one hourly prefetch pack. Users +whose machines have been off or otherwise have not fetched in over 30 days +might redownload all prefetch packfiles. This is rare. + +It is important to note that the clients always contact the origin server +for the refs advertisement, so the refs are frequently "ahead" of the +prefetched pack data. The missing objects are downloaded on-demand using +the `GET gvfs/objects/{oid}` requests, when needed by a command such as +`git checkout` or `git log`. Some Git optimizations disable checks that +would cause these on-demand downloads to be too aggressive. + +See Also +-------- + +[1] https://lore.kernel.org/git/RFC-cover-00.13-0000000000-20210805T150534Z-avarab@gmail.com/ + An earlier RFC for a bundle URI feature. + +[2] https://github.com/microsoft/VFSForGit/blob/master/Protocol.md + The GVFS Protocol diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.txt index f05e7bda1a..90c9760c23 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.txt @@ -40,7 +40,7 @@ Values 1-4 satisfy the requirements of parse_commit_gently(). There are two definitions of generation number: 1. Corrected committer dates (generation number v2) -2. Topological levels (generation nummber v1) +2. Topological levels (generation number v1) Define "corrected committer date" of a commit recursively as follows: @@ -48,7 +48,7 @@ Define "corrected committer date" of a commit recursively as follows: equal to its committer date. * A commit with at least one parent has corrected committer date equal to - the maximum of its commiter date and one more than the largest corrected + the maximum of its committer date and one more than the largest corrected committer date among its parents. * As a special case, a root commit with timestamp zero has corrected commit diff --git a/Documentation/technical/cruft-packs.txt b/Documentation/technical/cruft-packs.txt deleted file mode 100644 index d81f3a8982..0000000000 --- a/Documentation/technical/cruft-packs.txt +++ /dev/null @@ -1,123 +0,0 @@ -= Cruft packs - -The cruft packs feature offer an alternative to Git's traditional mechanism of -removing unreachable objects. This document provides an overview of Git's -pruning mechanism, and how a cruft pack can be used instead to accomplish the -same. - -== Background - -To remove unreachable objects from your repository, Git offers `git repack -Ad` -(see linkgit:git-repack[1]). Quoting from the documentation: - -[quote] -[...] unreachable objects in a previous pack become loose, unpacked objects, -instead of being left in the old pack. [...] loose unreachable objects will be -pruned according to normal expiry rules with the next 'git gc' invocation. - -Unreachable objects aren't removed immediately, since doing so could race with -an incoming push which may reference an object which is about to be deleted. -Instead, those unreachable objects are stored as loose objects and stay that way -until they are older than the expiration window, at which point they are removed -by linkgit:git-prune[1]. - -Git must store these unreachable objects loose in order to keep track of their -per-object mtimes. If these unreachable objects were written into one big pack, -then either freshening that pack (because an object contained within it was -re-written) or creating a new pack of unreachable objects would cause the pack's -mtime to get updated, and the objects within it would never leave the expiration -window. Instead, objects are stored loose in order to keep track of the -individual object mtimes and avoid a situation where all cruft objects are -freshened at once. - -This can lead to undesirable situations when a repository contains many -unreachable objects which have not yet left the grace period. Having large -directories in the shards of `.git/objects` can lead to decreased performance in -the repository. But given enough unreachable objects, this can lead to inode -starvation and degrade the performance of the whole system. Since we -can never pack those objects, these repositories often take up a large amount of -disk space, since we can only zlib compress them, but not store them in delta -chains. - -== Cruft packs - -A cruft pack eliminates the need for storing unreachable objects in a loose -state by including the per-object mtimes in a separate file alongside a single -pack containing all loose objects. - -A cruft pack is written by `git repack --cruft` when generating a new pack. -linkgit:git-pack-objects[1]'s `--cruft` option. Note that `git repack --cruft` -is a classic all-into-one repack, meaning that everything in the resulting pack is -reachable, and everything else is unreachable. Once written, the `--cruft` -option instructs `git repack` to generate another pack containing only objects -not packed in the previous step (which equates to packing all unreachable -objects together). This progresses as follows: - - 1. Enumerate every object, marking any object which is (a) not contained in a - kept-pack, and (b) whose mtime is within the grace period as a traversal - tip. - - 2. Perform a reachability traversal based on the tips gathered in the previous - step, adding every object along the way to the pack. - - 3. Write the pack out, along with a `.mtimes` file that records the per-object - timestamps. - -This mode is invoked internally by linkgit:git-repack[1] when instructed to -write a cruft pack. Crucially, the set of in-core kept packs is exactly the set -of packs which will not be deleted by the repack; in other words, they contain -all of the repository's reachable objects. - -When a repository already has a cruft pack, `git repack --cruft` typically only -adds objects to it. An exception to this is when `git repack` is given the -`--cruft-expiration` option, which allows the generated cruft pack to omit -expired objects instead of waiting for linkgit:git-gc[1] to expire those objects -later on. - -It is linkgit:git-gc[1] that is typically responsible for removing expired -unreachable objects. - -== Caution for mixed-version environments - -Repositories that have cruft packs in them will continue to work with any older -version of Git. Note, however, that previous versions of Git which do not -understand the `.mtimes` file will use the cruft pack's mtime as the mtime for -all of the objects in it. In other words, do not expect older (pre-cruft pack) -versions of Git to interpret or even read the contents of the `.mtimes` file. - -Note that having mixed versions of Git GC-ing the same repository can lead to -unreachable objects never being completely pruned. This can happen under the -following circumstances: - - - An older version of Git running GC explodes the contents of an existing - cruft pack loose, using the cruft pack's mtime. - - A newer version running GC collects those loose objects into a cruft pack, - where the .mtime file reflects the loose object's actual mtimes, but the - cruft pack mtime is "now". - -Repeating this process will lead to unreachable objects not getting pruned as a -result of repeatedly resetting the objects' mtimes to the present time. - -If you are GC-ing repositories in a mixed version environment, consider omitting -the `--cruft` option when using linkgit:git-repack[1] and linkgit:git-gc[1], and -leaving the `gc.cruftPacks` configuration unset until all writers understand -cruft packs. - -== Alternatives - -Notable alternatives to this design include: - - - The location of the per-object mtime data, and - - Storing unreachable objects in multiple cruft packs. - -On the location of mtime data, a new auxiliary file tied to the pack was chosen -to avoid complicating the `.idx` format. If the `.idx` format were ever to gain -support for optional chunks of data, it may make sense to consolidate the -`.mtimes` format into the `.idx` itself. - -Storing unreachable objects among multiple cruft packs (e.g., creating a new -cruft pack during each repacking operation including only unreachable objects -which aren't already stored in an earlier cruft pack) is significantly more -complicated to construct, and so aren't pursued here. The obvious drawback to -the current implementation is that the entire cruft pack must be re-written from -scratch. diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt index 260224b033..e2ac36dd21 100644 --- a/Documentation/technical/hash-function-transition.txt +++ b/Documentation/technical/hash-function-transition.txt @@ -205,7 +205,7 @@ SHA-1 content. Object storage ~~~~~~~~~~~~~~ Loose objects use zlib compression and packed objects use the packed -format described in Documentation/technical/pack-format.txt, just like +format described in linkgit:gitformat-pack[5], just like today. The content that is compressed and stored uses SHA-256 content instead of SHA-1 content. diff --git a/Documentation/technical/long-running-process-protocol.txt b/Documentation/technical/long-running-process-protocol.txt index aa0aa9af1c..6f33654b42 100644 --- a/Documentation/technical/long-running-process-protocol.txt +++ b/Documentation/technical/long-running-process-protocol.txt @@ -3,7 +3,7 @@ Long-running process protocol This protocol is used when Git needs to communicate with an external process throughout the entire life of a single Git command. All -communication is in pkt-line format (see technical/protocol-common.txt) +communication is in pkt-line format (see linkgit:gitprotocol-common[5]) over standard input and standard output. Handshake diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt index 1eb525fe76..9d453d4765 100644 --- a/Documentation/technical/packfile-uri.txt +++ b/Documentation/technical/packfile-uri.txt @@ -18,7 +18,7 @@ a `packfile-uris` argument, the server MAY send a `packfile-uris` section directly before the `packfile` section (right after `wanted-refs` if it is sent) containing URIs of any of the given protocols. The URIs point to packfiles that use only features that the client has declared that it supports -(e.g. ofs-delta and thin-pack). See protocol-v2.txt for the documentation of +(e.g. ofs-delta and thin-pack). See linkgit:gitprotocol-v2[5] for the documentation of this section. Clients should then download and index all the given URIs (in addition to diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt index 99f0eb3040..92fcee2bff 100644 --- a/Documentation/technical/partial-clone.txt +++ b/Documentation/technical/partial-clone.txt @@ -79,7 +79,7 @@ Design Details upload-pack negotiation. + This uses the existing capability discovery mechanism. -See "filter" in Documentation/technical/pack-protocol.txt. +See "filter" in linkgit:gitprotocol-pack[5]. - Clients pass a "filter-spec" to clone and fetch which is passed to the server to request filtering during packfile construction. diff --git a/Documentation/technical/remembering-renames.txt b/Documentation/technical/remembering-renames.txt index 2fd5cc88e0..1e34d91390 100644 --- a/Documentation/technical/remembering-renames.txt +++ b/Documentation/technical/remembering-renames.txt @@ -20,7 +20,7 @@ Outline: 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also a rename on MERGE_SIDE1 for the next pick - 4. A detailed description of the the counter-examples to #3. + 4. A detailed description of the counter-examples to #3. 5. Why the special cases in #4 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why @@ -407,7 +407,7 @@ considered to be "irrelevant". See for example the following commits: no longer relevant", 2021-03-13) Relevance is always determined by what the _other_ side of history has -done, in terms of modifing a file that our side renamed, or adding a +done, in terms of modifying a file that our side renamed, or adding a file to a directory which our side renamed. This means that a path that is "irrelevant" when picking the first commit of a series in a rebase or cherry-pick, may suddenly become "relevant" when picking the diff --git a/Documentation/technical/scalar.txt b/Documentation/technical/scalar.txt new file mode 100644 index 0000000000..921cb104c3 --- /dev/null +++ b/Documentation/technical/scalar.txt @@ -0,0 +1,66 @@ +Scalar +====== + +Scalar is a repository management tool that optimizes Git for use in large +repositories. It accomplishes this by helping users to take advantage of +advanced performance features in Git. Unlike most other Git built-in commands, +Scalar is not executed as a subcommand of 'git'; rather, it is built as a +separate executable containing its own series of subcommands. + +Background +---------- + +Scalar was originally designed as an add-on to Git and implemented as a .NET +Core application. It was created based on the learnings from the VFS for Git +project (another application aimed at improving the experience of working with +large repositories). As part of its initial implementation, Scalar relied on +custom features in the Microsoft fork of Git that have since been integrated +into core Git: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +With the requisite Git functionality in place and a desire to bring the benefits +of Scalar to the larger Git community, the Scalar application itself was ported +from C# to C and integrated upstream. + +Features +-------- + +Scalar is comprised of two major pieces of functionality: automatically +configuring built-in Git performance features and managing repository +enlistments. + +The Git performance features configured by Scalar (see "Background" for +examples) confer substantial performance benefits to large repositories, but are +either too experimental to enable for all of Git yet, or only benefit large +repositories. As new features are introduced, Scalar should be updated +accordingly to incorporate them. This will prevent the tool from becoming stale +while also providing a path for more easily bringing features to the appropriate +users. + +Enlistments are how Scalar knows which repositories on a user's system should +utilize Scalar-configured features. This allows it to update performance +settings when new ones are added to the tool, as well as centrally manage +repository maintenance. The enlistment structure - a root directory with a +`src/` subdirectory containing the cloned repository itself - is designed to +encourage users to route build outputs outside of the repository to avoid the +performance-limiting overhead of ignoring those files in Git. + +Design +------ + +Scalar is implemented in C and interacts with Git via a mix of child process +invocations of Git and direct usage of `libgit.a`. Internally, it is structured +much like other built-ins with subcommands (e.g., `git stash`), containing a +`cmd_()` function for each subcommand, routed through a `cmd_main()` +function. Most options are unique to each subcommand, with `scalar` respecting +some "global" `git` options (e.g., `-c` and `-C`). + +Because `scalar` is not invoked as a Git subcommand (like `git scalar`), it is +built and installed as its own executable in the `bin/` directory, alongside +`git`, `git-gui`, etc. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 865074bed4..dc9c6a663a 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -3133,7 +3133,7 @@ those "loose" objects. You can save space and make Git faster by moving these loose objects in to a "pack file", which stores a group of objects in an efficient compressed format; the details of how pack files are formatted can be -found in link:technical/pack-format.html[pack format]. +found in linkgit:gitformat-pack[5]. To put the loose objects into a pack, just run git repack: diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index fb16249620..e3eaeb4926 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.37.3 +DEF_VER=v2.38.GIT LF=' ' diff --git a/INSTALL b/INSTALL index 4140a3f5c8..89b15d71df 100644 --- a/INSTALL +++ b/INSTALL @@ -135,8 +135,7 @@ Issues of note: By default, git uses OpenSSL for SHA1 but it will use its own library (inspired by Mozilla's) with either NO_OPENSSL or - BLK_SHA1. Also included is a version optimized for PowerPC - (PPC_SHA1). + BLK_SHA1. - "libcurl" library is used for fetching and pushing repositories over http:// or https://, as well as by diff --git a/Makefile b/Makefile index 17fdb16dd8..6bfb62cbe9 100644 --- a/Makefile +++ b/Makefile @@ -155,9 +155,6 @@ include shared.mak # Define BLK_SHA1 environment variable to make use of the bundled # optimized C SHA1 routine. # -# Define PPC_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for PowerPC. -# # Define DC_SHA1 to unconditionally enable the collision-detecting sha1 # algorithm. This is slower, but may detect attempted collision attacks. # Takes priority over other *_SHA1 knobs. @@ -182,6 +179,8 @@ include shared.mak # # Define BLK_SHA256 to use the built-in SHA-256 routines. # +# Define NETTLE_SHA256 to use the SHA-256 routines in libnettle. +# # Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt. # # Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL. @@ -309,6 +308,11 @@ include shared.mak # distributions that want to use their packaged versions of Perl # modules, instead of the fallbacks shipped with Git. # +# Define NO_GITWEB if you do not want to build or install +# 'gitweb'. Note that defining NO_PERL currently has the same effect +# on not installing gitweb, but not on whether it's built in the +# gitweb/ directory. +# # Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python # but /usr/bin/python2.7 or /usr/bin/python3 on some platforms). # @@ -544,6 +548,7 @@ gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb +gitwebstaticdir = $(gitwebdir)/static perllibdir = $(sharedir)/perl5 localedir = $(sharedir)/locale template_dir = share/git-core/templates @@ -562,7 +567,7 @@ localedir_relative = $(patsubst $(prefix)/%,%,$(localedir)) htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir)) perllibdir_relative = $(patsubst $(prefix)/%,%,$(perllibdir)) -export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir +export prefix bindir sharedir sysconfdir perllibdir localedir # Set our default programs CC = cc @@ -600,7 +605,9 @@ FUZZ_OBJS = FUZZ_PROGRAMS = GIT_OBJS = LIB_OBJS = +SCALAR_OBJS = OBJECTS = +OTHER_PROGRAMS = PROGRAM_OBJS = PROGRAMS = EXCLUDED_PROGRAMS = @@ -681,9 +688,9 @@ SCRIPTS = $(SCRIPT_SH_GEN) \ ETAGS_TARGET = TAGS -FUZZ_OBJS += fuzz-commit-graph.o -FUZZ_OBJS += fuzz-pack-headers.o -FUZZ_OBJS += fuzz-pack-idx.o +FUZZ_OBJS += oss-fuzz/fuzz-commit-graph.o +FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o +FUZZ_OBJS += oss-fuzz/fuzz-pack-idx.o .PHONY: fuzz-objs fuzz-objs: $(FUZZ_OBJS) @@ -764,6 +771,7 @@ TEST_BUILTINS_OBJS += test-read-midx.o TEST_BUILTINS_OBJS += test-ref-store.o TEST_BUILTINS_OBJS += test-reftable.o TEST_BUILTINS_OBJS += test-regex.o +TEST_BUILTINS_OBJS += test-rot13-filter.o TEST_BUILTINS_OBJS += test-repository.o TEST_BUILTINS_OBJS += test-revision-walking.o TEST_BUILTINS_OBJS += test-run-command.o @@ -777,6 +785,7 @@ 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 +TEST_BUILTINS_OBJS += test-submodule.o TEST_BUILTINS_OBJS += test-subprocess.o TEST_BUILTINS_OBJS += test-trace2.o TEST_BUILTINS_OBJS += test-urlmatch-normalization.o @@ -809,13 +818,16 @@ BUILT_INS += git-show$X BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-switch$X +BUILT_INS += git-version$X BUILT_INS += git-whatchanged$X # what 'all' will build but not install in gitexecdir -OTHER_PROGRAMS = git$X +OTHER_PROGRAMS += git$X +OTHER_PROGRAMS += scalar$X # what test wrappers are needed and 'install' will install, in bindir BINDIR_PROGRAMS_NEED_X += git +BINDIR_PROGRAMS_NEED_X += scalar BINDIR_PROGRAMS_NEED_X += git-receive-pack BINDIR_PROGRAMS_NEED_X += git-shell BINDIR_PROGRAMS_NEED_X += git-upload-archive @@ -898,6 +910,7 @@ LIB_OBJS += blob.o LIB_OBJS += bloom.o LIB_OBJS += branch.o LIB_OBJS += bulk-checkin.o +LIB_OBJS += bundle-uri.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o LIB_OBJS += cbtree.o @@ -925,6 +938,7 @@ LIB_OBJS += ctype.o LIB_OBJS += date.o LIB_OBJS += decorate.o LIB_OBJS += delta-islands.o +LIB_OBJS += diagnose.o LIB_OBJS += diff-delta.o LIB_OBJS += diff-merges.o LIB_OBJS += diff-lib.o @@ -985,7 +999,6 @@ LIB_OBJS += merge-ort.o LIB_OBJS += merge-ort-wrappers.o LIB_OBJS += merge-recursive.o LIB_OBJS += merge.o -LIB_OBJS += mergesort.o LIB_OBJS += midx.o LIB_OBJS += name-hash.o LIB_OBJS += negotiator/default.o @@ -1146,6 +1159,7 @@ BUILTIN_OBJS += builtin/credential-cache.o BUILTIN_OBJS += builtin/credential-store.o BUILTIN_OBJS += builtin/credential.o BUILTIN_OBJS += builtin/describe.o +BUILTIN_OBJS += builtin/diagnose.o BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o @@ -1287,7 +1301,7 @@ SANITIZE_ADDRESS = # For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will # usually result in less CPU usage at the cost of higher peak memory. # Setting it to 0 will feed all files in a single spatch invocation. -SPATCH_FLAGS = --all-includes --patch . +SPATCH_FLAGS = --all-includes SPATCH_BATCH_SIZE = 1 include config.mak.uname @@ -1428,7 +1442,6 @@ ifeq ($(uname_S),Darwin) APPLE_COMMON_CRYPTO = YesPlease COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO endif - NO_REGEX = YesPlease PTHREAD_LIBS = endif @@ -1792,6 +1805,10 @@ ifdef APPLE_COMMON_CRYPTO SHA1_MAX_BLOCK_SIZE = 1024L*1024L*1024L endif +ifdef PPC_SHA1 +$(error the PPC_SHA1 flag has been removed along with the PowerPC-specific SHA-1 implementation.) +endif + ifdef OPENSSL_SHA1 EXTLIBS += $(LIB_4_CRYPTO) BASIC_CFLAGS += -DSHA1_OPENSSL @@ -1800,10 +1817,6 @@ ifdef BLK_SHA1 LIB_OBJS += block-sha1/sha1.o BASIC_CFLAGS += -DSHA1_BLK else -ifdef PPC_SHA1 - LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o - BASIC_CFLAGS += -DSHA1_PPC -else ifdef APPLE_COMMON_CRYPTO COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL BASIC_CFLAGS += -DSHA1_APPLE @@ -1837,12 +1850,15 @@ endif endif endif endif -endif ifdef OPENSSL_SHA256 EXTLIBS += $(LIB_4_CRYPTO) BASIC_CFLAGS += -DSHA256_OPENSSL else +ifdef NETTLE_SHA256 + BASIC_CFLAGS += -DSHA256_NETTLE + EXTLIBS += -lnettle +else ifdef GCRYPT_SHA256 BASIC_CFLAGS += -DSHA256_GCRYPT EXTLIBS += -lgcrypt @@ -1851,6 +1867,7 @@ else BASIC_CFLAGS += -DSHA256_BLK endif endif +endif ifdef SHA1_MAX_BLOCK_SIZE LIB_OBJS += compat/sha1-chunked.o @@ -2090,6 +2107,7 @@ htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative)) prefix_SQ = $(subst ','\'',$(prefix)) perllibdir_relative_SQ = $(subst ','\'',$(perllibdir_relative)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) +gitwebstaticdir_SQ = $(subst ','\'',$(gitwebstaticdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) @@ -2206,7 +2224,7 @@ profile-fast: profile-clean all:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) - $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) + $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) $(OTHER_PROGRAMS))), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif all:: @@ -2418,10 +2436,6 @@ GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile perllibdir: @echo '$(perllibdir_SQ)' -.PHONY: gitweb -gitweb: - $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all - git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES $(QUIET_GEN)$(cmd_munge_script) && \ chmod +x $@+ && \ @@ -2533,7 +2547,12 @@ GIT_OBJS += git.o .PHONY: git-objs git-objs: $(GIT_OBJS) +SCALAR_OBJS += scalar.o +.PHONY: scalar-objs +scalar-objs: $(SCALAR_OBJS) + OBJECTS += $(GIT_OBJS) +OBJECTS += $(SCALAR_OBJS) OBJECTS += $(PROGRAM_OBJS) OBJECTS += $(TEST_OBJS) OBJECTS += $(XDIFF_OBJS) @@ -2544,10 +2563,6 @@ ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif -SCALAR_SOURCES := contrib/scalar/scalar.c -SCALAR_OBJECTS := $(SCALAR_SOURCES:c=o) -OBJECTS += $(SCALAR_OBJECTS) - .PHONY: objects objects: $(OBJECTS) @@ -2582,13 +2597,7 @@ missing_compdb_dir = compdb_args = endif -ASM_SRC := $(wildcard $(OBJECTS:o=S)) -ASM_OBJ := $(ASM_SRC:S=o) -C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) - -$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) - $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< -$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) +$(OBJECTS): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< %.s: %.c GIT-CFLAGS FORCE @@ -2679,7 +2688,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) -contrib/scalar/scalar$X: $(SCALAR_OBJECTS) GIT-LDFLAGS $(GITLIBS) +scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(LIBS) @@ -2735,8 +2744,7 @@ XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ --keyword=__ --keyword=N__ --keyword="__n:1,2" MSGMERGE_FLAGS = --add-location --backup=off --update -LOCALIZED_C = $(sort $(FOUND_C_SOURCES) $(FOUND_H_SOURCES) $(SCALAR_SOURCES) \ - $(GENERATED_H)) +LOCALIZED_C = $(sort $(FOUND_C_SOURCES) $(FOUND_H_SOURCES) $(GENERATED_H)) LOCALIZED_SH = $(sort $(SCRIPT_SH) git-sh-setup.sh) LOCALIZED_PERL = $(sort $(SCRIPT_PERL)) @@ -2971,6 +2979,7 @@ GIT-BUILD-OPTIONS: FORCE @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+ @echo NO_PTHREADS=\''$(subst ','\'',$(subst ','\'',$(NO_PTHREADS)))'\' >>$@+ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+ + @echo NO_REGEX=\''$(subst ','\'',$(subst ','\'',$(NO_REGEX)))'\' >>$@+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+ @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+ @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+ @@ -3050,7 +3059,7 @@ bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template) $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@@BUILD_DIR@@|$(shell pwd)|' \ - -e 's|@@PROG@@|$(patsubst test-%,t/helper/test-%$(X),$(@F))$(patsubst git%,$(X),$(filter $(@F),$(BINDIR_PROGRAMS_NEED_X)))|' < $< > $@ && \ + -e 's|@@PROG@@|$(patsubst test-%,t/helper/test-%,$(@F))$(if $(filter-out $(BINDIR_PROGRAMS_NO_X),$(@F)),$(X),)|' < $< > $@ && \ chmod +x $@ # GNU make supports exporting all variables by "export" without parameters. @@ -3080,7 +3089,7 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) check-sha1:: t/helper/test-tool$X t/helper/test-sha1.sh -SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ)) +SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) $(SP_OBJ): %.sp: %.c %.o $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ @@ -3092,6 +3101,9 @@ $(SP_OBJ): %.sp: %.c %.o sparse: $(SP_OBJ) EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% +ifndef NETTLE_SHA256 + EXCEPT_HDRS += sha256/nettle.h +endif ifndef GCRYPT_SHA256 EXCEPT_HDRS += sha256/gcrypt.h endif @@ -3124,6 +3136,8 @@ check: $(GENERATED_H) exit 1; \ fi +COCCI_TEST_RES = $(wildcard contrib/coccinelle/tests/*.res) + %.cocci.patch: %.cocci $(COCCI_SOURCES) $(QUIET_SPATCH) \ if test $(SPATCH_BATCH_SIZE) = 0; then \ @@ -3132,7 +3146,8 @@ check: $(GENERATED_H) limit='-n $(SPATCH_BATCH_SIZE)'; \ fi; \ if ! echo $(COCCI_SOURCES) | xargs $$limit \ - $(SPATCH) --sp-file $< $(SPATCH_FLAGS) \ + $(SPATCH) $(SPATCH_FLAGS) \ + --sp-file $< --patch . \ >$@+ 2>$@.log; \ then \ cat $@.log; \ @@ -3143,13 +3158,43 @@ check: $(GENERATED_H) then \ echo ' ' SPATCH result: $@; \ fi + +COCCI_TEST_RES_GEN = $(addprefix .build/,$(COCCI_TEST_RES)) +$(COCCI_TEST_RES_GEN): .build/%.res : %.c +$(COCCI_TEST_RES_GEN): .build/%.res : %.res +$(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : contrib/coccinelle/%.cocci + $(call mkdir_p_parent_template) + $(QUIET_SPATCH_T)$(SPATCH) $(SPATCH_FLAGS) \ + --very-quiet --no-show-diff \ + --sp-file $< -o $@ \ + $(@:.build/%.res=%.c) && \ + cmp $(@:.build/%=%) $@ || \ + git -P diff --no-index $(@:.build/%=%) $@ 2>/dev/null; \ + +.PHONY: coccicheck-test +coccicheck-test: $(COCCI_TEST_RES_GEN) + +coccicheck: coccicheck-test coccicheck: $(addsuffix .patch,$(filter-out %.pending.cocci,$(wildcard contrib/coccinelle/*.cocci))) # See contrib/coccinelle/README +coccicheck-pending: coccicheck-test coccicheck-pending: $(addsuffix .patch,$(wildcard contrib/coccinelle/*.pending.cocci)) .PHONY: coccicheck coccicheck-pending +# "Sub"-Makefiles, not really because they can't be run stand-alone, +# only there to contain directory-specific rules and variables +## gitweb/Makefile inclusion: +MAK_DIR_GITWEB = gitweb/ +include gitweb/Makefile + +.PHONY: gitweb +gitweb: $(MAK_DIR_GITWEB_ALL) +ifndef NO_GITWEB +all:: gitweb +endif + ### Installation rules ifneq ($(filter /%,$(firstword $(template_dir))),) @@ -3222,21 +3267,20 @@ ifndef NO_PERL $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perllibdir_SQ)' (cd perl/build/lib && $(TAR) cf - .) | \ (cd '$(DESTDIR_SQ)$(perllibdir_SQ)' && umask 022 && $(TAR) xof -) - $(MAKE) -C gitweb install endif ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install endif ifneq (,$X) - $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';) + $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_COMMANDS_TO_INSTALL) $(OTHER_PROGRAMS))), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';) endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ destdir_from_execdir_SQ=$$(echo '$(gitexecdir_relative_SQ)' | sed -e 's|[^/][^/]*|..|g') && \ { test "$$bindir/" = "$$execdir/" || \ - for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ + for p in $(OTHER_PROGRAMS) $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ $(RM) "$$execdir/$$p" && \ test -n "$(INSTALL_SYMLINKS)" && \ ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/$$p" "$$execdir/$$p" || \ @@ -3277,10 +3321,8 @@ endif cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \ done -.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf +.PHONY: install-doc install-man install-man-perl install-html install-info install-pdf .PHONY: quick-install-doc quick-install-man quick-install-html -install-gitweb: - $(MAKE) -C gitweb install install-doc: install-man-perl $(MAKE) -C Documentation install @@ -3404,15 +3446,16 @@ profile-clean: $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) cocciclean: + $(RM) -r .build/contrib/coccinelle $(RM) contrib/coccinelle/*.cocci.patch* clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(RM) po/git.pot po/git-core.pot - $(RM) *.res + $(RM) git.res $(RM) $(OBJECTS) $(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB) - $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X + $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) $(RM) $(TEST_PROGRAMS) $(RM) $(FUZZ_PROGRAMS) $(RM) $(SP_OBJ) @@ -3426,7 +3469,6 @@ clean: profile-clean coverage-clean cocciclean $(MAKE) -C Documentation/ clean $(RM) Documentation/GIT-EXCLUDED-PROGRAMS ifndef NO_PERL - $(MAKE) -C gitweb clean $(RM) -r perl/build/ endif $(MAKE) -C templates/ clean @@ -3464,6 +3506,7 @@ ALL_COMMANDS += git-citool ALL_COMMANDS += git-gui ALL_COMMANDS += gitk ALL_COMMANDS += gitweb +ALL_COMMANDS += scalar .PHONY: check-docs check-docs:: @@ -3490,6 +3533,7 @@ check-docs:: sed -e '1,/^### command list/d' \ -e '/^#/d' \ -e '/guide$$/d' \ + -e '/interfaces$$/d' \ -e 's/[ ].*//' \ -e 's/^/listed /' command-list.txt; \ $(MAKE) -C Documentation print-man1 | \ diff --git a/RelNotes b/RelNotes index fcb45c8c04..758368388a 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.37.4.txt \ No newline at end of file +Documentation/RelNotes/2.39.0.txt \ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index 22fcd3412c..f071b2a1b4 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -430,7 +430,7 @@ struct pathname_entry { struct file_item *item; }; -static int pathname_entry_cmp(const void *unused_cmp_data, +static int pathname_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *he1, const struct hashmap_entry *he2, const void *name) diff --git a/add-patch.c b/add-patch.c index 509ca04456..33ecd8398a 100644 --- a/add-patch.c +++ b/add-patch.c @@ -191,10 +191,10 @@ static struct patch_mode patch_mode_worktree_head = { .apply_check_args = { "-R", NULL }, .is_reverse = 1, .prompt_mode = { - N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "), + N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "), + N_("Discard addition from worktree [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for discarding."), @@ -213,10 +213,10 @@ static struct patch_mode patch_mode_worktree_nothead = { .apply_args = { NULL }, .apply_check_args = { NULL }, .prompt_mode = { - N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply mode change to worktree [y,n,q,a,d%s,?]? "), + N_("Apply deletion to worktree [y,n,q,a,d%s,?]? "), + N_("Apply addition to worktree [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to worktree [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for applying."), @@ -238,6 +238,7 @@ struct hunk_header { * include the newline. */ size_t extra_start, extra_end, colored_extra_start, colored_extra_end; + unsigned suppress_colored_line_range:1; }; struct hunk { @@ -358,15 +359,14 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk) if (!eol) eol = s->colored.buf + s->colored.len; p = memmem(line, eol - line, "@@ -", 4); - if (!p) - return error(_("could not parse colored hunk header '%.*s'"), - (int)(eol - line), line); - p = memmem(p + 4, eol - p - 4, " @@", 3); - if (!p) - return error(_("could not parse colored hunk header '%.*s'"), - (int)(eol - line), line); + if (p && (p = memmem(p + 4, eol - p - 4, " @@", 3))) { + header->colored_extra_start = p + 3 - s->colored.buf; + } else { + /* could not parse colored hunk header, leave as-is */ + header->colored_extra_start = hunk->colored_start; + header->suppress_colored_line_range = 1; + } hunk->colored_start = eol - s->colored.buf + (*eol == '\n'); - header->colored_extra_start = p + 3 - s->colored.buf; header->colored_extra_end = hunk->colored_start; return 0; @@ -419,7 +419,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } color_arg_index = args.nr; /* Use `--no-color` explicitly, just in case `diff.color = always`. */ - strvec_pushl(&args, "--no-color", "-p", "--", NULL); + strvec_pushl(&args, "--no-color", "--ignore-submodules=dirty", "-p", + "--", NULL); for (i = 0; i < ps->nr; i++) strvec_push(&args, ps->items[i].original); @@ -592,7 +593,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) if (colored_eol) colored_p = colored_eol + 1; else if (p != pend) - /* colored shorter than non-colored? */ + /* non-colored has more lines? */ + goto mismatched_output; + else if (colored_p == colored_pend) + /* last line has no matching colored one? */ goto mismatched_output; else colored_p = colored_pend; @@ -656,6 +660,15 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, if (!colored) { p = s->plain.buf + header->extra_start; len = header->extra_end - header->extra_start; + } else if (header->suppress_colored_line_range) { + strbuf_add(out, + s->colored.buf + header->colored_extra_start, + header->colored_extra_end - + header->colored_extra_start); + + strbuf_add(out, s->colored.buf + hunk->colored_start, + hunk->colored_end - hunk->colored_start); + return; } else { strbuf_addstr(out, s->s.fraginfo_color); p = s->colored.buf + header->colored_extra_start; @@ -1547,7 +1560,7 @@ soft_increment: strbuf_remove(&s->answer, 0, 1); strbuf_trim(&s->answer); i = hunk_index - DISPLAY_HUNKS_LINES / 2; - if (i < file_diff->mode_change) + if (i < (int)file_diff->mode_change) i = file_diff->mode_change; while (s->answer.len == 0) { i = display_hunks(s, file_diff, i); diff --git a/advice.c b/advice.c index 6fda9edbc2..fd18968943 100644 --- a/advice.c +++ b/advice.c @@ -261,3 +261,22 @@ void detach_advice(const char *new_name) fprintf(stderr, fmt, new_name); } + +void advise_on_moving_dirty_path(struct string_list *pathspec_list) +{ + struct string_list_item *item; + + if (!pathspec_list->nr) + return; + + fprintf(stderr, _("The following paths have been moved outside the\n" + "sparse-checkout definition but are not sparse due to local\n" + "modifications.\n")); + for_each_string_list_item(item, pathspec_list) + fprintf(stderr, "%s\n", item->string); + + advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH, + _("To correct the sparsity of these paths, do the following:\n" + "* Use \"git add --sparse \" to update the index\n" + "* Use \"git sparse-checkout reapply\" to apply the sparsity rules")); +} diff --git a/advice.h b/advice.h index 7ddc6cbc1a..07e0f76833 100644 --- a/advice.h +++ b/advice.h @@ -74,5 +74,6 @@ void NORETURN die_conclude_merge(void); void NORETURN die_ff_impossible(void); void advise_on_updating_sparse_paths(struct string_list *pathspec_list); void detach_advice(const char *new_name); +void advise_on_moving_dirty_path(struct string_list *pathspec_list); #endif /* ADVICE_H */ diff --git a/archive-tar.c b/archive-tar.c index 042feb66d2..3e4822b684 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -38,11 +38,18 @@ static int write_tar_filter_archive(const struct archiver *ar, #define USTAR_MAX_MTIME 077777777777ULL #endif +static void tar_write_block(const void *buf) +{ + write_or_die(1, buf, BLOCKSIZE); +} + +static void (*write_block)(const void *) = tar_write_block; + /* writes out the whole block, but only if it is full */ static void write_if_needed(void) { if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); + write_block(block); offset = 0; } } @@ -66,7 +73,7 @@ static void do_write_blocked(const void *data, unsigned long size) write_if_needed(); } while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); + write_block(buf); size -= BLOCKSIZE; buf += BLOCKSIZE; } @@ -101,10 +108,10 @@ static void write_trailer(void) { int tail = BLOCKSIZE - offset; memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); + write_block(block); if (tail < 2 * RECORDSIZE) { memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); + write_block(block); } } @@ -359,7 +366,8 @@ static struct archiver *find_tar_filter(const char *name, size_t len) return NULL; } -static int tar_filter_config(const char *var, const char *value, void *data) +static int tar_filter_config(const char *var, const char *value, + void *data UNUSED) { struct archiver *ar; const char *name; @@ -383,8 +391,8 @@ static int tar_filter_config(const char *var, const char *value, void *data) if (!strcmp(type, "command")) { if (!value) return config_error_nonbool(var); - free(ar->data); - ar->data = xstrdup(value); + free(ar->filter_command); + ar->filter_command = xstrdup(value); return 0; } if (!strcmp(type, "remote")) { @@ -413,7 +421,7 @@ static int git_tar_config(const char *var, const char *value, void *cb) return tar_filter_config(var, value, cb); } -static int write_tar_archive(const struct archiver *ar, +static int write_tar_archive(const struct archiver *ar UNUSED, struct archiver_args *args) { int err = 0; @@ -425,17 +433,65 @@ static int write_tar_archive(const struct archiver *ar, return err; } +static git_zstream gzstream; +static unsigned char outbuf[16384]; + +static void tgz_deflate(int flush) +{ + while (gzstream.avail_in || flush == Z_FINISH) { + int status = git_deflate(&gzstream, flush); + if (!gzstream.avail_out || status == Z_STREAM_END) { + write_or_die(1, outbuf, gzstream.next_out - outbuf); + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + if (status == Z_STREAM_END) + break; + } + if (status != Z_OK && status != Z_BUF_ERROR) + die(_("deflate error (%d)"), status); + } +} + +static void tgz_write_block(const void *data) +{ + gzstream.next_in = (void *)data; + gzstream.avail_in = BLOCKSIZE; + tgz_deflate(Z_NO_FLUSH); +} + +static const char internal_gzip_command[] = "git archive gzip"; + static int write_tar_filter_archive(const struct archiver *ar, struct archiver_args *args) { +#if ZLIB_VERNUM >= 0x1221 + struct gz_header_s gzhead = { .os = 3 }; /* Unix, for reproducibility */ +#endif struct strbuf cmd = STRBUF_INIT; struct child_process filter = CHILD_PROCESS_INIT; int r; - if (!ar->data) + if (!ar->filter_command) BUG("tar-filter archiver called with no filter defined"); - strbuf_addstr(&cmd, ar->data); + if (!strcmp(ar->filter_command, internal_gzip_command)) { + write_block = tgz_write_block; + git_deflate_init_gzip(&gzstream, args->compression_level); +#if ZLIB_VERNUM >= 0x1221 + if (deflateSetHeader(&gzstream.z, &gzhead) != Z_OK) + BUG("deflateSetHeader() called too late"); +#endif + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + + r = write_tar_archive(ar, args); + + tgz_deflate(Z_FINISH); + git_deflate_end(&gzstream); + return r; + } + + strbuf_addstr(&cmd, ar->filter_command); if (args->compression_level >= 0) strbuf_addf(&cmd, " -%d", args->compression_level); @@ -471,14 +527,14 @@ void init_tar_archiver(void) int i; register_archiver(&tar_archiver); - tar_filter_config("tar.tgz.command", "gzip -cn", NULL); + tar_filter_config("tar.tgz.command", internal_gzip_command, NULL); tar_filter_config("tar.tgz.remote", "true", NULL); - tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL); + tar_filter_config("tar.tar.gz.command", internal_gzip_command, NULL); tar_filter_config("tar.tar.gz.remote", "true", NULL); git_config(git_tar_config, NULL); for (i = 0; i < nr_tar_filters; i++) { /* omit any filters that never had a command configured */ - if (tar_filters[i]->data) + if (tar_filters[i]->filter_command) register_archiver(tar_filters[i]); } } diff --git a/archive-zip.c b/archive-zip.c index 9fe43d740d..0456f1ebf1 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -612,12 +612,13 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time) *dos_time = tm.tm_sec / 2 + tm.tm_min * 32 + tm.tm_hour * 2048; } -static int archive_zip_config(const char *var, const char *value, void *data) +static int archive_zip_config(const char *var, const char *value, + void *data UNUSED) { return userdiff_config(var, value); } -static int write_zip_archive(const struct archiver *ar, +static int write_zip_archive(const struct archiver *ar UNUSED, struct archiver_args *args) { int err; diff --git a/archive.c b/archive.c index d5109abb89..61a79e4a22 100644 --- a/archive.c +++ b/archive.c @@ -382,7 +382,8 @@ struct path_exists_context { struct archiver_args *args; }; -static int reject_entry(const struct object_id *oid, struct strbuf *base, +static int reject_entry(const struct object_id *oid UNUSED, + struct strbuf *base, const char *filename, unsigned mode, void *context) { diff --git a/archive.h b/archive.h index 49fab71aaf..08bed3ed3a 100644 --- a/archive.h +++ b/archive.h @@ -43,7 +43,7 @@ struct archiver { const char *name; int (*write_archive)(const struct archiver *, struct archiver_args *); unsigned flags; - void *data; + char *filter_command; }; void register_archiver(struct archiver *); diff --git a/attr.c b/attr.c index 21e4ad25ad..8250b06953 100644 --- a/attr.c +++ b/attr.c @@ -61,10 +61,10 @@ struct attr_hash_entry { }; /* attr_hashmap comparison function */ -static int attr_hash_entry_cmp(const void *unused_cmp_data, +static int attr_hash_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct attr_hash_entry *a, *b; @@ -1023,7 +1023,7 @@ static int path_matches(const char *pathname, int pathlen, } return match_pathname(pathname, pathlen - isdir, base, baselen, - pattern, prefix, pat->patternlen, pat->flags); + pattern, prefix, pat->patternlen); } static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem); diff --git a/bisect.c b/bisect.c index b63669cc9d..fd581b85a7 100644 --- a/bisect.c +++ b/bisect.c @@ -441,7 +441,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches, } static int register_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) + int flags UNUSED, void *cb_data UNUSED) { struct strbuf good_prefix = STRBUF_INIT; strbuf_addstr(&good_prefix, term_good); @@ -648,11 +648,14 @@ static struct commit_list *managed_skipped(struct commit_list *list, } static void bisect_rev_setup(struct repository *r, struct rev_info *revs, + struct strvec *rev_argv, const char *prefix, const char *bad_format, const char *good_format, int read_paths) { - struct strvec rev_argv = STRVEC_INIT; + struct setup_revision_opt opt = { + .free_removed_argv_elements = 1, + }; int i; repo_init_revisions(r, revs, prefix); @@ -660,17 +663,16 @@ static void bisect_rev_setup(struct repository *r, struct rev_info *revs, revs->commit_format = CMIT_FMT_UNSPECIFIED; /* rev_argv.argv[0] will be ignored by setup_revisions */ - strvec_push(&rev_argv, "bisect_rev_setup"); - strvec_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid)); + strvec_push(rev_argv, "bisect_rev_setup"); + strvec_pushf(rev_argv, bad_format, oid_to_hex(current_bad_oid)); for (i = 0; i < good_revs.nr; i++) - strvec_pushf(&rev_argv, good_format, + strvec_pushf(rev_argv, good_format, oid_to_hex(good_revs.oid + i)); - strvec_push(&rev_argv, "--"); + strvec_push(rev_argv, "--"); if (read_paths) - read_bisect_paths(&rev_argv); + read_bisect_paths(rev_argv); - setup_revisions(rev_argv.nr, rev_argv.v, revs, NULL); - /* XXX leak rev_argv, as "revs" may still be pointing to it */ + setup_revisions(rev_argv->nr, rev_argv->v, revs, &opt); } static void bisect_common(struct rev_info *revs) @@ -873,10 +875,11 @@ static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int static int check_ancestors(struct repository *r, int rev_nr, struct commit **rev, const char *prefix) { + struct strvec rev_argv = STRVEC_INIT; struct rev_info revs; int res; - bisect_rev_setup(r, &revs, prefix, "^%s", "%s", 0); + bisect_rev_setup(r, &revs, &rev_argv, prefix, "^%s", "%s", 0); bisect_common(&revs); res = (revs.commits != NULL); @@ -885,6 +888,7 @@ static int check_ancestors(struct repository *r, int rev_nr, clear_commit_marks_many(rev_nr, rev, ALL_REV_FLAGS); release_revisions(&revs); + strvec_clear(&rev_argv); return res; } @@ -1010,6 +1014,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good) */ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) { + struct strvec rev_argv = STRVEC_INIT; struct rev_info revs = REV_INFO_INIT; struct commit_list *tried; int reaches = 0, all = 0, nr, steps; @@ -1037,7 +1042,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) if (res) goto cleanup; - bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1); + bisect_rev_setup(r, &revs, &rev_argv, prefix, "%s", "^%s", 1); revs.first_parent_only = !!(bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY); revs.limited = 1; @@ -1054,7 +1059,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) */ res = error_if_skipped_commits(tried, NULL); if (res < 0) - return res; + goto cleanup; printf(_("%s was both %s and %s\n"), oid_to_hex(current_bad_oid), term_good, @@ -1112,6 +1117,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) res = bisect_checkout(bisect_rev, no_checkout); cleanup: release_revisions(&revs); + strvec_clear(&rev_argv); return res; } @@ -1154,8 +1160,9 @@ int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -static int mark_for_removal(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int mark_for_removal(const char *refname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb_data) { struct string_list *refs = cb_data; char *ref = xstrfmt("refs/bisect%s", refname); diff --git a/blame.c b/blame.c index da1052ac94..8bfeaa1c63 100644 --- a/blame.c +++ b/blame.c @@ -1098,30 +1098,22 @@ static struct blame_entry *blame_merge(struct blame_entry *list1, } } -static void *get_next_blame(const void *p) -{ - return ((struct blame_entry *)p)->next; -} - -static void set_next_blame(void *p1, void *p2) -{ - ((struct blame_entry *)p1)->next = p2; -} +DEFINE_LIST_SORT(static, sort_blame_entries, struct blame_entry, next); /* * Final image line numbers are all different, so we don't need a * three-way comparison here. */ -static int compare_blame_final(const void *p1, const void *p2) +static int compare_blame_final(const struct blame_entry *e1, + const struct blame_entry *e2) { - return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno - ? 1 : -1; + return e1->lno > e2->lno ? 1 : -1; } -static int compare_blame_suspect(const void *p1, const void *p2) +static int compare_blame_suspect(const struct blame_entry *s1, + const struct blame_entry *s2) { - const struct blame_entry *s1 = p1, *s2 = p2; /* * to allow for collating suspects, we sort according to the * respective pointer value as the primary sorting criterion. @@ -1138,8 +1130,7 @@ static int compare_blame_suspect(const void *p1, const void *p2) void blame_sort_final(struct blame_scoreboard *sb) { - sb->ent = llist_mergesort(sb->ent, get_next_blame, set_next_blame, - compare_blame_final); + sort_blame_entries(&sb->ent, compare_blame_final); } static int compare_commits_by_reverse_commit_date(const void *a, @@ -1964,9 +1955,7 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb, parent, target, 0); *d.dstq = NULL; if (ignore_diffs) - newdest = llist_mergesort(newdest, get_next_blame, - set_next_blame, - compare_blame_suspect); + sort_blame_entries(&newdest, compare_blame_suspect); queue_blames(sb, parent, newdest); return; @@ -2383,8 +2372,7 @@ static int num_scapegoats(struct rev_info *revs, struct commit *commit, int reve */ static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed) { - blamed = llist_mergesort(blamed, get_next_blame, set_next_blame, - compare_blame_suspect); + sort_blame_entries(&blamed, compare_blame_suspect); while (blamed) { struct blame_origin *porigin = blamed->suspect; diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c index 5974cd7dd3..80cebd2756 100644 --- a/block-sha1/sha1.c +++ b/block-sha1/sha1.c @@ -28,10 +28,6 @@ * try to do the silly "optimize away loads" part because it won't * see what the value will be). * - * Ben Herrenschmidt reports that on PPC, the C version comes close - * to the optimized asm with this (ie on PPC you don't want that - * 'volatile', since there are lots of registers). - * * On ARM we get the best code generation by forcing a full memory barrier * between each SHA_ROUND, otherwise gcc happily get wild with spilling and * the stack frame size simply explode and performance goes down the drain. diff --git a/bloom.c b/bloom.c index 816f063dca..d0730525da 100644 --- a/bloom.c +++ b/bloom.c @@ -163,10 +163,10 @@ void init_bloom_filters(void) init_bloom_filter_slab(&bloom_filters); } -static int pathmap_cmp(const void *hashmap_cmp_fn_data, +static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *keydata) + const void *keydata UNUSED) { const struct pathmap_hash_entry *e1, *e2; diff --git a/branch.c b/branch.c index 4c8523c66a..d182756827 100644 --- a/branch.c +++ b/branch.c @@ -10,6 +10,7 @@ #include "worktree.h" #include "submodule-config.h" #include "run-command.h" +#include "strmap.h" struct tracking { struct refspec_item spec; @@ -369,6 +370,83 @@ int validate_branchname(const char *name, struct strbuf *ref) return ref_exists(ref->buf); } +static int initialized_checked_out_branches; +static struct strmap current_checked_out_branches = STRMAP_INIT; + +static void prepare_checked_out_branches(void) +{ + int i = 0; + struct worktree **worktrees; + + if (initialized_checked_out_branches) + return; + initialized_checked_out_branches = 1; + + worktrees = get_worktrees(); + + while (worktrees[i]) { + char *old; + struct wt_status_state state = { 0 }; + struct worktree *wt = worktrees[i++]; + struct string_list update_refs = STRING_LIST_INIT_DUP; + + if (wt->is_bare) + continue; + + if (wt->head_ref) { + old = strmap_put(¤t_checked_out_branches, + wt->head_ref, + xstrdup(wt->path)); + free(old); + } + + if (wt_status_check_rebase(wt, &state) && + (state.rebase_in_progress || state.rebase_interactive_in_progress) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + + if (wt_status_check_bisect(wt, &state) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + + if (!sequencer_get_update_refs_state(get_worktree_git_dir(wt), + &update_refs)) { + struct string_list_item *item; + for_each_string_list_item(item, &update_refs) { + old = strmap_put(¤t_checked_out_branches, + item->string, + xstrdup(wt->path)); + free(old); + } + string_list_clear(&update_refs, 1); + } + } + + free_worktrees(worktrees); +} + +const char *branch_checked_out(const char *refname) +{ + prepare_checked_out_branches(); + return strmap_get(¤t_checked_out_branches, refname); +} + /* * Check if a branch 'name' can be created as a new branch; die otherwise. * 'force' can be used when it is OK for the named branch already exists. @@ -377,9 +455,7 @@ int validate_branchname(const char *name, struct strbuf *ref) */ int validate_new_branchname(const char *name, struct strbuf *ref, int force) { - struct worktree **worktrees; - const struct worktree *wt; - + const char *path; if (!validate_branchname(name, ref)) return 0; @@ -387,13 +463,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force) die(_("a branch named '%s' already exists"), ref->buf + strlen("refs/heads/")); - worktrees = get_worktrees(); - wt = find_shared_symref(worktrees, "HEAD", ref->buf); - if (wt && !wt->is_bare) + if ((path = branch_checked_out(ref->buf))) die(_("cannot force update the branch '%s' " "checked out at '%s'"), - ref->buf + strlen("refs/heads/"), wt->path); - free_worktrees(worktrees); + ref->buf + strlen("refs/heads/"), path); return 1; } diff --git a/branch.h b/branch.h index 560b6b96a8..ef56103c05 100644 --- a/branch.h +++ b/branch.h @@ -101,6 +101,13 @@ void create_branches_recursively(struct repository *r, const char *name, const char *tracking_name, int force, int reflog, int quiet, enum branch_track track, int dry_run); + +/* + * If the branch at 'refname' is currently checked out in a worktree, + * then return the path to that worktree. + */ +const char *branch_checked_out(const char *refname); + /* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/builtin.h b/builtin.h index 40e9ecc848..8901a34d6b 100644 --- a/builtin.h +++ b/builtin.h @@ -144,6 +144,7 @@ int cmd_credential_cache(int argc, const char **argv, const char *prefix); int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix); int cmd_credential_store(int argc, const char **argv, const char *prefix); int cmd_describe(int argc, const char **argv, const char *prefix); +int cmd_diagnose(int argc, const char **argv, const char *prefix); int cmd_diff_files(int argc, const char **argv, const char *prefix); int cmd_diff_index(int argc, const char **argv, const char *prefix); int cmd_diff(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c index 93bec62afa..39fea24833 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2301,7 +2301,7 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar return 0; } -static int git_am_config(const char *k, const char *v, void *cb) +static int git_am_config(const char *k, const char *v, void *cb UNUSED) { int status; diff --git a/builtin/archive.c b/builtin/archive.c index 7176b041b6..f094390ee0 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -75,7 +75,7 @@ static int run_remote_archiver(int argc, const char **argv, #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ - PARSE_OPT_KEEP_UNKNOWN | \ + PARSE_OPT_KEEP_UNKNOWN_OPT | \ PARSE_OPT_NO_INTERNAL_HELP ) int cmd_archive(int argc, const char **argv, const char *prefix) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 8a052c7111..501245fac9 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -329,8 +329,9 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) return 0; } -static int inc_nr(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int inc_nr(const char *refname UNUSED, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb_data) { unsigned int *nr = (unsigned int *)cb_data; (*nr)++; @@ -518,7 +519,7 @@ finish: } static int add_bisect_ref(const char *refname, const struct object_id *oid, - int flags, void *cb) + int flags UNUSED, void *cb) { struct add_bisect_ref_data *data = cb; @@ -1134,8 +1135,9 @@ static int bisect_visualize(struct bisect_terms *terms, const char **argv, int a return res; } -static int get_first_good(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int get_first_good(const char *refname UNUSED, + const struct object_id *oid, + int flag UNUSED, void *cb_data) { oidcpy(cb_data, oid); return 1; @@ -1324,7 +1326,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_bisect_helper_usage, - PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN_OPT); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); diff --git a/builtin/blame.c b/builtin/blame.c index 02e39420b6..a9fe8cf7a6 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -920,6 +920,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) break; case PARSE_OPT_HELP: case PARSE_OPT_ERROR: + case PARSE_OPT_SUBCOMMAND: exit(129); case PARSE_OPT_COMPLETE: exit(0); diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d3..55cd9a6e99 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -204,7 +204,6 @@ static void delete_branch_config(const char *branchname) static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { - struct worktree **worktrees; struct commit *head_rev = NULL; struct object_id oid; char *name = NULL; @@ -242,8 +241,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } - worktrees = get_worktrees(); - for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -253,12 +250,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - const struct worktree *wt = - find_shared_symref(worktrees, "HEAD", name); - if (wt) { + const char *path; + if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); ret = 1; continue; } @@ -315,7 +311,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); strbuf_release(&bname); - free_worktrees(worktrees); return ret; } diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 9de32bc96e..530895be55 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -5,6 +5,7 @@ #include "compat/compiler.h" #include "hook.h" #include "hook-list.h" +#include "diagnose.h" static void get_system_info(struct strbuf *sys_info) @@ -59,7 +60,7 @@ static void get_populated_hooks(struct strbuf *hook_info, int nongit) } static const char * const bugreport_usage[] = { - N_("git bugreport [-o|--output-directory ] [-s|--suffix ]"), + N_("git bugreport [-o|--output-directory ] [-s|--suffix ] [--diagnose[=]"), NULL }; @@ -98,16 +99,21 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) int report = -1; time_t now = time(NULL); struct tm tm; + enum diagnose_mode diagnose = DIAGNOSE_NONE; char *option_output = NULL; char *option_suffix = "%Y-%m-%d-%H%M"; const char *user_relative_path = NULL; char *prefixed_filename; + size_t output_path_len; const struct option bugreport_options[] = { + OPT_CALLBACK_F(0, "diagnose", &diagnose, N_("mode"), + N_("create an additional zip archive of detailed diagnostics (default 'stats')"), + PARSE_OPT_OPTARG, option_parse_diagnose), OPT_STRING('o', "output-directory", &option_output, N_("path"), - N_("specify a destination for the bugreport file")), + N_("specify a destination for the bugreport file(s)")), OPT_STRING('s', "suffix", &option_suffix, N_("format"), - N_("specify a strftime format suffix for the filename")), + N_("specify a strftime format suffix for the filename(s)")), OPT_END() }; @@ -119,6 +125,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) option_output ? option_output : ""); strbuf_addstr(&report_path, prefixed_filename); strbuf_complete(&report_path, '/'); + output_path_len = report_path.len; strbuf_addstr(&report_path, "git-bugreport-"); strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0); @@ -133,6 +140,20 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) report_path.buf); } + /* Prepare diagnostics, if requested */ + if (diagnose != DIAGNOSE_NONE) { + struct strbuf zip_path = STRBUF_INIT; + strbuf_add(&zip_path, report_path.buf, output_path_len); + strbuf_addstr(&zip_path, "git-diagnostics-"); + strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0); + strbuf_addstr(&zip_path, ".zip"); + + if (create_diagnostics_archive(&zip_path, diagnose)) + die_errno(_("unable to create diagnostics archive %s"), zip_path.buf); + + strbuf_release(&zip_path); + } + /* Prepare the report contents */ get_bug_template(&buffer); diff --git a/builtin/bundle.c b/builtin/bundle.c index 2adad545a2..e80efce3a4 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -195,30 +195,19 @@ cleanup: int cmd_bundle(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("create", &fn, cmd_bundle_create), + OPT_SUBCOMMAND("verify", &fn, cmd_bundle_verify), + OPT_SUBCOMMAND("list-heads", &fn, cmd_bundle_list_heads), + OPT_SUBCOMMAND("unbundle", &fn, cmd_bundle_unbundle), OPT_END() }; - int result; argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + 0); packet_trace_identity("bundle"); - if (argc < 2) - usage_with_options(builtin_bundle_usage, options); - - else if (!strcmp(argv[0], "create")) - result = cmd_bundle_create(argc, argv, prefix); - else if (!strcmp(argv[0], "verify")) - result = cmd_bundle_verify(argc, argv, prefix); - else if (!strcmp(argv[0], "list-heads")) - result = cmd_bundle_list_heads(argc, argv, prefix); - else if (!strcmp(argv[0], "unbundle")) - result = cmd_bundle_unbundle(argc, argv, prefix); - else { - error(_("Unknown subcommand: %s"), argv[0]); - usage_with_options(builtin_bundle_usage, options); - } - return result ? 1 : 0; + return !!fn(argc, argv, prefix); } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 50cf38999d..989eee0bb4 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -16,6 +16,7 @@ #include "packfile.h" #include "object-store.h" #include "promisor-remote.h" +#include "mailmap.h" enum batch_mode { BATCH_MODE_CONTENTS, @@ -31,11 +32,28 @@ struct batch_options { int all_objects; int unordered; int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */ + int nul_terminated; const char *format; }; static const char *force_path; +static struct string_list mailmap = STRING_LIST_INIT_NODUP; +static int use_mailmap; + +static char *replace_idents_using_mailmap(char *, size_t *); + +static char *replace_idents_using_mailmap(char *object_buf, size_t *size) +{ + struct strbuf sb = STRBUF_INIT; + const char *headers[] = { "author ", "committer ", "tagger ", NULL }; + + strbuf_attach(&sb, object_buf, *size, *size + 1); + apply_mailmap_to_header(&sb, headers, &mailmap); + *size = sb.len; + return strbuf_detach(&sb, NULL); +} + static int filter_object(const char *path, unsigned mode, const struct object_id *oid, char **buf, unsigned long *size) @@ -71,6 +89,7 @@ static int stream_blob(const struct object_id *oid) static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { + int ret; struct object_id oid; enum object_type type; char *buf; @@ -106,7 +125,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (sb.len) { printf("%s\n", sb.buf); strbuf_release(&sb); - return 0; + ret = 0; + goto cleanup; } break; @@ -115,7 +135,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); printf("%"PRIuMAX"\n", (uintmax_t)size); - return 0; + ret = 0; + goto cleanup; case 'e': return !has_object_file(&oid); @@ -123,8 +144,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 'w': if (filter_object(path, obj_context.mode, - &oid, &buf, &size)) - return -1; + &oid, &buf, &size)) { + ret = -1; + goto cleanup; + } break; case 'c': @@ -143,15 +166,24 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, const char *ls_args[3] = { NULL }; ls_args[0] = "ls-tree"; ls_args[1] = obj_name; - return cmd_ls_tree(2, ls_args, NULL); + ret = cmd_ls_tree(2, ls_args, NULL); + goto cleanup; } - if (type == OBJ_BLOB) - return stream_blob(&oid); + if (type == OBJ_BLOB) { + ret = stream_blob(&oid); + goto cleanup; + } buf = read_object_file(&oid, &type, &size); if (!buf) die("Cannot read object %s", obj_name); + if (use_mailmap) { + size_t s = size; + buf = replace_idents_using_mailmap(buf, &s); + size = cast_size_t_to_ulong(s); + } + /* otherwise just spit out the data */ break; @@ -172,8 +204,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } else oidcpy(&blob_oid, &oid); - if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) - return stream_blob(&blob_oid); + if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) { + ret = stream_blob(&blob_oid); + goto cleanup; + } /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -183,6 +217,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } buf = read_object_with_reference(the_repository, &oid, exp_type_id, &size, NULL); + + if (use_mailmap) { + size_t s = size; + buf = replace_idents_using_mailmap(buf, &s); + size = cast_size_t_to_ulong(s); + } break; } default: @@ -193,9 +233,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, die("git cat-file %s: bad file", obj_name); write_or_die(1, buf, size); + ret = 0; +cleanup: free(buf); free(obj_context.path); - return 0; + return ret; } struct expand_data { @@ -348,11 +390,18 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d void *contents; contents = read_object_file(oid, &type, &size); + + if (use_mailmap) { + size_t s = size; + contents = replace_idents_using_mailmap(contents, &s); + size = cast_size_t_to_ulong(s); + } + if (!contents) die("object %s disappeared", oid_to_hex(oid)); if (type != data->type) die("object %s changed type!?", oid_to_hex(oid)); - if (data->info.sizep && size != data->size) + if (data->info.sizep && size != data->size && !use_mailmap) die("object %s changed size!?", oid_to_hex(oid)); batch_write(opt, contents, size); @@ -602,12 +651,20 @@ static void batch_objects_command(struct batch_options *opt, struct queued_cmd *queued_cmd = NULL; size_t alloc = 0, nr = 0; - while (!strbuf_getline(&input, stdin)) { - int i; + while (1) { + int i, ret; const struct parse_cmd *cmd = NULL; const char *p = NULL, *cmd_end; struct queued_cmd call = {0}; + if (opt->nul_terminated) + ret = strbuf_getline_nul(&input, stdin); + else + ret = strbuf_getline(&input, stdin); + + if (ret) + break; + if (!input.len) die(_("empty command in input")); if (isspace(*input.buf)) @@ -655,6 +712,7 @@ static void batch_objects_command(struct batch_options *opt, free_cmds(queued_cmd, &nr); } + free_cmds(queued_cmd, &nr); free(queued_cmd); strbuf_release(&input); } @@ -750,7 +808,16 @@ static int batch_objects(struct batch_options *opt) goto cleanup; } - while (strbuf_getline(&input, stdin) != EOF) { + while (1) { + int ret; + if (opt->nul_terminated) + ret = strbuf_getline_nul(&input, stdin); + else + ret = strbuf_getline(&input, stdin); + + if (ret == EOF) + break; + if (data.split_on_whitespace) { /* * Split at first whitespace, tying off the beginning @@ -843,6 +910,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), + OPT_BOOL(0, "use-mailmap", &use_mailmap, N_("use mail map file")), + OPT_ALIAS(0, "mailmap", "use-mailmap"), /* Batch mode */ OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), OPT_CALLBACK_F(0, "batch", &batch, N_("format"), @@ -853,6 +922,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) N_("like --batch, but don't emit "), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_BOOL('z', NULL, &batch.nul_terminated, N_("stdin is NUL-terminated")), OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"), N_("read commands from stdin"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, @@ -885,6 +955,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) opt_cw = (opt == 'c' || opt == 'w'); opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); + if (use_mailmap) + read_mailmap(&mailmap); + /* --batch-all-objects? */ if (opt == 'b') batch.all_objects = 1; @@ -908,6 +981,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else if (batch.all_objects) usage_msg_optf(_("'%s' requires a batch mode"), usage, options, "--batch-all-objects"); + else if (batch.nul_terminated) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "-z"); /* Batch defaults */ if (batch.buffer_output < 0) diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index bc67d3f0a8..fd0e5f8683 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -57,6 +57,8 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) int normalize = 0; int flags = 0; const char *refname; + char *to_free = NULL; + int ret = 1; if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); @@ -81,11 +83,14 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) refname = argv[i]; if (normalize) - refname = collapse_slashes(refname); + refname = to_free = collapse_slashes(refname); if (check_refname_format(refname, flags)) - return 1; + goto cleanup; if (normalize) printf("%s\n", refname); - return 0; + ret = 0; +cleanup: + free(to_free); + return ret; } diff --git a/builtin/checkout.c b/builtin/checkout.c index cdd96cd9c6..2a132392fb 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -125,7 +125,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm } static int update_some(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, void *context UNUSED) { int len; struct cache_entry *ce; @@ -711,6 +711,26 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } +static void init_topts(struct unpack_trees_options *topts, int merge, + int show_progress, int overwrite_ignore, + struct commit *old_commit) +{ + memset(topts, 0, sizeof(*topts)); + topts->head_idx = -1; + topts->src_index = &the_index; + topts->dst_index = &the_index; + + setup_unpack_trees_porcelain(topts, "checkout"); + + topts->initial_checkout = is_cache_unborn(); + topts->update = 1; + topts->merge = 1; + topts->quiet = merge && old_commit; + topts->verbose_update = show_progress; + topts->fn = twoway_merge; + topts->preserve_ignored = !overwrite_ignore; +} + static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, @@ -741,13 +761,6 @@ static int merge_working_tree(const struct checkout_opts *opts, struct unpack_trees_options topts; const struct object_id *old_commit_oid; - memset(&topts, 0, sizeof(topts)); - topts.head_idx = -1; - topts.src_index = &the_index; - topts.dst_index = &the_index; - - setup_unpack_trees_porcelain(&topts, "checkout"); - refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { @@ -756,17 +769,12 @@ static int merge_working_tree(const struct checkout_opts *opts, } /* 2-way merge to the new branch */ - topts.initial_checkout = is_cache_unborn(); - topts.update = 1; - topts.merge = 1; - topts.quiet = opts->merge && old_branch_info->commit; - topts.verbose_update = opts->show_progress; - topts.fn = twoway_merge; + init_topts(&topts, opts->merge, opts->show_progress, + opts->overwrite_ignore, old_branch_info->commit); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); - topts.preserve_ignored = !opts->overwrite_ignore; old_commit_oid = old_branch_info->commit ? &old_branch_info->commit->object.oid : @@ -982,7 +990,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, static int add_pending_uninteresting_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) + int flags UNUSED, void *cb_data) { add_pending_oid(cb_data, refname, oid, UNINTERESTING); return 0; diff --git a/builtin/clone.c b/builtin/clone.c index f5f902cce1..ed8d44bb6a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -34,6 +34,7 @@ #include "list-objects-filter-options.h" #include "hook.h" #include "bundle.h" +#include "bundle-uri.h" /* * Overall FIXMEs: @@ -72,11 +73,12 @@ static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; static int option_dissociate; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; -static struct list_objects_filter_options filter_options; +static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static int option_filter_submodules = -1; /* unspecified */ static int config_filter_submodules = -1; /* unspecified */ static struct string_list server_options = STRING_LIST_INIT_NODUP; static int option_remote_submodules; +static const char *bundle_uri; static int recurse_submodules_cb(const struct option *opt, const char *arg, int unset) @@ -160,6 +162,8 @@ static struct option builtin_clone_options[] = { N_("any cloned submodules will use their remote-tracking branch")), OPT_BOOL(0, "sparse", &option_sparse_checkout, N_("initialize sparse-checkout file to include only files at root")), + OPT_STRING(0, "bundle-uri", &bundle_uri, + N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")), OPT_END() }; @@ -494,6 +498,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs, /* if --branch=tag, pull the requested tag explicitly */ get_fetch_map(remote_head, tag_refspec, &tail, 0); } + free_refs(remote_head); } else { int i; for (i = 0; i < refspec->nr; i++) @@ -929,6 +934,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_no_checkout = 1; } + if (bundle_uri && deepen) + die(_("--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-exclude")); + repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); @@ -1228,6 +1236,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; + /* + * Before fetching from the remote, download and install bundle + * data from the --bundle-uri option. + */ + if (bundle_uri) { + /* At this point, we need the_repository to match the cloned repo. */ + if (repo_init(the_repository, git_dir, work_tree)) + warning(_("failed to initialize the repo, skipping bundle URI")); + else if (fetch_bundle_uri(the_repository, bundle_uri)) + warning(_("failed to fetch objects from bundle URI '%s'"), + bundle_uri); + } strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); refspec_ref_prefixes(&remote->fetch, diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 51c4040ea6..51557fe786 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -58,7 +58,7 @@ static struct option *add_common_options(struct option *to) return parse_options_concat(common_opts, to); } -static int graph_verify(int argc, const char **argv) +static int graph_verify(int argc, const char **argv, const char *prefix) { struct commit_graph *graph = NULL; struct object_directory *odb = NULL; @@ -80,7 +80,7 @@ static int graph_verify(int argc, const char **argv) trace2_cmd_mode("verify"); opts.progress = isatty(2); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_commit_graph_verify_usage, 0); if (argc) @@ -179,7 +179,7 @@ static int write_option_max_new_filters(const struct option *opt, } static int git_commit_graph_write_config(const char *var, const char *value, - void *cb) + void *cb UNUSED) { if (!strcmp(var, "commitgraph.maxnewfilters")) write_opts.max_new_filters = git_config_int(var, value); @@ -190,7 +190,7 @@ static int git_commit_graph_write_config(const char *var, const char *value, return 0; } -static int graph_write(int argc, const char **argv) +static int graph_write(int argc, const char **argv, const char *prefix) { struct string_list pack_indexes = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; @@ -241,7 +241,7 @@ static int graph_write(int argc, const char **argv) git_config(git_commit_graph_write_config, &opts); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_commit_graph_write_usage, 0); if (argc) @@ -307,26 +307,22 @@ cleanup: int cmd_commit_graph(int argc, const char **argv, const char *prefix) { - struct option *builtin_commit_graph_options = common_opts; + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_commit_graph_options[] = { + OPT_SUBCOMMAND("verify", &fn, graph_verify), + OPT_SUBCOMMAND("write", &fn, graph_write), + OPT_END(), + }; + struct option *options = parse_options_concat(builtin_commit_graph_options, common_opts); git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, - builtin_commit_graph_options, - builtin_commit_graph_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc) - goto usage; read_replace_refs = 0; save_commit_buffer = 0; - if (!strcmp(argv[0], "verify")) - return graph_verify(argc, argv); - else if (argc && !strcmp(argv[0], "write")) - return graph_write(argc, argv); + argc = parse_options(argc, argv, prefix, options, + builtin_commit_graph_usage, 0); + FREE_AND_NULL(options); - error(_("unrecognized subcommand: %s"), argv[0]); -usage: - usage_with_options(builtin_commit_graph_usage, - builtin_commit_graph_options); + return fn(argc, argv, prefix); } diff --git a/builtin/config.c b/builtin/config.c index e7b88a9c08..753e5fac29 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -207,7 +207,8 @@ static void show_config_scope(struct strbuf *buf) strbuf_addch(buf, term); } -static int show_all_config(const char *key_, const char *value_, void *cb) +static int show_all_config(const char *key_, const char *value_, + void *cb UNUSED) { if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; @@ -458,7 +459,8 @@ static const char *get_color_slot; static const char *get_colorbool_slot; static char parsed_color[COLOR_MAXLEN]; -static int git_get_color_config(const char *var, const char *value, void *cb) +static int git_get_color_config(const char *var, const char *value, + void *cb UNUSED) { if (!strcmp(var, get_color_slot)) { if (!value) @@ -490,7 +492,7 @@ static int get_colorbool_found; static int get_diff_color_found; static int get_color_ui_found; static int git_get_colorbool_config(const char *var, const char *value, - void *cb) + void *data UNUSED) { if (!strcmp(var, get_colorbool_slot)) get_colorbool_found = git_config_colorbool(var, value); diff --git a/builtin/describe.c b/builtin/describe.c index a76f1a1a7a..e17c4b4c69 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -63,7 +63,7 @@ static const char *prio_names[] = { N_("head"), N_("lightweight"), N_("annotated"), }; -static int commit_name_neq(const void *unused_cmp_data, +static int commit_name_neq(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *peeled) @@ -140,7 +140,8 @@ static void add_to_known_names(const char *path, } } -static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data) +static int get_name(const char *path, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { int is_tag = 0; struct object_id peeled; diff --git a/builtin/diagnose.c b/builtin/diagnose.c new file mode 100644 index 0000000000..576e0e8e38 --- /dev/null +++ b/builtin/diagnose.c @@ -0,0 +1,61 @@ +#include "builtin.h" +#include "parse-options.h" +#include "diagnose.h" + +static const char * const diagnose_usage[] = { + N_("git diagnose [-o|--output-directory ] [-s|--suffix ] [--mode=]"), + NULL +}; + +int cmd_diagnose(int argc, const char **argv, const char *prefix) +{ + struct strbuf zip_path = STRBUF_INIT; + time_t now = time(NULL); + struct tm tm; + enum diagnose_mode mode = DIAGNOSE_STATS; + char *option_output = NULL; + char *option_suffix = "%Y-%m-%d-%H%M"; + char *prefixed_filename; + + const struct option diagnose_options[] = { + OPT_STRING('o', "output-directory", &option_output, N_("path"), + N_("specify a destination for the diagnostics archive")), + OPT_STRING('s', "suffix", &option_suffix, N_("format"), + N_("specify a strftime format suffix for the filename")), + OPT_CALLBACK_F(0, "mode", &mode, "(stats|all)", + N_("specify the content of the diagnostic archive"), + PARSE_OPT_NONEG, option_parse_diagnose), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, diagnose_options, + diagnose_usage, 0); + + /* Prepare the path to put the result */ + prefixed_filename = prefix_filename(prefix, + option_output ? option_output : ""); + strbuf_addstr(&zip_path, prefixed_filename); + strbuf_complete(&zip_path, '/'); + + strbuf_addstr(&zip_path, "git-diagnostics-"); + strbuf_addftime(&zip_path, option_suffix, localtime_r(&now, &tm), 0, 0); + strbuf_addstr(&zip_path, ".zip"); + + switch (safe_create_leading_directories(zip_path.buf)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die_errno(_("could not create leading directories for '%s'"), + zip_path.buf); + } + + /* Prepare diagnostics */ + if (create_diagnostics_archive(&zip_path, mode)) + die_errno(_("unable to create diagnostics archive %s"), + zip_path.buf); + + free(prefixed_filename); + strbuf_release(&zip_path); + return 0; +} diff --git a/builtin/difftool.c b/builtin/difftool.c index b3c509b8de..4b10ad1a36 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -125,10 +125,10 @@ struct working_tree_entry { char path[FLEX_ARRAY]; }; -static int working_tree_entry_cmp(const void *unused_cmp_data, +static int working_tree_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct working_tree_entry *a, *b; @@ -148,10 +148,10 @@ struct pair_entry { const char path[FLEX_ARRAY]; }; -static int pair_cmp(const void *unused_cmp_data, +static int pair_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct pair_entry *a, *b; @@ -184,7 +184,7 @@ struct path_entry { char path[FLEX_ARRAY]; }; -static int path_entry_cmp(const void *unused_cmp_data, +static int path_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *key) @@ -716,7 +716,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) symlinks = has_symlinks; argc = parse_options(argc, argv, prefix, builtin_difftool_options, - builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); if (tool_help) diff --git a/builtin/env--helper.c b/builtin/env--helper.c index 27349098b0..ea04c16636 100644 --- a/builtin/env--helper.c +++ b/builtin/env--helper.c @@ -50,7 +50,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, opts, env__helper_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (env_default && !*env_default) usage_with_options(env__helper_usage, opts); if (!cmdmode) diff --git a/builtin/fast-export.c b/builtin/fast-export.c index e1748fb98b..3b3314e7b2 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -119,7 +119,7 @@ struct anonymized_entry_key { size_t orig_len; }; -static int anonymized_entry_cmp(const void *unused_cmp_data, +static int anonymized_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -1221,7 +1221,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) revs.sources = &revision_sources; revs.rewrite_parents = 1; argc = parse_options(argc, argv, prefix, options, fast_export_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); argc = setup_revisions(argc, argv, &revs, NULL); if (argc > 1) usage_with_options (fast_export_usage, options); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 14113cfd82..7134683ab9 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -46,7 +46,7 @@ struct object_entry { depth : DEPTH_BITS; }; -static int object_entry_hashcmp(const void *map_data, +static int object_entry_hashcmp(const void *map_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index f045bbbe94..afe679368d 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -62,6 +62,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) packet_trace_identity("fetch-pack"); memset(&args, 0, sizeof(args)); + list_objects_filter_init(&args.filter_options); args.uploadpack = "git-upload-pack"; for (i = 1; i < argc && *argv[i] == '-'; i++) { diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae..a0fca93bb6 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -80,7 +80,7 @@ static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; static struct refspec refmap = REFSPEC_INIT_FETCH; -static struct list_objects_filter_options filter_options; +static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; static int fetch_write_commit_graph = -1; @@ -301,7 +301,7 @@ struct refname_hash_entry { char refname[FLEX_ARRAY]; }; -static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data, +static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -329,7 +329,7 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map, static int add_one_refname(const char *refname, const struct object_id *oid, - int flag, void *cbdata) + int flag UNUSED, void *cbdata) { struct hashmap *refname_map = cbdata; @@ -490,7 +490,9 @@ static void filter_prefetch_refspec(struct refspec *rs) continue; if (!rs->items[i].dst || (rs->items[i].src && - !strncmp(rs->items[i].src, "refs/tags/", 10))) { + !strncmp(rs->items[i].src, + ref_namespace[NAMESPACE_TAGS].ref, + strlen(ref_namespace[NAMESPACE_TAGS].ref)))) { int j; free(rs->items[i].src); @@ -506,7 +508,7 @@ static void filter_prefetch_refspec(struct refspec *rs) } old_dst = rs->items[i].dst; - strbuf_addstr(&new_dst, "refs/prefetch/"); + strbuf_addstr(&new_dst, ref_namespace[NAMESPACE_PREFETCH].ref); /* * If old_dst starts with "refs/", then place @@ -881,11 +883,9 @@ static void format_display(struct strbuf *display, char code, static int update_local_ref(struct ref *ref, struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, - struct strbuf *display, int summary_width, - struct worktree **worktrees) + struct strbuf *display, int summary_width) { struct commit *current = NULL, *updated; - const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,16 +900,14 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + _("can't fetch into checked-out branch"), remote, pretty_ref, summary_width); return 1; } @@ -1110,10 +1108,10 @@ N_("it took %.2f seconds to check forced updates; you can use\n" static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, struct worktree **worktrees) + struct fetch_head *fetch_head) { int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; char *url; @@ -1240,8 +1238,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { rc |= update_local_ref(ref, transaction, what, - rm, ¬e, summary_width, - worktrees); + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1281,7 +1278,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); - strbuf_release(&err); free(url); return rc; } @@ -1332,8 +1328,7 @@ static int check_exist_and_connected(struct ref *ref_map) static int fetch_and_consume_refs(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int connectivity_checked = 1; int ret; @@ -1356,7 +1351,7 @@ static int fetch_and_consume_refs(struct transport *transport, trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, transaction, ref_map, - fetch_head, worktrees); + fetch_head); trace2_region_leave("fetch", "consume_refs", the_repository); out: @@ -1434,19 +1429,16 @@ cleanup: return result; } -static void check_not_current_branch(struct ref *ref_map, - struct worktree **worktrees) +static void check_not_current_branch(struct ref *ref_map) { - const struct worktree *wt; + const char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + (path = branch_checked_out(ref_map->peer_ref->name))) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) @@ -1472,8 +1464,9 @@ static void set_option(struct transport *transport, const char *name, const char } -static int add_oid(const char *refname, const struct object_id *oid, int flags, - void *cb_data) +static int add_oid(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct oid_array *oids = cb_data; @@ -1549,8 +1542,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) static int backfill_tags(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int retcode, cannot_reuse; @@ -1571,7 +1563,7 @@ static int backfill_tags(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); + retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head); if (gsecondary) { transport_disconnect(gsecondary); @@ -1592,7 +1584,6 @@ static int do_fetch(struct transport *transport, struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; - struct worktree **worktrees = get_worktrees(); struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; @@ -1626,9 +1617,21 @@ static int do_fetch(struct transport *transport, break; } } - } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, - &transport_ls_refs_options.ref_prefixes); + } else { + struct branch *branch = branch_get(NULL); + + if (transport->remote->fetch.nr) + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); + if (branch_has_merge_config(branch) && + !strcmp(branch->remote_name, transport->remote->name)) { + int i; + for (i = 0; i < branch->merge_nr; i++) { + strvec_push(&transport_ls_refs_options.ref_prefixes, + branch->merge[i]->src); + } + } + } if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; @@ -1650,7 +1653,7 @@ static int do_fetch(struct transport *transport, ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map, worktrees); + check_not_current_branch(ref_map); retcode = open_fetch_head(&fetch_head); if (retcode) @@ -1683,7 +1686,7 @@ static int do_fetch(struct transport *transport, retcode = 1; } - if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { + if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head)) { retcode = 1; goto cleanup; } @@ -1706,7 +1709,7 @@ static int do_fetch(struct transport *transport, * the transaction and don't commit anything. */ if (backfill_tags(transport, transaction, tags_ref_map, - &fetch_head, worktrees)) + &fetch_head)) retcode = 1; } @@ -1791,7 +1794,6 @@ cleanup: close_fetch_head(&fetch_head); strbuf_release(&err); free_refs(ref_map); - free_worktrees(worktrees); return retcode; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 6c73092f10..41acbc229e 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -228,6 +228,8 @@ static void mark_unreachable_referents(const struct object_id *oid) options.walk = mark_used; fsck_walk(obj, NULL, &options); + if (obj->type == OBJ_TREE) + free_tree_buffer((struct tree *)obj); } static int mark_loose_unreachable_referents(const struct object_id *oid, @@ -437,9 +439,6 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) out: if (obj->type == OBJ_TREE) free_tree_buffer((struct tree *)obj); - if (obj->type == OBJ_COMMIT) - free_commit_buffer(the_repository->parsed_objects, - (struct commit *)obj); return err; } @@ -488,8 +487,9 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, } static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) + const char *email UNUSED, + timestamp_t timestamp, int tz UNUSED, + const char *message UNUSED, void *cb_data) { const char *refname = cb_data; @@ -502,8 +502,9 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid return 0; } -static int fsck_handle_reflog(const char *logname, const struct object_id *oid, - int flag, void *cb_data) +static int fsck_handle_reflog(const char *logname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb_data) { struct strbuf refname = STRBUF_INIT; @@ -514,7 +515,7 @@ static int fsck_handle_reflog(const char *logname, const struct object_id *oid, } static int fsck_handle_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object *obj; @@ -851,6 +852,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) errors_found = 0; read_replace_refs = 0; + save_commit_buffer = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c index 2c109cf8b3..c69da93ece 100644 --- a/builtin/fsmonitor--daemon.c +++ b/builtin/fsmonitor--daemon.c @@ -13,8 +13,8 @@ static const char * const builtin_fsmonitor__daemon_usage[] = { N_("git fsmonitor--daemon start []"), N_("git fsmonitor--daemon run []"), - N_("git fsmonitor--daemon stop"), - N_("git fsmonitor--daemon status"), + "git fsmonitor--daemon stop", + "git fsmonitor--daemon status", NULL }; diff --git a/builtin/gc.c b/builtin/gc.c index 021e9256ae..2753bd15a5 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -168,9 +168,15 @@ struct maintenance_run_opts; static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) { struct strvec pack_refs_cmd = STRVEC_INIT; + int ret; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); - return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); + ret = run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); + + strvec_clear(&pack_refs_cmd); + + return ret; } static int too_many_loose_objects(void) @@ -776,8 +782,9 @@ struct cg_auto_data { int limit; }; -static int dfs_on_ref(const char *refname, - const struct object_id *oid, int flags, +static int dfs_on_ref(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct cg_auto_data *data = (struct cg_auto_data *)cb_data; @@ -904,12 +911,6 @@ static int fetch_remote(struct remote *remote, void *cbdata) static int maintenance_task_prefetch(struct maintenance_run_opts *opts) { - git_config_set_multivar_gently("log.excludedecoration", - "refs/prefetch/", - "refs/prefetch/", - CONFIG_FLAGS_FIXED_VALUE | - CONFIG_FLAGS_MULTI_REPLACE); - if (for_each_remote(fetch_remote, opts)) { error(_("failed to prefetch remotes")); return 1; @@ -1459,14 +1460,28 @@ static char *get_maintpath(void) return strbuf_detach(&sb, NULL); } -static int maintenance_register(void) +static char const * const builtin_maintenance_register_usage[] = { + "git maintenance register", + NULL +}; + +static int maintenance_register(int argc, const char **argv, const char *prefix) { + struct option options[] = { + OPT_END(), + }; int rc; char *config_value; struct child_process config_set = CHILD_PROCESS_INIT; struct child_process config_get = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_register_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_register_usage, + options); + /* Disable foreground maintenance */ git_config_set("maintenance.auto", "false"); @@ -1503,12 +1518,26 @@ done: return rc; } -static int maintenance_unregister(void) +static char const * const builtin_maintenance_unregister_usage[] = { + "git maintenance unregister", + NULL +}; + +static int maintenance_unregister(int argc, const char **argv, const char *prefix) { + struct option options[] = { + OPT_END(), + }; int rc; struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_unregister_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_unregister_usage, + options); + config_unset.git_cmd = 1; strvec_pushl(&config_unset.args, "config", "--global", "--unset", "--fixed-value", "maintenance.repo", maintpath, NULL); @@ -2059,6 +2088,7 @@ static int crontab_update_schedule(int run_maintenance, int fd) struct child_process crontab_edit = CHILD_PROCESS_INIT; FILE *cron_list, *cron_in; struct strbuf line = STRBUF_INIT; + struct tempfile *tmpedit = NULL; get_schedule_cmd(&cmd, NULL); strvec_split(&crontab_list.args, cmd); @@ -2073,6 +2103,17 @@ static int crontab_update_schedule(int run_maintenance, int fd) /* Ignore exit code, as an empty crontab will return error. */ finish_command(&crontab_list); + tmpedit = mks_tempfile_t(".git_cron_edit_tmpXXXXXX"); + if (!tmpedit) { + result = error(_("failed to create crontab temporary file")); + goto out; + } + cron_in = fdopen_tempfile(tmpedit, "w"); + if (!cron_in) { + result = error(_("failed to open temporary file")); + goto out; + } + /* * Read from the .lock file, filtering out the old * schedule while appending the new schedule. @@ -2080,19 +2121,6 @@ static int crontab_update_schedule(int run_maintenance, int fd) cron_list = fdopen(fd, "r"); rewind(cron_list); - strvec_split(&crontab_edit.args, cmd); - crontab_edit.in = -1; - crontab_edit.git_cmd = 0; - - if (start_command(&crontab_edit)) - return error(_("failed to run 'crontab'; your system might not support 'cron'")); - - cron_in = fdopen(crontab_edit.in, "w"); - if (!cron_in) { - result = error(_("failed to open stdin of 'crontab'")); - goto done_editing; - } - while (!strbuf_getline_lf(&line, cron_list)) { if (!in_old_region && !strcmp(line.buf, BEGIN_LINE)) in_old_region = 1; @@ -2126,14 +2154,22 @@ static int crontab_update_schedule(int run_maintenance, int fd) } fflush(cron_in); - fclose(cron_in); - close(crontab_edit.in); -done_editing: + strvec_split(&crontab_edit.args, cmd); + strvec_push(&crontab_edit.args, get_tempfile_path(tmpedit)); + crontab_edit.git_cmd = 0; + + if (start_command(&crontab_edit)) { + result = error(_("failed to run 'crontab'; your system might not support 'cron'")); + goto out; + } + if (finish_command(&crontab_edit)) result = error(_("'crontab' died")); else fclose(cron_list); +out: + delete_tempfile(&tmpedit); return result; } @@ -2490,6 +2526,7 @@ static int maintenance_start(int argc, const char **argv, const char *prefix) PARSE_OPT_NONEG, maintenance_opt_scheduler), OPT_END() }; + const char *register_args[] = { "register", NULL }; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_start_usage, 0); @@ -2499,34 +2536,46 @@ static int maintenance_start(int argc, const char **argv, const char *prefix) opts.scheduler = resolve_scheduler(opts.scheduler); validate_scheduler(opts.scheduler); - if (maintenance_register()) + if (maintenance_register(ARRAY_SIZE(register_args)-1, register_args, NULL)) warning(_("failed to add repo to global config")); return update_background_schedule(&opts, 1); } -static int maintenance_stop(void) +static const char *const builtin_maintenance_stop_usage[] = { + "git maintenance stop", + NULL +}; + +static int maintenance_stop(int argc, const char **argv, const char *prefix) { + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_stop_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_stop_usage, options); return update_background_schedule(NULL, 0); } -static const char builtin_maintenance_usage[] = N_("git maintenance []"); +static const char * const builtin_maintenance_usage[] = { + N_("git maintenance []"), + NULL, +}; int cmd_maintenance(int argc, const char **argv, const char *prefix) { - if (argc < 2 || - (argc == 2 && !strcmp(argv[1], "-h"))) - usage(builtin_maintenance_usage); - - if (!strcmp(argv[1], "run")) - return maintenance_run(argc - 1, argv + 1, prefix); - if (!strcmp(argv[1], "start")) - return maintenance_start(argc - 1, argv + 1, prefix); - if (!strcmp(argv[1], "stop")) - return maintenance_stop(); - if (!strcmp(argv[1], "register")) - return maintenance_register(); - if (!strcmp(argv[1], "unregister")) - return maintenance_unregister(); - - die(_("invalid subcommand: %s"), argv[1]); + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_maintenance_options[] = { + OPT_SUBCOMMAND("run", &fn, maintenance_run), + OPT_SUBCOMMAND("start", &fn, maintenance_start), + OPT_SUBCOMMAND("stop", &fn, maintenance_stop), + OPT_SUBCOMMAND("register", &fn, maintenance_register), + OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, builtin_maintenance_options, + builtin_maintenance_usage, 0); + return fn(argc, argv, prefix); } diff --git a/builtin/grep.c b/builtin/grep.c index bcb07ea7f7..e6bcdf860c 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)"), PARSE_OPT_NOCOMPLETE), + OPT_INTEGER('m', "max-count", &opt.max_count, + N_("maximum number of results per file")), OPT_END() }; grep_prefix = prefix; @@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (recurse_submodules && untracked) die(_("--untracked not supported with --recurse-submodules")); + /* + * Optimize out the case where the amount of matches is limited to zero. + * We do this to keep results consistent with GNU grep(1). + */ + if (opt.max_count == 0) + return 1; + if (show_in_pager) { if (num_threads > 1) warning(_("invalid option combination, ignoring --threads")); diff --git a/builtin/help.c b/builtin/help.c index 222f994f86..6f2796f211 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -43,6 +43,8 @@ static enum help_action { HELP_ACTION_ALL = 1, HELP_ACTION_GUIDES, HELP_ACTION_CONFIG, + HELP_ACTION_USER_INTERFACES, + HELP_ACTION_DEVELOPER_INTERFACES, HELP_ACTION_CONFIG_FOR_COMPLETION, HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, } cmd_mode; @@ -69,6 +71,12 @@ static struct option builtin_help_options[] = { OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), HELP_ACTION_GUIDES), + OPT_CMDMODE(0, "user-interfaces", &cmd_mode, + N_("print list of user-facing repository, command and file interfaces"), + HELP_ACTION_USER_INTERFACES), + OPT_CMDMODE(0, "developer-interfaces", &cmd_mode, + N_("print list of file formats, protocols and other developer interfaces"), + HELP_ACTION_DEVELOPER_INTERFACES), OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), HELP_ACTION_CONFIG), OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", @@ -81,9 +89,11 @@ static struct option builtin_help_options[] = { static const char * const builtin_help_usage[] = { "git help [-a|--all] [--[no-]verbose]] [--[no-]external-commands] [--[no-]aliases]", - N_("git help [[-i|--info] [-m|--man] [-w|--web]] []"), + N_("git help [[-i|--info] [-m|--man] [-w|--web]] [|]"), "git help [-g|--guides]", "git help [-c|--config]", + "git help [--user-interfaces]", + "git help [--developer-interfaces]", NULL }; @@ -430,6 +440,8 @@ static const char *cmd_to_page(const char *git_cmd) return git_cmd; else if (is_git_command(git_cmd)) return xstrfmt("git-%s", git_cmd); + else if (!strcmp("scalar", git_cmd)) + return xstrdup(git_cmd); else return xstrfmt("git%s", git_cmd); } @@ -654,6 +666,14 @@ int cmd_help(int argc, const char **argv, const char *prefix) opt_mode_usage(argc, "--config-for-completion", help_format); list_config_help(SHOW_CONFIG_VARS); return 0; + case HELP_ACTION_USER_INTERFACES: + opt_mode_usage(argc, "--user-interfaces", help_format); + list_user_interfaces_help(); + return 0; + case HELP_ACTION_DEVELOPER_INTERFACES: + opt_mode_usage(argc, "--developer-interfaces", help_format); + list_developer_interfaces_help(); + return 0; case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: opt_mode_usage(argc, "--config-sections-for-completion", help_format); diff --git a/builtin/hook.c b/builtin/hook.c index 54e5c6ec93..b6530d189a 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -67,18 +67,14 @@ usage: int cmd_hook(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option builtin_hook_options[] = { + OPT_SUBCOMMAND("run", &fn, run), OPT_END(), }; argc = parse_options(argc, argv, NULL, builtin_hook_options, - builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!argc) - goto usage; + builtin_hook_usage, 0); - if (!strcmp(argv[0], "run")) - return run(argc, argv, prefix); - -usage: - usage_with_options(builtin_hook_usage, builtin_hook_options); + return fn(argc, argv, prefix); } diff --git a/builtin/log.c b/builtin/log.c index 88a5e98875..ee19dc5d45 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -52,6 +52,7 @@ 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 const char *fmt_patch_subject_prefix = "PATCH"; static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; static const char *fmt_pretty; @@ -101,6 +102,20 @@ static int parse_decoration_style(const char *value) return -1; } +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; +static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; + +static int clear_decorations_callback(const struct option *opt, + const char *arg, int unset) +{ + string_list_clear(&decorate_refs_include, 0); + string_list_clear(&decorate_refs_exclude, 0); + use_default_decoration_filter = 0; + return 0; +} + static int decorate_callback(const struct option *opt, const char *arg, int unset) { if (unset) @@ -162,18 +177,61 @@ static void cmd_log_init_defaults(struct rev_info *rev) parse_date_format(default_date_mode, &rev->date_mode); } +static void set_default_decoration_filter(struct decoration_filter *decoration_filter) +{ + int i; + char *value = NULL; + struct string_list *include = decoration_filter->include_ref_pattern; + const struct string_list *config_exclude = + git_config_get_value_multi("log.excludeDecoration"); + + if (config_exclude) { + struct string_list_item *item; + for_each_string_list_item(item, config_exclude) + string_list_append(decoration_filter->exclude_ref_config_pattern, + item->string); + } + + /* + * By default, decorate_all is disabled. Enable it if + * log.initialDecorationSet=all. Don't ever disable it by config, + * since the command-line takes precedent. + */ + if (use_default_decoration_filter && + !git_config_get_string("log.initialdecorationset", &value) && + !strcmp("all", value)) + use_default_decoration_filter = 0; + free(value); + + if (!use_default_decoration_filter || + decoration_filter->exclude_ref_pattern->nr || + decoration_filter->include_ref_pattern->nr || + decoration_filter->exclude_ref_config_pattern->nr) + return; + + /* + * No command-line or config options were given, so + * populate with sensible defaults. + */ + for (i = 0; i < ARRAY_SIZE(ref_namespace); i++) { + if (!ref_namespace[i].decoration) + continue; + + string_list_append(include, ref_namespace[i].ref); + } +} + static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; int quiet = 0, source = 0, mailmap; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; - static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; - static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; - static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; - struct decoration_filter decoration_filter = {&decorate_refs_include, - &decorate_refs_exclude, - &decorate_refs_exclude_config}; + struct decoration_filter decoration_filter = { + .exclude_ref_pattern = &decorate_refs_exclude, + .include_ref_pattern = &decorate_refs_include, + .exclude_ref_config_pattern = &decorate_refs_exclude_config, + }; static struct revision_sources revision_sources; const struct option builtin_log_options[] = { @@ -181,6 +239,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")), OPT_ALIAS(0, "mailmap", "use-mailmap"), + OPT_CALLBACK_F(0, "clear-decorations", NULL, NULL, + N_("clear all previously-defined decoration filters"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + clear_decorations_callback), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match ")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, @@ -199,7 +261,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); if (quiet) @@ -265,16 +327,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } if (decoration_style || rev->simplify_by_decoration) { - const struct string_list *config_exclude = - repo_config_get_value_multi(the_repository, - "log.excludeDecoration"); - - if (config_exclude) { - struct string_list_item *item; - for_each_string_list_item(item, config_exclude) - string_list_append(&decorate_refs_exclude_config, - item->string); - } + set_default_decoration_filter(&decoration_filter); if (decoration_style) rev->show_decorations = 1; @@ -645,9 +698,10 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) return 0; } -static int show_tree_object(const struct object_id *oid, - struct strbuf *base, - const char *pathname, unsigned mode, void *context) +static int show_tree_object(const struct object_id *oid UNUSED, + struct strbuf *base UNUSED, + const char *pathname, unsigned mode, + void *context) { FILE *file = context; fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); @@ -668,10 +722,10 @@ static void show_setup_revisions_tweak(struct rev_info *rev, int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; - struct object_array_entry *objects; + unsigned int i; struct setup_revision_opt opt; struct pathspec match_all; - int i, count, ret = 0; + int ret = 0; init_log_defaults(); git_config(git_log_config, NULL); @@ -698,12 +752,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (!rev.no_walk) return cmd_log_deinit(cmd_log_walk(&rev), &rev); - count = rev.pending.nr; - objects = rev.pending.objects; rev.diffopt.no_free = 1; - for (i = 0; i < count && !ret; i++) { - struct object *o = objects[i].item; - const char *name = objects[i].name; + for (i = 0; i < rev.pending.nr && !ret; i++) { + struct object *o = rev.pending.objects[i].item; + const char *name = rev.pending.objects[i].name; switch (o->type) { case OBJ_BLOB: ret = show_blob_object(&o->oid, &rev, name); @@ -726,7 +778,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) if (!o) ret = error(_("could not read object %s"), oid_to_hex(oid)); - objects[i].item = o; + rev.pending.objects[i].item = o; i--; break; } @@ -743,11 +795,24 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.shown_one = 1; break; case OBJ_COMMIT: - rev.pending.nr = rev.pending.alloc = 0; - rev.pending.objects = NULL; + { + struct object_array old; + struct object_array blank = OBJECT_ARRAY_INIT; + + memcpy(&old, &rev.pending, sizeof(old)); + memcpy(&rev.pending, &blank, sizeof(rev.pending)); + add_object_array(o, name, &rev.pending); ret = cmd_log_walk_no_free(&rev); + + /* + * No need for + * object_array_clear(&pending). It was + * cleared already in prepare_revision_walk() + */ + memcpy(&rev.pending, &old, sizeof(rev.pending)); break; + } default: ret = error(_("unknown type: %d"), o->type); } @@ -995,6 +1060,10 @@ static int git_format_config(const char *var, const char *value, void *cb) from = NULL; return 0; } + if (!strcmp(var, "format.forceinbodyfrom")) { + force_in_body_from = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "format.notes")) { int b = git_parse_maybe_bool(value); if (b < 0) @@ -1886,6 +1955,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show changes against in cover letter or single patch")), OPT_INTEGER(0, "creation-factor", &creation_factor, N_("percentage by which creation is weighted")), + OPT_BOOL(0, "force-in-body-from", &force_in_body_from, + N_("show in-body From: even if identical to the e-mail header")), OPT_END() }; @@ -1926,9 +1997,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) */ argc = parse_options(argc, argv, prefix, builtin_format_patch_options, builtin_format_patch_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); + rev.force_in_body_from = force_in_body_from; + /* 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); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index e791b65e7e..4cf8a23648 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -11,6 +11,7 @@ #include "quote.h" #include "dir.h" #include "builtin.h" +#include "strbuf.h" #include "tree.h" #include "cache-tree.h" #include "parse-options.h" @@ -48,6 +49,7 @@ static char *ps_matched; static const char *with_tree; static int exc_given; static int exclude_args; +static const char *format; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -85,6 +87,16 @@ static void write_name(const char *name) stdout, line_terminator); } +static void write_name_to_buf(struct strbuf *sb, const char *name) +{ + const char *rel = relative_path(name, prefix_len ? prefix : NULL, sb); + + if (line_terminator) + quote_c_style(rel, sb, NULL, 0); + else + strbuf_addstr(sb, rel); +} + static const char *get_tag(const struct cache_entry *ce, const char *tag) { static char alttag[4]; @@ -222,6 +234,73 @@ static void show_submodule(struct repository *superproject, repo_clear(&subrepo); } +struct show_index_data { + const char *pathname; + struct index_state *istate; + const struct cache_entry *ce; +}; + +static size_t expand_show_index(struct strbuf *sb, const char *start, + void *context) +{ + struct show_index_data *data = context; + const char *end; + const char *p; + size_t len = strbuf_expand_literal_cb(sb, start, NULL); + struct stat st; + + if (len) + return len; + if (*start != '(') + die(_("bad ls-files format: element '%s' " + "does not start with '('"), start); + + end = strchr(start + 1, ')'); + if (!end) + die(_("bad ls-files format: element '%s' " + "does not end in ')'"), start); + + len = end - start + 1; + if (skip_prefix(start, "(objectmode)", &p)) + strbuf_addf(sb, "%06o", data->ce->ce_mode); + else if (skip_prefix(start, "(objectname)", &p)) + strbuf_add_unique_abbrev(sb, &data->ce->oid, abbrev); + else if (skip_prefix(start, "(stage)", &p)) + strbuf_addf(sb, "%d", ce_stage(data->ce)); + else if (skip_prefix(start, "(eolinfo:index)", &p)) + strbuf_addstr(sb, S_ISREG(data->ce->ce_mode) ? + get_cached_convert_stats_ascii(data->istate, + data->ce->name) : ""); + else if (skip_prefix(start, "(eolinfo:worktree)", &p)) + strbuf_addstr(sb, !lstat(data->pathname, &st) && + S_ISREG(st.st_mode) ? + get_wt_convert_stats_ascii(data->pathname) : ""); + else if (skip_prefix(start, "(eolattr)", &p)) + strbuf_addstr(sb, get_convert_attr_ascii(data->istate, + data->pathname)); + else if (skip_prefix(start, "(path)", &p)) + write_name_to_buf(sb, data->pathname); + else + die(_("bad ls-files format: %%%.*s"), (int)len, start); + + return len; +} + +static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce, + const char *format, const char *fullname) { + struct show_index_data data = { + .pathname = fullname, + .istate = repo->index, + .ce = ce, + }; + struct strbuf sb = STRBUF_INIT; + + strbuf_expand(&sb, format, expand_show_index, &data); + strbuf_addch(&sb, line_terminator); + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); +} + static void show_ce(struct repository *repo, struct dir_struct *dir, const struct cache_entry *ce, const char *fullname, const char *tag) @@ -236,6 +315,12 @@ static void show_ce(struct repository *repo, struct dir_struct *dir, max_prefix_len, ps_matched, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { + if (format) { + show_ce_fmt(repo, ce, format, fullname); + print_debug(ce); + return; + } + tag = get_tag(ce, tag); if (!show_stage) { @@ -675,6 +760,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("suppress duplicate entries")), OPT_BOOL(0, "sparse", &show_sparse_dirs, N_("show sparse directories in the presence of a sparse index")), + OPT_STRING_F(0, "format", &format, N_("format"), + N_("format to use for the output"), + PARSE_OPT_NONEG), OPT_END() }; int ret = 0; @@ -699,6 +787,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) for (i = 0; i < exclude_list.nr; i++) { add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args); } + + if (format && (show_stage || show_others || show_killed || + show_resolve_undo || skipping_duplicates || show_eol || show_tag)) + usage_msg_opt(_("--format cannot be used with -s, -o, -k, -t, " + "--resolve-undo, --deduplicate, --eol"), + ls_files_usage, builtin_ls_files_options); + if (show_tag || show_valid_bit || show_fsmonitor_bit) { tag_cached = "H "; tag_unmerged = "M "; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index e279be8bb6..c3ea09281a 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -142,7 +142,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname } static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, void *context UNUSED) { size_t baselen; int recurse = 0; @@ -213,7 +213,7 @@ static void show_tree_common_default_long(struct strbuf *base, static int show_tree_default(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, - void *context) + void *context UNUSED) { int early; int recurse; @@ -230,7 +230,8 @@ static int show_tree_default(const struct object_id *oid, struct strbuf *base, } static int show_tree_long(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; @@ -259,7 +260,8 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base, } static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; @@ -279,7 +281,8 @@ static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, } static int show_tree_object(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) + const char *pathname, unsigned mode, + void *context UNUSED) { int early; int recurse; diff --git a/builtin/merge-file.c b/builtin/merge-file.c index e695867ee5..c923bbf2ab 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -25,10 +25,10 @@ static int label_cb(const struct option *opt, const char *arg, int unset) int cmd_merge_file(int argc, const char **argv, const char *prefix) { - const char *names[3] = { NULL, NULL, NULL }; - mmfile_t mmfs[3]; - mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{0}}; + const char *names[3] = { 0 }; + mmfile_t mmfs[3] = { 0 }; + mmbuffer_t result = { 0 }; + xmparam_t xmp = { 0 }; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; struct option options[] = { @@ -71,21 +71,24 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) for (i = 0; i < 3; i++) { char *fname; - int ret; + mmfile_t *mmf = mmfs + i; if (!names[i]) names[i] = argv[i]; fname = prefix_filename(prefix, argv[i]); - ret = read_mmfile(mmfs + i, fname); + + if (read_mmfile(mmf, fname)) + ret = -1; + else if (mmf->size > MAX_XDIFF_SIZE || + buffer_is_binary(mmf->ptr, mmf->size)) + ret = error("Cannot merge binary files: %s", + argv[i]); + free(fname); if (ret) - return -1; + goto cleanup; - if (mmfs[i].size > MAX_XDIFF_SIZE || - buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s", - argv[i]); } xmp.ancestor = names[1]; @@ -93,9 +96,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmp.file2 = names[2]; ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result); - for (i = 0; i < 3; i++) - free(mmfs[i].ptr); - if (ret >= 0) { const char *filename = argv[0]; char *fpath = prefix_filename(prefix, argv[0]); @@ -116,5 +116,9 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret > 127) ret = 127; +cleanup: + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + return ret; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 5dc94d6f88..ae5782917b 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -2,13 +2,18 @@ #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" +#include "parse-options.h" #include "repository.h" #include "blob.h" #include "exec-cmd.h" #include "merge-blobs.h" +#include "quote.h" -static const char merge_tree_usage[] = "git merge-tree "; +static int line_termination = '\n'; struct merge_list { struct merge_list *next; @@ -28,7 +33,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void trivial_merge_trees(struct tree_desc t[3], const char *base); static const char *explanation(struct merge_list *entry) { @@ -225,7 +230,7 @@ static void unresolved_directory(const struct traverse_info *info, buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID - merge_trees(t, newbase); + trivial_merge_trees(t, newbase); free(buf0); free(buf1); @@ -342,7 +347,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void trivial_merge_trees(struct tree_desc t[3], const char *base) { struct traverse_info info; @@ -366,19 +371,18 @@ static void *get_tree_descriptor(struct repository *r, return buf; } -int cmd_merge_tree(int argc, const char **argv, const char *prefix) +static int trivial_merge(const char *base, + const char *branch1, + const char *branch2) { struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc != 4) - usage(merge_tree_usage); - - buf1 = get_tree_descriptor(r, t+0, argv[1]); - buf2 = get_tree_descriptor(r, t+1, argv[2]); - buf3 = get_tree_descriptor(r, t+2, argv[3]); - merge_trees(t, ""); + buf1 = get_tree_descriptor(r, t+0, base); + buf2 = get_tree_descriptor(r, t+1, branch1); + buf3 = get_tree_descriptor(r, t+2, branch2); + trivial_merge_trees(t, ""); free(buf1); free(buf2); free(buf3); @@ -386,3 +390,162 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) show_result(); return 0; } + +enum mode { + MODE_UNKNOWN, + MODE_TRIVIAL, + MODE_REAL, +}; + +struct merge_tree_options { + int mode; + int allow_unrelated_histories; + int show_messages; + int name_only; +}; + +static int real_merge(struct merge_tree_options *o, + const char *branch1, const char *branch2, + const char *prefix) +{ + struct commit *parent1, *parent2; + struct commit_list *merge_bases = NULL; + struct merge_options opt; + struct merge_result result = { 0 }; + + parent1 = get_merge_parent(branch1); + if (!parent1) + help_unknown_ref(branch1, "merge-tree", + _("not something we can merge")); + + parent2 = get_merge_parent(branch2); + if (!parent2) + help_unknown_ref(branch2, "merge-tree", + _("not something we can merge")); + + init_merge_options(&opt, the_repository); + + opt.show_rename_progress = 0; + + opt.branch1 = branch1; + opt.branch2 = branch2; + + /* + * Get the merge bases, in reverse order; see comment above + * merge_incore_recursive in merge-ort.h + */ + merge_bases = get_merge_bases(parent1, parent2); + if (!merge_bases && !o->allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + merge_bases = reverse_commit_list(merge_bases); + + merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result); + if (result.clean < 0) + die(_("failure to merge")); + + if (o->show_messages == -1) + o->show_messages = !result.clean; + + printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination); + if (!result.clean) { + struct string_list conflicted_files = STRING_LIST_INIT_NODUP; + const char *last = NULL; + int i; + + merge_get_conflicted_files(&result, &conflicted_files); + for (i = 0; i < conflicted_files.nr; i++) { + const char *name = conflicted_files.items[i].string; + struct stage_info *c = conflicted_files.items[i].util; + if (!o->name_only) + printf("%06o %s %d\t", + c->mode, oid_to_hex(&c->oid), c->stage); + else if (last && !strcmp(last, name)) + continue; + write_name_quoted_relative( + name, prefix, stdout, line_termination); + last = name; + } + string_list_clear(&conflicted_files, 1); + } + if (o->show_messages) { + putchar(line_termination); + merge_display_update_messages(&opt, line_termination == '\0', + &result); + } + merge_finalize(&opt, &result); + return !result.clean; /* result.clean < 0 handled above */ +} + +int cmd_merge_tree(int argc, const char **argv, const char *prefix) +{ + struct merge_tree_options o = { .show_messages = -1 }; + int expected_remaining_argc; + int original_argc; + + const char * const merge_tree_usage[] = { + N_("git merge-tree [--write-tree] [] "), + N_("git merge-tree [--trivial-merge] "), + NULL + }; + struct option mt_options[] = { + OPT_CMDMODE(0, "write-tree", &o.mode, + N_("do a real merge instead of a trivial merge"), + MODE_REAL), + OPT_CMDMODE(0, "trivial-merge", &o.mode, + N_("do a trivial merge only"), MODE_TRIVIAL), + OPT_BOOL(0, "messages", &o.show_messages, + N_("also show informational/conflict messages")), + OPT_SET_INT('z', NULL, &line_termination, + N_("separate paths with the NUL character"), '\0'), + OPT_BOOL_F(0, "name-only", + &o.name_only, + N_("list filenames without modes/oids/stages"), + PARSE_OPT_NONEG), + OPT_BOOL_F(0, "allow-unrelated-histories", + &o.allow_unrelated_histories, + N_("allow merging unrelated histories"), + PARSE_OPT_NONEG), + OPT_END() + }; + + /* Parse arguments */ + original_argc = argc - 1; /* ignoring argv[0] */ + argc = parse_options(argc, argv, prefix, mt_options, + merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION); + switch (o.mode) { + default: + BUG("unexpected command mode %d", o.mode); + case MODE_UNKNOWN: + switch (argc) { + default: + usage_with_options(merge_tree_usage, mt_options); + case 2: + o.mode = MODE_REAL; + break; + case 3: + o.mode = MODE_TRIVIAL; + break; + } + expected_remaining_argc = argc; + break; + case MODE_REAL: + expected_remaining_argc = 2; + break; + case MODE_TRIVIAL: + expected_remaining_argc = 3; + /* Removal of `--trivial-merge` is expected */ + original_argc--; + break; + } + if (o.mode == MODE_TRIVIAL && argc < original_argc) + die(_("--trivial-merge is incompatible with all other options")); + + if (argc != expected_remaining_argc) + usage_with_options(merge_tree_usage, mt_options); + + /* Do the relevant type of merge */ + if (o.mode == MODE_REAL) + return real_merge(&o, argv[0], argv[1], prefix); + else + return trivial_merge(argv[0], argv[1], argv[2]); +} diff --git a/builtin/merge.c b/builtin/merge.c index fcd87a2a09..5900b81729 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -313,8 +313,16 @@ static int save_state(struct object_id *stash) int len; struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; + int fd; int rc = -1; + fd = repo_hold_locked_index(the_repository, &lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + repo_update_index_if_able(the_repository, &lock_file); + rollback_lock_file(&lock_file); + strvec_pushl(&cp.args, "stash", "create", NULL); cp.out = -1; cp.git_cmd = 1; @@ -375,24 +383,26 @@ static void reset_hard(const struct object_id *oid, int verbose) static void restore_state(const struct object_id *head, const struct object_id *stash) { - struct strbuf sb = STRBUF_INIT; - const char *args[] = { "stash", "apply", NULL, NULL }; - - if (is_null_oid(stash)) - return; + struct strvec args = STRVEC_INIT; reset_hard(head, 1); - args[2] = oid_to_hex(stash); + if (is_null_oid(stash)) + goto refresh_cache; + + strvec_pushl(&args, "stash", "apply", "--index", "--quiet", NULL); + strvec_push(&args, oid_to_hex(stash)); /* * It is OK to ignore error here, for example when there was * nothing to restore. */ - run_command_v_opt(args, RUN_GIT_CMD); + run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); - strbuf_release(&sb); - refresh_cache(REFRESH_QUIET); +refresh_cache: + if (discard_cache() < 0 || read_cache() < 0) + die(_("could not read index")); } /* This is called when no merge was necessary. */ @@ -503,7 +513,6 @@ static void merge_name(const char *remote, struct strbuf *msg) { struct commit *remote_head; struct object_id branch_head; - struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; @@ -591,7 +600,6 @@ static void merge_name(const char *remote, struct strbuf *msg) oid_to_hex(&remote_head->object.oid), remote); cleanup: free(found_ref); - strbuf_release(&buf); strbuf_release(&bname); } @@ -759,8 +767,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, else clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); - if (clean < 0) - exit(128); + if (clean < 0) { + rollback_lock_file(&lock); + return 2; + } if (write_locked_index(&the_index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write %s"), get_index_file()); @@ -1604,6 +1614,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ refresh_cache(REFRESH_QUIET); if (allow_trivial && fast_forward != FF_ONLY) { + /* + * Must first ensure that index matches HEAD before + * attempting a trivial merge. + */ + struct tree *head_tree = get_commit_tree(head_commit); + struct strbuf sb = STRBUF_INIT; + + if (repo_index_has_changes(the_repository, head_tree, + &sb)) { + error(_("Your local changes to the following files would be overwritten by merge:\n %s"), + sb.buf); + strbuf_release(&sb); + return 2; + } + /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); @@ -1660,15 +1685,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * tree in the index -- this means that the index must be in * sync with the head commit. The strategies are responsible * to ensure this. + * + * Stash away the local changes so that we can try more than one + * and/or recover from merge strategies bailing while leaving the + * index and working tree polluted. */ - if (use_strategies_nr == 1 || - /* - * Stash away the local changes so that we can try more than one. - */ - save_state(&stash)) + if (save_state(&stash)) oidclr(&stash); - for (i = 0; !merge_was_ok && i < use_strategies_nr; i++) { + for (i = 0; i < use_strategies_nr; i++) { int ret, cnt; if (i) { printf(_("Rewinding the tree to pristine...\n")); @@ -1683,7 +1708,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ wt_strategy = use_strategies[i]->name; - ret = try_merge_strategy(use_strategies[i]->name, + ret = try_merge_strategy(wt_strategy, common, remoteheads, head_commit); /* @@ -1693,16 +1718,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ if (ret < 2) { if (!ret) { - if (option_commit) { - /* Automerge succeeded. */ - automerge_was_ok = 1; - break; - } + /* + * This strategy worked; no point in trying + * another. + */ merge_was_ok = 1; + best_strategy = wt_strategy; + break; } cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; if (best_cnt <= 0 || cnt <= best_cnt) { - best_strategy = use_strategies[i]->name; + best_strategy = wt_strategy; best_cnt = cnt; } } @@ -1712,7 +1738,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) { + if (merge_was_ok && option_commit) { + automerge_was_ok = 1; ret = finish_automerge(head_commit, head_subsumed, common, remoteheads, &result_tree, wt_strategy); diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5edbb7fe86..9b126d6ce0 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -78,7 +78,7 @@ static struct option *add_common_options(struct option *prev) } static int git_multi_pack_index_write_config(const char *var, const char *value, - void *cb) + void *cb UNUSED) { if (!strcmp(var, "pack.writebitmaphashcache")) { if (git_config_bool(var, value)) @@ -87,6 +87,13 @@ static int git_multi_pack_index_write_config(const char *var, const char *value, opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE; } + if (!strcmp(var, "pack.writebitmaplookuptable")) { + if (git_config_bool(var, value)) + opts.flags |= MIDX_WRITE_BITMAP_LOOKUP_TABLE; + else + opts.flags &= ~MIDX_WRITE_BITMAP_LOOKUP_TABLE; + } + /* * We should never make a fall-back call to 'git_default_config', since * this was already called in 'cmd_multi_pack_index()'. @@ -104,7 +111,8 @@ static void read_packs_from_stdin(struct string_list *to) strbuf_release(&buf); } -static int cmd_multi_pack_index_write(int argc, const char **argv) +static int cmd_multi_pack_index_write(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_write_options[] = { @@ -132,9 +140,9 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_write_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); @@ -160,7 +168,8 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) opts.refs_snapshot, opts.flags); } -static int cmd_multi_pack_index_verify(int argc, const char **argv) +static int cmd_multi_pack_index_verify(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_verify_options[] = { @@ -174,9 +183,9 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_verify_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_verify_usage, options); @@ -186,7 +195,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) return verify_midx_file(the_repository, opts.object_dir, opts.flags); } -static int cmd_multi_pack_index_expire(int argc, const char **argv) +static int cmd_multi_pack_index_expire(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_expire_options[] = { @@ -200,9 +210,9 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_expire_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_expire_usage, options); @@ -212,7 +222,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) return expire_midx_packs(the_repository, opts.object_dir, opts.flags); } -static int cmd_multi_pack_index_repack(int argc, const char **argv) +static int cmd_multi_pack_index_repack(int argc, const char **argv, + const char *prefix) { struct option *options; static struct option builtin_multi_pack_index_repack_options[] = { @@ -229,10 +240,10 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) if (isatty(2)) opts.flags |= MIDX_PROGRESS; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, options, builtin_multi_pack_index_repack_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_repack_usage, options); @@ -247,7 +258,15 @@ int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { int res; - struct option *builtin_multi_pack_index_options = common_opts; + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_multi_pack_index_options[] = { + OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack), + OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write), + OPT_SUBCOMMAND("verify", &fn, cmd_multi_pack_index_verify), + OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire), + OPT_END(), + }; + struct option *options = parse_options_concat(builtin_multi_pack_index_options, common_opts); git_config(git_default_config, NULL); @@ -256,31 +275,12 @@ int cmd_multi_pack_index(int argc, const char **argv, the_repository->objects->odb) opts.object_dir = xstrdup(the_repository->objects->odb->path); - argc = parse_options(argc, argv, prefix, - builtin_multi_pack_index_options, - builtin_multi_pack_index_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - - if (!argc) - goto usage; - - if (!strcmp(argv[0], "repack")) - res = cmd_multi_pack_index_repack(argc, argv); - else if (!strcmp(argv[0], "write")) - res = cmd_multi_pack_index_write(argc, argv); - else if (!strcmp(argv[0], "verify")) - res = cmd_multi_pack_index_verify(argc, argv); - else if (!strcmp(argv[0], "expire")) - res = cmd_multi_pack_index_expire(argc, argv); - else { - error(_("unrecognized subcommand: %s"), argv[0]); - goto usage; - } + argc = parse_options(argc, argv, prefix, options, + builtin_multi_pack_index_usage, 0); + FREE_AND_NULL(options); + + res = fn(argc, argv, prefix); free(opts.object_dir); return res; - -usage: - usage_with_options(builtin_multi_pack_index_usage, - builtin_multi_pack_index_options); } diff --git a/builtin/mv.c b/builtin/mv.c index 83a465ba83..3413ad1c9b 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -13,12 +13,20 @@ #include "string-list.h" #include "parse-options.h" #include "submodule.h" +#include "entry.h" static const char * const builtin_mv_usage[] = { N_("git mv [] ... "), NULL }; +enum update_mode { + WORKING_DIRECTORY = (1 << 1), + INDEX = (1 << 2), + SPARSE = (1 << 3), + SKIP_WORKTREE_DIR = (1 << 4), +}; + #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 @@ -63,7 +71,7 @@ static const char **internal_prefix_pathspec(const char *prefix, static const char *add_slash(const char *path) { size_t len = strlen(path); - if (path[len - 1] != '/') { + if (len && path[len - 1] != '/') { char *with_slash = xmalloc(st_add(len, 2)); memcpy(with_slash, path, len); with_slash[len++] = '/'; @@ -115,6 +123,39 @@ static int index_range_of_same_dir(const char *src, int length, return last - first; } +/* + * Given the path of a directory that does not exist on-disk, check whether the + * directory contains any entries in the index with the SKIP_WORKTREE flag + * enabled. + * Return 1 if such index entries exist. + * Return 0 otherwise. + */ +static int empty_dir_has_sparse_contents(const char *name) +{ + int ret = 0; + const char *with_slash = add_slash(name); + int length = strlen(with_slash); + + int pos = cache_name_pos(with_slash, length); + const struct cache_entry *ce; + + if (pos < 0) { + pos = -pos - 1; + if (pos >= the_index.cache_nr) + goto free_return; + ce = active_cache[pos]; + if (strncmp(with_slash, ce->name, length)) + goto free_return; + if (ce_skip_worktree(ce)) + ret = 1; + } + +free_return: + if (with_slash != name) + free((char *)with_slash); + return ret; +} + int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; @@ -129,12 +170,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; + 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; + enum update_mode *modes, dst_mode = 0; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; 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; git_config(git_default_config, NULL); @@ -148,7 +194,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); source = internal_prefix_pathspec(prefix, argv, argc, 0); - modes = xcalloc(argc, sizeof(enum update_mode)); + CALLOC_ARRAY(modes, argc); + /* * Keep trailing slash, needed to let * "git mv file no-such-dir/" error out, except in the case @@ -158,6 +205,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) 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 *)); if (dest_path[0][0] == '\0') @@ -165,18 +213,37 @@ int cmd_mv(int argc, const char **argv, const char *prefix) destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { - dest_path[0] = add_slash(dest_path[0]); - destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); } else { - if (argc != 1) + if (!path_in_sparse_checkout(dst_w_slash, &the_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]); - destination = dest_path; + } else { + destination = dest_path; + /* + * 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_index)) + dst_mode = SPARSE; + } + } + if (dst_w_slash != dest_path[0]) { + free((char *)dst_w_slash); + dst_w_slash = NULL; } /* Checking */ for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; - int length, src_is_dir; + int length; const char *bad = NULL; int skip_sparse = 0; @@ -185,54 +252,107 @@ int cmd_mv(int argc, const char **argv, const char *prefix) length = strlen(src); if (lstat(src, &st) < 0) { - /* only error if existence is expected. */ - if (modes[i] != SPARSE) + int pos; + const struct cache_entry *ce; + + pos = cache_name_pos(src, length); + if (pos < 0) { + const char *src_w_slash = add_slash(src); + if (!path_in_sparse_checkout(src_w_slash, &the_index) && + empty_dir_has_sparse_contents(src)) { + modes[i] |= SKIP_WORKTREE_DIR; + goto dir_check; + } + /* only error if existence is expected. */ + if (!(modes[i] & SPARSE)) + bad = _("bad source"); + goto act_on_entry; + } + ce = active_cache[pos]; + if (!ce_skip_worktree(ce)) { bad = _("bad source"); - } else if (!strncmp(src, dst, length) && - (dst[length] == 0 || dst[length] == '/')) { + goto act_on_entry; + } + if (!ignore_sparse) { + string_list_append(&only_match_skip_worktree, src); + goto act_on_entry; + } + /* Check if dst exists in index */ + if (cache_name_pos(dst, strlen(dst)) < 0) { + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!force) { + bad = _("destination exists"); + goto act_on_entry; + } + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!strncmp(src, dst, length) && + (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); - } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) + goto act_on_entry; + } + if (S_ISDIR(st.st_mode) + && lstat(dst, &st) == 0) { bad = _("cannot move directory over file"); - else if (src_is_dir) { + goto act_on_entry; + } + +dir_check: + if (S_ISDIR(st.st_mode)) { + int j, dst_len, n; int first = cache_name_pos(src, length), last; - if (first >= 0) + if (first >= 0) { prepare_move_submodule(src, first, submodule_gitfile + i); - else if (index_range_of_same_dir(src, length, - &first, &last) < 1) + goto act_on_entry; + } else if (index_range_of_same_dir(src, length, + &first, &last) < 1) { bad = _("source directory is empty"); - else { /* last - first >= 1 */ - int j, dst_len, n; - - modes[i] = WORKING_DIRECTORY; - n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); - REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); - - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const struct cache_entry *ce = active_cache[first + j]; - const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; - } - argc += last - first; + goto act_on_entry; } - } else if (!(ce = cache_file_exists(src, length, 0))) { + + /* last - first >= 1 */ + modes[i] |= WORKING_DIRECTORY; + + ALLOC_GROW(src_dir, src_dir_nr + 1, src_dir_alloc); + src_dir[src_dir_nr++] = src; + + n = argc + last - first; + REALLOC_ARRAY(source, n); + REALLOC_ARRAY(destination, n); + REALLOC_ARRAY(modes, n); + REALLOC_ARRAY(submodule_gitfile, n); + + dst = add_slash(dst); + dst_len = strlen(dst); + + for (j = 0; j < last - first; j++) { + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; + source[argc + j] = path; + destination[argc + j] = + prefix_path(dst, dst_len, path + length + 1); + memset(modes + argc + j, 0, sizeof(enum update_mode)); + modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; + submodule_gitfile[argc + j] = NULL; + } + argc += last - first; + goto act_on_entry; + } + if (!(ce = cache_file_exists(src, length, 0))) { bad = _("not under version control"); - } else if (ce_stage(ce)) { + goto act_on_entry; + } + if (ce_stage(ce)) { bad = _("conflicted"); - } else if (lstat(dst, &st) == 0 && - (!ignore_case || strcasecmp(src, dst))) { + goto act_on_entry; + } + if (lstat(dst, &st) == 0 && + (!ignore_case || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { /* @@ -246,34 +366,52 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else bad = _("Cannot overwrite"); } - } else if (string_list_has_string(&src_for_dst, dst)) + goto act_on_entry; + } + if (string_list_has_string(&src_for_dst, dst)) { bad = _("multiple sources for the same target"); - else if (is_dir_sep(dst[strlen(dst) - 1])) + goto act_on_entry; + } + if (is_dir_sep(dst[strlen(dst) - 1])) { bad = _("destination directory does not exist"); - else { - /* - * We check if the paths are in the sparse-checkout - * definition as a very final check, since that - * allows us to point the user to the --sparse - * option as a way to have a successful run. - */ - if (!ignore_sparse && - !path_in_sparse_checkout(src, &the_index)) { - string_list_append(&only_match_skip_worktree, src); - skip_sparse = 1; - } - if (!ignore_sparse && - !path_in_sparse_checkout(dst, &the_index)) { - string_list_append(&only_match_skip_worktree, dst); - skip_sparse = 1; + goto act_on_entry; + } + + if (ignore_sparse && + (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + index_entry_exists(&the_index, dst, strlen(dst))) { + bad = _("destination exists in the index"); + if (force) { + if (verbose) + warning(_("overwriting '%s'"), dst); + bad = NULL; + } else { + goto act_on_entry; } + } + /* + * We check if the paths are in the sparse-checkout + * definition as a very final check, since that + * allows us to point the user to the --sparse + * option as a way to have a successful run. + */ + if (!ignore_sparse && + !path_in_sparse_checkout(src, &the_index)) { + string_list_append(&only_match_skip_worktree, src); + skip_sparse = 1; + } + if (!ignore_sparse && + !path_in_sparse_checkout(dst, &the_index)) { + string_list_append(&only_match_skip_worktree, dst); + skip_sparse = 1; + } - if (skip_sparse) - goto remove_entry; + if (skip_sparse) + goto remove_entry; - string_list_insert(&src_for_dst, dst); - } + string_list_insert(&src_for_dst, dst); +act_on_entry: if (!bad) continue; if (!ignore_errors) @@ -282,14 +420,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry: if (--argc > 0) { int n = argc - i; - memmove(source + i, source + i + 1, - n * sizeof(char *)); - memmove(destination + i, destination + i + 1, - n * sizeof(char *)); - memmove(modes + i, modes + i + 1, - n * sizeof(enum update_mode)); - memmove(submodule_gitfile + i, submodule_gitfile + i + 1, - n * sizeof(char *)); + MOVE_ARRAY(source + i, source + i + 1, n); + MOVE_ARRAY(destination + i, destination + i + 1, n); + MOVE_ARRAY(modes + i, modes + i + 1, n); + MOVE_ARRAY(submodule_gitfile + i, + submodule_gitfile + i + 1, n); i--; } } @@ -304,11 +439,19 @@ remove_entry: const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; int pos; + int sparse_and_dirty = 0; + struct checkout state = CHECKOUT_INIT; + state.istate = &the_index; + + if (force) + state.force = 1; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { + if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) && + !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); @@ -322,14 +465,86 @@ remove_entry: 1); } - if (mode == WORKING_DIRECTORY) + if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) continue; pos = cache_name_pos(src, strlen(src)); assert(pos >= 0); + if (!(mode & SPARSE) && !lstat(src, &st)) + sparse_and_dirty = ce_modified(active_cache[pos], &st, 0); rename_cache_entry_at(pos, dst); + + if (ignore_sparse && + core_apply_sparse_checkout && + core_sparse_checkout_cone) { + /* + * NEEDSWORK: we are *not* paying attention to + * "out-to-out" move ( is out-of-cone and + * is out-of-cone) at this point. It + * should be added in a future patch. + */ + if ((mode & SPARSE) && + path_in_sparse_checkout(dst, &the_index)) { + /* from out-of-cone to in-cone */ + int dst_pos = cache_name_pos(dst, strlen(dst)); + struct cache_entry *dst_ce = active_cache[dst_pos]; + + dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; + + if (checkout_entry(dst_ce, &state, NULL, NULL)) + die(_("cannot checkout %s"), dst_ce->name); + } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && + !(mode & SPARSE) && + !path_in_sparse_checkout(dst, &the_index)) { + /* from in-cone to out-of-cone */ + int dst_pos = cache_name_pos(dst, strlen(dst)); + struct cache_entry *dst_ce = active_cache[dst_pos]; + + /* + * if src is clean, it will suffice to remove it + */ + if (!sparse_and_dirty) { + dst_ce->ce_flags |= CE_SKIP_WORKTREE; + unlink_or_warn(src); + } else { + /* + * if src is dirty, move it to the + * destination and create leading + * dirs if necessary + */ + char *dst_dup = xstrdup(dst); + string_list_append(&dirty_paths, dst); + safe_create_leading_directories(dst_dup); + FREE_AND_NULL(dst_dup); + rename(src, dst); + } + } + } } + /* + * 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); + + if (dirty_paths.nr) + advise_on_moving_dirty_path(&dirty_paths); + if (gitmodules_modified) stage_updated_gitmodules(&the_index); @@ -338,6 +553,7 @@ remove_entry: die(_("Unable to write new index file")); string_list_clear(&src_for_dst, 0); + string_list_clear(&dirty_paths, 0); UNLEAK(source); UNLEAK(dest_path); free(submodule_gitfile); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 580b1eb170..15535e914a 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -344,7 +344,8 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_) return a->taggerdate != b->taggerdate; } -static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) +static int name_ref(const char *path, const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct object *o = parse_object(the_repository, oid); struct name_ref_data *data = cb_data; diff --git a/builtin/notes.c b/builtin/notes.c index a3d0d15a22..be51f69225 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -994,17 +994,34 @@ static int get_ref(int argc, const char **argv, const char *prefix) int cmd_notes(int argc, const char **argv, const char *prefix) { - int result; const char *override_notes_ref = NULL; + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"), N_("use notes from ")), + OPT_SUBCOMMAND("list", &fn, list), + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("copy", &fn, copy), + OPT_SUBCOMMAND("append", &fn, append_edit), + OPT_SUBCOMMAND("edit", &fn, append_edit), + OPT_SUBCOMMAND("show", &fn, show), + OPT_SUBCOMMAND("merge", &fn, merge), + OPT_SUBCOMMAND("remove", &fn, remove_cmd), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("get-ref", &fn, get_ref), OPT_END() }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_notes_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + PARSE_OPT_SUBCOMMAND_OPTIONAL); + if (!fn) { + if (argc) { + error(_("unknown subcommand: `%s'"), argv[0]); + usage_with_options(git_notes_usage, options); + } + fn = list; + } if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; @@ -1014,28 +1031,5 @@ int cmd_notes(int argc, const char **argv, const char *prefix) strbuf_release(&sb); } - if (argc < 1 || !strcmp(argv[0], "list")) - result = list(argc, argv, prefix); - else if (!strcmp(argv[0], "add")) - result = add(argc, argv, prefix); - else if (!strcmp(argv[0], "copy")) - result = copy(argc, argv, prefix); - else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit")) - result = append_edit(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - result = show(argc, argv, prefix); - else if (!strcmp(argv[0], "merge")) - result = merge(argc, argv, prefix); - else if (!strcmp(argv[0], "remove")) - result = remove_cmd(argc, argv, prefix); - else if (!strcmp(argv[0], "prune")) - result = prune(argc, argv, prefix); - else if (!strcmp(argv[0], "get-ref")) - result = get_ref(argc, argv, prefix); - else { - result = error(_("unknown subcommand: %s"), argv[0]); - usage_with_options(git_notes_usage, options); - } - - return result ? 1 : 0; + return !!fn(argc, argv, prefix); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 39e28cfcaf..3658c05caf 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -759,8 +759,8 @@ static enum write_one_status write_one(struct hashfile *f, return WRITE_ONE_WRITTEN; } -static int mark_tagged(const char *path, const struct object_id *oid, int flag, - void *cb_data) +static int mark_tagged(const char *path UNUSED, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { struct object_id peeled; struct object_entry *entry = packlist_find(&to_pack, oid); @@ -3035,7 +3035,8 @@ static void add_tag_chain(const struct object_id *oid) } } -static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data) +static int add_ref_tag(const char *tag UNUSED, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { struct object_id peeled; @@ -3148,6 +3149,14 @@ static int git_pack_config(const char *k, const char *v, void *cb) else write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE; } + + if (!strcmp(k, "pack.writebitmaplookuptable")) { + if (git_config_bool(k, v)) + write_bitmap_options |= BITMAP_OPT_LOOKUP_TABLE; + else + write_bitmap_options &= ~BITMAP_OPT_LOOKUP_TABLE; + } + if (!strcmp(k, "pack.usebitmaps")) { use_bitmap_index_default = git_config_bool(k, v); return 0; @@ -3950,8 +3959,9 @@ static void record_recent_commit(struct commit *commit, void *data) } static int mark_bitmap_preferred_tip(const char *refname, - const struct object_id *oid, int flags, - void *_data) + const struct object_id *oid, + int flags UNUSED, + void *data UNUSED) { struct object_id peeled; struct object *object; diff --git a/builtin/pull.c b/builtin/pull.c index 01155ba67b..403a24d7ca 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -990,6 +990,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) int rebase_unspecified = 0; int can_ff; int divergent; + int ret; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -1100,7 +1101,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (is_null_oid(&orig_head)) { if (merge_heads.nr > 1) die(_("Cannot merge multiple branches into empty head.")); - return pull_into_void(merge_heads.oid, &curr_head); + ret = pull_into_void(merge_heads.oid, &curr_head); + goto cleanup; } if (merge_heads.nr > 1) { if (opt_rebase) @@ -1125,8 +1127,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) } if (opt_rebase) { - int ret = 0; - struct object_id newbase; struct object_id upstream; get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head, @@ -1149,12 +1149,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix) recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = rebase_submodules(); - return ret; + goto cleanup; } else { - int ret = run_merge(); + ret = run_merge(); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = update_submodules(); - return ret; + goto cleanup; } + +cleanup: + oid_array_clear(&merge_heads); + return ret; } diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 50318849d6..e2a74efb42 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -38,8 +38,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) OPT_END() }; struct option *options; - int res = 0; + int i, dash_dash = -1, res = 0; struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT; + struct object_id oid; + const char *three_dots = NULL; git_config(git_diff_ui_config, NULL); @@ -47,7 +49,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) options = parse_options_concat(range_diff_options, diffopt.parseopts); argc = parse_options(argc, argv, prefix, options, - builtin_range_diff_usage, 0); + builtin_range_diff_usage, PARSE_OPT_KEEP_DASHDASH); diff_setup_done(&diffopt); @@ -55,40 +57,91 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) if (!simple_color) diffopt.use_color = 1; - if (argc == 2) { - if (!is_range_diff_range(argv[0])) - die(_("not a commit range: '%s'"), argv[0]); - strbuf_addstr(&range1, argv[0]); + for (i = 0; i < argc; i++) + if (!strcmp(argv[i], "--")) { + dash_dash = i; + break; + } + + if (dash_dash == 3 || + (dash_dash < 0 && argc > 2 && + !get_oid_committish(argv[0], &oid) && + !get_oid_committish(argv[1], &oid) && + !get_oid_committish(argv[2], &oid))) { + if (dash_dash < 0) + ; /* already validated arguments */ + else if (get_oid_committish(argv[0], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[0]); + else if (get_oid_committish(argv[1], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[1]); + else if (get_oid_committish(argv[2], &oid)) + usage_msg_optf(_("not a revision: '%s'"), + builtin_range_diff_usage, options, + argv[2]); - if (!is_range_diff_range(argv[1])) - die(_("not a commit range: '%s'"), argv[1]); - strbuf_addstr(&range2, argv[1]); - } else if (argc == 3) { strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); strbuf_addf(&range2, "%s..%s", argv[0], argv[2]); - } else if (argc == 1) { - const char *b = strstr(argv[0], "..."), *a = argv[0]; + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 3 : dash_dash)); + } else if (dash_dash == 2 || + (dash_dash < 0 && argc > 1 && + is_range_diff_range(argv[0]) && + is_range_diff_range(argv[1]))) { + if (dash_dash < 0) + ; /* already validated arguments */ + else if (!is_range_diff_range(argv[0])) + usage_msg_optf(_("not a commit range: '%s'"), + builtin_range_diff_usage, options, + argv[0]); + else if (!is_range_diff_range(argv[1])) + usage_msg_optf(_("not a commit range: '%s'"), + builtin_range_diff_usage, options, + argv[1]); + + strbuf_addstr(&range1, argv[0]); + strbuf_addstr(&range2, argv[1]); + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 2 : dash_dash)); + } else if (dash_dash == 1 || + (dash_dash < 0 && argc > 0 && + (three_dots = strstr(argv[0], "...")))) { + const char *a, *b; int a_len; - if (!b) { - error(_("single arg format must be symmetric range")); - usage_with_options(builtin_range_diff_usage, options); - } + if (dash_dash < 0) + ; /* already validated arguments */ + else if (!(three_dots = strstr(argv[0], "..."))) + usage_msg_optf(_("not a symmetric range: '%s'"), + builtin_range_diff_usage, options, + argv[0]); - a_len = (int)(b - a); - if (!a_len) { + if (three_dots == argv[0]) { a = "HEAD"; a_len = strlen(a); + } else { + a = argv[0]; + a_len = (int)(three_dots - a); } - b += 3; - if (!*b) + + if (three_dots[3]) + b = three_dots + 3; + else b = "HEAD"; + strbuf_addf(&range1, "%s..%.*s", b, a_len, a); strbuf_addf(&range2, "%.*s..%s", a_len, a, b); - } else { - error(_("need two commit ranges")); - usage_with_options(builtin_range_diff_usage, options); - } + + strvec_pushv(&other_arg, argv + + (dash_dash < 0 ? 1 : dash_dash)); + } else + usage_msg_opt(_("need two commit ranges"), + builtin_range_diff_usage, options); FREE_AND_NULL(options); range_diff_opts.dual_color = simple_color < 1; diff --git a/builtin/rebase.c b/builtin/rebase.c index 70aa7c842f..56e4214b44 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -102,6 +102,7 @@ struct rebase_options { int reschedule_failed_exec; int reapply_cherry_picks; int fork_point; + int update_refs; }; #define REBASE_OPTIONS_INIT { \ @@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) ret = complete_action(the_repository, &replay, flags, shortrevisions, opts->onto_name, opts->onto, &opts->orig_head, &commands, opts->autosquash, + opts->update_refs, &todo_list); } @@ -800,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.updaterefs")) { + opts->update_refs = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "rebase.reschedulefailedexec")) { opts->reschedule_failed_exec = git_config_bool(var, value); return 0; @@ -1124,6 +1131,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_BOOL(0, "update-refs", &options.update_refs, + N_("update branches that point to commits " + "that are being rebased")), { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 31b48e728b..44bcea3a5b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -291,7 +291,7 @@ static void show_ref(const char *path, const struct object_id *oid) } static int show_ref_cb(const char *path_full, const struct object_id *oid, - int flag, void *data) + int flag UNUSED, void *data) { struct oidset *seen = data; const char *path = strip_namespace(path_full); @@ -465,7 +465,7 @@ static void rp_error(const char *err, ...) va_end(params); } -static int copy_to_sideband(int in, int out, void *arg) +static int copy_to_sideband(int in, int out UNUSED, void *arg UNUSED) { char data[128]; int keepalive_active = 0; diff --git a/builtin/reflog.c b/builtin/reflog.c index 4dd297dce8..57c5c0d061 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -56,7 +56,8 @@ struct worktree_reflogs { struct string_list reflogs; }; -static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data) +static int collect_reflog(const char *ref, const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct worktree_reflogs *cb = cb_data; struct worktree *worktree = cb->worktree; @@ -193,6 +194,8 @@ static int expire_unreachable_callback(const struct option *opt, { struct cmd_reflog_expire_cb *cmd = opt->value; + BUG_ON_OPT_NEG(unset); + if (parse_expiry_date(arg, &cmd->expire_unreachable)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); @@ -207,6 +210,8 @@ static int expire_total_callback(const struct option *opt, { struct cmd_reflog_expire_cb *cmd = opt->value; + BUG_ON_OPT_NEG(unset); + if (parse_expiry_date(arg, &cmd->expire_total)) die(_("invalid timestamp '%s' given to '--%s'"), arg, opt->long_name); @@ -223,7 +228,7 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix) parse_options(argc, argv, prefix, options, reflog_show_usage, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); return cmd_log_reflog(argc, argv, prefix); } @@ -404,40 +409,21 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) int cmd_reflog(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), + OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), + OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), OPT_END() }; argc = parse_options(argc, argv, prefix, options, reflog_usage, + PARSE_OPT_SUBCOMMAND_OPTIONAL | PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN | - PARSE_OPT_NO_INTERNAL_HELP); - - /* - * With "git reflog" we default to showing it. !argc is - * impossible with PARSE_OPT_KEEP_ARGV0. - */ - if (argc == 1) - goto log_reflog; - - if (!strcmp(argv[1], "-h")) - usage_with_options(reflog_usage, options); - else if (*argv[1] == '-') - goto log_reflog; - - if (!strcmp(argv[1], "show")) - return cmd_reflog_show(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "expire")) - return cmd_reflog_expire(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "delete")) - return cmd_reflog_delete(argc - 1, argv + 1, prefix); - else if (!strcmp(argv[1], "exists")) - return cmd_reflog_exists(argc - 1, argv + 1, prefix); - - /* - * Fall-through for e.g. "git reflog -1", "git reflog master", - * as well as the plain "git reflog" above goto above. - */ -log_reflog: - return cmd_log_reflog(argc, argv, prefix); + PARSE_OPT_KEEP_UNKNOWN_OPT); + if (fn) + return fn(argc - 1, argv + 1, prefix); + else + return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote.c b/builtin/remote.c index a3a0c27d7a..985b845a18 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -150,7 +150,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not) return 0; } -static int add(int argc, const char **argv) +static int add(int argc, const char **argv, const char *prefix) { int fetch = 0, fetch_tags = TAGS_DEFAULT; unsigned mirror = MIRROR_NONE; @@ -177,8 +177,8 @@ static int add(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_add_usage, 0); if (argc != 2) usage_with_options(builtin_remote_add_usage, options); @@ -264,7 +264,8 @@ static const char *abbrev_ref(const char *name, const char *prefix) } #define abbrev_branch(name) abbrev_ref((name), "refs/heads/") -static int config_read_branches(const char *key, const char *value, void *cb) +static int config_read_branches(const char *key, const char *value, + void *data UNUSED) { const char *orig_key = key; char *name; @@ -344,12 +345,13 @@ static void read_branches(void) struct ref_states { struct remote *remote; - struct string_list new_refs, stale, tracked, heads, push; + struct string_list new_refs, skipped, stale, tracked, heads, push; int queried; }; #define REF_STATES_INIT { \ .new_refs = STRING_LIST_INIT_DUP, \ + .skipped = STRING_LIST_INIT_DUP, \ .stale = STRING_LIST_INIT_DUP, \ .tracked = STRING_LIST_INIT_DUP, \ .heads = STRING_LIST_INIT_DUP, \ @@ -368,7 +370,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat states->remote->fetch.raw[i]); for (ref = fetch_map; ref; ref = ref->next) { - if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) + if (omit_name_by_refspec(ref->name, &states->remote->fetch)) + string_list_append(&states->skipped, abbrev_branch(ref->name)); + else if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new_refs, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); @@ -383,6 +387,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat free_refs(fetch_map); string_list_sort(&states->new_refs); + string_list_sort(&states->skipped); string_list_sort(&states->tracked); string_list_sort(&states->stale); @@ -534,7 +539,8 @@ struct branches_for_remote { }; static int add_branch_for_removal(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct branches_for_remote *branches = cb_data; struct refspec_item refspec; @@ -576,7 +582,8 @@ struct rename_info { }; static int read_remote_branches(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct rename_info *rename = cb_data; struct strbuf buf = STRBUF_INIT; @@ -676,7 +683,7 @@ static void handle_push_default(const char* old_name, const char* new_name) } -static int mv(int argc, const char **argv) +static int mv(int argc, const char **argv, const char *prefix) { int show_progress = isatty(2); struct option options[] = { @@ -691,7 +698,7 @@ static int mv(int argc, const char **argv) int i, refs_renamed_nr = 0, refspec_updated = 0; struct progress *progress = NULL; - argc = parse_options(argc, argv, NULL, options, + argc = parse_options(argc, argv, prefix, options, builtin_remote_rename_usage, 0); if (argc != 2) @@ -840,7 +847,7 @@ static int mv(int argc, const char **argv) return 0; } -static int rm(int argc, const char **argv) +static int rm(int argc, const char **argv, const char *prefix) { struct option options[] = { OPT_END() @@ -858,12 +865,14 @@ static int rm(int argc, const char **argv) cb_data.skipped = &skipped; cb_data.keep = &known_remotes; - if (argc != 2) + argc = parse_options(argc, argv, prefix, options, + builtin_remote_rm_usage, 0); + if (argc != 1) usage_with_options(builtin_remote_rm_usage, options); - remote = remote_get(argv[1]); + remote = remote_get(argv[0]); if (!remote_is_configured(remote, 1)) { - error(_("No such remote: '%s'"), argv[1]); + error(_("No such remote: '%s'"), argv[0]); exit(2); } @@ -941,6 +950,7 @@ static void clear_push_info(void *util, const char *string) static void free_remote_ref_states(struct ref_states *states) { string_list_clear(&states->new_refs, 0); + string_list_clear(&states->skipped, 0); string_list_clear(&states->stale, 1); string_list_clear(&states->tracked, 0); string_list_clear(&states->heads, 0); @@ -948,7 +958,8 @@ static void free_remote_ref_states(struct ref_states *states) } static int append_ref_to_tracked_list(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags, void *cb_data) { struct ref_states *states = cb_data; struct refspec_item refspec; @@ -1035,6 +1046,8 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) arg = states->remote->name; } else if (string_list_has_string(&states->tracked, name)) arg = _(" tracked"); + else if (string_list_has_string(&states->skipped, name)) + arg = _(" skipped"); else if (string_list_has_string(&states->stale, name)) arg = _(" stale (use 'git remote prune' to remove)"); else @@ -1247,7 +1260,7 @@ static int show_all(void) return result; } -static int show(int argc, const char **argv) +static int show(int argc, const char **argv, const char *prefix) { int no_query = 0, result = 0, query_flag = 0; struct option options[] = { @@ -1256,7 +1269,8 @@ static int show(int argc, const char **argv) }; struct show_info info = SHOW_INFO_INIT; - argc = parse_options(argc, argv, NULL, options, builtin_remote_show_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_show_usage, 0); if (argc < 1) @@ -1307,6 +1321,7 @@ static int show(int argc, const char **argv) /* remote branch info */ info.width = 0; for_each_string_list(&info.states.new_refs, add_remote_to_show_info, &info); + for_each_string_list(&info.states.skipped, add_remote_to_show_info, &info); for_each_string_list(&info.states.tracked, add_remote_to_show_info, &info); for_each_string_list(&info.states.stale, add_remote_to_show_info, &info); if (info.list.nr) @@ -1349,7 +1364,7 @@ static int show(int argc, const char **argv) return result; } -static int set_head(int argc, const char **argv) +static int set_head(int argc, const char **argv, const char *prefix) { int i, opt_a = 0, opt_d = 0, result = 0; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; @@ -1362,8 +1377,8 @@ static int set_head(int argc, const char **argv) N_("delete refs/remotes//HEAD")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_sethead_usage, 0); if (argc) strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]); @@ -1454,7 +1469,7 @@ static int prune_remote(const char *remote, int dry_run) return result; } -static int prune(int argc, const char **argv) +static int prune(int argc, const char **argv, const char *prefix) { int dry_run = 0, result = 0; struct option options[] = { @@ -1462,8 +1477,8 @@ static int prune(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage, - 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_prune_usage, 0); if (argc < 1) usage_with_options(builtin_remote_prune_usage, options); @@ -1474,7 +1489,7 @@ static int prune(int argc, const char **argv) return result; } -static int get_remote_default(const char *key, const char *value, void *priv) +static int get_remote_default(const char *key, const char *value UNUSED, void *priv) { if (strcmp(key, "remotes.default") == 0) { int *found = priv; @@ -1483,7 +1498,7 @@ static int get_remote_default(const char *key, const char *value, void *priv) return 0; } -static int update(int argc, const char **argv) +static int update(int argc, const char **argv, const char *prefix) { int i, prune = -1; struct option options[] = { @@ -1495,7 +1510,8 @@ static int update(int argc, const char **argv) int default_defined = 0; int retval; - argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_update_usage, PARSE_OPT_KEEP_ARGV0); strvec_push(&fetch_argv, "fetch"); @@ -1566,7 +1582,7 @@ static int set_remote_branches(const char *remotename, const char **branches, return 0; } -static int set_branches(int argc, const char **argv) +static int set_branches(int argc, const char **argv, const char *prefix) { int add_mode = 0; struct option options[] = { @@ -1574,7 +1590,7 @@ static int set_branches(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, NULL, options, + argc = parse_options(argc, argv, prefix, options, builtin_remote_setbranches_usage, 0); if (argc == 0) { error(_("no remote specified")); @@ -1585,7 +1601,7 @@ static int set_branches(int argc, const char **argv) return set_remote_branches(argv[0], argv + 1, add_mode); } -static int get_url(int argc, const char **argv) +static int get_url(int argc, const char **argv, const char *prefix) { int i, push_mode = 0, all_mode = 0; const char *remotename = NULL; @@ -1599,7 +1615,8 @@ static int get_url(int argc, const char **argv) N_("return all URLs")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0); + argc = parse_options(argc, argv, prefix, options, + builtin_remote_geturl_usage, 0); if (argc != 1) usage_with_options(builtin_remote_geturl_usage, options); @@ -1638,7 +1655,7 @@ static int get_url(int argc, const char **argv) return 0; } -static int set_url(int argc, const char **argv) +static int set_url(int argc, const char **argv, const char *prefix) { int i, push_mode = 0, add_mode = 0, delete_mode = 0; int matches = 0, negative_matches = 0; @@ -1659,7 +1676,8 @@ static int set_url(int argc, const char **argv) N_("delete URLs")), OPT_END() }; - argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage, + argc = parse_options(argc, argv, prefix, options, + builtin_remote_seturl_usage, PARSE_OPT_KEEP_ARGV0); if (add_mode && delete_mode) @@ -1730,41 +1748,33 @@ out: int cmd_remote(int argc, const char **argv, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("rename", &fn, mv), + OPT_SUBCOMMAND_F("rm", &fn, rm, PARSE_OPT_NOCOMPLETE), + OPT_SUBCOMMAND("remove", &fn, rm), + OPT_SUBCOMMAND("set-head", &fn, set_head), + OPT_SUBCOMMAND("set-branches", &fn, set_branches), + OPT_SUBCOMMAND("get-url", &fn, get_url), + OPT_SUBCOMMAND("set-url", &fn, set_url), + OPT_SUBCOMMAND("show", &fn, show), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("update", &fn, update), OPT_END() }; - int result; argc = parse_options(argc, argv, prefix, options, builtin_remote_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + PARSE_OPT_SUBCOMMAND_OPTIONAL); - if (argc < 1) - result = show_all(); - else if (!strcmp(argv[0], "add")) - result = add(argc, argv); - else if (!strcmp(argv[0], "rename")) - result = mv(argc, argv); - else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) - result = rm(argc, argv); - else if (!strcmp(argv[0], "set-head")) - result = set_head(argc, argv); - else if (!strcmp(argv[0], "set-branches")) - result = set_branches(argc, argv); - else if (!strcmp(argv[0], "get-url")) - result = get_url(argc, argv); - else if (!strcmp(argv[0], "set-url")) - result = set_url(argc, argv); - else if (!strcmp(argv[0], "show")) - result = show(argc, argv); - else if (!strcmp(argv[0], "prune")) - result = prune(argc, argv); - else if (!strcmp(argv[0], "update")) - result = update(argc, argv); - else { - error(_("Unknown subcommand: %s"), argv[0]); - usage_with_options(builtin_remote_usage, options); + if (fn) { + return !!fn(argc, argv, prefix); + } else { + if (argc) { + error(_("unknown subcommand: `%s'"), argv[0]); + usage_with_options(builtin_remote_usage, options); + } + return !!show_all(); } - - return result ? 1 : 0; } diff --git a/builtin/repack.c b/builtin/repack.c index 4a7ae4cf48..a5bacc7797 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -514,9 +514,9 @@ struct midx_snapshot_ref_data { int preferred; }; -static int midx_snapshot_ref_one(const char *refname, +static int midx_snapshot_ref_one(const char *refname UNUSED, const struct object_id *oid, - int flag, void *_data) + int flag UNUSED, void *_data) { struct midx_snapshot_ref_data *data = _data; struct object_id peeled; @@ -727,7 +727,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; struct string_list names = STRING_LIST_INIT_DUP; - struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_nonkept_packs = STRING_LIST_INIT_DUP; struct string_list existing_kept_packs = STRING_LIST_INIT_DUP; struct pack_geometry *geometry = NULL; @@ -1117,7 +1116,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } string_list_clear(&names, 0); - string_list_clear(&rollback, 0); string_list_clear(&existing_nonkept_packs, 0); string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); diff --git a/builtin/replace.c b/builtin/replace.c index 583702a098..a29e911d30 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -106,6 +106,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) size_t base_len; int had_error = 0; struct object_id oid; + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; strbuf_addstr(&ref, git_replace_ref_base); base_len = ref.len; @@ -147,6 +148,8 @@ static int check_ref_valid(struct object_id *object, struct strbuf *ref, int force) { + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; + strbuf_reset(ref); strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object)); if (check_refname_format(ref->buf, 0)) diff --git a/builtin/reset.c b/builtin/reset.c index 344fff8f3a..fdce6f8c85 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -174,88 +174,6 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } -static int pathspec_needs_expanded_index(const struct pathspec *pathspec) -{ - unsigned int i, pos; - int res = 0; - char *skip_worktree_seen = NULL; - - /* - * When using a magic pathspec, assume for the sake of simplicity that - * the index needs to be expanded to match all matchable files. - */ - if (pathspec->magic) - return 1; - - for (i = 0; i < pathspec->nr; i++) { - struct pathspec_item item = pathspec->items[i]; - - /* - * If the pathspec item has a wildcard, the index should be expanded - * if the pathspec has the possibility of matching a subset of entries inside - * of a sparse directory (but not the entire directory). - * - * If the pathspec item is a literal path, the index only needs to be expanded - * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't - * expand for in-cone files) and b) it doesn't match any sparse directories - * (since we can reset whole sparse directories without expanding them). - */ - if (item.nowildcard_len < item.len) { - /* - * Special case: if the pattern is a path inside the cone - * followed by only wildcards, the pattern cannot match - * partial sparse directories, so we know we don't need to - * expand the index. - * - * Examples: - * - in-cone/foo***: doesn't need expanded index - * - not-in-cone/bar*: may need expanded index - * - **.c: may need expanded index - */ - if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && - path_in_cone_mode_sparse_checkout(item.original, &the_index)) - continue; - - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - - if (!S_ISSPARSEDIR(ce->ce_mode)) - continue; - - /* - * If the pre-wildcard length is longer than the sparse - * directory name and the sparse directory is the first - * component of the pathspec, need to expand the index. - */ - if (item.nowildcard_len > ce_namelen(ce) && - !strncmp(item.original, ce->name, ce_namelen(ce))) { - res = 1; - break; - } - - /* - * If the pre-wildcard length is shorter than the sparse - * directory and the pathspec does not match the whole - * directory, need to expand the index. - */ - if (!strncmp(item.original, ce->name, item.nowildcard_len) && - wildmatch(item.original, ce->name, 0)) { - res = 1; - break; - } - } - } else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) && - !matches_skip_worktree(pathspec, i, &skip_worktree_seen)) - res = 1; - - if (res > 0) - break; - } - - free(skip_worktree_seen); - return res; -} - static int read_from_tree(const struct pathspec *pathspec, struct object_id *tree_oid, int intent_to_add) @@ -273,7 +191,7 @@ static int read_from_tree(const struct pathspec *pathspec, opt.change = diff_change; opt.add_remove = diff_addremove; - if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec)) + if (pathspec->nr && pathspec_needs_expanded_index(&the_index, pathspec)) ensure_full_index(&the_index); if (do_diff_cache(tree_oid, &opt)) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 30fd8e83ea..fba6f5d51f 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -46,6 +46,7 @@ static const char rev_list_usage[] = " --parents\n" " --children\n" " --objects | --objects-edge\n" +" --disk-usage[=human]\n" " --unpacked\n" " --header | --pretty\n" " --[no-]object-names\n" @@ -81,6 +82,7 @@ static int arg_show_object_names = 1; static int show_disk_usage; static off_t total_disk_usage; +static int human_readable; static off_t get_object_disk_usage(struct object *obj) { @@ -368,6 +370,17 @@ static int show_object_fast( return 1; } +static void print_disk_usage(off_t size) +{ + struct strbuf sb = STRBUF_INIT; + if (human_readable) + strbuf_humanise_bytes(&sb, size); + else + strbuf_addf(&sb, "%"PRIuMAX, (uintmax_t)size); + puts(sb.buf); + strbuf_release(&sb); +} + static inline int parse_missing_action_value(const char *value) { if (!strcmp(value, "error")) { @@ -473,6 +486,7 @@ static int try_bitmap_disk_usage(struct rev_info *revs, int filter_provided_objects) { struct bitmap_index *bitmap_git; + off_t size_from_bitmap; if (!show_disk_usage) return -1; @@ -481,8 +495,8 @@ static int try_bitmap_disk_usage(struct rev_info *revs, if (!bitmap_git) return -1; - printf("%"PRIuMAX"\n", - (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs)); + size_from_bitmap = get_disk_usage_from_bitmap(bitmap_git, revs); + print_disk_usage(size_from_bitmap); return 0; } @@ -624,7 +638,21 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } - if (!strcmp(arg, "--disk-usage")) { + if (skip_prefix(arg, "--disk-usage", &arg)) { + if (*arg == '=') { + if (!strcmp(++arg, "human")) { + human_readable = 1; + } else + die(_("invalid value for '%s': '%s', the only allowed format is '%s'"), + "--disk-usage=", arg, "human"); + } else if (*arg) { + /* + * Arguably should goto a label to continue chain of ifs? + * Doesn't matter unless we try to add --disk-usage-foo + * afterwards. + */ + usage(rev_list_usage); + } show_disk_usage = 1; info.flags |= REV_LIST_QUIET; continue; @@ -753,7 +781,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } if (show_disk_usage) - printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage); + print_disk_usage(total_disk_usage); cleanup: release_revisions(&revs); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index b259d8990a..8f61050bde 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -195,7 +195,8 @@ static int show_default(void) return 0; } -static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) +static int show_reference(const char *refname, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { if (ref_excluded(ref_excludes, refname)) return 0; @@ -203,7 +204,8 @@ static int show_reference(const char *refname, const struct object_id *oid, int return 0; } -static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) +static int anti_reference(const char *refname, const struct object_id *oid, + int flag UNUSED, void *cb_data UNUSED) { show_rev(REVERSED, oid, refname); return 0; @@ -479,6 +481,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) if (!s) s = help; + if (s == sb.buf) + die(_("missing opt-spec before option flags")); + if (s - sb.buf == 1) /* short option only */ o->short_name = *sb.buf; else if (sb.buf[1] != ',') /* long option only */ diff --git a/builtin/revert.c b/builtin/revert.c index f84c253f4c..ee2a0807f0 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -141,7 +141,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) argc = parse_options(argc, argv, NULL, options, usage_str, PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -246,6 +246,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix) res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); + if (opts.revs) + release_revisions(opts.revs); + free(opts.revs); return res; } diff --git a/builtin/rm.c b/builtin/rm.c index 84a935a16e..b6ba859fe4 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -287,6 +287,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) @@ -296,8 +298,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen = xcalloc(pathspec.nr, 1); - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); + if (pathspec_needs_expanded_index(&the_index, &pathspec)) + ensure_full_index(&the_index); + for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 086dfee45a..7a1e1fe7c0 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -381,6 +381,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) break; case PARSE_OPT_HELP: case PARSE_OPT_ERROR: + case PARSE_OPT_SUBCOMMAND: exit(129); case PARSE_OPT_COMPLETE: exit(0); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 64c649c6a2..d3f5715e3e 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -404,7 +404,7 @@ static int append_ref(const char *refname, const struct object_id *oid, } static int append_head_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object_id tmp; int ofs = 11; @@ -419,7 +419,7 @@ static int append_head_ref(const char *refname, const struct object_id *oid, } static int append_remote_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { struct object_id tmp; int ofs = 13; @@ -434,7 +434,7 @@ static int append_remote_ref(const char *refname, const struct object_id *oid, } static int append_tag_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data UNUSED) { if (!starts_with(refname, "refs/tags/")) return 0; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 5fa207a044..4856906108 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -47,7 +47,7 @@ static void show_one(const char *refname, const struct object_id *oid) } static int show_ref(const char *refname, const struct object_id *oid, - int flag, void *cbdata) + int flag UNUSED, void *cbdata UNUSED) { if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -77,8 +77,9 @@ match: return 0; } -static int add_existing(const char *refname, const struct object_id *oid, - int flag, void *cbdata) +static int add_existing(const char *refname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; string_list_insert(list, refname); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index f91e29b56a..287716db68 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -48,7 +48,7 @@ static char const * const builtin_sparse_checkout_list_usage[] = { NULL }; -static int sparse_checkout_list(int argc, const char **argv) +static int sparse_checkout_list(int argc, const char **argv, const char *prefix) { static struct option builtin_sparse_checkout_list_options[] = { OPT_END(), @@ -60,7 +60,7 @@ static int sparse_checkout_list(int argc, const char **argv) if (!core_apply_sparse_checkout) die(_("this worktree is not sparse")); - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_list_options, builtin_sparse_checkout_list_usage, 0); @@ -431,7 +431,7 @@ static struct sparse_checkout_init_opts { int sparse_index; } init_opts; -static int sparse_checkout_init(int argc, const char **argv) +static int sparse_checkout_init(int argc, const char **argv, const char *prefix) { struct pattern_list pl; char *sparse_filename; @@ -452,7 +452,7 @@ static int sparse_checkout_init(int argc, const char **argv) init_opts.cone_mode = -1; init_opts.sparse_index = -1; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_init_options, builtin_sparse_checkout_init_usage, 0); @@ -767,7 +767,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_add_options, builtin_sparse_checkout_add_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); sanitize_paths(argc, argv, prefix, add_opts.skip_checks); @@ -813,7 +813,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_set_options, builtin_sparse_checkout_set_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index)) return 1; @@ -843,7 +843,8 @@ static struct sparse_checkout_reapply_opts { int sparse_index; } reapply_opts; -static int sparse_checkout_reapply(int argc, const char **argv) +static int sparse_checkout_reapply(int argc, const char **argv, + const char *prefix) { static struct option builtin_sparse_checkout_reapply_options[] = { OPT_BOOL(0, "cone", &reapply_opts.cone_mode, @@ -859,7 +860,7 @@ static int sparse_checkout_reapply(int argc, const char **argv) reapply_opts.cone_mode = -1; reapply_opts.sparse_index = -1; - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_reapply_options, builtin_sparse_checkout_reapply_usage, 0); @@ -876,7 +877,8 @@ static char const * const builtin_sparse_checkout_disable_usage[] = { NULL }; -static int sparse_checkout_disable(int argc, const char **argv) +static int sparse_checkout_disable(int argc, const char **argv, + const char *prefix) { static struct option builtin_sparse_checkout_disable_options[] = { OPT_END(), @@ -895,7 +897,7 @@ static int sparse_checkout_disable(int argc, const char **argv) * forcibly return to a dense checkout regardless of initial state. */ - argc = parse_options(argc, argv, NULL, + argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_disable_options, builtin_sparse_checkout_disable_usage, 0); @@ -922,39 +924,25 @@ static int sparse_checkout_disable(int argc, const char **argv) int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) { - static struct option builtin_sparse_checkout_options[] = { + parse_opt_subcommand_fn *fn = NULL; + struct option builtin_sparse_checkout_options[] = { + OPT_SUBCOMMAND("list", &fn, sparse_checkout_list), + OPT_SUBCOMMAND("init", &fn, sparse_checkout_init), + OPT_SUBCOMMAND("set", &fn, sparse_checkout_set), + OPT_SUBCOMMAND("add", &fn, sparse_checkout_add), + OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply), + OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable), OPT_END(), }; - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(builtin_sparse_checkout_usage, - builtin_sparse_checkout_options); - argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_options, - builtin_sparse_checkout_usage, - PARSE_OPT_STOP_AT_NON_OPTION); + builtin_sparse_checkout_usage, 0); git_config(git_default_config, NULL); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; - if (argc > 0) { - if (!strcmp(argv[0], "list")) - return sparse_checkout_list(argc, argv); - if (!strcmp(argv[0], "init")) - return sparse_checkout_init(argc, argv); - if (!strcmp(argv[0], "set")) - return sparse_checkout_set(argc, argv, prefix); - if (!strcmp(argv[0], "add")) - return sparse_checkout_add(argc, argv, prefix); - if (!strcmp(argv[0], "reapply")) - return sparse_checkout_reapply(argc, argv); - if (!strcmp(argv[0], "disable")) - return sparse_checkout_disable(argc, argv); - } - - usage_with_options(builtin_sparse_checkout_usage, - builtin_sparse_checkout_options); + return fn(argc, argv, prefix); } diff --git a/builtin/stash.c b/builtin/stash.c index 30fa101460..2274aae255 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -638,9 +638,12 @@ cleanup: return ret; } -static int reject_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) +static int reject_reflog_ent(struct object_id *ooid UNUSED, + struct object_id *noid UNUSED, + const char *email UNUSED, + timestamp_t timestamp UNUSED, + int tz UNUSED, const char *message UNUSED, + void *cb_data UNUSED) { return 1; } @@ -782,7 +785,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_list_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (!ref_exists(ref_stash)) return 0; @@ -873,7 +876,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) init_revisions(&rev, prefix); argc = parse_options(argc, argv, prefix, options, git_stash_show_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_KEEP_DASHDASH); strvec_push(&revision_args, argv[0]); @@ -979,7 +982,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_store_usage, - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (argc != 1) { if (!quiet) @@ -1739,6 +1742,11 @@ static int push_stash(int argc, const char **argv, const char *prefix, include_untracked, only_staged); } +static int push_stash_unassumed(int argc, const char **argv, const char *prefix) +{ + return push_stash(argc, argv, prefix, 0); +} + static int save_stash(int argc, const char **argv, const char *prefix) { int keep_index = -1; @@ -1787,15 +1795,28 @@ int cmd_stash(int argc, const char **argv, const char *prefix) pid_t pid = getpid(); const char *index_file; struct strvec args = STRVEC_INIT; - + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("apply", &fn, apply_stash), + OPT_SUBCOMMAND("clear", &fn, clear_stash), + OPT_SUBCOMMAND("drop", &fn, drop_stash), + OPT_SUBCOMMAND("pop", &fn, pop_stash), + OPT_SUBCOMMAND("branch", &fn, branch_stash), + OPT_SUBCOMMAND("list", &fn, list_stash), + OPT_SUBCOMMAND("show", &fn, show_stash), + OPT_SUBCOMMAND("store", &fn, store_stash), + OPT_SUBCOMMAND("create", &fn, create_stash), + OPT_SUBCOMMAND("push", &fn, push_stash_unassumed), + OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE), OPT_END() }; git_config(git_stash_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_usage, - PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + PARSE_OPT_SUBCOMMAND_OPTIONAL | + PARSE_OPT_KEEP_UNKNOWN_OPT | + PARSE_OPT_KEEP_DASHDASH); prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -1804,33 +1825,10 @@ int cmd_stash(int argc, const char **argv, const char *prefix) strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); - if (!argc) - return !!push_stash(0, NULL, prefix, 0); - else if (!strcmp(argv[0], "apply")) - return !!apply_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "clear")) - return !!clear_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "drop")) - return !!drop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "pop")) - return !!pop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "branch")) - return !!branch_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "list")) - return !!list_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - return !!show_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "store")) - return !!store_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "create")) - return !!create_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix, 0); - else if (!strcmp(argv[0], "save")) - return !!save_stash(argc, argv, prefix); - else if (*argv[0] != '-') - usage_msg_optf(_("unknown subcommand: %s"), - git_stash_usage, options, argv[0]); + if (fn) + return !!fn(argc, argv, prefix); + else if (!argc) + return !!push_stash_unassumed(0, NULL, prefix); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c597df7528..0b4acb442b 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -31,45 +31,61 @@ typedef void (*each_submodule_fn)(const struct cache_entry *list_item, void *cb_data); -static char *repo_get_default_remote(struct repository *repo) +static int repo_get_default_remote(struct repository *repo, char **default_remote) { - char *dest = NULL, *ret; + char *dest = NULL; struct strbuf sb = STRBUF_INIT; struct ref_store *store = get_main_ref_store(repo); const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL, NULL); if (!refname) - die(_("No such ref: %s"), "HEAD"); + return die_message(_("No such ref: %s"), "HEAD"); /* detached HEAD */ - if (!strcmp(refname, "HEAD")) - return xstrdup("origin"); + if (!strcmp(refname, "HEAD")) { + *default_remote = xstrdup("origin"); + return 0; + } if (!skip_prefix(refname, "refs/heads/", &refname)) - die(_("Expecting a full ref name, got %s"), refname); + return die_message(_("Expecting a full ref name, got %s"), + refname); strbuf_addf(&sb, "branch.%s.remote", refname); if (repo_config_get_string(repo, sb.buf, &dest)) - ret = xstrdup("origin"); + *default_remote = xstrdup("origin"); else - ret = dest; + *default_remote = dest; strbuf_release(&sb); - return ret; + return 0; } -static char *get_default_remote_submodule(const char *module_path) +static int get_default_remote_submodule(const char *module_path, char **default_remote) { struct repository subrepo; + int ret; - repo_submodule_init(&subrepo, the_repository, module_path, null_oid()); - return repo_get_default_remote(&subrepo); + if (repo_submodule_init(&subrepo, the_repository, module_path, + null_oid()) < 0) + return die_message(_("could not get a repository handle for submodule '%s'"), + module_path); + ret = repo_get_default_remote(&subrepo, default_remote); + repo_clear(&subrepo); + + return ret; } static char *get_default_remote(void) { - return repo_get_default_remote(the_repository); + char *default_remote; + int code = repo_get_default_remote(the_repository, &default_remote); + + if (code) + exit(code); + + return default_remote; } static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet) @@ -96,32 +112,11 @@ static char *resolve_relative_url(const char *rel_url, const char *up_path, int return resolved_url; } -static int resolve_relative_url_test(int argc, const char **argv, const char *prefix) +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) { - char *remoteurl, *res; - const char *up_path, *url; - - if (argc != 4) - die("resolve-relative-url-test only accepts three arguments: "); - - up_path = argv[1]; - remoteurl = xstrdup(argv[2]); - url = argv[3]; - - if (!strcmp(up_path, "(null)")) - up_path = NULL; - - res = relative_url(remoteurl, url, up_path); - puts(res); - free(res); - free(remoteurl); - return 0; -} + const char *super_prefix = get_super_prefix(); -static char *do_get_submodule_displaypath(const char *path, - const char *prefix, - const char *super_prefix) -{ if (prefix && super_prefix) { BUG("cannot have prefix '%s' and superprefix '%s'", prefix, super_prefix); @@ -137,13 +132,6 @@ static char *do_get_submodule_displaypath(const char *path, } } -/* the result should be freed by the caller. */ -static char *get_submodule_displaypath(const char *path, const char *prefix) -{ - const char *super_prefix = get_super_prefix(); - return do_get_submodule_displaypath(path, prefix, super_prefix); -} - static char *compute_rev_name(const char *sub_path, const char* object_id) { struct strbuf sb = STRBUF_INIT; @@ -188,6 +176,11 @@ struct module_list { }; #define MODULE_LIST_INIT { 0 } +static void module_list_release(struct module_list *ml) +{ + free(ml->entries); +} + static int module_list_compute(int argc, const char **argv, const char *prefix, struct pathspec *pathspec, @@ -195,6 +188,7 @@ static int module_list_compute(int argc, const char **argv, { int i, result = 0; char *ps_matched = NULL; + parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL, prefix, argv); @@ -249,7 +243,7 @@ static void module_list_active(struct module_list *list) active_modules.entries[active_modules.nr++] = ce; } - free(list->entries); + module_list_release(list); *list = active_modules; } @@ -272,49 +266,11 @@ static char *get_up_path(const char *path) return strbuf_detach(&sb, NULL); } -static int module_list(int argc, const char **argv, const char *prefix) -{ - int i; - struct pathspec pathspec; - struct module_list list = MODULE_LIST_INIT; - - struct option module_list_options[] = { - OPT_STRING(0, "prefix", &prefix, - N_("path"), - N_("alternative anchor for relative paths")), - OPT_END() - }; - - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper list [--prefix=] [...]"), - NULL - }; - - argc = parse_options(argc, argv, prefix, module_list_options, - git_submodule_helper_usage, 0); - - if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; - - for (i = 0; i < list.nr; i++) { - const struct cache_entry *ce = list.entries[i]; - - if (ce_stage(ce)) - printf("%06o %s U\t", ce->ce_mode, - oid_to_hex(null_oid())); - else - printf("%06o %s %d\t", ce->ce_mode, - oid_to_hex(&ce->oid), ce_stage(ce)); - - fprintf(stdout, "%s\n", ce->name); - } - return 0; -} - static void for_each_listed_submodule(const struct module_list *list, each_submodule_fn fn, void *cb_data) { int i; + for (i = 0; i < list->nr; i++) fn(list->entries[i], cb_data); } @@ -334,7 +290,6 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, struct foreach_cb *info = cb_data; const char *path = list_item->name; const struct object_id *ce_oid = &list_item->oid; - const struct submodule *sub; struct child_process cp = CHILD_PROCESS_INIT; char *displaypath; @@ -433,26 +388,25 @@ cleanup: static int module_foreach(int argc, const char **argv, const char *prefix) { struct foreach_cb info = FOREACH_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; - struct option module_foreach_options[] = { OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")), OPT_BOOL(0, "recursive", &info.recursive, N_("recurse into nested submodules")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper foreach [--quiet] [--recursive] [--] "), + N_("git submodule foreach [--quiet] [--recursive] [--] "), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_foreach_options, git_submodule_helper_usage, 0); if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.argc = argc; info.argv = argv; @@ -460,7 +414,11 @@ static int module_foreach(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } static int starts_with_dot_slash(const char *const path) @@ -477,22 +435,19 @@ static int starts_with_dot_dot_slash(const char *const path) struct init_cb { const char *prefix; - const char *superprefix; unsigned int flags; }; #define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, - const char *superprefix, unsigned int flags) + unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; - char *upd = NULL, *url = NULL, *displaypath; + const char *upd; + char *url = NULL, *displaypath; - /* try superprefix from the environment, if it is not passed explicitly */ - if (!superprefix) - superprefix = get_super_prefix(); - displaypath = do_get_submodule_displaypath(path, prefix, superprefix); + displaypath = get_submodule_displaypath(path, prefix); sub = submodule_from_path(the_repository, null_oid(), path); @@ -529,6 +484,7 @@ static void init_submodule(const char *path, const char *prefix, if (starts_with_dot_dot_slash(url) || starts_with_dot_slash(url)) { char *oldurl = url; + url = resolve_relative_url(oldurl, NULL, 0); free(oldurl); } @@ -545,14 +501,15 @@ static void init_submodule(const char *path, const char *prefix, /* Copy "update" setting when it is not set yet */ strbuf_addf(&sb, "submodule.%s.update", sub->name); - if (git_config_get_string(sb.buf, &upd) && + if (git_config_get_string_tmp(sb.buf, &upd) && sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { if (sub->update_strategy.type == SM_UPDATE_COMMAND) { fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"), sub->name); - upd = xstrdup("none"); - } else - upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy)); + upd = "none"; + } else { + upd = submodule_update_type_to_string(sub->update_strategy.type); + } if (git_config_set_gently(sb.buf, upd)) die(_("Failed to register update mode for submodule path '%s'"), displaypath); @@ -560,37 +517,36 @@ static void init_submodule(const char *path, const char *prefix, strbuf_release(&sb); free(displaypath); free(url); - free(upd); } static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct init_cb *info = cb_data; - init_submodule(list_item->name, info->prefix, info->superprefix, info->flags); + + init_submodule(list_item->name, info->prefix, info->flags); } static int module_init(int argc, const char **argv, const char *prefix) { struct init_cb info = INIT_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; - struct option module_init_options[] = { OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper init [] []"), + N_("git submodule init [] []"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_init_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; /* * If there are no path args and submodule.active is set then, @@ -605,7 +561,11 @@ static int module_init(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, init_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct status_cb { @@ -623,20 +583,23 @@ static void print_status(unsigned int flags, char state, const char *path, printf("%c%s %s", state, oid_to_hex(oid), displaypath); if (state == ' ' || state == '+') { - const char *name = compute_rev_name(path, oid_to_hex(oid)); + char *name = compute_rev_name(path, oid_to_hex(oid)); if (name) printf(" (%s)", name); + free(name); } printf("\n"); } -static int handle_submodule_head_ref(const char *refname, - const struct object_id *oid, int flags, +static int handle_submodule_head_ref(const char *refname UNUSED, + const struct object_id *oid, + int flags UNUSED, void *cb_data) { struct object_id *output = cb_data; + if (oid) oidcpy(output, oid); @@ -743,6 +706,7 @@ static void status_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct status_cb *info = cb_data; + status_submodule(list_item->name, &list_item->oid, list_item->ce_flags, info->prefix, info->flags); } @@ -750,27 +714,26 @@ static void status_submodule_cb(const struct cache_entry *list_item, static int module_status(int argc, const char **argv, const char *prefix) { struct status_cb info = STATUS_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; - struct option module_status_options[] = { OPT__QUIET(&quiet, N_("suppress submodule status output")), OPT_BIT(0, "cached", &info.flags, N_("use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule status [--quiet] [--cached] [--recursive] [...]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_status_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -778,25 +741,11 @@ static int module_status(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, status_submodule_cb, &info); - return 0; -} - -static int module_name(int argc, const char **argv, const char *prefix) -{ - const struct submodule *sub; - - if (argc != 2) - usage(_("git submodule--helper name ")); - - sub = submodule_from_path(the_repository, null_oid(), argv[1]); - - if (!sub) - die(_("no submodule mapping found in .gitmodules for path '%s'"), - argv[1]); - - printf("%s\n", sub->name); - - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct module_cb { @@ -805,16 +754,34 @@ struct module_cb { struct object_id oid_src; struct object_id oid_dst; char status; - const char *sm_path; + char *sm_path; }; #define MODULE_CB_INIT { 0 } +static void module_cb_release(struct module_cb *mcb) +{ + free(mcb->sm_path); +} + struct module_cb_list { struct module_cb **entries; int alloc, nr; }; #define MODULE_CB_LIST_INIT { 0 } +static void module_cb_list_release(struct module_cb_list *mcbl) +{ + int i; + + for (i = 0; i < mcbl->nr; i++) { + struct module_cb *mcb = mcbl->entries[i]; + + module_cb_release(mcb); + free(mcb); + } + free(mcbl->entries); +} + struct summary_cb { int argc; const char **argv; @@ -851,7 +818,7 @@ static char *verify_submodule_committish(const char *sm_path, return strbuf_detach(&result, NULL); } -static void print_submodule_summary(struct summary_cb *info, char *errmsg, +static void print_submodule_summary(struct summary_cb *info, const char *errmsg, int total_commits, const char *displaypath, const char *src_abbrev, const char *dst_abbrev, struct module_cb *p) @@ -909,12 +876,13 @@ static void generate_submodule_summary(struct summary_cb *info, { char *displaypath, *src_abbrev = NULL, *dst_abbrev; int missing_src = 0, missing_dst = 0; - char *errmsg = NULL; + struct strbuf errmsg = STRBUF_INIT; int total_commits = -1; if (!info->cached && oideq(&p->oid_dst, null_oid())) { if (S_ISGITLINK(p->mod_dst)) { struct ref_store *refs = get_submodule_ref_store(p->sm_path); + if (refs) refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst); } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) { @@ -1009,28 +977,27 @@ static void generate_submodule_summary(struct summary_cb *info, * submodule, i.e., deleted or changed to blob */ if (S_ISGITLINK(p->mod_dst)) { - struct strbuf errmsg_str = STRBUF_INIT; if (missing_src && missing_dst) { - strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commits %s and %s\n", + strbuf_addf(&errmsg, " Warn: %s doesn't contain commits %s and %s\n", displaypath, oid_to_hex(&p->oid_src), oid_to_hex(&p->oid_dst)); } else { - strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commit %s\n", + strbuf_addf(&errmsg, " Warn: %s doesn't contain commit %s\n", displaypath, missing_src ? oid_to_hex(&p->oid_src) : oid_to_hex(&p->oid_dst)); } - errmsg = strbuf_detach(&errmsg_str, NULL); } } - print_submodule_summary(info, errmsg, total_commits, - displaypath, src_abbrev, + print_submodule_summary(info, errmsg.len ? errmsg.buf : NULL, + total_commits, displaypath, src_abbrev, dst_abbrev, p); free(displaypath); free(src_abbrev); free(dst_abbrev); + strbuf_release(&errmsg); } static void prepare_submodule_summary(struct summary_cb *info, @@ -1114,6 +1081,9 @@ static int compute_summary_module_list(struct object_id *head_oid, { struct strvec diff_args = STRVEC_INIT; struct rev_info rev; + struct setup_revision_opt opt = { + .free_removed_argv_elements = 1, + }; struct module_cb_list list = MODULE_CB_LIST_INIT; int ret = 0; @@ -1131,7 +1101,7 @@ static int compute_summary_module_list(struct object_id *head_oid, init_revisions(&rev, info->prefix); rev.abbrev = 0; precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); - setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); + setup_revisions(diff_args.nr, diff_args.v, &rev, &opt); rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = submodule_summary_callback; rev.diffopt.format_callback_data = &list; @@ -1158,6 +1128,7 @@ static int compute_summary_module_list(struct object_id *head_oid, cleanup: strvec_clear(&diff_args); release_revisions(&rev); + module_cb_list_release(&list); return ret; } @@ -1171,7 +1142,6 @@ static int module_summary(int argc, const char **argv, const char *prefix) enum diff_cmd diff_cmd = DIFF_INDEX; struct object_id head_oid; int ret; - struct option module_summary_options[] = { OPT_BOOL(0, "cached", &cached, N_("use the commit stored in the index instead of the submodule HEAD")), @@ -1183,9 +1153,8 @@ static int module_summary(int argc, const char **argv, const char *prefix) N_("limit the summary size")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper summary [] [] [--] []"), + N_("git submodule summary [] [] [--] []"), NULL }; @@ -1245,6 +1214,7 @@ static void sync_submodule(const char *path, const char *prefix, char *sub_origin_url, *super_config_url, *displaypath, *default_remote; struct strbuf sb = STRBUF_INIT; char *sub_config_path = NULL; + int code; if (!is_submodule_active(the_repository, path)) return; @@ -1255,6 +1225,7 @@ static void sync_submodule(const char *path, const char *prefix, if (starts_with_dot_dot_slash(sub->url) || starts_with_dot_slash(sub->url)) { char *up_path = get_up_path(path); + sub_origin_url = resolve_relative_url(sub->url, up_path, 1); super_config_url = resolve_relative_url(sub->url, NULL, 1); free(up_path); @@ -1283,10 +1254,9 @@ static void sync_submodule(const char *path, const char *prefix, goto cleanup; strbuf_reset(&sb); - default_remote = get_default_remote_submodule(path); - if (!default_remote) - die(_("failed to get the default remote for submodule '%s'"), - path); + code = get_default_remote_submodule(path, &default_remote); + if (code) + exit(code); remote_key = xstrfmt("remote.%s.url", default_remote); free(default_remote); @@ -1330,34 +1300,34 @@ cleanup: static void sync_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct sync_cb *info = cb_data; + sync_submodule(list_item->name, info->prefix, info->flags); } static int module_sync(int argc, const char **argv, const char *prefix) { struct sync_cb info = SYNC_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; int recursive = 0; - struct option module_sync_options[] = { OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")), OPT_BOOL(0, "recursive", &recursive, N_("recurse into nested submodules")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper sync [--quiet] [--recursive] []"), + N_("git submodule sync [--quiet] [--recursive] []"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_sync_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -1367,7 +1337,11 @@ static int module_sync(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, sync_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct deinit_cb { @@ -1411,6 +1385,7 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_FORCE)) { struct child_process cp_rm = CHILD_PROCESS_INIT; + cp_rm.git_cmd = 1; strvec_pushl(&cp_rm.args, "rm", "-qn", path, NULL); @@ -1447,6 +1422,7 @@ static void deinit_submodule(const char *path, const char *prefix, /* remove the .git/config entries (unless the user already did it) */ if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) { char *sub_key = xstrfmt("submodule.%s", sub->name); + /* * remove the whole section so we have a clean state when * the user later decides to init this submodule again @@ -1474,23 +1450,22 @@ static void deinit_submodule_cb(const struct cache_entry *list_item, static int module_deinit(int argc, const char **argv, const char *prefix) { struct deinit_cb info = DEINIT_CB_INIT; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; int quiet = 0; int force = 0; int all = 0; - struct option module_deinit_options[] = { OPT__QUIET(&quiet, N_("suppress submodule status output")), OPT__FORCE(&force, N_("remove submodule working trees even if they contain local changes"), 0), OPT_BOOL(0, "all", &all, N_("unregister all submodules")), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule deinit [--quiet] [-f | --force] [--all | [--] [...]]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, module_deinit_options, git_submodule_helper_usage, 0); @@ -1505,7 +1480,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix) die(_("Use '--all' if you really want to deinitialize all submodules")); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; info.prefix = prefix; if (quiet) @@ -1515,7 +1490,11 @@ static int module_deinit(int argc, const char **argv, const char *prefix) for_each_listed_submodule(&list, deinit_submodule_cb, &info); - return 0; + ret = 0; +cleanup: + module_list_release(&list); + clear_pathspec(&pathspec); + return ret; } struct module_clone_data { @@ -1525,7 +1504,6 @@ struct module_clone_data { const char *url; const char *depth; struct list_objects_filter_options *filter_options; - struct string_list reference; unsigned int quiet: 1; unsigned int progress: 1; unsigned int dissociate: 1; @@ -1533,7 +1511,6 @@ struct module_clone_data { int single_branch; }; #define MODULE_CLONE_DATA_INIT { \ - .reference = STRING_LIST_INIT_NODUP, \ .single_branch = -1, \ } @@ -1574,7 +1551,9 @@ static int add_possible_reference_from_superproject( struct strbuf err = STRBUF_INIT; strbuf_add(&sb, odb->path, len); - repo_init(&alternate, sb.buf, NULL); + if (repo_init(&alternate, sb.buf, NULL) < 0) + die(_("could not get a repository handle for gitdir '%s'"), + sb.buf); /* * We need to end the new path with '/' to mark it as a dir, @@ -1589,7 +1568,9 @@ static int add_possible_reference_from_superproject( sm_alternate = compute_alternate_path(sb.buf, &err); if (sm_alternate) { - string_list_append(sas->reference, xstrdup(sb.buf)); + char *p = strbuf_detach(&sb, NULL); + + string_list_append(sas->reference, p)->util = p; free(sm_alternate); } else { switch (sas->error_mode) { @@ -1648,23 +1629,31 @@ static void prepare_possible_alternates(const char *sm_name, free(error_strategy); } -static int clone_submodule(struct module_clone_data *clone_data) +static char *clone_submodule_sm_gitdir(const char *name) { - char *p, *sm_gitdir; - char *sm_alternate = NULL, *error_strategy = NULL; struct strbuf sb = STRBUF_INIT; - struct child_process cp = CHILD_PROCESS_INIT; + char *sm_gitdir; - submodule_name_to_gitdir(&sb, the_repository, clone_data->name); + submodule_name_to_gitdir(&sb, the_repository, name); sm_gitdir = absolute_pathdup(sb.buf); - strbuf_reset(&sb); + strbuf_release(&sb); - if (!is_absolute_path(clone_data->path)) { - strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path); - clone_data->path = strbuf_detach(&sb, NULL); - } else { - clone_data->path = xstrdup(clone_data->path); - } + return sm_gitdir; +} + +static int clone_submodule(const struct module_clone_data *clone_data, + struct string_list *reference) +{ + char *p; + char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name); + char *sm_alternate = NULL, *error_strategy = NULL; + struct child_process cp = CHILD_PROCESS_INIT; + const char *clone_data_path = clone_data->path; + char *to_free = NULL; + + if (!is_absolute_path(clone_data->path)) + clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(), + clone_data->path); if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) die(_("refusing to create/use '%s' in another submodule's " @@ -1674,7 +1663,7 @@ static int clone_submodule(struct module_clone_data *clone_data) if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); - prepare_possible_alternates(clone_data->name, &clone_data->reference); + prepare_possible_alternates(clone_data->name, reference); strvec_push(&cp.args, "clone"); strvec_push(&cp.args, "--no-checkout"); @@ -1684,9 +1673,10 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--progress"); if (clone_data->depth && *(clone_data->depth)) strvec_pushl(&cp.args, "--depth", clone_data->depth, NULL); - if (clone_data->reference.nr) { + if (reference->nr) { struct string_list_item *item; - for_each_string_list_item(item, &clone_data->reference) + + for_each_string_list_item(item, reference) strvec_pushl(&cp.args, "--reference", item->string, NULL); } @@ -1705,7 +1695,7 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--"); strvec_push(&cp.args, clone_data->url); - strvec_push(&cp.args, clone_data->path); + strvec_push(&cp.args, clone_data_path); cp.git_cmd = 1; prepare_submodule_repo_env(&cp.env); @@ -1713,23 +1703,25 @@ static int clone_submodule(struct module_clone_data *clone_data) if(run_command(&cp)) die(_("clone of '%s' into submodule path '%s' failed"), - clone_data->url, clone_data->path); + clone_data->url, clone_data_path); } else { - if (clone_data->require_init && !access(clone_data->path, X_OK) && - !is_empty_dir(clone_data->path)) - die(_("directory not empty: '%s'"), clone_data->path); - if (safe_create_leading_directories_const(clone_data->path) < 0) - die(_("could not create directory '%s'"), clone_data->path); - strbuf_addf(&sb, "%s/index", sm_gitdir); - unlink_or_warn(sb.buf); - strbuf_reset(&sb); + char *path; + + if (clone_data->require_init && !access(clone_data_path, X_OK) && + !is_empty_dir(clone_data_path)) + die(_("directory not empty: '%s'"), clone_data_path); + if (safe_create_leading_directories_const(clone_data_path) < 0) + die(_("could not create directory '%s'"), clone_data_path); + path = xstrfmt("%s/index", sm_gitdir); + unlink_or_warn(path); + free(path); } - connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0); + connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0); - p = git_pathdup_submodule(clone_data->path, "config"); + p = git_pathdup_submodule(clone_data_path, "config"); if (!p) - die(_("could not get submodule directory for '%s'"), clone_data->path); + die(_("could not get submodule directory for '%s'"), clone_data_path); /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ git_config_get_string("submodule.alternateLocation", &sm_alternate); @@ -1744,9 +1736,9 @@ static int clone_submodule(struct module_clone_data *clone_data) free(sm_alternate); free(error_strategy); - strbuf_release(&sb); free(sm_gitdir); free(p); + free(to_free); return 0; } @@ -1754,7 +1746,9 @@ static int module_clone(int argc, const char **argv, const char *prefix) { int dissociate = 0, quiet = 0, progress = 0, require_init = 0; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; - struct list_objects_filter_options filter_options; + struct string_list reference = STRING_LIST_INIT_NODUP; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &clone_data.prefix, @@ -1769,7 +1763,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_STRING(0, "url", &clone_data.url, N_("string"), N_("url where to clone the submodule from")), - OPT_STRING_LIST(0, "reference", &clone_data.reference, + OPT_STRING_LIST(0, "reference", &reference, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &dissociate, @@ -1787,7 +1781,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=] [--quiet] " "[--reference ] [--name ] [--depth ] " @@ -1796,7 +1789,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) NULL }; - memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); @@ -1810,31 +1802,33 @@ static int module_clone(int argc, const char **argv, const char *prefix) usage_with_options(git_submodule_helper_usage, module_clone_options); - clone_submodule(&clone_data); + clone_submodule(&clone_data, &reference); list_objects_filter_release(&filter_options); + string_list_clear(&reference, 1); return 0; } -static void determine_submodule_update_strategy(struct repository *r, - int just_cloned, - const char *path, - const char *update, - struct submodule_update_strategy *out) +static int determine_submodule_update_strategy(struct repository *r, + int just_cloned, + const char *path, + enum submodule_update_type update, + struct submodule_update_strategy *out) { const struct submodule *sub = submodule_from_path(r, null_oid(), path); char *key; const char *val; + int ret; key = xstrfmt("submodule.%s.update", sub->name); if (update) { - if (parse_submodule_update_strategy(update, out) < 0) - die(_("Invalid update mode '%s' for submodule path '%s'"), - update, path); + out->type = update; } else if (!repo_config_get_string_tmp(r, key, &val)) { - if (parse_submodule_update_strategy(val, out) < 0) - die(_("Invalid update mode '%s' configured for submodule path '%s'"), - val, path); + if (parse_submodule_update_strategy(val, out) < 0) { + ret = die_message(_("Invalid update mode '%s' configured for submodule path '%s'"), + val, path); + goto cleanup; + } } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { if (sub->update_strategy.type == SM_UPDATE_COMMAND) BUG("how did we read update = !command from .gitmodules?"); @@ -1849,7 +1843,10 @@ static void determine_submodule_update_strategy(struct repository *r, out->type == SM_UPDATE_NONE)) out->type = SM_UPDATE_CHECKOUT; + ret = 0; +cleanup: free(key); + return ret; } struct update_clone_data { @@ -1863,7 +1860,7 @@ struct submodule_update_clone { int current; /* configuration parameters which are passed on to the children */ - struct update_data *update_data; + const struct update_data *update_data; /* to be consumed by update_submodule() */ struct update_clone_data *update_clone; @@ -1878,11 +1875,16 @@ struct submodule_update_clone { }; #define SUBMODULE_UPDATE_CLONE_INIT { 0 } +static void submodule_update_clone_release(struct submodule_update_clone *suc) +{ + free(suc->update_clone); + free(suc->failed_clones); +} + struct update_data { const char *prefix; - const char *recursive_prefix; - const char *displaypath; - const char *update_default; + char *displaypath; + enum submodule_update_type update_default; struct object_id suboid; struct string_list references; struct submodule_update_strategy update_strategy; @@ -1917,6 +1919,12 @@ struct update_data { .max_jobs = 1, \ } +static void update_data_release(struct update_data *ud) +{ + free(ud->displaypath); + module_list_release(&ud->list); +} + static void next_submodule_warn_missing(struct submodule_update_clone *suc, struct strbuf *out, const char *displaypath) { @@ -1949,30 +1957,20 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, const char *update_string; enum submodule_update_type update_type; char *key; - struct strbuf displaypath_sb = STRBUF_INIT; + const struct update_data *ud = suc->update_data; + char *displaypath = get_submodule_displaypath(ce->name, ud->prefix); struct strbuf sb = STRBUF_INIT; - const char *displaypath = NULL; int needs_cloning = 0; int need_free_url = 0; if (ce_stage(ce)) { - if (suc->update_data->recursive_prefix) - strbuf_addf(&sb, "%s/%s", suc->update_data->recursive_prefix, ce->name); - else - strbuf_addstr(&sb, ce->name); - strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addf(out, _("Skipping unmerged submodule %s"), displaypath); strbuf_addch(out, '\n'); goto cleanup; } sub = submodule_from_path(the_repository, null_oid(), ce->name); - if (suc->update_data->recursive_prefix) - displaypath = relative_path(suc->update_data->recursive_prefix, - ce->name, &displaypath_sb); - else - displaypath = ce->name; - if (!sub) { next_submodule_warn_missing(suc, out, displaypath); goto cleanup; @@ -2051,6 +2049,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strvec_pushl(&child->args, "--url", url, NULL); if (suc->update_data->references.nr) { struct string_list_item *item; + for_each_string_list_item(item, &suc->update_data->references) strvec_pushl(&child->args, "--reference", item->string, NULL); } @@ -2062,7 +2061,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, "--no-single-branch"); cleanup: - strbuf_release(&displaypath_sb); + free(displaypath); strbuf_release(&sb); if (need_free_url) free((void*)url); @@ -2083,6 +2082,7 @@ static int update_clone_get_next_task(struct child_process *child, ce = suc->update_data->list.entries[suc->current]; if (prepare_to_clone_next_submodule(ce, child, suc, err)) { int *p = xmalloc(sizeof(*p)); + *p = suc->current; *idx_task_cb = p; suc->current++; @@ -2098,6 +2098,7 @@ static int update_clone_get_next_task(struct child_process *child, index = suc->current - suc->update_data->list.nr; if (index < suc->failed_clones_nr) { int *p; + ce = suc->failed_clones[index]; if (!prepare_to_clone_next_submodule(ce, child, suc, err)) { suc->current ++; @@ -2121,6 +2122,7 @@ static int update_clone_start_failure(struct strbuf *err, void *idx_task_cb) { struct submodule_update_clone *suc = suc_cb; + suc->quickstop = 1; return 1; } @@ -2132,9 +2134,9 @@ static int update_clone_task_finished(int result, { const struct cache_entry *ce; struct submodule_update_clone *suc = suc_cb; - int *idxP = idx_task_cb; int idx = *idxP; + free(idxP); if (!result) @@ -2167,19 +2169,20 @@ static int git_update_clone_config(const char *var, const char *value, void *cb) { int *max_jobs = cb; + if (!strcmp(var, "submodule.fetchjobs")) *max_jobs = parse_submodule_fetchjobs(var, value); return 0; } -static int is_tip_reachable(const char *path, struct object_id *oid) +static int is_tip_reachable(const char *path, const struct object_id *oid) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf rev = STRBUF_INIT; char *hex = oid_to_hex(oid); cp.git_cmd = 1; - cp.dir = xstrdup(path); + cp.dir = path; cp.no_stderr = 1; strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL); @@ -2191,13 +2194,14 @@ static int is_tip_reachable(const char *path, struct object_id *oid) return 1; } -static int fetch_in_submodule(const char *module_path, int depth, int quiet, struct object_id *oid) +static int fetch_in_submodule(const char *module_path, int depth, int quiet, + const struct object_id *oid) { struct child_process cp = CHILD_PROCESS_INIT; prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; - cp.dir = xstrdup(module_path); + cp.dir = module_path; strvec_push(&cp.args, "fetch"); if (quiet) @@ -2207,6 +2211,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str if (oid) { char *hex = oid_to_hex(oid); char *remote = get_default_remote(); + strvec_pushl(&cp.args, remote, hex, NULL); free(remote); } @@ -2214,11 +2219,11 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str return run_command(&cp); } -static int run_update_command(struct update_data *ud, int subforce) +static int run_update_command(const struct update_data *ud, int subforce) { struct child_process cp = CHILD_PROCESS_INIT; char *oid = oid_to_hex(&ud->oid); - int must_die_on_failure = 0; + int ret; switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: @@ -2232,55 +2237,50 @@ static int run_update_command(struct update_data *ud, int subforce) strvec_push(&cp.args, "rebase"); if (ud->quiet) strvec_push(&cp.args, "--quiet"); - must_die_on_failure = 1; break; case SM_UPDATE_MERGE: cp.git_cmd = 1; strvec_push(&cp.args, "merge"); if (ud->quiet) strvec_push(&cp.args, "--quiet"); - must_die_on_failure = 1; break; case SM_UPDATE_COMMAND: cp.use_shell = 1; strvec_push(&cp.args, ud->update_strategy.command); - must_die_on_failure = 1; break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } strvec_push(&cp.args, oid); - cp.dir = xstrdup(ud->sm_path); + cp.dir = ud->sm_path; prepare_submodule_repo_env(&cp.env); - if (run_command(&cp)) { + if ((ret = run_command(&cp))) { switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: die_message(_("Unable to checkout '%s' in submodule path '%s'"), oid, ud->displaypath); + /* No "ret" assignment, use "git checkout"'s */ break; case SM_UPDATE_REBASE: - die_message(_("Unable to rebase '%s' in submodule path '%s'"), - oid, ud->displaypath); + ret = die_message(_("Unable to rebase '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_MERGE: - die_message(_("Unable to merge '%s' in submodule path '%s'"), - oid, ud->displaypath); + ret = die_message(_("Unable to merge '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_COMMAND: - die_message(_("Execution of '%s %s' failed in submodule path '%s'"), - ud->update_strategy.command, oid, ud->displaypath); + ret = die_message(_("Execution of '%s %s' failed in submodule path '%s'"), + ud->update_strategy.command, oid, ud->displaypath); break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } - if (must_die_on_failure) - exit(128); - /* the command failed, but update must continue */ - return 1; + return ret; } if (ud->quiet) @@ -2304,14 +2304,14 @@ static int run_update_command(struct update_data *ud, int subforce) ud->displaypath, ud->update_strategy.command, oid); break; default: - BUG("unexpected update strategy type: %s", - submodule_strategy_to_string(&ud->update_strategy)); + BUG("unexpected update strategy type: %d", + ud->update_strategy.type); } return 0; } -static int run_update_procedure(struct update_data *ud) +static int run_update_procedure(const struct update_data *ud) { int subforce = is_null_oid(&ud->suboid) || ud->force; @@ -2333,59 +2333,67 @@ static int run_update_procedure(struct update_data *ud) */ if (!is_tip_reachable(ud->sm_path, &ud->oid) && fetch_in_submodule(ud->sm_path, ud->depth, ud->quiet, &ud->oid)) - die(_("Fetched in submodule path '%s', but it did not " - "contain %s. Direct fetching of that commit failed."), - ud->displaypath, oid_to_hex(&ud->oid)); + return die_message(_("Fetched in submodule path '%s', but it did not " + "contain %s. Direct fetching of that commit failed."), + ud->displaypath, oid_to_hex(&ud->oid)); } return run_update_command(ud, subforce); } -static const char *remote_submodule_branch(const char *path) +static int remote_submodule_branch(const char *path, const char **branch) { const struct submodule *sub; - const char *branch = NULL; char *key; + *branch = NULL; sub = submodule_from_path(the_repository, null_oid(), path); if (!sub) - return NULL; + return die_message(_("could not initialize submodule at path '%s'"), + path); key = xstrfmt("submodule.%s.branch", sub->name); - if (repo_config_get_string_tmp(the_repository, key, &branch)) - branch = sub->branch; + if (repo_config_get_string_tmp(the_repository, key, branch)) + *branch = sub->branch; free(key); - if (!branch) - return "HEAD"; + if (!*branch) { + *branch = "HEAD"; + return 0; + } - if (!strcmp(branch, ".")) { + if (!strcmp(*branch, ".")) { const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) - die(_("No such ref: %s"), "HEAD"); + return die_message(_("No such ref: %s"), "HEAD"); /* detached HEAD */ if (!strcmp(refname, "HEAD")) - die(_("Submodule (%s) branch configured to inherit " - "branch from superproject, but the superproject " - "is not on any branch"), sub->name); + return die_message(_("Submodule (%s) branch configured to inherit " + "branch from superproject, but the superproject " + "is not on any branch"), sub->name); if (!skip_prefix(refname, "refs/heads/", &refname)) - die(_("Expecting a full ref name, got %s"), refname); - return refname; + return die_message(_("Expecting a full ref name, got %s"), + refname); + + *branch = refname; + return 0; } - return branch; + /* Our "branch" is coming from repo_config_get_string_tmp() */ + return 0; } -static void ensure_core_worktree(const char *path) +static int ensure_core_worktree(const char *path) { const char *cw; struct repository subrepo; if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) - die(_("could not get a repository handle for submodule '%s'"), path); + return die_message(_("could not get a repository handle for submodule '%s'"), + path); if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { char *cfg_file, *abs_path; @@ -2403,15 +2411,39 @@ static void ensure_core_worktree(const char *path) free(abs_path); strbuf_release(&sb); } + + repo_clear(&subrepo); + return 0; +} + +static const char *submodule_update_type_to_label(enum submodule_update_type type) +{ + switch (type) { + case SM_UPDATE_CHECKOUT: + return "checkout"; + case SM_UPDATE_MERGE: + return "merge"; + case SM_UPDATE_REBASE: + return "rebase"; + case SM_UPDATE_UNSPECIFIED: + case SM_UPDATE_NONE: + case SM_UPDATE_COMMAND: + break; + } + BUG("unreachable with type %d", type); } -static void update_data_to_args(struct update_data *update_data, struct strvec *args) +static void update_data_to_args(const struct update_data *update_data, + struct strvec *args) { + enum submodule_update_type update_type = update_data->update_default; + + if (update_data->displaypath) { + strvec_push(args, "--super-prefix"); + strvec_pushf(args, "%s/", update_data->displaypath); + } strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL); strvec_pushf(args, "--jobs=%d", update_data->max_jobs); - if (update_data->recursive_prefix) - strvec_pushl(args, "--recursive-prefix", - update_data->recursive_prefix, NULL); if (update_data->quiet) strvec_push(args, "--quiet"); if (update_data->force) @@ -2430,10 +2462,13 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * strvec_push(args, "--require-init"); if (update_data->depth) strvec_pushf(args, "--depth=%d", update_data->depth); - if (update_data->update_default) - strvec_pushl(args, "--update", update_data->update_default, NULL); + if (update_type != SM_UPDATE_UNSPECIFIED) + strvec_pushf(args, "--%s", + submodule_update_type_to_label(update_type)); + if (update_data->references.nr) { struct string_list_item *item; + for_each_string_list_item(item, &update_data->references) strvec_pushl(args, "--reference", item->string, NULL); } @@ -2453,66 +2488,62 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * static int update_submodule(struct update_data *update_data) { - char *prefixed_path; - - ensure_core_worktree(update_data->sm_path); - - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrdup(update_data->sm_path); - - update_data->displaypath = get_submodule_displaypath(prefixed_path, - update_data->prefix); - free(prefixed_path); + int ret; - determine_submodule_update_strategy(the_repository, update_data->just_cloned, - update_data->sm_path, update_data->update_default, - &update_data->update_strategy); + ret = determine_submodule_update_strategy(the_repository, + update_data->just_cloned, + update_data->sm_path, + update_data->update_default, + &update_data->update_strategy); + if (ret) + return ret; if (update_data->just_cloned) oidcpy(&update_data->suboid, null_oid()); else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid)) - die(_("Unable to find current revision in submodule path '%s'"), - update_data->displaypath); + return die_message(_("Unable to find current revision in submodule path '%s'"), + update_data->displaypath); if (update_data->remote) { - char *remote_name = get_default_remote_submodule(update_data->sm_path); - const char *branch = remote_submodule_branch(update_data->sm_path); - char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + char *remote_name; + const char *branch; + char *remote_ref; + int code; + + code = get_default_remote_submodule(update_data->sm_path, &remote_name); + if (code) + return code; + code = remote_submodule_branch(update_data->sm_path, &branch); + if (code) + return code; + remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + + free(remote_name); if (!update_data->nofetch) { if (fetch_in_submodule(update_data->sm_path, update_data->depth, 0, NULL)) - die(_("Unable to fetch in submodule path '%s'"), - update_data->sm_path); + return die_message(_("Unable to fetch in submodule path '%s'"), + update_data->sm_path); } if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid)) - die(_("Unable to find %s revision in submodule path '%s'"), - remote_ref, update_data->sm_path); + return die_message(_("Unable to find %s revision in submodule path '%s'"), + remote_ref, update_data->sm_path); free(remote_ref); } - if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) - if (run_update_procedure(update_data)) - return 1; + if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) { + ret = run_update_procedure(update_data); + if (ret) + return ret; + } if (update_data->recursive) { struct child_process cp = CHILD_PROCESS_INIT; struct update_data next = *update_data; - int res; - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrfmt("%s/", update_data->sm_path); - - next.recursive_prefix = get_submodule_displaypath(prefixed_path, - update_data->prefix); next.prefix = NULL; oidcpy(&next.oid, null_oid()); oidcpy(&next.suboid, null_oid()); @@ -2522,16 +2553,11 @@ static int update_submodule(struct update_data *update_data) prepare_submodule_repo_env(&cp.env); update_data_to_args(&next, &cp.args); - /* die() if child process die()'d */ - res = run_command(&cp); - if (!res) - return 0; - die_message(_("Failed to recurse into submodule path '%s'"), - update_data->displaypath); - if (res == 128) - exit(res); - else if (res) - return 1; + ret = run_command(&cp); + if (ret) + die_message(_("Failed to recurse into submodule path '%s'"), + update_data->displaypath); + return ret; } return 0; @@ -2539,7 +2565,7 @@ static int update_submodule(struct update_data *update_data) static int update_submodules(struct update_data *update_data) { - int i, res = 0; + int i, ret = 0; struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; suc.update_data = update_data; @@ -2557,33 +2583,48 @@ static int update_submodules(struct update_data *update_data) * - the listener can avoid doing any work if fetching failed. */ if (suc.quickstop) { - res = 1; + ret = 1; goto cleanup; } for (i = 0; i < suc.update_clone_nr; i++) { struct update_clone_data ucd = suc.update_clone[i]; + int code; oidcpy(&update_data->oid, &ucd.oid); update_data->just_cloned = ucd.just_cloned; update_data->sm_path = ucd.sub->path; - if (update_submodule(update_data)) - res = 1; + code = ensure_core_worktree(update_data->sm_path); + if (code) + goto fail; + + update_data->displaypath = get_submodule_displaypath( + update_data->sm_path, update_data->prefix); + code = update_submodule(update_data); + FREE_AND_NULL(update_data->displaypath); +fail: + if (!code) + continue; + ret = code; + if (ret == 128) + goto cleanup; } cleanup: + submodule_update_clone_release(&suc); string_list_clear(&update_data->references, 0); - return res; + return ret; } static int module_update(int argc, const char **argv, const char *prefix) { - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; + struct pathspec pathspec2 = { 0 }; struct update_data opt = UPDATE_DATA_INIT; - struct list_objects_filter_options filter_options; + struct list_objects_filter_options filter_options = + LIST_OBJECTS_FILTER_INIT; int ret; - struct option module_update_options[] = { OPT__FORCE(&opt.force, N_("force checkout updates"), 0), OPT_BOOL(0, "init", &opt.init, @@ -2597,13 +2638,15 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_STRING(0, "prefix", &opt.prefix, N_("path"), N_("path into the working tree")), - OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, - N_("path"), - N_("path into the working tree, across nested " - "submodule boundaries")), - OPT_STRING(0, "update", &opt.update_default, - N_("string"), - N_("rebase, merge, checkout or none")), + OPT_SET_INT(0, "checkout", &opt.update_default, + N_("use the 'checkout' update strategy (default)"), + SM_UPDATE_CHECKOUT), + OPT_SET_INT('m', "merge", &opt.update_default, + N_("use the 'merge' update strategy"), + SM_UPDATE_MERGE), + OPT_SET_INT('r', "rebase", &opt.update_default, + N_("use the 'rebase' update strategy"), + SM_UPDATE_REBASE), OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &opt.dissociate, @@ -2619,13 +2662,12 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "progress", &opt.progress, N_("force cloning progress")), OPT_BOOL(0, "require-init", &opt.require_init, - N_("disallow cloning into non-empty directory")), + N_("disallow cloning into non-empty directory, implies --init")), OPT_BOOL(0, "single-branch", &opt.single_branch, N_("clone only one branch, HEAD or --branch")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; - const char *const git_submodule_helper_usage[] = { N_("git submodule [--quiet] update" " [--init [--filter=]] [--remote]" @@ -2639,10 +2681,12 @@ static int module_update(int argc, const char **argv, const char *prefix) update_clone_config_from_gitmodules(&opt.max_jobs); git_config(git_update_clone_config, &opt.max_jobs); - memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_update_options, git_submodule_helper_usage, 0); + if (opt.require_init) + opt.init = 1; + if (filter_options.choice && !opt.init) { usage_with_options(git_submodule_helper_usage, module_update_options); @@ -2651,13 +2695,11 @@ static int module_update(int argc, const char **argv, const char *prefix) opt.filter_options = &filter_options; if (opt.update_default) - if (parse_submodule_update_strategy(opt.update_default, - &opt.update_strategy) < 0) - die(_("bad value for update parameter")); + opt.update_strategy.type = opt.update_default; if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0) { - list_objects_filter_release(&filter_options); - return 1; + ret = 1; + goto cleanup; } if (pathspec.nr) @@ -2668,8 +2710,11 @@ static int module_update(int argc, const char **argv, const char *prefix) struct init_cb info = INIT_CB_INIT; if (module_list_compute(argc, argv, opt.prefix, - &pathspec, &list) < 0) - return 1; + &pathspec2, &list) < 0) { + module_list_release(&list); + ret = 1; + goto cleanup; + } /* * If there are no path args and submodule.active is set then, @@ -2679,15 +2724,19 @@ static int module_update(int argc, const char **argv, const char *prefix) module_list_active(&list); info.prefix = opt.prefix; - info.superprefix = opt.recursive_prefix; if (opt.quiet) info.flags |= OPT_QUIET; for_each_listed_submodule(&list, init_submodule_cb, &info); + module_list_release(&list); } ret = update_submodules(&opt); +cleanup: + update_data_release(&opt); list_objects_filter_release(&filter_options); + clear_pathspec(&pathspec); + clear_pathspec(&pathspec2); return ret; } @@ -2771,10 +2820,9 @@ static int push_check(int argc, const char **argv, const char *prefix) static int absorb_git_dirs(int argc, const char **argv, const char *prefix) { int i; - struct pathspec pathspec; + struct pathspec pathspec = { 0 }; struct module_list list = MODULE_LIST_INIT; unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES; - struct option embed_gitdir_options[] = { OPT_STRING(0, "prefix", &prefix, N_("path"), @@ -2783,53 +2831,26 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) ABSORB_GITDIR_RECURSE_SUBMODULES), OPT_END() }; - const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper absorb-git-dirs [] [...]"), + N_("git submodule absorbgitdirs [] [...]"), NULL }; + int ret = 1; argc = parse_options(argc, argv, prefix, embed_gitdir_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) - return 1; + goto cleanup; for (i = 0; i < list.nr; i++) absorb_git_dir_into_superproject(list.entries[i]->name, flags); - return 0; -} - -static int is_active(int argc, const char **argv, const char *prefix) -{ - if (argc != 2) - die("submodule--helper is-active takes exactly 1 argument"); - - return !is_submodule_active(the_repository, argv[1]); -} - -/* - * Exit non-zero if any of the submodule names given on the command line is - * invalid. If no names are given, filter stdin to print only valid names - * (which is primarily intended for testing). - */ -static int check_name(int argc, const char **argv, const char *prefix) -{ - if (argc > 1) { - while (*++argv) { - if (check_submodule_name(*argv) < 0) - return 1; - } - } else { - struct strbuf buf = STRBUF_INIT; - while (strbuf_getline(&buf, stdin) != EOF) { - if (!check_submodule_name(buf.buf)) - printf("%s\n", buf.buf); - } - strbuf_release(&buf); - } - return 0; + ret = 0; +cleanup: + clear_pathspec(&pathspec); + module_list_release(&list); + return ret; } static int module_config(int argc, const char **argv, const char *prefix) @@ -2838,7 +2859,6 @@ static int module_config(int argc, const char **argv, const char *prefix) CHECK_WRITEABLE = 1, DO_UNSET = 2 } command = 0; - struct option module_config_options[] = { OPT_CMDMODE(0, "check-writeable", &command, N_("check if it is safe to write to the .gitmodules file"), @@ -2884,13 +2904,12 @@ static int module_set_url(int argc, const char **argv, const char *prefix) const char *newurl; const char *path; char *config_name; - struct option options[] = { OPT__QUIET(&quiet, N_("suppress output for setting url of a submodule")), OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-url [--quiet] "), + N_("git submodule set-url [--quiet] "), NULL }; @@ -2915,13 +2934,13 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) const char *opt_branch = NULL; const char *path; char *config_name; - - /* - * We accept the `quiet` option for uniformity across subcommands, - * though there is nothing to make less verbose in this subcommand. - */ struct option options[] = { + /* + * We accept the `quiet` option for uniformity across subcommands, + * though there is nothing to make less verbose in this subcommand. + */ OPT_NOOP_NOARG('q', "quiet"), + OPT_BOOL('d', "default", &opt_default, N_("set the default tracking branch to master")), OPT_STRING('b', "branch", &opt_branch, N_("branch"), @@ -2929,8 +2948,8 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) "), - N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) "), + N_("git submodule set-branch [-q|--quiet] (-d|--default) "), + N_("git submodule set-branch [-q|--quiet] (-b|--branch) "), NULL }; @@ -2956,7 +2975,6 @@ static int module_create_branch(int argc, const char **argv, const char *prefix) { enum branch_track track; int quiet = 0, force = 0, reflog = 0, dry_run = 0; - struct option options[] = { OPT__QUIET(&quiet, N_("print only error messages")), OPT__FORCE(&force, N_("force creation"), 0), @@ -3019,8 +3037,10 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path) if (!capture_command(&cp_remote, &sb_remote_out, 0)) { char *next_line; char *line = sb_remote_out.buf; + while ((next_line = strchr(line, '\n')) != NULL) { size_t len = next_line - line; + if (strip_suffix_mem(line, &len, " (fetch)")) strbuf_addf(msg, " %.*s\n", (int)len, line); line = next_line + 1; @@ -3034,6 +3054,8 @@ static int add_submodule(const struct add_data *add_data) { char *submod_gitdir_path; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + struct string_list reference = STRING_LIST_INIT_NODUP; + int ret = -1; /* perhaps the path already exists and is already a git repo, else clone it */ if (is_directory(add_data->sm_path)) { @@ -3050,6 +3072,7 @@ static int add_submodule(const struct add_data *add_data) free(submod_gitdir_path); } else { struct child_process cp = CHILD_PROCESS_INIT; + submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name); if (is_directory(submod_gitdir_path)) { @@ -3088,15 +3111,17 @@ static int add_submodule(const struct add_data *add_data) clone_data.url = add_data->realrepo; clone_data.quiet = add_data->quiet; clone_data.progress = add_data->progress; - if (add_data->reference_path) - string_list_append(&clone_data.reference, - xstrdup(add_data->reference_path)); + if (add_data->reference_path) { + char *p = xstrdup(add_data->reference_path); + + string_list_append(&reference, p)->util = p; + } clone_data.dissociate = add_data->dissociate; if (add_data->depth >= 0) clone_data.depth = xstrfmt("%d", add_data->depth); - if (clone_submodule(&clone_data)) - return -1; + if (clone_submodule(&clone_data, &reference)) + goto cleanup; prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; @@ -3115,7 +3140,10 @@ static int add_submodule(const struct add_data *add_data) if (run_command(&cp)) die(_("unable to checkout submodule '%s'"), add_data->sm_path); } - return 0; + ret = 0; +cleanup: + string_list_clear(&reference, 1); + return ret; } static int config_submodule_in_gitmodules(const char *name, const char *var, const char *value) @@ -3136,7 +3164,7 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con static void configure_added_submodule(struct add_data *add_data) { char *key; - char *val = NULL; + const char *val; struct child_process add_submod = CHILD_PROCESS_INIT; struct child_process add_gitmodules = CHILD_PROCESS_INIT; @@ -3181,7 +3209,7 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!git_config_get_string("submodule.active", &val) && val) { + if (!git_config_get_string_tmp("submodule.active", &val)) { /* * If the submodule being added isn't already covered by the * current configured pathspec, set the submodule's active flag @@ -3255,7 +3283,6 @@ static int module_add(int argc, const char **argv, const char *prefix) int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; char *to_free = NULL; - struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), N_("branch of repository to add as submodule")), @@ -3272,11 +3299,12 @@ static int module_add(int argc, const char **argv, const char *prefix) OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")), OPT_END() }; - const char *const usage[] = { - N_("git submodule--helper add [] [--] []"), + N_("git submodule add [] [--] []"), NULL }; + struct strbuf sb = STRBUF_INIT; + int ret = 1; argc = parse_options(argc, argv, prefix, options, usage, 0); @@ -3296,8 +3324,12 @@ static int module_add(int argc, const char **argv, const char *prefix) else add_data.sm_path = xstrdup(argv[1]); - if (prefix && *prefix && !is_absolute_path(add_data.sm_path)) - add_data.sm_path = xstrfmt("%s%s", prefix, add_data.sm_path); + if (prefix && *prefix && !is_absolute_path(add_data.sm_path)) { + char *sm_path = add_data.sm_path; + + add_data.sm_path = xstrfmt("%s%s", prefix, sm_path); + free(sm_path); + } if (starts_with_dot_dot_slash(add_data.repo) || starts_with_dot_slash(add_data.repo)) { @@ -3326,20 +3358,17 @@ static int module_add(int argc, const char **argv, const char *prefix) die_on_repo_without_commits(add_data.sm_path); if (!force) { - int exit_code = -1; - struct strbuf sb = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; cp.no_stdout = 1; strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing", "--no-warn-embedded-repo", add_data.sm_path, NULL); - if ((exit_code = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) { + if ((ret = pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0))) { strbuf_complete_line(&sb); fputs(sb.buf, stderr); - free(add_data.sm_path); - return exit_code; + goto cleanup; } - strbuf_release(&sb); } if(!add_data.sm_name) @@ -3354,15 +3383,17 @@ static int module_add(int argc, const char **argv, const char *prefix) add_data.progress = !!progress; add_data.dissociate = !!dissociate; - if (add_submodule(&add_data)) { - free(add_data.sm_path); - return 1; - } + if (add_submodule(&add_data)) + goto cleanup; configure_added_submodule(&add_data); + + ret = 0; +cleanup: free(add_data.sm_path); free(to_free); + strbuf_release(&sb); - return 0; + return ret; } #define SUPPORT_SUPER_PREFIX (1<<0) @@ -3374,22 +3405,17 @@ struct cmd_struct { }; static struct cmd_struct commands[] = { - {"list", module_list, 0}, - {"name", module_name, 0}, - {"clone", module_clone, 0}, - {"add", module_add, SUPPORT_SUPER_PREFIX}, - {"update", module_update, 0}, - {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"clone", module_clone, SUPPORT_SUPER_PREFIX}, + {"add", module_add, 0}, + {"update", module_update, SUPPORT_SUPER_PREFIX}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, - {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"init", module_init, 0}, {"status", module_status, SUPPORT_SUPER_PREFIX}, {"sync", module_sync, SUPPORT_SUPER_PREFIX}, {"deinit", module_deinit, 0}, - {"summary", module_summary, SUPPORT_SUPER_PREFIX}, + {"summary", module_summary, 0}, {"push-check", push_check, 0}, - {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, - {"is-active", is_active, 0}, - {"check-name", check_name, 0}, + {"absorbgitdirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"config", module_config, 0}, {"set-url", module_set_url, 0}, {"set-branch", module_set_branch, 0}, diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index e547a08d6c..1b0f10225f 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -71,6 +71,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) if (!strcmp(argv[0], "HEAD") && !starts_with(argv[1], "refs/")) die("Refusing to point HEAD outside of refs/"); + if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0) + die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]); ret = !!create_symref(argv[0], argv[1], msg); break; default: diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 56d05e2725..43789b8ef2 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -97,15 +97,27 @@ static void use(int bytes) display_throughput(progress, consumed_bytes); } +/* + * Decompress zstream from the standard input into a newly + * allocated buffer of specified size and return the buffer. + * The caller is responsible to free the returned buffer. + * + * But for dry_run mode, "get_data()" is only used to check the + * integrity of data, and the returned buffer is not used at all. + * Therefore, in dry_run mode, "get_data()" will release the small + * allocated buffer which is reused to hold temporary zstream output + * and return NULL instead of returning garbage data. + */ static void *get_data(unsigned long size) { git_zstream stream; - void *buf = xmallocz(size); + unsigned long bufsize = dry_run && size > 8192 ? 8192 : size; + void *buf = xmallocz(bufsize); memset(&stream, 0, sizeof(stream)); stream.next_out = buf; - stream.avail_out = size; + stream.avail_out = bufsize; stream.next_in = fill(1); stream.avail_in = len; git_inflate_init(&stream); @@ -125,8 +137,17 @@ static void *get_data(unsigned long size) } stream.next_in = fill(1); stream.avail_in = len; + if (dry_run) { + /* reuse the buffer in dry_run mode */ + stream.next_out = buf; + stream.avail_out = bufsize > size - stream.total_out ? + size - stream.total_out : + bufsize; + } } git_inflate_end(&stream); + if (dry_run) + FREE_AND_NULL(buf); return buf; } @@ -326,10 +347,70 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, { void *buf = get_data(size); - if (!dry_run && buf) + if (buf) write_object(nr, type, buf, size); - else - free(buf); +} + +struct input_zstream_data { + git_zstream *zstream; + unsigned char buf[8192]; + int status; +}; + +static const void *feed_input_zstream(struct input_stream *in_stream, + unsigned long *readlen) +{ + struct input_zstream_data *data = in_stream->data; + git_zstream *zstream = data->zstream; + void *in = fill(1); + + if (in_stream->is_finished) { + *readlen = 0; + return NULL; + } + + zstream->next_out = data->buf; + zstream->avail_out = sizeof(data->buf); + zstream->next_in = in; + zstream->avail_in = len; + + data->status = git_inflate(zstream, 0); + + in_stream->is_finished = data->status != Z_OK; + use(len - zstream->avail_in); + *readlen = sizeof(data->buf) - zstream->avail_out; + + return data->buf; +} + +static void stream_blob(unsigned long size, unsigned nr) +{ + git_zstream zstream = { 0 }; + struct input_zstream_data data = { 0 }; + struct input_stream in_stream = { + .read = feed_input_zstream, + .data = &data, + }; + struct obj_info *info = &obj_list[nr]; + + data.zstream = &zstream; + git_inflate_init(&zstream); + + if (stream_loose_object(&in_stream, size, &info->oid)) + die(_("failed to write object in stream")); + + if (data.status != Z_STREAM_END) + die(_("inflate returned (%d)"), data.status); + git_inflate_end(&zstream); + + if (strict) { + struct blob *blob = lookup_blob(the_repository, &info->oid); + + if (!blob) + die(_("invalid blob object from stream")); + blob->object.flags |= FLAG_WRITTEN; + } + info->obj = NULL; } static int resolve_against_held(unsigned nr, const struct object_id *base, @@ -359,10 +440,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, oidread(&base_oid, fill(the_hash_algo->rawsz)); use(the_hash_algo->rawsz); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } if (has_object_file(&base_oid)) ; /* Ok we have this one */ else if (resolve_against_held(nr, &base_oid, @@ -398,10 +477,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, die("offset value out of bound for delta base object"); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } lo = 0; hi = nr; while (lo < hi) { @@ -468,9 +545,14 @@ static void unpack_one(unsigned nr) } switch (type) { + case OBJ_BLOB: + if (!dry_run && size > big_file_threshold) { + stream_blob(size, nr); + return; + } + /* fallthrough */ case OBJ_COMMIT: case OBJ_TREE: - case OBJ_BLOB: case OBJ_TAG: unpack_non_delta_entry(type, size, nr); return; diff --git a/builtin/worktree.c b/builtin/worktree.c index cd62eef240..c6710b2552 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1112,31 +1112,24 @@ static int repair(int ac, const char **av, const char *prefix) int cmd_worktree(int ac, const char **av, const char *prefix) { + parse_opt_subcommand_fn *fn = NULL; struct option options[] = { + OPT_SUBCOMMAND("add", &fn, add), + OPT_SUBCOMMAND("prune", &fn, prune), + OPT_SUBCOMMAND("list", &fn, list), + OPT_SUBCOMMAND("lock", &fn, lock_worktree), + OPT_SUBCOMMAND("unlock", &fn, unlock_worktree), + OPT_SUBCOMMAND("move", &fn, move_worktree), + OPT_SUBCOMMAND("remove", &fn, remove_worktree), + OPT_SUBCOMMAND("repair", &fn, repair), OPT_END() }; git_config(git_worktree_config, NULL); - if (ac < 2) - usage_with_options(worktree_usage, options); if (!prefix) prefix = ""; - if (!strcmp(av[1], "add")) - return add(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "prune")) - return prune(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "list")) - return list(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "lock")) - return lock_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "unlock")) - return unlock_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "move")) - return move_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "remove")) - return remove_worktree(ac - 1, av + 1, prefix); - if (!strcmp(av[1], "repair")) - return repair(ac - 1, av + 1, prefix); - usage_with_options(worktree_usage, options); + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + return fn(ac, av, prefix); } diff --git a/bulk-checkin.c b/bulk-checkin.c index 98ec893842..855b68ec23 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -340,6 +340,8 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename) */ if (!bulk_fsync_objdir || git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) { + if (errno == ENOSYS) + warning(_("core.fsyncMethod = batch is unsupported on this platform")); fsync_or_die(fd, filename); } } diff --git a/bundle-uri.c b/bundle-uri.c new file mode 100644 index 0000000000..4a8cc74ed0 --- /dev/null +++ b/bundle-uri.c @@ -0,0 +1,168 @@ +#include "cache.h" +#include "bundle-uri.h" +#include "bundle.h" +#include "object-store.h" +#include "refs.h" +#include "run-command.h" + +static int find_temp_filename(struct strbuf *name) +{ + int fd; + /* + * Find a temporary filename that is available. This is briefly + * racy, but unlikely to collide. + */ + fd = odb_mkstemp(name, "bundles/tmp_uri_XXXXXX"); + if (fd < 0) { + warning(_("failed to create temporary file")); + return -1; + } + + close(fd); + unlink(name->buf); + return 0; +} + +static int download_https_uri_to_file(const char *file, const char *uri) +{ + int result = 0; + struct child_process cp = CHILD_PROCESS_INIT; + FILE *child_in = NULL, *child_out = NULL; + struct strbuf line = STRBUF_INIT; + int found_get = 0; + + strvec_pushl(&cp.args, "git-remote-https", uri, NULL); + cp.in = -1; + cp.out = -1; + + if (start_command(&cp)) + return 1; + + child_in = fdopen(cp.in, "w"); + if (!child_in) { + result = 1; + goto cleanup; + } + + child_out = fdopen(cp.out, "r"); + if (!child_out) { + result = 1; + goto cleanup; + } + + fprintf(child_in, "capabilities\n"); + fflush(child_in); + + while (!strbuf_getline(&line, child_out)) { + if (!line.len) + break; + if (!strcmp(line.buf, "get")) + found_get = 1; + } + strbuf_release(&line); + + if (!found_get) { + result = error(_("insufficient capabilities")); + goto cleanup; + } + + fprintf(child_in, "get %s %s\n\n", uri, file); + +cleanup: + if (child_in) + fclose(child_in); + if (finish_command(&cp)) + return 1; + if (child_out) + fclose(child_out); + return result; +} + +static int copy_uri_to_file(const char *filename, const char *uri) +{ + const char *out; + + if (starts_with(uri, "https:") || + starts_with(uri, "http:")) + return download_https_uri_to_file(filename, uri); + + if (skip_prefix(uri, "file://", &out)) + uri = out; + + /* Copy as a file */ + return copy_file(filename, uri, 0); +} + +static int unbundle_from_file(struct repository *r, const char *file) +{ + int result = 0; + int bundle_fd; + struct bundle_header header = BUNDLE_HEADER_INIT; + struct string_list_item *refname; + struct strbuf bundle_ref = STRBUF_INIT; + size_t bundle_prefix_len; + + if ((bundle_fd = read_bundle_header(file, &header)) < 0) + return 1; + + if ((result = unbundle(r, &header, bundle_fd, NULL))) + return 1; + + /* + * Convert all refs/heads/ from the bundle into refs/bundles/ + * in the local repository. + */ + strbuf_addstr(&bundle_ref, "refs/bundles/"); + bundle_prefix_len = bundle_ref.len; + + for_each_string_list_item(refname, &header.references) { + struct object_id *oid = refname->util; + struct object_id old_oid; + const char *branch_name; + int has_old; + + if (!skip_prefix(refname->string, "refs/heads/", &branch_name)) + continue; + + strbuf_setlen(&bundle_ref, bundle_prefix_len); + strbuf_addstr(&bundle_ref, branch_name); + + has_old = !read_ref(bundle_ref.buf, &old_oid); + update_ref("fetched bundle", bundle_ref.buf, oid, + has_old ? &old_oid : NULL, + REF_SKIP_OID_VERIFICATION, + UPDATE_REFS_MSG_ON_ERR); + } + + bundle_header_release(&header); + return result; +} + +int fetch_bundle_uri(struct repository *r, const char *uri) +{ + int result = 0; + struct strbuf filename = STRBUF_INIT; + + if ((result = find_temp_filename(&filename))) + goto cleanup; + + if ((result = copy_uri_to_file(filename.buf, uri))) { + warning(_("failed to download bundle from URI '%s'"), uri); + goto cleanup; + } + + if ((result = !is_bundle(filename.buf, 0))) { + warning(_("file at URI '%s' is not a bundle"), uri); + goto cleanup; + } + + if ((result = unbundle_from_file(r, filename.buf))) { + warning(_("failed to unbundle bundle from URI '%s'"), uri); + goto cleanup; + } + +cleanup: + unlink(filename.buf); + strbuf_release(&filename); + return result; +} diff --git a/bundle-uri.h b/bundle-uri.h new file mode 100644 index 0000000000..8a152f1ef1 --- /dev/null +++ b/bundle-uri.h @@ -0,0 +1,14 @@ +#ifndef BUNDLE_URI_H +#define BUNDLE_URI_H + +struct repository; + +/** + * Fetch data from the given 'uri' and unbundle the bundle data found + * based on that information. + * + * Returns non-zero if no bundle information is found at the given 'uri'. + */ +int fetch_bundle_uri(struct repository *r, const char *uri); + +#endif diff --git a/bundle.h b/bundle.h index 0c052f5496..68ff39a0a7 100644 --- a/bundle.h +++ b/bundle.h @@ -18,6 +18,7 @@ struct bundle_header { { \ .prerequisites = STRING_LIST_INIT_DUP, \ .references = STRING_LIST_INIT_DUP, \ + .filter = LIST_OBJECTS_FILTER_INIT, \ } void bundle_header_init(struct bundle_header *header); void bundle_header_release(struct bundle_header *header); diff --git a/cache-tree.c b/cache-tree.c index 56db0b5026..c97111cccf 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -857,9 +857,7 @@ int cache_tree_matches_traversal(struct cache_tree *root, return 0; } -static void verify_one_sparse(struct repository *r, - struct index_state *istate, - struct cache_tree *it, +static void verify_one_sparse(struct index_state *istate, struct strbuf *path, int pos) { @@ -910,7 +908,7 @@ static int verify_one(struct repository *r, return 1; if (pos >= 0) { - verify_one_sparse(r, istate, it, path, pos); + verify_one_sparse(istate, path, pos); return 0; } diff --git a/cache.h b/cache.h index 33fc8337c6..26ed03bd6d 100644 --- a/cache.h +++ b/cache.h @@ -475,8 +475,7 @@ extern struct index_state the_index; /* * Values in this enum (except those outside the 3 bit range) are part - * of pack file format. See Documentation/technical/pack-format.txt - * for more information. + * of pack file format. See gitformat-pack(5) for more information. */ enum object_type { OBJ_BAD = -1, @@ -1017,7 +1016,6 @@ void reset_shared_repository(void); * commands that do not want replace references to be active. */ extern int read_replace_refs; -extern char *git_replace_ref_base; /* * These values are used to help identify parts of a repository to fsync. @@ -1697,6 +1695,12 @@ struct ident_split { */ int split_ident_line(struct ident_split *, const char *, int); +/* + * Given a commit or tag object buffer and the commit or tag headers, replaces + * the idents in the headers with their canonical versions using the mailmap mechanism. + */ +void apply_mailmap_to_header(struct strbuf *, const char **, struct string_list *); + /* * Compare split idents for equality or strict ordering. Note that we * compare only the ident part of the line, ignoring any timestamp. diff --git a/ci/lib.sh b/ci/lib.sh index f095519f8d..1b0cc2b57d 100755 --- a/ci/lib.sh +++ b/ci/lib.sh @@ -276,6 +276,7 @@ linux-musl) linux-leaks) export SANITIZE=leak export GIT_TEST_PASSING_SANITIZE_LEAK=true + export GIT_TEST_SANITIZE_LEAK_LOG=true ;; esac diff --git a/color.c b/color.c index 4f884c6b3d..f05d8a81d7 100644 --- a/color.c +++ b/color.c @@ -415,7 +415,7 @@ int want_color_fd(int fd, int var) return var; } -int git_color_config(const char *var, const char *value, void *cb) +int git_color_config(const char *var, const char *value, void *cb UNUSED) { if (!strcmp(var, "color.ui")) { git_use_color_default = git_config_colorbool(var, value); diff --git a/command-list.txt b/command-list.txt index 9bd6f3c48f..54b2a50f5f 100644 --- a/command-list.txt +++ b/command-list.txt @@ -43,6 +43,15 @@ # specified here, which can only have "guide" attribute and nothing # else. # +# User-facing repository, command and file interfaces such as +# documentation for the .gitmodules, .mailmap etc. files lives in man +# sections 5 and 7. These entries can only have the "userinterfaces" +# attribute and nothing else. +# +# Git's file formats and protocols, such as documentation for the +# *.bundle format lives in man section 5. These entries can only have +# the "developerinterfaces" attribute and nothing else. +# ### command list (do not change this line) # command name category [category] [category] git-add mainporcelain worktree @@ -82,6 +91,7 @@ git-cvsimport foreignscminterface git-cvsserver foreignscminterface git-daemon synchingrepositories git-describe mainporcelain +git-diagnose ancillaryinterrogators git-diff mainporcelain info git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators @@ -189,29 +199,42 @@ git-var plumbinginterrogators git-verify-commit ancillaryinterrogators git-verify-pack plumbinginterrogators git-verify-tag ancillaryinterrogators +git-version ancillaryinterrogators git-whatchanged ancillaryinterrogators complete git-worktree mainporcelain git-write-tree plumbingmanipulators -gitattributes guide -gitcli guide +gitattributes userinterfaces +gitcli userinterfaces gitcore-tutorial guide gitcredentials guide gitcvs-migration guide gitdiffcore guide giteveryday guide gitfaq guide +gitformat-bundle developerinterfaces +gitformat-chunk developerinterfaces +gitformat-commit-graph developerinterfaces +gitformat-index developerinterfaces +gitformat-pack developerinterfaces +gitformat-signature developerinterfaces gitglossary guide -githooks guide -gitignore guide +githooks userinterfaces +gitignore userinterfaces gitk mainporcelain -gitmailmap guide -gitmodules guide +gitmailmap userinterfaces +gitmodules userinterfaces gitnamespaces guide +gitprotocol-capabilities developerinterfaces +gitprotocol-common developerinterfaces +gitprotocol-http developerinterfaces +gitprotocol-pack developerinterfaces +gitprotocol-v2 developerinterfaces gitremote-helpers guide -gitrepository-layout guide -gitrevisions guide +gitrepository-layout userinterfaces +gitrevisions userinterfaces gitsubmodules guide gittutorial guide gittutorial-2 guide gitweb ancillaryinterrogators gitworkflows guide +scalar mainporcelain diff --git a/commit-graph.c b/commit-graph.c index a487d49c3e..a7d8755932 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -252,7 +252,8 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, } graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - ret = parse_commit_graph(r, graph_map, graph_size); + prepare_repo_settings(r); + ret = parse_commit_graph(&r->settings, graph_map, graph_size); if (ret) ret->odb = odb; @@ -321,7 +322,7 @@ static int graph_read_bloom_data(const unsigned char *chunk_start, return 0; } -struct commit_graph *parse_commit_graph(struct repository *r, +struct commit_graph *parse_commit_graph(struct repo_settings *s, void *graph_map, size_t graph_size) { const unsigned char *data; @@ -359,8 +360,6 @@ struct commit_graph *parse_commit_graph(struct repository *r, return NULL; } - prepare_repo_settings(r); - graph = alloc_commit_graph(); graph->hash_len = the_hash_algo->rawsz; @@ -390,7 +389,7 @@ struct commit_graph *parse_commit_graph(struct repository *r, pair_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges); pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs); - if (get_configured_generation_version(r) >= 2) { + if (s->commit_graph_generation_version >= 2) { pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA, &graph->chunk_generation_data); pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, @@ -400,7 +399,7 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->read_generation_data = 1; } - if (r->settings.commit_graph_read_changed_paths) { + if (s->commit_graph_read_changed_paths) { pair_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES, &graph->chunk_bloom_indexes); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA, @@ -902,7 +901,7 @@ struct commit *lookup_commit_in_graph(struct repository *repo, const struct obje struct commit *commit; uint32_t pos; - if (!repo->objects->commit_graph) + if (!prepare_commit_graph(repo)) return NULL; if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos)) return NULL; @@ -1640,9 +1639,9 @@ struct refs_cb_data { struct progress *progress; }; -static int add_ref_to_set(const char *refname, +static int add_ref_to_set(const char *refname UNUSED, const struct object_id *oid, - int flags, void *cb_data) + int flags UNUSED, void *cb_data) { struct object_id peeled; struct refs_cb_data *data = (struct refs_cb_data *)cb_data; @@ -2266,6 +2265,8 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx) } out: + if(dir) + closedir(dir); strbuf_release(&path); } diff --git a/commit-graph.h b/commit-graph.h index f23b9e9026..37faee6b66 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -108,7 +108,12 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, struct object_directory *odb); struct commit_graph *read_commit_graph_one(struct repository *r, struct object_directory *odb); -struct commit_graph *parse_commit_graph(struct repository *r, + +/* + * Callers should initialize the repo_settings with prepare_repo_settings() + * prior to calling parse_commit_graph(). + */ +struct commit_graph *parse_commit_graph(struct repo_settings *s, void *graph_map, size_t graph_size); /* diff --git a/commit.c b/commit.c index 1fb1b2ea90..89b8efc611 100644 --- a/commit.c +++ b/commit.c @@ -642,10 +642,11 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm return commit_list_insert(item, pp); } -static int commit_list_compare_by_date(const void *a, const void *b) +static int commit_list_compare_by_date(const struct commit_list *a, + const struct commit_list *b) { - timestamp_t a_date = ((const struct commit_list *)a)->item->date; - timestamp_t b_date = ((const struct commit_list *)b)->item->date; + timestamp_t a_date = a->item->date; + timestamp_t b_date = b->item->date; if (a_date < b_date) return 1; if (a_date > b_date) @@ -653,20 +654,11 @@ static int commit_list_compare_by_date(const void *a, const void *b) return 0; } -static void *commit_list_get_next(const void *a) -{ - return ((const struct commit_list *)a)->next; -} - -static void commit_list_set_next(void *a, void *next) -{ - ((struct commit_list *)a)->next = next; -} +DEFINE_LIST_SORT(static, commit_list_sort, struct commit_list, next); void commit_list_sort_by_date(struct commit_list **list) { - *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next, - commit_list_compare_by_date); + commit_list_sort(list, commit_list_compare_by_date); } struct commit *pop_most_recent_commit(struct commit_list **list, @@ -959,8 +951,9 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs) } static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) + const char *ident UNUSED, + timestamp_t timestamp UNUSED, int tz UNUSED, + const char *message UNUSED, void *cbdata) { struct rev_collect *revs = cbdata; diff --git a/common-main.c b/common-main.c index c531372f3f..0a22861f1c 100644 --- a/common-main.c +++ b/common-main.c @@ -40,6 +40,7 @@ int main(int argc, const char **argv) git_resolve_executable_dir(argv[0]); + setlocale(LC_CTYPE, ""); git_setup_gettext(); initialize_the_repository(); diff --git a/compat/disk.h b/compat/disk.h new file mode 100644 index 0000000000..50a32e3d8a --- /dev/null +++ b/compat/disk.h @@ -0,0 +1,56 @@ +#ifndef COMPAT_DISK_H +#define COMPAT_DISK_H + +#include "git-compat-util.h" + +static int get_disk_info(struct strbuf *out) +{ + struct strbuf buf = STRBUF_INIT; + int res = 0; + +#ifdef GIT_WINDOWS_NATIVE + char volume_name[MAX_PATH], fs_name[MAX_PATH]; + DWORD serial_number, component_length, flags; + ULARGE_INTEGER avail2caller, total, avail; + + strbuf_realpath(&buf, ".", 1); + if (!GetDiskFreeSpaceExA(buf.buf, &avail2caller, &total, &avail)) { + error(_("could not determine free disk size for '%s'"), + buf.buf); + res = -1; + goto cleanup; + } + + strbuf_setlen(&buf, offset_1st_component(buf.buf)); + if (!GetVolumeInformationA(buf.buf, volume_name, sizeof(volume_name), + &serial_number, &component_length, &flags, + fs_name, sizeof(fs_name))) { + error(_("could not get info for '%s'"), buf.buf); + res = -1; + goto cleanup; + } + strbuf_addf(out, "Available space on '%s': ", buf.buf); + strbuf_humanise_bytes(out, avail2caller.QuadPart); + strbuf_addch(out, '\n'); +#else + struct statvfs stat; + + strbuf_realpath(&buf, ".", 1); + if (statvfs(buf.buf, &stat) < 0) { + error_errno(_("could not determine free disk size for '%s'"), + buf.buf); + res = -1; + goto cleanup; + } + + strbuf_addf(out, "Available space on '%s': ", buf.buf); + strbuf_humanise_bytes(out, (off_t)stat.f_bsize * (off_t)stat.f_bavail); + strbuf_addf(out, " (mount flags 0x%lx)\n", stat.f_flag); +#endif + +cleanup: + strbuf_release(&buf); + return res; +} + +#endif /* COMPAT_DISK_H */ diff --git a/compat/fsmonitor/fsm-settings-win32.c b/compat/fsmonitor/fsm-settings-win32.c index 907655720b..e5ec5b0a9f 100644 --- a/compat/fsmonitor/fsm-settings-win32.c +++ b/compat/fsmonitor/fsm-settings-win32.c @@ -24,6 +24,59 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r) return FSMONITOR_REASON_OK; } +/* + * Check if monitoring remote working directories is allowed. + * + * By default, monitoring remote working directories is + * disabled. Users may override this behavior in enviroments where + * they have proper support. + */ +static int check_config_allowremote(struct repository *r) +{ + int allow; + + if (!repo_config_get_bool(r, "fsmonitor.allowremote", &allow)) + return allow; + + return -1; /* fsmonitor.allowremote not set */ +} + +/* + * Check remote working directory protocol. + * + * Error if client machine cannot get remote protocol information. + */ +static int check_remote_protocol(wchar_t *wpath) +{ + HANDLE h; + FILE_REMOTE_PROTOCOL_INFO proto_info; + + h = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (h == INVALID_HANDLE_VALUE) { + error(_("[GLE %ld] unable to open for read '%ls'"), + GetLastError(), wpath); + return -1; + } + + if (!GetFileInformationByHandleEx(h, FileRemoteProtocolInfo, + &proto_info, sizeof(proto_info))) { + error(_("[GLE %ld] unable to get protocol information for '%ls'"), + GetLastError(), wpath); + CloseHandle(h); + return -1; + } + + CloseHandle(h); + + trace_printf_key(&trace_fsmonitor, + "check_remote_protocol('%ls') remote protocol %#8.8lx", + wpath, proto_info.Protocol); + + return 0; +} + /* * Remote working directories are problematic for FSMonitor. * @@ -76,6 +129,7 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r) */ static enum fsmonitor_reason check_remote(struct repository *r) { + int ret; wchar_t wpath[MAX_PATH]; wchar_t wfullpath[MAX_PATH]; size_t wlen; @@ -115,6 +169,20 @@ static enum fsmonitor_reason check_remote(struct repository *r) trace_printf_key(&trace_fsmonitor, "check_remote('%s') true", r->worktree); + + ret = check_remote_protocol(wfullpath); + if (ret < 0) + return FSMONITOR_REASON_ERROR; + + switch (check_config_allowremote(r)) { + case 0: /* config overrides and disables */ + return FSMONITOR_REASON_REMOTE; + case 1: /* config overrides and enables */ + return FSMONITOR_REASON_OK; + default: + break; /* config has no opinion */ + } + return FSMONITOR_REASON_REMOTE; } diff --git a/compat/terminal.c b/compat/terminal.c index 7db330c52d..ea490a7ced 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -477,7 +477,7 @@ struct escape_sequence_entry { char sequence[FLEX_ARRAY]; }; -static int sequence_entry_cmp(const void *hashmap_cmp_fn_data, +static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, const struct escape_sequence_entry *e1, const struct escape_sequence_entry *e2, const void *keydata) diff --git a/config.c b/config.c index 9b0e9c9328..cbb5a3bab7 100644 --- a/config.c +++ b/config.c @@ -81,6 +81,17 @@ static enum config_scope current_parsing_scope; static int pack_compression_seen; static int zlib_compression_seen; +/* + * Config that comes from trusted scopes, namely: + * - CONFIG_SCOPE_SYSTEM (e.g. /etc/gitconfig) + * - CONFIG_SCOPE_GLOBAL (e.g. $HOME/.gitconfig, $XDG_CONFIG_HOME/git) + * - CONFIG_SCOPE_COMMAND (e.g. "-c" option, environment variables) + * + * This is declared here for code cleanliness, but unlike the other + * static variables, this does not hold config parser state. + */ +static struct config_set protected_config; + static int config_file_fgetc(struct config_source *conf) { return getc_unlocked(conf->u.file); @@ -351,7 +362,8 @@ static void populate_remote_urls(struct config_include_data *inc) current_parsing_scope = store_scope; } -static int forbid_remote_url(const char *var, const char *value, void *data) +static int forbid_remote_url(const char *var, const char *value UNUSED, + void *data UNUSED) { const char *remote_name; size_t remote_name_len; @@ -1968,6 +1980,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename, int ret = -1; FILE *f; + if (!filename) + BUG("filename cannot be NULL"); f = fopen_or_warn(filename, "r"); if (f) { ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, @@ -2324,10 +2338,10 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha return 0; } -static int config_set_element_cmp(const void *unused_cmp_data, +static int config_set_element_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct config_set_element *e1, *e2; @@ -2378,6 +2392,11 @@ int git_configset_add_file(struct config_set *cs, const char *filename) return git_config_from_file(config_set_callback, filename, cs); } +int git_configset_add_parameters(struct config_set *cs) +{ + return git_config_from_parameters(config_set_callback, cs); +} + int git_configset_get_value(struct config_set *cs, const char *key, const char **value) { const struct string_list *values = NULL; @@ -2619,6 +2638,36 @@ int repo_config_get_pathname(struct repository *repo, return ret; } +/* Read values into protected_config. */ +static void read_protected_config(void) +{ + char *xdg_config = NULL, *user_config = NULL, *system_config = NULL; + + git_configset_init(&protected_config); + + system_config = git_system_config(); + git_global_config(&user_config, &xdg_config); + + if (system_config) + git_configset_add_file(&protected_config, system_config); + if (xdg_config) + git_configset_add_file(&protected_config, xdg_config); + if (user_config) + git_configset_add_file(&protected_config, user_config); + git_configset_add_parameters(&protected_config); + + free(system_config); + free(xdg_config); + free(user_config); +} + +void git_protected_config(config_fn_t fn, void *data) +{ + if (!protected_config.hash_initialized) + read_protected_config(); + configset_iter(&protected_config, fn, data); +} + /* Functions used historically to read configuration from 'the_repository' */ void git_config(config_fn_t fn, void *data) { diff --git a/config.h b/config.h index 7654f61c63..ca994d7714 100644 --- a/config.h +++ b/config.h @@ -446,6 +446,15 @@ void git_configset_init(struct config_set *cs); */ int git_configset_add_file(struct config_set *cs, const char *filename); +/** + * Parses command line options and environment variables, and adds the + * variable-value pairs to the `config_set`. Returns 0 on success, or -1 + * if there is an error in parsing. The caller decides whether to free + * the incomplete configset or continue using it when the function + * returns -1. + */ +int git_configset_add_parameters(struct config_set *cs); + /** * Finds and returns the value list, sorted in order of increasing priority * for the configuration variable `key` and config set `cs`. When the @@ -505,6 +514,13 @@ int repo_config_get_maybe_bool(struct repository *repo, int repo_config_get_pathname(struct repository *repo, const char *key, const char **dest); +/* + * Functions for reading protected config. By definition, protected + * config ignores repository config, so these do not take a `struct + * repository` parameter. + */ +void git_protected_config(config_fn_t fn, void *data); + /** * Querying For Specific Variables * ------------------------------- diff --git a/configure.ac b/configure.ac index 7dcd048204..38ff86678a 100644 --- a/configure.ac +++ b/configure.ac @@ -237,9 +237,6 @@ AC_MSG_NOTICE([CHECKS for site configuration]) # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # -# Define PPC_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for PowerPC. -# # Define NO_OPENSSL environment variable if you do not have OpenSSL. # # Define OPENSSLDIR=/foo/bar if your openssl header and library files are in diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 2237109b57..ea2a531be8 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -610,7 +610,7 @@ unset(CMAKE_REQUIRED_INCLUDES) #programs set(PROGRAMS_BUILT git git-daemon git-http-backend git-sh-i18n--envsubst - git-shell) + git-shell scalar) if(NOT CURL_FOUND) list(APPEND excluded_progs git-http-fetch git-http-push) @@ -757,6 +757,9 @@ target_link_libraries(git-sh-i18n--envsubst common-main) add_executable(git-shell ${CMAKE_SOURCE_DIR}/shell.c) target_link_libraries(git-shell common-main) +add_executable(scalar ${CMAKE_SOURCE_DIR}/scalar.c) +target_link_libraries(scalar common-main) + if(CURL_FOUND) add_library(http_obj OBJECT ${CMAKE_SOURCE_DIR}/http.c) @@ -903,7 +906,7 @@ list(TRANSFORM git_perl_scripts PREPEND "${CMAKE_BINARY_DIR}/") #install foreach(program ${PROGRAMS_BUILT}) -if(program STREQUAL "git" OR program STREQUAL "git-shell") +if(program MATCHES "^(git|git-shell|scalar)$") install(TARGETS ${program} RUNTIME DESTINATION bin) else() @@ -977,7 +980,7 @@ endif() #wrapper scripts set(wrapper_scripts - git git-upload-pack git-receive-pack git-upload-archive git-shell git-remote-ext) + git git-upload-pack git-receive-pack git-upload-archive git-shell git-remote-ext scalar) set(wrapper_test_scripts test-fake-ssh test-tool) @@ -1076,7 +1079,7 @@ if(NOT ${CMAKE_BINARY_DIR}/CMakeCache.txt STREQUAL ${CACHE_PATH}) "string(REPLACE \"\${GIT_BUILD_DIR_REPL}\" \"GIT_BUILD_DIR=\\\"$TEST_DIRECTORY/../${BUILD_DIR_RELATIVE}\\\"\" content \"\${content}\")\n" "file(WRITE ${CMAKE_SOURCE_DIR}/t/test-lib.sh \${content})") #misc copies - file(COPY ${CMAKE_SOURCE_DIR}/t/chainlint.sed DESTINATION ${CMAKE_BINARY_DIR}/t/) + file(COPY ${CMAKE_SOURCE_DIR}/t/chainlint.pl DESTINATION ${CMAKE_BINARY_DIR}/t/) file(COPY ${CMAKE_SOURCE_DIR}/po/is.po DESTINATION ${CMAKE_BINARY_DIR}/po/) file(COPY ${CMAKE_SOURCE_DIR}/mergetools/tkdiff DESTINATION ${CMAKE_BINARY_DIR}/mergetools/) file(COPY ${CMAKE_SOURCE_DIR}/contrib/completion/git-prompt.sh DESTINATION ${CMAKE_BINARY_DIR}/contrib/completion/) diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 9a4f00cb1b..aa75937950 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -1,60 +1,58 @@ @@ -expression dst, src, n, E; +type T; +T *dst_ptr; +T *src_ptr; +expression n; @@ - memcpy(dst, src, n * sizeof( -- E[...] -+ *(E) - )) +- memcpy(dst_ptr, src_ptr, (n) * \( sizeof(T) +- \| sizeof(*(dst_ptr)) +- \| sizeof(*(src_ptr)) +- \| sizeof(dst_ptr[...]) +- \| sizeof(src_ptr[...]) +- \) ) ++ COPY_ARRAY(dst_ptr, src_ptr, n) @@ type T; -T *ptr; -T[] arr; -expression E, n; +T *dst_ptr; +T[] src_arr; +expression n; @@ -( - memcpy(ptr, E, -- n * sizeof(*(ptr)) -+ n * sizeof(T) - ) -| - memcpy(arr, E, -- n * sizeof(*(arr)) -+ n * sizeof(T) - ) -| - memcpy(E, ptr, -- n * sizeof(*(ptr)) -+ n * sizeof(T) - ) -| - memcpy(E, arr, -- n * sizeof(*(arr)) -+ n * sizeof(T) - ) -) +- memcpy(dst_ptr, src_arr, (n) * \( sizeof(T) +- \| sizeof(*(dst_ptr)) +- \| sizeof(*(src_arr)) +- \| sizeof(dst_ptr[...]) +- \| sizeof(src_arr[...]) +- \) ) ++ COPY_ARRAY(dst_ptr, src_arr, n) @@ type T; -T *dst_ptr; +T[] dst_arr; T *src_ptr; +expression n; +@@ +- memcpy(dst_arr, src_ptr, (n) * \( sizeof(T) +- \| sizeof(*(dst_arr)) +- \| sizeof(*(src_ptr)) +- \| sizeof(dst_arr[...]) +- \| sizeof(src_ptr[...]) +- \) ) ++ COPY_ARRAY(dst_arr, src_ptr, n) + +@@ +type T; T[] dst_arr; T[] src_arr; expression n; @@ -( -- memcpy(dst_ptr, src_ptr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_ptr, src_ptr, n) -| -- memcpy(dst_ptr, src_arr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_ptr, src_arr, n) -| -- memcpy(dst_arr, src_ptr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_arr, src_ptr, n) -| -- memcpy(dst_arr, src_arr, (n) * sizeof(T)) +- memcpy(dst_arr, src_arr, (n) * \( sizeof(T) +- \| sizeof(*(dst_arr)) +- \| sizeof(*(src_arr)) +- \| sizeof(dst_arr[...]) +- \| sizeof(src_arr[...]) +- \) ) + COPY_ARRAY(dst_arr, src_arr, n) -) @@ type T; diff --git a/contrib/coccinelle/tests/free.c b/contrib/coccinelle/tests/free.c new file mode 100644 index 0000000000..96d4abc0c7 --- /dev/null +++ b/contrib/coccinelle/tests/free.c @@ -0,0 +1,11 @@ +int use_FREE_AND_NULL(int *v) +{ + free(*v); + *v = NULL; +} + +int need_no_if(int *v) +{ + if (v) + free(v); +} diff --git a/contrib/coccinelle/tests/free.res b/contrib/coccinelle/tests/free.res new file mode 100644 index 0000000000..f90fd9f48e --- /dev/null +++ b/contrib/coccinelle/tests/free.res @@ -0,0 +1,9 @@ +int use_FREE_AND_NULL(int *v) +{ + FREE_AND_NULL(*v); +} + +int need_no_if(int *v) +{ + free(v); +} diff --git a/contrib/coccinelle/tests/unused.c b/contrib/coccinelle/tests/unused.c new file mode 100644 index 0000000000..8294d734ba --- /dev/null +++ b/contrib/coccinelle/tests/unused.c @@ -0,0 +1,82 @@ +void test_strbuf(void) +{ + struct strbuf sb1 = STRBUF_INIT; + struct strbuf sb2 = STRBUF_INIT; + struct strbuf sb3 = STRBUF_INIT; + struct strbuf sb4 = STRBUF_INIT; + struct strbuf sb5; + struct strbuf sb6 = { 0 }; + struct strbuf sb7 = STRBUF_INIT; + struct strbuf sb8 = STRBUF_INIT; + struct strbuf *sp1; + struct strbuf *sp2; + struct strbuf *sp3; + struct strbuf *sp4 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp5 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp7; + + strbuf_init(&sb5, 0); + strbuf_init(sp1, 0); + strbuf_init(sp2, 0); + strbuf_init(sp3, 0); + strbuf_init(sp4, 0); + strbuf_init(sp5, 0); + strbuf_init(sp6, 0); + strbuf_init(sp7, 0); + sp7 = xmalloc(sizeof(struct strbuf)); + + use_before(&sb3); + use_as_str("%s", sb7.buf); + use_as_str("%s", sp1->buf); + use_as_str("%s", sp6->buf); + pass_pp(&sp3); + + strbuf_release(&sb1); + strbuf_reset(&sb2); + strbuf_release(&sb3); + strbuf_release(&sb4); + strbuf_release(&sb5); + strbuf_release(&sb6); + strbuf_release(&sb7); + strbuf_release(sp1); + strbuf_release(sp2); + strbuf_release(sp3); + strbuf_release(sp4); + strbuf_release(sp5); + strbuf_release(sp6); + strbuf_release(sp7); + + use_after(&sb4); + + if (when_strict()) + return; + strbuf_release(&sb8); +} + +void test_other(void) +{ + struct string_list l = STRING_LIST_INIT_DUP; + struct strbuf sb = STRBUF_INIT; + + string_list_clear(&l, 0); + string_list_clear(&sb, 0); +} + +void test_worktrees(void) +{ + struct worktree **w1 = get_worktrees(); + struct worktree **w2 = get_worktrees(); + struct worktree **w3; + struct worktree **w4; + + w3 = get_worktrees(); + w4 = get_worktrees(); + + use_it(w4); + + free_worktrees(w1); + free_worktrees(w2); + free_worktrees(w3); + free_worktrees(w4); +} diff --git a/contrib/coccinelle/tests/unused.res b/contrib/coccinelle/tests/unused.res new file mode 100644 index 0000000000..6d3e745683 --- /dev/null +++ b/contrib/coccinelle/tests/unused.res @@ -0,0 +1,45 @@ +void test_strbuf(void) +{ + struct strbuf sb3 = STRBUF_INIT; + struct strbuf sb4 = STRBUF_INIT; + struct strbuf sb7 = STRBUF_INIT; + struct strbuf *sp1; + struct strbuf *sp3; + struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); + strbuf_init(sp1, 0); + strbuf_init(sp3, 0); + strbuf_init(sp6, 0); + + use_before(&sb3); + use_as_str("%s", sb7.buf); + use_as_str("%s", sp1->buf); + use_as_str("%s", sp6->buf); + pass_pp(&sp3); + + strbuf_release(&sb3); + strbuf_release(&sb4); + strbuf_release(&sb7); + strbuf_release(sp1); + strbuf_release(sp3); + strbuf_release(sp6); + + use_after(&sb4); + + if (when_strict()) + return; +} + +void test_other(void) +{ +} + +void test_worktrees(void) +{ + struct worktree **w4; + + w4 = get_worktrees(); + + use_it(w4); + + free_worktrees(w4); +} diff --git a/contrib/coccinelle/unused.cocci b/contrib/coccinelle/unused.cocci new file mode 100644 index 0000000000..d84046f82e --- /dev/null +++ b/contrib/coccinelle/unused.cocci @@ -0,0 +1,43 @@ +// This rule finds sequences of "unused" declerations and uses of a +// variable, where "unused" is defined to include only calling the +// equivalent of alloc, init & free functions on the variable. +@@ +type T; +identifier I; +// STRBUF_INIT, but also e.g. STRING_LIST_INIT_DUP (so no anchoring) +constant INIT_MACRO =~ "_INIT"; +identifier MALLOC1 =~ "^x?[mc]alloc$"; +identifier INIT_ASSIGN1 =~ "^get_worktrees$"; +identifier INIT_CALL1 =~ "^[a-z_]*_init$"; +identifier REL1 =~ "^[a-z_]*_(release|reset|clear|free)$"; +identifier REL2 =~ "^(release|clear|free)_[a-z_]*$"; +@@ + +( +- T I; +| +- T I = { 0 }; +| +- T I = INIT_MACRO; +| +- T I = MALLOC1(...); +| +- T I = INIT_ASSIGN1(...); +) + +<... when != \( I \| &I \) +( +- \( INIT_CALL1 \)( \( I \| &I \), ...); +| +- I = \( INIT_ASSIGN1 \)(...); +| +- I = MALLOC1(...); +) +...> + +( +- \( REL1 \| REL2 \)( \( I \| &I \), ...); +| +- \( REL1 \| REL2 \)( \( &I \| I \) ); +) + ... when != \( I \| &I \) diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 1435548e00..57972c2845 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -84,6 +84,10 @@ # single '?' character by setting GIT_PS1_COMPRESSSPARSESTATE, or omitted # by setting GIT_PS1_OMITSPARSESTATE. # +# If you would like to see a notification on the prompt when there are +# unresolved conflicts, set GIT_PS1_SHOWCONFLICTSTATE to "yes". The +# prompt will include "|CONFLICT". +# # If you would like to see more information about the identity of # commits checked out as a detached HEAD, set GIT_PS1_DESCRIBE_STYLE # to one of these values: @@ -508,6 +512,12 @@ __git_ps1 () r="$r $step/$total" fi + local conflict="" # state indicator for unresolved conflicts + if [[ "${GIT_PS1_SHOWCONFLICTSTATE}" == "yes" ]] && + [[ $(git ls-files --unmerged 2>/dev/null) ]]; then + conflict="|CONFLICT" + fi + local w="" local i="" local s="" @@ -572,7 +582,7 @@ __git_ps1 () fi local f="$h$w$i$s$u$p" - local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}" + local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}${conflict}" if [ $pcmode = yes ]; then if [ "${__git_printf_supports_v-}" != yes ]; then diff --git a/contrib/credential/netrc/t-git-credential-netrc.sh b/contrib/credential/netrc/t-git-credential-netrc.sh index 07227d0228..bf2777308a 100755 --- a/contrib/credential/netrc/t-git-credential-netrc.sh +++ b/contrib/credential/netrc/t-git-credential-netrc.sh @@ -3,16 +3,9 @@ cd ../../../t test_description='git-credential-netrc' . ./test-lib.sh + . "$TEST_DIRECTORY"/lib-perl.sh - if ! test_have_prereq PERL; then - skip_all='skipping perl interface tests, perl not available' - test_done - fi - - perl -MTest::More -e 0 2>/dev/null || { - skip_all="Perl Test::More unavailable, skipping test" - test_done - } + skip_all_if_no_Test_More # set up test repository @@ -20,13 +13,10 @@ 'set up test repository' \ 'git config --add gpg.program test.git-config-gpg' - # The external test will outputs its own plan - test_external_has_tap=1 - export PERL5LIB="$GITPERLLIB" - test_external \ - 'git-credential-netrc' \ + test_expect_success 'git-credential-netrc' ' perl "$GIT_BUILD_DIR"/contrib/credential/netrc/test.pl + ' test_done ) diff --git a/contrib/scalar/.gitignore b/contrib/scalar/.gitignore deleted file mode 100644 index ff3d47e84d..0000000000 --- a/contrib/scalar/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*.exe -/scalar diff --git a/contrib/scalar/Makefile b/contrib/scalar/Makefile deleted file mode 100644 index 37f283f35d..0000000000 --- a/contrib/scalar/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -# The default target of this Makefile is... -all:: - -# Import tree-wide shared Makefile behavior and libraries -include ../../shared.mak - -include ../../config.mak.uname --include ../../config.mak.autogen --include ../../config.mak - -TARGETS = scalar$(X) scalar.o -GITLIBS = ../../common-main.o ../../libgit.a ../../xdiff/lib.a - -all:: scalar$(X) ../../bin-wrappers/scalar - -$(GITLIBS): - $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(subst ../../,,$@) - -$(TARGETS): $(GITLIBS) scalar.c - $(QUIET_SUBDIR0)../.. $(QUIET_SUBDIR1) $(patsubst %,contrib/scalar/%,$@) - -clean: - $(RM) $(TARGETS) ../../bin-wrappers/scalar - -../../bin-wrappers/scalar: ../../wrap-for-bin.sh Makefile - @mkdir -p ../../bin-wrappers - $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|@@BUILD_DIR@@|$(shell cd ../.. && pwd)|' \ - -e 's|@@PROG@@|contrib/scalar/scalar$(X)|' < $< > $@ && \ - chmod +x $@ - -test: all - $(MAKE) -C t - -.PHONY: $(GITLIBS) all clean test FORCE diff --git a/contrib/scalar/README.md b/contrib/scalar/README.md deleted file mode 100644 index 634b5771ed..0000000000 --- a/contrib/scalar/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Scalar - an opinionated repository management tool - -Scalar is an add-on to Git that helps users take advantage of advanced -performance features in Git. Originally implemented in C# using .NET Core, -based on the learnings from the VFS for Git project, most of the techniques -developed by the Scalar project have been integrated into core Git already: - -* partial clone, -* commit graphs, -* multi-pack index, -* sparse checkout (cone mode), -* scheduled background maintenance, -* etc - -This directory contains the remaining parts of Scalar that are not (yet) in -core Git. - -## Roadmap - -The idea is to populate this directory via incremental patch series and -eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The -current plan involves the following patch series: - -- `scalar-the-beginning`: The initial patch series which sets up - `contrib/scalar/` and populates it with a minimal `scalar` command that - demonstrates the fundamental ideas. - -- `scalar-c-and-C`: The `scalar` command learns about two options that can be - specified before the command, `-c =` and `-C `. - -- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. - -- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar - register` and in `scalar clone`, for an enormous performance boost when - working in large worktrees. This patch series necessarily depends on Jeff - Hostetler's FSMonitor patch series to be integrated into Git. - -- `scalar-gentler-config-locking`: Scalar enlistments are registered in the - user's Git config. This usually does not represent any problem because it is - rare for a user to register an enlistment. However, in Scalar's functional - tests, Scalar enlistments are created galore, and in parallel, which can lead - to lock contention. This patch series works around that problem by re-trying - to lock the config file in a gentle fashion. - -- `scalar-extra-docs`: Add some extensive documentation that has been written - in the original Scalar project (all subject to discussion, of course). - -- `optionally-install-scalar`: Now that Scalar is feature (and documentation) - complete and is verified in CI builds, let's offer to install it. - -- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to - `gitk-git/` and to `git-gui/`, making it a top-level command. - -The following two patch series exist in Microsoft's fork of Git and are -publicly available. There is no current plan to upstream them, not because I -want to withhold these patches, but because I don't think the Git community is -interested in these patches. - -There are some interesting ideas there, but the implementation is too specific -to Azure Repos and/or VFS for Git to be of much help in general (and also: my -colleagues tried to upstream some patches already and the enthusiasm for -integrating things related to Azure Repos and VFS for Git can be summarized in -very, very few words). - -These still exist mainly because the GVFS protocol is what Azure Repos has -instead of partial clone, while Git is focused on improving partial clone: - -- `scalar-with-gvfs`: The primary purpose of this patch series is to support - existing Scalar users whose repositories are hosted in Azure Repos (which - does not support Git's partial clones, but supports its predecessor, the GVFS - protocol, which is used by Scalar to emulate the partial clone). - - Since the GVFS protocol will never be supported by core Git, this patch - series will remain in Microsoft's fork of Git. - -- `run-scalar-functional-tests`: The Scalar project developed a quite - comprehensive set of integration tests (or, "Functional Tests"). They are the - sole remaining part of the original C#-based Scalar project, and this patch - adds a GitHub workflow that runs them all. - - Since the tests partially depend on features that are only provided in the - `scalar-with-gvfs` patch series, this patch cannot be upstreamed. diff --git a/contrib/scalar/t/Makefile b/contrib/scalar/t/Makefile deleted file mode 100644 index 01e82e56d1..0000000000 --- a/contrib/scalar/t/Makefile +++ /dev/null @@ -1,81 +0,0 @@ -# Import tree-wide shared Makefile behavior and libraries -include ../../../shared.mak - -# Run scalar tests -# -# Copyright (c) 2005,2021 Junio C Hamano, Johannes Schindelin -# - --include ../../../config.mak.autogen --include ../../../config.mak - -SHELL_PATH ?= $(SHELL) -PERL_PATH ?= /usr/bin/perl -RM ?= rm -f -PROVE ?= prove -DEFAULT_TEST_TARGET ?= test -TEST_LINT ?= test-lint - -ifdef TEST_OUTPUT_DIRECTORY -TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results -else -TEST_RESULTS_DIRECTORY = ../../../t/test-results -endif - -# Shell quote; -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) - -T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) - -all: $(DEFAULT_TEST_TARGET) - -test: $(TEST_LINT) - $(MAKE) aggregate-results-and-cleanup - -prove: $(TEST_LINT) - @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) - $(MAKE) clean-except-prove-cache - -$(T): - @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) - -clean-except-prove-cache: - $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' - $(RM) -r valgrind/bin - -clean: clean-except-prove-cache - $(RM) .prove - -test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax - -test-lint-duplicates: - @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ - test -z "$$dups" || { \ - echo >&2 "duplicate test numbers:" $$dups; exit 1; } - -test-lint-executable: - @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \ - test -z "$$bad" || { \ - echo >&2 "non-executable tests:" $$bad; exit 1; } - -test-lint-shell-syntax: - @'$(PERL_PATH_SQ)' ../../../t/check-non-portable-shell.pl $(T) - -aggregate-results-and-cleanup: $(T) - $(MAKE) aggregate-results - $(MAKE) clean - -aggregate-results: - for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ - echo "$$f"; \ - done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh - -valgrind: - $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" - -test-results: - mkdir -p test-results - -.PHONY: $(T) aggregate-results clean valgrind diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 1af1d9653e..7562a395c2 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -50,6 +50,14 @@ m,message= use the given message as the commit message for the merge commit indent=0 +# Usage: say [MSG...] +say () { + if test -z "$arg_quiet" + then + printf '%s\n' "$*" + fi +} + # Usage: debug [MSG...] debug () { if test -n "$arg_debug" @@ -60,7 +68,7 @@ debug () { # Usage: progress [MSG...] progress () { - if test -z "$GIT_QUIET" + if test -z "$arg_quiet" then if test -z "$arg_debug" then @@ -146,6 +154,7 @@ main () { eval "$set_args" # Begin "real" flag parsing. + arg_quiet= arg_debug= arg_prefix= arg_split_branch= @@ -161,7 +170,7 @@ main () { case "$opt" in -q) - GIT_QUIET=1 + arg_quiet=1 ;; -d) arg_debug=1 @@ -252,7 +261,7 @@ main () { dir="$(dirname "$arg_prefix/.")" debug "command: {$arg_command}" - debug "quiet: {$GIT_QUIET}" + debug "quiet: {$arg_quiet}" debug "dir: {$dir}" debug "opts: {$*}" debug diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile index 276898eb6b..4655e0987b 100644 --- a/contrib/subtree/t/Makefile +++ b/contrib/subtree/t/Makefile @@ -47,10 +47,11 @@ pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' clean-except-prove-cache: - $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' + $(RM) -r 'trash directory'.* $(RM) -r valgrind/bin clean: clean-except-prove-cache + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) .prove test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax diff --git a/convert.c b/convert.c index 4d153729da..95e6a5244f 100644 --- a/convert.c +++ b/convert.c @@ -619,7 +619,7 @@ struct filter_params { const char *path; }; -static int filter_buffer_or_fd(int in, int out, void *data) +static int filter_buffer_or_fd(int in UNUSED, int out, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. @@ -1008,7 +1008,7 @@ static int apply_filter(const char *path, const char *src, size_t len, return 0; } -static int read_convert_config(const char *var, const char *value, void *cb) +static int read_convert_config(const char *var, const char *value, void *cb UNUSED) { const char *key, *name; size_t namelen; diff --git a/delta-islands.c b/delta-islands.c index aa98b2e541..26f9e99e1a 100644 --- a/delta-islands.c +++ b/delta-islands.c @@ -316,7 +316,7 @@ static regex_t *island_regexes; static unsigned int island_regexes_alloc, island_regexes_nr; static const char *core_island_name; -static int island_config_callback(const char *k, const char *v, void *cb) +static int island_config_callback(const char *k, const char *v, void *cb UNUSED) { if (!strcmp(k, "pack.island")) { struct strbuf re = STRBUF_INIT; @@ -365,7 +365,7 @@ static void add_ref_to_island(const char *island_name, const struct object_id *o } static int find_island_for_ref(const char *refname, const struct object_id *oid, - int flags, void *data) + int flags UNUSED, void *data UNUSED) { /* * We should advertise 'ARRAY_SIZE(matches) - 2' as the max, diff --git a/diagnose.c b/diagnose.c new file mode 100644 index 0000000000..8f26569896 --- /dev/null +++ b/diagnose.c @@ -0,0 +1,311 @@ +#include "cache.h" +#include "diagnose.h" +#include "compat/disk.h" +#include "archive.h" +#include "dir.h" +#include "help.h" +#include "strvec.h" +#include "object-store.h" +#include "packfile.h" + +struct archive_dir { + const char *path; + int recursive; +}; + +struct diagnose_option { + enum diagnose_mode mode; + const char *option_name; +}; + +static struct diagnose_option diagnose_options[] = { + { DIAGNOSE_STATS, "stats" }, + { DIAGNOSE_ALL, "all" }, +}; + +int option_parse_diagnose(const struct option *opt, const char *arg, int unset) +{ + int i; + enum diagnose_mode *diagnose = opt->value; + + if (!arg) { + *diagnose = unset ? DIAGNOSE_NONE : DIAGNOSE_STATS; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(diagnose_options); i++) { + if (!strcmp(arg, diagnose_options[i].option_name)) { + *diagnose = diagnose_options[i].mode; + return 0; + } + } + + return error(_("invalid --%s value '%s'"), opt->long_name, arg); +} + +static void dir_file_stats_objects(const char *full_path, size_t full_path_len, + const char *file_name, void *data) +{ + struct strbuf *buf = data; + struct stat st; + + if (!stat(full_path, &st)) + strbuf_addf(buf, "%-70s %16" PRIuMAX "\n", file_name, + (uintmax_t)st.st_size); +} + +static int dir_file_stats(struct object_directory *object_dir, void *data) +{ + struct strbuf *buf = data; + + strbuf_addf(buf, "Contents of %s:\n", object_dir->path); + + for_each_file_in_pack_dir(object_dir->path, dir_file_stats_objects, + data); + + return 0; +} + +/* + * Get the d_type of a dirent. If the d_type is unknown, derive it from + * stat.st_mode. + * + * Note that 'path' is assumed to have a trailing slash. It is also modified + * in-place during the execution of the function, but is then reverted to its + * original value before returning. + */ +static unsigned char get_dtype(struct dirent *e, struct strbuf *path) +{ + struct stat st; + unsigned char dtype = DTYPE(e); + size_t base_path_len; + + if (dtype != DT_UNKNOWN) + return dtype; + + /* d_type unknown in dirent, try to fall back on lstat results */ + base_path_len = path->len; + strbuf_addstr(path, e->d_name); + if (lstat(path->buf, &st)) + goto cleanup; + + /* determine d_type from st_mode */ + if (S_ISREG(st.st_mode)) + dtype = DT_REG; + else if (S_ISDIR(st.st_mode)) + dtype = DT_DIR; + else if (S_ISLNK(st.st_mode)) + dtype = DT_LNK; + +cleanup: + strbuf_setlen(path, base_path_len); + return dtype; +} + +static int count_files(struct strbuf *path) +{ + DIR *dir = opendir(path->buf); + struct dirent *e; + int count = 0; + + if (!dir) + return 0; + + while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) + if (get_dtype(e, path) == DT_REG) + count++; + + closedir(dir); + return count; +} + +static void loose_objs_stats(struct strbuf *buf, const char *path) +{ + DIR *dir = opendir(path); + struct dirent *e; + int count; + int total = 0; + unsigned char c; + struct strbuf count_path = STRBUF_INIT; + size_t base_path_len; + + if (!dir) + return; + + strbuf_addstr(buf, "Object directory stats for "); + strbuf_add_absolute_path(buf, path); + strbuf_addstr(buf, ":\n"); + + strbuf_add_absolute_path(&count_path, path); + strbuf_addch(&count_path, '/'); + base_path_len = count_path.len; + + while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) + if (get_dtype(e, &count_path) == DT_DIR && + strlen(e->d_name) == 2 && + !hex_to_bytes(&c, e->d_name, 1)) { + strbuf_setlen(&count_path, base_path_len); + strbuf_addf(&count_path, "%s/", e->d_name); + total += (count = count_files(&count_path)); + strbuf_addf(buf, "%s : %7d files\n", e->d_name, count); + } + + strbuf_addf(buf, "Total: %d loose objects", total); + + strbuf_release(&count_path); + closedir(dir); +} + +static int add_directory_to_archiver(struct strvec *archiver_args, + const char *path, int recurse) +{ + int at_root = !*path; + DIR *dir; + struct dirent *e; + struct strbuf buf = STRBUF_INIT; + size_t len; + int res = 0; + + dir = opendir(at_root ? "." : path); + if (!dir) { + if (errno == ENOENT) { + warning(_("could not archive missing directory '%s'"), path); + return 0; + } + return error_errno(_("could not open directory '%s'"), path); + } + + if (!at_root) + strbuf_addf(&buf, "%s/", path); + len = buf.len; + strvec_pushf(archiver_args, "--prefix=%s", buf.buf); + + while (!res && (e = readdir_skip_dot_and_dotdot(dir))) { + struct strbuf abspath = STRBUF_INIT; + unsigned char dtype; + + strbuf_add_absolute_path(&abspath, at_root ? "." : path); + strbuf_addch(&abspath, '/'); + dtype = get_dtype(e, &abspath); + + strbuf_setlen(&buf, len); + strbuf_addstr(&buf, e->d_name); + + if (dtype == DT_REG) + strvec_pushf(archiver_args, "--add-file=%s", buf.buf); + else if (dtype != DT_DIR) + warning(_("skipping '%s', which is neither file nor " + "directory"), buf.buf); + else if (recurse && + add_directory_to_archiver(archiver_args, + buf.buf, recurse) < 0) + res = -1; + + strbuf_release(&abspath); + } + + closedir(dir); + strbuf_release(&buf); + return res; +} + +int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode) +{ + struct strvec archiver_args = STRVEC_INIT; + char **argv_copy = NULL; + int stdout_fd = -1, archiver_fd = -1; + struct strbuf buf = STRBUF_INIT; + int res, i; + struct archive_dir archive_dirs[] = { + { ".git", 0 }, + { ".git/hooks", 0 }, + { ".git/info", 0 }, + { ".git/logs", 1 }, + { ".git/objects/info", 0 } + }; + + if (mode == DIAGNOSE_NONE) { + res = 0; + goto diagnose_cleanup; + } + + stdout_fd = dup(STDOUT_FILENO); + if (stdout_fd < 0) { + res = error_errno(_("could not duplicate stdout")); + goto diagnose_cleanup; + } + + archiver_fd = xopen(zip_path->buf, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (dup2(archiver_fd, STDOUT_FILENO) < 0) { + res = error_errno(_("could not redirect output")); + goto diagnose_cleanup; + } + + init_zip_archiver(); + strvec_pushl(&archiver_args, "git-diagnose", "--format=zip", NULL); + + strbuf_reset(&buf); + strbuf_addstr(&buf, "Collecting diagnostic info\n\n"); + get_version_info(&buf, 1); + + strbuf_addf(&buf, "Repository root: %s\n", the_repository->worktree); + get_disk_info(&buf); + write_or_die(stdout_fd, buf.buf, buf.len); + strvec_pushf(&archiver_args, + "--add-virtual-file=diagnostics.log:%.*s", + (int)buf.len, buf.buf); + + strbuf_reset(&buf); + strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:"); + dir_file_stats(the_repository->objects->odb, &buf); + foreach_alt_odb(dir_file_stats, &buf); + strvec_push(&archiver_args, buf.buf); + + strbuf_reset(&buf); + strbuf_addstr(&buf, "--add-virtual-file=objects-local.txt:"); + loose_objs_stats(&buf, ".git/objects"); + strvec_push(&archiver_args, buf.buf); + + /* Only include this if explicitly requested */ + if (mode == DIAGNOSE_ALL) { + for (i = 0; i < ARRAY_SIZE(archive_dirs); i++) { + if (add_directory_to_archiver(&archiver_args, + archive_dirs[i].path, + archive_dirs[i].recursive)) { + res = error_errno(_("could not add directory '%s' to archiver"), + archive_dirs[i].path); + goto diagnose_cleanup; + } + } + } + + strvec_pushl(&archiver_args, "--prefix=", + oid_to_hex(the_hash_algo->empty_tree), "--", NULL); + + /* `write_archive()` modifies the `argv` passed to it. Let it. */ + argv_copy = xmemdupz(archiver_args.v, + sizeof(char *) * archiver_args.nr); + res = write_archive(archiver_args.nr, (const char **)argv_copy, NULL, + the_repository, NULL, 0); + if (res) { + error(_("failed to write archive")); + goto diagnose_cleanup; + } + + fprintf(stderr, "\n" + "Diagnostics complete.\n" + "All of the gathered info is captured in '%s'\n", + zip_path->buf); + +diagnose_cleanup: + if (archiver_fd >= 0) { + dup2(stdout_fd, STDOUT_FILENO); + close(stdout_fd); + close(archiver_fd); + } + free(argv_copy); + strvec_clear(&archiver_args); + strbuf_release(&buf); + + return res; +} diff --git a/diagnose.h b/diagnose.h new file mode 100644 index 0000000000..7a4951a786 --- /dev/null +++ b/diagnose.h @@ -0,0 +1,17 @@ +#ifndef DIAGNOSE_H +#define DIAGNOSE_H + +#include "strbuf.h" +#include "parse-options.h" + +enum diagnose_mode { + DIAGNOSE_NONE, + DIAGNOSE_STATS, + DIAGNOSE_ALL +}; + +int option_parse_diagnose(const struct option *opt, const char *arg, int unset); + +int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode); + +#endif /* DIAGNOSE_H */ diff --git a/diff-merges.c b/diff-merges.c index 7f64156b8b..85cbefa5af 100644 --- a/diff-merges.c +++ b/diff-merges.c @@ -20,9 +20,20 @@ static void suppress(struct rev_info *revs) revs->remerge_diff = 0; } -static void set_separate(struct rev_info *revs) +static void common_setup(struct rev_info *revs) { suppress(revs); + revs->merges_need_diff = 1; +} + +static void set_none(struct rev_info *revs) +{ + suppress(revs); +} + +static void set_separate(struct rev_info *revs) +{ + common_setup(revs); revs->separate_merges = 1; revs->simplify_history = 0; } @@ -35,21 +46,21 @@ static void set_first_parent(struct rev_info *revs) static void set_combined(struct rev_info *revs) { - suppress(revs); + common_setup(revs); revs->combine_merges = 1; revs->dense_combined_merges = 0; } static void set_dense_combined(struct rev_info *revs) { - suppress(revs); + common_setup(revs); revs->combine_merges = 1; revs->dense_combined_merges = 1; } static void set_remerge_diff(struct rev_info *revs) { - suppress(revs); + common_setup(revs); revs->remerge_diff = 1; revs->simplify_history = 0; } @@ -57,18 +68,18 @@ static void set_remerge_diff(struct rev_info *revs) static diff_merges_setup_func_t func_by_opt(const char *optarg) { if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) - return suppress; + return set_none; if (!strcmp(optarg, "1") || !strcmp(optarg, "first-parent")) return set_first_parent; - else if (!strcmp(optarg, "separate")) + if (!strcmp(optarg, "separate")) return set_separate; - else if (!strcmp(optarg, "c") || !strcmp(optarg, "combined")) + if (!strcmp(optarg, "c") || !strcmp(optarg, "combined")) return set_combined; - else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined")) + if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined")) return set_dense_combined; - else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge")) + if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge")) return set_remerge_diff; - else if (!strcmp(optarg, "m") || !strcmp(optarg, "on")) + if (!strcmp(optarg, "m") || !strcmp(optarg, "on")) return set_to_default; return NULL; } @@ -81,10 +92,6 @@ static void set_diff_merges(struct rev_info *revs, const char *optarg) die(_("invalid value for '%s': '%s'"), "--diff-merges", optarg); func(revs); - - /* NOTE: the merges_need_diff flag is cleared by func() call */ - if (func != suppress) - revs->merges_need_diff = 1; } /* @@ -115,6 +122,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv) if (!suppress_m_parsing && !strcmp(arg, "-m")) { set_to_default(revs); + revs->merges_need_diff = 0; } else if (!strcmp(arg, "-c")) { set_combined(revs); revs->merges_imply_patch = 1; @@ -125,7 +133,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv) set_remerge_diff(revs); revs->merges_imply_patch = 1; } else if (!strcmp(arg, "--no-diff-merges")) { - suppress(revs); + set_none(revs); } else if (!strcmp(arg, "--combined-all-paths")) { revs->combined_all_paths = 1; } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) { @@ -139,7 +147,7 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv) void diff_merges_suppress(struct rev_info *revs) { - suppress(revs); + set_none(revs); } void diff_merges_default_to_first_parent(struct rev_info *revs) diff --git a/diff-no-index.c b/diff-no-index.c index 9a8b09346b..18edbdf4b5 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -243,7 +243,9 @@ int diff_no_index(struct rev_info *revs, int argc, const char **argv) { int i, no_index; + int ret = 1; const char *paths[2]; + char *to_free[ARRAY_SIZE(paths)] = { 0 }; struct strbuf replacement = STRBUF_INIT; const char *prefix = revs->prefix; struct option no_index_options[] = { @@ -265,7 +267,7 @@ int diff_no_index(struct rev_info *revs, } FREE_AND_NULL(options); for (i = 0; i < 2; i++) { - const char *p = argv[argc - 2 + i]; + const char *p = argv[i]; if (!strcmp(p, "-")) /* * stdin should be spelled as "-"; if you have @@ -273,7 +275,7 @@ int diff_no_index(struct rev_info *revs, */ p = file_from_standard_input; else if (prefix) - p = prefix_filename(prefix, p); + p = to_free[i] = prefix_filename(prefix, p); paths[i] = p; } @@ -295,16 +297,20 @@ int diff_no_index(struct rev_info *revs, revs->diffopt.flags.exit_with_status = 1; if (queue_diff(&revs->diffopt, paths[0], paths[1])) - return 1; + goto out; diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); - strbuf_release(&replacement); - /* * The return code for --no-index imitates diff(1): * 0 = no changes, 1 = changes, else error */ - return diff_result_code(&revs->diffopt, 0); + ret = diff_result_code(&revs->diffopt, 0); + +out: + for (i = 0; i < ARRAY_SIZE(to_free); i++) + free(to_free[i]); + strbuf_release(&replacement); + return ret; } diff --git a/diff.c b/diff.c index e71cf75886..648f6717a5 100644 --- a/diff.c +++ b/diff.c @@ -264,7 +264,8 @@ void init_diff_ui_defaults(void) diff_detect_rename_default = DIFF_DETECT_RENAME; } -int git_diff_heuristic_config(const char *var, const char *value, void *cb) +int git_diff_heuristic_config(const char *var, const char *value, + void *cb UNUSED) { if (!strcmp(var, "diff.indentheuristic")) diff_indent_heuristic = git_config_bool(var, value); @@ -916,7 +917,7 @@ struct interned_diff_symbol { static int interned_diff_symbol_cmp(const void *hashmap_cmp_fn_data, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *keydata) + const void *keydata UNUSED) { const struct diff_options *diffopt = hashmap_cmp_fn_data; const struct emitted_diff_symbol *a, *b; @@ -1289,7 +1290,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, { static const char *nneof = " No newline at end of file\n"; const char *context, *reset, *set, *set_sign, *meta, *fraginfo; - struct strbuf sb = STRBUF_INIT; enum diff_symbol s = eds->s; const char *line = eds->line; @@ -1521,7 +1521,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, default: BUG("unknown diff symbol"); } - strbuf_release(&sb); } static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s, @@ -3362,23 +3361,23 @@ struct userdiff_driver *get_textconv(struct repository *r, return userdiff_get_textconv(r, one->driver); } -static struct strbuf *additional_headers(struct diff_options *o, - const char *path) +static struct string_list *additional_headers(struct diff_options *o, + const char *path) { if (!o->additional_path_headers) return NULL; return strmap_get(o->additional_path_headers, path); } -static void add_formatted_headers(struct strbuf *msg, - struct strbuf *more_headers, +static void add_formatted_header(struct strbuf *msg, + const char *header, const char *line_prefix, const char *meta, const char *reset) { - char *next, *newline; + const char *next, *newline; - for (next = more_headers->buf; *next; next = newline) { + for (next = header; *next; next = newline) { newline = strchrnul(next, '\n'); strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta, (int)(newline - next), next, reset); @@ -3387,6 +3386,34 @@ static void add_formatted_headers(struct strbuf *msg, } } +static void add_formatted_headers(struct strbuf *msg, + struct string_list *more_headers, + const char *line_prefix, + const char *meta, + const char *reset) +{ + int i; + + for (i = 0; i < more_headers->nr; i++) + add_formatted_header(msg, more_headers->items[i].string, + line_prefix, meta, reset); +} + +static int diff_filepair_is_phoney(struct diff_filespec *one, + struct diff_filespec *two) +{ + /* + * This function specifically looks for pairs injected by + * create_filepairs_for_header_only_notifications(). Such + * pairs are "phoney" in that they do not represent any + * content or even mode difference, but were inserted because + * diff_queued_diff previously had no pair associated with + * that path but we needed some pair to avoid losing the + * "remerge CONFLICT" header associated with the path. + */ + return !DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two); +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -3418,14 +3445,16 @@ static void builtin_diff(const char *name_a, if (o->submodule_format == DIFF_SUBMODULE_LOG && (!one->mode || S_ISGITLINK(one->mode)) && - (!two->mode || S_ISGITLINK(two->mode))) { + (!two->mode || S_ISGITLINK(two->mode)) && + (!diff_filepair_is_phoney(one, two))) { show_submodule_diff_summary(o, one->path ? one->path : two->path, &one->oid, &two->oid, two->dirty_submodule); return; } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF && (!one->mode || S_ISGITLINK(one->mode)) && - (!two->mode || S_ISGITLINK(two->mode))) { + (!two->mode || S_ISGITLINK(two->mode)) && + (!diff_filepair_is_phoney(one, two))) { show_submodule_inline_diff(o, one->path ? one->path : two->path, &one->oid, &two->oid, two->dirty_submodule); @@ -3445,12 +3474,12 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) { + if (diff_filepair_is_phoney(one, two)) { /* - * We should only reach this point for pairs from + * We should only reach this point for pairs generated from * create_filepairs_for_header_only_notifications(). For - * these, we should avoid the "/dev/null" special casing - * above, meaning we avoid showing such pairs as either + * these, we want to avoid the "/dev/null" special casing + * above, because we do not want such pairs shown as either * "new file" or "deleted file" below. */ lbl[0] = a_one; @@ -4314,7 +4343,7 @@ static void fill_metainfo(struct strbuf *msg, const char *set = diff_get_color(use_color, DIFF_METAINFO); const char *reset = diff_get_color(use_color, DIFF_RESET); const char *line_prefix = diff_line_prefix(o); - struct strbuf *more_headers = NULL; + struct string_list *more_headers = NULL; *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); @@ -5650,7 +5679,7 @@ int diff_opt_parse(struct diff_options *options, ac = parse_options(ac, av, prefix, options->parseopts, NULL, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_NO_INTERNAL_HELP | PARSE_OPT_ONE_SHOT | PARSE_OPT_STOP_AT_NON_OPTION); @@ -5841,6 +5870,7 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) { int include_conflict_headers = (additional_headers(o, p->one->path) && + !o->pickaxe_opts && (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o))); /* @@ -5896,6 +5926,8 @@ int diff_queue_is_empty(struct diff_options *o) int i; int include_conflict_headers = (o->additional_path_headers && + strmap_get_size(o->additional_path_headers) && + !o->pickaxe_opts && (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o))); if (include_conflict_headers) diff --git a/dir.c b/dir.c index d7cfb08e44..7542950820 100644 --- a/dir.c +++ b/dir.c @@ -655,10 +655,10 @@ void parse_path_pattern(const char **pattern, *patternlen = len; } -int pl_hashmap_cmp(const void *unused_cmp_data, +int pl_hashmap_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *a, const struct hashmap_entry *b, - const void *key) + const void *key UNUSED) { const struct pattern_entry *ee1 = container_of(a, struct pattern_entry, ent); @@ -1244,8 +1244,7 @@ int match_basename(const char *basename, int basenamelen, int match_pathname(const char *pathname, int pathlen, const char *base, int baselen, - const char *pattern, int prefix, int patternlen, - unsigned flags) + const char *pattern, int prefix, int patternlen) { const char *name; int namelen; @@ -1347,8 +1346,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname if (match_pathname(pathname, pathlen, pattern->base, pattern->baselen ? pattern->baselen - 1 : 0, - exclude, prefix, pattern->patternlen, - pattern->flags)) { + exclude, prefix, pattern->patternlen)) { res = pattern; break; } diff --git a/dir.h b/dir.h index 7bc862030c..674747d93a 100644 --- a/dir.h +++ b/dir.h @@ -414,7 +414,7 @@ int match_basename(const char *, int, const char *, int, int, unsigned); int match_pathname(const char *, int, const char *, int, - const char *, int, int, unsigned); + const char *, int, int); struct path_pattern *last_matching_pattern(struct dir_struct *dir, struct index_state *istate, diff --git a/environment.c b/environment.c index b3296ce7d1..18d042b467 100644 --- a/environment.c +++ b/environment.c @@ -56,7 +56,6 @@ const char *askpass_program; const char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; int read_replace_refs = 1; -char *git_replace_ref_base; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding = "SHIFT-JIS"; @@ -162,6 +161,7 @@ const char *getenv_safe(struct strvec *argv, const char *name) void setup_git_env(const char *git_dir) { + char *git_replace_ref_base; const char *shallow_file; const char *replace_ref_base; struct set_gitdir_args args = { NULL }; @@ -182,9 +182,10 @@ void setup_git_env(const char *git_dir) if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) read_replace_refs = 0; replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT); - free(git_replace_ref_base); git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base : "refs/replace/"); + update_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base); + free(git_namespace); git_namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT)); shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); @@ -333,10 +334,10 @@ static void set_git_dir_1(const char *path) setup_git_env(path); } -static void update_relative_gitdir(const char *name, +static void update_relative_gitdir(const char *name UNUSED, const char *old_cwd, const char *new_cwd, - void *data) + void *data UNUSED) { char *path = reparent_relative_path(old_cwd, new_cwd, get_git_dir()); struct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb(); diff --git a/fetch-pack.c b/fetch-pack.c index cb6647d657..998fc2fa1e 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -26,6 +26,7 @@ #include "commit-reach.h" #include "commit-graph.h" #include "sigchain.h" +#include "mergesort.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -175,8 +176,10 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator, return 0; } -static int rev_list_insert_ref_oid(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int rev_list_insert_ref_oid(const char *refname UNUSED, + const struct object_id *oid, + int flag UNUSED, + void *cb_data) { return rev_list_insert_ref(cb_data, oid); } @@ -292,6 +295,29 @@ static void mark_tips(struct fetch_negotiator *negotiator, return; } +static void send_filter(struct fetch_pack_args *args, + struct strbuf *req_buf, + int server_supports_filter) +{ + if (args->filter_options.choice) { + const char *spec = + expand_list_objects_filter_spec(&args->filter_options); + if (server_supports_filter) { + print_verbose(args, _("Server supports filter")); + packet_buf_write(req_buf, "filter %s", spec); + trace2_data_string("fetch", the_repository, + "filter/effective", spec); + } else { + warning("filtering not recognized by server, ignoring"); + trace2_data_string("fetch", the_repository, + "filter/unsupported", spec); + } + } else { + trace2_data_string("fetch", the_repository, + "filter/none", ""); + } +} + static int find_common(struct fetch_negotiator *negotiator, struct fetch_pack_args *args, int fd[2], struct object_id *result_oid, @@ -299,6 +325,7 @@ static int find_common(struct fetch_negotiator *negotiator, { int fetching; int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; + int negotiation_round = 0, haves = 0; const struct object_id *oid; unsigned in_vain = 0; int got_continue = 0; @@ -389,11 +416,7 @@ static int find_common(struct fetch_negotiator *negotiator, packet_buf_write(&req_buf, "deepen-not %s", s->string); } } - if (server_supports_filtering && args->filter_options.choice) { - const char *spec = - expand_list_objects_filter_spec(&args->filter_options); - packet_buf_write(&req_buf, "filter %s", spec); - } + send_filter(args, &req_buf, server_supports_filtering); packet_buf_flush(&req_buf); state_len = req_buf.len; @@ -441,9 +464,19 @@ static int find_common(struct fetch_negotiator *negotiator, packet_buf_write(&req_buf, "have %s\n", oid_to_hex(oid)); print_verbose(args, "have %s", oid_to_hex(oid)); in_vain++; + haves++; if (flush_at <= ++count) { int ack; + negotiation_round++; + trace2_region_enter_printf("negotiation_v0_v1", "round", + the_repository, "%d", + negotiation_round); + trace2_data_intmax("negotiation_v0_v1", the_repository, + "haves_added", haves); + trace2_data_intmax("negotiation_v0_v1", the_repository, + "in_vain", in_vain); + haves = 0; packet_buf_flush(&req_buf); send_request(args, fd[1], &req_buf); strbuf_setlen(&req_buf, state_len); @@ -465,6 +498,9 @@ static int find_common(struct fetch_negotiator *negotiator, ack, oid_to_hex(result_oid)); switch (ack) { case ACK: + trace2_region_leave_printf("negotiation_v0_v1", "round", + the_repository, "%d", + negotiation_round); flushes = 0; multi_ack = 0; retval = 0; @@ -490,6 +526,7 @@ static int find_common(struct fetch_negotiator *negotiator, const char *hex = oid_to_hex(result_oid); packet_buf_write(&req_buf, "have %s\n", hex); state_len = req_buf.len; + haves++; /* * Reset in_vain because an ack * for this commit has not been @@ -508,6 +545,9 @@ static int find_common(struct fetch_negotiator *negotiator, } } while (ack); flushes--; + trace2_region_leave_printf("negotiation_v0_v1", "round", + the_repository, "%d", + negotiation_round); if (got_continue && MAX_IN_VAIN < in_vain) { print_verbose(args, _("giving up")); break; /* give up */ @@ -518,6 +558,8 @@ static int find_common(struct fetch_negotiator *negotiator, } done: trace2_region_leave("fetch-pack", "negotiation_v0_v1", the_repository); + trace2_data_intmax("negotiation_v0_v1", the_repository, "total_rounds", + negotiation_round); if (!got_ready || !no_done) { packet_buf_write(&req_buf, "done\n"); send_request(args, fd[1], &req_buf); @@ -560,8 +602,10 @@ static int mark_complete(const struct object_id *oid) return 0; } -static int mark_complete_oid(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int mark_complete_oid(const char *refname UNUSED, + const struct object_id *oid, + int flag UNUSED, + void *cb_data UNUSED) { return mark_complete(oid); } @@ -799,7 +843,7 @@ static int everything_local(struct fetch_pack_args *args, return retval; } -static int sideband_demux(int in, int out, void *data) +static int sideband_demux(int in UNUSED, int out, void *data) { int *xd = data; int ret; @@ -1025,6 +1069,13 @@ static int get_pack(struct fetch_pack_args *args, return 0; } +static int ref_compare_name(const struct ref *a, const struct ref *b) +{ + return strcmp(a->name, b->name); +} + +DEFINE_LIST_SORT(static, sort_ref_list, struct ref, next); + static int cmp_ref_by_name(const void *a_, const void *b_) { const struct ref *a = *((const struct ref **)a_); @@ -1323,15 +1374,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, die(_("Server does not support shallow requests")); /* Add filter */ - if (server_supports_feature("fetch", "filter", 0) && - args->filter_options.choice) { - const char *spec = - expand_list_objects_filter_spec(&args->filter_options); - print_verbose(args, _("Server supports filter")); - packet_buf_write(&req_buf, "filter %s", spec); - } else if (args->filter_options.choice) { - warning("filtering not recognized by server, ignoring"); - } + send_filter(args, &req_buf, + server_supports_feature("fetch", "filter", 0)); if (server_supports_feature("fetch", "packfile-uris", 0)) { int i; @@ -1361,6 +1405,8 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, haves_added = add_haves(negotiator, &req_buf, haves_to_send); *in_vain += haves_added; + trace2_data_intmax("negotiation_v2", the_repository, "haves_added", haves_added); + trace2_data_intmax("negotiation_v2", the_repository, "in_vain", *in_vain); if (!haves_added || (seen_ack && *in_vain >= MAX_IN_VAIN)) { /* Send Done */ packet_buf_write(&req_buf, "done\n"); @@ -1603,6 +1649,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, struct oidset common = OIDSET_INIT; struct packet_reader reader; int in_vain = 0, negotiation_started = 0; + int negotiation_round = 0; int haves_to_send = INITIAL_FLUSH; struct fetch_negotiator negotiator_alloc; struct fetch_negotiator *negotiator; @@ -1659,12 +1706,20 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, "negotiation_v2", the_repository); } + negotiation_round++; + trace2_region_enter_printf("negotiation_v2", "round", + the_repository, "%d", + negotiation_round); if (send_fetch_request(negotiator, fd[1], args, ref, &common, &haves_to_send, &in_vain, reader.use_sideband, - seen_ack)) + seen_ack)) { + trace2_region_leave_printf("negotiation_v2", "round", + the_repository, "%d", + negotiation_round); state = FETCH_GET_PACK; + } else state = FETCH_PROCESS_ACKS; break; @@ -1677,6 +1732,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, seen_ack = 1; oidset_insert(&common, &common_oid); } + trace2_region_leave_printf("negotiation_v2", "round", + the_repository, "%d", + negotiation_round); if (received_ready) { /* * Don't check for response delimiter; get_pack() will @@ -1692,6 +1750,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, trace2_region_leave("fetch-pack", "negotiation_v2", the_repository); + trace2_data_intmax("negotiation_v2", the_repository, + "total_rounds", negotiation_round); /* Check for shallow-info section */ if (process_section_header(&reader, "shallow-info", 1)) receive_shallow_info(args, &reader, shallows, si); @@ -2071,6 +2131,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, int in_vain = 0; int seen_ack = 0; int last_iteration = 0; + int negotiation_round = 0; timestamp_t min_generation = GENERATION_NUMBER_INFINITY; fetch_negotiator_init(the_repository, &negotiator); @@ -2084,11 +2145,17 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, add_to_object_array, &nt_object_array); + trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository); while (!last_iteration) { int haves_added; struct object_id common_oid; int received_ready = 0; + negotiation_round++; + + trace2_region_enter_printf("negotiate_using_fetch", "round", + the_repository, "%d", + negotiation_round); strbuf_reset(&req_buf); write_fetch_command_and_capabilities(&req_buf, server_options); @@ -2099,6 +2166,11 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, if (!haves_added || (seen_ack && in_vain >= MAX_IN_VAIN)) last_iteration = 1; + trace2_data_intmax("negotiate_using_fetch", the_repository, + "haves_added", haves_added); + trace2_data_intmax("negotiate_using_fetch", the_repository, + "in_vain", in_vain); + /* Send request */ packet_buf_flush(&req_buf); if (write_in_full(fd[1], req_buf.buf, req_buf.len) < 0) @@ -2131,7 +2203,13 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips, REACH_SCRATCH, 0, min_generation)) last_iteration = 1; + trace2_region_leave_printf("negotiation", "round", + the_repository, "%d", + negotiation_round); } + trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository); + trace2_data_intmax("negotiate_using_fetch", the_repository, + "total_rounds", negotiation_round); clear_common_flag(acked_commits); strbuf_release(&req_buf); } diff --git a/fuzz-commit-graph.c b/fuzz-commit-graph.c deleted file mode 100644 index e7cf6d5b0f..0000000000 --- a/fuzz-commit-graph.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "commit-graph.h" -#include "repository.h" - -struct commit_graph *parse_commit_graph(struct repository *r, - void *graph_map, size_t graph_size); - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - struct commit_graph *g; - - initialize_the_repository(); - g = parse_commit_graph(the_repository, (void *)data, size); - repo_clear(the_repository); - free_commit_graph(g); - - return 0; -} diff --git a/gettext.c b/gettext.c index bb5ba1fe7c..f139008d0a 100644 --- a/gettext.c +++ b/gettext.c @@ -10,7 +10,6 @@ #include "config.h" #ifndef NO_GETTEXT -# include # include # ifdef GIT_WINDOWS_NATIVE @@ -80,7 +79,6 @@ static int test_vsnprintf(const char *fmt, ...) static void init_gettext_charset(const char *domain) { - setlocale(LC_CTYPE, ""); charset = locale_charset(); bind_textdomain_codeset(domain, charset); diff --git a/git-compat-util.h b/git-compat-util.h index 6aee4d92e7..bac663353d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -189,6 +189,13 @@ struct strbuf; #define _NETBSD_SOURCE 1 #define _SGI_SOURCE 1 +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) \ + __attribute__((deprecated ("parameter declared as UNUSED"))) +#else +#define UNUSED +#endif + #if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */ # if !defined(_WIN32_WINNT) # define _WIN32_WINNT 0x0600 @@ -215,6 +222,7 @@ struct strbuf; #endif #include #include +#include #ifdef NEEDS_SYS_PARAM_H #include #endif @@ -261,6 +269,7 @@ static inline int is_xplatform_dir_sep(int c) #include #include #include +#include #include #ifndef NO_SYS_SELECT_H #include @@ -397,7 +406,9 @@ typedef uintmax_t timestamp_t; #endif #ifndef platform_core_config -static inline int noop_core_config(const char *var, const char *value, void *cb) +static inline int noop_core_config(const char *var UNUSED, + const char *value UNUSED, + void *cb UNUSED) { return 0; } @@ -490,7 +501,8 @@ static inline void extract_id_from_env(const char *env, uid_t *id) } } -static inline int is_path_owned_by_current_uid(const char *path, struct strbuf *report) +static inline int is_path_owned_by_current_uid(const char *path, + struct strbuf *report UNUSED) { struct stat st; uid_t euid; @@ -568,8 +580,11 @@ static inline int git_has_dir_sep(const char *path) /* The sentinel attribute is valid from gcc version 4.0 */ #if defined(__GNUC__) && (__GNUC__ >= 4) #define LAST_ARG_MUST_BE_NULL __attribute__((sentinel)) +/* warn_unused_result exists as of gcc 3.4.0, but be lazy and check 4.0 */ +#define RESULT_MUST_BE_USED __attribute__ ((warn_unused_result)) #else #define LAST_ARG_MUST_BE_NULL +#define RESULT_MUST_BE_USED #endif #define MAYBE_UNUSED __attribute__((__unused__)) diff --git a/git-instaweb.sh b/git-instaweb.sh index 4349566c89..c68f49454c 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -102,7 +102,7 @@ resolve_full_httpd () { start_httpd () { if test -f "$fqgitdir/pid"; then - say "Instance already running. Restarting..." + echo "Instance already running. Restarting..." stop_httpd fi diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh index 343fe7bccd..77e93121bf 100755 --- a/git-merge-resolve.sh +++ b/git-merge-resolve.sh @@ -5,6 +5,16 @@ # # Resolve two trees, using enhanced multi-base read-tree. +. git-sh-setup + +# Abort if index does not match HEAD +if ! git diff-index --quiet --cached HEAD -- +then + gettextln "Error: Your local changes to the following files would be overwritten by merge" + git diff-index --cached --name-only HEAD -- | sed -e 's/^/ /' + exit 2 +fi + # The first parameters up to -- are merge bases; the rest are heads. bases= head= remotes= sep_seen= for arg diff --git a/git-p4.py b/git-p4.py index 77dc19daa6..d26a980e5a 100755 --- a/git-p4.py +++ b/git-p4.py @@ -2259,7 +2259,7 @@ class P4Submit(Command, P4UserMap): raw=True): if regexp.search(line): if verbose: - print("got keyword match on %s in %s in %s" % (regex.pattern, line, file)) + print("got keyword match on %s in %s in %s" % (regexp.pattern, line, file)) kwfiles[file] = regexp break @@ -4402,19 +4402,16 @@ class P4Unshelve(Command): def renameBranch(self, branch_name): """Rename the existing branch to branch_name.N .""" - found = True for i in range(0, 1000): backup_branch_name = "{0}.{1}".format(branch_name, i) if not gitBranchExists(backup_branch_name): # Copy ref to backup gitUpdateRef(backup_branch_name, branch_name) gitDeleteRef(branch_name) - found = True print("renamed old unshelve branch to {0}".format(backup_branch_name)) break - - if not found: - sys.exit("gave up trying to rename existing branch {0}".format(sync.branch)) + else: + sys.exit("gave up trying to rename existing branch {0}".format(branch_name)) def findLastP4Revision(self, starting_point): """Look back from starting_point for the first commit created by git-p4 diff --git a/git-sh-setup.sh b/git-sh-setup.sh index d92df37e99..ce273fe0e4 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -57,15 +57,6 @@ die_with_status () { exit "$status" } -GIT_QUIET= - -say () { - if test -z "$GIT_QUIET" - then - printf '%s\n' "$*" - fi -} - if test -n "$OPTIONS_SPEC"; then usage() { "$0" -h @@ -285,13 +276,6 @@ get_author_ident_from_commit () { parse_ident_from_commit author AUTHOR } -# Clear repo-local GIT_* environment variables. Useful when switching to -# another repository (e.g. when entering a submodule). See also the env -# list in git_connect() -clear_local_git_env() { - unset $(git rev-parse --local-env-vars) -} - # Generate a virtual base file for a two-file merge. Uses git apply to # remove lines from $1 that are not in $2, leaving only common lines. create_virtual_base() { diff --git a/git-submodule.sh b/git-submodule.sh index fd0b4a2c94..5e5d21c010 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -30,6 +30,7 @@ GIT_PROTOCOL_FROM_USER=0 export GIT_PROTOCOL_FROM_USER command= +quiet= branch= force= reference= @@ -40,8 +41,9 @@ require_init= files= remote= nofetch= -update= -prefix= +rebase= +merge= +checkout= custom_name= depth= progress= @@ -56,17 +58,6 @@ isnumber() n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" } -# Sanitize the local git environment for use within a submodule. We -# can't simply use clear_local_git_env since we want to preserve some -# of the settings from GIT_CONFIG_PARAMETERS. -sanitize_submodule_env() -{ - save_config=$GIT_CONFIG_PARAMETERS - clear_local_git_env - GIT_CONFIG_PARAMETERS=$save_config - export GIT_CONFIG_PARAMETERS -} - # # Add a new submodule to the working tree, .gitmodules and the index # @@ -90,7 +81,7 @@ cmd_add() force=$1 ;; -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --progress) progress=1 @@ -138,7 +129,7 @@ cmd_add() usage fi - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${GIT_QUIET:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper add ${quiet:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@" } # @@ -154,7 +145,7 @@ cmd_foreach() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --recursive) recursive=1 @@ -169,7 +160,7 @@ cmd_foreach() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach ${quiet:+--quiet} ${recursive:+--recursive} -- "$@" } # @@ -184,7 +175,7 @@ cmd_init() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --) shift @@ -200,7 +191,7 @@ cmd_init() shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper init ${GIT_QUIET:+--quiet} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${quiet:+--quiet} -- "$@" } # @@ -217,7 +208,7 @@ cmd_deinit() force=$1 ;; -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --all) deinit_all=t @@ -236,7 +227,7 @@ cmd_deinit() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${quiet:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" } # @@ -251,10 +242,7 @@ cmd_update() do case "$1" in -q|--quiet) - GIT_QUIET=1 - ;; - -v) - unset GIT_QUIET + quiet=1 ;; --progress) progress=1 @@ -263,7 +251,6 @@ cmd_update() init=1 ;; --require-init) - init=1 require_init=1 ;; --remote) @@ -276,7 +263,7 @@ cmd_update() force=$1 ;; -r|--rebase) - update="rebase" + rebase=1 ;; --reference) case "$2" in '') usage ;; esac @@ -290,13 +277,13 @@ cmd_update() dissociate=1 ;; -m|--merge) - update="merge" + merge=1 ;; --recursive) recursive=1 ;; --checkout) - update="checkout" + checkout=1 ;; --recommend-shallow) recommend_shallow="--recommend-shallow" @@ -349,7 +336,7 @@ cmd_update() done git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update \ - ${GIT_QUIET:+--quiet} \ + ${quiet:+--quiet} \ ${force:+--force} \ ${progress:+"--progress"} \ ${remote:+--remote} \ @@ -357,8 +344,9 @@ cmd_update() ${init:+--init} \ ${nofetch:+--no-fetch} \ ${wt_prefix:+--prefix "$wt_prefix"} \ - ${prefix:+--recursive-prefix "$prefix"} \ - ${update:+--update "$update"} \ + ${rebase:+--rebase} \ + ${merge:+--merge} \ + ${checkout:+--checkout} \ ${reference:+"$reference"} \ ${dissociate:+"--dissociate"} \ ${depth:+"$depth"} \ @@ -409,7 +397,7 @@ cmd_set_branch() { shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-branch ${GIT_QUIET:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-branch ${quiet:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" } # @@ -422,7 +410,7 @@ cmd_set_url() { do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --) shift @@ -438,7 +426,7 @@ cmd_set_url() { shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-url ${quiet:+--quiet} -- "$@" } # @@ -459,7 +447,7 @@ cmd_summary() { do case "$1" in --cached) - cached="$1" + cached=1 ;; --files) files="$1" @@ -509,7 +497,7 @@ cmd_status() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --cached) cached=1 @@ -531,7 +519,7 @@ cmd_status() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper status ${GIT_QUIET:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper status ${quiet:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" } # # Sync remote urls for submodules @@ -544,7 +532,7 @@ cmd_sync() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 shift ;; --recursive) @@ -564,12 +552,12 @@ cmd_sync() esac done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper sync ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper sync ${quiet:+--quiet} ${recursive:+--recursive} -- "$@" } cmd_absorbgitdirs() { - git submodule--helper absorb-git-dirs --prefix "$wt_prefix" "$@" + git submodule--helper absorbgitdirs --prefix "$wt_prefix" "$@" } # This loop parses the command line arguments to find the @@ -585,18 +573,10 @@ do command=$1 ;; -q|--quiet) - GIT_QUIET=1 - ;; - -b|--branch) - case "$2" in - '') - usage - ;; - esac - branch="$2"; shift + quiet=1 ;; --cached) - cached="$1" + cached=1 ;; --) break @@ -622,12 +602,6 @@ then fi fi -# "-b branch" is accepted only by "add" and "set-branch" -if test -n "$branch" && (test "$command" != add || test "$command" != set-branch) -then - usage -fi - # "--cached" is accepted only by "status" and "summary" if test -n "$cached" && test "$command" != status && test "$command" != summary then diff --git a/git.c b/git.c index d7a7a82008..da411c5382 100644 --- a/git.c +++ b/git.c @@ -489,14 +489,14 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) static struct cmd_struct commands[] = { { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "am", cmd_am, RUN_SETUP | NEED_WORK_TREE }, - { "annotate", cmd_annotate, RUN_SETUP | NO_PARSEOPT }, + { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply, RUN_SETUP_GENTLY }, { "archive", cmd_archive, RUN_SETUP_GENTLY }, { "bisect--helper", cmd_bisect__helper, RUN_SETUP }, { "blame", cmd_blame, RUN_SETUP }, { "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG }, { "bugreport", cmd_bugreport, RUN_SETUP_GENTLY }, - { "bundle", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT }, + { "bundle", cmd_bundle, RUN_SETUP_GENTLY }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "check-attr", cmd_check_attr, RUN_SETUP }, { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE }, @@ -514,7 +514,7 @@ static struct cmd_struct commands[] = { { "column", cmd_column, RUN_SETUP_GENTLY }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-graph", cmd_commit_graph, RUN_SETUP }, - { "commit-tree", cmd_commit_tree, RUN_SETUP | NO_PARSEOPT }, + { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config, RUN_SETUP_GENTLY | DELAY_PAGER_CONFIG }, { "count-objects", cmd_count_objects, RUN_SETUP }, { "credential", cmd_credential, RUN_SETUP_GENTLY | NO_PARSEOPT }, @@ -522,6 +522,7 @@ static struct cmd_struct commands[] = { { "credential-cache--daemon", cmd_credential_cache_daemon }, { "credential-store", cmd_credential_store }, { "describe", cmd_describe, RUN_SETUP }, + { "diagnose", cmd_diagnose, RUN_SETUP_GENTLY }, { "diff", cmd_diff, NO_PARSEOPT }, { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "diff-index", cmd_diff_index, RUN_SETUP | NO_PARSEOPT }, @@ -553,9 +554,9 @@ static struct cmd_struct commands[] = { { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, - { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT }, + { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY }, { "mailsplit", cmd_mailsplit, NO_PARSEOPT }, - { "maintenance", cmd_maintenance, RUN_SETUP | NO_PARSEOPT }, + { "maintenance", cmd_maintenance, RUN_SETUP }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY }, @@ -565,8 +566,8 @@ static struct cmd_struct commands[] = { { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, - { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT }, - { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT }, + { "merge-tree", cmd_merge_tree, RUN_SETUP }, + { "mktag", cmd_mktag, RUN_SETUP }, { "mktree", cmd_mktree, RUN_SETUP }, { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, @@ -627,7 +628,7 @@ static struct cmd_struct commands[] = { { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP }, - { "worktree", cmd_worktree, RUN_SETUP | NO_PARSEOPT }, + { "worktree", cmd_worktree, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, }; diff --git a/gitweb/Makefile b/gitweb/Makefile index f13e23c4de..3b68ab2d67 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -1,8 +1,8 @@ -# The default target of this Makefile is... -all:: +ifndef MAK_DIR_GITWEB +$(error do not run gitweb/Makefile stand-alone anymore. The "gitweb" and \ +"install-gitweb" targets now live in the top-level Makefile) +endif -# Define V=1 to have a more verbose compile. -# # Define JSMIN to point to JavaScript minifier that functions as # a filter to have static/gitweb.js minified. # @@ -10,13 +10,6 @@ all:: # version of static/gitweb.css # -prefix ?= $(HOME) -bindir ?= $(prefix)/bin -gitwebdir ?= /var/www/cgi-bin - -RM ?= rm -f -INSTALL ?= install - # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf @@ -30,89 +23,45 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html -GITWEB_CSS = static/gitweb.css +GITWEB_CSS_IN = static/gitweb.css +GITWEB_CSS = $(GITWEB_CSS_IN) GITWEB_LOGO = static/git-logo.png GITWEB_FAVICON = static/git-favicon.png -GITWEB_JS = static/gitweb.js +GITWEB_JS_IN = static/gitweb.js +GITWEB_JS = $(GITWEB_JS_IN) GITWEB_SITE_HTML_HEAD_STRING = GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = HIGHLIGHT_BIN = highlight -# include user config --include ../config.mak.autogen --include ../config.mak --include config.mak - -# determine version -../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE - $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE - -ifneq ($(MAKECMDGOALS),clean) --include ../GIT-VERSION-FILE -endif - -### Build rules - -SHELL_PATH ?= $(SHELL) -PERL_PATH ?= /usr/bin/perl +# What targets we'll add to 'all' for "make gitweb" +GITWEB_ALL = +GITWEB_ALL += gitweb.cgi +GITWEB_ALL += $(GITWEB_JS) -# Shell quote; -bindir_SQ = $(subst ','\'',$(bindir))#' -gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' -gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#' -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' -DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' - -# Quiet generation (unless V=1) -QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir -QUIET_SUBDIR1 = - -ifneq ($(findstring $(MAKEFLAGS),w),w) -PRINT_DIR = --no-print-directory -else # "make -w" -NO_SUBDIR = : -endif - -ifneq ($(findstring $(MAKEFLAGS),s),s) -ifndef V - QUIET = @ - QUIET_GEN = $(QUIET)echo ' ' GEN $@; - QUIET_SUBDIR0 = +@subdir= - QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ - $(MAKE) $(PRINT_DIR) -C $$subdir - export V - export QUIET - export QUIET_GEN - export QUIET_SUBDIR0 - export QUIET_SUBDIR1 -endif -endif - -all:: gitweb.cgi static/gitweb.js +MAK_DIR_GITWEB_ALL = $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_ALL)) GITWEB_PROGRAMS = gitweb.cgi +GITWEB_JS_MIN = static/gitweb.min.js ifdef JSMIN -GITWEB_FILES += static/gitweb.min.js -GITWEB_JS = static/gitweb.min.js -all:: static/gitweb.min.js -static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS +GITWEB_JS = $(GITWEB_JS_MIN) +GITWEB_ALL += $(MAK_DIR_GITWEB)$(GITWEB_JS_MIN) +$(MAK_DIR_GITWEB)$(GITWEB_JS_MIN): $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)$(GITWEB_JS_MIN): $(MAK_DIR_GITWEB)$(GITWEB_JS_IN) $(QUIET_GEN)$(JSMIN) <$< >$@ -else -GITWEB_FILES += static/gitweb.js endif +GITWEB_FILES += $(GITWEB_JS) +GITWEB_CSS_MIN = static/gitweb.min.css ifdef CSSMIN -GITWEB_FILES += static/gitweb.min.css -GITWEB_CSS = static/gitweb.min.css -all:: static/gitweb.min.css -static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS +GITWEB_CSS = $(GITWEB_CSS_MIN) +GITWEB_ALL += $(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN) +$(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN): $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN): $(MAK_DIR_GITWEB)$(GITWEB_CSS_IN) $(QUIET_GEN)$(CSSMIN) <$< >$@ -else -GITWEB_FILES += static/gitweb.css endif +GITWEB_FILES += $(GITWEB_CSS) GITWEB_FILES += static/git-logo.png static/git-favicon.png @@ -120,6 +69,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # # js/lib/common-lib.js should be always first, then js/lib/*.js, # then the rest of files; js/gitweb.js should be last (if it exists) +GITWEB_JSLIB_FILES = GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/lib/cookies.js @@ -152,46 +102,45 @@ GITWEB_REPLACE = \ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g' -GITWEB-BUILD-OPTIONS: FORCE +.PHONY: FORCE +$(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS: FORCE @rm -f $@+ @echo "x" '$(PERL_PATH_SQ)' $(GITWEB_REPLACE) "$(JSMIN)|$(CSSMIN)" >$@+ @cmp -s $@+ $@ && rm -f $@+ || mv -f $@+ $@ -gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)gitweb.cgi: $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)gitweb.cgi: $(MAK_DIR_GITWEB)gitweb.perl $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ $(GITWEB_REPLACE) $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -static/gitweb.js: $(GITWEB_JSLIB_FILES) +$(MAK_DIR_GITWEB)static/gitweb.js: $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_JSLIB_FILES)) $(QUIET_GEN)$(RM) $@ $@+ && \ cat $^ >$@+ && \ mv $@+ $@ -### Testing rules - -test: - $(MAKE) -C ../t gitweb-test - -test-installed: - GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \ - $(MAKE) -C ../t gitweb-test - ### Installation rules -install: all +.PHONY: install-gitweb +install-gitweb: $(MAK_DIR_GITWEB_ALL) $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' - $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 755 $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_PROGRAMS)) '$(DESTDIR_SQ)$(gitwebdir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' - $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' + $(INSTALL) -m 644 $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_FILES)) \ + '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' +ifndef NO_GITWEB +ifndef NO_PERL +install: install-gitweb +endif +endif ### Cleaning rules -clean: - $(RM) gitweb.cgi static/gitweb.js \ - static/gitweb.min.js static/gitweb.min.css \ - GITWEB-BUILD-OPTIONS - -.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE - +.PHONY: gitweb-clean +gitweb-clean: + $(RM) $(addprefix $(MAK_DIR_GITWEB),gitweb.cgi $(GITWEB_JS_IN) \ + $(GITWEB_JS_MIN) $(GITWEB_CSS_MIN) \ + GITWEB-BUILD-OPTIONS) +clean: gitweb-clean diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1835487ab2..e66eb3d9ba 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3560,23 +3560,6 @@ sub parse_commit_text { $title =~ s/^ //; if ($title ne "") { $co{'title'} = chop_str($title, 80, 5); - # remove leading stuff of merges to make the interesting part visible - if (length($title) > 50) { - $title =~ s/^Automatic //; - $title =~ s/^merge (of|with) /Merge ... /i; - if (length($title) > 50) { - $title =~ s/(http|rsync):\/\///; - } - if (length($title) > 50) { - $title =~ s/(master|www|rsync)\.//; - } - if (length($title) > 50) { - $title =~ s/kernel.org:?//; - } - if (length($title) > 50) { - $title =~ s/\/pub\/scm//; - } - } $co{'title_short'} = chop_str($title, 50, 5); last; } diff --git a/gpg-interface.c b/gpg-interface.c index 947b58ad4d..9aa714bdee 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -165,15 +165,17 @@ static struct { { 0, "TRUST_", GPG_STATUS_TRUST_LEVEL }, }; -static struct { +/* Keep the order same as enum signature_trust_level */ +static struct sigcheck_gpg_trust_level { const char *key; + const char *display_key; enum signature_trust_level value; } sigcheck_gpg_trust_level[] = { - { "UNDEFINED", TRUST_UNDEFINED }, - { "NEVER", TRUST_NEVER }, - { "MARGINAL", TRUST_MARGINAL }, - { "FULLY", TRUST_FULLY }, - { "ULTIMATE", TRUST_ULTIMATE }, + { "UNDEFINED", "undefined", TRUST_UNDEFINED }, + { "NEVER", "never", TRUST_NEVER }, + { "MARGINAL", "marginal", TRUST_MARGINAL }, + { "FULLY", "fully", TRUST_FULLY }, + { "ULTIMATE", "ultimate", TRUST_ULTIMATE }, }; static void replace_cstring(char **field, const char *line, const char *next) @@ -697,7 +699,7 @@ void set_signing_key(const char *key) configured_signing_key = xstrdup(key); } -int git_gpg_config(const char *var, const char *value, void *cb) +int git_gpg_config(const char *var, const char *value, void *cb UNUSED) { struct gpg_format *fmt = NULL; char *fmtname = NULL; @@ -905,6 +907,20 @@ const char *get_signing_key(void) return git_committer_info(IDENT_STRICT | IDENT_NO_DATE); } +const char *gpg_trust_level_to_str(enum signature_trust_level level) +{ + struct sigcheck_gpg_trust_level *trust; + + if (level < 0 || level >= ARRAY_SIZE(sigcheck_gpg_trust_level)) + BUG("invalid trust level requested %d", level); + + trust = &sigcheck_gpg_trust_level[level]; + if (trust->value != level) + BUG("sigcheck_gpg_trust_level[] unsorted"); + + return sigcheck_gpg_trust_level[level].display_key; +} + int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { return use_format->sign_buffer(buffer, signature, signing_key); diff --git a/gpg-interface.h b/gpg-interface.h index b30cbdcd3d..8a9ef41779 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -71,6 +71,14 @@ size_t parse_signed_buffer(const char *buf, size_t size); int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); + +/* + * Returns corresponding string in lowercase for a given member of + * enum signature_trust_level. For example, `TRUST_ULTIMATE` will + * return "ultimate". + */ +const char *gpg_trust_level_to_str(enum signature_trust_level level); + int git_gpg_config(const char *, const char *, void *); void set_signing_key(const char *); const char *get_signing_key(void); diff --git a/grep.c b/grep.c index 82eb7da102..52a894c989 100644 --- a/grep.c +++ b/grep.c @@ -1615,7 +1615,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle return 0; goto next_line; } - if (hit) { + if (hit && (opt->max_count < 0 || count < opt->max_count)) { count++; if (opt->status_only) return 1; diff --git a/grep.h b/grep.h index c722d25ed9..bdcadce61b 100644 --- a/grep.h +++ b/grep.h @@ -171,6 +171,7 @@ struct grep_opt { int show_hunk_mark; int file_break; int heading; + int max_count; void *priv; void (*output)(struct grep_opt *opt, const void *data, size_t size); @@ -181,6 +182,7 @@ struct grep_opt { .relative = 1, \ .pathname = 1, \ .max_depth = -1, \ + .max_count = -1, \ .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \ .colors = { \ [GREP_COLOR_CONTEXT] = "", \ diff --git a/hash.h b/hash.h index 5d40368f18..36b64165fc 100644 --- a/hash.h +++ b/hash.h @@ -4,9 +4,7 @@ #include "git-compat-util.h" #include "repository.h" -#if defined(SHA1_PPC) -#include "ppc/sha1.h" -#elif defined(SHA1_APPLE) +#if defined(SHA1_APPLE) #include #elif defined(SHA1_OPENSSL) #include @@ -16,7 +14,9 @@ #include "block-sha1/sha1.h" #endif -#if defined(SHA256_GCRYPT) +#if defined(SHA256_NETTLE) +#include "sha256/nettle.h" +#elif defined(SHA256_GCRYPT) #define SHA256_NEEDS_CLONE_HELPER #include "sha256/gcrypt.h" #elif defined(SHA256_OPENSSL) @@ -30,7 +30,7 @@ * platform's underlying implementation of SHA-1; could be OpenSSL, * blk_SHA, Apple CommonCrypto, etc... Note that the relevant * SHA-1 header may have already defined platform_SHA_CTX for our - * own implementations like block-sha1 and ppc-sha1, so we list + * own implementations like block-sha1, so we list * the default for OpenSSL compatible SHA-1 implementations here. */ #define platform_SHA_CTX SHA_CTX diff --git a/hashmap.c b/hashmap.c index 134d2eec80..cf5fea87eb 100644 --- a/hashmap.c +++ b/hashmap.c @@ -142,10 +142,10 @@ static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map, return e; } -static int always_equal(const void *unused_cmp_data, - const struct hashmap_entry *unused1, - const struct hashmap_entry *unused2, - const void *unused_keydata) +static int always_equal(const void *cmp_data UNUSED, + const struct hashmap_entry *entry1 UNUSED, + const struct hashmap_entry *entry2 UNUSED, + const void *keydata UNUSED) { return 0; } @@ -313,7 +313,7 @@ struct pool_entry { unsigned char data[FLEX_ARRAY]; }; -static int pool_entry_cmp(const void *unused_cmp_data, +static int pool_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) diff --git a/help.c b/help.c index 41c41c2aa1..d04542d826 100644 --- a/help.c +++ b/help.c @@ -38,19 +38,30 @@ static struct category_description main_categories[] = { { CAT_plumbinginterrogators, N_("Low-level Commands / Interrogators") }, { CAT_synchingrepositories, N_("Low-level Commands / Syncing Repositories") }, { CAT_purehelpers, N_("Low-level Commands / Internal Helpers") }, + { CAT_userinterfaces, N_("User-facing repository, command and file interfaces") }, + { CAT_developerinterfaces, N_("Developer-facing file formats, protocols and other interfaces") }, { 0, NULL } }; static const char *drop_prefix(const char *name, uint32_t category) { const char *new_name; - - if (skip_prefix(name, "git-", &new_name)) - return new_name; - if (category == CAT_guide && skip_prefix(name, "git", &new_name)) + const char *prefix; + + switch (category) { + case CAT_guide: + case CAT_userinterfaces: + case CAT_developerinterfaces: + prefix = "git"; + break; + default: + prefix = "git-"; + break; + } + if (skip_prefix(name, prefix, &new_name)) return new_name; - return name; + return name; } static void extract_cmds(struct cmdname_help **p_cmds, uint32_t mask) @@ -426,6 +437,26 @@ void list_guides_help(void) putchar('\n'); } +void list_user_interfaces_help(void) +{ + struct category_description catdesc[] = { + { CAT_userinterfaces, N_("User-facing repository, command and file interfaces:") }, + { 0, NULL } + }; + print_cmd_by_category(catdesc, NULL); + putchar('\n'); +} + +void list_developer_interfaces_help(void) +{ + struct category_description catdesc[] = { + { CAT_developerinterfaces, N_("File formats, protocols and other developer interfaces:") }, + { 0, NULL } + }; + print_cmd_by_category(catdesc, NULL); + putchar('\n'); +} + static int get_alias(const char *var, const char *value, void *data) { struct string_list *list = data; @@ -750,8 +781,9 @@ struct similar_ref_cb { struct string_list *similar_refs; }; -static int append_similar_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) +static int append_similar_ref(const char *refname, + const struct object_id *oid UNUSED, + int flags UNUSED, void *cb_data) { struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data); char *branch = strrchr(refname, '/') + 1; diff --git a/help.h b/help.h index 971a3ad855..af073a7a02 100644 --- a/help.h +++ b/help.h @@ -22,6 +22,8 @@ static inline void mput_char(char c, unsigned int num) void list_common_cmds_help(void); void list_all_cmds_help(int show_external_commands, int show_aliases); void list_guides_help(void); +void list_user_interfaces_help(void); +void list_developer_interfaces_help(void); void list_all_main_cmds(struct string_list *list); void list_all_other_cmds(struct string_list *list); diff --git a/http-backend.c b/http-backend.c index 58b83a9f66..6eb3b2fe51 100644 --- a/http-backend.c +++ b/http-backend.c @@ -505,7 +505,7 @@ static void run_service(const char **argv, int buffer_input) } static int show_text_ref(const char *name, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, void *cb_data) { const char *name_nons = strip_namespace(name); struct strbuf *buf = cb_data; diff --git a/http.c b/http.c index 168ca30c55..5d0502f51f 100644 --- a/http.c +++ b/http.c @@ -1775,7 +1775,7 @@ static void write_accept_language(struct strbuf *buf) * LANGUAGE= LANG=en_US.UTF-8 -> "Accept-Language: en-US, *; q=0.1" * LANGUAGE= LANG=C -> "" */ -static const char *get_accept_language(void) +const char *http_get_accept_language_header(void) { if (!cached_accept_language) { struct strbuf buf = STRBUF_INIT; @@ -1829,7 +1829,7 @@ static int http_request(const char *url, fwrite_buffer); } - accept_language = get_accept_language(); + accept_language = http_get_accept_language_header(); if (accept_language) headers = curl_slist_append(headers, accept_language); diff --git a/http.h b/http.h index ba303cfb37..3c94c47910 100644 --- a/http.h +++ b/http.h @@ -178,6 +178,9 @@ int http_fetch_ref(const char *base, struct ref *ref); int http_get_info_packs(const char *base_url, struct packed_git **packs_head); +/* Helper for getting Accept-Language header */ +const char *http_get_accept_language_header(void); + struct http_pack_request { char *url; diff --git a/ident.c b/ident.c index 89ca5b4700..6de76f9421 100644 --- a/ident.c +++ b/ident.c @@ -8,6 +8,7 @@ #include "cache.h" #include "config.h" #include "date.h" +#include "mailmap.h" static struct strbuf git_default_name = STRBUF_INIT; static struct strbuf git_default_email = STRBUF_INIT; @@ -346,6 +347,79 @@ person_only: return 0; } +/* + * Returns the difference between the new and old length of the ident line. + */ +static ssize_t rewrite_ident_line(const char *person, size_t len, + struct strbuf *buf, + struct string_list *mailmap) +{ + size_t namelen, maillen; + const char *name; + const char *mail; + struct ident_split ident; + + if (split_ident_line(&ident, person, len)) + return 0; + + mail = ident.mail_begin; + maillen = ident.mail_end - ident.mail_begin; + name = ident.name_begin; + namelen = ident.name_end - ident.name_begin; + + if (map_user(mailmap, &mail, &maillen, &name, &namelen)) { + struct strbuf namemail = STRBUF_INIT; + size_t newlen; + + strbuf_addf(&namemail, "%.*s <%.*s>", + (int)namelen, name, (int)maillen, mail); + + strbuf_splice(buf, ident.name_begin - buf->buf, + ident.mail_end - ident.name_begin + 1, + namemail.buf, namemail.len); + newlen = namemail.len; + + strbuf_release(&namemail); + + return newlen - (ident.mail_end - ident.name_begin); + } + + return 0; +} + +void apply_mailmap_to_header(struct strbuf *buf, const char **header, + struct string_list *mailmap) +{ + size_t buf_offset = 0; + + if (!mailmap) + return; + + for (;;) { + const char *person, *line; + size_t i; + int found_header = 0; + + line = buf->buf + buf_offset; + if (!*line || *line == '\n') + return; /* End of headers */ + + for (i = 0; header[i]; i++) + if (skip_prefix(line, header[i], &person)) { + const char *endp = strchrnul(person, '\n'); + found_header = 1; + buf_offset += endp - line; + buf_offset += rewrite_ident_line(person, endp - person, buf, mailmap); + break; + } + + if (!found_header) { + buf_offset = strchrnul(line, '\n') - buf->buf; + if (buf->buf[buf_offset] == '\n') + buf_offset++; + } + } +} static void ident_env_hint(enum want_ident whose_ident) { @@ -594,7 +668,7 @@ static int set_ident(const char *var, const char *value) return 0; } -int git_ident_config(const char *var, const char *value, void *data) +int git_ident_config(const char *var, const char *value, void *data UNUSED) { if (!strcmp(var, "user.useconfigonly")) { ident_use_config_only = git_config_bool(var, value); diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 4b25287886..5339660238 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -108,7 +108,7 @@ int gently_parse_list_objects_filter( strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg); - memset(filter_options, 0, sizeof(*filter_options)); + list_objects_filter_init(filter_options); return 1; } @@ -143,6 +143,7 @@ static int parse_combine_subfilter( ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, filter_options->sub_alloc); + list_objects_filter_init(&filter_options->sub[new_index]); decoded = url_percent_decode(subspec->buf); @@ -187,10 +188,8 @@ static int parse_combine_filter( cleanup: strbuf_list_free(subspecs); - if (result) { + if (result) list_objects_filter_release(filter_options); - memset(filter_options, 0, sizeof(*filter_options)); - } return result; } @@ -204,10 +203,10 @@ static int allow_unencoded(char ch) static void filter_spec_append_urlencode( struct list_objects_filter_options *filter, const char *raw) { - struct strbuf buf = STRBUF_INIT; - strbuf_addstr_urlencode(&buf, raw, allow_unencoded); - trace_printf("Add to combine filter-spec: %s\n", buf.buf); - string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL)); + size_t orig_len = filter->filter_spec.len; + strbuf_addstr_urlencode(&filter->filter_spec, raw, allow_unencoded); + trace_printf("Add to combine filter-spec: %s\n", + filter->filter_spec.buf + orig_len); } /* @@ -225,13 +224,13 @@ static void transform_to_combine_type( struct list_objects_filter_options *sub_array = xcalloc(initial_sub_alloc, sizeof(*sub_array)); sub_array[0] = *filter_options; - memset(filter_options, 0, sizeof(*filter_options)); + list_objects_filter_init(filter_options); filter_options->sub = sub_array; filter_options->sub_alloc = initial_sub_alloc; } filter_options->sub_nr = 1; filter_options->choice = LOFC_COMBINE; - string_list_append(&filter_options->filter_spec, xstrdup("combine:")); + strbuf_addstr(&filter_options->filter_spec, "combine:"); filter_spec_append_urlencode( filter_options, list_objects_filter_spec(&filter_options->sub[0])); @@ -239,7 +238,7 @@ static void transform_to_combine_type( * We don't need the filter_spec strings for subfilter specs, only the * top level. */ - string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0); + strbuf_release(&filter_options->sub[0].filter_spec); } void list_objects_filter_die_if_populated( @@ -256,26 +255,32 @@ void parse_list_objects_filter( struct strbuf errbuf = STRBUF_INIT; int parse_error; + if (!filter_options->filter_spec.buf) + BUG("filter_options not properly initialized"); + if (!filter_options->choice) { - string_list_append(&filter_options->filter_spec, xstrdup(arg)); + strbuf_addstr(&filter_options->filter_spec, arg); parse_error = gently_parse_list_objects_filter( filter_options, arg, &errbuf); } else { + struct list_objects_filter_options *sub; + /* * Make filter_options an LOFC_COMBINE spec so we can trivially * add subspecs to it. */ transform_to_combine_type(filter_options); - string_list_append(&filter_options->filter_spec, xstrdup("+")); + strbuf_addch(&filter_options->filter_spec, '+'); filter_spec_append_urlencode(filter_options, arg); ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, filter_options->sub_alloc); + sub = &filter_options->sub[filter_options->sub_nr - 1]; - parse_error = gently_parse_list_objects_filter( - &filter_options->sub[filter_options->sub_nr - 1], arg, - &errbuf); + list_objects_filter_init(sub); + parse_error = gently_parse_list_objects_filter(sub, arg, + &errbuf); } if (parse_error) die("%s", errbuf.buf); @@ -299,31 +304,18 @@ int opt_parse_list_objects_filter(const struct option *opt, const char *list_objects_filter_spec(struct list_objects_filter_options *filter) { - if (!filter->filter_spec.nr) + if (!filter->filter_spec.len) BUG("no filter_spec available for this filter"); - if (filter->filter_spec.nr != 1) { - struct strbuf concatted = STRBUF_INIT; - strbuf_add_separated_string_list( - &concatted, "", &filter->filter_spec); - string_list_clear(&filter->filter_spec, /*free_util=*/0); - string_list_append( - &filter->filter_spec, strbuf_detach(&concatted, NULL)); - } - - return filter->filter_spec.items[0].string; + return filter->filter_spec.buf; } const char *expand_list_objects_filter_spec( struct list_objects_filter_options *filter) { if (filter->choice == LOFC_BLOB_LIMIT) { - struct strbuf expanded_spec = STRBUF_INIT; - strbuf_addf(&expanded_spec, "blob:limit=%lu", + strbuf_release(&filter->filter_spec); + strbuf_addf(&filter->filter_spec, "blob:limit=%lu", filter->blob_limit_value); - string_list_clear(&filter->filter_spec, /*free_util=*/0); - string_list_append( - &filter->filter_spec, - strbuf_detach(&expanded_spec, NULL)); } return list_objects_filter_spec(filter); @@ -336,12 +328,12 @@ void list_objects_filter_release( if (!filter_options) return; - string_list_clear(&filter_options->filter_spec, /*free_util=*/0); + strbuf_release(&filter_options->filter_spec); free(filter_options->sparse_oid_name); for (sub = 0; sub < filter_options->sub_nr; sub++) list_objects_filter_release(&filter_options->sub[sub]); free(filter_options->sub); - memset(filter_options, 0, sizeof(*filter_options)); + list_objects_filter_init(filter_options); } void partial_clone_register( @@ -394,11 +386,11 @@ void partial_clone_get_default_filter_spec( /* * Parse default value, but silently ignore it if it is invalid. */ - if (!promisor) + if (!promisor || !promisor->partial_clone_filter) return; - string_list_append(&filter_options->filter_spec, - promisor->partial_clone_filter); + strbuf_addstr(&filter_options->filter_spec, + promisor->partial_clone_filter); gently_parse_list_objects_filter(filter_options, promisor->partial_clone_filter, &errbuf); @@ -410,16 +402,21 @@ void list_objects_filter_copy( const struct list_objects_filter_options *src) { int i; - struct string_list_item *item; /* Copy everything. We will overwrite the pointers shortly. */ memcpy(dest, src, sizeof(struct list_objects_filter_options)); - string_list_init_dup(&dest->filter_spec); - for_each_string_list_item(item, &src->filter_spec) - string_list_append(&dest->filter_spec, item->string); + strbuf_init(&dest->filter_spec, 0); + strbuf_addbuf(&dest->filter_spec, &src->filter_spec); + dest->sparse_oid_name = xstrdup_or_null(src->sparse_oid_name); ALLOC_ARRAY(dest->sub, dest->sub_alloc); for (i = 0; i < src->sub_nr; i++) list_objects_filter_copy(&dest->sub[i], &src->sub[i]); } + +void list_objects_filter_init(struct list_objects_filter_options *filter_options) +{ + struct list_objects_filter_options blank = LIST_OBJECTS_FILTER_INIT; + memcpy(filter_options, &blank, sizeof(*filter_options)); +} diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h index ffc02d77e7..7eeadab2dd 100644 --- a/list-objects-filter-options.h +++ b/list-objects-filter-options.h @@ -35,7 +35,7 @@ struct list_objects_filter_options { * To get the raw filter spec given by the user, use the result of * list_objects_filter_spec(). */ - struct string_list filter_spec; + struct strbuf filter_spec; /* * 'choice' is determined by parsing the filter-spec. This indicates @@ -69,6 +69,9 @@ struct list_objects_filter_options { */ }; +#define LIST_OBJECTS_FILTER_INIT { .filter_spec = STRBUF_INIT } +void list_objects_filter_init(struct list_objects_filter_options *filter_options); + /* * Parse value of the argument to the "filter" keyword. * On the command line this looks like: diff --git a/ll-merge.c b/ll-merge.c index 14b8362019..8955d7e1f6 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -249,7 +249,8 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; static const char *default_ll_merge; -static int read_merge_config(const char *var, const char *value, void *cb) +static int read_merge_config(const char *var, const char *value, + void *cb UNUSED) { struct ll_merge_driver *fn; const char *key, *name; diff --git a/log-tree.c b/log-tree.c index d0ac0a6327..1dd5fcbf7b 100644 --- a/log-tree.c +++ b/log-tree.c @@ -135,12 +135,15 @@ static int ref_filter_match(const char *refname, } static int add_ref_decoration(const char *refname, const struct object_id *oid, - int flags, void *cb_data) + int flags UNUSED, + void *cb_data) { + int i; struct object *obj; enum object_type objtype; enum decoration_type deco_type = DECORATION_NONE; struct decoration_filter *filter = (struct decoration_filter *)cb_data; + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; if (filter && !ref_filter_match(refname, filter)) return 0; @@ -165,16 +168,21 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, return 0; obj = lookup_object_by_type(the_repository, oid, objtype); - if (starts_with(refname, "refs/heads/")) - deco_type = DECORATION_REF_LOCAL; - else if (starts_with(refname, "refs/remotes/")) - deco_type = DECORATION_REF_REMOTE; - else if (starts_with(refname, "refs/tags/")) - deco_type = DECORATION_REF_TAG; - else if (!strcmp(refname, "refs/stash")) - deco_type = DECORATION_REF_STASH; - else if (!strcmp(refname, "HEAD")) - deco_type = DECORATION_REF_HEAD; + for (i = 0; i < ARRAY_SIZE(ref_namespace); i++) { + struct ref_namespace_info *info = &ref_namespace[i]; + + if (!info->decoration) + continue; + if (info->exact) { + if (!strcmp(refname, info->ref)) { + deco_type = info->decoration; + break; + } + } else if (starts_with(refname, info->ref)) { + deco_type = info->decoration; + break; + } + } add_name_decoration(deco_type, refname, obj); while (obj->type == OBJ_TAG) { @@ -956,8 +964,7 @@ static void cleanup_additional_headers(struct diff_options *o) static int do_remerge_diff(struct rev_info *opt, struct commit_list *parents, - struct object_id *oid, - struct commit *commit) + struct object_id *oid) { struct merge_options o; struct commit_list *bases; @@ -1052,7 +1059,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log "for octopus merges.\n"); return 1; } - return do_remerge_diff(opt, parents, oid, commit); + return do_remerge_diff(opt, parents, oid); } if (opt->combine_merges) return do_diff_combined(opt, commit); diff --git a/ls-refs.c b/ls-refs.c index 98e69373c8..fa0d01b47c 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -136,7 +136,8 @@ static void send_possibly_unborn_head(struct ls_refs_data *data) strbuf_release(&namespaced); } -static int ls_refs_config(const char *var, const char *value, void *data) +static int ls_refs_config(const char *var, const char *value, + void *data UNUSED) { /* * We only serve fetches over v2 for now, so respect only "uploadpack" diff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c index ad04106169..748924a69b 100644 --- a/merge-ort-wrappers.c +++ b/merge-ort-wrappers.c @@ -10,8 +10,8 @@ static int unclean(struct merge_options *opt, struct tree *head) struct strbuf sb = STRBUF_INIT; if (head && repo_index_has_changes(opt->repo, head, &sb)) { - fprintf(stderr, _("Your local changes to the following files would be overwritten by merge:\n %s"), - sb.buf); + error(_("Your local changes to the following files would be overwritten by merge:\n %s"), + sb.buf); strbuf_release(&sb); return -1; } diff --git a/merge-ort.c b/merge-ort.c index ebb9a75425..99dcee2db8 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -349,13 +349,15 @@ struct merge_options_internal { struct mem_pool pool; /* - * output: special messages and conflict notices for various paths + * conflicts: logical conflicts and messages stored by _primary_ path * * This is a map of pathnames (a subset of the keys in "paths" above) - * to strbufs. It gathers various warning/conflict/notice messages - * for later processing. + * to struct string_list, with each item's `util` containing a + * `struct logical_conflict_info`. Note, though, that for each path, + * it only stores the logical conflicts for which that path is the + * primary path; the path might be part of additional conflicts. */ - struct strmap output; + struct strmap conflicts; /* * renames: various data relating to rename detection @@ -385,8 +387,24 @@ struct merge_options_internal { /* call_depth: recursion level counter for merging merge bases */ int call_depth; + + /* field that holds submodule conflict information */ + struct string_list conflicted_submodules; +}; + +struct conflicted_submodule_item { + char *abbrev; + int flag; }; +static void conflicted_submodule_item_free(void *util, const char *str) +{ + struct conflicted_submodule_item *item = util; + + free(item->abbrev); + free(item); +} + struct version_info { struct object_id oid; unsigned short mode; @@ -481,6 +499,100 @@ struct conflict_info { unsigned match_mask:3; }; +enum conflict_and_info_types { + /* "Simple" conflicts and informational messages */ + INFO_AUTO_MERGING = 0, + CONFLICT_CONTENTS, /* text file that failed to merge */ + CONFLICT_BINARY, + CONFLICT_FILE_DIRECTORY, + CONFLICT_DISTINCT_MODES, + CONFLICT_MODIFY_DELETE, + + /* Regular rename */ + CONFLICT_RENAME_RENAME, /* same file renamed differently */ + CONFLICT_RENAME_COLLIDES, /* rename/add or two files renamed to 1 */ + CONFLICT_RENAME_DELETE, + + /* Basic directory rename */ + CONFLICT_DIR_RENAME_SUGGESTED, + INFO_DIR_RENAME_APPLIED, + + /* Special directory rename cases */ + INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, + CONFLICT_DIR_RENAME_FILE_IN_WAY, + CONFLICT_DIR_RENAME_COLLISION, + CONFLICT_DIR_RENAME_SPLIT, + + /* Basic submodule */ + INFO_SUBMODULE_FAST_FORWARDING, + CONFLICT_SUBMODULE_FAILED_TO_MERGE, + + /* Special submodule cases broken out from FAILED_TO_MERGE */ + CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, + CONFLICT_SUBMODULE_NOT_INITIALIZED, + CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, + CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, + CONFLICT_SUBMODULE_NULL_MERGE_BASE, + + /* Keep this entry _last_ in the list */ + NB_CONFLICT_TYPES, +}; + +/* + * Short description of conflict type, relied upon by external tools. + * + * We can add more entries, but DO NOT change any of these strings. Also, + * Order MUST match conflict_info_and_types. + */ +static const char *type_short_descriptions[] = { + /*** "Simple" conflicts and informational messages ***/ + [INFO_AUTO_MERGING] = "Auto-merging", + [CONFLICT_CONTENTS] = "CONFLICT (contents)", + [CONFLICT_BINARY] = "CONFLICT (binary)", + [CONFLICT_FILE_DIRECTORY] = "CONFLICT (file/directory)", + [CONFLICT_DISTINCT_MODES] = "CONFLICT (distinct modes)", + [CONFLICT_MODIFY_DELETE] = "CONFLICT (modify/delete)", + + /*** Regular rename ***/ + [CONFLICT_RENAME_RENAME] = "CONFLICT (rename/rename)", + [CONFLICT_RENAME_COLLIDES] = "CONFLICT (rename involved in collision)", + [CONFLICT_RENAME_DELETE] = "CONFLICT (rename/delete)", + + /*** Basic directory rename ***/ + [CONFLICT_DIR_RENAME_SUGGESTED] = + "CONFLICT (directory rename suggested)", + [INFO_DIR_RENAME_APPLIED] = "Path updated due to directory rename", + + /*** Special directory rename cases ***/ + [INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME] = + "Directory rename skipped since directory was renamed on both sides", + [CONFLICT_DIR_RENAME_FILE_IN_WAY] = + "CONFLICT (file in way of directory rename)", + [CONFLICT_DIR_RENAME_COLLISION] = "CONFLICT(directory rename collision)", + [CONFLICT_DIR_RENAME_SPLIT] = "CONFLICT(directory rename unclear split)", + + /*** Basic submodule ***/ + [INFO_SUBMODULE_FAST_FORWARDING] = "Fast forwarding submodule", + [CONFLICT_SUBMODULE_FAILED_TO_MERGE] = "CONFLICT (submodule)", + + /*** Special submodule cases broken out from FAILED_TO_MERGE ***/ + [CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION] = + "CONFLICT (submodule with possible resolution)", + [CONFLICT_SUBMODULE_NOT_INITIALIZED] = + "CONFLICT (submodule not initialized)", + [CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE] = + "CONFLICT (submodule history not available)", + [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = + "CONFLICT (submodule may have rewinds)", + [CONFLICT_SUBMODULE_NULL_MERGE_BASE] = + "CONFLICT (submodule lacks merge base)" +}; + +struct logical_conflict_info { + enum conflict_and_info_types type; + struct strvec paths; +}; + /*** Function Grouping: various utility functions ***/ /* @@ -567,24 +679,32 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, struct strmap_entry *e; /* Release and free each strbuf found in output */ - strmap_for_each_entry(&opti->output, &iter, e) { - struct strbuf *sb = e->value; - strbuf_release(sb); + strmap_for_each_entry(&opti->conflicts, &iter, e) { + struct string_list *list = e->value; + for (int i = 0; i < list->nr; i++) { + struct logical_conflict_info *info = + list->items[i].util; + strvec_clear(&info->paths); + } /* - * While strictly speaking we don't need to free(sb) - * here because we could pass free_values=1 when - * calling strmap_clear() on opti->output, that would - * require strmap_clear to do another - * strmap_for_each_entry() loop, so we just free it - * while we're iterating anyway. + * While strictly speaking we don't need to + * free(conflicts) here because we could pass + * free_values=1 when calling strmap_clear() on + * opti->conflicts, that would require strmap_clear + * to do another strmap_for_each_entry() loop, so we + * just free it while we're iterating anyway. */ - free(sb); + string_list_clear(list, 1); + free(list); } - strmap_clear(&opti->output, 0); + strmap_clear(&opti->conflicts, 0); } mem_pool_discard(&opti->pool, 0); + string_list_clear_func(&opti->conflicted_submodules, + conflicted_submodule_item_free); + /* Clean out callback_data as well. */ FREE_AND_NULL(renames->callback_data); renames->callback_data_nr = renames->callback_data_alloc = 0; @@ -627,29 +747,57 @@ static void format_commit(struct strbuf *sb, strbuf_addch(sb, '\n'); } -__attribute__((format (printf, 4, 5))) +__attribute__((format (printf, 8, 9))) static void path_msg(struct merge_options *opt, - const char *path, + enum conflict_and_info_types type, int omittable_hint, /* skippable under --remerge-diff */ + const char *primary_path, + const char *other_path_1, /* may be NULL */ + const char *other_path_2, /* may be NULL */ + struct string_list *other_paths, /* may be NULL */ const char *fmt, ...) { va_list ap; - struct strbuf *sb, *dest; + struct string_list *path_conflicts; + struct logical_conflict_info *info; + struct strbuf buf = STRBUF_INIT; + struct strbuf *dest; struct strbuf tmp = STRBUF_INIT; + /* Sanity checks */ + assert(omittable_hint == + !starts_with(type_short_descriptions[type], "CONFLICT") || + type == CONFLICT_DIR_RENAME_SUGGESTED); if (opt->record_conflict_msgs_as_headers && omittable_hint) return; /* Do not record mere hints in headers */ if (opt->priv->call_depth && opt->verbosity < 5) return; /* Ignore messages from inner merges */ - sb = strmap_get(&opt->priv->output, path); - if (!sb) { - sb = xmalloc(sizeof(*sb)); - strbuf_init(sb, 0); - strmap_put(&opt->priv->output, path, sb); + /* Ensure path_conflicts (ptr to array of logical_conflict) allocated */ + path_conflicts = strmap_get(&opt->priv->conflicts, primary_path); + if (!path_conflicts) { + path_conflicts = xmalloc(sizeof(*path_conflicts)); + string_list_init_dup(path_conflicts); + strmap_put(&opt->priv->conflicts, primary_path, path_conflicts); } - dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb); + /* Add a logical_conflict at the end to store info from this call */ + info = xcalloc(1, sizeof(*info)); + info->type = type; + strvec_init(&info->paths); + + /* Handle the list of paths */ + strvec_push(&info->paths, primary_path); + if (other_path_1) + strvec_push(&info->paths, other_path_1); + if (other_path_2) + strvec_push(&info->paths, other_path_2); + if (other_paths) + for (int i = 0; i < other_paths->nr; i++) + strvec_push(&info->paths, other_paths->items[i].string); + + /* Handle message and its format, in normal case */ + dest = (opt->record_conflict_msgs_as_headers ? &tmp : &buf); va_start(ap, fmt); if (opt->priv->call_depth) { @@ -660,32 +808,32 @@ static void path_msg(struct merge_options *opt, strbuf_vaddf(dest, fmt, ap); va_end(ap); + /* Handle specialized formatting of message under --remerge-diff */ if (opt->record_conflict_msgs_as_headers) { int i_sb = 0, i_tmp = 0; /* Start with the specified prefix */ if (opt->msg_header_prefix) - strbuf_addf(sb, "%s ", opt->msg_header_prefix); + strbuf_addf(&buf, "%s ", opt->msg_header_prefix); /* Copy tmp to sb, adding spaces after newlines */ - strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */ + strbuf_grow(&buf, buf.len + 2*tmp.len); /* more than sufficient */ for (; i_tmp < tmp.len; i_tmp++, i_sb++) { /* Copy next character from tmp to sb */ - sb->buf[sb->len + i_sb] = tmp.buf[i_tmp]; + buf.buf[buf.len + i_sb] = tmp.buf[i_tmp]; /* If we copied a newline, add a space */ if (tmp.buf[i_tmp] == '\n') - sb->buf[++i_sb] = ' '; + buf.buf[++i_sb] = ' '; } /* Update length and ensure it's NUL-terminated */ - sb->len += i_sb; - sb->buf[sb->len] = '\0'; + buf.len += i_sb; + buf.buf[buf.len] = '\0'; strbuf_release(&tmp); } - - /* Add final newline character to sb */ - strbuf_addch(sb, '\n'); + string_list_append_nodup(path_conflicts, strbuf_detach(&buf, NULL)) + ->util = info; } static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool, @@ -1614,38 +1762,50 @@ static int merge_submodule(struct merge_options *opt, int i; int search = !opt->priv->call_depth; + int sub_not_initialized = 1; + int sub_flag = CONFLICT_SUBMODULE_FAILED_TO_MERGE; /* store fallback answer in result in case we fail */ oidcpy(result, opt->priv->call_depth ? o : a); /* we can not handle deletion conflicts */ - if (is_null_oid(o)) - return 0; - if (is_null_oid(a)) - return 0; - if (is_null_oid(b)) - return 0; + if (is_null_oid(a) || is_null_oid(b)) + BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); + + if ((sub_not_initialized = repo_submodule_init(&subrepo, + opt->repo, path, null_oid()))) { + path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (not checked out)"), + path); + sub_flag = CONFLICT_SUBMODULE_NOT_INITIALIZED; + goto cleanup; + } - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { - path_msg(opt, path, 0, - _("Failed to merge submodule %s (not checked out)"), - path); - return 0; + if (is_null_oid(o)) { + path_msg(opt, CONFLICT_SUBMODULE_NULL_MERGE_BASE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (no merge base)"), + path); + goto cleanup; } if (!(commit_o = lookup_commit_reference(&subrepo, o)) || !(commit_a = lookup_commit_reference(&subrepo, a)) || !(commit_b = lookup_commit_reference(&subrepo, b))) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s (commits not present)"), path); + sub_flag = CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE; goto cleanup; } /* check whether both changes are forward */ if (!repo_in_merge_bases(&subrepo, commit_o, commit_a) || !repo_in_merge_bases(&subrepo, commit_o, commit_b)) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s " "(commits don't follow merge-base)"), path); @@ -1655,7 +1815,8 @@ static int merge_submodule(struct merge_options *opt, /* Case #1: a is contained in b or vice versa */ if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) { oidcpy(result, b); - path_msg(opt, path, 1, + path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, + path, NULL, NULL, NULL, _("Note: Fast-forwarding submodule %s to %s"), path, oid_to_hex(b)); ret = 1; @@ -1663,7 +1824,8 @@ static int merge_submodule(struct merge_options *opt, } if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) { oidcpy(result, a); - path_msg(opt, path, 1, + path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, + path, NULL, NULL, NULL, _("Note: Fast-forwarding submodule %s to %s"), path, oid_to_hex(a)); ret = 1; @@ -1686,30 +1848,27 @@ static int merge_submodule(struct merge_options *opt, &merges); switch (parent_count) { case 0: - path_msg(opt, path, 0, _("Failed to merge submodule %s"), path); + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s"), path); break; case 1: format_commit(&sb, 4, &subrepo, (struct commit *)merges.objects[0].item); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s, but a possible merge " - "resolution exists:\n%s\n"), + "resolution exists: %s"), path, sb.buf); - path_msg(opt, path, 1, - _("If this is correct simply add it to the index " - "for example\n" - "by using:\n\n" - " git update-index --cacheinfo 160000 %s \"%s\"\n\n" - "which will accept this suggestion.\n"), - oid_to_hex(&merges.objects[0].item->oid), path); strbuf_release(&sb); break; default: for (i = 0; i < merges.nr; i++) format_commit(&sb, 4, &subrepo, (struct commit *)merges.objects[i].item); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s, but multiple " "possible merges exist:\n%s"), path, sb.buf); strbuf_release(&sb); @@ -1717,7 +1876,23 @@ static int merge_submodule(struct merge_options *opt, object_array_clear(&merges); cleanup: - repo_clear(&subrepo); + if (!opt->priv->call_depth && !ret) { + struct string_list *csub = &opt->priv->conflicted_submodules; + struct conflicted_submodule_item *util; + const char *abbrev; + + util = xmalloc(sizeof(*util)); + util->flag = sub_flag; + util->abbrev = NULL; + if (!sub_not_initialized) { + abbrev = repo_find_unique_abbrev(&subrepo, b, DEFAULT_ABBREV); + util->abbrev = xstrdup(abbrev); + } + string_list_append(csub, path)->util = util; + } + + if (!sub_not_initialized) + repo_clear(&subrepo); return ret; } @@ -1835,7 +2010,8 @@ static int merge_3way(struct merge_options *opt, &src1, name1, &src2, name2, &opt->priv->attr_index, &ll_opts); if (merge_status == LL_MERGE_BINARY_CONFLICT) - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_BINARY, 0, + path, NULL, NULL, NULL, "warning: Cannot merge binary files: %s (%s vs. %s)", path, name1, name2); @@ -1947,7 +2123,8 @@ static int handle_content_merge(struct merge_options *opt, if (ret) return -1; clean &= (merge_status == 0); - path_msg(opt, path, 1, _("Auto-merging %s"), path); + path_msg(opt, INFO_AUTO_MERGING, 1, path, NULL, NULL, NULL, + _("Auto-merging %s"), path); } else if (S_ISGITLINK(a->mode)) { int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); clean = merge_submodule(opt, pathnames[0], @@ -2085,21 +2262,24 @@ static char *handle_path_level_conflicts(struct merge_options *opt, c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); - path_msg(opt, new_path, 0, - _("CONFLICT (implicit dir rename): Existing file/dir " - "at %s in the way of implicit directory rename(s) " - "putting the following path(s) there: %s."), - new_path, collision_paths.buf); + path_msg(opt, CONFLICT_DIR_RENAME_FILE_IN_WAY, 0, + new_path, NULL, NULL, &c_info->source_files, + _("CONFLICT (implicit dir rename): Existing " + "file/dir at %s in the way of implicit " + "directory rename(s) putting the following " + "path(s) there: %s."), + new_path, collision_paths.buf); clean = 0; } else if (c_info->source_files.nr > 1) { c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); - path_msg(opt, new_path, 0, - _("CONFLICT (implicit dir rename): Cannot map more " - "than one path to %s; implicit directory renames " - "tried to put these paths there: %s"), - new_path, collision_paths.buf); + path_msg(opt, CONFLICT_DIR_RENAME_COLLISION, 0, + new_path, NULL, NULL, &c_info->source_files, + _("CONFLICT (implicit dir rename): Cannot map " + "more than one path to %s; implicit directory " + "renames tried to put these paths there: %s"), + new_path, collision_paths.buf); clean = 0; } @@ -2153,13 +2333,14 @@ static void get_provisional_directory_renames(struct merge_options *opt, continue; if (bad_max == max) { - path_msg(opt, source_dir, 0, - _("CONFLICT (directory rename split): " - "Unclear where to rename %s to; it was " - "renamed to multiple other directories, with " - "no destination getting a majority of the " - "files."), - source_dir); + path_msg(opt, CONFLICT_DIR_RENAME_SPLIT, 0, + source_dir, NULL, NULL, NULL, + _("CONFLICT (directory rename split): " + "Unclear where to rename %s to; it was " + "renamed to multiple other directories, " + "with no destination getting a majority of " + "the files."), + source_dir); *clean = 0; } else { strmap_put(&renames->dir_renames[side], @@ -2334,7 +2515,8 @@ static char *check_for_directory_rename(struct merge_options *opt, new_dir = rename_info->value; /* old_dir = rename_info->key; */ otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); if (otherinfo) { - path_msg(opt, rename_info->key, 1, + path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1, + rename_info->key, path, new_dir, NULL, _("WARNING: Avoiding applying %s -> %s rename " "to %s, because %s itself was renamed."), rename_info->key, new_dir, path, new_dir); @@ -2475,14 +2657,16 @@ static void apply_directory_rename_modifications(struct merge_options *opt, if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) { /* Notify user of updated path */ if (pair->status == 'A') - path_msg(opt, new_path, 1, + path_msg(opt, INFO_DIR_RENAME_APPLIED, 1, + new_path, old_path, NULL, NULL, _("Path updated: %s added in %s inside a " "directory that was renamed in %s; moving " "it to %s."), old_path, branch_with_new_path, branch_with_dir_rename, new_path); else - path_msg(opt, new_path, 1, + path_msg(opt, INFO_DIR_RENAME_APPLIED, 1, + new_path, old_path, NULL, NULL, _("Path updated: %s renamed to %s in %s, " "inside a directory that was renamed in %s; " "moving it to %s."), @@ -2495,7 +2679,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt, */ ci->path_conflict = 1; if (pair->status == 'A') - path_msg(opt, new_path, 1, + path_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1, + new_path, old_path, NULL, NULL, _("CONFLICT (file location): %s added in %s " "inside a directory that was renamed in %s, " "suggesting it should perhaps be moved to " @@ -2503,7 +2688,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt, old_path, branch_with_new_path, branch_with_dir_rename, new_path); else - path_msg(opt, new_path, 1, + path_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1, + new_path, old_path, NULL, NULL, _("CONFLICT (file location): %s renamed to %s " "in %s, inside a directory that was renamed " "in %s, suggesting it should perhaps be " @@ -2659,7 +2845,8 @@ static int process_renames(struct merge_options *opt, * and remove the setting of base->path_conflict to 1. */ base->path_conflict = 1; - path_msg(opt, oldpath, 0, + path_msg(opt, CONFLICT_RENAME_RENAME, 0, + pathnames[0], pathnames[1], pathnames[2], NULL, _("CONFLICT (rename/rename): %s renamed to " "%s in %s and to %s in %s."), pathnames[0], @@ -2754,7 +2941,8 @@ static int process_renames(struct merge_options *opt, memcpy(&newinfo->stages[target_index], &merged, sizeof(merged)); if (!clean) { - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_COLLIDES, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename involved in " "collision): rename of %s -> %s has " "content conflicts AND collides " @@ -2773,7 +2961,8 @@ static int process_renames(struct merge_options *opt, */ newinfo->path_conflict = 1; - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_DELETE, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename/delete): %s renamed " "to %s in %s, but deleted in %s."), oldpath, newpath, rename_branch, delete_branch); @@ -2797,7 +2986,8 @@ static int process_renames(struct merge_options *opt, } else if (source_deleted) { /* rename/delete */ newinfo->path_conflict = 1; - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_DELETE, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename/delete): %s renamed" " to %s in %s, but deleted in %s."), oldpath, newpath, @@ -3712,7 +3902,8 @@ static void process_entry(struct merge_options *opt, path = unique_path(opt, path, branch); strmap_put(&opt->priv->paths, path, new_ci); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_FILE_DIRECTORY, 0, + path, old_path, NULL, NULL, _("CONFLICT (file/directory): directory in the way " "of %s from %s; moving it to %s instead."), old_path, branch, path); @@ -3788,15 +3979,23 @@ static void process_entry(struct merge_options *opt, rename_b = 1; } + if (rename_a) + a_path = unique_path(opt, path, opt->branch1); + if (rename_b) + b_path = unique_path(opt, path, opt->branch2); + if (rename_a && rename_b) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_DISTINCT_MODES, 0, + path, a_path, b_path, NULL, _("CONFLICT (distinct types): %s had " "different types on each side; " "renamed both of them so each can " "be recorded somewhere."), path); } else { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_DISTINCT_MODES, 0, + path, rename_a ? a_path : b_path, + NULL, NULL, _("CONFLICT (distinct types): %s had " "different types on each side; " "renamed one of them so each can be " @@ -3833,14 +4032,10 @@ static void process_entry(struct merge_options *opt, /* Insert entries into opt->priv_paths */ assert(rename_a || rename_b); - if (rename_a) { - a_path = unique_path(opt, path, opt->branch1); + if (rename_a) strmap_put(&opt->priv->paths, a_path, ci); - } - if (rename_b) - b_path = unique_path(opt, path, opt->branch2); - else + if (!rename_b) b_path = path; strmap_put(&opt->priv->paths, b_path, new_ci); @@ -3891,7 +4086,8 @@ static void process_entry(struct merge_options *opt, reason = _("add/add"); if (S_ISGITLINK(merged_file.mode)) reason = _("submodule"); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_CONTENTS, 0, + path, NULL, NULL, NULL, _("CONFLICT (%s): Merge conflict in %s"), reason, path); } @@ -3935,7 +4131,8 @@ static void process_entry(struct merge_options *opt, * since the contents were not modified. */ } else { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_MODIFY_DELETE, 0, + path, NULL, NULL, NULL, _("CONFLICT (modify/delete): %s deleted in %s " "and modified in %s. Version %s of %s left " "in tree."), @@ -4223,21 +4420,8 @@ static int record_conflicted_index_entries(struct merge_options *opt) * the CE_SKIP_WORKTREE bit and manually write those * files to the working disk here. */ - if (ce_skip_worktree(ce)) { - struct stat st; - - if (!lstat(path, &st)) { - char *new_name = unique_path(opt, - path, - "cruft"); - - path_msg(opt, path, 1, - _("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"), - path, new_name); - errs |= rename(path, new_name); - } + if (ce_skip_worktree(ce)) errs |= checkout_entry(ce, &state, NULL, NULL); - } /* * Mark this cache entry for removal and instead add @@ -4279,6 +4463,152 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +static void print_submodule_conflict_suggestion(struct string_list *csub) { + struct string_list_item *item; + struct strbuf msg = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + struct strbuf subs = STRBUF_INIT; + + if (!csub->nr) + return; + + strbuf_add_separated_string_list(&subs, " ", csub); + for_each_string_list_item(item, csub) { + struct conflicted_submodule_item *util = item->util; + + /* + * NEEDSWORK: The steps to resolve these errors deserve a more + * detailed explanation than what is currently printed below. + */ + if (util->flag == CONFLICT_SUBMODULE_NOT_INITIALIZED || + util->flag == CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE) + continue; + + /* + * TRANSLATORS: This is a line of advice to resolve a merge + * conflict in a submodule. The first argument is the submodule + * name, and the second argument is the abbreviated id of the + * commit that needs to be merged. For example: + * - go to submodule (mysubmodule), and either merge commit abc1234" + */ + strbuf_addf(&tmp, _(" - go to submodule (%s), and either merge commit %s\n" + " or update to an existing commit which has merged those changes\n"), + item->string, util->abbrev); + } + + /* + * TRANSLATORS: This is a detailed message for resolving submodule + * conflicts. The first argument is string containing one step per + * submodule. The second is a space-separated list of submodule names. + */ + strbuf_addf(&msg, + _("Recursive merging with submodules currently only supports trivial cases.\n" + "Please manually handle the merging of each conflicted submodule.\n" + "This can be accomplished with the following steps:\n" + "%s" + " - come back to superproject and run:\n\n" + " git add %s\n\n" + " to record the above merge or update\n" + " - resolve any other conflicts in the superproject\n" + " - commit the resulting index in the superproject\n"), + tmp.buf, subs.buf); + + printf("%s", msg.buf); + + strbuf_release(&subs); + strbuf_release(&tmp); + strbuf_release(&msg); +} + +void merge_display_update_messages(struct merge_options *opt, + int detailed, + struct merge_result *result) +{ + struct merge_options_internal *opti = result->priv; + struct hashmap_iter iter; + struct strmap_entry *e; + struct string_list olist = STRING_LIST_INIT_NODUP; + + if (opt->record_conflict_msgs_as_headers) + BUG("Either display conflict messages or record them as headers, not both"); + + trace2_region_enter("merge", "display messages", opt->repo); + + /* Hack to pre-allocate olist to the desired size */ + ALLOC_GROW(olist.items, strmap_get_size(&opti->conflicts), + olist.alloc); + + /* Put every entry from output into olist, then sort */ + strmap_for_each_entry(&opti->conflicts, &iter, e) { + string_list_append(&olist, e->key)->util = e->value; + } + string_list_sort(&olist); + + /* Iterate over the items, printing them */ + for (int path_nr = 0; path_nr < olist.nr; ++path_nr) { + struct string_list *conflicts = olist.items[path_nr].util; + for (int i = 0; i < conflicts->nr; i++) { + struct logical_conflict_info *info = + conflicts->items[i].util; + + if (detailed) { + printf("%lu", (unsigned long)info->paths.nr); + putchar('\0'); + for (int n = 0; n < info->paths.nr; n++) { + fputs(info->paths.v[n], stdout); + putchar('\0'); + } + fputs(type_short_descriptions[info->type], + stdout); + putchar('\0'); + } + puts(conflicts->items[i].string); + if (detailed) + putchar('\0'); + } + } + string_list_clear(&olist, 0); + + print_submodule_conflict_suggestion(&opti->conflicted_submodules); + + /* Also include needed rename limit adjustment now */ + diff_warn_rename_limit("merge.renamelimit", + opti->renames.needed_limit, 0); + + trace2_region_leave("merge", "display messages", opt->repo); +} + +void merge_get_conflicted_files(struct merge_result *result, + struct string_list *conflicted_files) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + struct merge_options_internal *opti = result->priv; + + strmap_for_each_entry(&opti->conflicted, &iter, e) { + const char *path = e->key; + struct conflict_info *ci = e->value; + int i; + + VERIFY_CI(ci); + + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + struct stage_info *si; + + if (!(ci->filemask & (1ul << i))) + continue; + + si = xmalloc(sizeof(*si)); + si->stage = i+1; + si->mode = ci->stages[i].mode; + oidcpy(&si->oid, &ci->stages[i].oid); + string_list_append(conflicted_files, path)->util = si; + } + } + /* string_list_sort() uses a stable sort, so we're good */ + string_list_sort(conflicted_files); +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result, @@ -4321,43 +4651,8 @@ void merge_switch_to_result(struct merge_options *opt, fclose(fp); trace2_region_leave("merge", "write_auto_merge", opt->repo); } - - if (display_update_msgs) { - struct merge_options_internal *opti = result->priv; - struct hashmap_iter iter; - struct strmap_entry *e; - struct string_list olist = STRING_LIST_INIT_NODUP; - int i; - - if (opt->record_conflict_msgs_as_headers) - BUG("Either display conflict messages or record them as headers, not both"); - - trace2_region_enter("merge", "display messages", opt->repo); - - /* Hack to pre-allocate olist to the desired size */ - ALLOC_GROW(olist.items, strmap_get_size(&opti->output), - olist.alloc); - - /* Put every entry from output into olist, then sort */ - strmap_for_each_entry(&opti->output, &iter, e) { - string_list_append(&olist, e->key)->util = e->value; - } - string_list_sort(&olist); - - /* Iterate over the items, printing them */ - for (i = 0; i < olist.nr; ++i) { - struct strbuf *sb = olist.items[i].util; - - printf("%s", sb->buf); - } - string_list_clear(&olist, 0); - - /* Also include needed rename limit adjustment now */ - diff_warn_rename_limit("merge.renamelimit", - opti->renames.needed_limit, 0); - - trace2_region_leave("merge", "display messages", opt->repo); - } + if (display_update_msgs) + merge_display_update_messages(opt, /* detailed */ 0, result); merge_finalize(opt, result); } @@ -4477,6 +4772,7 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_enter("merge", "allocate/init", opt->repo); if (opt->priv) { clear_or_reinit_internal_opts(opt->priv, 1); + string_list_init_nodup(&opt->priv->conflicted_submodules); trace2_region_leave("merge", "allocate/init", opt->repo); return; } @@ -4531,11 +4827,11 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) strmap_init_with_options(&opt->priv->conflicted, pool, 0); /* - * keys & strbufs in output will sometimes need to outlive "paths", - * so it will have a copy of relevant keys. It's probably a small - * subset of the overall paths that have special output. + * keys & string_lists in conflicts will sometimes need to outlive + * "paths", so it will have a copy of relevant keys. It's probably + * a small subset of the overall paths that have special output. */ - strmap_init(&opt->priv->output); + strmap_init(&opt->priv->conflicts); trace2_region_leave("merge", "allocate/init", opt->repo); } @@ -4636,7 +4932,8 @@ redo: trace2_region_leave("merge", "process_entries", opt->repo); /* Set return values */ - result->path_messages = &opt->priv->output; + result->path_messages = &opt->priv->conflicts; + result->tree = parse_tree_indirect(&working_tree_oid); /* existence of conflicted entries implies unclean */ result->clean &= strmap_empty(&opt->priv->conflicted); diff --git a/merge-ort.h b/merge-ort.h index fe599b8786..a994c9a5fc 100644 --- a/merge-ort.h +++ b/merge-ort.h @@ -2,6 +2,7 @@ #define MERGE_ORT_H #include "merge-recursive.h" +#include "hash.h" struct commit; struct tree; @@ -27,7 +28,7 @@ struct merge_result { /* * Special messages and conflict notices for various paths * - * This is a map of pathnames to strbufs. It contains various + * This is a map of pathnames to a string_list. It contains various * warning/conflict/notice messages (possibly multiple per path) * that callers may want to use. */ @@ -80,6 +81,35 @@ void merge_switch_to_result(struct merge_options *opt, int update_worktree_and_index, int display_update_msgs); +/* + * Display messages about conflicts and which files were 3-way merged. + * Automatically called by merge_switch_to_result() with stream == stdout, + * so only call this when bypassing merge_switch_to_result(). + */ +void merge_display_update_messages(struct merge_options *opt, + int detailed, + struct merge_result *result); + +struct stage_info { + struct object_id oid; + int mode; + int stage; +}; + +/* + * Provide a list of path -> {struct stage_info*} mappings for + * all conflicted files. Note that each path could appear up to three + * times in the list, corresponding to 3 different stage entries. In short, + * this basically provides the info that would be printed by `ls-files -u`. + * + * result should have been populated by a call to + * one of the merge_incore_[non]recursive() functions. + * + * conflicted_files should be empty before calling this function. + */ +void merge_get_conflicted_files(struct merge_result *result, + struct string_list *conflicted_files); + /* Do needed cleanup when not calling merge_switch_to_result() */ void merge_finalize(struct merge_options *opt, struct merge_result *result); diff --git a/merge-recursive.c b/merge-recursive.c index b83a129b43..4ddd3adea0 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -45,7 +45,7 @@ struct path_hashmap_entry { char path[FLEX_ARRAY]; }; -static int path_hashmap_cmp(const void *cmp_data, +static int path_hashmap_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -89,10 +89,10 @@ static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap, return hashmap_get_entry(hashmap, &key, ent, NULL); } -static int dir_rename_cmp(const void *unused_cmp_data, +static int dir_rename_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct dir_rename_entry *e1, *e2; @@ -134,10 +134,10 @@ static struct collision_entry *collision_find_entry(struct hashmap *hashmap, return hashmap_get_entry(hashmap, &key, ent, NULL); } -static int collision_cmp(const void *unused_cmp_data, +static int collision_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { const struct collision_entry *e1, *e2; @@ -456,7 +456,7 @@ static void unpack_trees_finish(struct merge_options *opt) clear_unpack_trees_porcelain(&opt->priv->unpack_opts); } -static int save_files_dirs(const struct object_id *oid, +static int save_files_dirs(const struct object_id *oid UNUSED, struct strbuf *base, const char *path, unsigned int mode, void *context) { diff --git a/mergesort.c b/mergesort.c deleted file mode 100644 index bd9c6ef8ee..0000000000 --- a/mergesort.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "cache.h" -#include "mergesort.h" - -/* Combine two sorted lists. Take from `list` on equality. */ -static void *llist_merge(void *list, void *other, - void *(*get_next_fn)(const void *), - void (*set_next_fn)(void *, void *), - int (*compare_fn)(const void *, const void *)) -{ - void *result = list, *tail; - - if (compare_fn(list, other) > 0) { - result = other; - goto other; - } - for (;;) { - do { - tail = list; - list = get_next_fn(list); - if (!list) { - set_next_fn(tail, other); - return result; - } - } while (compare_fn(list, other) <= 0); - set_next_fn(tail, other); - other: - do { - tail = other; - other = get_next_fn(other); - if (!other) { - set_next_fn(tail, list); - return result; - } - } while (compare_fn(list, other) > 0); - set_next_fn(tail, list); - } -} - -/* - * Perform an iterative mergesort using an array of sublists. - * - * n is the number of items. - * ranks[i] is undefined if n & 2^i == 0, and assumed empty. - * ranks[i] contains a sublist of length 2^i otherwise. - * - * The number of bits in a void pointer limits the number of objects - * that can be created, and thus the number of array elements necessary - * to be able to sort any valid list. - * - * Adding an item to this array is like incrementing a binary number; - * positional values for set bits correspond to sublist lengths. - */ -void *llist_mergesort(void *list, - void *(*get_next_fn)(const void *), - void (*set_next_fn)(void *, void *), - int (*compare_fn)(const void *, const void *)) -{ - void *ranks[bitsizeof(void *)]; - size_t n = 0; - int i; - - while (list) { - void *next = get_next_fn(list); - if (next) - set_next_fn(list, NULL); - for (i = 0; n & ((size_t)1 << i); i++) - list = llist_merge(ranks[i], list, get_next_fn, - set_next_fn, compare_fn); - n++; - ranks[i] = list; - list = next; - } - - for (i = 0; n; i++, n >>= 1) { - if (!(n & 1)) - continue; - if (list) - list = llist_merge(ranks[i], list, get_next_fn, - set_next_fn, compare_fn); - else - list = ranks[i]; - } - return list; -} diff --git a/mergesort.h b/mergesort.h index 644cff1f96..7c36f08bd5 100644 --- a/mergesort.h +++ b/mergesort.h @@ -1,17 +1,105 @@ #ifndef MERGESORT_H #define MERGESORT_H +/* Combine two sorted lists. Take from `list` on equality. */ +#define DEFINE_LIST_MERGE_INTERNAL(name, type) \ +static type *name##__merge(type *list, type *other, \ + int (*compare_fn)(const type *, const type *))\ +{ \ + type *result = list, *tail; \ + int prefer_list = compare_fn(list, other) <= 0; \ + \ + if (!prefer_list) { \ + result = other; \ + SWAP(list, other); \ + } \ + for (;;) { \ + do { \ + tail = list; \ + list = name##__get_next(list); \ + if (!list) { \ + name##__set_next(tail, other); \ + return result; \ + } \ + } while (compare_fn(list, other) < prefer_list); \ + name##__set_next(tail, other); \ + prefer_list ^= 1; \ + SWAP(list, other); \ + } \ +} + /* - * Sort linked list in place. - * - get_next_fn() returns the next element given an element of a linked list. - * - set_next_fn() takes two elements A and B, and makes B the "next" element - * of A on the list. - * - compare_fn() takes two elements A and B, and returns negative, 0, positive - * as the same sign as "subtracting" B from A. + * Perform an iterative mergesort using an array of sublists. + * + * n is the number of items. + * ranks[i] is undefined if n & 2^i == 0, and assumed empty. + * ranks[i] contains a sublist of length 2^i otherwise. + * + * The number of bits in a void pointer limits the number of objects + * that can be created, and thus the number of array elements necessary + * to be able to sort any valid list. + * + * Adding an item to this array is like incrementing a binary number; + * positional values for set bits correspond to sublist lengths. */ -void *llist_mergesort(void *list, - void *(*get_next_fn)(const void *), - void (*set_next_fn)(void *, void *), - int (*compare_fn)(const void *, const void *)); +#define DEFINE_LIST_SORT_INTERNAL(scope, name, type) \ +scope void name(type **listp, \ + int (*compare_fn)(const type *, const type *)) \ +{ \ + type *list = *listp; \ + type *ranks[bitsizeof(type *)]; \ + size_t n = 0; \ + \ + if (!list) \ + return; \ + \ + for (;;) { \ + int i; \ + size_t m; \ + type *next = name##__get_next(list); \ + if (next) \ + name##__set_next(list, NULL); \ + for (i = 0, m = n;; i++, m >>= 1) { \ + if (m & 1) { \ + list = name##__merge(ranks[i], list, \ + compare_fn); \ + } else if (next) { \ + break; \ + } else if (!m) { \ + *listp = list; \ + return; \ + } \ + } \ + n++; \ + ranks[i] = list; \ + list = next; \ + } \ +} + +#define DECLARE_LIST_SORT(scope, name, type) \ +scope void name(type **listp, \ + int (*compare_fn)(const type *, const type *)) + +#define DEFINE_LIST_SORT_DEBUG(scope, name, type, next_member, \ + on_get_next, on_set_next) \ + \ +static inline type *name##__get_next(const type *elem) \ +{ \ + on_get_next; \ + return elem->next_member; \ +} \ + \ +static inline void name##__set_next(type *elem, type *next) \ +{ \ + on_set_next; \ + elem->next_member = next; \ +} \ + \ +DEFINE_LIST_MERGE_INTERNAL(name, type) \ +DEFINE_LIST_SORT_INTERNAL(scope, name, type) \ +DECLARE_LIST_SORT(scope, name, type) + +#define DEFINE_LIST_SORT(scope, name, type, next_member) \ +DEFINE_LIST_SORT_DEBUG(scope, name, type, next_member, (void)0, (void)0) #endif diff --git a/midx.c b/midx.c index 5f0dd386b0..c27d0e5f15 100644 --- a/midx.c +++ b/midx.c @@ -577,6 +577,78 @@ static void fill_pack_entry(uint32_t pack_int_id, entry->preferred = !!preferred; } +struct midx_fanout { + struct pack_midx_entry *entries; + uint32_t nr; + uint32_t alloc; +}; + +static void midx_fanout_grow(struct midx_fanout *fanout, uint32_t nr) +{ + ALLOC_GROW(fanout->entries, nr, fanout->alloc); +} + +static void midx_fanout_sort(struct midx_fanout *fanout) +{ + QSORT(fanout->entries, fanout->nr, midx_oid_compare); +} + +static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, + struct multi_pack_index *m, + uint32_t cur_fanout, + int preferred_pack) +{ + uint32_t start = 0, end; + uint32_t cur_object; + + if (cur_fanout) + start = ntohl(m->chunk_oid_fanout[cur_fanout - 1]); + end = ntohl(m->chunk_oid_fanout[cur_fanout]); + + for (cur_object = start; cur_object < end; cur_object++) { + if ((preferred_pack > -1) && + (preferred_pack == nth_midxed_pack_int_id(m, cur_object))) { + /* + * Objects from preferred packs are added + * separately. + */ + continue; + } + + midx_fanout_grow(fanout, fanout->nr + 1); + nth_midxed_pack_midx_entry(m, + &fanout->entries[fanout->nr], + cur_object); + fanout->entries[fanout->nr].preferred = 0; + fanout->nr++; + } +} + +static void midx_fanout_add_pack_fanout(struct midx_fanout *fanout, + struct pack_info *info, + uint32_t cur_pack, + int preferred, + uint32_t cur_fanout) +{ + struct packed_git *pack = info[cur_pack].p; + uint32_t start = 0, end; + uint32_t cur_object; + + if (cur_fanout) + start = get_pack_fanout(pack, cur_fanout - 1); + end = get_pack_fanout(pack, cur_fanout); + + for (cur_object = start; cur_object < end; cur_object++) { + midx_fanout_grow(fanout, fanout->nr + 1); + fill_pack_entry(cur_pack, + info[cur_pack].p, + cur_object, + &fanout->entries[fanout->nr], + preferred); + fanout->nr++; + } +} + /* * It is possible to artificially get into a state where there are many * duplicate copies of objects. That can create high memory pressure if @@ -595,8 +667,8 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, int preferred_pack) { uint32_t cur_fanout, cur_pack, cur_object; - uint32_t alloc_fanout, alloc_objects, total_objects = 0; - struct pack_midx_entry *entries_by_fanout = NULL; + uint32_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; @@ -608,74 +680,51 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, * slices to be evenly distributed, with some noise. Hence, * allocate slightly more than one 256th. */ - alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16; + alloc_objects = fanout.alloc = total_objects > 3200 ? total_objects / 200 : 16; - ALLOC_ARRAY(entries_by_fanout, alloc_fanout); + ALLOC_ARRAY(fanout.entries, fanout.alloc); ALLOC_ARRAY(deduplicated_entries, alloc_objects); *nr_objects = 0; for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) { - uint32_t nr_fanout = 0; - - if (m) { - uint32_t start = 0, end; - - if (cur_fanout) - start = ntohl(m->chunk_oid_fanout[cur_fanout - 1]); - end = ntohl(m->chunk_oid_fanout[cur_fanout]); - - for (cur_object = start; cur_object < end; cur_object++) { - ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout); - nth_midxed_pack_midx_entry(m, - &entries_by_fanout[nr_fanout], - cur_object); - if (nth_midxed_pack_int_id(m, cur_object) == preferred_pack) - entries_by_fanout[nr_fanout].preferred = 1; - else - entries_by_fanout[nr_fanout].preferred = 0; - nr_fanout++; - } - } + fanout.nr = 0; + + if (m) + midx_fanout_add_midx_fanout(&fanout, m, cur_fanout, + preferred_pack); for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) { - uint32_t start = 0, end; int preferred = cur_pack == preferred_pack; - - if (cur_fanout) - start = get_pack_fanout(info[cur_pack].p, cur_fanout - 1); - end = get_pack_fanout(info[cur_pack].p, cur_fanout); - - for (cur_object = start; cur_object < end; cur_object++) { - ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout); - fill_pack_entry(cur_pack, - info[cur_pack].p, - cur_object, - &entries_by_fanout[nr_fanout], - preferred); - nr_fanout++; - } + midx_fanout_add_pack_fanout(&fanout, + info, cur_pack, + preferred, cur_fanout); } - QSORT(entries_by_fanout, nr_fanout, midx_oid_compare); + if (-1 < preferred_pack && preferred_pack < start_pack) + midx_fanout_add_pack_fanout(&fanout, info, + preferred_pack, 1, + cur_fanout); + + midx_fanout_sort(&fanout); /* * The batch is now sorted by OID and then mtime (descending). * Take only the first duplicate. */ - for (cur_object = 0; cur_object < nr_fanout; cur_object++) { - if (cur_object && oideq(&entries_by_fanout[cur_object - 1].oid, - &entries_by_fanout[cur_object].oid)) + for (cur_object = 0; cur_object < fanout.nr; cur_object++) { + if (cur_object && oideq(&fanout.entries[cur_object - 1].oid, + &fanout.entries[cur_object].oid)) continue; ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects); memcpy(&deduplicated_entries[*nr_objects], - &entries_by_fanout[cur_object], + &fanout.entries[cur_object], sizeof(struct pack_midx_entry)); (*nr_objects)++; } } - free(entries_by_fanout); + free(fanout.entries); return deduplicated_entries; } @@ -1053,40 +1102,37 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr return cb.commits; } -static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash, - struct write_midx_context *ctx, - const char *refs_snapshot, +static int write_midx_bitmap(const char *midx_name, + const unsigned char *midx_hash, + struct packing_data *pdata, + struct commit **commits, + uint32_t commits_nr, + uint32_t *pack_order, unsigned flags) { - struct packing_data pdata; - struct pack_idx_entry **index; - struct commit **commits = NULL; - uint32_t i, commits_nr; + int ret, i; uint16_t options = 0; - char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash)); - int ret; - - if (!ctx->entries_nr) - BUG("cannot write a bitmap without any objects"); + struct pack_idx_entry **index; + char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, + hash_to_hex(midx_hash)); if (flags & MIDX_WRITE_BITMAP_HASH_CACHE) options |= BITMAP_OPT_HASH_CACHE; - prepare_midx_packing_data(&pdata, ctx); - - commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, ctx); + if (flags & MIDX_WRITE_BITMAP_LOOKUP_TABLE) + options |= BITMAP_OPT_LOOKUP_TABLE; /* * Build the MIDX-order index based on pdata.objects (which is already * in MIDX order; c.f., 'midx_pack_order_cmp()' for the definition of * this order). */ - ALLOC_ARRAY(index, pdata.nr_objects); - for (i = 0; i < pdata.nr_objects; i++) - index[i] = &pdata.objects[i].idx; + ALLOC_ARRAY(index, pdata->nr_objects); + 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_build_type_index(pdata, index, pdata->nr_objects); /* * bitmap_writer_finish expects objects in lex order, but pack_order @@ -1101,16 +1147,16 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash, * happens between bitmap_writer_build_type_index() and * bitmap_writer_finish(). */ - for (i = 0; i < pdata.nr_objects; i++) - index[ctx->pack_order[i]] = &pdata.objects[i].idx; + 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); + ret = bitmap_writer_build(pdata); if (ret < 0) goto cleanup; bitmap_writer_set_checksum(midx_hash); - bitmap_writer_finish(index, pdata.nr_objects, bitmap_name, options); + bitmap_writer_finish(index, pdata->nr_objects, bitmap_name, options); cleanup: free(index); @@ -1443,14 +1489,40 @@ static int write_midx_internal(const char *object_dir, if (flags & MIDX_WRITE_REV_INDEX && git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0)) write_midx_reverse_index(midx_name.buf, midx_hash, &ctx); + if (flags & MIDX_WRITE_BITMAP) { - if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx, - refs_snapshot, flags) < 0) { + struct packing_data pdata; + struct commit **commits; + uint32_t commits_nr; + + if (!ctx.entries_nr) + BUG("cannot write a bitmap without any objects"); + + prepare_midx_packing_data(&pdata, &ctx); + + commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, &ctx); + + /* + * The previous steps translated the information from + * 'entries' into information suitable for constructing + * bitmaps. We no longer need that array, so clear it to + * reduce memory pressure. + */ + FREE_AND_NULL(ctx.entries); + ctx.entries_nr = 0; + + if (write_midx_bitmap(midx_name.buf, midx_hash, &pdata, + commits, commits_nr, ctx.pack_order, + flags) < 0) { error(_("could not write multi-pack bitmap")); result = 1; goto cleanup; } } + /* + * NOTE: Do not use ctx.entries beyond this point, since it might + * have been freed in the previous if block. + */ if (ctx.m) close_object_store(the_repository->objects); diff --git a/midx.h b/midx.h index 22e8e53288..5578cd7b83 100644 --- a/midx.h +++ b/midx.h @@ -47,6 +47,7 @@ struct multi_pack_index { #define MIDX_WRITE_REV_INDEX (1 << 1) #define MIDX_WRITE_BITMAP (1 << 2) #define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3) +#define MIDX_WRITE_BITMAP_LOOKUP_TABLE (1 << 4) const unsigned char *get_midx_checksum(struct multi_pack_index *m); void get_midx_filename(struct strbuf *out, const char *object_dir); diff --git a/name-hash.c b/name-hash.c index 7487d33124..cd009c7c8a 100644 --- a/name-hash.c +++ b/name-hash.c @@ -18,7 +18,7 @@ struct dir_entry { char name[FLEX_ARRAY]; }; -static int dir_entry_cmp(const void *unused_cmp_data, +static int dir_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata) @@ -120,7 +120,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) add_dir_entry(istate, ce); } -static int cache_entry_cmp(const void *unused_cmp_data, +static int cache_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *remove) diff --git a/negotiator/default.c b/negotiator/default.c index 434189ae5d..b7e79feaf0 100644 --- a/negotiator/default.c +++ b/negotiator/default.c @@ -36,7 +36,8 @@ static void rev_list_push(struct negotiation_state *ns, } static int clear_marks(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, + void *cb_data UNUSED) { struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0); diff --git a/negotiator/skipping.c b/negotiator/skipping.c index 1236e79224..c4398f5ae1 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c @@ -72,7 +72,8 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int } static int clear_marks(const char *refname, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, + void *cb_data UNUSED) { struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0); diff --git a/notes.c b/notes.c index 7452e71cc8..f2805d51bb 100644 --- a/notes.c +++ b/notes.c @@ -924,8 +924,9 @@ out: return ret; } -static int string_list_add_one_ref(const char *refname, const struct object_id *oid, - int flag, void *cb) +static int string_list_add_one_ref(const char *refname, + const struct object_id *oid UNUSED, + int flag UNUSED, void *cb) { struct string_list *refs = cb; if (!unsorted_string_list_has_string(refs, refname)) @@ -1005,6 +1006,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, if (!notes_ref) notes_ref = default_notes_ref(); + update_ref_namespace(NAMESPACE_NOTES, xstrdup(notes_ref)); if (!combine_notes) combine_notes = combine_notes_concatenate; diff --git a/object-file.c b/object-file.c index 6c8e3b1660..5b270f046d 100644 --- a/object-file.c +++ b/object-file.c @@ -1951,6 +1951,96 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename) return fd; } +/** + * Common steps for loose object writers to start writing loose + * objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. + * + * Returns a "fd", which should later be provided to + * end_loose_object_common(). + */ +static int start_loose_object_common(struct strbuf *tmp_file, + const char *filename, unsigned flags, + git_zstream *stream, + unsigned char *buf, size_t buflen, + git_hash_ctx *c, + char *hdr, int hdrlen) +{ + int fd; + + fd = create_tmpfile(tmp_file, filename); + if (fd < 0) { + if (flags & HASH_SILENT) + return -1; + else if (errno == EACCES) + return error(_("insufficient permission for adding " + "an object to repository database %s"), + get_object_directory()); + else + return error_errno( + _("unable to create temporary file")); + } + + /* Setup zlib stream for compression */ + git_deflate_init(stream, zlib_compression_level); + stream->next_out = buf; + stream->avail_out = buflen; + the_hash_algo->init_fn(c); + + /* Start to feed header to zlib stream */ + stream->next_in = (unsigned char *)hdr; + stream->avail_in = hdrlen; + while (git_deflate(stream, 0) == Z_OK) + ; /* nothing */ + the_hash_algo->update_fn(c, hdr, hdrlen); + + return fd; +} + +/** + * Common steps for the inner git_deflate() loop for writing loose + * objects. Returns what git_deflate() returns. + */ +static int write_loose_object_common(git_hash_ctx *c, + git_zstream *stream, const int flush, + unsigned char *in0, const int fd, + unsigned char *compressed, + const size_t compressed_len) +{ + int ret; + + ret = git_deflate(stream, flush ? Z_FINISH : 0); + the_hash_algo->update_fn(c, in0, stream->next_in - in0); + if (write_buffer(fd, compressed, stream->next_out - compressed) < 0) + die(_("unable to write loose object file")); + stream->next_out = compressed; + stream->avail_out = compressed_len; + + return ret; +} + +/** + * Common steps for loose object writers to end writing loose objects: + * + * - End the compression of zlib stream. + * - Get the calculated oid to "oid". + */ +static int end_loose_object_common(git_hash_ctx *c, git_zstream *stream, + struct object_id *oid) +{ + int ret; + + ret = git_deflate_end_gently(stream); + if (ret != Z_OK) + return ret; + the_hash_algo->final_oid_fn(oid, c); + + return Z_OK; +} + static int write_loose_object(const struct object_id *oid, char *hdr, int hdrlen, const void *buf, unsigned long len, time_t mtime, unsigned flags) @@ -1968,50 +2058,29 @@ static int write_loose_object(const struct object_id *oid, char *hdr, loose_object_path(the_repository, &filename, oid); - fd = create_tmpfile(&tmp_file, filename.buf); - if (fd < 0) { - if (flags & HASH_SILENT) - return -1; - else if (errno == EACCES) - return error(_("insufficient permission for adding an object to repository database %s"), get_object_directory()); - else - return error_errno(_("unable to create temporary file")); - } - - /* Set it up */ - git_deflate_init(&stream, zlib_compression_level); - stream.next_out = compressed; - stream.avail_out = sizeof(compressed); - the_hash_algo->init_fn(&c); - - /* First header.. */ - stream.next_in = (unsigned char *)hdr; - stream.avail_in = hdrlen; - while (git_deflate(&stream, 0) == Z_OK) - ; /* nothing */ - the_hash_algo->update_fn(&c, hdr, hdrlen); + fd = start_loose_object_common(&tmp_file, filename.buf, flags, + &stream, compressed, sizeof(compressed), + &c, hdr, hdrlen); + if (fd < 0) + return -1; /* Then the data itself.. */ stream.next_in = (void *)buf; stream.avail_in = len; do { unsigned char *in0 = stream.next_in; - ret = git_deflate(&stream, Z_FINISH); - the_hash_algo->update_fn(&c, in0, stream.next_in - in0); - if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) - die(_("unable to write loose object file")); - stream.next_out = compressed; - stream.avail_out = sizeof(compressed); + + ret = write_loose_object_common(&c, &stream, 1, in0, fd, + compressed, sizeof(compressed)); } while (ret == Z_OK); if (ret != Z_STREAM_END) die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), ret); - ret = git_deflate_end_gently(&stream); + ret = end_loose_object_common(&c, &stream, ¶no_oid); if (ret != Z_OK) die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), ret); - the_hash_algo->final_oid_fn(¶no_oid, &c); if (!oideq(oid, ¶no_oid)) die(_("confused by unstable object source data for %s"), oid_to_hex(oid)); @@ -2050,6 +2119,110 @@ static int freshen_packed_object(const struct object_id *oid) return 1; } +int stream_loose_object(struct input_stream *in_stream, size_t len, + struct object_id *oid) +{ + int fd, ret, err = 0, flush = 0; + unsigned char compressed[4096]; + git_zstream stream; + git_hash_ctx c; + struct strbuf tmp_file = STRBUF_INIT; + struct strbuf filename = STRBUF_INIT; + int dirlen; + char hdr[MAX_HEADER_LEN]; + int hdrlen; + + if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) + prepare_loose_object_bulk_checkin(); + + /* Since oid is not determined, save tmp file to odb path. */ + strbuf_addf(&filename, "%s/", get_object_directory()); + hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len); + + /* + * Common steps for write_loose_object and stream_loose_object to + * start writing loose objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. + */ + fd = start_loose_object_common(&tmp_file, filename.buf, 0, + &stream, compressed, sizeof(compressed), + &c, hdr, hdrlen); + if (fd < 0) { + err = -1; + goto cleanup; + } + + /* Then the data itself.. */ + do { + unsigned char *in0 = stream.next_in; + + if (!stream.avail_in && !in_stream->is_finished) { + const void *in = in_stream->read(in_stream, &stream.avail_in); + stream.next_in = (void *)in; + in0 = (unsigned char *)in; + /* All data has been read. */ + if (in_stream->is_finished) + flush = 1; + } + ret = write_loose_object_common(&c, &stream, flush, in0, fd, + compressed, sizeof(compressed)); + /* + * Unlike write_loose_object(), we do not have the entire + * buffer. If we get Z_BUF_ERROR due to too few input bytes, + * then we'll replenish them in the next input_stream->read() + * call when we loop. + */ + } while (ret == Z_OK || ret == Z_BUF_ERROR); + + if (stream.total_in != len + hdrlen) + die(_("write stream object %ld != %"PRIuMAX), stream.total_in, + (uintmax_t)len + hdrlen); + + /* + * Common steps for write_loose_object and stream_loose_object to + * end writing loose oject: + * + * - End the compression of zlib stream. + * - Get the calculated oid. + */ + if (ret != Z_STREAM_END) + die(_("unable to stream deflate new object (%d)"), ret); + ret = end_loose_object_common(&c, &stream, oid); + if (ret != Z_OK) + die(_("deflateEnd on stream object failed (%d)"), ret); + close_loose_object(fd, tmp_file.buf); + + if (freshen_packed_object(oid) || freshen_loose_object(oid)) { + unlink_or_warn(tmp_file.buf); + goto cleanup; + } + + loose_object_path(the_repository, &filename, oid); + + /* We finally know the object path, and create the missing dir. */ + dirlen = directory_size(filename.buf); + if (dirlen) { + struct strbuf dir = STRBUF_INIT; + strbuf_add(&dir, filename.buf, dirlen); + + if (mkdir_in_gitdir(dir.buf) && errno != EEXIST) { + err = error_errno(_("unable to create directory %s"), dir.buf); + strbuf_release(&dir); + goto cleanup; + } + strbuf_release(&dir); + } + + err = finalize_object_file(tmp_file.buf, filename.buf); +cleanup: + strbuf_release(&tmp_file); + strbuf_release(&filename); + return err; +} + int write_object_file_flags(const void *buf, unsigned long len, enum object_type type, struct object_id *oid, unsigned flags) diff --git a/object-name.c b/object-name.c index 4d2746574c..2dd1a0f56e 100644 --- a/object-name.c +++ b/object-name.c @@ -1306,7 +1306,8 @@ struct handle_one_ref_cb { }; static int handle_one_ref(const char *path, const struct object_id *oid, - int flag, void *cb_data) + int flag UNUSED, + void *cb_data) { struct handle_one_ref_cb *cb = cb_data; struct commit_list **list = cb->list; @@ -1384,8 +1385,11 @@ struct grab_nth_branch_switch_cbdata { struct strbuf *sb; }; -static int grab_nth_branch_switch(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, +static int grab_nth_branch_switch(struct object_id *ooid UNUSED, + struct object_id *noid UNUSED, + const char *email UNUSED, + timestamp_t timestamp UNUSED, + int tz UNUSED, const char *message, void *cb_data) { struct grab_nth_branch_switch_cbdata *cb = cb_data; diff --git a/object-store.h b/object-store.h index 539ea43904..1be57abaf1 100644 --- a/object-store.h +++ b/object-store.h @@ -46,6 +46,12 @@ struct object_directory { char *path; }; +struct input_stream { + const void *(*read)(struct input_stream *, unsigned long *len); + void *data; + int is_finished; +}; + KHASH_INIT(odb_path_map, const char * /* key: odb_path */, struct object_directory *, 1, fspathhash, fspatheq) @@ -135,7 +141,7 @@ struct packed_git { struct multi_pack_index; -static inline int pack_map_entry_cmp(const void *unused_cmp_data, +static inline int pack_map_entry_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *entry, const struct hashmap_entry *entry2, const void *keydata) @@ -269,6 +275,8 @@ static inline int write_object_file(const void *buf, unsigned long len, int write_object_file_literally(const void *buf, unsigned long len, const char *type, struct object_id *oid, unsigned flags); +int stream_loose_object(struct input_stream *in_stream, size_t len, + struct object_id *oid); /* * Add an object file to the in-memory object store, without writing it diff --git a/object.c b/object.c index 588b8156f1..8a74eb85e9 100644 --- a/object.c +++ b/object.c @@ -233,7 +233,8 @@ struct object *parse_object_buffer(struct repository *r, const struct object_id if (commit) { if (parse_commit_buffer(r, commit, buffer, size, 1)) return NULL; - if (!get_cached_commit_buffer(r, commit, NULL)) { + if (save_commit_buffer && + !get_cached_commit_buffer(r, commit, NULL)) { set_commit_buffer(r, commit, buffer, size); *eaten_p = 1; } @@ -263,8 +264,11 @@ struct object *parse_object_or_die(const struct object_id *oid, die(_("unable to parse object: %s"), name ? name : oid_to_hex(oid)); } -struct object *parse_object(struct repository *r, const struct object_id *oid) +struct object *parse_object_with_flags(struct repository *r, + const struct object_id *oid, + enum parse_object_flags flags) { + int skip_hash = !!(flags & PARSE_OBJECT_SKIP_HASH_CHECK); unsigned long size; enum object_type type; int eaten; @@ -276,10 +280,16 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) if (obj && obj->parsed) return obj; + if (skip_hash) { + struct commit *commit = lookup_commit_in_graph(r, repl); + if (commit) + return &commit->object; + } + if ((obj && obj->type == OBJ_BLOB && repo_has_object_file(r, oid)) || (!obj && repo_has_object_file(r, oid) && oid_object_info(r, oid, NULL) == OBJ_BLOB)) { - if (stream_object_signature(r, repl) < 0) { + if (!skip_hash && stream_object_signature(r, repl) < 0) { error(_("hash mismatch %s"), oid_to_hex(oid)); return NULL; } @@ -289,7 +299,8 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) buffer = repo_read_object_file(r, oid, &type, &size); if (buffer) { - if (check_object_signature(r, repl, buffer, size, type) < 0) { + if (!skip_hash && + check_object_signature(r, repl, buffer, size, type) < 0) { free(buffer); error(_("hash mismatch %s"), oid_to_hex(repl)); return NULL; @@ -304,6 +315,11 @@ struct object *parse_object(struct repository *r, const struct object_id *oid) return NULL; } +struct object *parse_object(struct repository *r, const struct object_id *oid) +{ + return parse_object_with_flags(r, oid, 0); +} + struct object_list *object_list_insert(struct object *item, struct object_list **list_p) { diff --git a/object.h b/object.h index a2219464c2..31ebe11458 100644 --- a/object.h +++ b/object.h @@ -59,7 +59,7 @@ struct object_array { /* * object flag allocation: - * revision.h: 0---------10 15 23------26 + * revision.h: 0---------10 15 23------27 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 * walker.c: 0-2 @@ -128,7 +128,13 @@ void *object_as_type(struct object *obj, enum object_type type, int quiet); * * Returns NULL if the object is missing or corrupt. */ +enum parse_object_flags { + PARSE_OBJECT_SKIP_HASH_CHECK = 1 << 0, +}; struct object *parse_object(struct repository *r, const struct object_id *oid); +struct object *parse_object_with_flags(struct repository *r, + const struct object_id *oid, + enum parse_object_flags flags); /* * Like parse_object, but will die() instead of returning NULL. If the diff --git a/oidmap.c b/oidmap.c index 286a04a53c..49965fe856 100644 --- a/oidmap.c +++ b/oidmap.c @@ -1,7 +1,7 @@ #include "cache.h" #include "oidmap.h" -static int oidmap_neq(const void *hashmap_cmp_fn_data, +static int oidmap_neq(const void *hashmap_cmp_fn_data UNUSED, const struct hashmap_entry *e1, const struct hashmap_entry *e2, const void *keydata) diff --git a/oss-fuzz/.gitignore b/oss-fuzz/.gitignore new file mode 100644 index 0000000000..9acb74412e --- /dev/null +++ b/oss-fuzz/.gitignore @@ -0,0 +1,3 @@ +fuzz-commit-graph +fuzz-pack-headers +fuzz-pack-idx diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c new file mode 100644 index 0000000000..914026f5d8 --- /dev/null +++ b/oss-fuzz/fuzz-commit-graph.c @@ -0,0 +1,27 @@ +#include "commit-graph.h" +#include "repository.h" + +struct commit_graph *parse_commit_graph(struct repo_settings *s, + void *graph_map, size_t graph_size); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct commit_graph *g; + + initialize_the_repository(); + /* + * Initialize the_repository with commit-graph settings that would + * normally be read from the repository's gitdir. We want to avoid + * touching the disk to keep the individual fuzz-test cases as fast as + * possible. + */ + 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); + repo_clear(the_repository); + free_commit_graph(g); + + return 0; +} diff --git a/fuzz-pack-headers.c b/oss-fuzz/fuzz-pack-headers.c similarity index 100% rename from fuzz-pack-headers.c rename to oss-fuzz/fuzz-pack-headers.c diff --git a/fuzz-pack-idx.c b/oss-fuzz/fuzz-pack-idx.c similarity index 100% rename from fuzz-pack-idx.c rename to oss-fuzz/fuzz-pack-idx.c diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index c43375bd34..a213f5eddc 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -649,21 +649,18 @@ static const struct object_id *oid_access(size_t pos, const void *table) } static void write_selected_commits_v1(struct hashfile *f, - struct pack_idx_entry **index, - uint32_t index_nr) + uint32_t *commit_positions, + off_t *offsets) { int i; 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 (offsets) + offsets[i] = hashfile_total(f); - if (commit_pos < 0) - BUG("trying to write commit not in index"); - - hashwrite_be32(f, commit_pos); + hashwrite_be32(f, commit_positions[i]); hashwrite_u8(f, stored->xor_offset); hashwrite_u8(f, stored->flags); @@ -671,6 +668,79 @@ 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]; + + if (a > b) + return 1; + else if (a < b) + return -1; + + return 0; +} + +static void write_lookup_table(struct hashfile *f, + uint32_t *commit_positions, + off_t *offsets) +{ + uint32_t i; + uint32_t *table, *table_inv; + + ALLOC_ARRAY(table, writer.selected_nr); + ALLOC_ARRAY(table_inv, writer.selected_nr); + + for (i = 0; i < writer.selected_nr; i++) + table[i] = i; + + /* + * At the end of this sort table[j] = i means that the i'th + * 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); + + /* 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++) + 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]]; + uint32_t xor_offset = selected->xor_offset; + uint32_t xor_row; + + if (xor_offset) { + /* + * xor_index stores the index (in the bitmap entries) + * of the corresponding xor bitmap. But we need to convert + * this index into lookup table's index. So, table_inv[xor_index] + * gives us the index position w.r.t. the lookup table. + * + * If "k = table[i] - xor_offset" then the xor base is the k'th + * bitmap. `table_inv[k]` gives us the position of that bitmap + * in the lookup table. + */ + uint32_t xor_index = table[i] - xor_offset; + xor_row = table_inv[xor_index]; + } else { + xor_row = 0xffffffff; + } + + hashwrite_be32(f, commit_positions[table[i]]); + hashwrite_be64(f, (uint64_t)offsets[table[i]]); + hashwrite_be32(f, xor_row); + } + trace2_region_leave("pack-bitmap-write", "writing_lookup_table", the_repository); + + free(table); + free(table_inv); +} + static void write_hash_cache(struct hashfile *f, struct pack_idx_entry **index, uint32_t index_nr) @@ -683,7 +753,7 @@ static void write_hash_cache(struct hashfile *f, } } -void bitmap_writer_set_checksum(unsigned char *sha1) +void bitmap_writer_set_checksum(const unsigned char *sha1) { hashcpy(writer.pack_checksum, sha1); } @@ -697,6 +767,9 @@ 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; struct bitmap_disk_header header; @@ -715,7 +788,26 @@ void bitmap_writer_finish(struct pack_idx_entry **index, dump_bitmap(f, writer.trees); dump_bitmap(f, writer.blobs); dump_bitmap(f, writer.tags); - write_selected_commits_v1(f, index, index_nr); + + 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); + + if (commit_pos < 0) + BUG(_("trying to write commit not in index")); + + commit_positions[i] = commit_pos; + } + + write_selected_commits_v1(f, commit_positions, offsets); + + if (options & BITMAP_OPT_LOOKUP_TABLE) + write_lookup_table(f, commit_positions, offsets); if (options & BITMAP_OPT_HASH_CACHE) write_hash_cache(f, index, index_nr); @@ -730,4 +822,6 @@ 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 36134222d7..440407f1be 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -1,5 +1,6 @@ #include "cache.h" #include "commit.h" +#include "strbuf.h" #include "tag.h" #include "diff.h" #include "revision.h" @@ -82,6 +83,12 @@ struct bitmap_index { /* The checksum of the packfile or MIDX; points into map. */ const unsigned char *checksum; + /* + * If not NULL, this point into the commit table extension + * (within the memory mapped region `map`). + */ + unsigned char *table_lookup; + /* * Extended index. * @@ -138,7 +145,7 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index) index->map_size - index->map_pos); if (bitmap_size < 0) { - error("Failed to load bitmap index (corrupted?)"); + error(_("failed to load bitmap index (corrupted?)")); ewah_pool_free(b); return NULL; } @@ -160,14 +167,14 @@ static int load_bitmap_header(struct bitmap_index *index) size_t header_size = sizeof(*header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz; if (index->map_size < header_size + the_hash_algo->rawsz) - return error("Corrupted bitmap index (too small)"); + return error(_("corrupted bitmap index (too small)")); if (memcmp(header->magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE)) != 0) - return error("Corrupted bitmap index file (wrong header)"); + return error(_("corrupted bitmap index file (wrong header)")); index->version = ntohs(header->version); if (index->version != 1) - return error("Unsupported version for bitmap index file (%d)", index->version); + return error(_("unsupported version '%d' for bitmap index file"), index->version); /* Parse known bitmap format options */ { @@ -176,15 +183,25 @@ static int load_bitmap_header(struct bitmap_index *index) unsigned char *index_end = index->map + index->map_size - the_hash_algo->rawsz; if ((flags & BITMAP_OPT_FULL_DAG) == 0) - return error("Unsupported options for bitmap index file " + BUG("unsupported options for bitmap index file " "(Git requires BITMAP_OPT_FULL_DAG)"); if (flags & BITMAP_OPT_HASH_CACHE) { if (cache_size > index_end - index->map - header_size) - return error("corrupted bitmap index file (too short to fit hash cache)"); + return error(_("corrupted bitmap index file (too short to fit hash cache)")); index->hashes = (void *)(index_end - cache_size); index_end -= cache_size; } + + if (flags & BITMAP_OPT_LOOKUP_TABLE) { + size_t table_size = st_mult(ntohl(header->entry_count), + BITMAP_LOOKUP_TABLE_TRIPLET_WIDTH); + if (table_size > index_end - index->map - header_size) + return error(_("corrupted bitmap index file (too short to fit lookup table)")); + if (git_env_bool("GIT_TEST_READ_COMMIT_TABLE", 1)) + index->table_lookup = (void *)(index_end - table_size); + index_end -= table_size; + } } index->entry_count = ntohl(header->entry_count); @@ -211,11 +228,13 @@ static struct stored_bitmap *store_bitmap(struct bitmap_index *index, hash_pos = kh_put_oid_map(index->bitmaps, stored->oid, &ret); - /* a 0 return code means the insertion succeeded with no changes, - * because the SHA1 already existed on the map. this is bad, there - * shouldn't be duplicated commits in the index */ + /* + * A 0 return code means the insertion succeeded with no changes, + * because the SHA1 already existed on the map. This is bad, there + * shouldn't be duplicated commits in the index. + */ if (ret == 0) { - error("Duplicate entry in bitmap index: %s", oid_to_hex(oid)); + error(_("duplicate entry in bitmap index: '%s'"), oid_to_hex(oid)); return NULL; } @@ -259,14 +278,14 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) struct object_id oid; if (index->map_size - index->map_pos < 6) - return error("corrupt ewah bitmap: truncated header for entry %d", i); + return error(_("corrupt ewah bitmap: truncated header for entry %d"), i); commit_idx_pos = read_be32(index->map, &index->map_pos); xor_offset = read_u8(index->map, &index->map_pos); flags = read_u8(index->map, &index->map_pos); if (nth_bitmap_object_oid(index, &oid, commit_idx_pos) < 0) - return error("corrupt ewah bitmap: commit index %u out of range", + return error(_("corrupt ewah bitmap: commit index %u out of range"), (unsigned)commit_idx_pos); bitmap = read_bitmap_1(index); @@ -274,13 +293,13 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) return -1; if (xor_offset > MAX_XOR_OFFSET || xor_offset > i) - return error("Corrupted bitmap pack index"); + return error(_("corrupted bitmap pack index")); if (xor_offset > 0) { xor_bitmap = recent_bitmaps[(i - xor_offset) % MAX_XOR_OFFSET]; if (!xor_bitmap) - return error("Invalid XOR offset in bitmap pack index"); + return error(_("invalid XOR offset in bitmap pack index")); } recent_bitmaps[i % MAX_XOR_OFFSET] = store_bitmap( @@ -313,17 +332,21 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, struct multi_pack_index *midx) { struct stat st; - char *idx_name = midx_bitmap_filename(midx); - int fd = git_open(idx_name); + char *bitmap_name = midx_bitmap_filename(midx); + int fd = git_open(bitmap_name); uint32_t i; struct packed_git *preferred; - free(idx_name); - - if (fd < 0) + if (fd < 0) { + if (errno != ENOENT) + warning_errno("cannot open '%s'", bitmap_name); + free(bitmap_name); return -1; + } + free(bitmap_name); if (fstat(fd, &st)) { + error_errno(_("cannot fstat bitmap file")); close(fd); return -1; } @@ -332,7 +355,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, struct strbuf buf = STRBUF_INIT; get_midx_filename(&buf, midx->object_dir); /* ignore extra bitmap file; we can only handle one */ - warning("ignoring extra bitmap file: %s", buf.buf); + warning(_("ignoring extra bitmap file: '%s'"), buf.buf); close(fd); strbuf_release(&buf); return -1; @@ -348,8 +371,10 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, if (load_bitmap_header(bitmap_git) < 0) goto cleanup; - if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum)) + if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum)) { + error(_("checksum doesn't match in MIDX and bitmap")); goto cleanup; + } if (load_midx_revindex(bitmap_git->midx) < 0) { warning(_("multi-pack bitmap is missing required reverse index")); @@ -384,26 +409,31 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git { int fd; struct stat st; - char *idx_name; + char *bitmap_name; if (open_pack_index(packfile)) return -1; - idx_name = pack_bitmap_filename(packfile); - fd = git_open(idx_name); - free(idx_name); + bitmap_name = pack_bitmap_filename(packfile); + fd = git_open(bitmap_name); - if (fd < 0) + if (fd < 0) { + if (errno != ENOENT) + warning_errno("cannot open '%s'", bitmap_name); + free(bitmap_name); return -1; + } + free(bitmap_name); if (fstat(fd, &st)) { + error_errno(_("cannot fstat bitmap file")); close(fd); return -1; } if (bitmap_git->pack || bitmap_git->midx) { /* ignore extra bitmap file; we can only handle one */ - warning("ignoring extra bitmap file: %s", packfile->pack_name); + warning(_("ignoring extra bitmap file: '%s'"), packfile->pack_name); close(fd); return -1; } @@ -470,7 +500,7 @@ static int load_bitmap(struct bitmap_index *bitmap_git) !(bitmap_git->tags = read_bitmap_1(bitmap_git))) goto failed; - if (load_bitmap_entries_v1(bitmap_git) < 0) + if (!bitmap_git->table_lookup && load_bitmap_entries_v1(bitmap_git) < 0) goto failed; return 0; @@ -508,15 +538,16 @@ static int open_pack_bitmap(struct repository *r, static int open_midx_bitmap(struct repository *r, struct bitmap_index *bitmap_git) { + int ret = -1; struct multi_pack_index *midx; assert(!bitmap_git->map); for (midx = get_multi_pack_index(r); midx; midx = midx->next) { if (!open_midx_bitmap_1(bitmap_git, midx)) - return 0; + ret = 0; } - return -1; + return ret; } static int open_bitmap(struct repository *r, @@ -557,13 +588,255 @@ struct include_data { struct bitmap *seen; }; +struct bitmap_lookup_table_triplet { + uint32_t commit_pos; + uint64_t offset; + uint32_t xor_row; +}; + +struct bitmap_lookup_table_xor_item { + struct object_id oid; + uint64_t offset; +}; + +/* + * Given a `triplet` struct pointer and pointer `p`, this + * function reads the triplet beginning at `p` into the struct. + * Note that this function assumes that there is enough memory + * left for filling the `triplet` struct from `p`. + */ +static int bitmap_lookup_table_get_triplet_by_pointer(struct bitmap_lookup_table_triplet *triplet, + const unsigned char *p) +{ + if (!triplet) + return -1; + + triplet->commit_pos = get_be32(p); + p += sizeof(uint32_t); + triplet->offset = get_be64(p); + p += sizeof(uint64_t); + triplet->xor_row = get_be32(p); + return 0; +} + +/* + * This function gets the raw triplet from `row`'th row in the + * lookup table and fills that data to the `triplet`. + */ +static int bitmap_lookup_table_get_triplet(struct bitmap_index *bitmap_git, + uint32_t pos, + struct bitmap_lookup_table_triplet *triplet) +{ + unsigned char *p = NULL; + if (pos >= bitmap_git->entry_count) + return error(_("corrupt bitmap lookup table: triplet position out of index")); + + p = bitmap_git->table_lookup + st_mult(pos, BITMAP_LOOKUP_TABLE_TRIPLET_WIDTH); + + return bitmap_lookup_table_get_triplet_by_pointer(triplet, p); +} + +/* + * Searches for a matching triplet. `commit_pos` is a pointer + * to the wanted commit position value. `table_entry` points to + * a triplet in lookup table. The first 4 bytes of each + * triplet (pointed by `table_entry`) are compared with `*commit_pos`. + */ +static int triplet_cmp(const void *commit_pos, const void *table_entry) +{ + + uint32_t a = *(uint32_t *)commit_pos; + uint32_t b = get_be32(table_entry); + if (a > b) + return 1; + else if (a < b) + return -1; + + return 0; +} + +static uint32_t bitmap_bsearch_pos(struct bitmap_index *bitmap_git, + struct object_id *oid, + uint32_t *result) +{ + int found; + + if (bitmap_is_midx(bitmap_git)) + found = bsearch_midx(oid, bitmap_git->midx, result); + else + found = bsearch_pack(oid, bitmap_git->pack, result); + + return found; +} + +/* + * `bsearch_triplet_by_pos` function searches for the raw triplet + * having commit position same as `commit_pos` and fills `triplet` + * object from the raw triplet. Returns 1 on success and 0 on + * failure. + */ +static int bitmap_bsearch_triplet_by_pos(uint32_t commit_pos, + struct bitmap_index *bitmap_git, + struct bitmap_lookup_table_triplet *triplet) +{ + unsigned char *p = bsearch(&commit_pos, bitmap_git->table_lookup, bitmap_git->entry_count, + BITMAP_LOOKUP_TABLE_TRIPLET_WIDTH, triplet_cmp); + + if (!p) + return -1; + + return bitmap_lookup_table_get_triplet_by_pointer(triplet, p); +} + +static struct stored_bitmap *lazy_bitmap_for_commit(struct bitmap_index *bitmap_git, + struct commit *commit) +{ + uint32_t commit_pos, xor_row; + uint64_t offset; + int flags; + struct bitmap_lookup_table_triplet triplet; + struct object_id *oid = &commit->object.oid; + struct ewah_bitmap *bitmap; + struct stored_bitmap *xor_bitmap = NULL; + const int bitmap_header_size = 6; + static struct bitmap_lookup_table_xor_item *xor_items = NULL; + static size_t xor_items_nr = 0, xor_items_alloc = 0; + static int is_corrupt = 0; + int xor_flags; + khiter_t hash_pos; + struct bitmap_lookup_table_xor_item *xor_item; + + if (is_corrupt) + return NULL; + + if (!bitmap_bsearch_pos(bitmap_git, oid, &commit_pos)) + return NULL; + + if (bitmap_bsearch_triplet_by_pos(commit_pos, bitmap_git, &triplet) < 0) + return NULL; + + xor_items_nr = 0; + offset = triplet.offset; + xor_row = triplet.xor_row; + + while (xor_row != 0xffffffff) { + ALLOC_GROW(xor_items, xor_items_nr + 1, xor_items_alloc); + + if (xor_items_nr + 1 >= bitmap_git->entry_count) { + error(_("corrupt bitmap lookup table: xor chain exceeds entry count")); + goto corrupt; + } + + if (bitmap_lookup_table_get_triplet(bitmap_git, xor_row, &triplet) < 0) + goto corrupt; + + xor_item = &xor_items[xor_items_nr]; + xor_item->offset = triplet.offset; + + if (nth_bitmap_object_oid(bitmap_git, &xor_item->oid, triplet.commit_pos) < 0) { + error(_("corrupt bitmap lookup table: commit index %u out of range"), + triplet.commit_pos); + goto corrupt; + } + + hash_pos = kh_get_oid_map(bitmap_git->bitmaps, xor_item->oid); + + /* + * If desired bitmap is already stored, we don't need + * to iterate further. Because we know that bitmaps + * that are needed to be parsed to parse this bitmap + * has already been stored. So, assign this stored bitmap + * to the xor_bitmap. + */ + if (hash_pos < kh_end(bitmap_git->bitmaps) && + (xor_bitmap = kh_value(bitmap_git->bitmaps, hash_pos))) + break; + xor_items_nr++; + xor_row = triplet.xor_row; + } + + while (xor_items_nr) { + xor_item = &xor_items[xor_items_nr - 1]; + bitmap_git->map_pos = xor_item->offset; + if (bitmap_git->map_size - bitmap_git->map_pos < bitmap_header_size) { + error(_("corrupt ewah bitmap: truncated header for bitmap of commit \"%s\""), + oid_to_hex(&xor_item->oid)); + goto corrupt; + } + + bitmap_git->map_pos += sizeof(uint32_t) + sizeof(uint8_t); + xor_flags = read_u8(bitmap_git->map, &bitmap_git->map_pos); + bitmap = read_bitmap_1(bitmap_git); + + if (!bitmap) + goto corrupt; + + xor_bitmap = store_bitmap(bitmap_git, bitmap, &xor_item->oid, xor_bitmap, xor_flags); + xor_items_nr--; + } + + bitmap_git->map_pos = offset; + if (bitmap_git->map_size - bitmap_git->map_pos < bitmap_header_size) { + error(_("corrupt ewah bitmap: truncated header for bitmap of commit \"%s\""), + oid_to_hex(oid)); + goto corrupt; + } + + /* + * Don't bother reading the commit's index position or its xor + * offset: + * + * - The commit's index position is irrelevant to us, since + * load_bitmap_entries_v1 only uses it to learn the object + * id which is used to compute the hashmap's key. We already + * have an object id, so no need to look it up again. + * + * - The xor_offset is unusable for us, since it specifies how + * many entries previous to ours we should look at. This + * makes sense when reading the bitmaps sequentially (as in + * load_bitmap_entries_v1()), since we can keep track of + * each bitmap as we read them. + * + * But it can't work for us, since the bitmap's don't have a + * fixed size. So we learn the position of the xor'd bitmap + * from the commit table (and resolve it to a bitmap in the + * above if-statement). + * + * Instead, we can skip ahead and immediately read the flags and + * ewah bitmap. + */ + bitmap_git->map_pos += sizeof(uint32_t) + sizeof(uint8_t); + flags = read_u8(bitmap_git->map, &bitmap_git->map_pos); + bitmap = read_bitmap_1(bitmap_git); + + if (!bitmap) + goto corrupt; + + return store_bitmap(bitmap_git, bitmap, oid, xor_bitmap, flags); + +corrupt: + free(xor_items); + is_corrupt = 1; + return NULL; +} + struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git, struct commit *commit) { khiter_t hash_pos = kh_get_oid_map(bitmap_git->bitmaps, commit->object.oid); - if (hash_pos >= kh_end(bitmap_git->bitmaps)) - return NULL; + if (hash_pos >= kh_end(bitmap_git->bitmaps)) { + struct stored_bitmap *bitmap = NULL; + if (!bitmap_git->table_lookup) + return NULL; + + /* this is a fairly hot codepath - no trace2_region please */ + /* NEEDSWORK: cache misses aren't recorded */ + bitmap = lazy_bitmap_for_commit(bitmap_git, commit); + if (!bitmap) + return NULL; + return lookup_stored_bitmap(bitmap); + } return lookup_stored_bitmap(kh_value(bitmap_git->bitmaps, hash_pos)); } @@ -831,7 +1104,7 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git, revs->include_check_data = &incdata; if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); show_data.bitmap_git = bitmap_git; show_data.base = base; @@ -1640,15 +1913,15 @@ static void test_bitmap_type(struct bitmap_test_data *tdata, } if (bitmap_type == OBJ_NONE) - die("object %s not found in type bitmaps", + die(_("object '%s' not found in type bitmaps"), oid_to_hex(&obj->oid)); if (bitmaps_nr > 1) - die("object %s does not have a unique type", + die(_("object '%s' does not have a unique type"), oid_to_hex(&obj->oid)); if (bitmap_type != obj->type) - die("object %s: real type %s, expected: %s", + die(_("object '%s': real type '%s', expected: '%s'"), oid_to_hex(&obj->oid), type_name(obj->type), type_name(bitmap_type)); @@ -1662,7 +1935,7 @@ static void test_show_object(struct object *object, const char *name, bitmap_pos = bitmap_position(tdata->bitmap_git, &object->oid); if (bitmap_pos < 0) - die("Object not in bitmap: %s\n", oid_to_hex(&object->oid)); + die(_("object not in bitmap: '%s'"), oid_to_hex(&object->oid)); test_bitmap_type(tdata, object, bitmap_pos); bitmap_set(tdata->base, bitmap_pos); @@ -1677,7 +1950,7 @@ static void test_show_commit(struct commit *commit, void *data) bitmap_pos = bitmap_position(tdata->bitmap_git, &commit->object.oid); if (bitmap_pos < 0) - die("Object not in bitmap: %s\n", oid_to_hex(&commit->object.oid)); + die(_("object not in bitmap: '%s'"), oid_to_hex(&commit->object.oid)); test_bitmap_type(tdata, &commit->object, bitmap_pos); bitmap_set(tdata->base, bitmap_pos); @@ -1694,26 +1967,28 @@ void test_bitmap_walk(struct rev_info *revs) struct ewah_bitmap *bm; if (!(bitmap_git = prepare_bitmap_git(revs->repo))) - die("failed to load bitmap indexes"); + die(_("failed to load bitmap indexes")); if (revs->pending.nr != 1) - die("you must specify exactly one commit to test"); + die(_("you must specify exactly one commit to test")); - fprintf(stderr, "Bitmap v%d test (%d entries loaded)\n", - bitmap_git->version, bitmap_git->entry_count); + fprintf_ln(stderr, "Bitmap v%d test (%d entries%s)", + bitmap_git->version, + bitmap_git->entry_count, + bitmap_git->table_lookup ? "" : " loaded"); root = revs->pending.objects[0].item; bm = bitmap_for_commit(bitmap_git, (struct commit *)root); if (bm) { - fprintf(stderr, "Found bitmap for %s. %d bits / %08x checksum\n", + fprintf_ln(stderr, "Found bitmap for '%s'. %d bits / %08x checksum", oid_to_hex(&root->oid), (int)bm->bit_size, ewah_checksum(bm)); result = ewah_to_bitmap(bm); } if (!result) - die("Commit %s doesn't have an indexed bitmap", oid_to_hex(&root->oid)); + die(_("commit '%s' doesn't have an indexed bitmap"), oid_to_hex(&root->oid)); revs->tag_objects = 1; revs->tree_objects = 1; @@ -1722,7 +1997,7 @@ void test_bitmap_walk(struct rev_info *revs) result_popcnt = bitmap_popcount(result); if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); tdata.bitmap_git = bitmap_git; tdata.base = bitmap_new(); @@ -1738,9 +2013,9 @@ void test_bitmap_walk(struct rev_info *revs) stop_progress(&tdata.prg); if (bitmap_equals(result, tdata.base)) - fprintf(stderr, "OK!\n"); + fprintf_ln(stderr, "OK!"); else - die("mismatch in bitmap results"); + die(_("mismatch in bitmap results")); bitmap_free(result); bitmap_free(tdata.base); @@ -1753,15 +2028,24 @@ void test_bitmap_walk(struct rev_info *revs) int test_bitmap_commits(struct repository *r) { - struct bitmap_index *bitmap_git = prepare_bitmap_git(r); struct object_id oid; MAYBE_UNUSED void *value; + struct bitmap_index *bitmap_git = prepare_bitmap_git(r); if (!bitmap_git) - die("failed to load bitmap indexes"); + die(_("failed to load bitmap indexes")); + + /* + * As this function is only used to print bitmap selected + * commits, we don't have to read the commit table. + */ + if (bitmap_git->table_lookup) { + if (load_bitmap_entries_v1(bitmap_git) < 0) + die(_("failed to load bitmap indexes")); + } kh_foreach(bitmap_git->bitmaps, oid, value, { - printf("%s\n", oid_to_hex(&oid)); + printf_ln("%s", oid_to_hex(&oid)); }); free_bitmap_index(bitmap_git); @@ -1786,7 +2070,7 @@ int test_bitmap_hashes(struct repository *r) nth_bitmap_object_oid(bitmap_git, &oid, index_pos); - printf("%s %"PRIu32"\n", + printf_ln("%s %"PRIu32"", oid_to_hex(&oid), get_be32(bitmap_git->hashes + index_pos)); } @@ -1948,7 +2232,7 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, struct object_id oid; nth_midxed_object_oid(&oid, bitmap_git->midx, midx_pos); - die(_("could not find %s in pack %s at offset %"PRIuMAX), + die(_("could not find '%s' in pack '%s' at offset %"PRIuMAX), oid_to_hex(&oid), pack->pack_name, (uintmax_t)offset); @@ -1984,7 +2268,7 @@ static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git) continue; if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0) - die(_("unable to get disk usage of %s"), + die(_("unable to get disk usage of '%s'"), oid_to_hex(&obj->oid)); total += object_size; diff --git a/pack-bitmap.h b/pack-bitmap.h index 3d3ddd7734..f0180b5276 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -23,9 +23,19 @@ struct bitmap_disk_header { #define NEEDS_BITMAP (1u<<22) +/* + * The width in bytes of a single triplet in the lookup table + * extension: + * (commit_pos, offset, xor_row) + * + * whose fields ar 32-, 64-, 32- bits wide, respectively. + */ +#define BITMAP_LOOKUP_TABLE_TRIPLET_WIDTH (16) + enum pack_bitmap_opts { - BITMAP_OPT_FULL_DAG = 1, - BITMAP_OPT_HASH_CACHE = 4, + BITMAP_OPT_FULL_DAG = 0x1, + BITMAP_OPT_HASH_CACHE = 0x4, + BITMAP_OPT_LOOKUP_TABLE = 0x10, }; enum pack_bitmap_flags { @@ -75,7 +85,7 @@ 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(unsigned char *sha1); +void bitmap_writer_set_checksum(const unsigned char *sha1); void bitmap_writer_build_type_index(struct packing_data *to_pack, struct pack_idx_entry **index, uint32_t index_nr); diff --git a/pack-revindex.h b/pack-revindex.h index 74f4eae668..4974e75eb4 100644 --- a/pack-revindex.h +++ b/pack-revindex.h @@ -22,7 +22,7 @@ * * - pack position refers to an object's position within a non-existent pack * described by the MIDX. The pack structure is described in - * Documentation/technical/pack-format.txt. + * gitformat-pack(5). * * It is effectively a concatanation of all packs in the MIDX (ordered by * their numeric ID within the MIDX) in their original order within each diff --git a/packfile.c b/packfile.c index 75c254e0e0..c0d7dd93f4 100644 --- a/packfile.c +++ b/packfile.c @@ -941,20 +941,10 @@ unsigned long repo_approximate_object_count(struct repository *r) return r->objects->approximate_object_count; } -static void *get_next_packed_git(const void *p) -{ - return ((const struct packed_git *)p)->next; -} - -static void set_next_packed_git(void *p, void *next) -{ - ((struct packed_git *)p)->next = next; -} +DEFINE_LIST_SORT(static, sort_packs, struct packed_git, next); -static int sort_pack(const void *a_, const void *b_) +static int sort_pack(const struct packed_git *a, const struct packed_git *b) { - const struct packed_git *a = a_; - const struct packed_git *b = b_; int st; /* @@ -981,9 +971,7 @@ static int sort_pack(const void *a_, const void *b_) static void rearrange_packed_git(struct repository *r) { - r->objects->packed_git = llist_mergesort( - r->objects->packed_git, get_next_packed_git, - set_next_packed_git, sort_pack); + sort_packs(&r->objects->packed_git, sort_pack); } static void prepare_packed_git_mru(struct repository *r) @@ -1404,7 +1392,7 @@ static int delta_base_cache_key_eq(const struct delta_base_cache_key *a, return a->p == b->p && a->base_offset == b->base_offset; } -static int delta_base_cache_hash_cmp(const void *unused_cmp_data, +static int delta_base_cache_hash_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *va, const struct hashmap_entry *vb, const void *vkey) @@ -2286,7 +2274,8 @@ int is_promisor_object(const struct object_id *oid) if (has_promisor_remote()) { for_each_packed_object(add_promisor_object, &promisor_objects, - FOR_EACH_OBJECT_PROMISOR_ONLY); + FOR_EACH_OBJECT_PROMISOR_ONLY | + FOR_EACH_OBJECT_PACK_ORDER); } promisor_objects_prepared = 1; } diff --git a/pager.c b/pager.c index 5cfe23b025..b66bbff278 100644 --- a/pager.c +++ b/pager.c @@ -38,7 +38,8 @@ static void wait_for_pager_signal(int signo) raise(signo); } -static int core_pager_config(const char *var, const char *value, void *data) +static int core_pager_config(const char *var, const char *value, + void *data UNUSED) { if (!strcmp(var, "core.pager")) return git_config_string(&pager_program, var, value); diff --git a/parse-options.c b/parse-options.c index edf55d3ef5..a1ec932f0f 100644 --- a/parse-options.c +++ b/parse-options.c @@ -324,6 +324,8 @@ static enum parse_opt_result parse_long_opt( const char *rest, *long_name = options->long_name; enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG; + if (options->type == OPTION_SUBCOMMAND) + continue; if (!long_name) continue; @@ -332,7 +334,7 @@ again: rest = NULL; if (!rest) { /* abbreviated? */ - if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && + if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) && !strncmp(long_name, arg, arg_end - arg)) { is_abbreviated: if (abbrev_option && @@ -419,6 +421,19 @@ static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p, return PARSE_OPT_ERROR; } +static enum parse_opt_result parse_subcommand(const char *arg, + const struct option *options) +{ + for (; options->type != OPTION_END; options++) + if (options->type == OPTION_SUBCOMMAND && + !strcmp(options->long_name, arg)) { + *(parse_opt_subcommand_fn **)options->value = options->subcommand_fn; + return PARSE_OPT_SUBCOMMAND; + } + + return PARSE_OPT_UNKNOWN; +} + static void check_typos(const char *arg, const struct option *options) { if (strlen(arg) < 3) @@ -442,6 +457,7 @@ static void check_typos(const char *arg, const struct option *options) static void parse_options_check(const struct option *opts) { char short_opts[128]; + void *subcommand_value = NULL; memset(short_opts, '\0', sizeof(short_opts)); for (; opts->type != OPTION_END; opts++) { @@ -489,6 +505,14 @@ static void parse_options_check(const struct option *opts) "Are you using parse_options_step() directly?\n" "That case is not supported yet."); break; + case OPTION_SUBCOMMAND: + if (!opts->value || !opts->subcommand_fn) + optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function"); + if (!subcommand_value) + subcommand_value = opts->value; + else if (subcommand_value != opts->value) + optbug(opts, "all OPTION_SUBCOMMANDs need the same value"); + break; default: ; /* ok. (usually accepts an argument) */ } @@ -499,6 +523,14 @@ static void parse_options_check(const struct option *opts) BUG_if_bug("invalid 'struct option'"); } +static int has_subcommands(const struct option *options) +{ + for (; options->type != OPTION_END; options++) + if (options->type == OPTION_SUBCOMMAND) + return 1; + return 0; +} + static void parse_options_start_1(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, const struct option *options, @@ -515,7 +547,20 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx, ctx->prefix = prefix; ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0); ctx->flags = flags; - if ((flags & PARSE_OPT_KEEP_UNKNOWN) && + ctx->has_subcommands = has_subcommands(options); + if (!ctx->has_subcommands && (flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) + BUG("Using PARSE_OPT_SUBCOMMAND_OPTIONAL without subcommands"); + if (ctx->has_subcommands) { + if (flags & PARSE_OPT_STOP_AT_NON_OPTION) + BUG("subcommands are incompatible with PARSE_OPT_STOP_AT_NON_OPTION"); + if (!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) { + if (flags & PARSE_OPT_KEEP_UNKNOWN_OPT) + BUG("subcommands are incompatible with PARSE_OPT_KEEP_UNKNOWN_OPT unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL"); + if (flags & PARSE_OPT_KEEP_DASHDASH) + BUG("subcommands are incompatible with PARSE_OPT_KEEP_DASHDASH unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL"); + } + } + if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) && (flags & PARSE_OPT_STOP_AT_NON_OPTION) && !(flags & PARSE_OPT_ONE_SHOT)) BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); @@ -589,6 +634,7 @@ static int show_gitcomp(const struct option *opts, int show_all) int nr_noopts = 0; for (; opts->type != OPTION_END; opts++) { + const char *prefix = "--"; const char *suffix = ""; if (!opts->long_name) @@ -598,6 +644,9 @@ static int show_gitcomp(const struct option *opts, int show_all) continue; switch (opts->type) { + case OPTION_SUBCOMMAND: + prefix = ""; + break; case OPTION_GROUP: continue; case OPTION_STRING: @@ -620,7 +669,8 @@ static int show_gitcomp(const struct option *opts, int show_all) suffix = "="; if (starts_with(opts->long_name, "no-")) nr_noopts++; - printf(" --%s%s", opts->long_name, suffix); + printf("%s%s%s%s", opts == original_opts ? "" : " ", + prefix, opts->long_name, suffix); } show_negated_gitcomp(original_opts, show_all, -1); show_negated_gitcomp(original_opts, show_all, nr_noopts); @@ -743,10 +793,38 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, if (*arg != '-' || !arg[1]) { if (parse_nodash_opt(ctx, arg, options) == 0) continue; - if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION) - return PARSE_OPT_NON_OPTION; - ctx->out[ctx->cpidx++] = ctx->argv[0]; - continue; + if (!ctx->has_subcommands) { + if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION) + return PARSE_OPT_NON_OPTION; + ctx->out[ctx->cpidx++] = ctx->argv[0]; + continue; + } + switch (parse_subcommand(arg, options)) { + case PARSE_OPT_SUBCOMMAND: + return PARSE_OPT_SUBCOMMAND; + case PARSE_OPT_UNKNOWN: + if (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) + /* + * arg is neither a short or long + * option nor a subcommand. Since + * this command has a default + * operation mode, we have to treat + * this arg and all remaining args + * as args meant to that default + * operation mode. + * So we are done parsing. + */ + return PARSE_OPT_DONE; + error(_("unknown subcommand: `%s'"), arg); + usage_with_options(usagestr, options); + case PARSE_OPT_COMPLETE: + case PARSE_OPT_HELP: + case PARSE_OPT_ERROR: + case PARSE_OPT_DONE: + case PARSE_OPT_NON_OPTION: + /* Impossible. */ + BUG("parse_subcommand() cannot return these"); + } } /* lone -h asks for help */ @@ -774,6 +852,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, goto show_usage; goto unknown; case PARSE_OPT_NON_OPTION: + case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_HELP: case PARSE_OPT_COMPLETE: BUG("parse_short_opt() cannot return these"); @@ -799,6 +878,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, *(char *)ctx->argv[0] = '-'; goto unknown; case PARSE_OPT_NON_OPTION: + case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_COMPLETE: case PARSE_OPT_HELP: BUG("parse_short_opt() cannot return these"); @@ -830,6 +910,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, case PARSE_OPT_HELP: goto show_usage; case PARSE_OPT_NON_OPTION: + case PARSE_OPT_SUBCOMMAND: case PARSE_OPT_COMPLETE: BUG("parse_long_opt() cannot return these"); case PARSE_OPT_DONE: @@ -839,7 +920,19 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, unknown: if (ctx->flags & PARSE_OPT_ONE_SHOT) break; - if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN)) + if (ctx->has_subcommands && + (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) && + (ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) { + /* + * Found an unknown option given to a command with + * subcommands that has a default operation mode: + * we treat this option and all remaining args as + * arguments meant to that default operation mode. + * So we are done parsing. + */ + return PARSE_OPT_DONE; + } + if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) return PARSE_OPT_UNKNOWN; ctx->out[ctx->cpidx++] = ctx->argv[0]; ctx->opt = NULL; @@ -884,7 +977,14 @@ int parse_options(int argc, const char **argv, case PARSE_OPT_COMPLETE: exit(0); case PARSE_OPT_NON_OPTION: + case PARSE_OPT_SUBCOMMAND: + break; case PARSE_OPT_DONE: + if (ctx.has_subcommands && + !(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) { + error(_("need a subcommand")); + usage_with_options(usagestr, options); + } break; case PARSE_OPT_UNKNOWN: if (ctx.argv[0][1] == '-') { @@ -1009,6 +1109,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t size_t pos; int pad; + if (opts->type == OPTION_SUBCOMMAND) + continue; if (opts->type == OPTION_GROUP) { fputc('\n', outfile); need_newline = 0; diff --git a/parse-options.h b/parse-options.h index 685fccac13..b6ef86e0d1 100644 --- a/parse-options.h +++ b/parse-options.h @@ -11,6 +11,7 @@ enum parse_opt_type { OPTION_GROUP, OPTION_NUMBER, OPTION_ALIAS, + OPTION_SUBCOMMAND, /* options with no arguments */ OPTION_BIT, OPTION_NEGBIT, @@ -30,10 +31,11 @@ enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1 << 0, PARSE_OPT_STOP_AT_NON_OPTION = 1 << 1, PARSE_OPT_KEEP_ARGV0 = 1 << 2, - PARSE_OPT_KEEP_UNKNOWN = 1 << 3, + PARSE_OPT_KEEP_UNKNOWN_OPT = 1 << 3, PARSE_OPT_NO_INTERNAL_HELP = 1 << 4, PARSE_OPT_ONE_SHOT = 1 << 5, PARSE_OPT_SHELL_EVAL = 1 << 6, + PARSE_OPT_SUBCOMMAND_OPTIONAL = 1 << 7, }; enum parse_opt_option_flags { @@ -56,6 +58,7 @@ enum parse_opt_result { PARSE_OPT_ERROR = -1, /* must be the same as error() */ PARSE_OPT_DONE = 0, /* fixed so that "return 0" works */ PARSE_OPT_NON_OPTION, + PARSE_OPT_SUBCOMMAND, PARSE_OPT_UNKNOWN }; @@ -67,6 +70,9 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, const struct option *opt, const char *arg, int unset); +typedef int parse_opt_subcommand_fn(int argc, const char **argv, + const char *prefix); + /* * `type`:: * holds the type of the option, you must have an OPTION_END last in your @@ -76,7 +82,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * the character to use as a short option name, '\0' if none. * * `long_name`:: - * the long option name, without the leading dashes, NULL if none. + * the long option (without the leading dashes) or subcommand name, + * NULL if none. * * `value`:: * stores pointers to the values to be filled. @@ -93,7 +100,7 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * * `help`:: * the short help associated to what the option does. - * Must never be NULL (except for OPTION_END). + * Must never be NULL (except for OPTION_END and OPTION_SUBCOMMAND). * OPTION_GROUP uses this pointer to store the group header. * Should be wrapped by N_() for translation. * @@ -109,7 +116,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * is last on the command line. If the option is * not last it will require an argument. * Should not be used with PARSE_OPT_OPTARG. - * PARSE_OPT_NODASH: this option doesn't start with a dash. + * PARSE_OPT_NODASH: this option doesn't start with a dash; can only be a + * short option and can't accept arguments. * PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets * (i.e. '') in the help message. * Useful for options with multiple parameters. @@ -130,6 +138,9 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * `ll_callback`:: * pointer to the callback to use for OPTION_LOWLEVEL_CALLBACK * + * `subcommand_fn`:: + * pointer to a function to use for OPTION_SUBCOMMAND. + * It will be put in value when the subcommand is given on the command line. */ struct option { enum parse_opt_type type; @@ -144,6 +155,7 @@ struct option { intptr_t defval; parse_opt_ll_cb *ll_callback; intptr_t extra; + parse_opt_subcommand_fn *subcommand_fn; }; #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \ @@ -205,6 +217,14 @@ struct option { #define OPT_ALIAS(s, l, source_long_name) \ { OPTION_ALIAS, (s), (l), (source_long_name) } +#define OPT_SUBCOMMAND_F(l, v, fn, f) { \ + .type = OPTION_SUBCOMMAND, \ + .long_name = (l), \ + .value = (v), \ + .flags = (f), \ + .subcommand_fn = (fn) } +#define OPT_SUBCOMMAND(l, v, fn) OPT_SUBCOMMAND_F((l), (v), (fn), 0) + /* * parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. argv0 is assumed program name and @@ -294,6 +314,7 @@ struct parse_opt_ctx_t { int argc, cpidx, total; const char *opt; enum parse_opt_flags flags; + unsigned has_subcommands; const char *prefix; const char **alias_groups; /* must be in groups of 3 elements! */ struct option *updated_options; diff --git a/patch-ids.c b/patch-ids.c index 8bf425555d..46c6a8f3ea 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -38,7 +38,7 @@ int commit_patch_id(struct commit *commit, struct diff_options *options, static int patch_id_neq(const void *cmpfn_data, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata UNUSED) { /* NEEDSWORK: const correctness? */ struct diff_options *opt = (void *)cmpfn_data; diff --git a/pathspec.c b/pathspec.c index 84ad9c73cf..46e77a85fe 100644 --- a/pathspec.c +++ b/pathspec.c @@ -759,3 +759,92 @@ int match_pathspec_attrs(struct index_state *istate, return 1; } + +int pathspec_needs_expanded_index(struct index_state *istate, + const struct pathspec *pathspec) +{ + unsigned int i, pos; + int res = 0; + char *skip_worktree_seen = NULL; + + /* + * If index is not sparse, no index expansion is needed. + */ + if (!istate->sparse_index) + return 0; + + /* + * When using a magic pathspec, assume for the sake of simplicity that + * the index needs to be expanded to match all matchable files. + */ + if (pathspec->magic) + return 1; + + for (i = 0; i < pathspec->nr; i++) { + struct pathspec_item item = pathspec->items[i]; + + /* + * If the pathspec item has a wildcard, the index should be expanded + * if the pathspec has the possibility of matching a subset of entries inside + * of a sparse directory (but not the entire directory). + * + * If the pathspec item is a literal path, the index only needs to be expanded + * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't + * expand for in-cone files) and b) it doesn't match any sparse directories + * (since we can reset whole sparse directories without expanding them). + */ + if (item.nowildcard_len < item.len) { + /* + * Special case: if the pattern is a path inside the cone + * followed by only wildcards, the pattern cannot match + * partial sparse directories, so we know we don't need to + * expand the index. + * + * Examples: + * - in-cone/foo***: doesn't need expanded index + * - not-in-cone/bar*: may need expanded index + * - **.c: may need expanded index + */ + if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && + path_in_cone_mode_sparse_checkout(item.original, istate)) + continue; + + for (pos = 0; pos < istate->cache_nr; pos++) { + struct cache_entry *ce = istate->cache[pos]; + + if (!S_ISSPARSEDIR(ce->ce_mode)) + continue; + + /* + * If the pre-wildcard length is longer than the sparse + * directory name and the sparse directory is the first + * component of the pathspec, need to expand the index. + */ + if (item.nowildcard_len > ce_namelen(ce) && + !strncmp(item.original, ce->name, ce_namelen(ce))) { + res = 1; + break; + } + + /* + * If the pre-wildcard length is shorter than the sparse + * directory and the pathspec does not match the whole + * directory, need to expand the index. + */ + if (!strncmp(item.original, ce->name, item.nowildcard_len) && + wildmatch(item.original, ce->name, 0)) { + res = 1; + break; + } + } + } else if (!path_in_cone_mode_sparse_checkout(item.original, istate) && + !matches_skip_worktree(pathspec, i, &skip_worktree_seen)) + res = 1; + + if (res > 0) + break; + } + + free(skip_worktree_seen); + return res; +} diff --git a/pathspec.h b/pathspec.h index 402ebb8080..41f6adfbb4 100644 --- a/pathspec.h +++ b/pathspec.h @@ -171,4 +171,16 @@ int match_pathspec_attrs(struct index_state *istate, const char *name, int namelen, const struct pathspec_item *item); +/* + * Determine whether a pathspec will match only entire index entries (non-sparse + * files and/or entire sparse directories). If the pathspec has the potential to + * match partial contents of a sparse directory, return 1 to indicate the index + * should be expanded to match the appropriate index entries. + * + * For the sake of simplicity, always return 1 if using a more complex "magic" + * pathspec. + */ +int pathspec_needs_expanded_index(struct index_state *istate, + const struct pathspec *pathspec); + #endif /* PATHSPEC_H */ diff --git a/pkt-line.c b/pkt-line.c index 8e43c2def4..ce4e73b683 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -309,7 +309,8 @@ int write_packetized_from_fd_no_flush(int fd_in, int fd_out) return err; } -int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out) +int write_packetized_from_buf_no_flush_count(const char *src_in, size_t len, + int fd_out, int *packet_counter) { int err = 0; size_t bytes_written = 0; @@ -324,6 +325,8 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou break; err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; + if (packet_counter) + (*packet_counter)++; } return err; } diff --git a/pkt-line.h b/pkt-line.h index 1f623de60a..79c538b99e 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -32,7 +32,13 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f int packet_flush_gently(int fd); int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int write_packetized_from_fd_no_flush(int fd_in, int fd_out); -int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out); +int write_packetized_from_buf_no_flush_count(const char *src_in, size_t len, + int fd_out, int *packet_counter); +static inline int write_packetized_from_buf_no_flush(const char *src_in, + size_t len, int fd_out) +{ + return write_packetized_from_buf_no_flush_count(src_in, len, fd_out, NULL); +} /* * Stdio versions of packet_write functions. When mixing these with fd diff --git a/po/bg.po b/po/bg.po index 934784fe52..8b2a936ba0 100644 --- a/po/bg.po +++ b/po/bg.po @@ -1,8 +1,7 @@ # Bulgarian translation of git po-file. -# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Alexander Shopov . +# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Alexander Shopov . # This file is distributed under the same license as the git package. # Alexander Shopov , 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022. -# # ======================== # DICTIONARY TO MERGE IN GIT GUI # ------------------------ @@ -64,6 +63,8 @@ # pathspec magic опция за магически пътища # bitmap index индекс на база битови маски # multi-pack bitmap многопакетната битова маска +# ewah bitmap битова маска във формат EWAH +# type bitmap битова маска на видовете # mark маркер # plumbing команди от системно ниво # porcelain команди от потребителско ниво @@ -165,6 +166,7 @@ # scheduler планиращ модул # snapshot снимка # enlistment зачислена директория +# remove enlistment отчислявам директория # zealous merge засилено сливане # unregister отчислявам # marked counting изброяване @@ -184,7 +186,13 @@ # traverse обхождам # timestamp времево клеймо # bare repository голо хранилище -# +# resolve-undo отмяна на разрешените подавания +# maintenance задачи по поддръжка +# GLE последна грешка в нишката - от GetLatError: https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror +# lookup table таблица със съответствия +# out of range извън диапазона +# checksum сума за проверка +# superproject обхващащ проект # ------------------------ # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$ # ------------------------ @@ -199,13 +207,12 @@ # msgcat todo1.po todo2.po > todo.po # grep '^#: ' todo.po | sed 's/^#: //' | tr ' ' '\n' | sed 's/:[0-9]*$//' > FILES # for i in `sort -u FILES`; do cnt=`grep $i FILES | wc -l`; echo $cnt $i ;done | sort -n -# msgid "" msgstr "" -"Project-Id-Version: git 2.37\n" +"Project-Id-Version: git 2.38\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2022-06-22 15:43+0200\n" -"PO-Revision-Date: 2022-06-22 15:44+0200\n" +"POT-Creation-Date: 2022-09-28 10:59+0200\n" +"PO-Revision-Date: 2022-09-28 10:59+0200\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -363,7 +370,6 @@ msgstr "път" msgid "could not refresh index" msgstr "индексът не може да бъде обновен" -# #, c-format msgid "Bye.\n" msgstr "Изход.\n" @@ -625,6 +631,24 @@ msgstr "" "d — без прилагане на това и всички следващи парчета от файла от индекса и " "работното дърво\n" +#, c-format, perl-format +msgid "Apply mode change to worktree [y,n,q,a,d%s,?]? " +msgstr "" +"Прилагане на промяната в правата за достъп към работното дърво [y,n,q,a,d" +"%s,?]? " + +#, c-format, perl-format +msgid "Apply deletion to worktree [y,n,q,a,d%s,?]? " +msgstr "Прилагане на изтриването към работното дърво [y,n,q,a,d%s,?]? " + +#, c-format, perl-format +msgid "Apply addition to worktree [y,n,q,a,d%s,?]? " +msgstr "Прилагане на добавянето към работното дърво [y,n,q,a,d%s,?]? " + +#, c-format, perl-format +msgid "Apply this hunk to worktree [y,n,q,a,d%s,?]? " +msgstr "Прилагане на парчето към работното дърво [y,n,q,a,d%s,?]? " + msgid "" "y - apply this hunk to worktree\n" "n - do not apply this hunk to worktree\n" @@ -642,10 +666,6 @@ msgstr "" msgid "could not parse hunk header '%.*s'" msgstr "заглавната част на парчето „%.*s“ не може да се анализира" -#, c-format -msgid "could not parse colored hunk header '%.*s'" -msgstr "заглавната част на оцветеното парче „%.*s“ не може да се анализира" - msgid "could not parse diff" msgstr "разликата не може да се анализира" @@ -936,6 +956,29 @@ msgstr "" "detachedHead“\n" "да е „false“ (лъжа̀).\n" +#, c-format +msgid "" +"The following paths have been moved outside the\n" +"sparse-checkout definition but are not sparse due to local\n" +"modifications.\n" +msgstr "" +"Следните пътища са преместени извън дефинициите за частично\n" +"изтегляне, но вече не са частични заради локални промени.\n" + +msgid "" +"To correct the sparsity of these paths, do the following:\n" +"* Use \"git add --sparse \" to update the index\n" +"* Use \"git sparse-checkout reapply\" to apply the sparsity rules" +msgstr "" +"Ако искате да ги обновите, пробвайте едно от следните:\n" +" ⁃ обновете индекса с командата:\n" +"\n" +" git add --sparse ПЪТ…\n" +"\n" +" ⁃ приложете правилата за частичност с командата:\n" +"\n" +" git sparse-checkout reapply" + msgid "cmdline ends with \\" msgstr "командният ред завършва с „/“" @@ -1460,6 +1503,10 @@ msgstr "обектът-BLOB „%s“ не може да бъде обработ msgid "unsupported file mode: 0%o (SHA1: %s)" msgstr "неподдържани права за достъп до файл: 0%o (SHA1: %s)" +#, c-format +msgid "deflate error (%d)" +msgstr "грешка при декомпресиране с „deflate“ (%d)" + #, c-format msgid "unable to start '%s' filter" msgstr "филтърът „%s“ не може да бъде стартиран" @@ -1479,10 +1526,6 @@ msgstr "пътят не е правилно кодиран в UTF-8: %s" msgid "path too long (%d chars, SHA1: %s): %s" msgstr "твърде дълъг път (%d знака, SHA1: %s): %s" -#, c-format -msgid "deflate error (%d)" -msgstr "грешка при декомпресиране с „deflate“ (%d)" - #, c-format msgid "timestamp too large for this system: %" msgstr "времевото клеймо е твърде голямо за тази система: %" @@ -3315,8 +3358,13 @@ msgstr "библиотека на C: " msgid "not run from a git repository - no hooks to show\n" msgstr "командата е стартирана извън хранилище на Git, затова няма куки\n" -msgid "git bugreport [-o|--output-directory ] [-s|--suffix ]" -msgstr "git bugreport [-o|--output-directory ФАЙЛ] [-s|--suffix ФОРМАТ]" +# FIXME +msgid "" +"git bugreport [-o|--output-directory ] [-s|--suffix ] [--" +"diagnose[=]" +msgstr "" +"git bugreport [-o|--output-directory ФАЙЛ] [-s|--suffix ФОРМАТ] [--" +"diagnose[=РЕЖИМ]]" msgid "" "Thank you for filling out a Git bug report!\n" @@ -3353,16 +3401,29 @@ msgstr "" "Разгледайте останалата част от доклада за грешка по-долу.\n" "Може да изтриете редовете, които не искате да споделите.\n" -msgid "specify a destination for the bugreport file" -msgstr "укажете файла, в който да се запази докладът за грешка" +msgid "mode" +msgstr "РЕЖИМ" -msgid "specify a strftime format suffix for the filename" -msgstr "укажете суфикса на файла във формат за „strftime“" +msgid "" +"create an additional zip archive of detailed diagnostics (default 'stats')" +msgstr "" +"създаване на допълнителен архив във формат zip с подробни диагностични " +"съобщения (стандартно е „stats“ — статистика)" + +msgid "specify a destination for the bugreport file(s)" +msgstr "укажете местоположение, в което да се запазят докладите за грешка" + +msgid "specify a strftime format suffix for the filename(s)" +msgstr "укажете суфикса на файловете във формат за „strftime“" #, c-format msgid "could not create leading directories for '%s'" msgstr "родителските директории на „%s“ не може да бъдат създадени" +#, c-format +msgid "unable to create diagnostics archive %s" +msgstr "не може да се създаде архив за диагностичните съобщения: %s" + msgid "System Info" msgstr "Информация за системата" @@ -3421,10 +3482,6 @@ msgstr "За приемането на пратка е необходимо хр msgid "Unbundling objects" msgstr "Разпакетиране на пратки от обекти" -#, c-format -msgid "Unknown subcommand: %s" -msgstr "Непозната подкоманда: %s" - #, c-format msgid "cannot read object %s '%s'" msgstr "обектът „%s“ (%s) не може да бъде прочетен" @@ -3504,6 +3561,11 @@ msgstr "извеждане на размера на обект" msgid "allow -s and -t to work with broken/corrupt objects" msgstr "позволяване на опциите „-s“ и „-t“ да работят с повредени обекти" +msgid "use mail map file" +msgstr "" +"използване на файл за съответствията на имената и адресите на е-поща („." +"mailmap“)" + msgid "Batch objects requested on stdin (or --batch-all-objects)" msgstr "" "Пакетно извеждане на заявените обекти на стандартния изход (или „--batch-all-" @@ -3515,6 +3577,9 @@ msgstr "извеждане на пълното съдържание на ОБЕ msgid "like --batch, but don't emit " msgstr "като „--batch“, но без извеждане на съдържанието" +msgid "stdin is NUL-terminated" +msgstr "стандартният вход да ползва нулевия знак „NUL“ за разделител" + msgid "read commands from stdin" msgstr "изчитане на командите от стандартния вход" @@ -4403,6 +4468,13 @@ msgstr "" "инициализиране на файла за частично изтегляне („.git/info/sparse-checkout“) " "да съдържа само файловете в основната директория" +msgid "uri" +msgstr "АДРЕС" + +msgid "a URI for downloading bundles before fetching from origin remote" +msgstr "" +"АДРЕС за доставяне на пратки на git преди доставяне от отдалеченото хранилище" + #, c-format msgid "info: Could not add alternate for '%s': %s\n" msgstr "" @@ -4468,10 +4540,10 @@ msgstr "обектът „%s“ не може да бъде обновен" msgid "failed to initialize sparse-checkout" msgstr "частичното изтегляне не може да се инициализира" -msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n" +msgid "remote HEAD refers to nonexistent ref, unable to checkout" msgstr "" -"указателят „HEAD“ от отдалеченото хранилище сочи към нещо,\n" -"което не съществува. Не може да се изтегли определен клон.\n" +"указателят „HEAD“ от отдалеченото хранилище сочи към нещо, което не " +"съществува. Изтегляне не може да се извърши" msgid "unable to checkout working tree" msgstr "работното дърво не може да бъде подготвено" @@ -4495,6 +4567,13 @@ msgstr "Трябва да укажете кое хранилище искате msgid "options '%s' and '%s %s' cannot be used together" msgstr "опциите „%s“ и „%s %s“ са несъвместими" +msgid "" +"--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-" +"exclude" +msgstr "" +"опцията „--bundle-uri“ е несъвместима с „--depth“, „--shallow-since“ и „--" +"shallow-exclude“" + #, c-format msgid "repository '%s' does not exist" msgstr "не съществува хранилище „%s“" @@ -4574,6 +4653,14 @@ msgstr "опцията „--local“ се прескача" msgid "cannot clone from filtered bundle" msgstr "не може да се клонира от филтрирана пратка" +msgid "failed to initialize the repo, skipping bundle URI" +msgstr "" +"хранилището не може да се инициализира, адресът на пратката се прескача" + +#, c-format +msgid "failed to fetch objects from bundle URI '%s'" +msgstr "неуспешно доставяне на обекти от пратка на адрес „%s“" + msgid "remote transport reported error" msgstr "отдалеченият транспорт върна грешка" @@ -4694,10 +4781,6 @@ msgstr "" msgid "Collecting commits from input" msgstr "Получаване на подаванията от входа" -#, c-format -msgid "unrecognized subcommand: %s" -msgstr "непозната подкоманда: %s" - msgid "" "git commit-tree [(-p )...] [-S[]] [(-m )...] [(-F " ")...] " @@ -5066,9 +5149,6 @@ msgstr "подробна информация за състоянието (ст msgid "terminate entries with NUL" msgstr "разделяне на елементите с нулевия знак „NUL“" -msgid "mode" -msgstr "РЕЖИМ" - msgid "show untracked files, optional modes: all, normal, no. (Default: all)" msgstr "" "извеждане на неследените файлове. Възможните РЕЖИМи са „all“ (подробна " @@ -5685,6 +5765,22 @@ msgstr "Не са открити имена — нищо не може да бъ msgid "option '%s' and commit-ishes cannot be used together" msgstr "опциите „%s“ и указателите към обекти са несъвместими" +msgid "" +"git diagnose [-o|--output-directory ] [-s|--suffix ] [--" +"mode=]" +msgstr "" +"git diagnose [-o|--output-directory ФАЙЛ] [-s|--suffix ФОРМАТ] [--" +"diagnose[=РЕЖИМ]]" + +msgid "specify a destination for the diagnostics archive" +msgstr "укажете местоположение на архива с диагностичната информация" + +msgid "specify a strftime format suffix for the filename" +msgstr "укажете суфикса на файла във формат за „strftime“" + +msgid "specify the content of the diagnostic archive" +msgstr "указване на съдържанието на диагностичния архив" + msgid "--merge-base only works with two commits" msgstr "опцията „--merge-base“ изисква точно две подавания" @@ -6066,12 +6162,9 @@ msgstr "[актуален]" msgid "[rejected]" msgstr "[отхвърлен]" -msgid "can't fetch in current branch" +msgid "can't fetch into checked-out branch" msgstr "в текущия клон не може да се доставя" -msgid "checked out in another worktree" -msgstr "изтеглен в друго работно дърво" - msgid "[tag update]" msgstr "[обновяване на етикетите]" @@ -6487,7 +6580,6 @@ msgstr "Проверка на директориите с обекти" msgid "Checking %s link" msgstr "Проверка на връзките на „%s“" -# #, c-format msgid "invalid %s" msgstr "неправилен указател „%s“" @@ -6514,6 +6606,10 @@ msgstr "„%s“: неправилен указател за SHA1 в дърво msgid "non-tree in cache-tree" msgstr "в дървото на кеша има нещо, което не е дърво" +#, c-format +msgid "%s: invalid sha1 pointer in resolve-undo" +msgstr "„%s“: неправилен указател за отмяна на разрешените подавания" + msgid "git fsck [] [...]" msgstr "git fsck [ОПЦИЯ…] [ОБЕКТ…]" @@ -6578,7 +6674,7 @@ msgstr "git fsmonitor--daemon status" #, c-format msgid "value of '%s' out of range: %d" -msgstr "дължината на „%s“ e извън интервала: %d" +msgstr "дължината на „%s“ e извън диапазона: %d" #, c-format msgid "value of '%s' not bool or int: %d" @@ -6856,13 +6952,16 @@ msgstr "" "неуспешно изпълнение на „crontab -l“. Системата ви може да не поддържа " "„cron“" +msgid "failed to create crontab temporary file" +msgstr "не може да се създаде временен файл за crontab" + +msgid "failed to open temporary file" +msgstr "временният файл не може да се отвори" + msgid "failed to run 'crontab'; your system might not support 'cron'" msgstr "" "неуспешно изпълнение на „crontab“. Системата ви може да не поддържа „cron“" -msgid "failed to open stdin of 'crontab'" -msgstr "стандартният вход на „crontab“ не може да се отвори" - msgid "'crontab' died" msgstr "процесът на „crontab“ умря" @@ -6909,10 +7008,6 @@ msgstr "неуспешно добавяне на хранилище към фа msgid "git maintenance []" msgstr "git maintenance ПОДКОМАНДА [ОПЦИЯ…]" -#, c-format -msgid "invalid subcommand: %s" -msgstr "неправилна подкоманда: „%s“" - msgid "git grep [] [-e] [...] [[--] ...]" msgstr "git grep [ОПЦИЯ…] [-e] ШАБЛОН [ВЕРСИЯ…] [[--] ПЪТ…]" @@ -7094,6 +7189,9 @@ msgstr "" "позволяване на стартирането на grep(1) (текущият компилат пренебрегва тази " "опция)" +msgid "maximum number of results per file" +msgstr "максимален брой резултати във файл" + msgid "no pattern given" msgstr "не сте задали шаблон" @@ -7178,16 +7276,22 @@ msgid "show info page" msgstr "показване на информационна страница" msgid "print command description" -msgstr "извеждане на описанието на команда" +msgstr "показване на описанието на команда" msgid "print list of useful guides" msgstr "показване на списък с въведения" +msgid "print list of user-facing repository, command and file interfaces" +msgstr "показване на списък с командите за файлове и хранилища за потребители" + +msgid "print list of file formats, protocols and other developer interfaces" +msgstr "показване на списък с файлови формати, протоколи и др. за програмисти" + msgid "print all configuration variable names" msgstr "показване на имената на всички конфигуриращи променливи" -msgid "git help [[-i|--info] [-m|--man] [-w|--web]] []" -msgstr "git help [[-i|--info] [-m|--man] [-w|--web]] [КОМАНДА…]" +msgid "git help [[-i|--info] [-m|--man] [-w|--web]] [|]" +msgstr "git help [[-i|--info] [-m|--man] [-w|--web]] [КОМАНДА|ДОКУМЕНТ]" #, c-format msgid "unrecognized help format '%s'" @@ -7403,7 +7507,7 @@ msgstr[1] "действието завърши с %d локални обекта #, c-format msgid "Unexpected tail checksum for %s (disk corruption?)" msgstr "" -"Неочаквана последваща сума за грешки за „%s“ (причината може да е грешка в " +"Неочаквана последваща сума за проверка на „%s“ (причината може да е грешка в " "диска)" #, c-format @@ -7666,10 +7770,8 @@ msgstr "без извеждане на разликите" msgid "show source" msgstr "извеждане на изходния код" -msgid "use mail map file" -msgstr "" -"използване на файл за съответствията на имената и адресите на е-поща („." -"mailmap“)" +msgid "clear all previously-defined decoration filters" +msgstr "изчистване на зададените филтри за форма̀та на извежданата информация" msgid "only decorate refs that match " msgstr "специален формат само на указателите напасващи на ШАБЛОНа" @@ -7936,6 +8038,11 @@ msgstr "" msgid "percentage by which creation is weighted" msgstr "процент за претегляне при оценка на създаването" +msgid "show in-body From: even if identical to the e-mail header" +msgstr "" +"показване на заглавната част „From:“ в тялото на писмото дори и да е същата " +"като тази на плика на писмото" + #, c-format msgid "invalid ident line: %s" msgstr "грешна идентичност: %s" @@ -7998,6 +8105,18 @@ msgstr "" "Следеният отдалечен клон не бе открит, затова изрично задайте " "ОТДАЛЕЧЕН_КЛОН.\n" +#, c-format +msgid "bad ls-files format: element '%s' does not start with '('" +msgstr "неправилен формат за „ls-tree“: елементът „%s“ не започва с „(“" + +#, c-format +msgid "bad ls-files format: element '%s' does not end in ')'" +msgstr "неправилен формат за „ls-tree“: елементът „%s“ не завършва с „)“" + +#, c-format +msgid "bad ls-files format: %%%.*s" +msgstr "неправилен формат за „ls-tree“: %%%.*s" + msgid "git ls-files [] [...]" msgstr "git ls-files [ОПЦИЯ…] [ФАЙЛ…]" @@ -8085,6 +8204,13 @@ msgid "show sparse directories in the presence of a sparse index" msgstr "" "указване на частично изтеглените директории при наличието на частичен индекс" +msgid "" +"--format cannot be used with -s, -o, -k, -t, --resolve-undo, --deduplicate, " +"--eol" +msgstr "" +"опцията „--format“ е несъвместима с „-s“/„-o“/„-k“/„-t“/„--resolve-undo“/„--" +"deduplicate“/„--eol“" + msgid "" "git ls-remote [--heads] [--tags] [--refs] [--upload-pack=]\n" " [-q | --quiet] [--exit-code] [--get-url]\n" @@ -8310,6 +8436,40 @@ msgstr "указателят „%s“ не може да бъде изтрит" msgid "Merging %s with %s\n" msgstr "Сливане на „%s“ с „%s“\n" +msgid "not something we can merge" +msgstr "не може да се слее" + +msgid "refusing to merge unrelated histories" +msgstr "независими истории не може да се слеят" + +msgid "failure to merge" +msgstr "неуспешно сливане" + +msgid "git merge-tree [--write-tree] [] " +msgstr "git merge-tree [--write-tree] [ОПЦИЯ…] КЛОН_1 КЛОН_2" + +msgid "git merge-tree [--trivial-merge] " +msgstr "git merge-tree [--trivial-merge] БАЗОВО_ДЪРВО КЛОН_1 КЛОН_2" + +msgid "do a real merge instead of a trivial merge" +msgstr "същинско, а не тривиално сливане" + +msgid "do a trivial merge only" +msgstr "само тривиално сливане" + +msgid "also show informational/conflict messages" +msgstr "показване и информационните съобщения, и тези при конфликт" + +msgid "list filenames without modes/oids/stages" +msgstr "" +"извеждане на имената на файлове без режими/идентификатори на обекти/етапи" + +msgid "allow merging unrelated histories" +msgstr "позволяване на сливане на независими истории" + +msgid "--trivial-merge is incompatible with all other options" +msgstr "„--trivial-merge“ е несъвместима с другите опции" + msgid "git merge [] [...]" msgstr "git merge [ОПЦИЯ…] [ПОДАВАНЕ…]" @@ -8391,9 +8551,6 @@ msgstr "преустановяване (--abort) без промяна на ин msgid "continue the current in-progress merge" msgstr "продължаване на текущото сливане" -msgid "allow merging unrelated histories" -msgstr "позволяване на сливане на независими истории" - msgid "bypass pre-merge-commit and commit-msg hooks" msgstr "" "без изпълнение на куките преди подаване и сливане и при промяна на " @@ -8516,9 +8673,6 @@ msgstr "„%s“ не може да се затвори" msgid "not something we can merge in %s: %s" msgstr "не може да се слее в „%s“: %s" -msgid "not something we can merge" -msgstr "не може да се слее" - msgid "--abort expects no arguments" msgstr "опцията „--abort“ не приема аргументи" @@ -8573,13 +8727,18 @@ msgstr "„%s“ — не е нещо, което може да се слее" msgid "Can merge only exactly one commit into empty head" msgstr "Може да слеете точно едно подаване във връх без история" -msgid "refusing to merge unrelated histories" -msgstr "независими истории не може да се слеят" - #, c-format msgid "Updating %s..%s\n" msgstr "Обновяване „%s..%s“\n" +#, c-format +msgid "" +"Your local changes to the following files would be overwritten by merge:\n" +" %s" +msgstr "" +"Сливането ще презапише локалните промѐни на тези файлове:\n" +" %s" + #, c-format msgid "Trying really trivial in-index merge...\n" msgstr "Проба със сливане в рамките на индекса…\n" @@ -8615,6 +8774,10 @@ msgid "Automatic merge went well; stopped before committing as requested\n" msgstr "" "Автоматичното сливане завърши успешно. Самото подаване не е извършено\n" +#, c-format +msgid "When finished, apply stashed changes with `git stash pop`\n" +msgstr "При завършване скатаните промени да се приложат с „git stash pop“\n" + #, c-format msgid "warning: tag input does not pass fsck: %s" msgstr "ПРЕДУПРЕЖДЕНИЕ: аргументът-етикет не минава проверка с „fsck“: %s" @@ -8738,6 +8901,9 @@ msgstr "Проверка на преименуването на обект от msgid "bad source" msgstr "неправилен обект" +msgid "destination exists" +msgstr "целта съществува" + msgid "can not move directory into itself" msgstr "директория не може да се премести в себе си" @@ -8753,9 +8919,6 @@ msgstr "не е под контрола на Git" msgid "conflicted" msgstr "конфликт" -msgid "destination exists" -msgstr "целта съществува" - #, c-format msgid "overwriting '%s'" msgstr "презаписване на „%s“" @@ -8769,6 +8932,9 @@ msgstr "множество източници за една цел" msgid "destination directory does not exist" msgstr "целевата директория не съществува" +msgid "destination exists in the index" +msgstr "целта съществува в индекса" + #, c-format msgid "%s, source=%s, destination=%s" msgstr "%s, обект: „%s“, цел: „%s“" @@ -9136,8 +9302,8 @@ msgid "use notes from " msgstr "да се използва бележката сочена от този УКАЗАТЕЛ_ЗА_БЕЛЕЖКА" #, c-format -msgid "unknown subcommand: %s" -msgstr "непозната подкоманда: %s" +msgid "unknown subcommand: `%s'" +msgstr "непозната подкоманда: „%s“" msgid "" "git pack-objects --stdout [...] [< | < ]" @@ -10065,12 +10231,17 @@ msgstr "извеждане само на информацията за първ msgid "only emit output related to the second range" msgstr "извеждане само на информацията за втория диапазон" +#, c-format +msgid "not a revision: '%s'" +msgstr "„%s“ не е версия" + #, c-format msgid "not a commit range: '%s'" msgstr "не е диапазон с подавания: „%s“" -msgid "single arg format must be symmetric range" -msgstr "вариантът с единствен аргумент изисква симетричен диапазон" +#, c-format +msgid "not a symmetric range: '%s'" +msgstr "не е симетричен диапазон: „%s“" msgid "need two commit ranges" msgstr "необходими са два диапазона с подавания" @@ -10344,6 +10515,9 @@ msgid "move commits that begin with squash!/fixup! under -i" msgstr "" "преместване на подаванията, които започват със „squash!“/“fixup!“ при „-i“" +msgid "update branches that point to commits that are being rebased" +msgstr "обновяване на клоните, които сочат към пребазирани подавания" + msgid "add exec lines after each commit of the editable list" msgstr "" "добавяне на редове с команди за изпълнение след всяко подаване в " @@ -10895,6 +11069,9 @@ msgstr " нов (следващото доставяне ще го разпол msgid " tracked" msgstr " следен" +msgid " skipped" +msgstr " прескочен" + msgid " stale (use 'git remote prune' to remove)" msgstr " стар (изтрийте чрез „git remote prune“)" @@ -11587,6 +11764,10 @@ msgstr "Новият индекс не може да бъде записан." msgid "unable to get disk usage of %s" msgstr "използваното място за „%s“ не може да бъде получено" +#, c-format +msgid "invalid value for '%s': '%s', the only allowed format is '%s'" +msgstr "неправилна стойност за „%s“: „%s“, единственият позволен формат е „%s“" + msgid "rev-list does not support display of notes" msgstr "командата „rev-list“ не поддържа извеждането на бележки" @@ -11612,6 +11793,9 @@ msgstr "неочакван край на входа" msgid "no usage string given before the `--' separator" msgstr "липсва низ преди разделителя „--“" +msgid "missing opt-spec before option flags" +msgstr "липсват опции пред флаговете за опции" + msgid "Needed a single revision" msgstr "Изисква точно една версия" @@ -12391,6 +12575,10 @@ msgstr "добавяне на „# “ в началото на всеки ре msgid "Expecting a full ref name, got %s" msgstr "Очаква се пълно име на указател, а не „%s“" +#, c-format +msgid "could not get a repository handle for submodule '%s'" +msgstr "не може да се получи връзка към хранилище за подмодула „%s“" + #, c-format msgid "" "could not look up configuration '%s'. Assuming this repository is its own " @@ -12399,12 +12587,6 @@ msgstr "" "настройката „%s“ липсва. Приема се, че това хранилище е правилният източник " "за себе си." -msgid "alternative anchor for relative paths" -msgstr "директория за определянето на относителните пътища" - -msgid "git submodule--helper list [--prefix=] [...]" -msgstr "git submodule--helper list [--prefix=ПЪТ] [ПЪТ…]" - #, c-format msgid "No url found for submodule path '%s' in .gitmodules" msgstr "Във файла „.gitmodules“ не е открит адрес за пътя към подмодул „%s“" @@ -12437,8 +12619,8 @@ msgstr "без извеждане на изход при въвеждането msgid "recurse into nested submodules" msgstr "рекурсивно обхождане на подмодулите" -msgid "git submodule--helper foreach [--quiet] [--recursive] [--] " -msgstr "git submodule--helper foreach [--quiet] [--recursive] [--] КОМАНДА" +msgid "git submodule foreach [--quiet] [--recursive] [--] " +msgstr "git submodule foreach [--quiet] [--recursive] [--] КОМАНДА" #, c-format msgid "Failed to register url for submodule path '%s'" @@ -12460,8 +12642,8 @@ msgstr "" msgid "suppress output for initializing a submodule" msgstr "без извеждане на информация при инициализирането на подмодул" -msgid "git submodule--helper init [] []" -msgstr "git submodule--helper init [ОПЦИЯ…] [ПЪТ]" +msgid "git submodule init [] []" +msgstr "git submodule init [ОПЦИЯ…] [ПЪТ]" #, c-format msgid "no submodule mapping found in .gitmodules for path '%s'" @@ -12488,9 +12670,6 @@ msgstr "" msgid "git submodule status [--quiet] [--cached] [--recursive] [...]" msgstr "git submodule status [--quiet] [--cached] [--recursive] [ПЪТ…]" -msgid "git submodule--helper name " -msgstr "git submodule--helper name ПЪТ" - #, c-format msgid "* %s %s(blob)->%s(submodule)" msgstr "● %s %s(обект-BLOB)→%s(подмодул)" @@ -12527,8 +12706,8 @@ msgstr "прескачане на подмодули, чиято настрой msgid "limit the summary size" msgstr "ограничаване на размера на обобщението" -msgid "git submodule--helper summary [] [] [--] []" -msgstr "git submodule--helper summary [ОПЦИЯ…] [ПОДАВАНЕ] [--] [ПЪТ]" +msgid "git submodule summary [] [] [--] []" +msgstr "git submodule summary [ОПЦИЯ…] [ПОДАВАНЕ] [--] [ПЪТ]" msgid "could not fetch a revision for HEAD" msgstr "не може да се достави версия за „HEAD“" @@ -12541,10 +12720,6 @@ msgstr "Синхронизиране на адреса на подмодул з msgid "failed to register url for submodule path '%s'" msgstr "неуспешно регистриране на адрес за пътя към подмодул „%s“" -#, c-format -msgid "failed to get the default remote for submodule '%s'" -msgstr "отдалеченият адрес на подмодула „%s“ не може да бъде получен" - #, c-format msgid "failed to update remote for submodule '%s'" msgstr "отдалеченият адрес на подмодула „%s“ не може да бъде променен" @@ -12552,8 +12727,8 @@ msgstr "отдалеченият адрес на подмодула „%s“ н msgid "suppress output of synchronizing submodule url" msgstr "без извеждане на информация при синхронизирането на подмодул" -msgid "git submodule--helper sync [--quiet] [--recursive] []" -msgstr "git submodule--helper sync [--quiet] [--recursive] [ПЪТ]" +msgid "git submodule sync [--quiet] [--recursive] []" +msgstr "git submodule sync [--quiet] [--recursive] [ПЪТ]" #, c-format msgid "" @@ -12615,6 +12790,10 @@ msgstr "" "задайте настройката „submodule.alternateErrorStrategy“ да е „info“ или\n" "при клониране ползвайте опцията „--reference-if-able“ вместо „--reference“." +#, c-format +msgid "could not get a repository handle for gitdir '%s'" +msgstr "не може да се получи връзка към хранилище за директорията на git „%s“" + #, c-format msgid "submodule '%s' cannot add alternate: %s" msgstr "към подмодула „%s“ не може да се добави алтернативен източник: %s" @@ -12646,6 +12825,9 @@ msgstr "директорията не е празна: „%s“" msgid "could not get submodule directory for '%s'" msgstr "директорията на подмодула „%s“ не може да бъде получена" +msgid "alternative anchor for relative paths" +msgstr "директория за определянето на относителните пътища" + msgid "where the new submodule will be cloned to" msgstr "къде да се клонира новият подмодул" @@ -12673,10 +12855,6 @@ msgstr "" "[--name ИМЕ] [--depth ДЪЛБОЧИНА] [--single-branch] [--filter ФИЛТЪР] --url " "АДРЕС --path ПЪТ" -#, c-format -msgid "Invalid update mode '%s' for submodule path '%s'" -msgstr "Неправилен режим на обновяване „%s“ за пътя към подмодул „%s“" - #, c-format msgid "Invalid update mode '%s' configured for submodule path '%s'" msgstr "" @@ -12752,6 +12930,10 @@ msgstr "" "Подмодулът в пътя „%s“ е доставен, но не съдържа обекта със сума\n" "„%s“. Директното доставяне на това подаване е неуспешно." +#, c-format +msgid "could not initialize submodule at path '%s'" +msgstr "подмодулът в „%s“ не може да се инициализира" + #, c-format msgid "" "Submodule (%s) branch configured to inherit branch from superproject, but " @@ -12760,10 +12942,6 @@ msgstr "" "Клонът на подмодула „%s“ е настроен да наследява клона от обхващащия проект, " "но той не е на никой клон" -#, c-format -msgid "could not get a repository handle for submodule '%s'" -msgstr "не може да се получи връзка към хранилище за подмодула „%s“" - #, c-format msgid "Unable to find current revision in submodule path '%s'" msgstr "Текущата версия за подмодула в „%s“ липсва" @@ -12798,13 +12976,15 @@ msgstr "без доставяне на новите обекти от отдал msgid "path into the working tree" msgstr "път към работното дърво" -msgid "path into the working tree, across nested submodule boundaries" -msgstr "път към работното дърво, през границите на вложените подмодули" - -msgid "rebase, merge, checkout or none" +msgid "use the 'checkout' update strategy (default)" msgstr "" -"„rebase“ (пребазиране), „merge“ (сливане), „checkout“ (изтегляне) или " -"„none“ (нищо да не се прави)" +"използване на стратегията за обновяване „checkout“ (изтегляне — стандартно)" + +msgid "use the 'merge' update strategy" +msgstr "използване на стратегията за обновяване „merge“ (сливане)" + +msgid "use the 'rebase' update strategy" +msgstr "използване на стратегията за обновяване „rebase“ (пребазиране)" msgid "create a shallow clone truncated to the specified number of revisions" msgstr "извършване на плитко клониране, отрязано до указания брой версии" @@ -12818,6 +12998,9 @@ msgstr "дали първоначалното клониране да е пли msgid "don't print cloning progress" msgstr "без извеждане на напредъка на клонирането" +msgid "disallow cloning into non-empty directory, implies --init" +msgstr "предотвратяване на клониране в непразна история, включва „--init“" + msgid "" "git submodule [--quiet] update [--init [--filter=]] [--remote] " "[-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-" @@ -12829,14 +13012,11 @@ msgstr "" "shallow] [--reference ] [--recursive] [--[no-]single-branch] " "[--] [ПЪТ…]" -msgid "bad value for update parameter" -msgstr "неправилен параметър към опцията „--update“" - msgid "recurse into submodules" msgstr "рекурсивно обхождане подмодулите" -msgid "git submodule--helper absorb-git-dirs [] [...]" -msgstr "git submodule--helper absorb-git-dirs [ОПЦИЯ…] [ПЪТ…]" +msgid "git submodule absorbgitdirs [] [...]" +msgstr "git submodule absorbgitdirs [ОПЦИЯ…] [ПЪТ…]" msgid "check if it is safe to write to the .gitmodules file" msgstr "проверка дали писането във файла „.gitmodules“ е безопасно" @@ -12856,8 +13036,8 @@ msgstr "файлът „.gitmodules“ трябва да е в работнот msgid "suppress output for setting url of a submodule" msgstr "без извеждане на информация при задаването на адреса на подмодул" -msgid "git submodule--helper set-url [--quiet] " -msgstr "git submodule--helper set-url [--quiet] [ПЪТ] [НОВ_ПЪТ]" +msgid "git submodule set-url [--quiet] " +msgstr "git submodule set-url [--quiet] [ПЪТ] [НОВ_ПЪТ]" msgid "set the default tracking branch to master" msgstr "задаване на стандартния следящ клон да е „master“" @@ -12865,12 +13045,11 @@ msgstr "задаване на стандартния следящ клон да msgid "set the default tracking branch" msgstr "задаване на стандартния следящ клон" -msgid "git submodule--helper set-branch [-q|--quiet] (-d|--default) " -msgstr "git submodule--helper set-branch [-q|--quiet] (-d|--default) ПЪТ" +msgid "git submodule set-branch [-q|--quiet] (-d|--default) " +msgstr "git submodule set-branch [-q|--quiet] (-d|--default) ПЪТ" -msgid "" -"git submodule--helper set-branch [-q|--quiet] (-b|--branch) " -msgstr "git submodule--helper set-branch [-q|--quiet] (-b|--branch) КЛОН ПЪТ" +msgid "git submodule set-branch [-q|--quiet] (-b|--branch) " +msgstr "git submodule set-branch [-q|--quiet] (-b|--branch) КЛОН ПЪТ" msgid "--branch or --default required" msgstr "необходимо е една от опциите „--branch“ и „--default“" @@ -12968,8 +13147,8 @@ msgid "" msgstr "" "името на подмодула да е указаното, а не да е автоматично същото като пътя" -msgid "git submodule--helper add [] [--] []" -msgstr "git submodule--helper add [ОПЦИЯ…] [--] ХРАНИЛИЩЕ [ПЪТ]" +msgid "git submodule add [] [--] []" +msgstr "git submodule add [ОПЦИЯ…] [--] ХРАНИЛИЩЕ [ПЪТ]" msgid "Relative path can only be used from the toplevel of the working tree" msgstr "" @@ -13178,6 +13357,16 @@ msgstr "Обновен етикет „%s“ (бе „%s“)\n" msgid "pack exceeds maximum allowed size" msgstr "пакетният файл надвишава максималния възможен размер" +msgid "failed to write object in stream" +msgstr "някой обект не може да се запише в потока" + +#, c-format +msgid "inflate returned (%d)" +msgstr "декомпресирането с „inflate“ завърши (%d)" + +msgid "invalid blob object from stream" +msgstr "неправилен обект-BLOB от потока" + msgid "Unpacking objects" msgstr "Разпакетиране на обектите" @@ -13726,6 +13915,27 @@ msgstr "запис на обект-дърво за поддиректорият msgid "only useful for debugging" msgstr "само за изчистване на грешки" +msgid "core.fsyncMethod = batch is unsupported on this platform" +msgstr "„core.fsyncMethod = batch“ не се поддържа на тази платформа" + +msgid "failed to create temporary file" +msgstr "не може да се създаде временен файл" + +msgid "insufficient capabilities" +msgstr "недостатъчно възможности" + +#, c-format +msgid "failed to download bundle from URI '%s'" +msgstr "неуспешно изтегляне на пратка от адрес: „%s“" + +#, c-format +msgid "file at URI '%s' is not a bundle" +msgstr "файлът на адрес „%s“ не е пратка" + +#, c-format +msgid "failed to unbundle bundle from URI '%s'" +msgstr "неуспешно разпакетиране на пратка от адрес „%s“" + #, c-format msgid "unrecognized bundle hash algorithm: %s" msgstr "непознат алгоритъм за контролни суми за пратки „%s“" @@ -13939,6 +14149,9 @@ msgstr "" "Задаване на име удобно за потребителите на обект въз основа на наличен " "указател" +msgid "Generate a zip archive of diagnostic information" +msgstr "Създаване на архив във формат zip с диагностична информация" + msgid "Show changes between commits, commit and working tree, etc" msgstr "Извеждане на разликите между подаванията, версиите, работното дърво" @@ -14070,8 +14283,8 @@ msgstr "Сливане на файловете, които се нуждаят msgid "The standard helper program to use with git-merge-index" msgstr "Стандартната помощна програма за „git-merge-index“" -msgid "Show three-way merge without touching index" -msgstr "Извеждане на тройно сливане без промяна на индекса" +msgid "Perform merge without touching index or working tree" +msgstr "Извършване на сливането без промени по индекса или работното време" msgid "Run merge conflict resolution tools to resolve merge conflicts" msgstr "Изпълнение на програмите за коригиране на конфликтите при сливане" @@ -14266,6 +14479,9 @@ msgstr "Проверка на пакетираните архивни файло msgid "Check the GPG signature of tags" msgstr "Проверка на подписите GPG върху етикетите" +msgid "Display version information about Git" +msgstr "Извеждане на версията на Git" + msgid "Show logs with difference each commit introduces" msgstr "Извеждане на журнал с разликите, въведени с всяко подаване" @@ -14299,6 +14515,24 @@ msgstr "Полезен минимален набор от команди за е msgid "Frequently asked questions about using Git" msgstr "Често задавани въпроси за употребата на Git" +msgid "The bundle file format" +msgstr "Формат на пратките" + +msgid "Chunk-based file formats" +msgstr "Формати на откъсите" + +msgid "Git commit graph format" +msgstr "Формат на гра̀фа с подаванията" + +msgid "Git index format" +msgstr "Формат на индекса" + +msgid "Git pack format" +msgstr "Формат на пакетите" + +msgid "Git cryptographic signature formats" +msgstr "Формати на криптографските подписи" + msgid "A Git Glossary" msgstr "Речник с термините на Git" @@ -14320,6 +14554,21 @@ msgstr "Дефиниране на свойствата на подмодулит msgid "Git namespaces" msgstr "Пространства от имена на Git" +msgid "Protocol v0 and v1 capabilities" +msgstr "Възможности на протоколи, версия 1 и 2" + +msgid "Things common to various protocols" +msgstr "Общи за различните протоколи възможности" + +msgid "Git HTTP-based protocols" +msgstr "Протоколи на Git на основа на HTTP" + +msgid "How packs are transferred over-the-wire" +msgstr "Как пакетите се пренасят по мрежата" + +msgid "Git Wire Protocol, Version 2" +msgstr "Мрежов протокол на Git, версия 2" + msgid "Helper programs to interact with remote repositories" msgstr "Помощни програми за работа с отдалечените хранилища" @@ -14344,6 +14593,9 @@ msgstr "Уеб интерфейс на Git" msgid "An overview of recommended workflows with Git" msgstr "Общ преглед на препоръчваните начини за работа с Git" +msgid "A tool for managing large Git repositories" +msgstr "Инструмент за управление на големи хранилища на Git " + msgid "commit-graph file is too small" msgstr "файлът за гра̀фа с подаванията е твърде малък" @@ -14601,6 +14853,14 @@ msgstr "липсва информация за компилатора\n" msgid "no libc information available\n" msgstr "липсва информация за библиотеката на C\n" +#, c-format +msgid "could not determine free disk size for '%s'" +msgstr "не може да се определи свободното дисково пространство за „%s“" + +#, c-format +msgid "could not get info for '%s'" +msgstr "не може да се получи информацията за „%s“" + #, c-format msgid "[GLE %ld] health thread could not open '%ls'" msgstr "" @@ -14672,6 +14932,18 @@ msgstr "" "промѐните по директориите не може да бъдат прочетени [последна грешка в " "нишката: GLE=%ld]" +#, c-format +msgid "[GLE %ld] unable to open for read '%ls'" +msgstr "" +"файлът „%2$ls“ не може да бъде отворен за четене [последна грешка в нишката: " +"GLE=%1$ld]" + +#, c-format +msgid "[GLE %ld] unable to get protocol information for '%ls'" +msgstr "" +"информацията за протокола на „%2$ls“ не може да се получи [последна грешка в " +"нишката: GLE=%1$ld]" + #, c-format msgid "failed to copy SID (%ld)" msgstr "идентификаторът за сигурност (SID=%ld) не може да бъде копиран" @@ -14942,7 +15214,7 @@ msgstr "„%s“ не е правилна стойност за време за #, c-format msgid "abbrev length out of range: %d" -msgstr "дължината на съкращаване е извън интервала ([4; 40]): %d" +msgstr "дължината на съкращаване е извън диапазона ([4; 40]): %d" #, c-format msgid "bad zlib compression level %d" @@ -15225,196 +15497,20 @@ msgid "failed to close rev-list's stdin" msgstr "стандартният вход на списъка с версиите не може да бъде затворен" #, c-format -msgid "'%s' does not exist" -msgstr "„%s“ не съществува." - -msgid "need a working directory" -msgstr "необходима е работна директория" +msgid "illegal crlf_action %d" +msgstr "неправилно действие за край на ред (crlf_action): %d" -msgid "could not find enlistment root" -msgstr "началната зачислена директория не може да бъде открита" +#, c-format +msgid "CRLF would be replaced by LF in %s" +msgstr "всяка последователност от знаци „CRLF“ ще бъдe заменена с „LF“ в „%s“." #, c-format -msgid "could not switch to '%s'" -msgstr "не може да се премине към „%s“" - -#, c-format -msgid "could not configure %s=%s" -msgstr "настройката „%s=%s“ не може да се зададе" - -msgid "could not configure log.excludeDecoration" -msgstr "„log.excludeDecoration“ не може да се настрои" - -msgid "Scalar enlistments require a worktree" -msgstr "Зачисляването на директории чрез „scalar“ изисква работно дърво" - -#, c-format -msgid "could not open directory '%s'" -msgstr "директорията „%s“ не може да бъде отворена" - -#, c-format -msgid "skipping '%s', which is neither file nor directory" -msgstr "„%s“ се прескача, защото не е нито файл, нито директория" - -#, c-format -msgid "could not determine free disk size for '%s'" -msgstr "не може да се определи свободното дисково пространство за „%s“" - -#, c-format -msgid "could not get info for '%s'" -msgstr "не може да се получи информацията за „%s“" - -#, c-format -msgid "remote HEAD is not a branch: '%.*s'" -msgstr "отдалеченият указател „HEAD“ не сочи към клон: „%.*s“" - -msgid "failed to get default branch name from remote; using local default" -msgstr "" -"името на стандартния клон на отдалеченото хранилище не може да се получи, " -"затова ще се ползва локално настроеното име на стандартния клон" - -msgid "failed to get default branch name" -msgstr "неуспешно получаване на името на стандартния клон" - -msgid "failed to unregister repository" -msgstr "хранилището не може да бъде отчислено" - -msgid "failed to delete enlistment directory" -msgstr "зачислената директория не може да бъде изтрита" - -msgid "branch to checkout after clone" -msgstr "към кой клон да се премине след клониране" - -msgid "when cloning, create full working directory" -msgstr "при клониране да се създава пълна работна директория" - -msgid "only download metadata for the branch that will be checked out" -msgstr "да се свалят метаданните само за изтегляния клон" - -msgid "scalar clone [] [--] []" -msgstr "scalar clone [ОПЦИЯ…] [--] ХРАНИЛИЩЕ [ДИРЕКТОРИЯ]" - -#, c-format -msgid "cannot deduce worktree name from '%s'" -msgstr "името на работното дърво не може да се извлече от „%s“" - -#, c-format -msgid "directory '%s' exists already" -msgstr "директорията „%s“ вече съществува" - -#, c-format -msgid "failed to get default branch for '%s'" -msgstr "основният клон на „%s“ не може да бъде получен" - -#, c-format -msgid "could not configure remote in '%s'" -msgstr "отдалеченото хранилище в „%s“ не може да се настрои" - -#, c-format -msgid "could not configure '%s'" -msgstr "„%s“ не може да се настрои" - -msgid "partial clone failed; attempting full clone" -msgstr "неуспешно създаване на непълно хранилище, ще се опита пълно хранилище" - -msgid "could not configure for full clone" -msgstr "не може да се настрои пълно клониране" - -msgid "scalar diagnose []" -msgstr "scalar diagnose [ЗАЧИСЛЕНА_ДИРЕКТОРИЯ]" - -#, c-format -msgid "could not create directory for '%s'" -msgstr "директорията за „%s“ не може да се създадe" - -msgid "could not duplicate stdout" -msgstr "стандартният изход не може да се дублира" - -msgid "failed to write archive" -msgstr "неуспешен запис на архива" - -msgid "`scalar list` does not take arguments" -msgstr "„scalar list“ не приема аргументи" - -msgid "scalar register []" -msgstr "scalar register [ЗАЧИСЛЕНА_ДИРЕКТОРИЯ]" - -msgid "reconfigure all registered enlistments" -msgstr "пренастройване на всички зачислени директории" - -msgid "scalar reconfigure [--all | ]" -msgstr "scalar reconfigure [--all | ЗАЧИСЛЕНА_ДИРЕКТОРИЯ]" - -msgid "--all or , but not both" -msgstr "опцията „--all“ и указването на зачислена директория не са съвместими" - -#, c-format -msgid "git repository gone in '%s'" -msgstr "вече няма хранилище на git в „%s“" - -msgid "" -"scalar run []\n" -"Tasks:\n" -msgstr "" -"scalar run ЗАДАЧА [ЗАЧИСЛЕНА_ДИРЕКТОРИЯ]\n" -"Задачи:\n" - -#, c-format -msgid "no such task: '%s'" -msgstr "няма задача с име „%s“" - -msgid "scalar unregister []" -msgstr "scalar unregister [ЗАЧИСЛЕНА_ДИРЕКТОРИЯ]" - -msgid "scalar delete " -msgstr "scalar delete ЗАЧИСЛЕНА_ДИРЕКТОРИЯ" - -msgid "refusing to delete current working directory" -msgstr "текущата работна директория няма да бъде изтрита" - -msgid "include Git version" -msgstr "включване и на версията на git" - -msgid "include Git's build options" -msgstr "включване и на опциите за компилиране на git" - -msgid "scalar verbose [-v | --verbose] [--build-options]" -msgstr "scalar verbose [-v | --verbose] [--build-options]" - -msgid "-C requires a " -msgstr "„-C“ изисква ДИРЕКТОРИЯ" - -#, c-format -msgid "could not change to '%s'" -msgstr "не може да се влезе в директорията „%s“" - -msgid "-c requires a = argument" -msgstr "опцията „-c“ изисква аргумент във вид КЛЮЧ=СТОЙНОСТ" - -msgid "" -"scalar [-C ] [-c =] []\n" -"\n" -"Commands:\n" -msgstr "" -"scalar [-C ДИРЕКТОРИЯ] [-c КЛЮЧ=СТОЙНОСТ] КОМАНДА [ОПЦИЯ…]<\n" -"\n" -"Команди:\n" - -#, c-format -msgid "illegal crlf_action %d" -msgstr "неправилно действие за край на ред (crlf_action): %d" - -#, c-format -msgid "CRLF would be replaced by LF in %s" -msgstr "всяка последователност от знаци „CRLF“ ще бъдe заменена с „LF“ в „%s“." - -#, c-format -msgid "" -"in the working copy of '%s', CRLF will be replaced by LF the next time Git " -"touches it" -msgstr "" -"следващия път, когато git работи в работното копие на „%s“, всяка " -"последователност от знаци „CRLF“ ще бъдe заменена с „LF“" +msgid "" +"in the working copy of '%s', CRLF will be replaced by LF the next time Git " +"touches it" +msgstr "" +"следващия път, когато git работи в работното копие на „%s“, всяка " +"последователност от знаци „CRLF“ ще бъдe заменена с „LF“" #, c-format msgid "LF would be replaced by CRLF in %s" @@ -15613,6 +15709,32 @@ msgstr "" msgid "Marked %d islands, done.\n" msgstr "Отбелязани са %d групи, работата приключи.\n" +#, c-format +msgid "invalid --%s value '%s'" +msgstr "неправилна стойност за „--%s“: „%s“" + +#, c-format +msgid "could not archive missing directory '%s'" +msgstr "директорията „%s“ липсва и не може да се архивира" + +#, c-format +msgid "could not open directory '%s'" +msgstr "директорията „%s“ не може да бъде отворена" + +#, c-format +msgid "skipping '%s', which is neither file nor directory" +msgstr "„%s“ се прескача, защото не е нито файл, нито директория" + +msgid "could not duplicate stdout" +msgstr "стандартният изход не може да се дублира" + +#, c-format +msgid "could not add directory '%s' to archiver" +msgstr "директорията „%s“ не може да бъде добавена към архива" + +msgid "failed to write archive" +msgstr "неуспешен запис на архива" + msgid "--merge-base does not work with ranges" msgstr "опцията „--merge-base“ не работи с диапазони" @@ -16242,6 +16364,9 @@ msgstr "git fetch-pack: очаква се „ACK“/„NAK“, а бе полу msgid "unable to write to remote" msgstr "невъзможно писане към отдалечено хранилище" +msgid "Server supports filter" +msgstr "Сървърът поддържа филтри" + #, c-format msgid "invalid shallow line: %s" msgstr "неправилен плитък ред: „%s“" @@ -16352,9 +16477,6 @@ msgstr "сървърът не поддържа алгоритъм „%s“" msgid "Server does not support shallow requests" msgstr "Сървърът не поддържа плитки заявки" -msgid "Server supports filter" -msgstr "Сървърът поддържа филтри" - msgid "unable to write request to remote" msgstr "невъзможно писане към отдалечено хранилище" @@ -16710,6 +16832,12 @@ msgstr "Команди от ниско ниво / Синхронизация н msgid "Low-level Commands / Internal Helpers" msgstr "Команди от ниско ниво / Допълнителни инструменти" +msgid "User-facing repository, command and file interfaces" +msgstr "Команди за хранилища, файлове и др. интерфейси за потребители:" + +msgid "Developer-facing file formats, protocols and other interfaces" +msgstr "Файлови формати, протоколи и др. интерфейси за програмисти:" + #, c-format msgid "available git commands in '%s'" msgstr "налични команди на git от „%s“" @@ -16723,6 +16851,12 @@ msgstr "Това са най-често използваните команди msgid "The Git concept guides are:" msgstr "Ръководствата за концепциите в Git са:" +msgid "User-facing repository, command and file interfaces:" +msgstr "Команди за хранилища, файлове и др. за потребители:" + +msgid "File formats, protocols and other developer interfaces:" +msgstr "Файлови формати, протоколи и др. за програмисти:" + msgid "External commands" msgstr "Външни команди" @@ -16810,10 +16944,6 @@ msgstr "" "За да изключите това предупреждение, изпълнете:\n" " git config advice.ignoredHook false" -#, c-format -msgid "Couldn't start hook '%s'\n" -msgstr "Куката „%s“ не може да се стартира\n" - #, c-format msgid "argument to --packfile must be a valid hash (got '%s')" msgstr "опцията „--packfile“ изисква валидна контролна сума (а не „%s“)" @@ -17005,18 +17135,14 @@ msgstr "след аргументите към „ls-refs“ се очаква msgid "quoted CRLF detected" msgstr "цитирани знаци CRLF" -#, c-format -msgid "" -"Your local changes to the following files would be overwritten by merge:\n" -" %s" -msgstr "" -"Сливането ще презапише локалните промѐни на тези файлове:\n" -" %s" - #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Неуспешно сливане на подмодула „%s“ (не е изтеглен)" +#, c-format +msgid "Failed to merge submodule %s (no merge base)" +msgstr "Неуспешно сливане на подмодула „%s“ (няма база за сливане)" + #, c-format msgid "Failed to merge submodule %s (commits not present)" msgstr "Неуспешно сливане на подмодула „%s“ (няма подавания)" @@ -17037,26 +17163,8 @@ msgstr "Неуспешно сливане на подмодула „%s“" #, c-format msgid "" -"Failed to merge submodule %s, but a possible merge resolution exists:\n" -"%s\n" -msgstr "" -"Неуспешно сливане на подмодула „%s“, но е открито възможно решение:\n" -"%s\n" - -#, c-format -msgid "" -"If this is correct simply add it to the index for example\n" -"by using:\n" -"\n" -" git update-index --cacheinfo 160000 %s \"%s\"\n" -"\n" -"which will accept this suggestion.\n" -msgstr "" -"Ако това е така, добавете го към индекса с команда като следната:\n" -"\n" -" git update-index --cacheinfo 160000 %s \"%s\"\n" -"\n" -"Това приема предложеното.\n" +"Failed to merge submodule %s, but a possible merge resolution exists: %s" +msgstr "Неуспешно сливане на подмодула „%s“, но е открито възможно решение: %s" #, c-format msgid "" @@ -17219,13 +17327,45 @@ msgstr "" "КОНФЛИКТ (промяна/изтриване): „%s“ е изтрит в %s, а е променен в %s. Версия " "%s на „%s“ е оставена в дървото." +#. TRANSLATORS: This is a line of advice to resolve a merge +#. conflict in a submodule. The first argument is the submodule +#. name, and the second argument is the abbreviated id of the +#. commit that needs to be merged. For example: +#. - go to submodule (mysubmodule), and either merge commit abc1234" +#. #, c-format msgid "" -"Note: %s not up to date and in way of checking out conflicted version; old " -"copy renamed to %s" +" - go to submodule (%s), and either merge commit %s\n" +" or update to an existing commit which has merged those changes\n" msgstr "" -"Бележка: „%s“ не е актуален и пречи за преминаването към версията с " -"конфликт. Старият вариант е преименуван на „%s“" +" ⁃ преминаване към подмодула (%s), след които или де се слее подаването\n" +" „%s“, или да се обновяви към съществуващо подаване, в които\n" +" тези промени са слети\n" + +#, c-format +msgid "" +"Recursive merging with submodules currently only supports trivial cases.\n" +"Please manually handle the merging of each conflicted submodule.\n" +"This can be accomplished with the following steps:\n" +"%s - come back to superproject and run:\n" +"\n" +" git add %s\n" +"\n" +" to record the above merge or update\n" +" - resolve any other conflicts in the superproject\n" +" - commit the resulting index in the superproject\n" +msgstr "" +"В момента рекурсивното сливане с модули поддържа само тривиалните случаи.\n" +"Ще трябва ръчно да разрешавате конфликтите във всеки модул, в който се " +"появят.\n" +"Това може да се постигне чрез следните стъпки:\n" +"%s — върнете се към обхващащия проект и изпълнете:\n" +"\n" +" git add %s\n" +"\n" +" за да отбележите сливането или обновяването\n" +" ⁃ решете конфликтите в обхващащия проект\n" +" ⁃ подайте индекса в обхващащия проект\n" #. TRANSLATORS: The %s arguments are: 1) tree hash of a merge #. base, and 2-3) the trees for the two trees we're merging. @@ -17305,6 +17445,21 @@ msgstr "" "Открито е сливане, което може да решава проблема със сливането на " "подмодула:\n" +#, c-format +msgid "" +"If this is correct simply add it to the index for example\n" +"by using:\n" +"\n" +" git update-index --cacheinfo 160000 %s \"%s\"\n" +"\n" +"which will accept this suggestion.\n" +msgstr "" +"Ако това е така, добавете го към индекса с команда като следната:\n" +"\n" +" git update-index --cacheinfo 160000 %s \"%s\"\n" +"\n" +"Това приема предложеното.\n" + #, c-format msgid "Failed to merge submodule %s (multiple merges found)" msgstr "Неуспешно сливане на подмодула „%s“ (открити са множество сливания)" @@ -17546,7 +17701,8 @@ msgstr "неправилен ред: „%s“." msgid "ignoring existing multi-pack-index; checksum mismatch" msgstr "" -"индексът за множество пакети се прескача, защото контролната сума не съвпада" +"индексът за множество пакети се прескача, защото сумата за проверка не " +"съвпада" msgid "could not load pack" msgstr "пакетът не може да се зареди" @@ -17596,7 +17752,7 @@ msgid "multi-pack-index file exists, but failed to parse" msgstr "файлът с индекса за множество пакети, но не може да бъде анализиран" msgid "incorrect checksum" -msgstr "неправилна контролна сума" +msgstr "неправилна сума за проверка" msgid "Looking for referenced packfiles" msgstr "Търсене на указаните пакетни файлове" @@ -17851,6 +18007,22 @@ msgstr "неуспешно приключване на „deflate“ върху msgid "confused by unstable object source data for %s" msgstr "грешка поради нестабилния източник данни за обектите „%s“" +#, c-format +msgid "write stream object %ld != %" +msgstr "при записа на обект в потока: %ld != %" + +#, c-format +msgid "unable to stream deflate new object (%d)" +msgstr "новият обект не може да се компресира поточно с „deflate“ (%d)" + +#, c-format +msgid "deflateEnd on stream object failed (%d)" +msgstr "неуспешно изпълнение на „deflateEnd“ върху поточния обект: %d" + +#, c-format +msgid "unable to create directory %s" +msgstr "директорията „%s“ не може да бъде създадена" + #, c-format msgid "cannot read object for %s" msgstr "обектът за „%s“ не може да се прочете" @@ -18037,63 +18209,178 @@ msgid "" "path '%s' is in the index, but not '%s'\n" "hint: Did you mean ':%d:%s' aka ':%d:./%s'?" msgstr "" -"пътят „%s“ е в индекса, но не в „%s“\n" -"Пробвайте с „%d:%s“, което е същото като „%d:./%s“." +"пътят „%s“ е в индекса, но не в „%s“\n" +"Пробвайте с „%d:%s“, което е същото като „%d:./%s“." + +#, c-format +msgid "path '%s' exists on disk, but not in the index" +msgstr "пътят „%s“ съществува на диска, но не е в индекса" + +#, c-format +msgid "path '%s' does not exist (neither on disk nor in the index)" +msgstr "пътят „%s“ не съществува нито на диска, нито в индекса" + +msgid "relative path syntax can't be used outside working tree" +msgstr "относителен път не може да се ползва извън работното дърво" + +#, c-format +msgid ": required, only '%s' given" +msgstr "необходими са и двете — ОБЕКТ:ПЪТ, а е даден само ОБЕКТът „%s“" + +#, c-format +msgid "invalid object name '%.*s'." +msgstr "неправилно име на обект: „%.*s“" + +#, c-format +msgid "invalid object type \"%s\"" +msgstr "неправилен вид обект: „%s“" + +#, c-format +msgid "object %s is a %s, not a %s" +msgstr "обектът „%s“ е %s, а не %s" + +#, c-format +msgid "object %s has unknown type id %d" +msgstr "обектът „%s“ е непознат вид: %d" + +#, c-format +msgid "unable to parse object: %s" +msgstr "обектът „%s“ не може да бъде анализиран" + +#, c-format +msgid "hash mismatch %s" +msgstr "разлика в контролната сума: „%s“" + +msgid "trying to write commit not in index" +msgstr "опит за записване на обект за подаване извън индекса" + +msgid "failed to load bitmap index (corrupted?)" +msgstr "" +"индексът на база битови маски не може да се зареди (възможно е да е повреден)" + +msgid "corrupted bitmap index (too small)" +msgstr "повреден индекс на база битови маски (прекалено малък)" + +msgid "corrupted bitmap index file (wrong header)" +msgstr "повреден индекс на база битови маски (неправилна заглавна част)" + +#, c-format +msgid "unsupported version '%d' for bitmap index file" +msgstr "версия „%d“ на файла за индекса на база битови маски не се поддържа" + +msgid "corrupted bitmap index file (too short to fit hash cache)" +msgstr "" +"повреден индекс на база битови маски (прекалено малък, за да напасне на кеша " +"за контролните суми)" + +msgid "corrupted bitmap index file (too short to fit lookup table)" +msgstr "" +"повреден файл за индекс на база битови маски (прекалено е малък дори и за " +"таблицата със съответствия)" + +#, c-format +msgid "duplicate entry in bitmap index: '%s'" +msgstr "повтарящ се запис в индекс на база битови маски: „%s“" + +#, c-format +msgid "corrupt ewah bitmap: truncated header for entry %d" +msgstr "" +"повредена битова маска във формат EWAH: отрязана заглавна част за запис %d" + +#, c-format +msgid "corrupt ewah bitmap: commit index %u out of range" +msgstr "" +"повредена битова маска във формат EWAH: индексът на подаването %u е извън " +"диапазона" + +msgid "corrupted bitmap pack index" +msgstr "повреден пакетен индекс на база битови маски" + +msgid "invalid XOR offset in bitmap pack index" +msgstr "неправилно отместване по XOR в пакетен индекс на база битови маски" + +msgid "cannot fstat bitmap file" +msgstr "не може да се получи информация за файла с битови маски с „fstat“" + +#, c-format +msgid "ignoring extra bitmap file: '%s'" +msgstr "игнориране на излишния файл с битови маски: „%s“" + +msgid "checksum doesn't match in MIDX and bitmap" +msgstr "" +"сумата за проверка се различава във файла с индекса за множество пакети и " +"битовата маска" + +msgid "multi-pack bitmap is missing required reverse index" +msgstr "задължителният обратен индекс липсва в многопакетната битова маска" #, c-format -msgid "path '%s' exists on disk, but not in the index" -msgstr "пътят „%s“ съществува на диска, но не е в индекса" +msgid "could not open pack %s" +msgstr "пакетът „%s“ не може да се отвори" #, c-format -msgid "path '%s' does not exist (neither on disk nor in the index)" -msgstr "пътят „%s“ не съществува нито на диска, нито в индекса" +msgid "preferred pack (%s) is invalid" +msgstr "предпочитаният пакет „%s“ е неправилен" -msgid "relative path syntax can't be used outside working tree" -msgstr "относителен път не може да се ползва извън работното дърво" +msgid "corrupt bitmap lookup table: triplet position out of index" +msgstr "" +"повредена таблица със съответствия: местоположението на тройката е извън " +"индекса" -#, c-format -msgid ": required, only '%s' given" -msgstr "необходими са и двете — ОБЕКТ:ПЪТ, а е даден само ОБЕКТът „%s“" +msgid "corrupt bitmap lookup table: xor chain exceeds entry count" +msgstr "" +"повредена таблица със съответствия: веригата за XOR е по-дълга от броя на " +"записите" #, c-format -msgid "invalid object name '%.*s'." -msgstr "неправилно име на обект: „%.*s“" +msgid "corrupt bitmap lookup table: commit index %u out of range" +msgstr "" +"повредена таблица със съответствия: индексът на подаването %u е извън " +"диапазона" #, c-format -msgid "invalid object type \"%s\"" -msgstr "неправилен вид обект: „%s“" +msgid "corrupt ewah bitmap: truncated header for bitmap of commit \"%s\"" +msgstr "" +"повредена битова маска във формат EWAH: отрязана заглавна част за битовата " +"маска на подаване „%s“" #, c-format -msgid "object %s is a %s, not a %s" -msgstr "обектът „%s“ е %s, а не %s" +msgid "object '%s' not found in type bitmaps" +msgstr "обектът „%s“ липсва в битовата маска на видовете" #, c-format -msgid "object %s has unknown type id %d" -msgstr "обектът „%s“ е непознат вид: %d" +msgid "object '%s' does not have a unique type" +msgstr "обектът „%s“ няма уникален вид" #, c-format -msgid "unable to parse object: %s" -msgstr "обектът „%s“ не може да бъде анализиран" +msgid "object '%s': real type '%s', expected: '%s'" +msgstr "обект „%s“: истинският му вид е „%s“, а се очакваше „%s“" #, c-format -msgid "hash mismatch %s" -msgstr "разлика в контролната сума: „%s“" +msgid "object not in bitmap: '%s'" +msgstr "обектът „%s“ липсва в индекса на база битови маски" -msgid "multi-pack bitmap is missing required reverse index" -msgstr "задължителният обратен индекс липсва в многопакетната битова маска" +msgid "failed to load bitmap indexes" +msgstr "неуспешно зареждане на индекси на база битови маски" -#, c-format -msgid "could not open pack %s" -msgstr "пакетът „%s“ не може да се отвори" +msgid "you must specify exactly one commit to test" +msgstr "трябва да укажете точно едно подаване за тестване" #, c-format -msgid "preferred pack (%s) is invalid" -msgstr "предпочитаният пакет „%s“ е неправилен" +msgid "commit '%s' doesn't have an indexed bitmap" +msgstr "подаването „%s“ няма индекс на база битови маски" + +msgid "mismatch in bitmap results" +msgstr "различие в резултатите от битовите маски" #, c-format -msgid "could not find %s in pack %s at offset %" +msgid "could not find '%s' in pack '%s' at offset %" msgstr "„%s“ липсва в пакет „%s“ при отместване %" +#, c-format +msgid "unable to get disk usage of '%s'" +msgstr "използваното място за съхранение на „%s“ не може да бъде получено" + #, c-format msgid "mtimes file %s is too small" msgstr "файлът с времето на промяна (mtime) „%s“ е твърде малък" @@ -18230,6 +18517,9 @@ msgstr "„--%s“ (с 2 тирета) ли имахте предвид?" msgid "alias of --%s" msgstr "псевдоним на „--%s“" +msgid "need a subcommand" +msgstr "необходима е подкоманда" + #, c-format msgid "unknown option `%s'" msgstr "непозната опция: „%s“" @@ -18683,9 +18973,12 @@ msgid "" "l, label