diff options
147 files changed, 1889 insertions, 792 deletions
@@ -27,6 +27,7 @@ Ben Walton <bdwalton@gmail.com> <bwalton@artsci.utoronto.ca> Benoit Sigoure <tsunanet@gmail.com> <tsuna@lrde.epita.fr> Bernt Hansen <bernt@norang.ca> <bernt@alumni.uwaterloo.ca> Brandon Casey <drafnel@gmail.com> <casey@nrlssc.navy.mil> +Brandon Williams <bwilliams.eng@gmail.com> <bmwill@google.com> brian m. carlson <sandals@crustytoothpaste.net> brian m. carlson <sandals@crustytoothpaste.net> <sandals@crustytoothpaste.ath.cx> Bryan Larsen <bryan@larsen.st> <bryan.larsen@gmail.com> diff --git a/Documentation/RelNotes/2.21.0.txt b/Documentation/RelNotes/2.21.0.txt index 8a5e53b429..ea78e22a24 100644 --- a/Documentation/RelNotes/2.21.0.txt +++ b/Documentation/RelNotes/2.21.0.txt @@ -24,6 +24,33 @@ UI, Workflows & Features object into account (e.g. a tag object would want to go under refs/tags/). + * "git checkout [<tree-ish>] path..." learned to report the number of + paths that have been checked out of the index or the tree-ish, + which gives it the same degree of noisy-ness as the case in which + the command checks out a branch. + + * "git quiltimport" learned "--keep-non-patch" option. + + * "git worktree remove" and "git worktree move" refused to work when + there is a submodule involved. This has been loosened to ignore + uninitialized submodules. + + * "git cherry-pick -m1" was forbidden when picking a non-merge + commit, even though there _is_ parent number 1 for such a commit. + This was done to avoid mistakes back when "cherry-pick" was about + picking a single commit, but is no longer useful with "cherry-pick" + that can pick a range of commits. Now the "-m$num" option is + allowed when picking any commit, as long as $num names an existing + parent of the commit. + + * Update "git multimail" from the upstream. + + * "git p4" update. + + * The "--format=<placeholder>" option of for-each-ref, branch and tag + learned to show a few more traits of objects that can be learned by + the object_info API. + Performance, Internal Implementation, Development Support etc. @@ -33,6 +60,23 @@ Performance, Internal Implementation, Development Support etc. * More codepaths become aware of working with in-core repository instance other than the default "the_repository". + * The "strncat()" function is now among the banned functions. + + * Portability updates for the HPE NonStop platform. + + * Earlier we added "-Wformat-security" to developer builds, assuming + that "-Wall" (which includes "-Wformat" which in turn is required + to use "-Wformat-security") is always in effect. This is not true + when config.mak.autogen is in use, unfortunately. This has been + fixed by unconditionally adding "-Wall" to developer builds. + + * The loose object cache used to optimize existence look-up has been + updated. + + * Flaky tests can now be repeatedly run under load with the + "--stress" option. + (merge fb7d1e3ac8 sg/stress-test later to maint). + Fixes since v2.20 ----------------- @@ -63,4 +107,82 @@ Fixes since v2.20 which has been corrected. (merge 02818a98d7 mk/http-backend-kill-children-before-exit later to maint). + * "git rev-list --exclude-promisor-objects" had to take an object + that does not exist locally (and is lazily available) from the + command line without barfing, but the code dereferenced NULL. + (merge 4cf67869b2 md/list-lazy-objects-fix later to maint). + + * The traversal over tree objects has learned to honor + ":(attr:label)" pathspec match, which has been implemented only for + enumerating paths on the filesystem. + (merge 5a0b97b34c nd/attr-pathspec-in-tree-walk later to maint). + + * BSD port updates. + (merge 4e3ecbd439 cb/openbsd-allows-reading-directory later to maint). + (merge b6bdc2a0f5 cb/t5004-empty-tar-archive-fix later to maint). + (merge 82cbc8cde2 cb/test-lint-cp-a later to maint). + + * Lines that begin with a certain keyword that come over the wire, as + well as lines that consist only of one of these keywords, ought to + be painted in color for easier eyeballing, but the latter was + broken ever since the feature was introduced in 2.19, which has + been corrected. + (merge 1f67290450 hn/highlight-sideband-keywords later to maint). + + * "git log -G<regex>" looked for a hunk in the "git log -p" patch + output that contained a string that matches the given pattern. + Optimize this code to ignore binary files, which by default will + not show any hunk that would match any pattern (unless textconv or + the --text option is in effect, that is). + (merge e0e7cb8080 tb/log-G-binary later to maint). + + * "git submodule update" ought to use a single job unless asked, but + by mistake used multiple jobs, which has been fixed. + (merge e3a9d1aca9 sb/submodule-fetchjobs-default-to-one later to maint). + + * "git stripspace" should be usable outside a git repository, but + under the "-s" or "-c" mode, it didn't. + (merge 957da75802 jn/stripspace-wo-repository later to maint). + + * Some of the documentation pages formatted incorrectly with + Asciidoctor, which have been fixed. + (merge b62eb1d2f4 ma/asciidoctor later to maint). + + * The core.worktree setting in a submodule repository should not be + pointing at a directory when the submodule loses its working tree + (e.g. getting deinit'ed), but the code did not properly maintain + this invariant. + + * With zsh, "git cmd path<TAB>" was completed to "git cmd path name" + when the completed path has a special character like SP in it, + without any attempt to keep "path name" a single filename. This + has been fixed to complete it to "git cmd path\ name" just like + Bash completion does. + + * The test suite tried to see if it is run under bash, but the check + itself failed under some other implementations of shell (notably + under NetBSD). This has been corrected. + (merge 54ea72f09c sg/test-bash-version-fix later to maint). + + * "git gc" and "git repack" did not close the open packfiles that + they found unneeded before removing them, which didn't work on a + platform incapable of removing an open file. This has been + corrected. + (merge 5bdece0d70 js/gc-repack-close-before-remove later to maint). + * Code cleanup, docfix, build fix, etc. + (merge 89ba9a79ae hb/t0061-dot-in-path-fix later to maint). + (merge d173e799ea sb/diff-color-moved-config-option-fixup later to maint). + (merge a8f5a59067 en/directory-renames-nothanks-doc-update later to maint). + (merge ec36c42a63 nd/indentation-fix later to maint). + (merge f116ee21cd do/gitweb-strict-export-conf-doc later to maint). + (merge 112ea42663 fd/gitweb-snapshot-conf-doc-fix later to maint). + (merge 1cadad6f65 tb/use-common-win32-pathfuncs-on-cygwin later to maint). + (merge 57e9dcaa65 km/rebase-doc-typofix later to maint). + (merge b8b4cb27e6 ds/gc-doc-typofix later to maint). + (merge 3b3357626e nd/style-opening-brace later to maint). + (merge b4583d5595 es/doc-worktree-guessremote-config later to maint). + (merge cce99cd8c6 ds/commit-graph-assert-missing-parents later to maint). + (merge 0650614982 cy/completion-typofix later to maint). + (merge 6881925ef5 rs/sha1-file-close-mapped-file-on-error later to maint). + (merge bd8d6f0def en/show-ref-doc-fix later to maint). diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index f079bf6b7e..331d250e04 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -64,3 +64,8 @@ instead of: ------------------------------------------- + Defaults to false. + +rebase.rescheduleFailedExec:: + Automatically reschedule `exec` commands that failed. This only makes + sense in interactive mode (or when an `--exec` option was provided). + This is the same as specifying the `--reschedule-failed-exec` option. diff --git a/Documentation/config/worktree.txt b/Documentation/config/worktree.txt index b853798fc2..048e349482 100644 --- a/Documentation/config/worktree.txt +++ b/Documentation/config/worktree.txt @@ -1,6 +1,6 @@ worktree.guessRemote:: - With `add`, if no branch argument, and neither of `-b` nor - `-B` nor `--detach` are given, the command defaults to + If no branch is specified and neither `-b` nor `-B` nor + `--detach` is used, then `git worktree add` defaults to creating a new branch from HEAD. If `worktree.guessRemote` is set to true, `worktree add` tries to find a remote-tracking branch whose name uniquely matches the new branch name. If diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 0378cd574e..b94d332f71 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -524,6 +524,8 @@ struct), and want to know the history of that block since it first came into being: use the feature iteratively to feed the interesting block in the preimage back into `-S`, and keep going until you get the very first version of the block. ++ +Binary files are searched as well. -G<regex>:: Look for differences whose patch text contains added/removed @@ -543,6 +545,9 @@ While `git log -G"regexec\(regexp"` will show this commit, `git log -S"regexec\(regexp" --pickaxe-regex` will not (because the number of occurrences of that string did not change). + +Unless `--text` is supplied patches of binary files without a textconv +filter will be ignored. ++ See the 'pickaxe' entry in linkgit:gitdiffcore[7] for more information. diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt index 763afabb6d..f58e9c43e6 100644 --- a/Documentation/git-column.txt +++ b/Documentation/git-column.txt @@ -47,7 +47,7 @@ OPTIONS The number of spaces between columns. One space by default. EXAMPLES ------- +-------- Format data by columns: ------------ diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 901faef1bf..774cecc7ed 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -128,13 +128,18 @@ objecttype:: objectsize:: The size of the object (the same as 'git cat-file -s' reports). - + Append `:disk` to get the size, in bytes, that the object takes up on + disk. See the note about on-disk sizes in the `CAVEATS` section below. objectname:: The object name (aka SHA-1). For a non-ambiguous abbreviation of the object name append `:short`. For an abbreviation of the object name with desired length append `:short=<length>`, where the minimum length is MINIMUM_ABBREV. The length may be exceeded to ensure unique object names. +deltabase:: + This expands to the object name of the delta base for the + given object, if it is stored as a delta. Otherwise it + expands to the null object name (all zeroes). upstream:: The name of a local ref which can be considered ``upstream'' @@ -361,6 +366,20 @@ This prints the authorname, if present. git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)" ------------ +CAVEATS +------- + +Note that the sizes of objects on disk are reported accurately, but care +should be taken in drawing conclusions about which refs or objects are +responsible for disk usage. The size of a packed non-delta object may be +much larger than the size of objects which delta against it, but the +choice of which object is the base and which is the delta is arbitrary +and is subject to change during a repack. + +Note also that multiple copies of an object may be present in the object +database; in this case, it is undefined which copy's size or delta base +will be reported. + SEE ALSO -------- linkgit:git-show-ref[1] diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index c20ee6c789..a7442499f6 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -137,7 +137,7 @@ The optional configuration variable `gc.packRefs` determines if it within all non-bare repos or it can be set to a boolean value. This defaults to true. -The optional configuration variable `gc.commitGraph` determines if +The optional configuration variable `gc.writeCommitGraph` determines if 'git gc' should run 'git commit-graph write'. This can be set to a boolean value. This defaults to false. diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 3c5a67fb96..057076ca38 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -38,8 +38,6 @@ the repository to another place if --separate-git-dir is given). OPTIONS ------- --- - -q:: --quiet:: @@ -111,8 +109,6 @@ into it. If you provide a 'directory', the command is run inside it. If this directory does not exist, it will be created. --- - TEMPLATE DIRECTORY ------------------ diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index 8cf952b4de..70562dc4c0 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>] - [--series <file>] + [--series <file>] [--keep-non-patch] DESCRIPTION @@ -56,6 +56,9 @@ The default for the series file is <patches>/series or the value of the `$QUILT_SERIES` environment variable. +--keep-non-patch:: + Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index dff17b3178..4dd5853d6e 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -462,6 +462,12 @@ without an explicit `--interactive`. + See also INCOMPATIBLE OPTIONS below. +-y <cmd>:: + This is the same as passing `--reschedule-failed-exec` before + `-x <cmd>`, i.e. it appends the specified `exec` command and + turns on the mode where failed `exec` commands are automatically + rescheduled. + --root:: Rebase all commits reachable from <branch>, instead of limiting them with an <upstream>. This allows you to rebase @@ -501,6 +507,11 @@ See also INCOMPATIBLE OPTIONS below. with care: the final stash application after a successful rebase might result in non-trivial conflicts. +--reschedule-failed-exec:: +--no-reschedule-failed-exec:: + Automatically reschedule `exec` commands that failed. This only makes + sense in interactive mode (or when an `--exec` option was provided). + INCOMPATIBLE OPTIONS -------------------- @@ -570,8 +581,9 @@ it to keep commits that started empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~ -The merge and interactive backends work fine with -directory rename detection. The am backend sometimes does not. +Directory rename heuristics are enabled in the merge and interactive +backends. Due to the lack of accurate tree information, directory +rename detection is disabled in the am backend. include::merge-strategies.txt[] @@ -979,7 +991,7 @@ when the merge operation did not even start), it is rescheduled immediately. At this time, the `merge` command will *always* use the `recursive` merge strategy for regular merges, and `octopus` for octopus merges, -strategy, with no way to choose a different one. To work around +with no way to choose a different one. To work around this, an `exec` command can be used to call `git merge` explicitly, using the fact that the labels are worktree-local refs (the ref `refs/rewritten/onto` would correspond to the label `onto`, for example). diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index d28e6154c6..ab4d271925 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -37,8 +37,8 @@ OPTIONS Show the HEAD reference, even if it would normally be filtered out. ---tags:: --heads:: +--tags:: Limit to "refs/heads" and "refs/tags", respectively. These options are not mutually exclusive; when given both, references stored in diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index d9f422d560..861d821d7f 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -197,31 +197,33 @@ codes can be interpreted as follows: Ignored files are not listed, unless `--ignored` option is in effect, in which case `XY` are `!!`. - X Y Meaning - ------------------------------------------------- - [AMD] not updated - M [ MD] updated in index - A [ MD] added to index - D deleted from index - R [ MD] renamed in index - C [ MD] copied in index - [MARC] index and work tree matches - [ MARC] M work tree changed since index - [ MARC] D deleted in work tree - [ D] R renamed in work tree - [ D] C copied in work tree - ------------------------------------------------- - D D unmerged, both deleted - A U unmerged, added by us - U D unmerged, deleted by them - U A unmerged, added by them - D U unmerged, deleted by us - A A unmerged, both added - U U unmerged, both modified - ------------------------------------------------- - ? ? untracked - ! ! ignored - ------------------------------------------------- +.... +X Y Meaning +------------------------------------------------- + [AMD] not updated +M [ MD] updated in index +A [ MD] added to index +D deleted from index +R [ MD] renamed in index +C [ MD] copied in index +[MARC] index and work tree matches +[ MARC] M work tree changed since index +[ MARC] D deleted in work tree +[ D] R renamed in work tree +[ D] C copied in work tree +------------------------------------------------- +D D unmerged, both deleted +A U unmerged, added by us +U D unmerged, deleted by them +U A unmerged, added by them +D U unmerged, deleted by us +A A unmerged, both added +U U unmerged, both modified +------------------------------------------------- +? ? untracked +! ! ignored +------------------------------------------------- +.... Submodules have more state and instead report M the submodule has a different HEAD than @@ -281,14 +283,16 @@ don't recognize. If `--branch` is given, a series of header lines are printed with information about the current branch. - Line Notes - ------------------------------------------------------------ - # branch.oid <commit> | (initial) Current commit. - # branch.head <branch> | (detached) Current branch. - # branch.upstream <upstream_branch> If upstream is set. - # branch.ab +<ahead> -<behind> If upstream is set and - the commit is present. - ------------------------------------------------------------ +.... +Line Notes +------------------------------------------------------------ +# branch.oid <commit> | (initial) Current commit. +# branch.head <branch> | (detached) Current branch. +# branch.upstream <upstream_branch> If upstream is set. +# branch.ab +<ahead> -<behind> If upstream is set and + the commit is present. +------------------------------------------------------------ +.... ### Changed Tracked Entries @@ -306,56 +310,60 @@ Renamed or copied entries have the following format: 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath> - Field Meaning - -------------------------------------------------------- - <XY> A 2 character field containing the staged and - unstaged XY values described in the short format, - with unchanged indicated by a "." rather than - a space. - <sub> A 4 character field describing the submodule state. - "N..." when the entry is not a submodule. - "S<c><m><u>" when the entry is a submodule. - <c> is "C" if the commit changed; otherwise ".". - <m> is "M" if it has tracked changes; otherwise ".". - <u> is "U" if there are untracked changes; otherwise ".". - <mH> The octal file mode in HEAD. - <mI> The octal file mode in the index. - <mW> The octal file mode in the worktree. - <hH> The object name in HEAD. - <hI> The object name in the index. - <X><score> The rename or copy score (denoting the percentage - of similarity between the source and target of the - move or copy). For example "R100" or "C75". - <path> The pathname. In a renamed/copied entry, this - is the target path. - <sep> When the `-z` option is used, the 2 pathnames are separated - with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) - byte separates them. - <origPath> The pathname in the commit at HEAD or in the index. - This is only present in a renamed/copied entry, and - tells where the renamed/copied contents came from. - -------------------------------------------------------- +.... +Field Meaning +-------------------------------------------------------- +<XY> A 2 character field containing the staged and + unstaged XY values described in the short format, + with unchanged indicated by a "." rather than + a space. +<sub> A 4 character field describing the submodule state. + "N..." when the entry is not a submodule. + "S<c><m><u>" when the entry is a submodule. + <c> is "C" if the commit changed; otherwise ".". + <m> is "M" if it has tracked changes; otherwise ".". + <u> is "U" if there are untracked changes; otherwise ".". +<mH> The octal file mode in HEAD. +<mI> The octal file mode in the index. +<mW> The octal file mode in the worktree. +<hH> The object name in HEAD. +<hI> The object name in the index. +<X><score> The rename or copy score (denoting the percentage + of similarity between the source and target of the + move or copy). For example "R100" or "C75". +<path> The pathname. In a renamed/copied entry, this + is the target path. +<sep> When the `-z` option is used, the 2 pathnames are separated + with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) + byte separates them. +<origPath> The pathname in the commit at HEAD or in the index. + This is only present in a renamed/copied entry, and + tells where the renamed/copied contents came from. +-------------------------------------------------------- +.... Unmerged entries have the following format; the first character is a "u" to distinguish from ordinary changed entries. u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> - Field Meaning - -------------------------------------------------------- - <XY> A 2 character field describing the conflict type - as described in the short format. - <sub> A 4 character field describing the submodule state - as described above. - <m1> The octal file mode in stage 1. - <m2> The octal file mode in stage 2. - <m3> The octal file mode in stage 3. - <mW> The octal file mode in the worktree. - <h1> The object name in stage 1. - <h2> The object name in stage 2. - <h3> The object name in stage 3. - <path> The pathname. - -------------------------------------------------------- +.... +Field Meaning +-------------------------------------------------------- +<XY> A 2 character field describing the conflict type + as described in the short format. +<sub> A 4 character field describing the submodule state + as described above. +<m1> The octal file mode in stage 1. +<m2> The octal file mode in stage 2. +<m3> The octal file mode in stage 3. +<mW> The octal file mode in the worktree. +<h1> The object name in stage 1. +<h2> The object name in stage 2. +<h3> The object name in stage 3. +<path> The pathname. +-------------------------------------------------------- +.... ### Other Items diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index c0a60f3158..c970d9fe43 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -242,7 +242,8 @@ textual diff has an added or a deleted line that matches the given regular expression. This means that it will detect in-file (or what rename-detection considers the same file) moves, which is noise. The implementation runs diff twice and greps, and this can be quite -expensive. +expensive. To speed things up binary files without textconv filters +will be ignored. When `-S` or `-G` are used without `--pickaxe-all`, only filepairs that match their respective criterion are kept in the output. When diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt index c0a326e388..92535dbac5 100644 --- a/Documentation/gitweb.conf.txt +++ b/Documentation/gitweb.conf.txt @@ -207,8 +207,8 @@ subsection on linkgit:gitweb[1] manpage. $strict_export:: Only allow viewing of repositories also shown on the overview page. - This for example makes `$gitweb_export_ok` file decide if repository is - available and not only if it is shown. If `$gitweb_list` points to + This for example makes `$export_ok` file decide if repository is + available and not only if it is shown. If `$projects_list` points to file with list of project, only those repositories listed would be available for gitweb. Can be set during building gitweb via `GITWEB_STRICT_EXPORT`. By default this variable is not set, which @@ -684,7 +684,7 @@ compressed tar archive) and "zip"; please consult gitweb sources for a definitive list. By default only "tgz" is offered. + This feature can be configured on a per-repository basis via -repository's `gitweb.blame` configuration variable, which contains +repository's `gitweb.snapshot` configuration variable, which contains a comma separated list of formats or "none" to disable snapshots. Unknown values are ignored. diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 0d2aa48c63..023ca95e7c 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -404,6 +404,8 @@ these forms: - "`!ATTR`" requires that the attribute `ATTR` be unspecified. + +Note that when matching against a tree object, attributes are still +obtained from working tree, not from the given tree object. exclude;; After a path matches any non-exclude pathspec, it will be run diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index bab5f50b17..98b538bc77 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -13,8 +13,6 @@ has a line that matches `<pattern>`), unless otherwise noted. Note that these are applied before commit ordering and formatting options, such as `--reverse`. --- - -<number>:: -n <number>:: --max-count=<number>:: @@ -308,8 +306,6 @@ ifdef::git-rev-list[] `<header>` text will be printed with each progress update. endif::git-rev-list[] --- - History Simplification ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt index 1ef66bd788..896c7b3878 100644 --- a/Documentation/technical/partial-clone.txt +++ b/Documentation/technical/partial-clone.txt @@ -114,7 +114,7 @@ their "<name>.pack" and "<name>.idx" files. that it has, either because the local repository has that object in one of its promisor packfiles, or because another promisor object refers to it. + -When Git encounters a missing object, Git can see if it a promisor object +When Git encounters a missing object, Git can see if it is a promisor object and handle it appropriately. If not, Git can report a corruption. + This means that there is no need for the client to explicitly maintain an @@ -3352,7 +3352,8 @@ static int checkout_target(struct index_state *istate, costate.refresh_cache = 1; costate.istate = istate; - if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) + if (checkout_entry(ce, &costate, NULL, NULL) || + lstat(ce->name, st)) return error(_("cannot checkout %s"), ce->name); return 0; } diff --git a/archive-tar.c b/archive-tar.c index a58e1a8ebf..4aabd566fb 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -142,7 +142,7 @@ static int stream_blocked(const struct object_id *oid) * string and appends it to a struct strbuf. */ static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) + const char *value, unsigned int valuelen) { int len, tmp; @@ -36,8 +36,8 @@ void init_archivers(void) } static void format_subst(const struct commit *commit, - const char *src, size_t len, - struct strbuf *buf) + const char *src, size_t len, + struct strbuf *buf) { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; @@ -285,7 +285,8 @@ int write_archive_entries(struct archiver_args *args, git_attr_set_direction(GIT_ATTR_INDEX); } - err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec, + err = read_tree_recursive(args->repo, args->tree, "", + 0, 0, &args->pathspec, queue_or_write_archive_entry, &context); if (err == READ_TREE_RECURSIVE) @@ -346,7 +347,8 @@ static int path_exists(struct archiver_args *args, const char *path) ctx.args = args; parse_pathspec(&ctx.pathspec, 0, 0, "", paths); ctx.pathspec.recursive = 1; - ret = read_tree_recursive(args->tree, "", 0, 0, &ctx.pathspec, + ret = read_tree_recursive(args->repo, args->tree, "", + 0, 0, &ctx.pathspec, reject_entry, &ctx); clear_pathspec(&ctx.pathspec); return ret != 0; @@ -16,6 +16,8 @@ #define strcat(x,y) BANNED(strcat) #undef strncpy #define strncpy(x,y,n) BANNED(strncpy) +#undef strncat +#define strncat(x,y,n) BANNED(strncat) #undef sprintf #undef vsprintf @@ -558,7 +558,8 @@ struct commit_list *filter_skipped(struct commit_list *list, * is increased by one between each call, but that should not matter * for this application. */ -static unsigned get_prn(unsigned count) { +static unsigned get_prn(unsigned count) +{ count = count * 1103515245 + 12345; return (count/65536) % PRN_MODULO; } diff --git a/builtin/add.c b/builtin/add.c index f65c172299..12247b48fd 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -176,7 +176,7 @@ static void refresh(int verbose, const struct pathspec *pathspec) die(_("pathspec '%s' did not match any files"), pathspec->items[i].match); } - free(seen); + free(seen); } int run_add_interactive(const char *revision, const char *patch_mode, diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index eb74774cbc..a2a726ad8d 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -67,7 +67,8 @@ static int checkout_file(const char *name, const char *prefix) continue; did_checkout = 1; if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; } @@ -111,7 +112,8 @@ static void checkout_all(const char *prefix, int prefix_length) write_tempfile_record(last_ce->name, prefix); } if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; last_ce = ce; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 08b0ac48f3..6fadf412e8 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -44,6 +44,7 @@ struct checkout_opts { int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + int count_checkout_paths; /* * If new checkout options are added, skip_merge_working_tree * should be updated accordingly. @@ -115,7 +116,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base, static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + read_tree_recursive(the_repository, tree, "", 0, 0, + pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -165,12 +167,13 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) } static int checkout_stage(int stage, const struct cache_entry *ce, int pos, - const struct checkout *state) + const struct checkout *state, int *nr_checkouts) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { if (ce_stage(active_cache[pos]) == stage) - return checkout_entry(active_cache[pos], state, NULL); + return checkout_entry(active_cache[pos], state, + NULL, nr_checkouts); pos++; } if (stage == 2) @@ -179,7 +182,7 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, const struct checkout *state) +static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; @@ -242,7 +245,7 @@ static int checkout_merged(int pos, const struct checkout *state) ce = make_transient_cache_entry(mode, &oid, path, 2); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); - status = checkout_entry(ce, state, NULL); + status = checkout_entry(ce, state, NULL, nr_checkouts); discard_cache_entry(ce); return status; } @@ -257,6 +260,7 @@ static int checkout_paths(const struct checkout_opts *opts, struct commit *head; int errs = 0; struct lock_file lock_file = LOCK_INIT; + int nr_checkouts = 0; if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with updating paths"), "--track"); @@ -371,17 +375,36 @@ static int checkout_paths(const struct checkout_opts *opts, struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { - errs |= checkout_entry(ce, &state, NULL); + errs |= checkout_entry(ce, &state, + NULL, &nr_checkouts); continue; } if (opts->writeout_stage) - errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); + errs |= checkout_stage(opts->writeout_stage, + ce, pos, + &state, &nr_checkouts); else if (opts->merge) - errs |= checkout_merged(pos, &state); + errs |= checkout_merged(pos, &state, + &nr_checkouts); pos = skip_same_name(ce, pos) - 1; } } - errs |= finish_delayed_checkout(&state); + errs |= finish_delayed_checkout(&state, &nr_checkouts); + + if (opts->count_checkout_paths) { + if (opts->source_tree) + fprintf_ln(stderr, Q_("Checked out %d path out of %s", + "Checked out %d paths out of %s", + nr_checkouts), + nr_checkouts, + find_unique_abbrev(&opts->source_tree->object.oid, + DEFAULT_ABBREV)); + else + fprintf_ln(stderr, Q_("Checked out %d path out of the index", + "Checked out %d paths out of the index", + nr_checkouts), + nr_checkouts); + } if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); @@ -1065,6 +1088,7 @@ static int parse_branchname_arg(int argc, const char **argv, has_dash_dash = 1; /* case (3) or (1) */ else if (dash_dash_pos >= 2) die(_("only one reference expected, %d given."), dash_dash_pos); + opts->count_checkout_paths = !opts->quiet && !has_dash_dash; if (!strcmp(arg, "-")) arg = "@{-1}"; diff --git a/builtin/config.c b/builtin/config.c index 84385ef165..99bc7ef64e 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -164,7 +164,8 @@ static NORETURN void usage_builtin_config(void) usage_with_options(builtin_config_usage, builtin_config_options); } -static void check_argc(int argc, int min, int max) { +static void check_argc(int argc, int min, int max) +{ if (argc >= min && argc <= max) return; if (min == max) diff --git a/builtin/difftool.c b/builtin/difftool.c index 544b0e8639..71318c26e1 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -323,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid, int ret; ce = make_transient_cache_entry(mode, oid, path, 0); - ret = checkout_entry(ce, state, NULL); + ret = checkout_entry(ce, state, NULL, NULL); discard_cache_entry(ce); return ret; diff --git a/builtin/fetch.c b/builtin/fetch.c index e0140327aa..c0ade48f5d 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1478,7 +1478,8 @@ static inline void fetch_one_setup_partial(struct remote *remote) */ if (strcmp(remote->name, repository_format_partial_clone)) { if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); + die(_("--filter can only be used with the remote " + "configured in extensions.partialclone")); return; } @@ -1646,7 +1647,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) result = fetch_one(remote, argc, argv, prune_tags_ok); } else { if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); + die(_("--filter can only be used with the remote " + "configured in extensions.partialclone")); /* TODO should this also die if we have a previous partial-clone? */ result = fetch_multiple(&list); } diff --git a/builtin/gc.c b/builtin/gc.c index 871a56f1c5..020f725acc 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -317,7 +317,7 @@ static void add_repack_all_option(struct string_list *keep_pack) static void add_repack_incremental_option(void) { - argv_array_push(&repack, "--no-write-bitmap-index"); + argv_array_push(&repack, "--no-write-bitmap-index"); } static int need_to_gc(void) @@ -659,8 +659,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); - if (pack_garbage.nr > 0) + if (pack_garbage.nr > 0) { + close_all_packs(the_repository->objects); clean_pack_garbage(); + } if (gc_write_commit_graph) write_commit_graph_reachable(get_object_directory(), 0, diff --git a/builtin/grep.c b/builtin/grep.c index bad9c0a3d5..4748195ae1 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -553,7 +553,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (match != all_entries_interesting) { strbuf_addstr(&name, base->buf + tn_len); - match = tree_entry_interesting(&entry, &name, + match = tree_entry_interesting(repo->index, + &entry, &name, 0, pathspec); strbuf_setlen(&name, name_base_len); diff --git a/builtin/log.c b/builtin/log.c index e8e51068bd..3e145fe502 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -641,8 +641,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, - show_tree_object, rev.diffopt.file); + read_tree_recursive(the_repository, (struct tree *)o, "", + 0, 0, &match_all, show_tree_object, + rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c70a9c7158..7e0dcaa359 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -441,7 +441,7 @@ void overlay_tree_on_index(struct index_state *istate, PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(tree, 1, &pathspec, istate)) + if (read_tree(the_repository, tree, 1, &pathspec, istate)) die("unable to read tree entries %s", tree_name); for (i = 0; i < istate->cache_nr; i++) { diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7d581d6463..7cad3f24eb 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -185,5 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); + return !!read_tree_recursive(the_repository, tree, "", 0, 0, + &pathspec, show_tree, NULL); } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 70f6fc9167..4984b7e12e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -346,7 +346,7 @@ static void merge_trees(struct tree_desc t[3], const char *base) setup_traverse_info(&info, base); info.fn = threeway_callback; - traverse_trees(3, t, &info); + traverse_trees(&the_index, 3, t, &info); } static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 24bba8147f..889df2c755 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3084,14 +3084,16 @@ static void record_recent_commit(struct commit *commit, void *data) static void get_object_list(int ac, const char **av) { struct rev_info revs; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; char line[1000]; int flags = 0; int save_warning; repo_init_revisions(the_repository, &revs, NULL); save_commit_buffer = 0; - revs.allow_exclude_promisor_objects_opt = 1; - setup_revisions(ac, av, &revs, NULL); + setup_revisions(ac, av, &revs, &s_r_opt); /* make sure shallows are read */ is_repository_shallow(the_repository); diff --git a/builtin/prune.c b/builtin/prune.c index e42653b99c..1ec9ddd751 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -120,7 +120,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; read_replace_refs = 0; ref_paranoia = 1; - revs.allow_exclude_promisor_objects_opt = 1; repo_init_revisions(the_repository, &revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); diff --git a/builtin/push.c b/builtin/push.c index ee1e842027..021dd3b1e4 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -143,7 +143,9 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { +static NORETURN int die_push_simple(struct branch *branch, + struct remote *remote) +{ /* * There's no point in using shorten_unambiguous_ref here, * as the ambiguity would be on the remote side, not what diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index dd2a55ab1d..c2c2d51a3b 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -193,6 +193,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")), OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), + OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END() }; diff --git a/builtin/rebase.c b/builtin/rebase.c index 00de70365e..26a6d0ea33 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -104,6 +104,7 @@ struct rebase_options { int rebase_merges, rebase_cousins; char *strategy, *strategy_opts; struct strbuf git_format_patch_opt; + int reschedule_failed_exec; }; static int is_interactive(struct rebase_options *opts) @@ -415,6 +416,8 @@ static int run_specific_rebase(struct rebase_options *opts) argv_array_push(&child.args, opts->gpg_sign_opt); if (opts->signoff) argv_array_push(&child.args, "--signoff"); + if (opts->reschedule_failed_exec) + argv_array_push(&child.args, "--reschedule-failed-exec"); status = run_command(&child); goto finished_rebase; @@ -674,6 +677,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.reschedulefailedexec")) { + opts->reschedule_failed_exec = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -746,6 +754,23 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, return 0; } +struct opt_y { + struct string_list *list; + struct rebase_options *options; +}; + +static int parse_opt_y(const struct option *opt, const char *arg, int unset) +{ + struct opt_y *o = opt->value; + + if (unset || !arg) + return -1; + + o->options->reschedule_failed_exec = 1; + string_list_append(o->list, arg); + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -826,6 +851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; + struct opt_y opt_y = { .list = &exec, .options = &options }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -903,6 +929,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + { OPTION_CALLBACK, 'y', NULL, &opt_y, N_("<cmd>"), + N_("same as --reschedule-failed-exec -x <cmd>"), + PARSE_OPT_NONEG, parse_opt_y }, OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), @@ -920,6 +949,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "strategy")), OPT_BOOL(0, "root", &options.root, N_("rebase all reachable commits up to the root(s)")), + OPT_BOOL(0, "reschedule-failed-exec", + &options.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END(), }; int i; @@ -1216,6 +1248,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.reschedule_failed_exec && !is_interactive(&options)) + die(_("--reschedule-failed-exec requires an interactive rebase")); + if (options.git_am_opts.argc) { /* all am options except -q are compatible only with --am */ for (i = options.git_am_opts.argc - 1; i >= 0; i--) @@ -1241,7 +1276,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } - if (options.type == REBASE_PRESERVE_MERGES) + if (options.type == REBASE_PRESERVE_MERGES) { /* * Note: incompatibility with --signoff handled in signoff block above * Note: incompatibility with --interactive is just a strong warning; @@ -1251,6 +1286,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("error: cannot combine '--preserve-merges' with " "'--rebase-merges'")); + if (options.reschedule_failed_exec) + die(_("error: cannot combine '--preserve-merges' with " + "'--reschedule-failed-exec'")); + } + if (options.rebase_merges) { if (strategy_options.nr) die(_("error: cannot combine '--rebase-merges' with " diff --git a/builtin/repack.c b/builtin/repack.c index 2a1c7b21c5..67f8978043 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -419,6 +419,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); + close_all_packs(the_repository->objects); + /* * Ok we have prepared all new packfiles. * First see if there are packs of the same name and if so diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 2880ed37e3..51e9e1267e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -361,6 +361,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct rev_list_info info; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; int i; int bisect_list = 0; int bisect_show_vars = 0; @@ -374,7 +377,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; - revs.allow_exclude_promisor_objects_opt = 1; revs.commit_format = CMIT_FMT_UNSPECIFIED; revs.do_not_die_on_missing_tree = 1; @@ -406,7 +408,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } } - argc = setup_revisions(argc, argv, &revs, NULL); + argc = setup_revisions(argc, argv, &revs, &s_r_opt); memset(&info, 0, sizeof(info)); info.revs = &revs; diff --git a/builtin/stripspace.c b/builtin/stripspace.c index bdf0328869..be33eb83c1 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -30,6 +30,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; enum stripspace_mode mode = STRIP_DEFAULT; + int nongit; const struct option options[] = { OPT_CMDMODE('s', "strip-comments", &mode, @@ -46,7 +47,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) usage_with_options(stripspace_usage, options); if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) { - setup_git_directory_gently(NULL); + setup_git_directory_gently(&nongit); git_config(git_default_config, NULL); } diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index b45514be31..6881b6a9cb 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1131,6 +1131,8 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_QUIET)) printf(format, displaypath); + submodule_unset_core_worktree(sub); + strbuf_release(&sb_rm); } @@ -1552,7 +1554,7 @@ struct submodule_update_clone { #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ NULL, NULL, NULL, \ - NULL, 0, 0, 0, NULL, 0, 0, 0} + NULL, 0, 0, 0, NULL, 0, 0, 1} static void next_submodule_warn_missing(struct submodule_update_clone *suc, @@ -2046,7 +2048,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) struct repository subrepo; if (argc != 2) - BUG("submodule--helper connect-gitdir-workingtree <name> <path>"); + BUG("submodule--helper ensure-core-worktree <path>"); path = argv[1]; diff --git a/builtin/worktree.c b/builtin/worktree.c index 5e84026177..3f9907fcc9 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -9,6 +9,7 @@ #include "refs.h" #include "run-command.h" #include "sigchain.h" +#include "submodule.h" #include "refs.h" #include "utf8.h" #include "worktree.h" @@ -724,20 +725,36 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) static void validate_no_submodules(const struct worktree *wt) { struct index_state istate = { NULL }; + struct strbuf path = STRBUF_INIT; int i, found_submodules = 0; - if (read_index_from(&istate, worktree_git_path(wt, "index"), - get_worktree_git_dir(wt)) > 0) { + if (is_directory(worktree_git_path(wt, "modules"))) { + /* + * There could be false positives, e.g. the "modules" + * directory exists but is empty. But it's a rare case and + * this simpler check is probably good enough for now. + */ + found_submodules = 1; + } else if (read_index_from(&istate, worktree_git_path(wt, "index"), + get_worktree_git_dir(wt)) > 0) { for (i = 0; i < istate.cache_nr; i++) { struct cache_entry *ce = istate.cache[i]; + int err; - if (S_ISGITLINK(ce->ce_mode)) { - found_submodules = 1; - break; - } + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", wt->path, ce->name); + if (!is_submodule_populated_gently(path.buf, &err)) + continue; + + found_submodules = 1; + break; } } discard_index(&istate); + strbuf_release(&path); if (found_submodules) die(_("working trees containing submodules cannot be moved or removed")); diff --git a/cache-tree.c b/cache-tree.c index 190c6e5aa6..eabb8fb654 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -448,7 +448,7 @@ int cache_tree_update(struct index_state *istate, int flags) } static void write_one(struct strbuf *buffer, struct cache_tree *it, - const char *path, int pathlen) + const char *path, int pathlen) { int i; @@ -1539,9 +1539,9 @@ struct checkout { #define CHECKOUT_INIT { NULL, "" } #define TEMPORARY_FILENAME_LENGTH 25 -extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); +extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts); extern void enable_delayed_checkout(struct checkout *state); -extern int finish_delayed_checkout(struct checkout *state); +extern int finish_delayed_checkout(struct checkout *state, int *nr_checkouts); struct cache_def { struct strbuf path; diff --git a/commit-graph.c b/commit-graph.c index 99163c244b..0d6ba6da2d 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -34,7 +34,6 @@ #define GRAPH_OID_LEN GRAPH_OID_LEN_SHA1 #define GRAPH_OCTOPUS_EDGES_NEEDED 0x80000000 -#define GRAPH_PARENT_MISSING 0x7fffffff #define GRAPH_EDGE_LAST_MASK 0x7fffffff #define GRAPH_PARENT_NONE 0x70000000 @@ -493,7 +492,9 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len, commit_to_sha1); if (edge_value < 0) - edge_value = GRAPH_PARENT_MISSING; + BUG("missing parent %s for commit %s", + oid_to_hex(&parent->item->object.oid), + oid_to_hex(&(*list)->object.oid)); } hashwrite_be32(f, edge_value); @@ -511,7 +512,9 @@ static void write_graph_chunk_data(struct hashfile *f, int hash_len, nr_commits, commit_to_sha1); if (edge_value < 0) - edge_value = GRAPH_PARENT_MISSING; + BUG("missing parent %s for commit %s", + oid_to_hex(&parent->item->object.oid), + oid_to_hex(&(*list)->object.oid)); } hashwrite_be32(f, edge_value); @@ -564,7 +567,9 @@ static void write_graph_chunk_large_edges(struct hashfile *f, commit_to_sha1); if (edge_value < 0) - edge_value = GRAPH_PARENT_MISSING; + BUG("missing parent %s for commit %s", + oid_to_hex(&parent->item->object.oid), + oid_to_hex(&(*list)->object.oid)); else if (!parent->next) edge_value |= GRAPH_LAST_EDGE; @@ -638,26 +643,29 @@ static void add_missing_parents(struct packed_oid_list *oids, struct commit *com static void close_reachable(struct packed_oid_list *oids, int report_progress) { - int i; + int i, j; struct commit *commit; struct progress *progress = NULL; - int j = 0; if (report_progress) progress = start_delayed_progress( - _("Annotating commits in commit graph"), 0); + _("Loading known commits in commit graph"), j = 0); for (i = 0; i < oids->nr; i++) { display_progress(progress, ++j); commit = lookup_commit(the_repository, &oids->list[i]); if (commit) commit->object.flags |= UNINTERESTING; } + stop_progress(&progress); /* * As this loop runs, oids->nr may grow, but not more * than the number of missing commits in the reachable * closure. */ + if (report_progress) + progress = start_delayed_progress( + _("Expanding reachable commits in commit graph"), j = 0); for (i = 0; i < oids->nr; i++) { display_progress(progress, ++j); commit = lookup_commit(the_repository, &oids->list[i]); @@ -665,7 +673,11 @@ static void close_reachable(struct packed_oid_list *oids, int report_progress) if (commit && !parse_commit(commit)) add_missing_parents(oids, commit); } + stop_progress(&progress); + if (report_progress) + progress = start_delayed_progress( + _("Clearing commit marks in commit graph"), j = 0); for (i = 0; i < oids->nr; i++) { display_progress(progress, ++j); commit = lookup_commit(the_repository, &oids->list[i]); @@ -861,7 +873,7 @@ void write_commit_graph(const char *obj_dir, count_distinct++; } - if (count_distinct >= GRAPH_PARENT_MISSING) + if (count_distinct >= GRAPH_EDGE_LAST_MASK) die(_("the commit graph format cannot write %d commits"), count_distinct); commits.nr = 0; @@ -888,7 +900,7 @@ void write_commit_graph(const char *obj_dir, } num_chunks = num_extra_edges ? 4 : 3; - if (commits.nr >= GRAPH_PARENT_MISSING) + if (commits.nr >= GRAPH_EDGE_LAST_MASK) die(_("too many commits to write graph")); compute_generation_numbers(&commits, report_progress); diff --git a/compat/cygwin.c b/compat/cygwin.c deleted file mode 100644 index b9862d606d..0000000000 --- a/compat/cygwin.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "../git-compat-util.h" -#include "../cache.h" - -int cygwin_offset_1st_component(const char *path) -{ - const char *pos = path; - /* unc paths */ - if (is_dir_sep(pos[0]) && is_dir_sep(pos[1])) { - /* skip server name */ - pos = strchr(pos + 2, '/'); - if (!pos) - return 0; /* Error: malformed unc path */ - - do { - pos++; - } while (*pos && pos[0] != '/'); - } - return pos + is_dir_sep(*pos) - path; -} diff --git a/compat/cygwin.h b/compat/cygwin.h deleted file mode 100644 index 8e52de4644..0000000000 --- a/compat/cygwin.h +++ /dev/null @@ -1,2 +0,0 @@ -int cygwin_offset_1st_component(const char *path); -#define offset_1st_component cygwin_offset_1st_component diff --git a/compat/mingw.c b/compat/mingw.c index 34b3880b29..b459e1a291 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -350,7 +350,7 @@ static inline int needs_hiding(const char *path) return 0; /* We cannot use basename(), as it would remove trailing slashes */ - mingw_skip_dos_drive_prefix((char **)&path); + win32_skip_dos_drive_prefix((char **)&path); if (!*path) return 0; @@ -2275,33 +2275,6 @@ pid_t waitpid(pid_t pid, int *status, int options) return -1; } -int mingw_skip_dos_drive_prefix(char **path) -{ - int ret = has_dos_drive_prefix(*path); - *path += ret; - return ret; -} - -int mingw_offset_1st_component(const char *path) -{ - char *pos = (char *)path; - - /* unc paths */ - if (!skip_dos_drive_prefix(&pos) && - is_dir_sep(pos[0]) && is_dir_sep(pos[1])) { - /* skip server name */ - pos = strpbrk(pos + 2, "\\/"); - if (!pos) - return 0; /* Error: malformed unc path */ - - do { - pos++; - } while (*pos && !is_dir_sep(*pos)); - } - - return pos + is_dir_sep(*pos) - path; -} - int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) { int upos = 0, wpos = 0; diff --git a/compat/mingw.h b/compat/mingw.h index 8c24ddaa3e..30d9fb3e36 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -443,32 +443,12 @@ HANDLE winansi_get_osfhandle(int fd); * git specific compatibility */ -#define has_dos_drive_prefix(path) \ - (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) -int mingw_skip_dos_drive_prefix(char **path); -#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix -static inline int mingw_is_dir_sep(int c) -{ - return c == '/' || c == '\\'; -} -#define is_dir_sep mingw_is_dir_sep -static inline char *mingw_find_last_dir_sep(const char *path) -{ - char *ret = NULL; - for (; *path; ++path) - if (is_dir_sep(*path)) - ret = (char *)path; - return ret; -} static inline void convert_slashes(char *path) { for (; *path; path++) if (*path == '\\') *path = '/'; } -#define find_last_dir_sep mingw_find_last_dir_sep -int mingw_offset_1st_component(const char *path); -#define offset_1st_component mingw_offset_1st_component #define PATH_SEP ';' extern char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c index 51cd60baa3..c0d838834a 100644 --- a/compat/regex/regcomp.c +++ b/compat/regex/regcomp.c @@ -17,6 +17,14 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ +#if defined __TANDEM + /* This is currently duplicated from git-compat-utils.h */ +# ifdef NO_INTPTR_T + typedef long intptr_t; + typedef unsigned long uintptr_t; +# endif +#endif + static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern, size_t length, reg_syntax_t syntax); static void re_compile_fastmap_iter (regex_t *bufp, diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c new file mode 100644 index 0000000000..d9d3641de8 --- /dev/null +++ b/compat/win32/path-utils.c @@ -0,0 +1,28 @@ +#include "../../git-compat-util.h" + +int win32_skip_dos_drive_prefix(char **path) +{ + int ret = has_dos_drive_prefix(*path); + *path += ret; + return ret; +} + +int win32_offset_1st_component(const char *path) +{ + char *pos = (char *)path; + + /* unc paths */ + if (!skip_dos_drive_prefix(&pos) && + is_dir_sep(pos[0]) && is_dir_sep(pos[1])) { + /* skip server name */ + pos = strpbrk(pos + 2, "\\/"); + if (!pos) + return 0; /* Error: malformed unc path */ + + do { + pos++; + } while (*pos && !is_dir_sep(*pos)); + } + + return pos + is_dir_sep(*pos) - path; +} diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h new file mode 100644 index 0000000000..0f70d43920 --- /dev/null +++ b/compat/win32/path-utils.h @@ -0,0 +1,20 @@ +#define has_dos_drive_prefix(path) \ + (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) +int win32_skip_dos_drive_prefix(char **path); +#define skip_dos_drive_prefix win32_skip_dos_drive_prefix +static inline int win32_is_dir_sep(int c) +{ + return c == '/' || c == '\\'; +} +#define is_dir_sep win32_is_dir_sep +static inline char *win32_find_last_dir_sep(const char *path) +{ + char *ret = NULL; + for (; *path; ++path) + if (is_dir_sep(*path)) + ret = (char *)path; + return ret; +} +#define find_last_dir_sep win32_find_last_dir_sep +int win32_offset_1st_component(const char *path); +#define offset_1st_component win32_offset_1st_component diff --git a/config.mak.dev b/config.mak.dev index bbeeff44fe..7354fe15b3 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -6,6 +6,7 @@ CFLAGS += -pedantic # don't warn for each N_ use CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0 endif +CFLAGS += -Wall CFLAGS += -Wdeclaration-after-statement CFLAGS += -Wformat-security CFLAGS += -Wno-format-zero-length diff --git a/config.mak.uname b/config.mak.uname index 3ee7da0e23..7b36a1dfe7 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -187,7 +187,7 @@ ifeq ($(uname_O),Cygwin) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo MMAP_PREVENTS_DELETE = UnfortunatelyYes - COMPAT_OBJS += compat/cygwin.o + COMPAT_OBJS += compat/win32/path-utils.o FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),FreeBSD) @@ -233,6 +233,7 @@ ifeq ($(uname_S),OpenBSD) HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease PROCFS_EXECUTABLE_PATH = /proc/curproc/file + FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),MirBSD) NO_STRCASESTR = YesPlease @@ -441,26 +442,43 @@ ifeq ($(uname_S),NONSTOP_KERNEL) # INLINE='' would just replace one set of warnings with another and # still not compile in c89 mode, due to non-const array initializations. CC = cc -c99 + # Build down-rev compatible objects that don't use our new getopt_long. + ifeq ($(uname_R).$(uname_V),J06.21) + CC += -WRVU=J06.20 + endif + ifeq ($(uname_R).$(uname_V),L17.02) + CC += -WRVU=L16.05 + endif # Disable all optimization, seems to result in bad code, with -O or -O2 # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects # abends on "git push". Needs more investigation. - CFLAGS = -g -O0 + CFLAGS = -g -O0 -Winline # We'd want it to be here. prefix = /usr/local - # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl). - PERL_PATH = ${prefix}/bin/perl - PYTHON_PATH = ${prefix}/bin/python - + # perl and python must be in /usr/bin on NonStop - supplied by HPE + # with operating system in that managed directory. + PERL_PATH = /usr/bin/perl + PYTHON_PATH = /usr/bin/python + # The current /usr/coreutils/rm at lowest support level does not work + # with the git test structure. Long paths as in + # 'trash directory...' cause rm to terminate prematurely without fully + # removing the directory at OS releases J06.21 and L17.02. + # Default to the older rm until those two releases are deprecated. + RM = /bin/rm -f # As detected by './configure'. # Missdetected, hence commented out, see below. #NO_CURL = YesPlease # Added manually, see above. + NEEDS_SSL_WITH_CURL = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + HAVE_DEV_TTY = YesPlease HAVE_LIBCHARSET_H = YesPlease HAVE_STRINGS_H = YesPlease NEEDS_LIBICONV = YesPlease NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease NO_SYS_SELECT_H = UnfortunatelyYes NO_D_TYPE_IN_DIRENT = YesPlease + NO_GETTEXT = YesPlease NO_HSTRERROR = YesPlease NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease @@ -470,7 +488,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL) NO_MKDTEMP = YesPlease # Currently libiconv-1.9.1. OLD_ICONV = UnfortunatelyYes - NO_REGEX = YesPlease + NO_REGEX = NeedsStartEnd NO_PTHREADS = UnfortunatelyYes # Not detected (nor checked for) by './configure'. @@ -527,6 +545,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/path-utils.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1 diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9e8ec95c3c..499e56f83d 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -438,7 +438,7 @@ __gitcomp_nl () # Callers must take care of providing only paths that match the current path # to be completed and adding any prefix path components, if necessary. # 1: List of newline-separated matching paths, complete with all prefix -# path componens. +# path components. __gitcomp_file_direct () { local IFS=$'\n' @@ -855,7 +855,7 @@ __git_compute_merge_strategies () __git_complete_revlist_file () { - local pfx ls ref cur_="$cur" + local dequoted_word pfx ls ref cur_="$cur" case "$cur_" in *..?*:*) return @@ -863,14 +863,18 @@ __git_complete_revlist_file () ?*:*) ref="${cur_%%:*}" cur_="${cur_#*:}" - case "$cur_" in + + __git_dequote "$cur_" + + case "$dequoted_word" in ?*/*) - pfx="${cur_%/*}" - cur_="${cur_##*/}" + pfx="${dequoted_word%/*}" + cur_="${dequoted_word##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; *) + cur_="$dequoted_word" ls="$ref" ;; esac @@ -880,21 +884,10 @@ __git_complete_revlist_file () *) pfx="$ref:$pfx" ;; esac - __gitcomp_nl "$(__git ls-tree "$ls" \ - | sed '/^100... blob /{ - s,^.* ,, - s,$, , - } - /^120000 blob /{ - s,^.* ,, - s,$, , - } - /^040000 tree /{ - s,^.* ,, - s,$,/, - } - s/^.* //')" \ - "$pfx" "$cur_" "" + __gitcomp_file "$(__git ls-tree "$ls" \ + | sed 's/^.* // + s/$//')" \ + "$pfx" "$cur_" ;; *...*) pfx="${cur_%...*}..." @@ -2993,7 +2986,7 @@ if [[ -n ${ZSH_VERSION-} ]] && local IFS=$'\n' compset -P '*[=:]' - compadd -Q -f -- ${=1} && _ret=0 + compadd -f -- ${=1} && _ret=0 } __gitcomp_file () @@ -3002,7 +2995,7 @@ if [[ -n ${ZSH_VERSION-} ]] && local IFS=$'\n' compset -P '*[=:]' - compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 + compadd -p "${2-}" -f -- ${=1} && _ret=0 } _git () diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index 049d6b80f6..886bf95d1f 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -99,7 +99,7 @@ __gitcomp_file_direct () local IFS=$'\n' compset -P '*[=:]' - compadd -Q -f -- ${=1} && _ret=0 + compadd -f -- ${=1} && _ret=0 } __gitcomp_file () @@ -108,7 +108,7 @@ __gitcomp_file () local IFS=$'\n' compset -P '*[=:]' - compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 + compadd -p "${2-}" -f -- ${=1} && _ret=0 } __git_zsh_bash_func () diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES index 2076cf972b..35791fd02c 100644 --- a/contrib/hooks/multimail/CHANGES +++ b/contrib/hooks/multimail/CHANGES @@ -1,3 +1,59 @@ +Release 1.5.0 +============= + +Backward-incompatible change +---------------------------- + +The name of classes for environment was misnamed as `*Environement`. +It is now `*Environment`. + +New features +------------ + +* A Thread-Index header is now added to each email sent (except for + combined emails where it would not make sense), so that MS Outlook + properly groups messages by threads even though they have a + different subject line. Unfortunately, even adding this header the + threading still seems to be unreliable, but it is unclear whether + this is an issue on our side or on MS Outlook's side (see discussion + here: https://github.com/git-multimail/git-multimail/pull/194). + +* A new variable multimailhook.ExcludeMergeRevisions was added to send + notification emails only for non-merge commits. + +* For gitolite environment, it is now possible to specify the mail map + in a separate file in addition to gitolite.conf, using the variable + multimailhook.MailaddressMap. + +Internal changes +---------------- + +* The testsuite now uses GIT_PRINT_SHA1_ELLIPSIS where needed for + compatibility with recent Git versions. Only tests are affected. + +* We don't try to install pyflakes in the continuous integration job + for old Python versions where it's no longer available. + +* Stop using the deprecated cgi.escape in Python 3. + +* New flake8 warnings have been fixed. + +* Python 3.6 is now tested against on Travis-CI. + +* A bunch of lgtm.com warnings have been fixed. + +Bug fixes +--------- + +* SMTPMailer logs in only once now. It used to re-login for each email + sent which triggered errors for some SMTP servers. + +* migrate-mailhook-config was broken by internal refactoring, it + should now work again. + +This version was tested with Python 2.6 to 3.7. It was tested with Git +1.7.10.406.gdc801, 2.15.1 and 2.20.1.98.gecbdaf0. + Release 1.4.0 ============= diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst index da65570e9b..de20a54287 100644 --- a/contrib/hooks/multimail/CONTRIBUTING.rst +++ b/contrib/hooks/multimail/CONTRIBUTING.rst @@ -4,9 +4,8 @@ Contributing git-multimail is an open-source project, built by volunteers. We would welcome your help! -The current maintainers are Matthieu Moy -<matthieu.moy@grenoble-inp.fr> and Michael Haggerty -<mhagger@alum.mit.edu>. +The current maintainers are `Matthieu Moy <http://matthieu-moy.fr>`__ and +`Michael Haggerty <https://github.com/mhagger>`__. Please note that although a copy of git-multimail is distributed in the "contrib" section of the main Git project, development takes place @@ -33,6 +32,29 @@ mailing list`_. Please CC emails regarding git-multimail to the maintainers so that we don't overlook them. +Help needed: testers/maintainer for specific environments/OS +------------------------------------------------------------ + +The current maintainer uses and tests git-multimail on Linux with the +Generic environment. More testers, or better contributors are needed +to test git-multimail on other real-life setups: + +* Mac OS X, Windows: git-multimail is currently not supported on these + platforms. But since we have no external dependencies and try to + write code as portable as possible, it is possible that + git-multimail already runs there and if not, it is likely that it + could be ported easily. + + Patches to improve support for Windows and OS X are welcome. + Ideally, there would be a sub-maintainer for each OS who would test + at least once before each release (around twice a year). + +* Gerrit, Stash, Gitolite environments: although the testsuite + contains tests for these environments, a tester/maintainer for each + environment would be welcome to test and report failure (or success) + on real-life environments periodically (here also, feedback before + each release would be highly appreciated). + .. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail .. _`Git mailing list`: git@vger.kernel.org diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git index 161b0230a0..044444245d 100644 --- a/contrib/hooks/multimail/README.Git +++ b/contrib/hooks/multimail/README.Git @@ -6,10 +6,10 @@ website: https://github.com/git-multimail/git-multimail The version in this directory was obtained from the upstream project -on August 17 2016 and consists of the "git-multimail" subdirectory from +on January 07 2019 and consists of the "git-multimail" subdirectory from revision - 07b1cb6bfd7be156c62e1afa17cae13b850a869f refs/tags/1.4.0 + 04e80e6c40be465cc62b6c246f0fcb8fd2cfd454 refs/tags/1.5.0 Please see the README file in this directory for information about how to report bugs or contribute to git-multimail. diff --git a/contrib/hooks/multimail/README b/contrib/hooks/multimail/README.rst index 5105373aea..7c0fc4a6ef 100644 --- a/contrib/hooks/multimail/README +++ b/contrib/hooks/multimail/README.rst @@ -1,4 +1,4 @@ -git-multimail version 1.4.0 +git-multimail version 1.5.0 =========================== .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master @@ -20,8 +20,8 @@ GPLv2 (see the COPYING file for details). Please note: although, as a convenience, git-multimail may be distributed along with the main Git project, development of -git-multimail takes place in its own, separate project. See section -"Getting involved" below for more information. +git-multimail takes place in its own, separate project. Please, read +`<CONTRIBUTING.rst>`__ for more information. By default, for each push received by the repository, git-multimail: @@ -89,6 +89,10 @@ Requirements the multimailhook.mailer configuration variable below for how to configure git-multimail to send emails via an SMTP server. +* git-multimail is currently tested only on Linux. It may or may not + work on other platforms such as Windows and Mac OS. See + `<CONTRIBUTING.rst>`__ to improve the situation. + Invocation ---------- @@ -369,7 +373,7 @@ multimailhook.mailer unset, then the value of multimailhook.from is used. multimailhook.smtpServerTimeout - Timeout in seconds. + Timeout in seconds. Default is 10. multimailhook.smtpEncryption Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls). @@ -419,8 +423,20 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange If config values are unset, the value of the From: header is determined as follows: - 1. (gitolite environment only) Parse gitolite.conf, looking for a - block of comments that looks like this:: + 1. (gitolite environment only) + 1.a) If ``multimailhook.MailaddressMap`` is set, and is a path + to an existing file (if relative, it is considered relative to + the place where ``gitolite.conf`` is located), then this file + should contain lines like:: + + username Firstname Lastname <email@example.com> + + git-multimail will then look for a line where ``$GL_USER`` + matches the ``username`` part, and use the rest of the line for + the ``From:`` header. + + 1.b) Parse gitolite.conf, looking for a block of comments that + looks like this:: # BEGIN USER EMAILS # username Firstname Lastname <email@example.com> @@ -436,6 +452,11 @@ multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange 3. Use the value of multimailhook.envelopeSender. +multimailhook.MailaddressMap + (gitolite environment only) + File to look for a ``From:`` address based on the user doing the + push. Defaults to unset. See ``multimailhook.from`` for details. + multimailhook.administrator The name and/or email address of the administrator of the Git repository; used in FOOTER_TEMPLATE. Default is @@ -484,6 +505,11 @@ multimailhook.maxCommitEmails mailbombing, for example on an initial push. To disable commit emails limit, set this option to 0. The default is 500. +multimailhook.excludeMergeRevisions + When sending out revision emails, do not consider merge commits (the + functional equivalent of `rev-list --no-merges`). + The default is `false` (send merge commit emails). + multimailhook.emailStrictUTF8 If this boolean option is set to `true`, then the main part of the email body is forced to be valid UTF-8. Any characters that are diff --git a/contrib/hooks/multimail/doc/gitolite.rst b/contrib/hooks/multimail/doc/gitolite.rst index 00aedd9c57..5054833105 100644 --- a/contrib/hooks/multimail/doc/gitolite.rst +++ b/contrib/hooks/multimail/doc/gitolite.rst @@ -46,6 +46,15 @@ and add:: config multimailhook.mailingList = # Where emails should be sent config multimailhook.from = # From address to use +Note that by default, gitolite forbids ``<`` and ``>`` in variable +values (for security/paranoia reasons, see +`compensating for UNSAFE_PATT +<http://gitolite.com/gitolite/git-config/index.html#compensating-for-unsafe95patt>`__ +in gitolite's documentation for explanations and a way to disable +this). As a consequence, you will not be able to use ``First Last +<First.Last@example.com>`` as recipient email, but specifying +``First.Last@example.com`` alone works. + Obviously, you can customize all parameters on a per-repository basis by adding these ``config multimailhook.*`` lines in the section corresponding to a repository or set of repositories. diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py index 73fdda6b14..8823399e75 100755 --- a/contrib/hooks/multimail/git_multimail.py +++ b/contrib/hooks/multimail/git_multimail.py @@ -1,6 +1,6 @@ #! /usr/bin/env python -__version__ = '1.4.0' +__version__ = '1.5.0' # Copyright (c) 2015-2016 Matthieu Moy and others # Copyright (c) 2012-2014 Michael Haggerty and others @@ -64,7 +64,9 @@ except ImportError: # Python < 2.6 do not have ssl, but that's OK if we don't use it. pass import time -import cgi + +import uuid +import base64 PYTHON3 = sys.version_info >= (3, 0) @@ -73,7 +75,7 @@ if sys.version_info <= (2, 5): for element in iterable: if not element: return False - return True + return True def is_ascii(s): @@ -108,6 +110,12 @@ if PYTHON3: return out.decode(sys.getdefaultencoding()) except UnicodeEncodeError: return out.decode(ENCODING) + + import html + + def html_escape(s): + return html.escape(s) + else: def is_string(s): try: @@ -130,6 +138,10 @@ else: def next(it): return it.next() + import cgi + + def html_escape(s): + return cgi.escape(s, True) try: from email.charset import Charset @@ -190,6 +202,7 @@ Content-Transfer-Encoding: 8bit Message-ID: %(msgid)s From: %(fromaddr)s Reply-To: %(reply_to)s +Thread-Index: %(thread_index)s X-Git-Host: %(fqdn)s X-Git-Repo: %(repo_shortname)s X-Git-Refname: %(refname)s @@ -322,6 +335,7 @@ From: %(fromaddr)s Reply-To: %(reply_to)s In-Reply-To: %(reply_to_msgid)s References: %(reply_to_msgid)s +Thread-Index: %(thread_index)s X-Git-Host: %(fqdn)s X-Git-Repo: %(repo_shortname)s X-Git-Refname: %(refname)s @@ -763,6 +777,9 @@ class GitObject(object): def __eq__(self, other): return isinstance(other, GitObject) and self.sha1 == other.sha1 + def __ne__(self, other): + return not self == other + def __hash__(self): return hash(self.sha1) @@ -852,7 +869,7 @@ class Change(object): if html_escape_val: for k in values: if is_string(values[k]): - values[k] = cgi.escape(values[k], True) + values[k] = html_escape(values[k]) for line in template.splitlines(True): yield line % values @@ -909,7 +926,7 @@ class Change(object): raise NotImplementedError() - def generate_email_body(self): + def generate_email_body(self, push): """Generate the main part of the email body, a line at a time. The text in the body might be truncated after a specified @@ -936,7 +953,7 @@ class Change(object): yield "<pre style='margin:0'>\n" for line in lines: - yield cgi.escape(line) + yield html_escape(line) yield '</pre>\n' else: @@ -1011,7 +1028,7 @@ class Change(object): fgcolor = '404040' # Chop the trailing LF, we don't want it inside <pre>. - line = cgi.escape(line[:-1]) + line = html_escape(line[:-1]) if bgcolor or fgcolor: style = 'display:block; white-space:pre;' @@ -1060,6 +1077,10 @@ class Revision(Change): self.author = read_git_output(['log', '--no-walk', '--format=%aN <%aE>', self.rev.sha1]) self.recipients = self.environment.get_revision_recipients(self) + # -s is short for --no-patch, but -s works on older git's (e.g. 1.7) + self.parents = read_git_lines(['show', '-s', '--format=%P', + self.rev.sha1])[0].split() + self.cc_recipients = '' if self.environment.get_scancommitforcc(): self.cc_recipients = ', '.join(to.strip() for to in self._cc_recipients()) @@ -1090,6 +1111,7 @@ class Revision(Change): oneline = oneline[:max_subject_length - 6] + ' [...]' values['rev'] = self.rev.sha1 + values['parents'] = ' '.join(self.parents) values['rev_short'] = self.rev.short values['change_type'] = self.change_type values['refname'] = self.refname @@ -1097,6 +1119,7 @@ class Revision(Change): values['short_refname'] = self.reference_change.short_refname values['refname_type'] = self.reference_change.refname_type values['reply_to_msgid'] = self.reference_change.msgid + values['thread_index'] = self.reference_change.thread_index values['num'] = self.num values['tot'] = self.tot values['recipients'] = self.recipients @@ -1244,6 +1267,23 @@ class ReferenceChange(Change): old=old, new=new, rev=rev, ) + @staticmethod + def make_thread_index(): + """Return a string appropriate for the Thread-Index header, + needed by MS Outlook to get threading right. + + The format is (base64-encoded): + - 1 byte must be 1 + - 5 bytes encode a date (hardcoded here) + - 16 bytes for a globally unique identifier + + FIXME: Unfortunately, even with the Thread-Index field, MS + Outlook doesn't seem to do the threading reliably (see + https://github.com/git-multimail/git-multimail/pull/194). + """ + thread_index = b'\x01\x00\x00\x12\x34\x56' + uuid.uuid4().bytes + return base64.standard_b64encode(thread_index).decode('ascii') + def __init__(self, environment, refname, short_refname, old, new, rev): Change.__init__(self, environment) self.change_type = { @@ -1257,6 +1297,7 @@ class ReferenceChange(Change): self.new = new self.rev = rev self.msgid = make_msgid() + self.thread_index = self.make_thread_index() self.diffopts = environment.diffopts self.graphopts = environment.graphopts self.logopts = environment.logopts @@ -1276,6 +1317,7 @@ class ReferenceChange(Change): values['refname'] = self.refname values['short_refname'] = self.short_refname values['msgid'] = self.msgid + values['thread_index'] = self.thread_index values['recipients'] = self.recipients values['oldrev'] = str(self.old) values['oldrev_short'] = self.old.short @@ -1941,6 +1983,9 @@ class Mailer(object): def __init__(self, environment): self.environment = environment + def close(self): + pass + def send(self, lines, to_addrs): """Send an email consisting of lines. @@ -2054,6 +2099,7 @@ class SMTPMailer(Mailer): self.username = smtpuser self.password = smtppass self.smtpcacerts = smtpcacerts + self.loggedin = False try: def call(klass, server, timeout): try: @@ -2130,20 +2176,30 @@ class SMTPMailer(Mailer): % (self.smtpserver, sys.exc_info()[1])) sys.exit(1) - def __del__(self): + def close(self): if hasattr(self, 'smtp'): self.smtp.quit() del self.smtp + def __del__(self): + self.close() + def send(self, lines, to_addrs): try: if self.username or self.password: - self.smtp.login(self.username, self.password) + if not self.loggedin: + self.smtp.login(self.username, self.password) + self.loggedin = True msg = ''.join(lines) # turn comma-separated list into Python list if needed. if is_string(to_addrs): to_addrs = [email for (name, email) in getaddresses([to_addrs])] self.smtp.sendmail(self.envelopesender, to_addrs, msg) + except socket.timeout: + self.environment.get_logger().error( + '*** Error sending email ***\n' + '*** SMTP server timed out (timeout is %s)\n' + % self.smtpservertimeout) except smtplib.SMTPResponseException: err = sys.exc_info()[1] self.environment.get_logger().error( @@ -2171,7 +2227,8 @@ class OutputMailer(Mailer): SEPARATOR = '=' * 75 + '\n' - def __init__(self, f): + def __init__(self, f, environment=None): + super(OutputMailer, self).__init__(environment=environment) self.f = f def send(self, lines, to_addrs): @@ -2382,6 +2439,7 @@ class Environment(object): self.html_in_footer = False self.commitBrowseURL = None self.maxcommitemails = 500 + self.excludemergerevisions = False self.diffopts = ['--stat', '--summary', '--find-copies-harder'] self.graphopts = ['--oneline', '--decorate'] self.logopts = [] @@ -2621,6 +2679,8 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): self.commitBrowseURL = config.get('commitBrowseURL') + self.excludemergerevisions = config.get('excludeMergeRevisions') + maxcommitemails = config.get('maxcommitemails') if maxcommitemails is not None: try: @@ -3152,7 +3212,10 @@ class GitoliteEnvironmentHighPrecMixin(Environment): return self.osenv.get('GL_USER', 'unknown user') -class GitoliteEnvironmentLowPrecMixin(Environment): +class GitoliteEnvironmentLowPrecMixin( + ConfigEnvironmentMixin, + Environment): + def get_repo_shortname(self): # The gitolite environment variable $GL_REPO is a pretty good # repo_shortname (though it's probably not as good as a value @@ -3162,6 +3225,16 @@ class GitoliteEnvironmentLowPrecMixin(Environment): super(GitoliteEnvironmentLowPrecMixin, self).get_repo_shortname() ) + @staticmethod + def _compile_regex(re_template): + return ( + re.compile(re_template % x) + for x in ( + r'BEGIN\s+USER\s+EMAILS', + r'([^\s]+)\s+(.*)', + r'END\s+USER\s+EMAILS', + )) + def get_fromaddr(self, change=None): GL_USER = self.osenv.get('GL_USER') if GL_USER is not None: @@ -3174,18 +3247,42 @@ class GitoliteEnvironmentLowPrecMixin(Environment): GL_CONF = self.osenv.get( 'GL_CONF', os.path.join(GL_ADMINDIR, 'conf', 'gitolite.conf')) + + mailaddress_map = self.config.get('MailaddressMap') + # If relative, consider relative to GL_CONF: + if mailaddress_map: + mailaddress_map = os.path.join(os.path.dirname(GL_CONF), + mailaddress_map) + if os.path.isfile(mailaddress_map): + f = open(mailaddress_map, 'rU') + try: + # Leading '#' is optional + re_begin, re_user, re_end = self._compile_regex( + r'^(?:\s*#)?\s*%s\s*$') + for l in f: + l = l.rstrip('\n') + if re_begin.match(l) or re_end.match(l): + continue # Ignore these lines + m = re_user.match(l) + if m: + if m.group(1) == GL_USER: + return m.group(2) + else: + continue # Not this user, but not an error + raise ConfigurationException( + "Syntax error in mail address map.\n" + "Check file {}.\n" + "Line: {}".format(mailaddress_map, l)) + + finally: + f.close() + if os.path.isfile(GL_CONF): f = open(GL_CONF, 'rU') try: in_user_emails_section = False - re_template = r'^\s*#\s*%s\s*$' - re_begin, re_user, re_end = ( - re.compile(re_template % x) - for x in ( - r'BEGIN\s+USER\s+EMAILS', - re.escape(GL_USER) + r'\s+(.*)', - r'END\s+USER\s+EMAILS', - )) + re_begin, re_user, re_end = self._compile_regex( + r'^\s*#\s*%s\s*$') for l in f: l = l.rstrip('\n') if not in_user_emails_section: @@ -3195,8 +3292,8 @@ class GitoliteEnvironmentLowPrecMixin(Environment): if re_end.match(l): break m = re_user.match(l) - if m: - return m.group(1) + if m and m.group(1) == GL_USER: + return m.group(2) finally: f.close() return super(GitoliteEnvironmentLowPrecMixin, self).get_fromaddr(change) @@ -3228,7 +3325,7 @@ class StashEnvironmentHighPrecMixin(Environment): self.__repo = repo def get_pusher(self): - return re.match('(.*?)\s*<', self.__user).group(1) + return re.match(r'(.*?)\s*<', self.__user).group(1) def get_pusher_email(self): return self.__user @@ -3262,7 +3359,7 @@ class GerritEnvironmentHighPrecMixin(Environment): if self.__submitter.find('<') != -1: # Submitter has a configured email, we transformed # __submitter into an RFC 2822 string already. - return re.match('(.*?)\s*<', self.__submitter).group(1) + return re.match(r'(.*?)\s*<', self.__submitter).group(1) else: # Submitter has no configured email, it's just his name. return self.__submitter @@ -3615,6 +3712,9 @@ class Push(object): for (num, sha1) in enumerate(sha1s): rev = Revision(change, GitObject(sha1), num=num + 1, tot=len(sha1s)) + if len(rev.parents) > 1 and change.environment.excludemergerevisions: + # skipping a merge commit + continue if not rev.recipients and rev.cc_recipients: change.environment.log_msg('*** Replacing Cc: with To:') rev.recipients = rev.cc_recipients @@ -3664,11 +3764,14 @@ def run_as_post_receive_hook(environment, mailer): changes.append( ReferenceChange.create(environment, oldrev, newrev, refname) ) - if changes: - push = Push(environment, changes) + if not changes: + mailer.close() + return + push = Push(environment, changes) + try: push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): - mailer.__del__() + finally: + mailer.close() def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False): @@ -3687,10 +3790,14 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send= refname, ), ] + if not changes: + mailer.close() + return push = Push(environment, changes, force_send) - push.send_emails(mailer, body_filter=environment.filter_body) - if hasattr(mailer, '__del__'): - mailer.__del__() + try: + push.send_emails(mailer, body_filter=environment.filter_body) + finally: + mailer.close() def check_ref_filter(environment): @@ -3860,7 +3967,7 @@ def build_environment_klass(env_name): low_prec_mixin = known_env['lowprec'] environment_mixins.append(low_prec_mixin) environment_mixins.append(Environment) - klass_name = env_name.capitalize() + 'Environement' + klass_name = env_name.capitalize() + 'Environment' environment_klass = type( klass_name, tuple(environment_mixins), @@ -4057,21 +4164,21 @@ class Logger(object): environment, 'git_multimail.error', environment.error_log_file, logging.ERROR) self.loggers.append(error_log_file) - def info(self, msg): + def info(self, msg, *args, **kwargs): for l in self.loggers: - l.info(msg) + l.info(msg, *args, **kwargs) - def debug(self, msg): + def debug(self, msg, *args, **kwargs): for l in self.loggers: - l.debug(msg) + l.debug(msg, *args, **kwargs) - def warning(self, msg): + def warning(self, msg, *args, **kwargs): for l in self.loggers: - l.warning(msg) + l.warning(msg, *args, **kwargs) - def error(self, msg): + def error(self, msg, *args, **kwargs): for l in self.loggers: - l.error(msg) + l.error(msg, *args, **kwargs) def main(args): @@ -4189,7 +4296,7 @@ def main(args): show_env(environment, sys.stderr) if options.stdout or environment.stdout: - mailer = OutputMailer(sys.stdout) + mailer = OutputMailer(sys.stdout, environment) else: mailer = choose_mailer(config, environment) @@ -4234,5 +4341,6 @@ def main(args): sys.stderr.write(msg) sys.exit(1) + if __name__ == '__main__': main(sys.argv[1:]) diff --git a/contrib/hooks/multimail/migrate-mailhook-config b/contrib/hooks/multimail/migrate-mailhook-config index 992657bbdc..241ba22fa3 100755 --- a/contrib/hooks/multimail/migrate-mailhook-config +++ b/contrib/hooks/multimail/migrate-mailhook-config @@ -110,11 +110,12 @@ def is_section_empty(section, local): try: read_output( - ['git', 'config'] - + local_option - + ['--get-regexp', '^%s\.' % (section,)] + ['git', 'config'] + + local_option + + ['--get-regexp', '^%s\.' % (section,)] ) - except CommandError, e: + except CommandError: + t, e, traceback = sys.exc_info() if e.retcode == 1: # This means that no settings were found. return True @@ -188,7 +189,9 @@ def migrate_config(strict=False, retain=False, overwrite=False): sys.stderr.write( '...copying "%s.%s" to "%s.%s"\n' % (old.section, name, new.section, name) ) - new.set_recipients(name, old.get_recipients(name)) + old_recipients = old.get_all(name, default=None) + old_recipients = ', '.join(o.strip() for o in old_recipients) + new.set_recipients(name, old_recipients) if strict: sys.stderr.write( diff --git a/contrib/hooks/multimail/post-receive.example b/contrib/hooks/multimail/post-receive.example index 1ea113d274..b9bb11834e 100755 --- a/contrib/hooks/multimail/post-receive.example +++ b/contrib/hooks/multimail/post-receive.example @@ -30,7 +30,6 @@ script's behavior could be changed or customized. """ import sys -import os # If necessary, add the path to the directory containing # git_multimail.py to the Python path as follows. (This is not @@ -86,6 +85,7 @@ mailer = git_multimail.choose_mailer(config, environment) # Use Python's smtplib to send emails. Both arguments are required. #mailer = git_multimail.SMTPMailer( +# environment=environment, # envelopesender='git-repo@example.com', # # The smtpserver argument can also include a port number; e.g., # # smtpserver='mail.example.com:25' @@ -705,7 +705,7 @@ static int filter_buffer_or_fd(int in, int out, void *data) } static int apply_single_file_filter(const char *path, const char *src, size_t len, int fd, - struct strbuf *dst, const char *cmd) + struct strbuf *dst, const char *cmd) { /* * Create a pipeline to have the command filter the buffer's @@ -778,7 +778,8 @@ static int start_multi_file_filter_fn(struct subprocess_entry *subprocess) static void handle_filter_error(const struct strbuf *filter_status, struct cmd2process *entry, - const unsigned int wanted_capability) { + const unsigned int wanted_capability) +{ if (!strcmp(filter_status->buf, "error")) ; /* The filter signaled a problem with the file. */ else if (!strcmp(filter_status->buf, "abort") && wanted_capability) { @@ -1091,7 +1092,7 @@ static int count_ident(const char *cp, unsigned long size) } static int ident_to_git(const char *path, const char *src, size_t len, - struct strbuf *buf, int ident) + struct strbuf *buf, int ident) { char *dst, *dollar; @@ -1135,7 +1136,7 @@ static int ident_to_git(const char *path, const char *src, size_t len, } static int ident_to_worktree(const char *path, const char *src, size_t len, - struct strbuf *buf, int ident) + struct strbuf *buf, int ident) { struct object_id oid; char *to_free = NULL, *dollar, *spc; diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 4dfbc8c9f9..ec1271f89c 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -91,7 +91,8 @@ static timestamp_t check_expirations(void) } static int read_request(FILE *fh, struct credential *c, - struct strbuf *action, int *timeout) { + struct strbuf *action, int *timeout) +{ static struct strbuf item = STRBUF_INIT; const char *p; @@ -291,7 +291,7 @@ static int parse_color_moved(const char *arg) return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed-zebra', 'plain'")); } -static int parse_color_moved_ws(const char *arg) +static unsigned parse_color_moved_ws(const char *arg) { int ret = 0; struct string_list l = STRING_LIST_INIT_DUP; @@ -312,15 +312,19 @@ static int parse_color_moved_ws(const char *arg) ret |= XDF_IGNORE_WHITESPACE; else if (!strcmp(sb.buf, "allow-indentation-change")) ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE; - else - error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf); + else { + ret |= COLOR_MOVED_WS_ERROR; + error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), sb.buf); + } strbuf_release(&sb); } if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) && - (ret & XDF_WHITESPACE_FLAGS)) - die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes")); + (ret & XDF_WHITESPACE_FLAGS)) { + error(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes")); + ret |= COLOR_MOVED_WS_ERROR; + } string_list_clear(&l, 0); @@ -341,8 +345,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "diff.colormovedws")) { - int cm = parse_color_moved_ws(value); - if (cm < 0) + unsigned cm = parse_color_moved_ws(value); + if (cm & COLOR_MOVED_WS_ERROR) return -1; diff_color_moved_ws_default = cm; return 0; @@ -489,7 +493,7 @@ static const char *external_diff(void) if (done_preparing) return external_diff_cmd; - external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); + external_diff_cmd = xstrdup_or_null(getenv("GIT_EXTERNAL_DIFF")); if (!external_diff_cmd) external_diff_cmd = external_diff_cmd_cfg; done_preparing = 1; @@ -1637,7 +1641,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata, strbuf_release(&msgbuf); } -static struct diff_tempfile *claim_diff_tempfile(void) { +static struct diff_tempfile *claim_diff_tempfile(void) +{ int i; for (i = 0; i < ARRAY_SIZE(diff_temp); i++) if (!diff_temp[i].name) @@ -4819,7 +4824,8 @@ static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt) return 0; } -static void enable_patch_output(int *fmt) { +static void enable_patch_output(int *fmt) +{ *fmt &= ~DIFF_FORMAT_NO_OUTPUT; *fmt |= DIFF_FORMAT_PATCH; } @@ -5034,10 +5040,13 @@ int diff_opt_parse(struct diff_options *options, else if (skip_prefix(arg, "--color-moved=", &arg)) { int cm = parse_color_moved(arg); if (cm < 0) - die("bad --color-moved argument: %s", arg); + return error("bad --color-moved argument: %s", arg); options->color_moved = cm; } else if (skip_prefix(arg, "--color-moved-ws=", &arg)) { - options->color_moved_ws_handling = parse_color_moved_ws(arg); + unsigned cm = parse_color_moved_ws(arg); + if (cm & COLOR_MOVED_WS_ERROR) + return -1; + options->color_moved_ws_handling = cm; } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; @@ -225,7 +225,8 @@ struct diff_options { /* XDF_WHITESPACE_FLAGS regarding block detection are set at 2, 3, 4 */ #define COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE (1<<5) - int color_moved_ws_handling; + #define COLOR_MOVED_WS_ERROR (1<<0) + unsigned color_moved_ws_handling; struct repository *repo; }; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index ad939d2861..a9c6d60df2 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -154,6 +154,12 @@ static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, if (textconv_one == textconv_two && diff_unmodified_pair(p)) return 0; + if ((o->pickaxe_opts & DIFF_PICKAXE_KIND_G) && + !o->flags.text && + ((!textconv_one && diff_filespec_is_binary(o->repo, p->one)) || + (!textconv_two && diff_filespec_is_binary(o->repo, p->two)))) + return 0; + mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr); mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr); @@ -276,44 +276,6 @@ static int do_read_blob(const struct object_id *oid, struct oid_stat *oid_stat, #define DO_MATCH_DIRECTORY (1<<1) #define DO_MATCH_SUBMODULE (1<<2) -static int match_attrs(const struct index_state *istate, - const char *name, int namelen, - const struct pathspec_item *item) -{ - int i; - char *to_free = NULL; - - if (name[namelen]) - name = to_free = xmemdupz(name, namelen); - - git_check_attr(istate, name, item->attr_check); - - free(to_free); - - for (i = 0; i < item->attr_match_nr; i++) { - const char *value; - int matched; - enum attr_match_mode match_mode; - - value = item->attr_check->items[i].value; - match_mode = item->attr_match[i].match_mode; - - if (ATTR_TRUE(value)) - matched = (match_mode == MATCH_SET); - else if (ATTR_FALSE(value)) - matched = (match_mode == MATCH_UNSET); - else if (ATTR_UNSET(value)) - matched = (match_mode == MATCH_UNSPECIFIED); - else - matched = (match_mode == MATCH_VALUE && - !strcmp(item->attr_match[i].value, value)); - if (!matched) - return 0; - } - - return 1; -} - /* * Does 'match' match the given name? * A match is found if @@ -367,7 +329,8 @@ static int match_pathspec_item(const struct index_state *istate, strncmp(item->match, name - prefix, item->prefix)) return 0; - if (item->attr_match_nr && !match_attrs(istate, name, namelen, item)) + if (item->attr_match_nr && + !match_pathspec_attrs(istate, name, namelen, item)) return 0; /* If the match was just the prefix, we matched */ @@ -161,7 +161,7 @@ static int remove_available_paths(struct string_list_item *item, void *cb_data) return !available; } -int finish_delayed_checkout(struct checkout *state) +int finish_delayed_checkout(struct checkout *state, int *nr_checkouts) { int errs = 0; unsigned delayed_object_count; @@ -226,7 +226,7 @@ int finish_delayed_checkout(struct checkout *state) ce = index_file_exists(state->istate, path->string, strlen(path->string), 0); if (ce) { - errs |= checkout_entry(ce, state, NULL); + errs |= checkout_entry(ce, state, NULL, nr_checkouts); filtered_bytes += ce->ce_stat_data.sd_size; display_throughput(progress, filtered_bytes); } else @@ -435,8 +435,8 @@ static void mark_colliding_entries(const struct checkout *state, * its name is returned in topath[], which must be able to hold at * least TEMPORARY_FILENAME_LENGTH bytes long. */ -int checkout_entry(struct cache_entry *ce, - const struct checkout *state, char *topath) +int checkout_entry(struct cache_entry *ce, const struct checkout *state, + char *topath, int *nr_checkouts) { static struct strbuf path = STRBUF_INIT; struct stat st; @@ -506,5 +506,7 @@ int checkout_entry(struct cache_entry *ce, return 0; create_directories(path.buf, path.len, state); + if (nr_checkouts) + (*nr_checkouts)++; return write_entry(ce, path.buf, state, 0); } diff --git a/git-compat-util.h b/git-compat-util.h index 09b0102cae..29a19902aa 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -193,10 +193,11 @@ #endif #if defined(__CYGWIN__) -#include "compat/cygwin.h" +#include "compat/win32/path-utils.h" #endif #if defined(__MINGW32__) /* pull in Windows compatibility stuff */ +#include "compat/win32/path-utils.h" #include "compat/mingw.h" #elif defined(_MSC_VER) #include "compat/msvc.h" @@ -397,6 +398,19 @@ static inline char *git_find_last_dir_sep(const char *path) #define query_user_email() NULL #endif +#ifdef __TANDEM +#include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)> +#include <floss.h(floss_getpwuid)> +#ifndef NSIG +/* + * NonStop NSE and NSX do not provide NSIG. SIGGUARDIAN(99) is the highest + * known, by detective work using kill -l as a list is all signals + * instead of signal.h where it should be. + */ +# define NSIG 100 +#endif +#endif + #if defined(__HP_cc) && (__HP_cc >= 61000) #define NORETURN __attribute__((noreturn)) #define NORETURN_PTR @@ -721,7 +735,7 @@ extern const char *githstrerror(int herror); #ifdef NO_MEMMEM #define memmem gitmemmem void *gitmemmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen); + const void *needle, size_t needlelen); #endif #ifdef OVERRIDE_STRDUP diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index b4c7dbfa57..3bb0682db5 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -26,6 +26,7 @@ f,force-rebase! cherry-pick all commits, even if unchanged m,merge! use merging strategies to rebase i,interactive! let the user edit the list of commits to rebase x,exec=! add exec lines after each commit of the editable list +y=! same as --reschedule-failed-exec -x k,keep-empty preserve empty commits during rebase allow-empty-message allow rebasing commits with empty messages stat! display a diffstat of what changed upstream @@ -48,6 +49,7 @@ skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase quit! abort but keep HEAD where it is show-current-patch! show the patch file being applied or merged +reschedule-failed-exec automatically reschedule failed exec commands " . git-sh-setup set_reflog_action rebase @@ -92,11 +94,14 @@ autosquash= keep_empty= allow_empty_message=--allow-empty-message signoff= +reschedule_failed_exec= test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +test "$(git config --bool rebase.reschedulefailedexec)" = "true" && +reschedule_failed_exec=--reschedule-failed-exec . git-rebase--common read_basic_state () { @@ -126,6 +131,8 @@ read_basic_state () { signoff="$(cat "$state_dir"/signoff)" force_rebase=t } + test -f "$state_dir"/reschedule-failed-exec && + reschedule_failed_exec=t } finish_rebase () { @@ -163,7 +170,8 @@ run_interactive () { "$allow_empty_message" "$autosquash" "$verbose" \ "$force_rebase" "$onto_name" "$head_name" "$strategy" \ "$strategy_opts" "$cmd" "$switch_to" \ - "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" + "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" \ + "$reschedule_failed_exec" } run_specific_rebase () { @@ -255,6 +263,11 @@ do cmd="${cmd}exec ${1#--exec=}${LF}" test -z "$interactive_rebase" && interactive_rebase=implied ;; + -y*) + reschedule_failed_exec=--reschedule-failed-exec + cmd="${cmd}exec ${1#-y}${LF}" + test -z "$interactive_rebase" && interactive_rebase=implied + ;; --interactive) interactive_rebase=explicit ;; @@ -378,6 +391,12 @@ do --gpg-sign=*) gpg_sign_opt="-S${1#--gpg-sign=}" ;; + --reschedule-failed-exec) + reschedule_failed_exec=--reschedule-failed-exec + ;; + --no-reschedule-failed-exec) + reschedule_failed_exec= + ;; --) shift break @@ -534,6 +553,9 @@ then # git-rebase.txt caveats with "unless you know what you are doing" test -n "$rebase_merges" && die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")" + + test -n "$reschedule_failed_exec" && + die "$(gettext "error: cannot combine '--preserve-merges' with '--reschedule-failed-exec'")" fi if test -n "$rebase_merges" @@ -332,6 +332,8 @@ def p4_check_access(min_expiration=1): die_bad_access("p4 error: {0}".format(data)) else: die_bad_access("unknown error") + elif code == "info": + return else: die_bad_access("unknown error code {0}".format(code)) diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 6d3a88decd..e3d3909743 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -8,6 +8,7 @@ n,dry-run dry run author= author name and email address for patches without any patches= path to the quilt patches series= path to the quilt series file +keep-non-patch Pass -b to git mailinfo " SUBDIRECTORY_ON=Yes . git-sh-setup @@ -32,6 +33,9 @@ do shift QUILT_SERIES="$1" ;; + --keep-non-patch) + MAILINFO_OPT="-b" + ;; --) shift break;; @@ -98,7 +102,7 @@ do continue fi echo $patch_name - git mailinfo "$tmp_msg" "$tmp_patch" \ + git mailinfo $MAILINFO_OPT "$tmp_msg" "$tmp_patch" \ <"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3 test -s "$tmp_patch" || { echo "Patch is empty. Was it split wrong?" diff --git a/git-rebase--common.sh b/git-rebase--common.sh index 7e39d22871..a8a44608e0 100644 --- a/git-rebase--common.sh +++ b/git-rebase--common.sh @@ -19,6 +19,7 @@ write_basic_state () { "$state_dir"/allow_rerere_autoupdate test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff + test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec } apply_autostash () { @@ -98,7 +98,8 @@ static int list_cmds(const char *spec) return 0; } -static void commit_pager_choice(void) { +static void commit_pager_choice(void) +{ switch (use_pager) { case 0: setenv("GIT_PAGER", "cat", 1); diff --git a/imap-send.c b/imap-send.c index b4eb886e2a..18ca6ba10a 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1471,7 +1471,8 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) } static int curl_append_msgs_to_imap(struct imap_server_conf *server, - struct strbuf* all_msgs, int total) { + struct strbuf* all_msgs, int total) +{ int ofs = 0; int n = 0; struct buffer msgbuf = { STRBUF_INIT, 0 }; diff --git a/list-objects.c b/list-objects.c index cf7f25bed3..4e2789768d 100644 --- a/list-objects.c +++ b/list-objects.c @@ -114,7 +114,8 @@ static void process_tree_contents(struct traversal_context *ctx, while (tree_entry(&desc, &entry)) { if (match != all_entries_interesting) { - match = tree_entry_interesting(&entry, base, 0, + match = tree_entry_interesting(ctx->revs->repo->index, + &entry, base, 0, &ctx->revs->diffopt.pathspec); if (match == all_entries_not_interesting) break; diff --git a/merge-recursive.c b/merge-recursive.c index ecf8db0b71..59ba4b4a1a 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -469,7 +469,8 @@ static void get_files_dirs(struct merge_options *o, struct tree *tree) { struct pathspec match_all; memset(&match_all, 0, sizeof(match_all)); - read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o); + read_tree_recursive(the_repository, tree, "", 0, 0, + &match_all, save_files_dirs, o); } static int get_tree_entry_if_blob(const struct object_id *tree, diff --git a/object-store.h b/object-store.h index 60758efad8..e16aa38cae 100644 --- a/object-store.h +++ b/object-store.h @@ -20,7 +20,7 @@ struct object_directory { * Be sure to call odb_load_loose_cache() before using. */ char loose_objects_subdir_seen[256]; - struct oid_array loose_objects_cache; + struct oid_array loose_objects_cache[256]; /* * Path to the alternative object store. If this is a relative path, @@ -48,11 +48,14 @@ void add_to_alternates_file(const char *dir); void add_to_alternates_memory(const char *dir); /* - * Populate an odb's loose object cache for one particular subdirectory (i.e., - * the one that corresponds to the first byte of objects you're interested in, - * from 0 to 255 inclusive). + * Populate and return the loose object cache array corresponding to the + * given object ID. */ -void odb_load_loose_cache(struct object_directory *odb, int subdir_nr); +struct oid_array *odb_loose_cache(struct object_directory *odb, + const struct object_id *oid); + +/* Empty the loose object cache for the specified object directory. */ +void odb_clear_loose_cache(struct object_directory *odb); struct packed_git { struct packed_git *next; @@ -485,7 +485,7 @@ struct raw_object_store *raw_object_store_new(void) static void free_object_directory(struct object_directory *odb) { free(odb->path); - oid_array_clear(&odb->loose_objects_cache); + odb_clear_loose_cache(odb); free(odb); } diff --git a/packfile.c b/packfile.c index 8c6b47cc77..0fe9c21bf1 100644 --- a/packfile.c +++ b/packfile.c @@ -994,11 +994,8 @@ void reprepare_packed_git(struct repository *r) { struct object_directory *odb; - for (odb = r->objects->odb; odb; odb = odb->next) { - oid_array_clear(&odb->loose_objects_cache); - memset(&odb->loose_objects_subdir_seen, 0, - sizeof(odb->loose_objects_subdir_seen)); - } + for (odb = r->objects->odb; odb; odb = odb->next) + odb_clear_loose_cache(odb); r->objects->approximate_object_count_valid = 0; r->objects->packed_git_initialized = 0; diff --git a/parse-options.c b/parse-options.c index 01c2acbd27..9f84bacce6 100644 --- a/parse-options.c +++ b/parse-options.c @@ -236,7 +236,7 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio } static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, - const struct option *options) + const struct option *options) { const struct option *all_opts = options; const char *arg_end = strchrnul(arg, '='); diff --git a/parse-options.h b/parse-options.h index 81380c22b6..14fe32428e 100644 --- a/parse-options.h +++ b/parse-options.h @@ -175,11 +175,11 @@ struct option { * Returns the number of arguments left in argv[]. */ extern int parse_options(int argc, const char **argv, const char *prefix, - const struct option *options, - const char * const usagestr[], int flags); + const struct option *options, + const char * const usagestr[], int flags); extern NORETURN void usage_with_options(const char * const *usagestr, - const struct option *options); + const struct option *options); extern NORETURN void usage_msg_opt(const char *msg, const char * const *usagestr, diff --git a/pathspec.c b/pathspec.c index 6f005996fd..e85298f68c 100644 --- a/pathspec.c +++ b/pathspec.c @@ -659,3 +659,41 @@ void clear_pathspec(struct pathspec *pathspec) FREE_AND_NULL(pathspec->items); pathspec->nr = 0; } + +int match_pathspec_attrs(const struct index_state *istate, + const char *name, int namelen, + const struct pathspec_item *item) +{ + int i; + char *to_free = NULL; + + if (name[namelen]) + name = to_free = xmemdupz(name, namelen); + + git_check_attr(istate, name, item->attr_check); + + free(to_free); + + for (i = 0; i < item->attr_match_nr; i++) { + const char *value; + int matched; + enum attr_match_mode match_mode; + + value = item->attr_check->items[i].value; + match_mode = item->attr_match[i].match_mode; + + if (ATTR_TRUE(value)) + matched = (match_mode == MATCH_SET); + else if (ATTR_FALSE(value)) + matched = (match_mode == MATCH_UNSET); + else if (ATTR_UNSET(value)) + matched = (match_mode == MATCH_UNSPECIFIED); + else + matched = (match_mode == MATCH_VALUE && + !strcmp(item->attr_match[i].value, value)); + if (!matched) + return 0; + } + + return 1; +} diff --git a/pathspec.h b/pathspec.h index a6525a6551..1c18a2c90c 100644 --- a/pathspec.h +++ b/pathspec.h @@ -80,13 +80,13 @@ struct pathspec { * Any arguments used are copied. It is safe for the caller to modify * or free 'prefix' and 'args' after calling this function. */ -extern void parse_pathspec(struct pathspec *pathspec, - unsigned magic_mask, - unsigned flags, - const char *prefix, - const char **args); -extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src); -extern void clear_pathspec(struct pathspec *); +void parse_pathspec(struct pathspec *pathspec, + unsigned magic_mask, + unsigned flags, + const char *prefix, + const char **args); +void copy_pathspec(struct pathspec *dst, const struct pathspec *src); +void clear_pathspec(struct pathspec *); static inline int ps_strncmp(const struct pathspec_item *item, const char *s1, const char *s2, size_t n) @@ -106,10 +106,13 @@ static inline int ps_strcmp(const struct pathspec_item *item, return strcmp(s1, s2); } -extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, - const struct index_state *istate, - char *seen); -extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec, - const struct index_state *istate); +void add_pathspec_matches_against_index(const struct pathspec *pathspec, + const struct index_state *istate, + char *seen); +char *find_pathspecs_matching_against_index(const struct pathspec *pathspec, + const struct index_state *istate); +int match_pathspec_attrs(const struct index_state *istate, + const char *name, int namelen, + const struct pathspec_item *item); #endif /* PATHSPEC_H */ @@ -234,7 +234,7 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen) * Return value is the same as in (1). */ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, - struct strbuf *sb, FILE *fp, int no_dq) + struct strbuf *sb, FILE *fp, int no_dq) { #undef EMIT #define EMIT(c) \ diff --git a/read-cache.c b/read-cache.c index 48c1797a4a..bfff271a3d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -3495,71 +3495,71 @@ static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset) { - const char *index = NULL; - uint32_t extsize, ext_version; - struct index_entry_offset_table *ieot; - int i, nr; - - /* find the IEOT extension */ - if (!offset) - return NULL; - while (offset <= mmap_size - the_hash_algo->rawsz - 8) { - extsize = get_be32(mmap + offset + 4); - if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) { - index = mmap + offset + 4 + 4; - break; - } - offset += 8; - offset += extsize; - } - if (!index) - return NULL; - - /* validate the version is IEOT_VERSION */ - ext_version = get_be32(index); - if (ext_version != IEOT_VERSION) { - error("invalid IEOT version %d", ext_version); - return NULL; - } - index += sizeof(uint32_t); - - /* extension size - version bytes / bytes per entry */ - nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t)); - if (!nr) { - error("invalid number of IEOT entries %d", nr); - return NULL; - } - ieot = xmalloc(sizeof(struct index_entry_offset_table) - + (nr * sizeof(struct index_entry_offset))); - ieot->nr = nr; - for (i = 0; i < nr; i++) { - ieot->entries[i].offset = get_be32(index); - index += sizeof(uint32_t); - ieot->entries[i].nr = get_be32(index); - index += sizeof(uint32_t); - } - - return ieot; + const char *index = NULL; + uint32_t extsize, ext_version; + struct index_entry_offset_table *ieot; + int i, nr; + + /* find the IEOT extension */ + if (!offset) + return NULL; + while (offset <= mmap_size - the_hash_algo->rawsz - 8) { + extsize = get_be32(mmap + offset + 4); + if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) { + index = mmap + offset + 4 + 4; + break; + } + offset += 8; + offset += extsize; + } + if (!index) + return NULL; + + /* validate the version is IEOT_VERSION */ + ext_version = get_be32(index); + if (ext_version != IEOT_VERSION) { + error("invalid IEOT version %d", ext_version); + return NULL; + } + index += sizeof(uint32_t); + + /* extension size - version bytes / bytes per entry */ + nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t)); + if (!nr) { + error("invalid number of IEOT entries %d", nr); + return NULL; + } + ieot = xmalloc(sizeof(struct index_entry_offset_table) + + (nr * sizeof(struct index_entry_offset))); + ieot->nr = nr; + for (i = 0; i < nr; i++) { + ieot->entries[i].offset = get_be32(index); + index += sizeof(uint32_t); + ieot->entries[i].nr = get_be32(index); + index += sizeof(uint32_t); + } + + return ieot; } static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot) { - uint32_t buffer; - int i; + uint32_t buffer; + int i; - /* version */ - put_be32(&buffer, IEOT_VERSION); - strbuf_add(sb, &buffer, sizeof(uint32_t)); + /* version */ + put_be32(&buffer, IEOT_VERSION); + strbuf_add(sb, &buffer, sizeof(uint32_t)); - /* ieot */ - for (i = 0; i < ieot->nr; i++) { + /* ieot */ + for (i = 0; i < ieot->nr; i++) { - /* offset */ - put_be32(&buffer, ieot->entries[i].offset); - strbuf_add(sb, &buffer, sizeof(uint32_t)); + /* offset */ + put_be32(&buffer, ieot->entries[i].offset); + strbuf_add(sb, &buffer, sizeof(uint32_t)); - /* count */ - put_be32(&buffer, ieot->entries[i].nr); - strbuf_add(sb, &buffer, sizeof(uint32_t)); - } + /* count */ + put_be32(&buffer, ieot->entries[i].nr); + strbuf_add(sb, &buffer, sizeof(uint32_t)); + } } diff --git a/ref-filter.c b/ref-filter.c index 61d75d5c86..422a9c9ae3 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -231,12 +231,30 @@ static int objecttype_atom_parser(const struct ref_format *format, struct used_a static int objectsize_atom_parser(const struct ref_format *format, struct used_atom *atom, const char *arg, struct strbuf *err) { + if (!arg) { + if (*atom->name == '*') + oi_deref.info.sizep = &oi_deref.size; + else + oi.info.sizep = &oi.size; + } else if (!strcmp(arg, "disk")) { + if (*atom->name == '*') + oi_deref.info.disk_sizep = &oi_deref.disk_size; + else + oi.info.disk_sizep = &oi.disk_size; + } else + return strbuf_addf_ret(err, -1, _("unrecognized %%(objectsize) argument: %s"), arg); + return 0; +} + +static int deltabase_atom_parser(const struct ref_format *format, struct used_atom *atom, + const char *arg, struct strbuf *err) +{ if (arg) - return strbuf_addf_ret(err, -1, _("%%(objectsize) does not take arguments")); + return strbuf_addf_ret(err, -1, _("%%(deltabase) does not take arguments")); if (*atom->name == '*') - oi_deref.info.sizep = &oi_deref.size; + oi_deref.info.delta_base_sha1 = oi_deref.delta_base_oid.hash; else - oi.info.sizep = &oi.size; + oi.info.delta_base_sha1 = oi.delta_base_oid.hash; return 0; } @@ -431,6 +449,7 @@ static struct { { "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser }, { "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser }, { "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser }, + { "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser }, { "tree", SOURCE_OBJ }, { "parent", SOURCE_OBJ }, { "numparent", SOURCE_OBJ, FIELD_ULONG }, @@ -880,10 +899,14 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_ name++; if (!strcmp(name, "objecttype")) v->s = xstrdup(type_name(oi->type)); - else if (!strcmp(name, "objectsize")) { + else if (!strcmp(name, "objectsize:disk")) { + v->value = oi->disk_size; + v->s = xstrfmt("%"PRIuMAX, (uintmax_t)oi->disk_size); + } else if (!strcmp(name, "objectsize")) { v->value = oi->size; v->s = xstrfmt("%"PRIuMAX , (uintmax_t)oi->size); - } + } else if (!strcmp(name, "deltabase")) + v->s = xstrdup(oid_to_hex(&oi->delta_base_oid)); else if (deref) grab_objectname(name, &oi->oid, v, &used_atom[i]); } @@ -1482,6 +1505,8 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj OBJECT_INFO_LOOKUP_REPLACE)) return strbuf_addf_ret(err, -1, _("missing object %s for %s"), oid_to_hex(&oi->oid), ref->refname); + if (oi->info.disk_sizep && oi->disk_size < 0) + BUG("Object size is less than zero."); if (oi->info.contentp) { *obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten); diff --git a/remote-curl.c b/remote-curl.c index 1220dffcdc..90d565c8c5 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -617,7 +617,8 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) return err; } -static curl_off_t xcurl_off_t(size_t len) { +static curl_off_t xcurl_off_t(size_t len) +{ uintmax_t size = len; if (size > maximum_signed_value_of_type(curl_off_t)) die("cannot handle pushes this big"); diff --git a/revision.c b/revision.c index 13e0519c02..13cfb59b38 100644 --- a/revision.c +++ b/revision.c @@ -1463,6 +1463,7 @@ void repo_init_revisions(struct repository *r, revs->abbrev = DEFAULT_ABBREV; revs->ignore_merges = 1; revs->simplify_history = 1; + revs->pruning.repo = r; revs->pruning.flags.recursive = 1; revs->pruning.flags.quick = 1; revs->pruning.add_remove = file_add_remove; @@ -1495,8 +1496,8 @@ void repo_init_revisions(struct repository *r, } static void add_pending_commit_list(struct rev_info *revs, - struct commit_list *commit_list, - unsigned int flags) + struct commit_list *commit_list, + unsigned int flags) { while (commit_list) { struct object *object = &commit_list->item->object; @@ -1729,6 +1730,8 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, &oid, flags ^ local_flags); + if (!object) + return revs->ignore_missing ? 0 : -1; add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags); add_pending_object_with_path(revs, object, arg, oc.mode, oc.path); free(oc.path); @@ -1791,7 +1794,8 @@ static void add_message_grep(struct rev_info *revs, const char *pattern) } static int handle_revision_opt(struct rev_info *revs, int argc, const char **argv, - int *unkc, const char **unkv) + int *unkc, const char **unkv, + const struct setup_revision_opt* opt) { const char *arg = argv[0]; const char *optarg; @@ -2151,7 +2155,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->limited = 1; } else if (!strcmp(arg, "--ignore-missing")) { revs->ignore_missing = 1; - } else if (revs->allow_exclude_promisor_objects_opt && + } else if (opt && opt->allow_exclude_promisor_objects && !strcmp(arg, "--exclude-promisor-objects")) { if (fetch_if_missing) BUG("exclude_promisor_objects can only be used when fetch_if_missing is 0"); @@ -2173,7 +2177,7 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, const char * const usagestr[]) { int n = handle_revision_opt(revs, ctx->argc, ctx->argv, - &ctx->cpidx, ctx->out); + &ctx->cpidx, ctx->out, NULL); if (n <= 0) { error("unknown option `%s'", ctx->argv[0]); usage_with_options(usagestr, options); @@ -2391,7 +2395,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s continue; } - opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv); + opts = handle_revision_opt(revs, argc - i, argv + i, + &left, argv, opt); if (opts > 0) { i += opts - 1; continue; diff --git a/revision.h b/revision.h index 7987bfcd2e..52e5a88ff5 100644 --- a/revision.h +++ b/revision.h @@ -161,7 +161,6 @@ struct rev_info { do_not_die_on_missing_tree:1, /* for internal use only */ - allow_exclude_promisor_objects_opt:1, exclude_promisor_objects:1; /* Diff flags */ @@ -297,7 +296,8 @@ struct setup_revision_opt { const char *def; void (*tweak)(struct rev_info *, struct setup_revision_opt *); const char *submodule; /* TODO: drop this and use rev_info->repo */ - int assume_dashdash; + unsigned int assume_dashdash:1, + allow_exclude_promisor_objects:1; unsigned revarg_opt; }; diff --git a/sequencer.c b/sequencer.c index b68bca0bef..213815dbfc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -158,6 +158,7 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate") static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") +static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") static int git_sequencer_config(const char *k, const char *v, void *cb) { @@ -1689,7 +1690,8 @@ static int update_squash_messages(struct repository *r, return res; } -static void flush_rewritten_pending(void) { +static void flush_rewritten_pending(void) +{ struct strbuf buf = STRBUF_INIT; struct object_id newoid; FILE *out; @@ -1714,7 +1716,8 @@ static void flush_rewritten_pending(void) { } static void record_in_rewritten(struct object_id *oid, - enum todo_command next_command) { + enum todo_command next_command) +{ FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a"); if (!out) @@ -1788,9 +1791,13 @@ static int do_pick_commit(struct repository *r, return error(_("commit %s does not have parent %d"), oid_to_hex(&commit->object.oid), opts->mainline); parent = p->item; - } else if (0 < opts->mainline) - return error(_("mainline was specified but commit %s is not a merge."), - oid_to_hex(&commit->object.oid)); + } else if (1 < opts->mainline) + /* + * Non-first parent explicitly specified as mainline for + * non-merge commit + */ + return error(_("commit %s does not have parent %d"), + oid_to_hex(&commit->object.oid), opts->mainline); else parent = commit->parents->item; @@ -2388,6 +2395,9 @@ static int read_populate_opts(struct replay_opts *opts) opts->signoff = 1; } + if (file_exists(rebase_path_reschedule_failed_exec())) + opts->reschedule_failed_exec = 1; + read_strategy_opts(opts, &buf); strbuf_release(&buf); @@ -2469,6 +2479,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign); if (opts->signoff) write_file(rebase_path_signoff(), "--signoff\n"); + if (opts->reschedule_failed_exec) + write_file(rebase_path_reschedule_failed_exec(), "%s", ""); return 0; } @@ -3626,9 +3638,10 @@ static int pick_commits(struct repository *r, *end_of_arg = saved; /* Reread the todo file if it has changed. */ - if (res) - ; /* fall through */ - else if (stat(get_todo_path(opts), &st)) + if (res) { + if (opts->reschedule_failed_exec) + reschedule = 1; + } else if (stat(get_todo_path(opts), &st)) res = error_errno(_("could not stat '%s'"), get_todo_path(opts)); else if (match_stat_data(&todo_list->stat, &st)) { diff --git a/sequencer.h b/sequencer.h index 9d83f0f3e9..5d7bc04751 100644 --- a/sequencer.h +++ b/sequencer.h @@ -40,6 +40,7 @@ struct replay_opts { int allow_empty_message; int keep_redundant_commits; int verbose; + int reschedule_failed_exec; int mainline; @@ -831,16 +831,6 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, return NULL; } -static const char *setup_nongit(const char *cwd, int *nongit_ok) -{ - if (!nongit_ok) - die(_("not a git repository (or any of the parent directories): %s"), DEFAULT_GIT_DIR_ENVIRONMENT); - if (chdir(cwd)) - die_errno(_("cannot come back to cwd")); - *nongit_ok = 1; - return NULL; -} - static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len) { struct stat buf; @@ -1054,7 +1044,7 @@ const char *setup_git_directory_gently(int *nongit_ok) { static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT; - const char *prefix; + const char *prefix = NULL; struct repository_format repo_fmt; /* @@ -1079,9 +1069,6 @@ const char *setup_git_directory_gently(int *nongit_ok) strbuf_addbuf(&dir, &cwd); switch (setup_git_directory_gently_1(&dir, &gitdir, 1)) { - case GIT_DIR_NONE: - prefix = NULL; - break; case GIT_DIR_EXPLICIT: prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok); break; @@ -1097,29 +1084,51 @@ const char *setup_git_directory_gently(int *nongit_ok) prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok); break; case GIT_DIR_HIT_CEILING: - prefix = setup_nongit(cwd.buf, nongit_ok); + if (!nongit_ok) + die(_("not a git repository (or any of the parent directories): %s"), + DEFAULT_GIT_DIR_ENVIRONMENT); + *nongit_ok = 1; break; case GIT_DIR_HIT_MOUNT_POINT: - if (nongit_ok) { - *nongit_ok = 1; - strbuf_release(&cwd); - strbuf_release(&dir); - return NULL; - } - die(_("not a git repository (or any parent up to mount point %s)\n" - "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."), - dir.buf); + if (!nongit_ok) + die(_("not a git repository (or any parent up to mount point %s)\n" + "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."), + dir.buf); + *nongit_ok = 1; + break; + case GIT_DIR_NONE: + /* + * As a safeguard against setup_git_directory_gently_1 returning + * this value, fallthrough to BUG. Otherwise it is possible to + * set startup_info->have_repository to 1 when we did nothing to + * find a repository. + */ default: BUG("unhandled setup_git_directory_1() result"); } - if (prefix) - setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1); - else + /* + * At this point, nongit_ok is stable. If it is non-NULL and points + * to a non-zero value, then this means that we haven't found a + * repository and that the caller expects startup_info to reflect + * this. + * + * Regardless of the state of nongit_ok, startup_info->prefix and + * the GIT_PREFIX environment variable must always match. For details + * see Documentation/config/alias.txt. + */ + if (nongit_ok && *nongit_ok) { + startup_info->have_repository = 0; + startup_info->prefix = NULL; setenv(GIT_PREFIX_ENVIRONMENT, "", 1); - - startup_info->have_repository = !nongit_ok || !*nongit_ok; - startup_info->prefix = prefix; + } else { + startup_info->have_repository = 1; + startup_info->prefix = prefix; + if (prefix) + setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1); + else + setenv(GIT_PREFIX_ENVIRONMENT, "", 1); + } /* * Not all paths through the setup code will call 'set_git_dir()' (which @@ -1132,7 +1141,10 @@ const char *setup_git_directory_gently(int *nongit_ok) * the user has set GIT_DIR. It may be beneficial to disallow bogus * GIT_DIR values at some point in the future. */ - if (startup_info->have_repository || getenv(GIT_DIR_ENVIRONMENT)) { + if (/* GIT_DIR_EXPLICIT, GIT_DIR_DISCOVERED, GIT_DIR_BARE */ + startup_info->have_repository || + /* GIT_DIR_EXPLICIT */ + getenv(GIT_DIR_ENVIRONMENT)) { if (!the_repository->gitdir) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); if (!gitdir) diff --git a/sha1-file.c b/sha1-file.c index 5a272f70de..386b96e1d7 100644 --- a/sha1-file.c +++ b/sha1-file.c @@ -924,7 +924,6 @@ static int open_sha1_file(struct repository *r, static int quick_has_loose(struct repository *r, const unsigned char *sha1) { - int subdir_nr = sha1[0]; struct object_id oid; struct object_directory *odb; @@ -932,8 +931,7 @@ static int quick_has_loose(struct repository *r, prepare_alt_odb(r); for (odb = r->objects->odb; odb; odb = odb->next) { - odb_load_loose_cache(odb, subdir_nr); - if (oid_array_lookup(&odb->loose_objects_cache, &oid) >= 0) + if (oid_array_lookup(odb_loose_cache(odb, &oid), &oid) >= 0) return 1; } return 0; @@ -962,6 +960,7 @@ static void *map_sha1_file_1(struct repository *r, const char *path, if (!*size) { /* mmap() is forbidden on empty files */ error(_("object file %s is empty"), path); + close(fd); return NULL; } map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -2152,8 +2151,10 @@ static int append_loose_object(const struct object_id *oid, const char *path, return 0; } -void odb_load_loose_cache(struct object_directory *odb, int subdir_nr) +struct oid_array *odb_loose_cache(struct object_directory *odb, + const struct object_id *oid) { + int subdir_nr = oid->hash[0]; struct strbuf buf = STRBUF_INIT; if (subdir_nr < 0 || @@ -2161,15 +2162,26 @@ void odb_load_loose_cache(struct object_directory *odb, int subdir_nr) BUG("subdir_nr out of range"); if (odb->loose_objects_subdir_seen[subdir_nr]) - return; + return &odb->loose_objects_cache[subdir_nr]; strbuf_addstr(&buf, odb->path); for_each_file_in_obj_subdir(subdir_nr, &buf, append_loose_object, NULL, NULL, - &odb->loose_objects_cache); + &odb->loose_objects_cache[subdir_nr]); odb->loose_objects_subdir_seen[subdir_nr] = 1; strbuf_release(&buf); + return &odb->loose_objects_cache[subdir_nr]; +} + +void odb_clear_loose_cache(struct object_directory *odb) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(odb->loose_objects_cache); i++) + oid_array_clear(&odb->loose_objects_cache[i]); + memset(&odb->loose_objects_subdir_seen, 0, + sizeof(odb->loose_objects_subdir_seen)); } static int check_stream_sha1(git_zstream *stream, diff --git a/sha1-name.c b/sha1-name.c index b24502811b..a656481c6a 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -87,21 +87,21 @@ static int match_sha(unsigned, const unsigned char *, const unsigned char *); static void find_short_object_filename(struct disambiguate_state *ds) { - int subdir_nr = ds->bin_pfx.hash[0]; struct object_directory *odb; for (odb = the_repository->objects->odb; odb && !ds->ambiguous; odb = odb->next) { int pos; + struct oid_array *loose_objects; - odb_load_loose_cache(odb, subdir_nr); - pos = oid_array_lookup(&odb->loose_objects_cache, &ds->bin_pfx); + loose_objects = odb_loose_cache(odb, &ds->bin_pfx); + pos = oid_array_lookup(loose_objects, &ds->bin_pfx); if (pos < 0) pos = -1 - pos; - while (!ds->ambiguous && pos < odb->loose_objects_cache.nr) { + while (!ds->ambiguous && pos < loose_objects->nr) { const struct object_id *oid; - oid = odb->loose_objects_cache.oid + pos; + oid = loose_objects->oid + pos; if (!match_sha(ds->len, ds->bin_pfx.hash, oid->hash)) break; update_candidates(ds, oid); diff --git a/sideband.c b/sideband.c index 368647acf8..7c3d33d3f8 100644 --- a/sideband.c +++ b/sideband.c @@ -87,7 +87,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) struct keyword_entry *p = keywords + i; int len = strlen(p->keyword); - if (n <= len) + if (n < len) continue; /* * Match case insensitively, so we colorize output from existing @@ -95,7 +95,8 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) * messages. We only highlight the word precisely, so * "successful" stays uncolored. */ - if (!strncasecmp(p->keyword, src, len) && !isalnum(src[len])) { + if (!strncasecmp(p->keyword, src, len) && + (len == n || !isalnum(src[len]))) { strbuf_addstr(dest, p->color); strbuf_add(dest, src, len); strbuf_addstr(dest, GIT_COLOR_RESET); diff --git a/string-list.c b/string-list.c index 1f6063f2a2..a917955fbd 100644 --- a/string-list.c +++ b/string-list.c @@ -155,7 +155,8 @@ static int item_is_not_empty(struct string_list_item *item, void *unused) return *item->string != '\0'; } -void string_list_remove_empty_items(struct string_list *list, int free_util) { +void string_list_remove_empty_items(struct string_list *list, int free_util) +{ filter_string_list(list, free_util, item_is_not_empty, NULL); } diff --git a/submodule.c b/submodule.c index 6415cc5580..d393e947e6 100644 --- a/submodule.c +++ b/submodule.c @@ -1561,6 +1561,18 @@ out: return ret; } +void submodule_unset_core_worktree(const struct submodule *sub) +{ + char *config_path = xstrfmt("%s/modules/%s/config", + get_git_common_dir(), sub->name); + + if (git_config_set_in_file_gently(config_path, "core.worktree", NULL)) + warning(_("Could not unset core.worktree setting in submodule '%s'"), + sub->path); + + free(config_path); +} + static const char *get_super_prefix_or_empty(void) { const char *s = get_super_prefix(); @@ -1726,6 +1738,8 @@ int submodule_move_head(const char *path, if (is_empty_dir(path)) rmdir_or_warn(path); + + submodule_unset_core_worktree(sub); } } out: diff --git a/submodule.h b/submodule.h index a680214c01..9e18e9b807 100644 --- a/submodule.h +++ b/submodule.h @@ -131,6 +131,8 @@ int submodule_move_head(const char *path, const char *new_head, unsigned flags); +void submodule_unset_core_worktree(const struct submodule *sub); + /* * Prepare the "env_array" parameter of a "struct child_process" for executing * a submodule by clearing any repo-specific environment variables, but diff --git a/symlinks.c b/symlinks.c index 5261e8cf49..69d458a24d 100644 --- a/symlinks.c +++ b/symlinks.c @@ -221,7 +221,7 @@ int has_symlink_leading_path(const char *name, int len) */ int check_leading_path(const char *name, int len) { - return threaded_check_leading_path(&default_cache, name, len); + return threaded_check_leading_path(&default_cache, name, len); } /* @@ -186,6 +186,22 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel jobs until + one of them fails. Useful for reproducing rare failures in + flaky tests. The number of parallel jobs is, in order of + precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD + environment variable, or twice the number of available + processors (as shown by the 'getconf' utility), or 8. + Implies `--verbose -x --immediate` to get the most information + about the failure. Note that the verbose output of each test + job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out', + and only the output of the failed test job is shown on the + terminal. The names of the trash directories get a + '.stress-<nr>' suffix, and the trash directory of the failed + test job is renamed to end with a '.stress-failed' suffix. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various @@ -425,7 +441,8 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index b45bdac688..8037eef777 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -35,6 +35,7 @@ while (<>) { chomp; } + /\bcp\s+-a/ and err 'cp -a is not portable'; /\bsed\s+-i/ and err 'sed -i is not portable'; /\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c index 77ac5bc33f..d013bccdda 100644 --- a/t/helper/test-sigchain.c +++ b/t/helper/test-sigchain.c @@ -14,7 +14,8 @@ X(two) X(three) #undef X -int cmd__sigchain(int argc, const char **argv) { +int cmd__sigchain(int argc, const char **argv) +{ sigchain_push(SIGTERM, one); sigchain_push(SIGTERM, two); sigchain_push(SIGTERM, three); diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index f98de95c15..79db3b7ae5 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo @@ -54,19 +54,11 @@ start_git_daemon() { "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ >&3 2>git_daemon_output & GIT_DAEMON_PID=$! - >daemon.log { read -r line <&7 - printf "%s\n" "$line" - printf >&4 "%s\n" "$line" - ( - while read -r line <&7 - do - printf "%s\n" "$line" - printf >&4 "%s\n" "$line" - done - ) & - } 7<git_daemon_output >>"$TRASH_DIRECTORY/daemon.log" && + printf "%s\n" "$line" >&4 + cat <&7 >&4 & + } 7<git_daemon_output && # Check expected output if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble" diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index c27599474c..b3be3ba011 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index a8130f9119..f3b478c307 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1 @@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \ diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index a8729f8232..e465116ef9 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 016391723c..5b56b23166 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -235,7 +235,7 @@ reset_work_tree_to_interested () { then mkdir -p submodule_update/.git/modules/sub1/modules && cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2 - GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree + # core.worktree is unset for sub2 as it is not checked out fi && # indicate we are interested in the submodule: git -C submodule_update config submodule.sub1.url "bogus" && @@ -709,7 +709,8 @@ test_submodule_recursing_with_args_common() { git branch -t remove_sub1 origin/remove_sub1 && $command remove_sub1 && test_superproject_content origin/remove_sub1 && - ! test -e sub1 + ! test -e sub1 && + test_must_fail git config -f .git/modules/sub1/config core.worktree ) ' # ... absorbing a .git directory along the way. diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index beb5927f77..3587e454f1 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -293,9 +293,9 @@ checkout_files () { do rm crlf_false_attr__$f.txt && if test -z "$ceol"; then - git checkout crlf_false_attr__$f.txt + git checkout -- crlf_false_attr__$f.txt else - git -c core.eol=$ceol checkout crlf_false_attr__$f.txt + git -c core.eol=$ceol checkout -- crlf_false_attr__$f.txt fi done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index 5ce47e8af5..0c24a0f9a3 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -430,9 +430,15 @@ test_expect_success '-c with changed comment char' ' test_expect_success '-c with comment char defined in .git/config' ' test_config core.commentchar = && printf "= foo\n" >expect && - printf "foo" | ( - mkdir sub && cd sub && git stripspace -c - ) >actual && + rm -fr sub && + mkdir sub && + printf "foo" | git -C sub stripspace -c >actual && + test_cmp expect actual +' + +test_expect_success '-c outside git repository' ' + printf "# foo\n" >expect && + printf "foo" | nongit git stripspace -c >actual && test_cmp expect actual ' diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 96bf6d6a7d..99a614bc7c 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -31,7 +31,15 @@ test_expect_success 'run_command can run a command' ' test_must_be_empty err ' -test_expect_success 'run_command is restricted to PATH' ' + +test_lazy_prereq RUNS_COMMANDS_FROM_PWD ' + write_script runs-commands-from-pwd <<-\EOF && + true + EOF + runs-commands-from-pwd >/dev/null 2>&1 +' + +test_expect_success !RUNS_COMMANDS_FROM_PWD 'run_command is restricted to PATH' ' write_script should-not-run <<-\EOF && echo yikes EOF diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index ba3887f178..bce02788e6 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -349,7 +349,7 @@ test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob grep $(git -C repo rev-parse bar) out # sanity check that some walking was done ' -test_expect_success 'rev-list accepts missing and promised objects on command line' ' +test_expect_success 'rev-list dies for missing objects on cmd line' ' rm -rf repo && test_create_repo repo && test_commit -C repo foo && @@ -366,7 +366,19 @@ test_expect_success 'rev-list accepts missing and promised objects on command li git -C repo config core.repositoryformatversion 1 && git -C repo config extensions.partialclone "arbitrary string" && - git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB" + + for OBJ in "$COMMIT" "$TREE" "$BLOB"; do + test_must_fail git -C repo rev-list --objects \ + --exclude-promisor-objects "$OBJ" && + test_must_fail git -C repo rev-list --objects-edge-aggressive \ + --exclude-promisor-objects "$OBJ" && + + # Do not die or crash when --ignore-missing is passed. + git -C repo rev-list --ignore-missing --objects \ + --exclude-promisor-objects "$OBJ" && + git -C repo rev-list --ignore-missing --objects-edge-aggressive \ + --exclude-promisor-objects "$OBJ" + done ' test_expect_success 'gc repacks promisor objects separately from non-promisor objects' ' @@ -480,7 +492,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 33c0337733..939d18d728 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -112,6 +112,26 @@ test_expect_success 'move locked worktree (force)' ' git worktree move --force --force flump ploof ' +test_expect_success 'move a repo with uninitialized submodule' ' + git init withsub && + ( + cd withsub && + test_commit initial && + git submodule add "$PWD"/.git sub && + git commit -m withsub && + git worktree add second HEAD && + git worktree move second third + ) +' + +test_expect_success 'not move a repo with initialized submodule' ' + ( + cd withsub && + git -C third submodule update && + test_must_fail git worktree move third forth + ) +' + test_expect_success 'remove main worktree' ' test_must_fail git worktree remove . ' @@ -185,4 +205,21 @@ test_expect_success 'remove cleans up .git/worktrees when empty' ' ) ' +test_expect_success 'remove a repo with uninitialized submodule' ' + ( + cd withsub && + git worktree add to-remove HEAD && + git worktree remove to-remove + ) +' + +test_expect_success 'not remove a repo with initialized submodule' ' + ( + cd withsub && + git worktree add to-remove HEAD && + git -C to-remove submodule update && + test_must_fail git worktree remove to-remove + ) +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 0210b2ac6f..25aaacacfc 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -254,4 +254,18 @@ test_expect_success 'the todo command "break" works' ' test_path_is_file execed ' +test_expect_success '--reschedule-failed-exec' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ && + grep "^exec false" .git/rebase-merge/git-rebase-todo && + git rebase --abort && + test_must_fail git -c rebase.rescheduleFailedExec=true \ + rebase -x false HEAD^ 2>err && + grep "^exec false" .git/rebase-merge/git-rebase-todo && + test_i18ngrep "has been rescheduled" err && + git rebase --abort && + test_must_fail git rebase -y false HEAD^ 2>err && + test_i18ngrep "has been rescheduled" err +' + test_done diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh index b1602718f8..8b635a196d 100755 --- a/t/t3502-cherry-pick-merge.sh +++ b/t/t3502-cherry-pick-merge.sh @@ -40,12 +40,12 @@ test_expect_success 'cherry-pick -m complains of bogus numbers' ' test_expect_code 129 git cherry-pick -m 0 b ' -test_expect_success 'cherry-pick a non-merge with -m should fail' ' +test_expect_success 'cherry-pick explicit first parent of a non-merge' ' git reset --hard && git checkout a^0 && - test_expect_code 128 git cherry-pick -m 1 b && - git diff --exit-code a -- + git cherry-pick -m 1 b && + git diff --exit-code c -- ' @@ -84,12 +84,12 @@ test_expect_success 'cherry pick a merge relative to nonexistent parent should f ' -test_expect_success 'revert a non-merge with -m should fail' ' +test_expect_success 'revert explicit first parent of a non-merge' ' git reset --hard && git checkout c^0 && - test_must_fail git revert -m 1 b && - git diff --exit-code c + git revert -m 1 b && + git diff --exit-code a -- ' diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh index fb889ac6f0..127dd0082f 100755 --- a/t/t3506-cherry-pick-ff.sh +++ b/t/t3506-cherry-pick-ff.sh @@ -64,10 +64,10 @@ test_expect_success 'merge setup' ' git checkout -b new A ' -test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' ' +test_expect_success 'cherry-pick explicit first parent of a non-merge with --ff' ' git reset --hard A -- && - test_must_fail git cherry-pick --ff -m 1 B && - git diff --exit-code A -- + git cherry-pick --ff -m 1 B && + git diff --exit-code C -- ' test_expect_success 'cherry pick a merge with --ff but without -m should fail' ' diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index c84eeefdc9..941d5026da 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -61,7 +61,11 @@ test_expect_success 'cherry-pick mid-cherry-pick-sequence' ' test_expect_success 'cherry-pick persists opts correctly' ' pristine_detach initial && - test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick && + # to make sure that the session to cherry-pick a sequence + # gets interrupted, use a high-enough number that is larger + # than the number of parents of any commit we have created + mainline=4 && + test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours initial..anotherpick && test_path_is_dir .git/sequencer && test_path_is_file .git/sequencer/head && test_path_is_file .git/sequencer/todo && @@ -69,7 +73,7 @@ test_expect_success 'cherry-pick persists opts correctly' ' echo "true" >expect && git config --file=.git/sequencer/opts --get-all options.signoff >actual && test_cmp expect actual && - echo "1" >expect && + echo "$mainline" >expect && git config --file=.git/sequencer/opts --get-all options.mainline >actual && test_cmp expect actual && echo "recursive" >expect && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a9fb226c5a..9a3e4fdfec 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -1890,6 +1890,24 @@ test_expect_success 'compare whitespace delta across moved blocks' ' test_cmp expected actual ' +test_expect_success 'bogus settings in move detection erroring out' ' + test_must_fail git diff --color-moved=bogus 2>err && + test_i18ngrep "must be one of" err && + test_i18ngrep bogus err && + + test_must_fail git -c diff.colormoved=bogus diff 2>err && + test_i18ngrep "must be one of" err && + test_i18ngrep "from command-line config" err && + + test_must_fail git diff --color-moved-ws=bogus 2>err && + test_i18ngrep "possible values" err && + test_i18ngrep bogus err && + + test_must_fail git -c diff.colormovedws=bogus diff 2>err && + test_i18ngrep "possible values" err && + test_i18ngrep "from command-line config" err +' + test_expect_success 'compare whitespace delta incompatible with other space options' ' test_must_fail git diff \ --color-moved-ws=allow-indentation-change,ignore-all-space \ diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh index 844df760f7..5d06f5f45e 100755 --- a/t/t4209-log-pickaxe.sh +++ b/t/t4209-log-pickaxe.sh @@ -106,4 +106,39 @@ test_expect_success 'log -S --no-textconv (missing textconv tool)' ' rm .gitattributes ' +test_expect_success 'setup log -[GS] binary & --text' ' + git checkout --orphan GS-binary-and-text && + git read-tree --empty && + printf "a\na\0a\n" >data.bin && + git add data.bin && + git commit -m "create binary file" data.bin && + printf "a\na\0a\n" >>data.bin && + git commit -m "modify binary file" data.bin && + git rm data.bin && + git commit -m "delete binary file" data.bin && + git log >full-log +' + +test_expect_success 'log -G ignores binary files' ' + git log -Ga >log && + test_must_be_empty log +' + +test_expect_success 'log -G looks into binary files with -a' ' + git log -a -Ga >log && + test_cmp log full-log +' + +test_expect_success 'log -G looks into binary files with textconv filter' ' + test_when_finished "rm .gitattributes" && + echo "* diff=bin" >.gitattributes && + git -c diff.bin.textconv=cat log -Ga >log && + test_cmp log full-log +' + +test_expect_success 'log -S looks into binary files' ' + git log -Sa >log && + test_cmp log full-log +' + test_done diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index ced44355ca..271eb5a1fd 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -3,8 +3,12 @@ test_description='test corner cases of git-archive' . ./test-lib.sh -test_expect_success 'create commit with empty tree' ' - git commit --allow-empty -m foo +# the 10knuls.tar file is used to test for an empty git generated tar +# without having to invoke tar because an otherwise valid empty GNU tar +# will be considered broken by {Open,Net}BSD tar +test_expect_success 'create commit with empty tree and fake empty tar' ' + git commit --allow-empty -m foo && + perl -e "print \"\\0\" x 10240" >10knuls.tar ' # Make a dir and clean it up afterwards @@ -47,7 +51,6 @@ test_expect_success HEADER_ONLY_TAR_OK 'tar archive of commit with empty tree' ' test_expect_success 'tar archive of empty tree is empty' ' git archive --format=tar HEAD: >empty.tar && - perl -e "print \"\\0\" x 10240" >10knuls.tar && test_cmp_bin 10knuls.tar empty.tar ' @@ -106,16 +109,12 @@ test_expect_success 'create a commit with an empty subtree' ' test_expect_success 'archive empty subtree with no pathspec' ' git archive --format=tar $root_tree >subtree-all.tar && - make_dir extract && - "$TAR" xf subtree-all.tar -C extract && - check_dir extract + test_cmp_bin 10knuls.tar subtree-all.tar ' test_expect_success 'archive empty subtree by direct pathspec' ' git archive --format=tar $root_tree -- sub >subtree-path.tar && - make_dir extract && - "$TAR" xf subtree-path.tar -C extract && - check_dir extract + test_cmp_bin 10knuls.tar subtree-path.tar ' ZIPINFO=zipinfo diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index f81b6813c0..2a8c449661 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -17,6 +17,7 @@ test_expect_success 'setup' ' echo " " "error: leading space" echo " " echo Err + echo SUCCESS exit 0 EOF echo 1 >file && @@ -35,6 +36,7 @@ test_expect_success 'keywords' ' grep "<BOLD;RED>error<RESET>: error" decoded && grep "<YELLOW>hint<RESET>:" decoded && grep "<BOLD;GREEN>success<RESET>:" decoded && + grep "<BOLD;GREEN>SUCCESS<RESET>" decoded && grep "<BOLD;YELLOW>warning<RESET>:" decoded ' diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 32e722db2e..cd9e60632d 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -260,7 +260,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 6c2f9b2ba2..a0317556c6 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -524,6 +524,8 @@ test_expect_success 'fetching submodules respects parallel settings' ' git config fetch.recurseSubmodules true && ( cd downstream && + GIT_TRACE=$(pwd)/trace.out git fetch && + grep "1 tasks" trace.out && GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 && grep "7 tasks" trace.out && git config submodule.fetchJobs 8 && diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 7466aad111..58ee787685 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -183,19 +183,6 @@ test_expect_success 'hostname cannot break out of directory' ' git ls-remote "$GIT_DAEMON_URL/escape.git" ' -test_expect_success 'daemon log records all attributes' ' - cat >expect <<-\EOF && - Extended attribute "host": localhost - Extended attribute "protocol": version=1 - EOF - >daemon.log && - GIT_OVERRIDE_VIRTUAL_HOST=localhost \ - git -c protocol.version=1 \ - ls-remote "$GIT_DAEMON_URL/interp.git" && - grep -i extended.attribute daemon.log | cut -d" " -f2- >actual && - test_cmp expect actual -' - test_expect_success FAKENC 'hostname interpolation works after LF-stripping' ' { printf "git-upload-pack /interp.git\n\0host=localhost" | packetize diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 8bbc7068ac..d6948cbdab 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -487,7 +487,7 @@ test_clone_url () { expect_ssh "$@" } -test_expect_success !MINGW 'clone c:temp is ssl' ' +test_expect_success !MINGW,!CYGWIN 'clone c:temp is ssl' ' test_clone_url c:temp c temp ' diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh index e436a73962..457cc167c7 100755 --- a/t/t6135-pathspec-with-attrs.sh +++ b/t/t6135-pathspec-with-attrs.sh @@ -31,7 +31,7 @@ test_expect_success 'setup a tree' ' mkdir sub && while read path do - : >$path && + echo content >$path && git add $path || return 1 done <expect && git commit -m "initial commit" && @@ -48,6 +48,10 @@ test_expect_success 'pathspec with labels and non existent .gitattributes' ' test_must_be_empty actual ' +test_expect_success 'pathspec with labels and non existent .gitattributes (2)' ' + test_must_fail git grep content HEAD -- ":(attr:label)" +' + test_expect_success 'setup .gitattributes' ' cat <<-\EOF >.gitattributes && fileA labelA @@ -74,6 +78,15 @@ test_expect_success 'check specific set attr' ' test_cmp expect actual ' +test_expect_success 'check specific set attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileSetLabel + HEAD:sub/fileSetLabel + EOF + git grep -l content HEAD ":(attr:label)" >actual && + test_cmp expect actual +' + test_expect_success 'check specific unset attr' ' cat <<-\EOF >expect && fileUnsetLabel @@ -83,6 +96,15 @@ test_expect_success 'check specific unset attr' ' test_cmp expect actual ' +test_expect_success 'check specific unset attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileUnsetLabel + HEAD:sub/fileUnsetLabel + EOF + git grep -l content HEAD ":(attr:-label)" >actual && + test_cmp expect actual +' + test_expect_success 'check specific value attr' ' cat <<-\EOF >expect && fileValue @@ -94,6 +116,16 @@ test_expect_success 'check specific value attr' ' test_must_be_empty actual ' +test_expect_success 'check specific value attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileValue + HEAD:sub/fileValue + EOF + git grep -l content HEAD ":(attr:label=foo)" >actual && + test_cmp expect actual && + test_must_fail git grep -l content HEAD ":(attr:label=bar)" +' + test_expect_success 'check unspecified attr' ' cat <<-\EOF >expect && .gitattributes @@ -118,6 +150,30 @@ test_expect_success 'check unspecified attr' ' test_cmp expect actual ' +test_expect_success 'check unspecified attr (2)' ' + cat <<-\EOF >expect && + HEAD:.gitattributes + HEAD:fileA + HEAD:fileAB + HEAD:fileAC + HEAD:fileB + HEAD:fileBC + HEAD:fileC + HEAD:fileNoLabel + HEAD:fileWrongLabel + HEAD:sub/fileA + HEAD:sub/fileAB + HEAD:sub/fileAC + HEAD:sub/fileB + HEAD:sub/fileBC + HEAD:sub/fileC + HEAD:sub/fileNoLabel + HEAD:sub/fileWrongLabel + EOF + git grep -l ^ HEAD ":(attr:!label)" >actual && + test_cmp expect actual +' + test_expect_success 'check multiple unspecified attr' ' cat <<-\EOF >expect && .gitattributes diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 97bfbee6e8..0ffd630713 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -83,6 +83,8 @@ test_atom head push:strip=1 remotes/myfork/master test_atom head push:strip=-1 master test_atom head objecttype commit test_atom head objectsize 171 +test_atom head objectsize:disk 138 +test_atom head deltabase 0000000000000000000000000000000000000000 test_atom head objectname $(git rev-parse refs/heads/master) test_atom head objectname:short $(git rev-parse --short refs/heads/master) test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master) @@ -124,6 +126,10 @@ test_atom tag upstream '' test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize 154 +test_atom tag objectsize:disk 138 +test_atom tag '*objectsize:disk' 138 +test_atom tag deltabase 0000000000000000000000000000000000000000 +test_atom tag '*deltabase' 0000000000000000000000000000000000000000 test_atom tag objectname $(git rev-parse refs/tags/testtag) test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag) test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master) diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 76a7cb0af7..aba2d4d6ee 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -984,6 +984,11 @@ test_expect_success 'submodule deinit should remove the whole submodule section rmdir init ' +test_expect_success 'submodule deinit should unset core.worktree' ' + test_path_is_file .git/modules/example/config && + test_must_fail git config -f .git/modules/example/config core.worktree +' + test_expect_success 'submodule deinit from subdirectory' ' git submodule update --init && git config submodule.example.foo bar && diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh index ce74c12da2..1cfa150768 100755 --- a/t/t7412-submodule-absorbgitdirs.sh +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -75,7 +75,12 @@ test_expect_success 're-setup nested submodule' ' GIT_WORK_TREE=../../../nested git -C sub1/.git/modules/nested config \ core.worktree "../../../nested" && # make sure this re-setup is correct - git status --ignore-submodules=none + git status --ignore-submodules=none && + + # also make sure this old setup does not regress + git submodule update --init --recursive >out 2>err && + test_must_be_empty out && + test_must_be_empty err ' test_expect_success 'absorb the git dir in a nested submodule' ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 6558eee499..3a2c6326d8 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1516,8 +1516,8 @@ test_expect_success 'show completes all refs' ' test_expect_success '<ref>: completes paths' ' test_completion "git show mytag:f" <<-\EOF - file1 Z - file2 Z + file1Z + file2Z EOF ' @@ -1526,7 +1526,7 @@ test_expect_success 'complete tree filename with spaces' ' git add "name with spaces" && git commit -m spaces && test_completion "git show HEAD:nam" <<-\EOF - name with spaces Z + name with spacesZ EOF ' @@ -1535,8 +1535,8 @@ test_expect_success 'complete tree filename with metacharacters' ' git add "name with \${meta}" && git commit -m meta && test_completion "git show HEAD:nam" <<-\EOF - name with ${meta} Z - name with spaces Z + name with ${meta}Z + name with spacesZ EOF ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 6b3bbf99e4..92cf8f812c 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1263,3 +1263,42 @@ test_oid () { fi && eval "printf '%s' \"\${$var}\"" } + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\$$var + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + ;; + *[^0-9]*|0*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 0f1faa24b2..a1abb1177a 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -71,19 +71,222 @@ then exit 1 fi +# Parse options while taking care to leave $@ intact, so we will still +# have all the original command line options when executing the test +# script again for '--tee' and '--verbose-log' below. +store_arg_to= +prev_opt= +for opt +do + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in + -d|--d|--de|--deb|--debu|--debug) + debug=t ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; + -r) + store_arg_to=run_list + ;; + --run=*) + run_list=${opt#--*=} ;; + -h|--h|--he|--hel|--help) + help=t ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + --verbose-only=*) + verbose_only=${opt#--*=} + ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t ;; + --with-dashes) + with_dashes=t ;; + --no-color) + color= ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck + tee=t + ;; + --valgrind=*) + valgrind=${opt#--*=} + tee=t + ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} + tee=t + ;; + --tee) + tee=t ;; + --root=*) + root=${opt#--*=} ;; + --chain-lint) + GIT_TEST_CHAIN_LINT=1 ;; + --no-chain-lint) + GIT_TEST_CHAIN_LINT=0 ;; + -x) + trace=t ;; + -V|--verbose-log) + verbose_log=t + tee=t + ;; + --stress) + stress=t ;; + --stress=*) + stress=${opt#--*=} + case "$stress" in + *[^0-9]*|0*|"") + echo "error: --stress=<N> requires the number of jobs to run" >&2 + exit 1 + ;; + *) # Good. + ;; + esac + ;; + *) + echo "error: unknown test option '$opt'" >&2; exit 1 ;; + esac + + prev_opt=$opt +done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi + +if test -n "$valgrind_only" +then + test -z "$valgrind" && valgrind=memcheck + test -z "$verbose" && verbose_only="$valgrind_only" +elif test -n "$valgrind" +then + test -z "$verbose_log" && verbose=t +fi + +if test -n "$stress" +then + verbose=t + trace=t + immediate=t +fi + +TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}" +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX" +TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac + +# If --stress was passed, run this test repeatedly in several parallel loops. +if test "$GIT_TEST_STRESS_STARTED" = "done" +then + : # Don't stress test again. +elif test -n "$stress" +then + if test "$stress" != t + then + job_count=$stress + elif test -n "$GIT_TEST_STRESS_LOAD" + then + job_count="$GIT_TEST_STRESS_LOAD" + elif job_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null) && + test -n "$job_count" + then + job_count=$((2 * $job_count)) + else + job_count=8 + fi + + mkdir -p "$TEST_RESULTS_DIR" + stressfail="$TEST_RESULTS_BASE.stress-failed" + rm -f "$stressfail" + + stress_exit=0 + trap ' + kill $job_pids 2>/dev/null + wait + stress_exit=1 + ' TERM INT HUP + + job_pids= + job_nr=0 + while test $job_nr -lt "$job_count" + do + ( + GIT_TEST_STRESS_STARTED=done + GIT_TEST_STRESS_JOB_NR=$job_nr + export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + + trap ' + kill $test_pid 2>/dev/null + wait + exit 1 + ' TERM INT + + cnt=0 + while ! test -e "$stressfail" + do + $TEST_SHELL_PATH "$0" "$@" >"$TEST_RESULTS_BASE.stress-$job_nr.out" 2>&1 & + test_pid=$! + + if wait $test_pid + then + printf "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + else + echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail" + printf "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + fi + cnt=$(($cnt + 1)) + done + ) & + job_pids="$job_pids $!" + job_nr=$(($job_nr + 1)) + done + + wait + + if test -f "$stressfail" + then + echo "Log(s) of failed test run(s):" + for failed_job_nr in $(sort -n "$stressfail") + do + echo "Contents of '$TEST_RESULTS_BASE.stress-$failed_job_nr.out':" + cat "$TEST_RESULTS_BASE.stress-$failed_job_nr.out" + done + rm -rf "$TRASH_DIRECTORY.stress-failed" + # Move the last one. + mv "$TRASH_DIRECTORY.stress-$failed_job_nr" "$TRASH_DIRECTORY.stress-failed" + fi + + exit $stress_exit +fi + # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. -case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*) - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" +if test "$GIT_TEST_TEE_STARTED" = "done" +then + : # do not redirect again +elif test -n "$tee" +then + mkdir -p "$TEST_RESULTS_DIR" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results @@ -91,11 +294,38 @@ done,*) >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit - ;; -esac +fi + +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + # + # Perform this version check _after_ the test script was + # potentially re-executed with $TEST_SHELL_PATH for '--tee' or + # '--verbose-log', so the right shell is checked and the + # warning is issued only once. + if test -n "$BASH_VERSION" && eval ' + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + ' + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences. @@ -193,7 +423,7 @@ fi # Add libc MALLOC and MALLOC_PERTURB test # only if we are not executing the test with valgrind -if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null || +if test -n "$valgrind" || test -n "$TEST_NO_MALLOC_CHECK" then setup_malloc_check () { @@ -264,100 +494,6 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 -do - case "$1" in - -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; - -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; - -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) - GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;; - -r) - shift; test "$#" -ne 0 || { - echo 'error: -r requires an argument' >&2; - exit 1; - } - run_list=$1; shift ;; - --run=*) - run_list=${1#--*=}; shift ;; - -h|--h|--he|--hel|--help) - help=t; shift ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; - --verbose-only=*) - verbose_only=${1#--*=} - shift ;; - -q|--q|--qu|--qui|--quie|--quiet) - # Ignore --quiet under a TAP::Harness. Saying how many tests - # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; - --with-dashes) - with_dashes=t; shift ;; - --no-color) - color=; shift ;; - --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck - shift ;; - --valgrind=*) - valgrind=${1#--*=} - shift ;; - --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; - --tee) - shift ;; # was handled already - --root=*) - root=${1#--*=} - shift ;; - --chain-lint) - GIT_TEST_CHAIN_LINT=1 - shift ;; - --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 - shift ;; - -x) - # Some test scripts can't be reliably traced with '-x', - # unless the test is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). Check whether - # this test is marked as such, and ignore '-x' if it - # isn't executed with a suitable Bash version. - if test -z "$test_untraceable" || { - test -n "$BASH_VERSION" && { - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - } - } - then - trace=t - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - fi - shift ;; - -V|--verbose-log) - verbose_log=t - shift ;; - *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; - esac -done - -if test -n "$valgrind_only" -then - test -z "$valgrind" && valgrind=memcheck - test -z "$verbose" && verbose_only="$valgrind_only" -elif test -n "$valgrind" -then - test -z "$verbose_log" && verbose=t -fi - -if test -n "$trace" && test -z "$verbose_log" -then - verbose=t -fi - if test -n "$color" then # Save the color control sequences now rather than run tput @@ -476,7 +612,7 @@ die () { GIT_EXIT_OK= trap 'die' EXIT -trap 'exit $?' INT +trap 'exit $?' INT TERM HUP # The user-facing functions are loaded from a separate file so that # test_perf subshells can have them too @@ -818,12 +954,9 @@ test_done () { if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$TEST_RESULTS_DIR" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed @@ -1029,12 +1162,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" diff --git a/transport-helper.c b/transport-helper.c index bf225c698f..6cf3bb324e 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1026,7 +1026,8 @@ static int push_refs(struct transport *transport, } -static int has_attribute(const char *attrs, const char *attr) { +static int has_attribute(const char *attrs, const char *attr) +{ int len; if (!attrs) return 0; @@ -1225,9 +1226,8 @@ static int udt_do_read(struct unidirectional_transfer *t) return 0; /* No space for more. */ transfer_debug("%s is readable", t->src_name); - bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse); - if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN && - errno != EINTR) { + bytes = xread(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse); + if (bytes < 0) { error_errno(_("read(%s) failed"), t->src_name); return -1; } else if (bytes == 0) { @@ -1254,7 +1254,7 @@ static int udt_do_write(struct unidirectional_transfer *t) transfer_debug("%s is writable", t->dest_name); bytes = xwrite(t->dest, t->buf, t->bufuse); - if (bytes < 0 && errno != EWOULDBLOCK) { + if (bytes < 0) { error_errno(_("write(%s) failed"), t->dest_name); return -1; } else if (bytes > 0) { diff --git a/tree-diff.c b/tree-diff.c index 0e54324610..34ee3b13b8 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -299,7 +299,8 @@ static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, enum interesting match; while (t->size) { - match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); + match = tree_entry_interesting(opt->repo->index, &t->entry, + base, 0, &opt->pathspec); if (match) { if (match == all_entries_not_interesting) t->size = 0; diff --git a/tree-walk.c b/tree-walk.c index 79bafbd1a2..08210a4109 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -365,7 +365,8 @@ static void free_extended_entry(struct tree_desc_x *t) } } -static inline int prune_traversal(struct name_entry *e, +static inline int prune_traversal(struct index_state *istate, + struct name_entry *e, struct traverse_info *info, struct strbuf *base, int still_interesting) @@ -374,10 +375,13 @@ static inline int prune_traversal(struct name_entry *e, return 2; if (still_interesting < 0) return still_interesting; - return tree_entry_interesting(e, base, 0, info->pathspec); + return tree_entry_interesting(istate, e, base, + 0, info->pathspec); } -int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) +int traverse_trees(struct index_state *istate, + int n, struct tree_desc *t, + struct traverse_info *info) { int error = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); @@ -461,7 +465,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) } if (!mask) break; - interesting = prune_traversal(e, info, &base, interesting); + interesting = prune_traversal(istate, e, info, &base, interesting); if (interesting < 0) break; if (interesting) { @@ -928,7 +932,8 @@ static int match_wildcard_base(const struct pathspec_item *item, * Pre-condition: either baselen == base_offset (i.e. empty path) * or base[baselen-1] == '/' (i.e. with trailing slash). */ -static enum interesting do_match(const struct name_entry *entry, +static enum interesting do_match(struct index_state *istate, + const struct name_entry *entry, struct strbuf *base, int base_offset, const struct pathspec *ps, int exclude) @@ -944,7 +949,8 @@ static enum interesting do_match(const struct name_entry *entry, PATHSPEC_LITERAL | PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); + PATHSPEC_EXCLUDE | + PATHSPEC_ATTR); if (!ps->nr) { if (!ps->recursive || @@ -976,14 +982,20 @@ static enum interesting do_match(const struct name_entry *entry, if (!ps->recursive || !(ps->magic & PATHSPEC_MAXDEPTH) || - ps->max_depth == -1) - return all_entries_interesting; - - return within_depth(base_str + matchlen + 1, - baselen - matchlen - 1, - !!S_ISDIR(entry->mode), - ps->max_depth) ? - entry_interesting : entry_not_interesting; + ps->max_depth == -1) { + if (!item->attr_match_nr) + return all_entries_interesting; + else + goto interesting; + } + + if (within_depth(base_str + matchlen + 1, + baselen - matchlen - 1, + !!S_ISDIR(entry->mode), + ps->max_depth)) + goto interesting; + else + return entry_not_interesting; } /* Either there must be no base, or the base must match. */ @@ -991,12 +1003,12 @@ static enum interesting do_match(const struct name_entry *entry, if (match_entry(item, entry, pathlen, match + baselen, matchlen - baselen, &never_interesting)) - return entry_interesting; + goto interesting; if (item->nowildcard_len < item->len) { if (!git_fnmatch(item, match + baselen, entry->path, item->nowildcard_len - baselen)) - return entry_interesting; + goto interesting; /* * Match all directories. We'll try to @@ -1017,7 +1029,7 @@ static enum interesting do_match(const struct name_entry *entry, !ps_strncmp(item, match + baselen, entry->path, item->nowildcard_len - baselen)) - return entry_interesting; + goto interesting; } continue; @@ -1052,7 +1064,7 @@ match_wildcards: if (!git_fnmatch(item, match, base->buf + base_offset, item->nowildcard_len)) { strbuf_setlen(base, base_offset + baselen); - return entry_interesting; + goto interesting; } /* @@ -1066,7 +1078,7 @@ match_wildcards: !ps_strncmp(item, match, base->buf + base_offset, item->nowildcard_len)) { strbuf_setlen(base, base_offset + baselen); - return entry_interesting; + goto interesting; } strbuf_setlen(base, base_offset + baselen); @@ -1080,6 +1092,38 @@ match_wildcards: */ if (ps->recursive && S_ISDIR(entry->mode)) return entry_interesting; + continue; +interesting: + if (item->attr_match_nr) { + int ret; + + /* + * Must not return all_entries_not_interesting + * prematurely. We do not know if all entries do not + * match some attributes with current attr API. + */ + never_interesting = entry_not_interesting; + + /* + * Consider all directories interesting (because some + * of those files inside may match some attributes + * even though the parent dir does not) + * + * FIXME: attributes _can_ match directories and we + * can probably return all_entries_interesting or + * all_entries_not_interesting here if matched. + */ + if (S_ISDIR(entry->mode)) + return entry_interesting; + + strbuf_add(base, entry->path, pathlen); + ret = match_pathspec_attrs(istate, base->buf + base_offset, + base->len - base_offset, item); + strbuf_setlen(base, base_offset + baselen); + if (!ret) + continue; + } + return entry_interesting; } return never_interesting; /* No matches */ } @@ -1090,12 +1134,13 @@ match_wildcards: * Pre-condition: either baselen == base_offset (i.e. empty path) * or base[baselen-1] == '/' (i.e. with trailing slash). */ -enum interesting tree_entry_interesting(const struct name_entry *entry, +enum interesting tree_entry_interesting(struct index_state *istate, + const struct name_entry *entry, struct strbuf *base, int base_offset, const struct pathspec *ps) { enum interesting positive, negative; - positive = do_match(entry, base, base_offset, ps, 0); + positive = do_match(istate, entry, base, base_offset, ps, 0); /* * case | entry | positive | negative | result @@ -1132,7 +1177,7 @@ enum interesting tree_entry_interesting(const struct name_entry *entry, positive <= entry_not_interesting) /* #1, #2, #11, #12 */ return positive; - negative = do_match(entry, base, base_offset, ps, 1); + negative = do_match(istate, entry, base, base_offset, ps, 1); /* #8, #18 */ if (positive == all_entries_interesting && diff --git a/tree-walk.h b/tree-walk.h index 196831007e..eefd26bb62 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -1,6 +1,7 @@ #ifndef TREE_WALK_H #define TREE_WALK_H +struct index_state; struct strbuf; struct name_entry { @@ -48,7 +49,7 @@ void *fill_tree_descriptor(struct tree_desc *desc, const struct object_id *oid); struct traverse_info; typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); -int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); +int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info); enum follow_symlinks_result { FOUND = 0, /* This includes out-of-tree links */ @@ -98,8 +99,9 @@ enum interesting { all_entries_interesting = 2 /* yes, and all subsequent entries will be */ }; -extern enum interesting tree_entry_interesting(const struct name_entry *, - struct strbuf *, int, - const struct pathspec *ps); +enum interesting tree_entry_interesting(struct index_state *istate, + const struct name_entry *, + struct strbuf *, int, + const struct pathspec *ps); #endif @@ -60,7 +60,8 @@ static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base ADD_CACHE_JUST_APPEND); } -static int read_tree_1(struct tree *tree, struct strbuf *base, +static int read_tree_1(struct repository *r, + struct tree *tree, struct strbuf *base, int stage, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) { @@ -77,7 +78,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, while (tree_entry(&desc, &entry)) { if (retval != all_entries_interesting) { - retval = tree_entry_interesting(&entry, base, 0, pathspec); + retval = tree_entry_interesting(r->index, &entry, + base, 0, pathspec); if (retval == all_entries_not_interesting) break; if (retval == entry_not_interesting) @@ -99,7 +101,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, else if (S_ISGITLINK(entry.mode)) { struct commit *commit; - commit = lookup_commit(the_repository, entry.oid); + commit = lookup_commit(r, entry.oid); if (!commit) die("Commit %s in submodule path %s%s not found", oid_to_hex(entry.oid), @@ -118,7 +120,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, len = tree_entry_len(&entry); strbuf_add(base, entry.path, len); strbuf_addch(base, '/'); - retval = read_tree_1(lookup_tree(the_repository, &oid), + retval = read_tree_1(r, lookup_tree(r, &oid), base, stage, pathspec, fn, context); strbuf_setlen(base, oldlen); @@ -128,7 +130,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, return 0; } -int read_tree_recursive(struct tree *tree, +int read_tree_recursive(struct repository *r, + struct tree *tree, const char *base, int baselen, int stage, const struct pathspec *pathspec, read_tree_fn_t fn, void *context) @@ -137,7 +140,7 @@ int read_tree_recursive(struct tree *tree, int ret; strbuf_add(&sb, base, baselen); - ret = read_tree_1(tree, &sb, stage, pathspec, fn, context); + ret = read_tree_1(r, tree, &sb, stage, pathspec, fn, context); strbuf_release(&sb); return ret; } @@ -152,8 +155,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_) ce2->name, ce2->ce_namelen, ce_stage(ce2)); } -int read_tree(struct tree *tree, int stage, struct pathspec *match, - struct index_state *istate) +int read_tree(struct repository *r, struct tree *tree, int stage, + struct pathspec *match, struct index_state *istate) { read_tree_fn_t fn = NULL; int i, err; @@ -181,7 +184,7 @@ int read_tree(struct tree *tree, int stage, struct pathspec *match, if (!fn) fn = read_one_entry_quick; - err = read_tree_recursive(tree, "", 0, stage, match, fn, istate); + err = read_tree_recursive(r, tree, "", 0, stage, match, fn, istate); if (fn == read_one_entry || err) return err; @@ -3,7 +3,7 @@ #include "object.h" -extern const char *tree_type; +struct repository; struct strbuf; struct tree { @@ -12,6 +12,8 @@ struct tree { unsigned long size; }; +extern const char *tree_type; + struct tree *lookup_tree(struct repository *r, const struct object_id *oid); int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size); @@ -29,12 +31,14 @@ struct tree *parse_tree_indirect(const struct object_id *oid); #define READ_TREE_RECURSIVE 1 typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, int, void *); -extern int read_tree_recursive(struct tree *tree, - const char *base, int baselen, - int stage, const struct pathspec *pathspec, - read_tree_fn_t fn, void *context); +int read_tree_recursive(struct repository *r, + struct tree *tree, + const char *base, int baselen, + int stage, const struct pathspec *pathspec, + read_tree_fn_t fn, void *context); -extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec, - struct index_state *istate); +int read_tree(struct repository *r, struct tree *tree, + int stage, struct pathspec *pathspec, + struct index_state *istate); #endif /* TREE_H */ diff --git a/unpack-trees.c b/unpack-trees.c index 6d53cbfc86..94265a7df0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -294,7 +294,7 @@ static void load_gitmodules_file(struct index_state *index, repo_read_gitmodules(the_repository); } else if (state && (ce->ce_flags & CE_UPDATE)) { submodule_free(the_repository); - checkout_entry(ce, state, NULL); + checkout_entry(ce, state, NULL, NULL); repo_read_gitmodules(the_repository); } } @@ -450,12 +450,12 @@ static int check_updates(struct unpack_trees_options *o) display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; if (o->update && !o->dry_run) { - errs |= checkout_entry(ce, &state, NULL); + errs |= checkout_entry(ce, &state, NULL, NULL); } } } stop_progress(&progress); - errs |= finish_delayed_checkout(&state); + errs |= finish_delayed_checkout(&state, NULL); if (o->update) git_attr_set_direction(GIT_ATTR_CHECKIN); @@ -794,6 +794,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { + struct unpack_trees_options *o = info->data; int i, ret, bottom; int nr_buf = 0; struct tree_desc t[MAX_UNPACK_TREES]; @@ -804,7 +805,6 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info); if (nr_entries > 0) { - struct unpack_trees_options *o = info->data; int pos = index_pos_by_traverse_info(names, info); if (!o->merge || df_conflicts) @@ -863,7 +863,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, } bottom = switch_cache_bottom(&newinfo); - ret = traverse_trees(n, t, &newinfo); + ret = traverse_trees(o->src_index, n, t, &newinfo); restore_cache_bottom(&newinfo, bottom); for (i = 0; i < nr_buf; i++) @@ -1550,7 +1550,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } trace_performance_enter(); - ret = traverse_trees(len, t, &info); + ret = traverse_trees(o->src_index, len, t, &info); trace_performance_leave("traverse_trees"); if (ret < 0) goto return_failed; @@ -104,7 +104,8 @@ void end_url_with_slash(struct strbuf *buf, const char *url) strbuf_complete(buf, '/'); } -void str_end_url_with_slash(const char *url, char **dest) { +void str_end_url_with_slash(const char *url, char **dest) +{ struct strbuf buf = STRBUF_INIT; end_url_with_slash(&buf, url); free(*dest); diff --git a/userdiff.c b/userdiff.c index 97007abe5b..3a78fbf504 100644 --- a/userdiff.c +++ b/userdiff.c @@ -265,7 +265,8 @@ int userdiff_config(const char *k, const char *v) return 0; } -struct userdiff_driver *userdiff_find_by_name(const char *name) { +struct userdiff_driver *userdiff_find_by_name(const char *name) +{ int len = strlen(name); return userdiff_find_by_namelen(name, len); } |
