diff options
259 files changed, 12639 insertions, 3143 deletions
diff --git a/.gitignore b/.gitignore index b1020b875f..833ef3b0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--helper /git-rebase--interactive /git-rebase--merge /git-receive-pack @@ -151,7 +151,8 @@ Matthias Kestenholz <matthias@spinlock.ch> <mk@spinlock.ch> Matthias Urlichs <matthias@urlichs.de> <smurf@kiste.(none)> Matthias Urlichs <matthias@urlichs.de> <smurf@smurf.noris.de> Michael Coleman <tutufan@gmail.com> -Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm> +Michael J Gruber <git@grubix.eu> <michaeljgruber+gmane@fastmail.fm> +Michael J Gruber <git@grubix.eu> <git@drmicha.warpmail.net> Michael S. Tsirkin <mst@kernel.org> <mst@redhat.com> Michael S. Tsirkin <mst@kernel.org> <mst@mellanox.co.il> Michael S. Tsirkin <mst@kernel.org> <mst@dev.mellanox.co.il> @@ -177,6 +178,7 @@ Paolo Bonzini <bonzini@gnu.org> <paolo.bonzini@lu.unisi.ch> Pascal Obry <pascal@obry.net> <pascal.obry@gmail.com> Pascal Obry <pascal@obry.net> <pascal.obry@wanadoo.fr> Pat Notz <patnotz@gmail.com> <pknotz@sandia.gov> +Patrick Steinhardt <ps@pks.im> <patrick.steinhardt@elego.de> Paul Mackerras <paulus@samba.org> <paulus@dorrigo.(none)> Paul Mackerras <paulus@samba.org> <paulus@pogo.(none)> Peter Baumann <waste.manager@gmx.de> <Peter.B.Baumann@stud.informatik.uni-erlangen.de> diff --git a/Documentation/RelNotes/2.12.2.txt b/Documentation/RelNotes/2.12.2.txt index 9efc348353..441939709c 100644 --- a/Documentation/RelNotes/2.12.2.txt +++ b/Documentation/RelNotes/2.12.2.txt @@ -58,4 +58,26 @@ Fixes since v2.12.1 ignore anything after '>' when picking addresses, to allow non-address cruft like " # stable 4.4" after the address. + * "git push" had a handful of codepaths that could lead to a deadlock + when unexpected error happened, which has been fixed. + + * Code to read submodule.<name>.ignore config did not state the + variable name correctly when giving an error message diagnosing + misconfiguration. + + * "git ls-remote" and "git archive --remote" are designed to work + without being in a directory under Git's control. However, recent + updates revealed that we randomly look into a directory called + .git/ without actually doing necessary set-up when working in a + repository. Stop doing so. + + * The code to parse the command line "git grep <patterns>... <rev> + [[--] <pathspec>...]" has been cleaned up, and a handful of bugs + have been fixed (e.g. we used to check "--" if it is a rev). + + * The code to parse "git -c VAR=VAL cmd" and set configuration + variable for the duration of cmd had two small bugs, which have + been fixed. + This supersedes jc/config-case-cmdline topic that has been discarded. + Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.13.0.txt b/Documentation/RelNotes/2.13.0.txt new file mode 100644 index 0000000000..adf6e44a49 --- /dev/null +++ b/Documentation/RelNotes/2.13.0.txt @@ -0,0 +1,390 @@ +Git 2.13 Release Notes +====================== + +Backward compatibility notes. + + * Use of an empty string as a pathspec element that is used for + 'everything matches' is still warned and Git asks users to use a + more explicit '.' for that instead. The hope is that existing + users will not mind this change, and eventually the warning can be + turned into a hard error, upgrading the deprecation into removal of + this (mis)feature. That is not scheduled to happen in the upcoming + release (yet). + + * The historical argument order "git merge <msg> HEAD <commit>..." + has been deprecated for quite some time, and will be removed in a + future release. + + * The default location "~/.git-credential-cache/socket" for the + socket used to communicate with the credential-cache daemon has + been moved to "~/.cache/git/credential/socket". + + +Updates since v2.12 +------------------- + +UI, Workflows & Features + + * "git describe" and "git name-rev" have been taught to take more + than one refname patterns to restrict the set of refs to base their + naming output on, and also learned to take negative patterns to + name refs not to be used for naming via their "--exclude" option. + + * Deletion of a branch "foo/bar" could remove .git/refs/heads/foo + once there no longer is any other branch whose name begins with + "foo/", but we didn't do so so far. Now we do. + + * When "git merge" detects a path that is renamed in one history + while the other history deleted (or modified) it, it now reports + both paths to help the user understand what is going on in the two + histories being merged. + + * The <url> part in "http.<url>.<variable>" configuration variable + can now be spelled with '*' that serves as wildcard. + E.g. "http.https://*.example.com.proxy" can be used to specify the + proxy used for https://a.example.com, https://b.example.com, etc., + i.e. any host in the example.com domain. + + * "git tag" did not leave useful message when adding a new entry to + reflog; this was left unnoticed for a long time because refs/tags/* + doesn't keep reflog by default. + + * The "negative" pathspec feature was somewhat more cumbersome to use + than necessary in that its short-hand used "!" which needed to be + escaped from shells, and it required "exclude from what?" specified. + + * The command line options for ssh invocation needs to be tweaked for + some implementations of SSH (e.g. PuTTY plink wants "-P <port>" + while OpenSSH wants "-p <port>" to specify port to connect to), and + the variant was guessed when GIT_SSH environment variable is used + to specify it. The logic to guess now applies to the command + specified by the newer GIT_SSH_COMMAND and also core.sshcommand + configuration variable, and comes with an escape hatch for users to + deal with misdetected cases. + + * The "--git-path", "--git-common-dir", and "--shared-index-path" + options of "git rev-parse" did not produce usable output. They are + now updated to show the path to the correct file, relative to where + the caller is. + + * "git diff -W" has been taught to handle the case where a new + function is added at the end of the file better. + + * "git update-ref -d" and other operations to delete references did + not leave any entry in HEAD's reflog when the reference being + deleted was the current branch. This is not a problem in practice + because you do not want to delete the branch you are currently on, + but caused renaming of the current branch to something else not to + be logged in a useful way. + + * "Cc:" on the trailer part does not have to conform to RFC strictly, + unlike in the e-mail header. "git send-email" has been updated to + ignore anything after '>' when picking addresses, to allow non-address + cruft like " # stable 4.4" after the address. + + * When "git submodule init" decides that the submodule in the working + tree is its upstream, it now gives a warning as it is not a very + common setup. + (merge d1b3b81aab sb/submodule-init-url-selection later to maint). + + * "git stash save" takes a pathspec so that the local changes can be + stashed away only partially. + (merge 9e140909f6 tg/stash-push later to maint). + + * Documentation for "git ls-files" did not refer to core.quotePath. + + * The experimental "split index" feature has gained a few + configuration variables to make it easier to use. + + * From a working tree of a repository, a new option of "rev-parse" + lets you ask if the repository is used as a submodule of another + project, and where the root level of the working tree of that + project (i.e. your superproject) is. + + * The pathspec mechanism learned to further limit the paths that + match the pattern to those that have specified attributes attached + via the gitattributes mechanism. + + * Our source code has used the SHA1_HEADER cpp macro after "#include" + in the C code to switch among the SHA-1 implementations. Instead, + list the exact header file names and switch among implementations + using "#ifdef BLK_SHA1/#include "block-sha1/sha1.h"/.../#endif"; + this helps some IDE tools. + + * The start-up sequence of "git" needs to figure out some configured + settings before it finds and set itself up in the location of the + repository and was quite messy due to its "chicken-and-egg" nature. + The code has been restructured. + + * The command line prompt (in contrib/) learned a new 'tag' style + that can be specified with GIT_PS1_DESCRIBE_STYLE, to describe a + detached HEAD with "git describe --tags". + + * The configuration file learned a new "includeIf.<condition>.path" + that includes the contents of the given path only when the + condition holds. This allows you to say "include this work-related + bit only in the repositories under my ~/work/ directory". + + * Recent update to "rebase -i" started showing a message that is not + a warning with "warning:" prefix by mistake. This has been fixed. + + * Recently we started passing the "--push-options" through the + external remote helper interface; now the "smart HTTP" remote + helper understands what to do with the passed information. + + * "git describe --dirty" dies when it cannot be determined if the + state in the working tree matches that of HEAD (e.g. broken + repository or broken submodule). The command learned a new option + "git describe --broken" to give "$name-broken" (where $name is the + description of HEAD) in such a case. + + +Performance, Internal Implementation, Development Support etc. + + * The code to list branches in "git branch" has been consolidated + with the more generic ref-filter API. + + * Resource usage while enumerating refs from alternate object store + has been optimized to help receiving end of "push" that hosts a + repository with many "forks". + + * The gitattributes machinery is being taught to work better in a + multi-threaded environment. + + * "git rebase -i" starts using the recently updated "sequencer" code. + + * Code and design clean-up for the refs API. + + * The preload-index code has been taught not to bother with the index + entries that are paths that are not checked out by "sparse checkout". + + * Some warning() messages from "git clean" were updated to show the + errno from failed system calls. + + * The "parse_config_key()" API function has been cleaned up. + (merge ad8c7cdadd jk/parse-config-key-cleanup later to maint). + + * A test that creates a confusing branch whose name is HEAD has been + corrected not to do so. + + * The code that parses header fields in the commit object has been + updated for (micro)performance and code hygiene. + + * An helper function to make it easier to append the result from + real_path() to a strbuf has been added. + (merge 33ad9ddd0b rs/strbuf-add-real-path later to maint). + + * Reduce authentication round-trip over HTTP when the server supports + just a single authentication method. This also improves the + behaviour when Git is misconfigured to enable http.emptyAuth + against a server that does not authenticate without a username + (i.e. not using Kerberos etc., which makes http.emptyAuth + pointless). + + * Windows port wants to use OpenSSL's implementation of SHA-1 + routines, so let them. + + * The t/perf performance test suite was not prepared to test not so + old versions of Git, but now it covers versions of Git that are not + so ancient. + (merge 28e1fb5466 jt/perf-updates later to maint). + + * Add 32-bit Linux variant to the set of platforms to be tested with + Travis CI. + + * "git branch --list" takes the "--abbrev" and "--no-abbrev" options + to control the output of the object name in its "-v"(erbose) + output, but a recent update started ignoring them; fix it before + the breakage reaches to any released version. + + * Picking two versions of Git and running tests to make sure the + older one and the newer one interoperate happily has now become + possible. + (merge bd4d9d993c jk/interop-test later to maint). + + * "uchar [40]" to "struct object_id" conversion continues. + + * "git tag --contains" used to (ab)use the object bits to keep track + of the state of object reachability without clearing them after + use; this has been cleaned up and made to use the newer commit-slab + facility. + + * The "debug" helper used in the test framework learned to run + a command under "gdb" interactively. + (merge 59210dd56c sg/test-with-stdin later to maint). + + * The "detect attempt to create collisions" variant of SHA-1 + implementation by Marc Stevens (CWI) and Dan Shumow (Microsoft) + has been integrated and made the default. + + * The test framework learned to detect unterminated here documents. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.12 +----------------- + +Unless otherwise noted, all the fixes since v2.12 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * "git repack --depth=<n>" for a long time busted the specified depth + when reusing delta from existing packs. This has been corrected. + (merge 42b766d765 jk/delta-chain-limit later to maint). + + * The code to parse the command line "git grep <patterns>... <rev> + [[--] <pathspec>...]" has been cleaned up, and a handful of bugs + have been fixed (e.g. we used to check "--" if it is a rev). + + * "git ls-remote" and "git archive --remote" are designed to work + without being in a directory under Git's control. However, recent + updates revealed that we randomly look into a directory called + .git/ without actually doing necessary set-up when working in a + repository. Stop doing so. + + * "git show-branch" expected there were only very short branch names + in the repository and used a fixed-length buffer to hold them + without checking for overflow. + + * A caller of tempfile API that uses stdio interface to write to + files may ignore errors while writing, which is detected when + tempfile is closed (with a call to ferror()). By that time, the + original errno that may have told us what went wrong is likely to + be long gone and was overwritten by an irrelevant value. + close_tempfile() now resets errno to EIO to make errno at least + predictable. + + * "git remote rm X", when a branch has remote X configured as the + value of its branch.*.remote, tried to remove branch.*.remote and + branch.*.merge and failed if either is unset. + + * A "gc.log" file left by a backgrounded "gc --auto" disables further + automatic gc; it has been taught to run at least once a day (by + default) by ignoring a stale "gc.log" file that is too old. + + * The code to parse "git -c VAR=VAL cmd" and set configuration + variable for the duration of cmd had two small bugs, which have + been fixed. + + * user.email that consists of only cruft chars should consistently + error out, but didn't. + (merge 94425552f3 jk/ident-empty later to maint). + + * "git upload-pack", which is a counter-part of "git fetch", did not + report a request for a ref that was not advertised as invalid. + This is generally not a problem (because "git fetch" will stop + before making such a request), but is the right thing to do. + + * A leak in a codepath to read from a packed object in (rare) cases + has been plugged. + + * When a redirected http transport gets an error during the + redirected request, we ignored the error we got from the server, + and ended up giving a not-so-useful error message. + + * The patch subcommand of "git add -i" was meant to have paths + selection prompt just like other subcommand, unlike "git add -p" + directly jumps to hunk selection. Recently, this was broken and + "add -i" lost the paths selection dialog, but it now has been + fixed. + + * Git v2.12 was shipped with an embarrassing breakage where various + operations that verify paths given from the user stopped dying when + seeing an issue, and instead later triggering segfault. + + * There is no need for Python only to give a few messages to the + standard error stream, but we somehow did. + + * The code to parse "git log -L..." command line was buggy when there + are many ranges specified with -L; overrun of the allocated buffer + has been fixed. + + * The command-line parsing of "git log -L" copied internal data + structures using incorrect size on ILP32 systems. + + * "git diff --quiet" relies on the size field in diff_filespec to be + correctly populated, but diff_populate_filespec() helper function + made an incorrect short-cut when asked only to populate the size + field for paths that need to go through convert_to_git() (e.g. CRLF + conversion). + + * A few tests were run conditionally under (rare) conditions where + they cannot be run (like running cvs tests under 'root' account). + (merge c6507484a2 ab/cond-skip-tests later to maint). + + * "git branch @" created refs/heads/@ as a branch, and in general the + code that handled @{-1} and @{upstream} was a bit too loose in + disambiguating. + (merge fd4692ff70 jk/interpret-branch-name later to maint). + + * "git fetch" that requests a commit by object name, when the other + side does not allow such an request, failed without much + explanation. + (merge d56583ded6 mm/fetch-show-error-message-on-unadvertised-object later to maint). + + * "git filter-branch --prune-empty" drops a single-parent commit that + becomes a no-op, but did not drop a root commit whose tree is empty. + (merge 32da7467eb dp/filter-branch-prune-empty later to maint). + + * Recent versions of Git treats http alternates (used in dumb http + transport) just like HTTP redirects and requires the client to + enable following it, due to security concerns. But we forgot to + give a warning when we decide not to honor the alternates. + (merge 5cae73d5d2 ew/http-alternates-as-redirects-warning later to maint). + + * "git push" had a handful of codepaths that could lead to a deadlock + when unexpected error happened, which has been fixed. + + * "Dumb http" transport used to misparse a nonsense http-alternates + response, which has been fixed. + + * "git add -p <pathspec>" unnecessarily expanded the pathspec to a + list of individual files that matches the pathspec by running "git + ls-files <pathspec>", before feeding it to "git diff-index" to see + which paths have changes, because historically the pathspec + language supported by "diff-index" was weaker. These days they are + equivalent and there is no reason to internally expand it. This + helps both performance and avoids command line argument limit on + some platforms. + (merge 7288e12cce jk/add-i-use-pathspecs later to maint). + + * "git status --porcelain" is supposed to give a stable output, but a + few strings were left as translatable by mistake. + + * "git revert -m 0 $merge_commit" complained that reverting a merge + needs to say relative to which parent the reversion needs to + happen, as if "-m 0" weren't given. The correct diagnosis is that + "-m 0" does not refer to the first parent ("-m 1" does). This has + been fixed. + + * Code to read submodule.<name>.ignore config did not state the + variable name correctly when giving an error message diagnosing + misconfiguration. + + * Fix for NO_PTHREADS build. + (merge 7b91929ba0 jk/execv-dashed-external later to maint). + + * Fix for potential segv introduced in v2.11.0 and later (also + v2.10.2) to "git log --pickaxe-regex -S". + (merge f53c5de29c js/regexec-buf later to maint). + + * A few unterminated here documents in tests were fixed, which in + turn revealed incorrect expectations the tests make. These tests + have been updated. + (merge b42ca35e5c st/verify-tag later to maint). + + * Other minor doc, test and build updates and code cleanups. + (merge dfa3ad3238 rs/blame-code-cleanup later to maint). + (merge ffddfc6328 jk/rev-parse-cleanup later to maint). + (merge f20754802a jk/pack-name-cleanups later to maint). + (merge d4aae459cd sb/wt-status-cleanup later to maint). + (merge e94eac49e6 rs/http-push-cleanup later to maint). + (merge ba6746c08f rs/path-name-safety-cleanup later to maint). + (merge d41626ff9e rs/shortlog-cleanup later to maint). + (merge dce96c41f9 rs/update-hook-optim later to maint). + (merge 37e61153e2 jk/quote-env-path-list-component later to maint). + (merge a4dded0189 sb/submodule-update-initial-runs-custom-script later to maint). + (merge 70471ed9bb sb/t3600-rephrase later to maint). + (merge e7e183d6ee km/config-grammofix later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 038a32d01b..1df1965457 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -79,18 +79,69 @@ escape sequences) are invalid. Includes ~~~~~~~~ -You can include one config file from another by setting the special +You can include a config file from another by setting the special `include.path` variable to the name of the file to be included. The variable takes a pathname as its value, and is subject to tilde -expansion. +expansion. `include.path` can be given multiple times. -The -included file is expanded immediately, as if its contents had been +The included file is expanded immediately, as if its contents had been found at the location of the include directive. If the value of the -`include.path` variable is a relative path, the path is considered to be -relative to the configuration file in which the include directive was -found. See below for examples. +`include.path` variable is a relative path, the path is considered to +be relative to the configuration file in which the include directive +was found. See below for examples. +Conditional includes +~~~~~~~~~~~~~~~~~~~~ + +You can include a config file from another conditionally by setting a +`includeIf.<condition>.path` variable to the name of the file to be +included. The variable's value is treated the same way as +`include.path`. `includeIf.<condition>.path` can be given multiple times. + +The condition starts with a keyword followed by a colon and some data +whose format and meaning depends on the keyword. Supported keywords +are: + +`gitdir`:: + + The data that follows the keyword `gitdir:` is used as a glob + pattern. If the location of the .git directory matches the + pattern, the include condition is met. ++ +The .git location may be auto-discovered, or come from `$GIT_DIR` +environment variable. If the repository is auto discovered via a .git +file (e.g. from submodules, or a linked worktree), the .git location +would be the final location where the .git directory is, not where the +.git file is. ++ +The pattern can contain standard globbing wildcards and two additional +ones, `**/` and `/**`, that can match multiple path components. Please +refer to linkgit:gitignore[5] for details. For convenience: + + * If the pattern starts with `~/`, `~` will be substituted with the + content of the environment variable `HOME`. + + * If the pattern starts with `./`, it is replaced with the directory + containing the current config file. + + * If the pattern does not start with either `~/`, `./` or `/`, `**/` + will be automatically prepended. For example, the pattern `foo/bar` + becomes `**/foo/bar` and would match `/any/path/to/foo/bar`. + + * If the pattern ends with `/`, `**` will be automatically added. For + example, the pattern `foo/` becomes `foo/**`. In other words, it + matches "foo" and everything inside, recursively. + +`gitdir/i`:: + This is the same as `gitdir` except that matching is done + case-insensitively (e.g. on case-insensitive file sytems) + +A few more notes on matching via `gitdir` and `gitdir/i`: + + * Symlinks in `$GIT_DIR` are not resolved before matching. + + * Note that "../" is not special and will match literally, which is + unlikely what you want. Example ~~~~~~~ @@ -119,6 +170,17 @@ Example path = foo ; expand "foo" relative to the current file path = ~/foo ; expand "foo" in your `$HOME` directory + ; include if $GIT_DIR is /path/to/foo/.git + [includeIf "gitdir:/path/to/foo/.git"] + path = /path/to/foo.inc + + ; include for all repositories inside /path/to/group + [includeIf "gitdir:/path/to/group/"] + path = /path/to/foo.inc + + ; include for all repositories inside $HOME/to/group + [includeIf "gitdir:~/to/group/"] + path = /path/to/foo.inc Values ~~~~~~ @@ -334,6 +396,10 @@ core.trustctime:: crawlers and some backup systems). See linkgit:git-update-index[1]. True by default. +core.splitIndex:: + If true, the split-index feature of the index will be used. + See linkgit:git-update-index[1]. False by default. + core.untrackedCache:: Determines what to do about the untracked cache feature of the index. It will be kept, if this variable is unset or set to @@ -350,16 +416,19 @@ core.checkStat:: all fields, including the sub-second part of mtime and ctime. core.quotePath:: - The commands that output paths (e.g. 'ls-files', - 'diff'), when not given the `-z` option, will quote - "unusual" characters in the pathname by enclosing the - pathname in a double-quote pair and with backslashes the - same way strings in C source code are quoted. If this - variable is set to false, the bytes higher than 0x80 are - not quoted but output as verbatim. Note that double - quote, backslash and control characters are always - quoted without `-z` regardless of the setting of this - variable. + Commands that output paths (e.g. 'ls-files', 'diff'), will + quote "unusual" characters in the pathname by enclosing the + pathname in double-quotes and escaping those characters with + backslashes in the same way C escapes control characters (e.g. + `\t` for TAB, `\n` for LF, `\\` for backslash) or bytes with + values larger than 0x80 (e.g. octal `\302\265` for "micro" in + UTF-8). If this variable is set to false, bytes higher than + 0x80 are not considered "unusual" any more. Double-quotes, + backslash and control characters are always escaped regardless + of the setting of this variable. A simple space character is + not considered "unusual". Many commands can output pathnames + completely verbatim using the `-z` option. The default value + is true. core.eol:: Sets the line ending type to use in the working directory for @@ -668,13 +737,13 @@ alternative to having an `init.templateDir` where you've changed default hooks. core.editor:: - Commands such as `commit` and `tag` that lets you edit - messages by launching an editor uses the value of this + Commands such as `commit` and `tag` that let you edit + messages by launching an editor use the value of this variable when it is set, and the environment variable `GIT_EDITOR` is not set. See linkgit:git-var[1]. core.commentChar:: - Commands such as `commit` and `tag` that lets you edit + Commands such as `commit` and `tag` that let you edit messages consider a line that begins with this character commented, and removes them after the editor returns (default '#'). @@ -1925,7 +1994,10 @@ http.<url>.*:: must match exactly between the config key and the URL. . Host/domain name (e.g., `example.com` in `https://example.com/`). - This field must match exactly between the config key and the URL. + This field must match between the config key and the URL. It is + possible to specify a `*` as part of the host name to match all subdomains + at this level. `https://*.example.com/` for example would match + `https://foo.example.com/`, but not `https://foo.bar.example.com/`. . Port number (e.g., `8080` in `http://example.com:8080/`). This field must match exactly between the config key and the URL. @@ -1960,6 +2032,17 @@ Environment variable settings always override any matches. The URLs that are matched against are those given directly to Git commands. This means any URLs visited as a result of a redirection do not participate in matching. +ssh.variant:: + Depending on the value of the environment variables `GIT_SSH` or + `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git + auto-detects whether to adjust its command-line parameters for use + with plink or tortoiseplink, as opposed to the default (OpenSSH). ++ +The config variable `ssh.variant` can be set to override this auto-detection; +valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value +will be treated as normal ssh. This setting can be overridden via the +environment variable `GIT_SSH_VARIANT`. + i18n.commitEncoding:: Character encoding the commit messages are stored in; Git itself does not care per se, but this information is necessary e.g. when @@ -2438,6 +2521,8 @@ push.default:: pushing to the same repository you would normally pull from (i.e. central workflow). +* `tracking` - This is a deprecated synonym for `upstream`. + * `simple` - in centralized workflow, work like `upstream` with an added safety to refuse to push if the upstream branch's name is different from the local one. @@ -2833,6 +2918,31 @@ showbranch.default:: The default set of branches for linkgit:git-show-branch[1]. See linkgit:git-show-branch[1]. +splitIndex.maxPercentChange:: + When the split index feature is used, this specifies the + percent of entries the split index can contain compared to the + total number of entries in both the split index and the shared + index before a new shared index is written. + The value should be between 0 and 100. If the value is 0 then + a new shared index is always written, if it is 100 a new + shared index is never written. + By default the value is 20, so a new shared index is written + if the number of entries in the split index would be greater + than 20 percent of the total number of entries. + See linkgit:git-update-index[1]. + +splitIndex.sharedIndexExpire:: + When the split index feature is used, shared index files that + were not modified since the time this variable specifies will + be removed when a new shared index file is created. The value + "now" expires all entries immediately, and "never" suppresses + expiration altogether. + The default value is "2.weeks.ago". + Note that a shared index file is considered modified (for the + purpose of expiration) each time a new split-index file is + either created based on it or read from it. + See linkgit:git-update-index[1]. + status.relativePaths:: By default, linkgit:git-status[1] shows paths relative to the current directory. Setting this variable to `false` shows paths diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index cf5262622f..706916c94c 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -78,9 +78,10 @@ Example: :100644 100644 5be4a4...... 000000...... M file.c ------------------------------------------------ -When `-z` option is not used, TAB, LF, and backslash characters -in pathnames are represented as `\t`, `\n`, and `\\`, -respectively. +Without the `-z` option, pathnames with "unusual" characters are +quoted as explained for the configuration variable `core.quotePath` +(see linkgit:git-config[1]). Using `-z` the filename is output +verbatim and the line is terminated by a NUL byte. diff format for merges ---------------------- diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index d2a7ff56e8..231105cff4 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -53,10 +53,9 @@ The index line includes the SHA-1 checksum before and after the change. The <mode> is included if the file mode does not change; otherwise, separate lines indicate the old and the new mode. -3. TAB, LF, double quote and backslash characters in pathnames - are represented as `\t`, `\n`, `\"` and `\\`, respectively. - If there is need for such substitution then the whole - pathname is put in double quotes. +3. Pathnames with "unusual" characters are quoted as explained for + the configuration variable `core.quotePath` (see + linkgit:git-config[1]). 4. All the `file1` files in the output refer to files before the commit, and all the `file2` files refer to files after the commit. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d91ddbd5fe..89cc0f48de 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -192,10 +192,9 @@ ifndef::git-log[] given, do not munge pathnames and use NULs as output field terminators. endif::git-log[] + -Without this option, each pathname output will have TAB, LF, double quotes, -and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`, -respectively, and the pathname will be enclosed in double quotes if -any of those replacements occurred. +Without this option, pathnames with "unusual" characters are quoted as +explained for the configuration variable `core.quotePath` (see +linkgit:git-config[1]). --name-only:: Show only names of changed files. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 8ddb207409..631cbd840a 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -108,10 +108,9 @@ the information is read from the current index instead. When `--numstat` has been given, do not munge pathnames, but use a NUL-terminated machine-readable format. + -Without this option, each pathname output will have TAB, LF, double quotes, -and backslash characters replaced with `\t`, `\n`, `\"`, and `\\`, -respectively, and the pathname will be enclosed in double quotes if -any of those replacements occurred. +Without this option, pathnames with "unusual" characters are quoted as +explained for the configuration variable `core.quotePath` (see +linkgit:git-config[1]). -p<n>:: Remove <n> leading slashes from traditional diff paths. The diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 514b0d0b91..c203e8c073 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -12,7 +12,7 @@ SYNOPSIS [--list] [-v [--abbrev=<length> | --no-abbrev]] [--column[=<options>] | --no-column] [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>] - [--points-at <object>] [<pattern>...] + [--points-at <object>] [--format=<format>] [<pattern>...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] 'git branch' --unset-upstream [<branchname>] @@ -258,6 +258,11 @@ start-point is either a local or remote-tracking branch. --points-at <object>:: Only list branches of the given object. +--format <format>:: + A string that interpolates `%(fieldname)` from the object + pointed at by a ref being shown. The format is the same as + that of linkgit:git-for-each-ref[1]. + Examples -------- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 8e2c0662dd..d6399c0af8 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -256,6 +256,13 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. out anyway. In other words, the ref can be held by more than one worktree. +--[no-]recurse-submodules:: + Using --recurse-submodules will update the content of all initialized + submodules according to the commit recorded in the superproject. If + local modifications in a submodule would be overwritten the checkout + will fail unless `-f` is used. If nothing (or --no-recurse-submodules) + is used, the work trees of submodules will not be updated. + <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 4f8f20a360..ed0f5b94b3 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -117,9 +117,12 @@ OPTIONS -z:: --null:: - When showing `short` or `porcelain` status output, terminate - entries in the status output with NUL, instead of LF. If no - format is given, implies the `--porcelain` output format. + When showing `short` or `porcelain` status output, print the + filename verbatim and terminate the entries with NUL, instead of LF. + If no format is given, implies the `--porcelain` output format. + Without the `-z` option, filenames with "unusual" characters are + quoted as explained for the configuration variable `core.quotePath` + (see linkgit:git-config[1]). -F <file>:: --file=<file>:: @@ -460,7 +463,7 @@ order). See linkgit:git-var[1] for details. HOOKS ----- This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`, -and `post-commit` hooks. See linkgit:githooks[5] for more +`post-commit` and `post-rewrite` hooks. See linkgit:githooks[5] for more information. FILES diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt index 96208f822e..2b85826393 100644 --- a/Documentation/git-credential-cache.txt +++ b/Documentation/git-credential-cache.txt @@ -33,10 +33,13 @@ OPTIONS --socket <path>:: Use `<path>` to contact a running cache daemon (or start a new - cache daemon if one is not started). Defaults to - `~/.git-credential-cache/socket`. If your home directory is on a - network-mounted filesystem, you may need to change this to a - local filesystem. You must specify an absolute path. + cache daemon if one is not started). + Defaults to `$XDG_CACHE_HOME/git/credential/socket` unless + `~/.git-credential-cache/` exists in which case + `~/.git-credential-cache/socket` is used instead. + If your home directory is on a network-mounted filesystem, you + may need to change this to a local filesystem. You must specify + an absolute path. CONTROLLING THE DAEMON ---------------------- diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index e4ac448ff5..26f19d3b07 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -30,9 +30,14 @@ OPTIONS Commit-ish object names to describe. Defaults to HEAD if omitted. --dirty[=<mark>]:: - Describe the working tree. - It means describe HEAD and appends <mark> (`-dirty` by - default) if the working tree is dirty. +--broken[=<mark>]:: + Describe the state of the working tree. When the working + tree matches HEAD, the output is the same as "git describe + HEAD". If the working tree has local modification "-dirty" + is appended to it. If a repository is corrupt and Git + cannot determine if there is local modification, Git will + error out, unless `--broken' is given, which appends + the suffix "-broken" instead. --all:: Instead of using only the annotated tags, use any ref @@ -83,7 +88,20 @@ OPTIONS --match <pattern>:: Only consider tags matching the given `glob(7)` pattern, excluding the "refs/tags/" prefix. This can be used to avoid - leaking private tags from the repository. + leaking private tags from the repository. If given multiple times, a + list of patterns will be accumulated, and tags matching any of the + patterns will be considered. Use `--no-match` to clear and reset the + list of patterns. + +--exclude <pattern>:: + Do not consider tags matching the given `glob(7)` pattern, excluding + the "refs/tags/" prefix. This can be used to narrow the tag space and + find only tags matching some meaningful criteria. If given multiple + times, a list of patterns will be accumulated and tags matching any + of the patterns will be excluded. When combined with --match a tag will + be considered when it matches at least one --match pattern and does not + match any of the --exclude patterns. Use `--no-exclude` to clear and + reset the list of patterns. --always:: Show uniquely abbreviated commit object as fallback. diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 0a09698c03..6e4bb02204 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -167,14 +167,12 @@ to other tags will be rewritten to point to the underlying commit. project root. Implies <<Remap_to_ancestor>>. --prune-empty:: - Some kind of filters will generate empty commits, that left the tree - untouched. This switch allow git-filter-branch to ignore such - commits. Though, this switch only applies for commits that have one - and only one parent, it will hence keep merges points. Also, this - option is not compatible with the use of `--commit-filter`. Though you - just need to use the function 'git_commit_non_empty_tree "$@"' instead - of the `git commit-tree "$@"` idiom in your commit filter to make that - happen. + Some filters will generate empty commits that leave the tree untouched. + This option instructs git-filter-branch to remove such commits if they + have exactly one or zero non-pruned parents; merge commits will + therefore remain intact. This option cannot be used together with + `--commit-filter`, though the same effect can be achieved by using the + provided `git_commit_non_empty_tree` function in a commit filter. --original <namespace>:: Use this option to set the namespace where the original commits diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index abe13f3bed..111e1be6f5 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -95,11 +95,20 @@ refname:: The name of the ref (the part after $GIT_DIR/). For a non-ambiguous short name of the ref append `:short`. The option core.warnAmbiguousRefs is used to select the strict - abbreviation mode. If `strip=<N>` is appended, strips `<N>` - slash-separated path components from the front of the refname - (e.g., `%(refname:strip=2)` turns `refs/tags/foo` into `foo`. - `<N>` must be a positive integer. If a displayed ref has fewer - components than `<N>`, the command aborts with an error. + abbreviation mode. If `lstrip=<N>` (`rstrip=<N>`) is appended, strips `<N>` + slash-separated path components from the front (back) of the refname + (e.g. `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo` and + `%(refname:rstrip=2)` turns `refs/tags/foo` into `refs`). + If `<N>` is a negative number, strip as many path components as + necessary from the specified end to leave `-<N>` path components + (e.g. `%(refname:lstrip=-2)` turns + `refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)` + turns `refs/tags/foo` into `refs`). When the ref does not have + enough components, the result becomes an empty string if + stripping with positive <N>, or it becomes the full refname if + stripping with negative <N>. Neither is an error. ++ +`strip` can be used as a synomym to `lstrip`. objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). @@ -110,21 +119,31 @@ objectsize:: 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. upstream:: The name of a local ref which can be considered ``upstream'' - from the displayed ref. Respects `:short` in the same way as - `refname` above. Additionally respects `:track` to show - "[ahead N, behind M]" and `:trackshort` to show the terse - version: ">" (ahead), "<" (behind), "<>" (ahead and behind), - or "=" (in sync). Has no effect if the ref does not have - tracking information associated with it. + from the displayed ref. Respects `:short`, `:lstrip` and + `:rstrip` in the same way as `refname` above. Additionally + respects `:track` to show "[ahead N, behind M]" and + `:trackshort` to show the terse version: ">" (ahead), "<" + (behind), "<>" (ahead and behind), or "=" (in sync). `:track` + also prints "[gone]" whenever unknown upstream ref is + encountered. Append `:track,nobracket` to show tracking + information without brackets (i.e "ahead N, behind M"). Has + no effect if the ref does not have tracking information + associated with it. All the options apart from `nobracket` + are mutually exclusive, but if used together the last option + is selected. push:: - The name of a local ref which represents the `@{push}` location - for the displayed ref. Respects `:short`, `:track`, and - `:trackshort` options as `upstream` does. Produces an empty - string if no `@{push}` ref is configured. + The name of a local ref which represents the `@{push}` + location for the displayed ref. Respects `:short`, `:lstrip`, + `:rstrip`, `:track`, and `:trackshort` options as `upstream` + does. Produces an empty string if no `@{push}` ref is + configured. HEAD:: '*' if HEAD matches current ref (the checked out branch), ' ' @@ -149,6 +168,25 @@ align:: quoted, but if nested then only the topmost level performs quoting. +if:: + Used as %(if)...%(then)...%(end) or + %(if)...%(then)...%(else)...%(end). If there is an atom with + value or string literal after the %(if) then everything after + the %(then) is printed, else if the %(else) atom is used, then + everything after %(else) is printed. We ignore space when + evaluating the string before %(then), this is useful when we + use the %(HEAD) atom which prints either "*" or " " and we + want to apply the 'if' condition only on the 'HEAD' ref. + Append ":equals=<string>" or ":notequals=<string>" to compare + the value between the %(if:...) and %(then) atoms with the + given string. + +symref:: + The ref which the given symbolic ref refers to. If not a + symbolic ref, nothing is printed. Respects the `:short`, + `:lstrip` and `:rstrip` options in the same way as `refname` + above. + In addition to the above, for commit and tag objects, the header field names (`tree`, `parent`, `object`, `type`, and `tag`) can be used to specify the value in the header field. @@ -186,6 +224,14 @@ As a special case for the date-type fields, you may specify a format for the date by adding `:` followed by date format name (see the values the `--date` option to linkgit:git-rev-list[1] takes). +Some atoms like %(align) and %(if) always require a matching %(end). +We call them "opening atoms" and sometimes denote them as %($open). + +When a scripting language specific quoting is in effect, everything +between a top-level opening atom and its matching %(end) is evaluated +according to the semantics of the opening atom and only its result +from the top-level is quoted. + EXAMPLES -------- @@ -273,6 +319,22 @@ eval=`git for-each-ref --shell --format="$fmt" \ eval "$eval" ------------ + +An example to show the usage of %(if)...%(then)...%(else)...%(end). +This prefixes the current branch with a star. + +------------ +git for-each-ref --format="%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short)" refs/heads/ +------------ + + +An example to show the usage of %(if)...%(then)...%(end). +This prints the authorname, if present. + +------------ +git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)" +------------ + SEE ALSO -------- linkgit:git-show-ref[1] diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 9b200b379b..f7a069bb92 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -239,7 +239,7 @@ keeping them as Git notes allows them to be maintained between versions of the patch series (but see the discussion of the `notes.rewrite` configuration options in linkgit:git-notes[1] to use this workflow). ---[no]-signature=<signature>:: +--[no-]signature=<signature>:: Add a signature to each message produced. Per RFC 3676 the signature is separated from the body by a line with '-- ' on it. If the signature option is omitted the signature defaults to the Git version diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 446209e206..1cab703f73 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -77,7 +77,8 @@ OPTIONS succeed. -z:: - \0 line termination on output. + \0 line termination on output and do not quote filenames. + See OUTPUT below for more information. -x <pattern>:: --exclude=<pattern>:: @@ -196,9 +197,10 @@ the index records up to three such pairs; one from tree O in stage the user (or the porcelain) to see what should eventually be recorded at the path. (see linkgit:git-read-tree[1] for more information on state) -When `-z` option is not used, TAB, LF, and backslash characters -in pathnames are represented as `\t`, `\n`, and `\\`, -respectively. +Without the `-z` option, pathnames with "unusual" characters are +quoted as explained for the configuration variable `core.quotePath` +(see linkgit:git-config[1]). Using `-z` the filename is output +verbatim and the line is terminated by a NUL byte. Exclude Patterns diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index dbc91f98ff..9dee7bef35 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -53,7 +53,8 @@ OPTIONS Show object size of blob (file) entries. -z:: - \0 line termination on output. + \0 line termination on output and do not quote filenames. + See OUTPUT FORMAT below for more information. --name-only:: --name-status:: @@ -82,8 +83,6 @@ Output Format ------------- <mode> SP <type> SP <object> TAB <file> -Unless the `-z` option is used, TAB, LF, and backslash characters -in pathnames are represented as `\t`, `\n`, and `\\`, respectively. This output format is compatible with what `--index-info --stdin` of 'git update-index' expects. @@ -95,6 +94,11 @@ Object size identified by <object> is given in bytes, and right-justified with minimum width of 7 characters. Object size is given only for blobs (file) entries; for other entries `-` character is used in place of size. +Without the `-z` option, pathnames with "unusual" characters are +quoted as explained for the configuration variable `core.quotePath` +(see linkgit:git-config[1]). Using `-z` the filename is output +verbatim and the line is terminated by a NUL byte. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index ca28fb8e2a..e8e68f528c 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -26,7 +26,18 @@ OPTIONS --refs=<pattern>:: Only use refs whose names match a given shell pattern. The pattern - can be one of branch name, tag name or fully qualified ref name. + can be one of branch name, tag name or fully qualified ref name. If + given multiple times, use refs whose names match any of the given shell + patterns. Use `--no-refs` to clear any previous ref patterns given. + +--exclude=<pattern>:: + Do not use any ref whose name matches a given shell pattern. The + pattern can be one of branch name, tag name or fully qualified ref + name. If given multiple times, a ref will be excluded when it matches + any of the given patterns. When used together with --refs, a ref will + be used as a match only when it matches at least one --refs pattern and + does not match any --exclude patterns. Use `--no-exclude` to clear the + list of exclude patterns. --all:: List all commits reachable from all refs diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index fa1d557e5b..ed9d63ef4a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -115,6 +115,12 @@ OPTIONS directories the index file and index output file are located in. +--[no-]recurse-submodules:: + Using --recurse-submodules will update the content of all initialized + submodules according to the commit recorded in the superproject by + calling read-tree recursively, also setting the submodules HEAD to be + detached at that commit. + --no-sparse-checkout:: Disable sparse checkout support even if `core.sparseCheckout` is true. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 7241e96893..c40c470448 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -217,6 +217,10 @@ If `$GIT_DIR` is not defined and the current directory is not detected to lie in a Git repository or work tree print a message to stderr and exit with nonzero status. +--absolute-git-dir:: + Like `--git-dir`, but its output is always the canonicalized + absolute path. + --git-common-dir:: Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`. @@ -257,6 +261,12 @@ print a message to stderr and exit with nonzero status. --show-toplevel:: Show the absolute path of the top-level directory. +--show-superproject-working-tree + Show the absolute path of the root of the superproject's + working tree (if exists) that uses the current repository as + its submodule. Outputs nothing if the current repository is + not used as a submodule by any project. + --shared-index-path:: Show the path to the shared index file in split index mode, or empty if not in split-index mode. diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 642d0ef199..9d66166f69 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -89,7 +89,7 @@ See the CONFIGURATION section for `sendemail.multiEdit`. reply to the given Message-Id, which avoids breaking threads to provide a new patch series. The second and subsequent emails will be sent as replies according to - the `--[no]-chain-reply-to` setting. + the `--[no-]chain-reply-to` setting. + So for example when `--thread` and `--no-chain-reply-to` are specified, the second and subsequent patches will be replies to the first one like in the diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index a831dd0288..966abb0df8 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -81,6 +81,12 @@ be in a separate packet, and the list must end with a flush packet. will also fail if the actual call to `gpg --sign` fails. See linkgit:git-receive-pack[1] for the details on the receiving end. +--push-option=<string>:: + Pass the specified string as a push option for consumption by + hooks on the server side. If the server doesn't support push + options, error out. See linkgit:git-push[1] and + linkgit:githooks[5] for details. + <host>:: A remote host to house the repository. When this part is specified, 'git-receive-pack' is invoked via diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 2e9e344cd7..70191d06b6 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,8 +13,11 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] - [-u|--include-untracked] [-a|--all] [<message>]] +'git stash' save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [<message>] +'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [-m|--message <message>]] + [--] [<pathspec>...]] 'git stash' clear 'git stash' create [<message>] 'git stash' store [-m|--message <message>] [-q|--quiet] <commit> @@ -46,14 +49,24 @@ OPTIONS ------- save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]:: +push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]:: Save your local modifications to a new 'stash' and roll them back to HEAD (in the working tree and in the index). The <message> part is optional and gives - the description along with the stashed state. For quickly making - a snapshot, you can omit _both_ "save" and <message>, but giving - only <message> does not trigger this action to prevent a misspelled - subcommand from making an unwanted stash. + the description along with the stashed state. ++ +For quickly making a snapshot, you can omit "push". In this mode, +non-option arguments are not allowed to prevent a misspelled +subcommand from making an unwanted stash. The two exceptions to this +are `stash -p` which acts as alias for `stash push -p` and pathspecs, +which are allowed after a double hyphen `--` for disambiguation. ++ +When pathspec is given to 'git stash push', the new stash records the +modified states only for the files that match the pathspec. The index +entries and working tree files are then rolled back to the state in +HEAD only for these files, too, leaving files that do not match the +pathspec intact. + If the `--keep-index` option is used, all changes already added to the index are left intact. diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 725065ef2d..ba873657cf 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -322,10 +322,9 @@ When the `-z` option is given, pathnames are printed as is and without any quoting and lines are terminated with a NUL (ASCII 0x00) byte. -Otherwise, all pathnames will be "C-quoted" if they contain any tab, -linefeed, double quote, or backslash characters. In C-quoting, these -characters will be replaced with the corresponding C-style escape -sequences and the resulting pathname will be double quoted. +Without the `-z` option, pathnames with "unusual" characters are +quoted as explained for the configuration variable `core.quotePath` +(see linkgit:git-config[1]). CONFIGURATION diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 8acc72ebb8..e05d0cddef 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -73,13 +73,17 @@ configuration entries unless `--name` is used to specify a logical name. + <repository> is the URL of the new submodule's origin repository. This may be either an absolute URL, or (if it begins with ./ -or ../), the location relative to the superproject's origin +or ../), the location relative to the superproject's default remote repository (Please note that to specify a repository 'foo.git' which is located right next to a superproject 'bar.git', you'll have to use '../foo.git' instead of './foo.git' - as one might expect when following the rules for relative URLs - because the evaluation of relative URLs in Git is identical to that of relative directories). -If the superproject doesn't have an origin configured ++ +The default remote is the remote of the remote tracking branch +of the current branch. If no such remote tracking branch exists or +the HEAD is detached, "origin" is assumed to be the default remote. +If the superproject doesn't have a default remote configured the superproject is its own authoritative upstream and the current working directory is used instead. + @@ -118,18 +122,24 @@ too (and can also report changes to a submodule's work tree). init [--] [<path>...]:: Initialize the submodules recorded in the index (which were - added and committed elsewhere) by copying submodule - names and urls from .gitmodules to .git/config. - Optional <path> arguments limit which submodules will be initialized. - It will also copy the value of `submodule.$name.update` into - .git/config. - The key used in .git/config is `submodule.$name.url`. - This command does not alter existing information in .git/config. - You can then customize the submodule clone URLs in .git/config - for your local setup and proceed to `git submodule update`; - you can also just use `git submodule update --init` without - the explicit 'init' step if you do not intend to customize - any submodule locations. + added and committed elsewhere) by setting `submodule.$name.url` + in .git/config. It uses the same setting from .gitmodules as + a template. If the URL is relative, it will be resolved using + the default remote. If there is no default remote, the current + repository will be assumed to be upstream. ++ +Optional <path> arguments limit which submodules will be initialized. +If no path is specified, all submodules are initialized. ++ +When present, it will also copy the value of `submodule.$name.update`. +This command does not alter existing information in .git/config. +You can then customize the submodule clone URLs in .git/config +for your local setup and proceed to `git submodule update`; +you can also just use `git submodule update --init` without +the explicit 'init' step if you do not intend to customize +any submodule locations. ++ +See the add subcommand for the defintion of default remote. deinit [-f|--force] (--all|[--] <path>...):: Unregister the given submodules, i.e. remove the whole diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 7386c93162..1579abf3c3 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -163,14 +163,16 @@ may not support it yet. --split-index:: --no-split-index:: - Enable or disable split index mode. If enabled, the index is - split into two files, $GIT_DIR/index and $GIT_DIR/sharedindex.<SHA-1>. - Changes are accumulated in $GIT_DIR/index while the shared - index file contains all index entries stays unchanged. If - split-index mode is already enabled and `--split-index` is - given again, all changes in $GIT_DIR/index are pushed back to - the shared index file. This mode is designed for very large - indexes that take a significant amount of time to read or write. + Enable or disable split index mode. If split-index mode is + already enabled and `--split-index` is given again, all + changes in $GIT_DIR/index are pushed back to the shared index + file. ++ +These options take effect whatever the value of the `core.splitIndex` +configuration variable (see linkgit:git-config[1]). But a warning is +emitted when the change goes against the configured value, as the +configured value will take effect next time the index is read and this +will remove the intended effect of the option. --untracked-cache:: --no-untracked-cache:: @@ -388,6 +390,31 @@ Although this bit looks similar to assume-unchanged bit, its goal is different from assume-unchanged bit's. Skip-worktree also takes precedence over assume-unchanged bit when both are set. +Split index +----------- + +This mode is designed for repositories with very large indexes, and +aims at reducing the time it takes to repeatedly write these indexes. + +In this mode, the index is split into two files, $GIT_DIR/index and +$GIT_DIR/sharedindex.<SHA-1>. Changes are accumulated in +$GIT_DIR/index, the split index, while the shared index file contains +all index entries and stays unchanged. + +All changes in the split index are pushed back to the shared index +file when the number of entries in the split index reaches a level +specified by the splitIndex.maxPercentChange config variable (see +linkgit:git-config[1]). + +Each time a new shared index file is created, the old shared index +files are deleted if their modification time is older than what is +specified by the splitIndex.sharedIndexExpire config variable (see +linkgit:git-config[1]). + +To avoid deleting a shared index file that is still used, its +modification time is updated to the current time everytime a new split +index based on the shared index file is either created or read from. + Untracked cache --------------- diff --git a/Documentation/git.txt b/Documentation/git.txt index 25560f69ff..ecc1bb4bd7 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -44,9 +44,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.12.1/git.html[documentation for release 2.12.1] +* link:v2.12.2/git.html[documentation for release 2.12.2] * release notes for + link:RelNotes/2.12.2.txt[2.12.2]. link:RelNotes/2.12.1.txt[2.12.1]. link:RelNotes/2.12.0.txt[2.12]. @@ -1026,6 +1027,12 @@ Usually it is easier to configure any desired options through your personal `.ssh/config` file. Please consult your ssh documentation for further details. +`GIT_SSH_VARIANT`:: + If this environment variable is set, it overrides Git's autodetection + whether `GIT_SSH`/`GIT_SSH_COMMAND`/`core.sshCommand` refer to OpenSSH, + plink or tortoiseplink. This variable overrides the config setting + `ssh.variant` that serves the same purpose. + `GIT_ASKPASS`:: If this environment variable is set, then Git commands which need to acquire passwords or passphrases (e.g. for HTTP or IMAP authentication) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index e0b66c1220..a53d093ca1 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form: pattern attr1 attr2 ... That is, a pattern followed by an attributes list, -separated by whitespaces. When the pattern matches the -path in question, the attributes listed on the line are given to -the path. +separated by whitespaces. Leading and trailing whitespaces are +ignored. Lines that begin with '#' are ignored. Patterns +that begin with a double quote are quoted in C style. +When the pattern matches the path in question, the attributes +listed on the line are given to the path. Each attribute can be in one of these states for a given path: @@ -86,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead. Attributes for all users on a system should be placed in the `$(prefix)/etc/gitattributes` file. -Sometimes you would need to override an setting of an attribute +Sometimes you would need to override a setting of an attribute for a path to `Unspecified` state. This can be done by listing the name of the attribute prefixed with an exclamation point `!`. diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 8ad29e61a9..6e991c2469 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -384,10 +384,33 @@ full pathname may have special meaning: + Glob magic is incompatible with literal magic. +attr;; +After `attr:` comes a space separated list of "attribute +requirements", all of which must be met in order for the +path to be considered a match; this is in addition to the +usual non-magic pathspec pattern matching. +See linkgit:gitattributes[5]. ++ +Each of the attribute requirements for the path takes one of +these forms: + +- "`ATTR`" requires that the attribute `ATTR` be set. + +- "`-ATTR`" requires that the attribute `ATTR` be unset. + +- "`ATTR=VALUE`" requires that the attribute `ATTR` be + set to the string `VALUE`. + +- "`!ATTR`" requires that the attribute `ATTR` be + unspecified. ++ + exclude;; After a path matches any non-exclude pathspec, it will be run - through all exclude pathspec (magic signature: `!`). If it - matches, the path is ignored. + through all exclude pathspec (magic signature: `!` or its + synonym `^`). If it matches, the path is ignored. When there + is no non-exclude pathspec, the exclusion is applied to the + result set as if invoked without any pathspec. -- [[def_parent]]parent:: diff --git a/Documentation/technical/api-gitattributes.txt b/Documentation/technical/api-gitattributes.txt index 2602668677..e7cbb7c13a 100644 --- a/Documentation/technical/api-gitattributes.txt +++ b/Documentation/technical/api-gitattributes.txt @@ -16,10 +16,15 @@ Data Structure of no interest to the calling programs. The name of the attribute can be retrieved by calling `git_attr_name()`. -`struct git_attr_check`:: +`struct attr_check_item`:: - This structure represents a set of attributes to check in a call - to `git_check_attr()` function, and receives the results. + This structure represents one attribute and its value. + +`struct attr_check`:: + + This structure represents a collection of `attr_check_item`. + It is passed to `git_check_attr()` function, specifying the + attributes to check, and receives their values. Attribute Values @@ -27,7 +32,7 @@ Attribute Values An attribute for a path can be in one of four states: Set, Unset, Unspecified or set to a string, and `.value` member of `struct -git_attr_check` records it. There are three macros to check these: +attr_check_item` records it. There are three macros to check these: `ATTR_TRUE()`:: @@ -48,49 +53,51 @@ value of the attribute for the path. Querying Specific Attributes ---------------------------- -* Prepare an array of `struct git_attr_check` to define the list of - attributes you would want to check. To populate this array, you would - need to define necessary attributes by calling `git_attr()` function. +* Prepare `struct attr_check` using attr_check_initl() + function, enumerating the names of attributes whose values you are + interested in, terminated with a NULL pointer. Alternatively, an + empty `struct attr_check` can be prepared by calling + `attr_check_alloc()` function and then attributes you want to + ask about can be added to it with `attr_check_append()` + function. * Call `git_check_attr()` to check the attributes for the path. -* Inspect `git_attr_check` structure to see how each of the attribute in - the array is defined for the path. +* Inspect `attr_check` structure to see how each of the + attribute in the array is defined for the path. Example ------- -To see how attributes "crlf" and "indent" are set for different paths. +To see how attributes "crlf" and "ident" are set for different paths. -. Prepare an array of `struct git_attr_check` with two elements (because - we are checking two attributes). Initialize their `attr` member with - pointers to `struct git_attr` obtained by calling `git_attr()`: +. Prepare a `struct attr_check` with two elements (because + we are checking two attributes): ------------ -static struct git_attr_check check[2]; +static struct attr_check *check; static void setup_check(void) { - if (check[0].attr) + if (check) return; /* already done */ - check[0].attr = git_attr("crlf"); - check[1].attr = git_attr("ident"); + check = attr_check_initl("crlf", "ident", NULL); } ------------ -. Call `git_check_attr()` with the prepared array of `struct git_attr_check`: +. Call `git_check_attr()` with the prepared `struct attr_check`: ------------ const char *path; setup_check(); - git_check_attr(path, ARRAY_SIZE(check), check); + git_check_attr(path, check); ------------ -. Act on `.value` member of the result, left in `check[]`: +. Act on `.value` member of the result, left in `check->items[]`: ------------ - const char *value = check[0].value; + const char *value = check->items[0].value; if (ATTR_TRUE(value)) { The attribute is Set, by listing only the name of the @@ -109,20 +116,39 @@ static void setup_check(void) } ------------ +To see how attributes in argv[] are set for different paths, only +the first step in the above would be different. + +------------ +static struct attr_check *check; +static void setup_check(const char **argv) +{ + check = attr_check_alloc(); + while (*argv) { + struct git_attr *attr = git_attr(*argv); + attr_check_append(check, attr); + argv++; + } +} +------------ + Querying All Attributes ----------------------- To get the values of all attributes associated with a file: -* Call `git_all_attrs()`, which returns an array of `git_attr_check` - structures. +* Prepare an empty `attr_check` structure by calling + `attr_check_alloc()`. + +* Call `git_all_attrs()`, which populates the `attr_check` + with the attributes attached to the path. -* Iterate over the `git_attr_check` array to examine the attribute - names and values. The name of the attribute described by a - `git_attr_check` object can be retrieved via - `git_attr_name(check[i].attr)`. (Please note that no items will be - returned for unset attributes, so `ATTR_UNSET()` will return false - for all returned `git_array_check` objects.) +* Iterate over the `attr_check.items[]` array to examine + the attribute names and values. The name of the attribute + described by a `attr_check.items[]` object can be retrieved via + `git_attr_name(check->items[i].attr)`. (Please note that no items + will be returned for unset attributes, so `ATTR_UNSET()` will return + false for all returned `attr_check.items[]` objects.) -* Free the `git_array_check` array. +* Free the `attr_check` struct by calling `attr_check_free()`. diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 27bd701c0d..36768b479e 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -168,6 +168,11 @@ There are some macros to easily define options: Introduce an option with string argument. The string argument is put into `str_var`. +`OPT_STRING_LIST(short, long, &struct string_list, arg_str, description)`:: + Introduce an option with string argument. + The string argument is stored as an element in `string_list`. + Use of `--no-option` will clear the list of preceding values. + `OPT_INTEGER(short, long, &int_var, description)`:: Introduce an option with integer argument. The integer is put into `int_var`. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d18eb6d9b1..817d1cf7ef 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.12.1 +DEF_VER=v2.12.GIT LF=' ' @@ -140,6 +140,13 @@ all:: # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # +# Define DC_SHA1 to unconditionally enable the collision-detecting sha1 +# algorithm. This is slower, but may detect attempted collision attacks. +# Takes priority over other *_SHA1 knobs. +# +# Define OPENSSL_SHA1 environment variable when running make to link +# with the SHA1 routine from openssl library. +# # Define SHA1_MAX_BLOCK_SIZE to limit the amount of data that will be hashed # in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO # wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined. @@ -779,6 +786,7 @@ LIB_OBJS += notes-cache.o LIB_OBJS += notes-merge.o LIB_OBJS += notes-utils.o LIB_OBJS += object.o +LIB_OBJS += oidset.o LIB_OBJS += pack-bitmap.o LIB_OBJS += pack-bitmap-write.o LIB_OBJS += pack-check.o @@ -930,6 +938,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o @@ -1381,20 +1390,27 @@ ifdef APPLE_COMMON_CRYPTO SHA1_MAX_BLOCK_SIZE = 1024L*1024L*1024L endif +ifdef OPENSSL_SHA1 + EXTLIBS += $(LIB_4_CRYPTO) + BASIC_CFLAGS += -DSHA1_OPENSSL +else ifdef BLK_SHA1 - SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o + BASIC_CFLAGS += -DSHA1_BLK else ifdef PPC_SHA1 - SHA1_HEADER = "ppc/sha1.h" LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o + BASIC_CFLAGS += -DSHA1_PPC else ifdef APPLE_COMMON_CRYPTO COMPAT_CFLAGS += -DCOMMON_DIGEST_FOR_OPENSSL - SHA1_HEADER = <CommonCrypto/CommonDigest.h> + BASIC_CFLAGS += -DSHA1_APPLE else - SHA1_HEADER = <openssl/sha.h> - EXTLIBS += $(LIB_4_CRYPTO) + DC_SHA1 := YesPlease + LIB_OBJS += sha1dc/sha1.o + LIB_OBJS += sha1dc/ubc_check.o + BASIC_CFLAGS += -DSHA1_DC +endif endif endif endif @@ -1586,7 +1602,6 @@ endif # Shell quote (do not use $(call) to accommodate ancient setups); -SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) @@ -1619,8 +1634,7 @@ PERLLIB_EXTRA_SQ = $(subst ','\'',$(PERLLIB_EXTRA)) # from the dependency list, that would make each entry appear twice. LIBS = $(filter-out %.o, $(GITLIBS)) $(EXTLIBS) -BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ - $(COMPAT_CFLAGS) +BASIC_CFLAGS += $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) # Quote for C @@ -2223,6 +2237,7 @@ GIT-BUILD-OPTIONS: FORCE @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+ @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+ + @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+ ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif @@ -2249,6 +2264,9 @@ endif ifdef GIT_PERF_MAKE_OPTS @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+ endif +ifdef GIT_INTEROP_MAKE_OPTS + @echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+ +endif ifdef TEST_GIT_INDEX_VERSION @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+ endif @@ -1 +1 @@ -Documentation/RelNotes/2.12.2.txt
\ No newline at end of file +Documentation/RelNotes/2.13.0.txt
\ No newline at end of file @@ -246,29 +246,21 @@ char *absolute_pathdup(const char *path) return strbuf_detach(&sb, NULL); } -/* - * Unlike prefix_path, this should be used if the named file does - * not have to interact with index entry; i.e. name of a random file - * on the filesystem. - */ -const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) +char *prefix_filename(const char *pfx, const char *arg) { - static struct strbuf path = STRBUF_INIT; -#ifndef GIT_WINDOWS_NATIVE - if (!pfx_len || is_absolute_path(arg)) - return arg; - strbuf_reset(&path); - strbuf_add(&path, pfx, pfx_len); - strbuf_addstr(&path, arg); -#else - /* don't add prefix to absolute paths, but still replace '\' by '/' */ - strbuf_reset(&path); - if (is_absolute_path(arg)) + struct strbuf path = STRBUF_INIT; + size_t pfx_len = pfx ? strlen(pfx) : 0; + + if (!pfx_len) + ; /* nothing to prefix */ + else if (is_absolute_path(arg)) pfx_len = 0; - else if (pfx_len) + else strbuf_add(&path, pfx, pfx_len); + strbuf_addstr(&path, arg); +#ifdef GIT_WINDOWS_NATIVE convert_slashes(path.buf + pfx_len); #endif - return path.buf; + return strbuf_detach(&path, NULL); } @@ -2046,7 +2046,7 @@ static void prefix_one(struct apply_state *state, char **name) char *old_name = *name; if (!old_name) return; - *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name)); + *name = prefix_filename(state->prefix, *name); free(old_name); } @@ -4805,6 +4805,7 @@ int apply_all_patches(struct apply_state *state, for (i = 0; i < argc; i++) { const char *arg = argv[i]; + char *to_free = NULL; int fd; if (!strcmp(arg, "-")) { @@ -4814,21 +4815,21 @@ int apply_all_patches(struct apply_state *state, errs |= res; read_stdin = 0; continue; - } else if (0 < state->prefix_length) - arg = prefix_filename(state->prefix, - state->prefix_length, - arg); + } else + arg = to_free = prefix_filename(state->prefix, arg); fd = open(arg, O_RDONLY); if (fd < 0) { error(_("can't open patch '%s': %s"), arg, strerror(errno)); res = -128; + free(to_free); goto end; } read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options); close(fd); + free(to_free); if (res < 0) goto end; errs |= res; @@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args, return buffer; } -static void setup_archive_check(struct git_attr_check *check) -{ - static struct git_attr *attr_export_ignore; - static struct git_attr *attr_export_subst; - - if (!attr_export_ignore) { - attr_export_ignore = git_attr("export-ignore"); - attr_export_subst = git_attr("export-subst"); - } - check[0].attr = attr_export_ignore; - check[1].attr = attr_export_subst; -} - struct directory { struct directory *up; struct object_id oid; @@ -120,10 +107,10 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, void *context) { static struct strbuf path = STRBUF_INIT; + static struct attr_check *check; struct archiver_context *c = context; struct archiver_args *args = c->args; write_archive_entry_fn_t write_entry = c->write_entry; - struct git_attr_check check[2]; const char *path_without_prefix; int err; @@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, strbuf_addch(&path, '/'); path_without_prefix = path.buf + args->baselen; - setup_archive_check(check); - if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) { - if (ATTR_TRUE(check[0].value)) + if (!check) + check = attr_check_initl("export-ignore", "export-subst", NULL); + if (!git_check_attr(path_without_prefix, check)) { + if (ATTR_TRUE(check->items[0].value)) return 0; - args->convert = ATTR_TRUE(check[1].value); + args->convert = ATTR_TRUE(check->items[1].value); } if (S_ISDIR(mode) || S_ISGITLINK(mode)) { @@ -13,6 +13,8 @@ #include "attr.h" #include "dir.h" #include "utf8.h" +#include "quote.h" +#include "thread-utils.h" const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; @@ -22,99 +24,234 @@ static const char git_attr__unknown[] = "(builtin)unknown"; #define ATTR__UNSET NULL #define ATTR__UNKNOWN git_attr__unknown -/* This is a randomly chosen prime. */ -#define HASHSIZE 257 - #ifndef DEBUG_ATTR #define DEBUG_ATTR 0 #endif struct git_attr { - struct git_attr *next; - unsigned h; - int attr_nr; - int maybe_macro; - int maybe_real; - char name[FLEX_ARRAY]; + int attr_nr; /* unique attribute number */ + char name[FLEX_ARRAY]; /* attribute name */ }; -static int attr_nr; -static int cannot_trust_maybe_real; - -static struct git_attr_check *check_all_attr; -static struct git_attr *(git_attr_hash[HASHSIZE]); -char *git_attr_name(struct git_attr *attr) +const char *git_attr_name(const struct git_attr *attr) { return attr->name; } -static unsigned hash_name(const char *name, int namelen) +struct attr_hashmap { + struct hashmap map; +#ifndef NO_PTHREADS + pthread_mutex_t mutex; +#endif +}; + +static inline void hashmap_lock(struct attr_hashmap *map) { - unsigned val = 0, c; +#ifndef NO_PTHREADS + pthread_mutex_lock(&map->mutex); +#endif +} - while (namelen--) { - c = *name++; - val = ((val << 7) | (val >> 22)) ^ c; +static inline void hashmap_unlock(struct attr_hashmap *map) +{ +#ifndef NO_PTHREADS + pthread_mutex_unlock(&map->mutex); +#endif +} + +/* + * The global dictionary of all interned attributes. This + * is a singleton object which is shared between threads. + * Access to this dictionary must be surrounded with a mutex. + */ +static struct attr_hashmap g_attr_hashmap; + +/* The container for objects stored in "struct attr_hashmap" */ +struct attr_hash_entry { + struct hashmap_entry ent; /* must be the first member! */ + const char *key; /* the key; memory should be owned by value */ + size_t keylen; /* length of the key */ + void *value; /* the stored value */ +}; + +/* attr_hashmap comparison function */ +static int attr_hash_entry_cmp(const struct attr_hash_entry *a, + const struct attr_hash_entry *b, + void *unused) +{ + return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen); +} + +/* Initialize an 'attr_hashmap' object */ +static void attr_hashmap_init(struct attr_hashmap *map) +{ + hashmap_init(&map->map, (hashmap_cmp_fn) attr_hash_entry_cmp, 0); +} + +/* + * Retrieve the 'value' stored in a hashmap given the provided 'key'. + * If there is no matching entry, return NULL. + */ +static void *attr_hashmap_get(struct attr_hashmap *map, + const char *key, size_t keylen) +{ + struct attr_hash_entry k; + struct attr_hash_entry *e; + + if (!map->map.tablesize) + attr_hashmap_init(map); + + hashmap_entry_init(&k, memhash(key, keylen)); + k.key = key; + k.keylen = keylen; + e = hashmap_get(&map->map, &k, NULL); + + return e ? e->value : NULL; +} + +/* Add 'value' to a hashmap based on the provided 'key'. */ +static void attr_hashmap_add(struct attr_hashmap *map, + const char *key, size_t keylen, + void *value) +{ + struct attr_hash_entry *e; + + if (!map->map.tablesize) + attr_hashmap_init(map); + + e = xmalloc(sizeof(struct attr_hash_entry)); + hashmap_entry_init(e, memhash(key, keylen)); + e->key = key; + e->keylen = keylen; + e->value = value; + + hashmap_add(&map->map, e); +} + +struct all_attrs_item { + const struct git_attr *attr; + const char *value; + /* + * If 'macro' is non-NULL, indicates that 'attr' is a macro based on + * the current attribute stack and contains a pointer to the match_attr + * definition of the macro + */ + const struct match_attr *macro; +}; + +/* + * Reallocate and reinitialize the array of all attributes (which is used in + * the attribute collection process) in 'check' based on the global dictionary + * of attributes. + */ +static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check) +{ + int i; + + hashmap_lock(map); + + if (map->map.size < check->all_attrs_nr) + die("BUG: interned attributes shouldn't be deleted"); + + /* + * If the number of attributes in the global dictionary has increased + * (or this attr_check instance doesn't have an initialized all_attrs + * field), reallocate the provided attr_check instance's all_attrs + * field and fill each entry with its corresponding git_attr. + */ + if (map->map.size != check->all_attrs_nr) { + struct attr_hash_entry *e; + struct hashmap_iter iter; + hashmap_iter_init(&map->map, &iter); + + REALLOC_ARRAY(check->all_attrs, map->map.size); + check->all_attrs_nr = map->map.size; + + while ((e = hashmap_iter_next(&iter))) { + const struct git_attr *a = e->value; + check->all_attrs[a->attr_nr].attr = a; + } + } + + hashmap_unlock(map); + + /* + * Re-initialize every entry in check->all_attrs. + * This re-initialization can live outside of the locked region since + * the attribute dictionary is no longer being accessed. + */ + for (i = 0; i < check->all_attrs_nr; i++) { + check->all_attrs[i].value = ATTR__UNKNOWN; + check->all_attrs[i].macro = NULL; } - return val; } -static int invalid_attr_name(const char *name, int namelen) +static int attr_name_valid(const char *name, size_t namelen) { /* * Attribute name cannot begin with '-' and must consist of * characters from [-A-Za-z0-9_.]. */ if (namelen <= 0 || *name == '-') - return -1; + return 0; while (namelen--) { char ch = *name++; if (! (ch == '-' || ch == '.' || ch == '_' || ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')) ) - return -1; + return 0; } - return 0; + return 1; +} + +static void report_invalid_attr(const char *name, size_t len, + const char *src, int lineno) +{ + struct strbuf err = STRBUF_INIT; + strbuf_addf(&err, _("%.*s is not a valid attribute name"), + (int) len, name); + fprintf(stderr, "%s: %s:%d\n", err.buf, src, lineno); + strbuf_release(&err); } -static struct git_attr *git_attr_internal(const char *name, int len) +/* + * Given a 'name', lookup and return the corresponding attribute in the global + * dictionary. If no entry is found, create a new attribute and store it in + * the dictionary. + */ +static const struct git_attr *git_attr_internal(const char *name, int namelen) { - unsigned hval = hash_name(name, len); - unsigned pos = hval % HASHSIZE; struct git_attr *a; - for (a = git_attr_hash[pos]; a; a = a->next) { - if (a->h == hval && - !memcmp(a->name, name, len) && !a->name[len]) - return a; + if (!attr_name_valid(name, namelen)) + return NULL; + + hashmap_lock(&g_attr_hashmap); + + a = attr_hashmap_get(&g_attr_hashmap, name, namelen); + + if (!a) { + FLEX_ALLOC_MEM(a, name, name, namelen); + a->attr_nr = g_attr_hashmap.map.size; + + attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a); + assert(a->attr_nr == (g_attr_hashmap.map.size - 1)); } - if (invalid_attr_name(name, len)) - return NULL; + hashmap_unlock(&g_attr_hashmap); - FLEX_ALLOC_MEM(a, name, name, len); - a->h = hval; - a->next = git_attr_hash[pos]; - a->attr_nr = attr_nr++; - a->maybe_macro = 0; - a->maybe_real = 0; - git_attr_hash[pos] = a; - - REALLOC_ARRAY(check_all_attr, attr_nr); - check_all_attr[a->attr_nr].attr = a; - check_all_attr[a->attr_nr].value = ATTR__UNKNOWN; return a; } -struct git_attr *git_attr(const char *name) +const struct git_attr *git_attr(const char *name) { return git_attr_internal(name, strlen(name)); } /* What does a matched pattern decide? */ struct attr_state { - struct git_attr *attr; + const struct git_attr *attr; const char *setto; }; @@ -131,9 +268,8 @@ struct pattern { * If is_macro is true, then u.attr is a pointer to the git_attr being * defined. * - * If is_macro is false, then u.pattern points at the filename pattern - * to which the rule applies. (The memory pointed to is part of the - * memory block allocated for the match_attr instance.) + * If is_macro is false, then u.pat is the filename pattern to which the + * rule applies. * * In either case, num_attr is the number of attributes affected by * this rule, and state is an array listing them. The attributes are @@ -142,7 +278,7 @@ struct pattern { struct match_attr { union { struct pattern pat; - struct git_attr *attr; + const struct git_attr *attr; } u; char is_macro; unsigned num_attr; @@ -177,13 +313,17 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, cp++; len--; } - if (invalid_attr_name(cp, len)) { - fprintf(stderr, - "%.*s is not a valid attribute name: %s:%d\n", - len, cp, src, lineno); + if (!attr_name_valid(cp, len)) { + report_invalid_attr(cp, len, src, lineno); return NULL; } } else { + /* + * As this function is always called twice, once with + * e == NULL in the first pass and then e != NULL in + * the second pass, no need for attr_name_valid() + * check here. + */ if (*cp == '-' || *cp == '!') { e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET; cp++; @@ -207,41 +347,47 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, const char *cp, *name, *states; struct match_attr *res = NULL; int is_macro; + struct strbuf pattern = STRBUF_INIT; cp = line + strspn(line, blank); if (!*cp || *cp == '#') return NULL; name = cp; - namelen = strcspn(name, blank); + + if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) { + name = pattern.buf; + namelen = pattern.len; + } else { + namelen = strcspn(name, blank); + states = name + namelen; + } + if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen && starts_with(name, ATTRIBUTE_MACRO_PREFIX)) { if (!macro_ok) { fprintf(stderr, "%s not allowed: %s:%d\n", name, src, lineno); - return NULL; + goto fail_return; } is_macro = 1; name += strlen(ATTRIBUTE_MACRO_PREFIX); name += strspn(name, blank); namelen = strcspn(name, blank); - if (invalid_attr_name(name, namelen)) { - fprintf(stderr, - "%.*s is not a valid attribute name: %s:%d\n", - namelen, name, src, lineno); - return NULL; + if (!attr_name_valid(name, namelen)) { + report_invalid_attr(name, namelen, src, lineno); + goto fail_return; } } else is_macro = 0; - states = name + namelen; states += strspn(states, blank); /* First pass to count the attr_states */ for (cp = states, num_attr = 0; *cp; num_attr++) { cp = parse_attr(src, lineno, cp, NULL); if (!cp) - return NULL; + goto fail_return; } res = xcalloc(1, @@ -250,7 +396,6 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, (is_macro ? 0 : namelen + 1)); if (is_macro) { res->u.attr = git_attr_internal(name, namelen); - res->u.attr->maybe_macro = 1; } else { char *p = (char *)&(res->state[num_attr]); memcpy(p, name, namelen); @@ -262,7 +407,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, if (res->u.pat.flags & EXC_FLAG_NEGATIVE) { warning(_("Negative patterns are ignored in git attributes\n" "Use '\\!' for literal leading exclamation.")); - return NULL; + goto fail_return; } } res->is_macro = is_macro; @@ -271,13 +416,15 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, /* Second pass to fill the attr_states */ for (cp = states, i = 0; *cp; i++) { cp = parse_attr(src, lineno, cp, &(res->state[i])); - if (!is_macro) - res->state[i].attr->maybe_real = 1; - if (res->state[i].attr->maybe_macro) - cannot_trust_maybe_real = 1; } + strbuf_release(&pattern); return res; + +fail_return: + strbuf_release(&pattern); + free(res); + return NULL; } /* @@ -295,19 +442,19 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, * directory (again, reading the file from top to bottom) down to the * current directory, and then scan the list backwards to find the first match. * This is exactly the same as what is_excluded() does in dir.c to deal with - * .gitignore + * .gitignore file and info/excludes file as a fallback. */ -static struct attr_stack { +struct attr_stack { struct attr_stack *prev; char *origin; size_t originlen; unsigned num_matches; unsigned alloc; struct match_attr **attrs; -} *attr_stack; +}; -static void free_attr_elem(struct attr_stack *e) +static void attr_stack_free(struct attr_stack *e) { int i; free(e->origin); @@ -330,6 +477,190 @@ static void free_attr_elem(struct attr_stack *e) free(e); } +static void drop_attr_stack(struct attr_stack **stack) +{ + while (*stack) { + struct attr_stack *elem = *stack; + *stack = elem->prev; + attr_stack_free(elem); + } +} + +/* List of all attr_check structs; access should be surrounded by mutex */ +static struct check_vector { + size_t nr; + size_t alloc; + struct attr_check **checks; +#ifndef NO_PTHREADS + pthread_mutex_t mutex; +#endif +} check_vector; + +static inline void vector_lock(void) +{ +#ifndef NO_PTHREADS + pthread_mutex_lock(&check_vector.mutex); +#endif +} + +static inline void vector_unlock(void) +{ +#ifndef NO_PTHREADS + pthread_mutex_unlock(&check_vector.mutex); +#endif +} + +static void check_vector_add(struct attr_check *c) +{ + vector_lock(); + + ALLOC_GROW(check_vector.checks, + check_vector.nr + 1, + check_vector.alloc); + check_vector.checks[check_vector.nr++] = c; + + vector_unlock(); +} + +static void check_vector_remove(struct attr_check *check) +{ + int i; + + vector_lock(); + + /* Find entry */ + for (i = 0; i < check_vector.nr; i++) + if (check_vector.checks[i] == check) + break; + + if (i >= check_vector.nr) + die("BUG: no entry found"); + + /* shift entries over */ + for (; i < check_vector.nr - 1; i++) + check_vector.checks[i] = check_vector.checks[i + 1]; + + check_vector.nr--; + + vector_unlock(); +} + +/* Iterate through all attr_check instances and drop their stacks */ +static void drop_all_attr_stacks(void) +{ + int i; + + vector_lock(); + + for (i = 0; i < check_vector.nr; i++) { + drop_attr_stack(&check_vector.checks[i]->stack); + } + + vector_unlock(); +} + +struct attr_check *attr_check_alloc(void) +{ + struct attr_check *c = xcalloc(1, sizeof(struct attr_check)); + + /* save pointer to the check struct */ + check_vector_add(c); + + return c; +} + +struct attr_check *attr_check_initl(const char *one, ...) +{ + struct attr_check *check; + int cnt; + va_list params; + const char *param; + + va_start(params, one); + for (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++) + ; + va_end(params); + + check = attr_check_alloc(); + check->nr = cnt; + check->alloc = cnt; + check->items = xcalloc(cnt, sizeof(struct attr_check_item)); + + check->items[0].attr = git_attr(one); + va_start(params, one); + for (cnt = 1; cnt < check->nr; cnt++) { + const struct git_attr *attr; + param = va_arg(params, const char *); + if (!param) + die("BUG: counted %d != ended at %d", + check->nr, cnt); + attr = git_attr(param); + if (!attr) + die("BUG: %s: not a valid attribute name", param); + check->items[cnt].attr = attr; + } + va_end(params); + return check; +} + +struct attr_check *attr_check_dup(const struct attr_check *check) +{ + struct attr_check *ret; + + if (!check) + return NULL; + + ret = attr_check_alloc(); + + ret->nr = check->nr; + ret->alloc = check->alloc; + ALLOC_ARRAY(ret->items, ret->nr); + COPY_ARRAY(ret->items, check->items, ret->nr); + + return ret; +} + +struct attr_check_item *attr_check_append(struct attr_check *check, + const struct git_attr *attr) +{ + struct attr_check_item *item; + + ALLOC_GROW(check->items, check->nr + 1, check->alloc); + item = &check->items[check->nr++]; + item->attr = attr; + return item; +} + +void attr_check_reset(struct attr_check *check) +{ + check->nr = 0; +} + +void attr_check_clear(struct attr_check *check) +{ + free(check->items); + check->items = NULL; + check->alloc = 0; + check->nr = 0; + + free(check->all_attrs); + check->all_attrs = NULL; + check->all_attrs_nr = 0; + + drop_attr_stack(&check->stack); +} + +void attr_check_free(struct attr_check *check) +{ + if (check) { + /* Remove check from the check vector */ + check_vector_remove(check); + + attr_check_clear(check); + free(check); + } +} + static const char *builtin_attr[] = { "[attr]binary -diff -merge -text", NULL, @@ -362,9 +693,31 @@ static struct attr_stack *read_attr_from_array(const char **list) return res; } +/* + * Callers into the attribute system assume there is a single, system-wide + * global state where attributes are read from and when the state is flipped by + * calling git_attr_set_direction(), the stack frames that have been + * constructed need to be discarded so so that subsequent calls into the + * attribute system will lazily read from the right place. Since changing + * direction causes a global paradigm shift, it should not ever be called while + * another thread could potentially be calling into the attribute system. + */ static enum git_attr_direction direction; static struct index_state *use_index; +void git_attr_set_direction(enum git_attr_direction new_direction, + struct index_state *istate) +{ + if (is_bare_repository() && new_direction != GIT_ATTR_INDEX) + die("BUG: non-INDEX attr direction in a bare repo"); + + if (new_direction != direction) + drop_all_attr_stacks(); + + direction = new_direction; + use_index = istate; +} + static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) { FILE *fp = fopen(path, "r"); @@ -402,8 +755,8 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok) for (sp = buf; *sp; ) { char *ep; int more; - for (ep = sp; *ep && *ep != '\n'; ep++) - ; + + ep = strchrnul(sp, '\n'); more = (*ep == '\n'); *ep = '\0'; handle_attr_line(res, sp, path, ++lineno, macro_ok); @@ -415,25 +768,28 @@ static struct attr_stack *read_attr_from_index(const char *path, int macro_ok) static struct attr_stack *read_attr(const char *path, int macro_ok) { - struct attr_stack *res; + struct attr_stack *res = NULL; - if (direction == GIT_ATTR_CHECKOUT) { + if (direction == GIT_ATTR_INDEX) { res = read_attr_from_index(path, macro_ok); - if (!res) - res = read_attr_from_file(path, macro_ok); - } - else if (direction == GIT_ATTR_CHECKIN) { - res = read_attr_from_file(path, macro_ok); - if (!res) - /* - * There is no checked out .gitattributes file there, but - * we might have it in the index. We allow operation in a - * sparsely checked out work tree, so read from it. - */ + } else if (!is_bare_repository()) { + if (direction == GIT_ATTR_CHECKOUT) { res = read_attr_from_index(path, macro_ok); + if (!res) + res = read_attr_from_file(path, macro_ok); + } else if (direction == GIT_ATTR_CHECKIN) { + res = read_attr_from_file(path, macro_ok); + if (!res) + /* + * There is no checked out .gitattributes file + * there, but we might have it in the index. + * We allow operation in a sparsely checked out + * work tree, so read from it. + */ + res = read_attr_from_index(path, macro_ok); + } } - else - res = read_attr_from_index(path, macro_ok); + if (!res) res = xcalloc(1, sizeof(*res)); return res; @@ -464,16 +820,7 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr #define debug_push(a) do { ; } while (0) #define debug_pop(a) do { ; } while (0) #define debug_set(a,b,c,d) do { ; } while (0) -#endif - -static void drop_attr_stack(void) -{ - while (attr_stack) { - struct attr_stack *elem = attr_stack; - attr_stack = elem->prev; - free_attr_elem(elem); - } -} +#endif /* DEBUG_ATTR */ static const char *git_etc_gitattributes(void) { @@ -483,6 +830,14 @@ static const char *git_etc_gitattributes(void) return system_wide; } +static const char *get_home_gitattributes(void) +{ + if (!git_attributes_file) + git_attributes_file = xdg_config_home("attributes"); + + return git_attributes_file; +} + static int git_attr_system(void) { return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); @@ -490,64 +845,60 @@ static int git_attr_system(void) static GIT_PATH_FUNC(git_path_info_attributes, INFOATTRIBUTES_FILE) -static void bootstrap_attr_stack(void) +static void push_stack(struct attr_stack **attr_stack_p, + struct attr_stack *elem, char *origin, size_t originlen) { - struct attr_stack *elem; + if (elem) { + elem->origin = origin; + if (origin) + elem->originlen = originlen; + elem->prev = *attr_stack_p; + *attr_stack_p = elem; + } +} - if (attr_stack) +static void bootstrap_attr_stack(struct attr_stack **stack) +{ + struct attr_stack *e; + + if (*stack) return; - elem = read_attr_from_array(builtin_attr); - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; + /* builtin frame */ + e = read_attr_from_array(builtin_attr); + push_stack(stack, e, NULL, 0); + /* system-wide frame */ if (git_attr_system()) { - elem = read_attr_from_file(git_etc_gitattributes(), 1); - if (elem) { - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; - } + e = read_attr_from_file(git_etc_gitattributes(), 1); + push_stack(stack, e, NULL, 0); } - if (!git_attributes_file) - git_attributes_file = xdg_config_home("attributes"); - if (git_attributes_file) { - elem = read_attr_from_file(git_attributes_file, 1); - if (elem) { - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; - } + /* home directory */ + if (get_home_gitattributes()) { + e = read_attr_from_file(get_home_gitattributes(), 1); + push_stack(stack, e, NULL, 0); } - if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { - elem = read_attr(GITATTRIBUTES_FILE, 1); - elem->origin = xstrdup(""); - elem->originlen = 0; - elem->prev = attr_stack; - attr_stack = elem; - debug_push(elem); - } + /* root directory */ + e = read_attr(GITATTRIBUTES_FILE, 1); + push_stack(stack, e, xstrdup(""), 0); + /* info frame */ if (startup_info->have_repository) - elem = read_attr_from_file(git_path_info_attributes(), 1); + e = read_attr_from_file(git_path_info_attributes(), 1); else - elem = NULL; - - if (!elem) - elem = xcalloc(1, sizeof(*elem)); - elem->origin = NULL; - elem->prev = attr_stack; - attr_stack = elem; + e = NULL; + if (!e) + e = xcalloc(1, sizeof(struct attr_stack)); + push_stack(stack, e, NULL, 0); } -static void prepare_attr_stack(const char *path, int dirlen) +static void prepare_attr_stack(const char *path, int dirlen, + struct attr_stack **stack) { - struct attr_stack *elem, *info; - int len; - const char *cp; + struct attr_stack *info; + struct strbuf pathbuf = STRBUF_INIT; /* * At the bottom of the attribute stack is the built-in @@ -564,13 +915,13 @@ static void prepare_attr_stack(const char *path, int dirlen) * .gitattributes in deeper directories to shallower ones, * and finally use the built-in set as the default. */ - bootstrap_attr_stack(); + bootstrap_attr_stack(stack); /* * Pop the "info" one that is always at the top of the stack. */ - info = attr_stack; - attr_stack = info->prev; + info = *stack; + *stack = info->prev; /* * Pop the ones from directories that are not the prefix of @@ -578,59 +929,63 @@ static void prepare_attr_stack(const char *path, int dirlen) * the root one (whose origin is an empty string "") or the builtin * one (whose origin is NULL) without popping it. */ - while (attr_stack->origin) { - int namelen = strlen(attr_stack->origin); + while ((*stack)->origin) { + int namelen = (*stack)->originlen; + struct attr_stack *elem; - elem = attr_stack; + elem = *stack; if (namelen <= dirlen && !strncmp(elem->origin, path, namelen) && (!namelen || path[namelen] == '/')) break; debug_pop(elem); - attr_stack = elem->prev; - free_attr_elem(elem); + *stack = elem->prev; + attr_stack_free(elem); } /* - * Read from parent directories and push them down + * bootstrap_attr_stack() should have added, and the + * above loop should have stopped before popping, the + * root element whose attr_stack->origin is set to an + * empty string. */ - if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { - /* - * bootstrap_attr_stack() should have added, and the - * above loop should have stopped before popping, the - * root element whose attr_stack->origin is set to an - * empty string. - */ - struct strbuf pathbuf = STRBUF_INIT; - - assert(attr_stack->origin); - while (1) { - len = strlen(attr_stack->origin); - if (dirlen <= len) - break; - cp = memchr(path + len + 1, '/', dirlen - len - 1); - if (!cp) - cp = path + dirlen; - strbuf_add(&pathbuf, path, cp - path); + assert((*stack)->origin); + + strbuf_addstr(&pathbuf, (*stack)->origin); + /* Build up to the directory 'path' is in */ + while (pathbuf.len < dirlen) { + size_t len = pathbuf.len; + struct attr_stack *next; + char *origin; + + /* Skip path-separator */ + if (len < dirlen && is_dir_sep(path[len])) + len++; + /* Find the end of the next component */ + while (len < dirlen && !is_dir_sep(path[len])) + len++; + + if (pathbuf.len > 0) strbuf_addch(&pathbuf, '/'); - strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE); - elem = read_attr(pathbuf.buf, 0); - strbuf_setlen(&pathbuf, cp - path); - elem->origin = strbuf_detach(&pathbuf, &elem->originlen); - elem->prev = attr_stack; - attr_stack = elem; - debug_push(elem); - } + strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len)); + strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE); + + next = read_attr(pathbuf.buf, 0); + + /* reset the pathbuf to not include "/.gitattributes" */ + strbuf_setlen(&pathbuf, len); - strbuf_release(&pathbuf); + origin = xstrdup(pathbuf.buf); + push_stack(stack, next, origin, len); } /* * Finally push the "info" one at the top of the stack. */ - info->prev = attr_stack; - attr_stack = info; + push_stack(stack, info, NULL, 0); + + strbuf_release(&pathbuf); } static int path_matches(const char *pathname, int pathlen, @@ -656,16 +1011,16 @@ static int path_matches(const char *pathname, int pathlen, pattern, prefix, pat->patternlen, pat->flags); } -static int macroexpand_one(int attr_nr, int rem); +static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem); -static int fill_one(const char *what, struct match_attr *a, int rem) +static int fill_one(const char *what, struct all_attrs_item *all_attrs, + const struct match_attr *a, int rem) { - struct git_attr_check *check = check_all_attr; int i; - for (i = a->num_attr - 1; 0 < rem && 0 <= i; i--) { - struct git_attr *attr = a->state[i].attr; - const char **n = &(check[attr->attr_nr].value); + for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) { + const struct git_attr *attr = a->state[i].attr; + const char **n = &(all_attrs[attr->attr_nr].value); const char *v = a->state[i].setto; if (*n == ATTR__UNKNOWN) { @@ -674,64 +1029,72 @@ static int fill_one(const char *what, struct match_attr *a, int rem) attr, v); *n = v; rem--; - rem = macroexpand_one(attr->attr_nr, rem); + rem = macroexpand_one(all_attrs, attr->attr_nr, rem); } } return rem; } static int fill(const char *path, int pathlen, int basename_offset, - struct attr_stack *stk, int rem) + const struct attr_stack *stack, + struct all_attrs_item *all_attrs, int rem) { - int i; - const char *base = stk->origin ? stk->origin : ""; + for (; rem > 0 && stack; stack = stack->prev) { + int i; + const char *base = stack->origin ? stack->origin : ""; - for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) { - struct match_attr *a = stk->attrs[i]; - if (a->is_macro) - continue; - if (path_matches(path, pathlen, basename_offset, - &a->u.pat, base, stk->originlen)) - rem = fill_one("fill", a, rem); + for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) { + const struct match_attr *a = stack->attrs[i]; + if (a->is_macro) + continue; + if (path_matches(path, pathlen, basename_offset, + &a->u.pat, base, stack->originlen)) + rem = fill_one("fill", all_attrs, a, rem); + } } + return rem; } -static int macroexpand_one(int nr, int rem) +static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem) { - struct attr_stack *stk; - struct match_attr *a = NULL; - int i; + const struct all_attrs_item *item = &all_attrs[nr]; - if (check_all_attr[nr].value != ATTR__TRUE || - !check_all_attr[nr].attr->maybe_macro) + if (item->macro && item->value == ATTR__TRUE) + return fill_one("expand", all_attrs, item->macro, rem); + else return rem; +} - for (stk = attr_stack; !a && stk; stk = stk->prev) - for (i = stk->num_matches - 1; !a && 0 <= i; i--) { - struct match_attr *ma = stk->attrs[i]; - if (!ma->is_macro) - continue; - if (ma->u.attr->attr_nr == nr) - a = ma; +/* + * Marks the attributes which are macros based on the attribute stack. + * This prevents having to search through the attribute stack each time + * a macro needs to be expanded during the fill stage. + */ +static void determine_macros(struct all_attrs_item *all_attrs, + const struct attr_stack *stack) +{ + for (; stack; stack = stack->prev) { + int i; + for (i = stack->num_matches - 1; i >= 0; i--) { + const struct match_attr *ma = stack->attrs[i]; + if (ma->is_macro) { + int n = ma->u.attr->attr_nr; + if (!all_attrs[n].macro) { + all_attrs[n].macro = ma; + } + } } - - if (a) - rem = fill_one("expand", a, rem); - - return rem; + } } /* - * Collect attributes for path into the array pointed to by - * check_all_attr. If num is non-zero, only attributes in check[] are - * collected. Otherwise all attributes are collected. + * Collect attributes for path into the array pointed to by check->all_attrs. + * If check->check_nr is non-zero, only attributes in check[] are collected. + * Otherwise all attributes are collected. */ -static void collect_some_attrs(const char *path, int num, - struct git_attr_check *check) - +static void collect_some_attrs(const char *path, struct attr_check *check) { - struct attr_stack *stk; int i, pathlen, rem, dirlen; const char *cp, *last_slash = NULL; int basename_offset; @@ -749,81 +1112,67 @@ static void collect_some_attrs(const char *path, int num, dirlen = 0; } - prepare_attr_stack(path, dirlen); - for (i = 0; i < attr_nr; i++) - check_all_attr[i].value = ATTR__UNKNOWN; - if (num && !cannot_trust_maybe_real) { + prepare_attr_stack(path, dirlen, &check->stack); + all_attrs_init(&g_attr_hashmap, check); + determine_macros(check->all_attrs, check->stack); + + if (check->nr) { rem = 0; - for (i = 0; i < num; i++) { - if (!check[i].attr->maybe_real) { - struct git_attr_check *c; - c = check_all_attr + check[i].attr->attr_nr; - c->value = ATTR__UNSET; + for (i = 0; i < check->nr; i++) { + int n = check->items[i].attr->attr_nr; + struct all_attrs_item *item = &check->all_attrs[n]; + if (item->macro) { + item->value = ATTR__UNSET; rem++; } } - if (rem == num) + if (rem == check->nr) return; } - rem = attr_nr; - for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = fill(path, pathlen, basename_offset, stk, rem); + rem = check->all_attrs_nr; + fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } -int git_check_attr(const char *path, int num, struct git_attr_check *check) +int git_check_attr(const char *path, struct attr_check *check) { int i; - collect_some_attrs(path, num, check); + collect_some_attrs(path, check); - for (i = 0; i < num; i++) { - const char *value = check_all_attr[check[i].attr->attr_nr].value; + for (i = 0; i < check->nr; i++) { + size_t n = check->items[i].attr->attr_nr; + const char *value = check->all_attrs[n].value; if (value == ATTR__UNKNOWN) value = ATTR__UNSET; - check[i].value = value; + check->items[i].value = value; } return 0; } -int git_all_attrs(const char *path, int *num, struct git_attr_check **check) +void git_all_attrs(const char *path, struct attr_check *check) { - int i, count, j; + int i; - collect_some_attrs(path, 0, NULL); + attr_check_reset(check); + collect_some_attrs(path, check); - /* Count the number of attributes that are set. */ - count = 0; - for (i = 0; i < attr_nr; i++) { - const char *value = check_all_attr[i].value; - if (value != ATTR__UNSET && value != ATTR__UNKNOWN) - ++count; - } - *num = count; - ALLOC_ARRAY(*check, count); - j = 0; - for (i = 0; i < attr_nr; i++) { - const char *value = check_all_attr[i].value; - if (value != ATTR__UNSET && value != ATTR__UNKNOWN) { - (*check)[j].attr = check_all_attr[i].attr; - (*check)[j].value = value; - ++j; - } + for (i = 0; i < check->all_attrs_nr; i++) { + const char *name = check->all_attrs[i].attr->name; + const char *value = check->all_attrs[i].value; + struct attr_check_item *item; + if (value == ATTR__UNSET || value == ATTR__UNKNOWN) + continue; + item = attr_check_append(check, git_attr(name)); + item->value = value; } - - return 0; } -void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate) +void attr_start(void) { - enum git_attr_direction old = direction; - - if (is_bare_repository() && new != GIT_ATTR_INDEX) - die("BUG: non-INDEX attr direction in a bare repo"); - - direction = new; - if (new != old) - drop_attr_stack(); - use_index = istate; +#ifndef NO_PTHREADS + pthread_mutex_init(&g_attr_hashmap.mutex, NULL); + pthread_mutex_init(&check_vector.mutex, NULL); +#endif } @@ -4,11 +4,15 @@ /* An attribute is a pointer to this opaque structure */ struct git_attr; +/* opaque structures used internally for attribute collection */ +struct all_attrs_item; +struct attr_stack; + /* * Given a string, return the gitattribute object that * corresponds to it. */ -struct git_attr *git_attr(const char *); +const struct git_attr *git_attr(const char *); /* Internal use */ extern const char git_attr__true[]; @@ -20,38 +24,58 @@ extern const char git_attr__false[]; #define ATTR_UNSET(v) ((v) == NULL) /* - * Send one or more git_attr_check to git_check_attr(), and + * Send one or more git_attr_check to git_check_attrs(), and * each 'value' member tells what its value is. * Unset one is returned as NULL. */ -struct git_attr_check { - struct git_attr *attr; +struct attr_check_item { + const struct git_attr *attr; const char *value; }; +struct attr_check { + int nr; + int alloc; + struct attr_check_item *items; + int all_attrs_nr; + struct all_attrs_item *all_attrs; + struct attr_stack *stack; +}; + +extern struct attr_check *attr_check_alloc(void); +extern struct attr_check *attr_check_initl(const char *, ...); +extern struct attr_check *attr_check_dup(const struct attr_check *check); + +extern struct attr_check_item *attr_check_append(struct attr_check *check, + const struct git_attr *attr); + +extern void attr_check_reset(struct attr_check *check); +extern void attr_check_clear(struct attr_check *check); +extern void attr_check_free(struct attr_check *check); + /* * Return the name of the attribute represented by the argument. The * return value is a pointer to a null-delimited string that is part * of the internal data structure; it should not be modified or freed. */ -char *git_attr_name(struct git_attr *); +extern const char *git_attr_name(const struct git_attr *); -int git_check_attr(const char *path, int, struct git_attr_check *); +extern int git_check_attr(const char *path, struct attr_check *check); /* - * Retrieve all attributes that apply to the specified path. *num - * will be set to the number of attributes on the path; **check will - * be set to point at a newly-allocated array of git_attr_check - * objects describing the attributes and their values. *check must be - * free()ed by the caller. + * Retrieve all attributes that apply to the specified path. + * check holds the attributes and their values. */ -int git_all_attrs(const char *path, int *num, struct git_attr_check **check); +extern void git_all_attrs(const char *path, struct attr_check *check); enum git_attr_direction { GIT_ATTR_CHECKIN, GIT_ATTR_CHECKOUT, GIT_ATTR_INDEX }; -void git_attr_set_direction(enum git_attr_direction, struct index_state *); +void git_attr_set_direction(enum git_attr_direction new_direction, + struct index_state *istate); + +extern void attr_start(void); #endif /* ATTR_H */ @@ -345,7 +345,8 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree) branch, wt->path); } -int replace_each_worktree_head_symref(const char *oldref, const char *newref) +int replace_each_worktree_head_symref(const char *oldref, const char *newref, + const char *logmsg) { int ret = 0; struct worktree **worktrees = get_worktrees(0); @@ -358,7 +359,7 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref) continue; if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]), - newref)) { + newref, logmsg)) { ret = -1; error(_("HEAD of working tree %s is not updated"), worktrees[i]->path); @@ -71,6 +71,7 @@ extern void die_if_checked_out(const char *branch, int ignore_current_worktree); * This will be used when renaming a branch. Returns 0 if successful, non-zero * otherwise. */ -extern int replace_each_worktree_head_symref(const char *oldref, const char *newref); +extern int replace_each_worktree_head_symref(const char *oldref, const char *newref, + const char *logmsg); #endif @@ -103,6 +103,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); diff --git a/builtin/am.c b/builtin/am.c index 31fb60578f..f7a7a971fb 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1049,7 +1049,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, } else { write_state_text(state, "abort-safety", ""); if (!state->rebasing) - delete_ref("ORIG_HEAD", NULL, 0); + delete_ref(NULL, "ORIG_HEAD", NULL, 0); } /* @@ -2172,7 +2172,7 @@ static void am_abort(struct am_state *state) has_curr_head ? &curr_head : NULL, 0, UPDATE_REFS_DIE_ON_ERR); else if (curr_branch) - delete_ref(curr_branch, NULL, REF_NODEREF); + delete_ref(NULL, curr_branch, NULL, REF_NODEREF); free(curr_branch); am_destroy(state); diff --git a/builtin/blame.c b/builtin/blame.c index cffc626540..f7aa95f4ba 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -658,8 +658,11 @@ static struct origin *find_rename(struct scoreboard *sb, /* * Append a new blame entry to a given output queue. */ -static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e) +static void add_blame_entry(struct blame_entry ***queue, + const struct blame_entry *src) { + struct blame_entry *e = xmalloc(sizeof(*e)); + memcpy(e, src, sizeof(*e)); origin_incref(e->suspect); e->next = **queue; @@ -760,21 +763,15 @@ static void split_blame(struct blame_entry ***blamed, struct blame_entry *split, struct blame_entry *e) { - struct blame_entry *new_entry; - if (split[0].suspect && split[2].suspect) { /* The first part (reuse storage for the existing entry e) */ dup_entry(unblamed, e, &split[0]); /* The last part -- me */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(unblamed, new_entry); + add_blame_entry(unblamed, &split[2]); /* ... and the middle part -- parent */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(blamed, new_entry); + add_blame_entry(blamed, &split[1]); } else if (!split[0].suspect && !split[2].suspect) /* @@ -785,18 +782,12 @@ static void split_blame(struct blame_entry ***blamed, else if (split[0].suspect) { /* me and then parent */ dup_entry(unblamed, e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(blamed, new_entry); + add_blame_entry(blamed, &split[1]); } else { /* parent and then me */ dup_entry(blamed, e, &split[1]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(unblamed, new_entry); + add_blame_entry(unblamed, &split[2]); } } diff --git a/builtin/branch.c b/builtin/branch.c index 9d30f55b0b..52688f2e1b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -28,20 +28,21 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), N_("git branch [<options>] [-r | -a] [--points-at]"), + N_("git branch [<options>] [-r | -a] [--format]"), NULL }; static const char *head; -static unsigned char head_sha1[20]; +static struct object_id head_oid; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ - GIT_COLOR_RED, /* REMOTE */ - GIT_COLOR_NORMAL, /* LOCAL */ - GIT_COLOR_GREEN, /* CURRENT */ - GIT_COLOR_BLUE, /* UPSTREAM */ + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_RED, /* REMOTE */ + GIT_COLOR_NORMAL, /* LOCAL */ + GIT_COLOR_GREEN, /* CURRENT */ + GIT_COLOR_BLUE, /* UPSTREAM */ }; enum color_branch { BRANCH_COLOR_RESET = 0, @@ -117,13 +118,13 @@ static int branch_merged(int kind, const char *name, if (kind == FILTER_REFS_BRANCHES) { struct branch *branch = branch_get(name); const char *upstream = branch_get_upstream(branch, NULL); - unsigned char sha1[20]; + struct object_id oid; if (upstream && (reference_name = reference_name_to_free = resolve_refdup(upstream, RESOLVE_REF_READING, - sha1, NULL)) != NULL) - reference_rev = lookup_commit_reference(sha1); + oid.hash, NULL)) != NULL) + reference_rev = lookup_commit_reference(oid.hash); } if (!reference_rev) reference_rev = head_rev; @@ -153,10 +154,10 @@ static int branch_merged(int kind, const char *name, } static int check_branch_commit(const char *branchname, const char *refname, - const unsigned char *sha1, struct commit *head_rev, + const struct object_id *oid, struct commit *head_rev, int kinds, int force) { - struct commit *rev = lookup_commit_reference(sha1); + struct commit *rev = lookup_commit_reference(oid->hash); if (!rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; @@ -183,31 +184,34 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { struct commit *head_rev = NULL; - unsigned char sha1[20]; + struct object_id oid; char *name = NULL; const char *fmt; int i; int ret = 0; int remote_branch = 0; struct strbuf bname = STRBUF_INIT; + unsigned allowed_interpret; switch (kinds) { case FILTER_REFS_REMOTES: fmt = "refs/remotes/%s"; /* For subsequent UI messages */ remote_branch = 1; + allowed_interpret = INTERPRET_BRANCH_REMOTE; force = 1; break; case FILTER_REFS_BRANCHES: fmt = "refs/heads/%s"; + allowed_interpret = INTERPRET_BRANCH_LOCAL; break; default: die(_("cannot use -a with -d")); } if (!force) { - head_rev = lookup_commit_reference(head_sha1); + head_rev = lookup_commit_reference(head_oid.hash); if (!head_rev) die(_("Couldn't look up commit object for HEAD")); } @@ -215,7 +219,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, char *target = NULL; int flags = 0; - strbuf_branchname(&bname, argv[i]); + strbuf_branchname(&bname, argv[i], allowed_interpret); free(name); name = mkpathdup(fmt, bname.buf); @@ -235,7 +239,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE | RESOLVE_REF_ALLOW_BAD_NAME, - sha1, &flags); + oid.hash, &flags); if (!target) { error(remote_branch ? _("remote-tracking branch '%s' not found.") @@ -245,13 +249,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) && - check_branch_commit(bname.buf, name, sha1, head_rev, kinds, + check_branch_commit(bname.buf, name, &oid, head_rev, kinds, force)) { ret = 1; goto next; } - if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1, + if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash, REF_NODEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") @@ -267,7 +271,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, bname.buf, (flags & REF_ISBROKEN) ? "broken" : (flags & REF_ISSYMREF) ? target - : find_unique_abbrev(sha1, DEFAULT_ABBREV)); + : find_unique_abbrev(oid.hash, DEFAULT_ABBREV)); } delete_branch_config(bname.buf); @@ -280,221 +284,109 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, return(ret); } -static void fill_tracking_info(struct strbuf *stat, const char *branch_name, - int show_upstream_ref) +static int calc_maxwidth(struct ref_array *refs, int remote_bonus) { - int ours, theirs; - char *ref = NULL; - struct branch *branch = branch_get(branch_name); - const char *upstream; - struct strbuf fancy = STRBUF_INIT; - int upstream_is_gone = 0; - int added_decoration = 1; - - if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) { - if (!upstream) - return; - upstream_is_gone = 1; - } - - if (show_upstream_ref) { - ref = shorten_unambiguous_ref(upstream, 0); - if (want_color(branch_use_color)) - strbuf_addf(&fancy, "%s%s%s", - branch_get_color(BRANCH_COLOR_UPSTREAM), - ref, branch_get_color(BRANCH_COLOR_RESET)); - else - strbuf_addstr(&fancy, ref); - } + int i, max = 0; + for (i = 0; i < refs->nr; i++) { + struct ref_array_item *it = refs->items[i]; + const char *desc = it->refname; + int w; - if (upstream_is_gone) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: gone]"), fancy.buf); - else - added_decoration = 0; - } else if (!ours && !theirs) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s]"), fancy.buf); - else - added_decoration = 0; - } else if (!ours) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs); - else - strbuf_addf(stat, _("[behind %d]"), theirs); + skip_prefix(it->refname, "refs/heads/", &desc); + skip_prefix(it->refname, "refs/remotes/", &desc); + if (it->kind == FILTER_REFS_DETACHED_HEAD) { + char *head_desc = get_head_description(); + w = utf8_strwidth(head_desc); + free(head_desc); + } else + w = utf8_strwidth(desc); - } else if (!theirs) { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours); - else - strbuf_addf(stat, _("[ahead %d]"), ours); - } else { - if (show_upstream_ref) - strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), - fancy.buf, ours, theirs); - else - strbuf_addf(stat, _("[ahead %d, behind %d]"), - ours, theirs); + if (it->kind == FILTER_REFS_REMOTES) + w += remote_bonus; + if (w > max) + max = w; } - strbuf_release(&fancy); - if (added_decoration) - strbuf_addch(stat, ' '); - free(ref); + return max; } -static void add_verbose_info(struct strbuf *out, struct ref_array_item *item, - struct ref_filter *filter, const char *refname) +static const char *quote_literal_for_format(const char *s) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = _(" **** invalid ref ****"); - struct commit *commit = item->commit; + static struct strbuf buf = STRBUF_INIT; - if (!parse_commit(commit)) { - pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject); - sub = subject.buf; + strbuf_reset(&buf); + while (*s) { + const char *ep = strchrnul(s, '%'); + if (s < ep) + strbuf_add(&buf, s, ep - s); + if (*ep == '%') { + strbuf_addstr(&buf, "%%"); + s = ep + 1; + } else { + s = ep; + } } - - if (item->kind == FILTER_REFS_BRANCHES) - fill_tracking_info(&stat, refname, filter->verbose > 1); - - strbuf_addf(out, " %s %s%s", - find_unique_abbrev(item->commit->object.oid.hash, filter->abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); + return buf.buf; } -static char *get_head_description(void) +static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix) { - struct strbuf desc = STRBUF_INIT; - struct wt_status_state state; - memset(&state, 0, sizeof(state)); - wt_status_get_state(&state, 1); - if (state.rebase_in_progress || - state.rebase_interactive_in_progress) - strbuf_addf(&desc, _("(no branch, rebasing %s)"), - state.branch); - else if (state.bisect_in_progress) - strbuf_addf(&desc, _("(no branch, bisect started on %s)"), - state.branch); - else if (state.detached_from) { - if (state.detached_at) - /* TRANSLATORS: make sure this matches - "HEAD detached at " in wt-status.c */ - strbuf_addf(&desc, _("(HEAD detached at %s)"), - state.detached_from); - else - /* TRANSLATORS: make sure this matches - "HEAD detached from " in wt-status.c */ - strbuf_addf(&desc, _("(HEAD detached from %s)"), - state.detached_from); - } - else - strbuf_addstr(&desc, _("(no branch)")); - free(state.branch); - free(state.onto); - free(state.detached_from); - return strbuf_detach(&desc, NULL); -} + struct strbuf fmt = STRBUF_INIT; + struct strbuf local = STRBUF_INIT; + struct strbuf remote = STRBUF_INIT; -static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, - struct ref_filter *filter, const char *remote_prefix) -{ - char c; - int current = 0; - int color; - struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - const char *prefix_to_show = ""; - const char *prefix_to_skip = NULL; - const char *desc = item->refname; - char *to_free = NULL; + strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else) %%(end)", + branch_get_color(BRANCH_COLOR_CURRENT)); - switch (item->kind) { - case FILTER_REFS_BRANCHES: - prefix_to_skip = "refs/heads/"; - skip_prefix(desc, prefix_to_skip, &desc); - if (!filter->detached && !strcmp(desc, head)) - current = 1; + if (filter->verbose) { + struct strbuf obname = STRBUF_INIT; + + if (filter->abbrev < 0) + strbuf_addf(&obname, "%%(objectname:short)"); + else if (!filter->abbrev) + strbuf_addf(&obname, "%%(objectname)"); else - color = BRANCH_COLOR_LOCAL; - break; - case FILTER_REFS_REMOTES: - prefix_to_skip = "refs/remotes/"; - skip_prefix(desc, prefix_to_skip, &desc); - color = BRANCH_COLOR_REMOTE; - prefix_to_show = remote_prefix; - break; - case FILTER_REFS_DETACHED_HEAD: - desc = to_free = get_head_description(); - current = 1; - break; - default: - color = BRANCH_COLOR_PLAIN; - break; - } + strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev); - c = ' '; - if (current) { - c = '*'; - color = BRANCH_COLOR_CURRENT; - } + strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth); + strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET)); + strbuf_addf(&local, " %s ", obname.buf); - strbuf_addf(&name, "%s%s", prefix_to_show, desc); - if (filter->verbose) { - int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); - strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), - maxwidth + utf8_compensation, name.buf, - branch_get_color(BRANCH_COLOR_RESET)); - } else - strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), - name.buf, branch_get_color(BRANCH_COLOR_RESET)); - - if (item->symref) { - const char *symref = item->symref; - if (prefix_to_skip) - skip_prefix(symref, prefix_to_skip, &symref); - strbuf_addf(&out, " -> %s", symref); - } - else if (filter->verbose) - /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ - add_verbose_info(&out, item, filter, desc); - if (column_active(colopts)) { - assert(!filter->verbose && "--column and --verbose are incompatible"); - string_list_append(&output, out.buf); + if (filter->verbose > 1) + strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)" + "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)", + branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)"); + + strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s" + "%%(if)%%(symref)%%(then) -> %%(symref:short)" + "%%(else) %s %%(contents:subject)%%(end)", + branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix), + branch_get_color(BRANCH_COLOR_RESET), obname.buf); + strbuf_release(&obname); } else { - printf("%s\n", out.buf); + strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)", + branch_get_color(BRANCH_COLOR_RESET)); + strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)", + branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix), + branch_get_color(BRANCH_COLOR_RESET)); } - strbuf_release(&name); - strbuf_release(&out); - free(to_free); -} -static int calc_maxwidth(struct ref_array *refs, int remote_bonus) -{ - int i, max = 0; - for (i = 0; i < refs->nr; i++) { - struct ref_array_item *it = refs->items[i]; - const char *desc = it->refname; - int w; + strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf); - skip_prefix(it->refname, "refs/heads/", &desc); - skip_prefix(it->refname, "refs/remotes/", &desc); - w = utf8_strwidth(desc); - - if (it->kind == FILTER_REFS_REMOTES) - w += remote_bonus; - if (w > max) - max = w; - } - return max; + strbuf_release(&local); + strbuf_release(&remote); + return strbuf_detach(&fmt, NULL); } -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { int i; struct ref_array array; int maxwidth = 0; const char *remote_prefix = ""; + struct strbuf out = STRBUF_INIT; + char *to_free = NULL; /* * If we are listing more than just remote branches, @@ -506,18 +398,32 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin memset(&array, 0, sizeof(array)); - verify_ref_format("%(refname)%(symref)"); filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); + if (!format) + format = to_free = build_format(filter, maxwidth, remote_prefix); + verify_ref_format(format); + ref_array_sort(sorting, &array); - for (i = 0; i < array.nr; i++) - format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix); + for (i = 0; i < array.nr; i++) { + format_ref_array_item(array.items[i], format, 0, &out); + if (column_active(colopts)) { + assert(!filter->verbose && "--column and --verbose are incompatible"); + /* format to a string_list to let print_columns() do its job */ + string_list_append(&output, out.buf); + } else { + fwrite(out.buf, 1, out.len, stdout); + putchar('\n'); + } + strbuf_release(&out); + } ref_array_clear(&array); + free(to_free); } static void reject_rebase_or_bisect_branch(const char *target) @@ -579,14 +485,15 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); - strbuf_release(&logmsg); if (recovery) warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); - if (replace_each_worktree_head_symref(oldref.buf, newref.buf)) + if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); + strbuf_release(&logmsg); + strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); @@ -638,6 +545,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct ref_filter filter; int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + const char *format = NULL; struct option options[] = { OPT_GROUP(N_("Generic options")), @@ -679,9 +587,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) N_("print only branches of the object"), 0, parse_opt_object_name }, OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), + OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), OPT_END(), }; + setup_ref_filter_porcelain_msg(); + memset(&filter, 0, sizeof(filter)); filter.kind = FILTER_REFS_BRANCHES; filter.abbrev = -1; @@ -693,7 +604,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_refdup("HEAD", 0, head_sha1, NULL); + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) @@ -749,7 +660,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); sorting->ignore_case = icase; - print_ref_list(&filter, sorting); + print_ref_list(&filter, sorting, format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; diff --git a/builtin/bundle.c b/builtin/bundle.c index 4883a435a9..d0de59b94f 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -20,21 +20,15 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) struct bundle_header header; const char *cmd, *bundle_file; int bundle_fd = -1; - char buffer[PATH_MAX]; if (argc < 3) usage(builtin_bundle_usage); cmd = argv[1]; - bundle_file = argv[2]; + bundle_file = prefix_filename(prefix, argv[2]); argc -= 2; argv += 2; - if (prefix && bundle_file[0] != '/') { - snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); - bundle_file = buffer; - } - memset(&header, 0, sizeof(header)); if (strcmp(cmd, "create") && (bundle_fd = read_bundle_header(bundle_file, &header)) < 0) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 30383e9eb4..8b85cb8cf0 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -409,20 +409,20 @@ static int batch_object_cb(const unsigned char sha1[20], void *vdata) return 0; } -static int batch_loose_object(const unsigned char *sha1, +static int batch_loose_object(const struct object_id *oid, const char *path, void *data) { - sha1_array_append(data, sha1); + sha1_array_append(data, oid->hash); return 0; } -static int batch_packed_object(const unsigned char *sha1, +static int batch_packed_object(const struct object_id *oid, struct packed_git *pack, uint32_t pos, void *data) { - sha1_array_append(data, sha1); + sha1_array_append(data, oid->hash); return 0; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 53a5a18c16..4d01ca0c8b 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -24,12 +24,13 @@ static const struct option check_attr_options[] = { OPT_END() }; -static void output_attr(int cnt, struct git_attr_check *check, - const char *file) +static void output_attr(struct attr_check *check, const char *file) { int j; + int cnt = check->nr; + for (j = 0; j < cnt; j++) { - const char *value = check[j].value; + const char *value = check->items[j].value; if (ATTR_TRUE(value)) value = "set"; @@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check, printf("%s%c" /* path */ "%s%c" /* attrname */ "%s%c" /* attrvalue */, - file, 0, git_attr_name(check[j].attr), 0, value, 0); + file, 0, + git_attr_name(check->items[j].attr), 0, value, 0); } else { quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", git_attr_name(check[j].attr), value); + printf(": %s: %s\n", + git_attr_name(check->items[j].attr), value); } - } } -static void check_attr(const char *prefix, int cnt, - struct git_attr_check *check, const char *file) +static void check_attr(const char *prefix, + struct attr_check *check, + int collect_all, + const char *file) { char *full_path = prefix_path(prefix, prefix ? strlen(prefix) : 0, file); - if (check != NULL) { - if (git_check_attr(full_path, cnt, check)) - die("git_check_attr died"); - output_attr(cnt, check, file); + + if (collect_all) { + git_all_attrs(full_path, check); } else { - if (git_all_attrs(full_path, &cnt, &check)) - die("git_all_attrs died"); - output_attr(cnt, check, file); - free(check); + if (git_check_attr(full_path, check)) + die("git_check_attr died"); } + output_attr(check, file); + free(full_path); } -static void check_attr_stdin_paths(const char *prefix, int cnt, - struct git_attr_check *check) +static void check_attr_stdin_paths(const char *prefix, + struct attr_check *check, + int collect_all) { struct strbuf buf = STRBUF_INIT; struct strbuf unquoted = STRBUF_INIT; @@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt, die("line is badly quoted"); strbuf_swap(&buf, &unquoted); } - check_attr(prefix, cnt, check, buf.buf); + check_attr(prefix, check, collect_all, buf.buf); maybe_flush_or_die(stdout, "attribute to stdout"); } strbuf_release(&buf); @@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg) int cmd_check_attr(int argc, const char **argv, const char *prefix) { - struct git_attr_check *check; + struct attr_check *check; int cnt, i, doubledash, filei; if (!is_bare_repository()) @@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) error_with_usage("No file specified"); } - if (all_attrs) { - check = NULL; - } else { - check = xcalloc(cnt, sizeof(*check)); + check = attr_check_alloc(); + if (!all_attrs) { for (i = 0; i < cnt; i++) { - const char *name; - struct git_attr *a; - name = argv[i]; - a = git_attr(name); + const struct git_attr *a = git_attr(argv[i]); + if (!a) return error("%s: not a valid attribute name", - name); - check[i].attr = a; + argv[i]); + attr_check_append(check, a); } } if (stdin_paths) - check_attr_stdin_paths(prefix, cnt, check); + check_attr_stdin_paths(prefix, check, all_attrs); else { for (i = filei; i < argc; i++) - check_attr(prefix, cnt, check, argv[i]); + check_attr(prefix, check, all_attrs, argv[i]); maybe_flush_or_die(stdout, "attribute to stdout"); } + + attr_check_free(check); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index f174f50303..3ecc47bec0 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -21,12 +21,31 @@ #include "submodule-config.h" #include "submodule.h" +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; + static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + recurse_submodules = RECURSE_SUBMODULES_OFF; + return 0; + } + if (arg) + recurse_submodules = + parse_update_recurse_submodules_arg(opt->long_name, + arg); + else + recurse_submodules = RECURSE_SUBMODULES_ON; + + return 0; +} + struct checkout_opts { int patch_mode; int quiet; @@ -452,7 +471,7 @@ static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; - strbuf_branchname(&buf, branch->name); + strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) branch->name = xstrdup(buf.buf); strbuf_splice(&buf, 0, 0, "refs/heads/", 11); @@ -1163,6 +1182,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("second guess 'git checkout <no-such-branch>'")), OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), + { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, + "checkout", "control recursive updating of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), OPT_END(), }; @@ -1193,6 +1215,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) git_xmerge_config("merge.conflictstyle", conflict_style, NULL); } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { + git_config(submodule_config, NULL); + if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) + set_config_update_recurse_submodules(recurse_submodules); + } + if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) die(_("-b, -B and --orphan are mutually exclusive")); diff --git a/builtin/clean.c b/builtin/clean.c index d6bc3aaaea..d861f836a2 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -174,8 +174,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, /* an empty dir could be removed even if it is unreadble */ res = dry_run ? 0 : rmdir(path->buf); if (res) { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } return res; @@ -208,8 +210,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; } @@ -230,8 +234,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (!res) *dir_gone = 1; else { + int saved_errno = errno; quote_path_relative(path->buf, prefix, "ed); - warning(_(msg_warn_remove_failed), quoted.buf); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; } @@ -981,8 +987,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } else { res = dry_run ? 0 : unlink(abs_path.buf); if (res) { + int saved_errno = errno; qname = quote_path_relative(item->string, NULL, &buf); - warning(_(msg_warn_remove_failed), qname); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { qname = quote_path_relative(item->string, NULL, &buf); diff --git a/builtin/clone.c b/builtin/clone.c index 3f63edbbf9..b4c929bb8a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -681,7 +681,7 @@ static void update_head(const struct ref *our, const struct ref *remote, static int checkout(int submodule_progress) { - unsigned char sha1[20]; + struct object_id oid; char *head; struct lock_file *lock_file; struct unpack_trees_options opts; @@ -692,7 +692,7 @@ static int checkout(int submodule_progress) if (option_no_checkout) return 0; - head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, oid.hash, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout.\n")); @@ -700,7 +700,7 @@ static int checkout(int submodule_progress) } if (!strcmp(head, "HEAD")) { if (advice_detached_head) - detach_advice(sha1_to_hex(sha1)); + detach_advice(oid_to_hex(&oid)); } else { if (!starts_with(head, "refs/heads/")) die(_("HEAD not found below refs/heads!")); @@ -721,7 +721,7 @@ static int checkout(int submodule_progress) opts.src_index = &the_index; opts.dst_index = &the_index; - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(oid.hash); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts) < 0) @@ -731,7 +731,7 @@ static int checkout(int submodule_progress) die(_("unable to write new index file")); err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(sha1), "1", NULL); + oid_to_hex(&oid), "1", NULL); if (!err && option_recursive) { struct argv_array args = ARGV_ARRAY_INIT; diff --git a/builtin/commit.c b/builtin/commit.c index 2de5f6cc64..4e288bc513 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -496,7 +496,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, struct wt_status *s) { - unsigned char sha1[20]; + struct object_id oid; if (s->relative_paths) s->prefix = prefix; @@ -509,9 +509,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + s->is_initial = get_sha1(s->reference, oid.hash) ? 1 : 0; if (!s->is_initial) - hashcpy(s->sha1_commit, sha1); + hashcpy(s->sha1_commit, oid.hash); s->status_format = status_format; s->ignore_submodule_arg = ignore_submodule_arg; @@ -885,7 +885,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; } else { - unsigned char sha1[20]; + struct object_id oid; const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) @@ -894,7 +894,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (amend) parent = "HEAD^1"; - if (get_sha1(parent, sha1)) { + if (get_sha1(parent, oid.hash)) { int i, ita_nr = 0; for (i = 0; i < active_nr; i++) @@ -1332,7 +1332,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) { static struct wt_status s; int fd; - unsigned char sha1[20]; + struct object_id oid; static struct option builtin_status_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT_SET_INT('s', "short", &status_format, @@ -1382,9 +1382,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) fd = hold_locked_index(&index_lock, 0); - s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + s.is_initial = get_sha1(s.reference, oid.hash) ? 1 : 0; if (!s.is_initial) - hashcpy(s.sha1_commit, sha1); + hashcpy(s.sha1_commit, oid.hash); s.ignore_submodule_arg = ignore_submodule_arg; s.status_format = status_format; @@ -1418,19 +1418,19 @@ static const char *implicit_ident_advice(void) } -static void print_summary(const char *prefix, const unsigned char *sha1, +static void print_summary(const char *prefix, const struct object_id *oid, int initial_commit) { struct rev_info rev; struct commit *commit; struct strbuf format = STRBUF_INIT; - unsigned char junk_sha1[20]; + struct object_id junk_oid; const char *head; struct pretty_print_context pctx = {0}; struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; - commit = lookup_commit(sha1); + commit = lookup_commit(oid->hash); if (!commit) die(_("couldn't look up newly created commit")); if (parse_commit(commit)) @@ -1477,7 +1477,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL); + head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL); if (!strcmp(head, "HEAD")) head = _("detached HEAD"); else @@ -1522,8 +1522,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -static int run_rewrite_hook(const unsigned char *oldsha1, - const unsigned char *newsha1) +static int run_rewrite_hook(const struct object_id *oldoid, + const struct object_id *newoid) { struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; @@ -1544,7 +1544,7 @@ static int run_rewrite_hook(const unsigned char *oldsha1, code = start_command(&proc); if (code) return code; - strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); sigchain_push(SIGPIPE, SIG_IGN); write_in_full(proc.in, sb.buf, sb.len); close(proc.in); @@ -1636,7 +1636,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; char *nl; - unsigned char sha1[20]; + struct object_id oid; struct commit_list *parents = NULL; struct stat statbuf; struct commit *current_head = NULL; @@ -1651,10 +1651,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ s.colopts = 0; - if (get_sha1("HEAD", sha1)) + if (get_sha1("HEAD", oid.hash)) current_head = NULL; else { - current_head = lookup_commit_or_die(sha1, "HEAD"); + current_head = lookup_commit_or_die(oid.hash, "HEAD"); if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } @@ -1759,7 +1759,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1, - parents, sha1, author_ident.buf, sign_commit, extra)) { + parents, oid.hash, author_ident.buf, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } @@ -1776,7 +1776,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, "HEAD", sha1, + ref_transaction_update(transaction, "HEAD", oid.hash, current_head ? current_head->object.oid.hash : null_sha1, 0, sb.buf, &err) || @@ -1805,13 +1805,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { /* we are amending, so current_head is not NULL */ - copy_note_for_rewrite(cfg, current_head->object.oid.hash, sha1); + copy_note_for_rewrite(cfg, current_head->object.oid.hash, oid.hash); finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } - run_rewrite_hook(current_head->object.oid.hash, sha1); + run_rewrite_hook(¤t_head->object.oid, &oid); } if (!quiet) - print_summary(prefix, sha1, !current_head); + print_summary(prefix, &oid, !current_head); strbuf_release(&err); return 0; diff --git a/builtin/config.c b/builtin/config.c index 05843a0f96..4f49a0edb9 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -527,9 +527,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = - xstrdup(prefix_filename(prefix, - strlen(prefix), - given_config_source.file)); + prefix_filename(prefix, given_config_source.file); } if (respect_includes == -1) diff --git a/builtin/count-objects.c b/builtin/count-objects.c index a04b4f2ef3..acb05940fc 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -53,7 +53,7 @@ static void loose_garbage(const char *path) report_garbage(PACKDIR_FILE_GARBAGE, path); } -static int count_loose(const unsigned char *sha1, const char *path, void *data) +static int count_loose(const struct object_id *oid, const char *path, void *data) { struct stat st; @@ -62,7 +62,7 @@ static int count_loose(const unsigned char *sha1, const char *path, void *data) else { loose_size += on_disk_bytes(st); loose++; - if (verbose && has_sha1_pack(sha1)) + if (verbose && has_sha1_pack(oid->hash)) packed_loose++; } return 0; diff --git a/builtin/describe.c b/builtin/describe.c index 01490a157e..45adbf67d5 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -9,6 +9,7 @@ #include "diff.h" #include "hashmap.h" #include "argv-array.h" +#include "run-command.h" #define SEEN (1u << 0) #define MAX_TAGS (FLAG_BITS - 1) @@ -28,9 +29,10 @@ static int abbrev = -1; /* unspecified */ static int max_candidates = 10; static struct hashmap names; static int have_util; -static const char *pattern; +static struct string_list patterns = STRING_LIST_INIT_NODUP; +static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP; static int always; -static const char *dirty; +static const char *suffix, *dirty, *broken; /* diff-index command arguments to check if working tree is dirty. */ static const char *diff_index_args[] = { @@ -39,11 +41,11 @@ static const char *diff_index_args[] = { struct commit_name { struct hashmap_entry entry; - unsigned char peeled[20]; + struct object_id peeled; struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; - unsigned char sha1[20]; + struct object_id oid; char *path; }; @@ -54,17 +56,17 @@ static const char *prio_names[] = { static int commit_name_cmp(const struct commit_name *cn1, const struct commit_name *cn2, const void *peeled) { - return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled); + return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled); } -static inline struct commit_name *find_commit_name(const unsigned char *peeled) +static inline struct commit_name *find_commit_name(const struct object_id *peeled) { - return hashmap_get_from_hash(&names, sha1hash(peeled), peeled); + return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash); } static int replace_name(struct commit_name *e, int prio, - const unsigned char *sha1, + const struct object_id *oid, struct tag **tag) { if (!e || e->prio < prio) @@ -77,13 +79,13 @@ static int replace_name(struct commit_name *e, struct tag *t; if (!e->tag) { - t = lookup_tag(e->sha1); + t = lookup_tag(e->oid.hash); if (!t || parse_tag(t)) return 1; e->tag = t; } - t = lookup_tag(sha1); + t = lookup_tag(oid->hash); if (!t || parse_tag(t)) return 0; *tag = t; @@ -96,24 +98,24 @@ static int replace_name(struct commit_name *e, } static void add_to_known_names(const char *path, - const unsigned char *peeled, + const struct object_id *peeled, int prio, - const unsigned char *sha1) + const struct object_id *oid) { struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; - if (replace_name(e, prio, sha1, &tag)) { + if (replace_name(e, prio, oid, &tag)) { if (!e) { e = xmalloc(sizeof(struct commit_name)); - hashcpy(e->peeled, peeled); - hashmap_entry_init(e, sha1hash(peeled)); + oidcpy(&e->peeled, peeled); + hashmap_entry_init(e, sha1hash(peeled->hash)); hashmap_add(&names, e); e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; - hashcpy(e->sha1, sha1); + oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); } @@ -129,9 +131,40 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi if (!all && !is_tag) return 0; - /* Accept only tags that match the pattern, if given */ - if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL))) - return 0; + /* + * If we're given exclude patterns, first exclude any tag which match + * any of the exclude pattern. + */ + if (exclude_patterns.nr) { + struct string_list_item *item; + + if (!is_tag) + return 0; + + for_each_string_list_item(item, &exclude_patterns) { + if (!wildmatch(item->string, path + 10, 0, NULL)) + return 0; + } + } + + /* + * If we're given patterns, accept only tags which match at least one + * pattern. + */ + if (patterns.nr) { + struct string_list_item *item; + + if (!is_tag) + return 0; + + for_each_string_list_item(item, &patterns) { + if (!wildmatch(item->string, path + 10, 0, NULL)) + break; + + /* If we get here, no pattern matched. */ + return 0; + } + } /* Is it annotated? */ if (!peel_ref(path, peeled.hash)) { @@ -154,7 +187,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi else prio = 0; - add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash); + add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid); return 0; } @@ -212,7 +245,7 @@ static unsigned long finish_depth_computation( static void display_name(struct commit_name *n) { if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(n->sha1); + n->tag = lookup_tag(n->oid.hash); if (!n->tag || parse_tag(n->tag)) die(_("annotated tag %s not available"), n->path); } @@ -230,14 +263,14 @@ static void display_name(struct commit_name *n) printf("%s", n->path); } -static void show_suffix(int depth, const unsigned char *sha1) +static void show_suffix(int depth, const struct object_id *oid) { - printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev)); + printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); } static void describe(const char *arg, int last_one) { - unsigned char sha1[20]; + struct object_id oid; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; struct commit_name *n; @@ -246,22 +279,22 @@ static void describe(const char *arg, int last_one) unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - if (get_sha1(arg, sha1)) + if (get_oid(arg, &oid)) die(_("Not a valid object name %s"), arg); - cmit = lookup_commit_reference(sha1); + cmit = lookup_commit_reference(oid.hash); if (!cmit) die(_("%s is not a valid '%s' object"), arg, commit_type); - n = find_commit_name(cmit->object.oid.hash); + n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ display_name(n); if (longformat) - show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1); - if (dirty) - printf("%s", dirty); + show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid); + if (suffix) + printf("%s", suffix); printf("\n"); return; } @@ -276,7 +309,7 @@ static void describe(const char *arg, int last_one) struct commit *c; struct commit_name *n = hashmap_iter_first(&names, &iter); for (; n; n = hashmap_iter_next(&iter)) { - c = lookup_commit_reference_gently(n->peeled, 1); + c = lookup_commit_reference_gently(n->peeled.hash, 1); if (c) c->util = n; } @@ -337,8 +370,8 @@ static void describe(const char *arg, int last_one) struct object_id *oid = &cmit->object.oid; if (always) { printf("%s", find_unique_abbrev(oid->hash, abbrev)); - if (dirty) - printf("%s", dirty); + if (suffix) + printf("%s", suffix); printf("\n"); return; } @@ -380,9 +413,9 @@ static void describe(const char *arg, int last_one) display_name(all_matches[0].name); if (abbrev) - show_suffix(all_matches[0].depth, cmit->object.oid.hash); - if (dirty) - printf("%s", dirty); + show_suffix(all_matches[0].depth, &cmit->object.oid); + if (suffix) + printf("%s", suffix); printf("\n"); if (!last_one) @@ -404,13 +437,18 @@ int cmd_describe(int argc, const char **argv, const char *prefix) N_("only output exact matches"), 0), OPT_INTEGER(0, "candidates", &max_candidates, N_("consider <n> most recent tags (default: 10)")), - OPT_STRING(0, "match", &pattern, N_("pattern"), + OPT_STRING_LIST(0, "match", &patterns, N_("pattern"), N_("only consider tags matching <pattern>")), + OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"), + N_("do not consider tags matching <pattern>")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), {OPTION_STRING, 0, "dirty", &dirty, N_("mark"), N_("append <mark> on dirty working tree (default: \"-dirty\")"), PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, + {OPTION_STRING, 0, "broken", &broken, N_("mark"), + N_("append <mark> on broken working tree (default: \"-broken\")"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "-broken"}, OPT_END(), }; @@ -430,6 +468,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("--long is incompatible with --abbrev=0")); if (contains) { + struct string_list_item *item; struct argv_array args; argv_array_init(&args); @@ -440,8 +479,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix) argv_array_push(&args, "--always"); if (!all) { argv_array_push(&args, "--tags"); - if (pattern) - argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); + for_each_string_list_item(item, &patterns) + argv_array_pushf(&args, "--refs=refs/tags/%s", item->string); + for_each_string_list_item(item, &exclude_patterns) + argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string); } if (argc) argv_array_pushv(&args, argv); @@ -456,7 +497,28 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("No names found, cannot describe anything.")); if (argc == 0) { - if (dirty) { + if (broken) { + struct child_process cp = CHILD_PROCESS_INIT; + argv_array_pushv(&cp.args, diff_index_args); + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.no_stdout = 1; + + if (!dirty) + dirty = "-dirty"; + + switch (run_command(&cp)) { + case 0: + suffix = NULL; + break; + case 1: + suffix = dirty; + break; + default: + /* diff-index aborted abnormally */ + suffix = broken; + } + } else if (dirty) { static struct lock_file index_lock; int fd; @@ -469,11 +531,15 @@ int cmd_describe(int argc, const char **argv, const char *prefix) if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) - dirty = NULL; + suffix = NULL; + else + suffix = dirty; } describe("HEAD", 1); } else if (dirty) { die(_("--dirty is incompatible with commit-ishes")); + } else if (broken) { + die(_("--broken is incompatible with commit-ishes")); } else { while (argc-- > 0) describe(*argv++, argc == 0); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 8ce00480cd..326f88b657 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -7,46 +7,44 @@ static struct rev_info log_tree_opt; -static int diff_tree_commit_sha1(const unsigned char *sha1) +static int diff_tree_commit_sha1(const struct object_id *oid) { - struct commit *commit = lookup_commit_reference(sha1); + struct commit *commit = lookup_commit_reference(oid->hash); if (!commit) return -1; return log_tree_commit(&log_tree_opt, commit); } /* Diff one or more commits. */ -static int stdin_diff_commit(struct commit *commit, char *line, int len) +static int stdin_diff_commit(struct commit *commit, const char *p) { - unsigned char sha1[20]; - if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { - /* Graft the fake parents locally to the commit */ - int pos = 41; - struct commit_list **pptr; - - /* Free the real parent list */ - free_commit_list(commit->parents); - commit->parents = NULL; - pptr = &(commit->parents); - while (line[pos] && !get_sha1_hex(line + pos, sha1)) { - struct commit *parent = lookup_commit(sha1); - if (parent) { - pptr = &commit_list_insert(parent, pptr)->next; - } - pos += 41; + struct object_id oid; + struct commit_list **pptr = NULL; + + /* Graft the fake parents locally to the commit */ + while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) { + struct commit *parent = lookup_commit(oid.hash); + if (!pptr) { + /* Free the real parent list */ + free_commit_list(commit->parents); + commit->parents = NULL; + pptr = &(commit->parents); + } + if (parent) { + pptr = &commit_list_insert(parent, pptr)->next; } } return log_tree_commit(&log_tree_opt, commit); } /* Diff two trees. */ -static int stdin_diff_trees(struct tree *tree1, char *line, int len) +static int stdin_diff_trees(struct tree *tree1, const char *p) { - unsigned char sha1[20]; + struct object_id oid; struct tree *tree2; - if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1)) + if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p) return error("Need exactly two trees, separated by a space"); - tree2 = lookup_tree(sha1); + tree2 = lookup_tree(oid.hash); if (!tree2 || parse_tree(tree2)) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), @@ -60,23 +58,24 @@ static int stdin_diff_trees(struct tree *tree1, char *line, int len) static int diff_tree_stdin(char *line) { int len = strlen(line); - unsigned char sha1[20]; + struct object_id oid; struct object *obj; + const char *p; if (!len || line[len-1] != '\n') return -1; line[len-1] = 0; - if (get_sha1_hex(line, sha1)) + if (parse_oid_hex(line, &oid, &p)) return -1; - obj = parse_object(sha1); + obj = parse_object(oid.hash); if (!obj) return -1; if (obj->type == OBJ_COMMIT) - return stdin_diff_commit((struct commit *)obj, line, len); + return stdin_diff_commit((struct commit *)obj, p); if (obj->type == OBJ_TREE) - return stdin_diff_trees((struct tree *)obj, line, len); + return stdin_diff_trees((struct tree *)obj, p); error("Object %s is a %s, not a commit or tree", - sha1_to_hex(sha1), typename(obj->type)); + oid_to_hex(&oid), typename(obj->type)); return -1; } @@ -141,7 +140,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) break; case 1: tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(tree1->oid.hash); + diff_tree_commit_sha1(&tree1->oid); break; case 2: tree1 = opt->pending.objects[0].item; @@ -164,9 +163,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | DIFF_SETUP_USE_CACHE); while (fgets(line, sizeof(line), stdin)) { - unsigned char sha1[20]; + struct object_id oid; - if (get_sha1_hex(line, sha1)) { + if (get_oid_hex(line, &oid)) { fputs(line, stdout); fflush(stdout); } diff --git a/builtin/difftool.c b/builtin/difftool.c index d13350ce83..25e54ad3ed 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -254,6 +254,49 @@ static int ensure_leading_directories(char *path) } } +/* + * Unconditional writing of a plain regular file is what + * "git difftool --dir-diff" wants to do for symlinks. We are preparing two + * temporary directories to be fed to a Git-unaware tool that knows how to + * show a diff of two directories (e.g. "diff -r A B"). + * + * Because the tool is Git-unaware, if a symbolic link appears in either of + * these temporary directories, it will try to dereference and show the + * difference of the target of the symbolic link, which is not what we want, + * as the goal of the dir-diff mode is to produce an output that is logically + * equivalent to what "git diff" produces. + * + * Most importantly, we want to get textual comparison of the result of the + * readlink(2). get_symlink() provides that---it returns the contents of + * the symlink that gets written to a regular file to force the external tool + * to compare the readlink(2) result as text, even on a filesystem that is + * capable of doing a symbolic link. + */ +static char *get_symlink(const struct object_id *oid, const char *path) +{ + char *data; + if (is_null_oid(oid)) { + /* The symlink is unknown to Git so read from the filesystem */ + struct strbuf link = STRBUF_INIT; + if (has_symlinks) { + if (strbuf_readlink(&link, path, strlen(path))) + die(_("could not read symlink %s"), path); + } else if (strbuf_read_file(&link, path, 128)) + die(_("could not read symlink file %s"), path); + + data = strbuf_detach(&link, NULL); + } else { + enum object_type type; + unsigned long size; + data = read_sha1_file(oid->hash, &type, &size); + if (!data) + die(_("could not read object %s for symlink %s"), + oid_to_hex(oid), path); + } + + return data; +} + static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, int argc, const char **argv) { @@ -270,8 +313,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, struct hashmap working_tree_dups, submodules, symlinks2; struct hashmap_iter iter; struct pair_entry *entry; - enum object_type type; - unsigned long size; struct index_state wtindex; struct checkout lstate, rstate; int rc, flags = RUN_GIT_CMD, err = 0; @@ -377,13 +418,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, } if (S_ISLNK(lmode)) { - char *content = read_sha1_file(loid.hash, &type, &size); + char *content = get_symlink(&loid, src_path); add_left_or_right(&symlinks2, src_path, content, 0); free(content); } if (S_ISLNK(rmode)) { - char *content = read_sha1_file(roid.hash, &type, &size); + char *content = get_symlink(&roid, dst_path); add_left_or_right(&symlinks2, dst_path, content, 1); free(content); } @@ -397,7 +438,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, return error("could not write '%s'", src_path); } - if (rmode) { + if (rmode && !S_ISLNK(rmode)) { struct working_tree_entry *entry; /* Avoid duplicate working_tree entries */ diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 1e815b5577..e0220630d0 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -212,7 +212,7 @@ static char *anonymize_blob(unsigned long *size) return strbuf_detach(&out, NULL); } -static void export_blob(const unsigned char *sha1) +static void export_blob(const struct object_id *oid) { unsigned long size; enum object_type type; @@ -223,34 +223,34 @@ static void export_blob(const unsigned char *sha1) if (no_data) return; - if (is_null_sha1(sha1)) + if (is_null_oid(oid)) return; - object = lookup_object(sha1); + object = lookup_object(oid->hash); if (object && object->flags & SHOWN) return; if (anonymize) { buf = anonymize_blob(&size); - object = (struct object *)lookup_blob(sha1); + object = (struct object *)lookup_blob(oid->hash); eaten = 0; } else { - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf) - die ("Could not read blob %s", sha1_to_hex(sha1)); - if (check_sha1_signature(sha1, buf, size, typename(type)) < 0) - die("sha1 mismatch in blob %s", sha1_to_hex(sha1)); - object = parse_object_buffer(sha1, type, size, buf, &eaten); + die ("Could not read blob %s", oid_to_hex(oid)); + if (check_sha1_signature(oid->hash, buf, size, typename(type)) < 0) + die("sha1 mismatch in blob %s", oid_to_hex(oid)); + object = parse_object_buffer(oid->hash, type, size, buf, &eaten); } if (!object) - die("Could not read blob %s", sha1_to_hex(sha1)); + die("Could not read blob %s", oid_to_hex(oid)); mark_next_object(object); printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size); if (size && fwrite(buf, size, 1, stdout) != 1) - die_errno ("Could not write blob '%s'", sha1_to_hex(sha1)); + die_errno ("Could not write blob '%s'", oid_to_hex(oid)); printf("\n"); show_progress(); @@ -323,19 +323,19 @@ static void print_path(const char *path) } } -static void *generate_fake_sha1(const void *old, size_t *len) +static void *generate_fake_oid(const void *old, size_t *len) { static uint32_t counter = 1; /* avoid null sha1 */ - unsigned char *out = xcalloc(20, 1); - put_be32(out + 16, counter++); + unsigned char *out = xcalloc(GIT_SHA1_RAWSZ, 1); + put_be32(out + GIT_SHA1_RAWSZ - 4, counter++); return out; } -static const unsigned char *anonymize_sha1(const unsigned char *sha1) +static const unsigned char *anonymize_sha1(const struct object_id *oid) { static struct hashmap sha1s; - size_t len = 20; - return anonymize_mem(&sha1s, generate_fake_sha1, sha1, &len); + size_t len = GIT_SHA1_RAWSZ; + return anonymize_mem(&sha1s, generate_fake_oid, oid, &len); } static void show_filemodify(struct diff_queue_struct *q, @@ -383,7 +383,7 @@ static void show_filemodify(struct diff_queue_struct *q, if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, sha1_to_hex(anonymize ? - anonymize_sha1(spec->oid.hash) : + anonymize_sha1(&spec->oid) : spec->oid.hash)); else { struct object *object = lookup_object(spec->oid.hash); @@ -572,7 +572,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - export_blob(diff_queued_diff.queue[i]->two->oid.hash); + export_blob(&diff_queued_diff.queue[i]->two->oid); refname = commit->util; if (anonymize) { @@ -797,14 +797,14 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) for (i = 0; i < info->nr; i++) { struct rev_cmdline_entry *e = info->rev + i; - unsigned char sha1[20]; + struct object_id oid; struct commit *commit; char *full_name; if (e->flags & UNINTERESTING) continue; - if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) + if (dwim_ref(e->name, strlen(e->name), oid.hash, &full_name) != 1) continue; if (refspecs) { @@ -828,7 +828,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) case OBJ_COMMIT: break; case OBJ_BLOB: - export_blob(commit->object.oid.hash); + export_blob(&commit->object.oid); continue; default: /* OBJ_TAG (nested tags) is already handled */ warning("Tag points to object of unexpected type %s, skipping.", @@ -912,7 +912,7 @@ static void import_marks(char *input_file) while (fgets(line, sizeof(line), f)) { uint32_t mark; char *line_end, *mark_end; - unsigned char sha1[20]; + struct object_id oid; struct object *object; struct commit *commit; enum object_type type; @@ -924,28 +924,28 @@ static void import_marks(char *input_file) mark = strtoumax(line + 1, &mark_end, 10); if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1)) + || *mark_end != ' ' || get_oid_hex(mark_end + 1, &oid)) die("corrupt mark line: %s", line); if (last_idnum < mark) last_idnum = mark; - type = sha1_object_info(sha1, NULL); + type = sha1_object_info(oid.hash, NULL); if (type < 0) - die("object not found: %s", sha1_to_hex(sha1)); + die("object not found: %s", oid_to_hex(&oid)); if (type != OBJ_COMMIT) /* only commits */ continue; - commit = lookup_commit(sha1); + commit = lookup_commit(oid.hash); if (!commit) - die("not a commit? can't happen: %s", sha1_to_hex(sha1)); + die("not a commit? can't happen: %s", oid_to_hex(&oid)); object = &commit->object; if (object->flags & SHOWN) - error("Object %s already has a mark", sha1_to_hex(sha1)); + error("Object %s already has a mark", oid_to_hex(&oid)); mark_object(object, mark); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index cfe9e447c2..2a1c1c213f 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -219,12 +219,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) * remote no-such-ref' would silently succeed without issuing * an error. */ - for (i = 0; i < nr_sought; i++) { - if (!sought[i] || sought[i]->matched) - continue; - error("no such remote ref %s", sought[i]->name); - ret = 1; - } + ret |= report_unmatched_refs(sought, nr_sought); while (ref) { printf("%s %s\n", diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index efab62fd85..6faa3c0d24 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -41,7 +41,7 @@ struct src_data { }; struct origin_data { - unsigned char sha1[20]; + struct object_id oid; unsigned is_local_branch:1; }; @@ -59,8 +59,8 @@ static struct string_list origins = STRING_LIST_INIT_DUP; struct merge_parents { int alloc, nr; struct merge_parent { - unsigned char given[20]; - unsigned char commit[20]; + struct object_id given; + struct object_id commit; unsigned char used; } *item; }; @@ -70,14 +70,14 @@ struct merge_parents { * hundreds of heads at a time anyway. */ static struct merge_parent *find_merge_parent(struct merge_parents *table, - unsigned char *given, - unsigned char *commit) + struct object_id *given, + struct object_id *commit) { int i; for (i = 0; i < table->nr; i++) { - if (given && hashcmp(table->item[i].given, given)) + if (given && oidcmp(&table->item[i].given, given)) continue; - if (commit && hashcmp(table->item[i].commit, commit)) + if (commit && oidcmp(&table->item[i].commit, commit)) continue; return &table->item[i]; } @@ -85,14 +85,14 @@ static struct merge_parent *find_merge_parent(struct merge_parents *table, } static void add_merge_parent(struct merge_parents *table, - unsigned char *given, - unsigned char *commit) + struct object_id *given, + struct object_id *commit) { if (table->nr && find_merge_parent(table, given, commit)) return; ALLOC_GROW(table->item, table->nr + 1, table->alloc); - hashcpy(table->item[table->nr].given, given); - hashcpy(table->item[table->nr].commit, commit); + oidcpy(&table->item[table->nr].given, given); + oidcpy(&table->item[table->nr].commit, commit); table->item[table->nr].used = 0; table->nr++; } @@ -106,30 +106,30 @@ static int handle_line(char *line, struct merge_parents *merge_parents) struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; - unsigned char sha1[20]; + struct object_id oid; - if (len < 43 || line[40] != '\t') + if (len < GIT_SHA1_HEXSZ + 3 || line[GIT_SHA1_HEXSZ] != '\t') return 1; - if (starts_with(line + 41, "not-for-merge")) + if (starts_with(line + GIT_SHA1_HEXSZ + 1, "not-for-merge")) return 0; - if (line[41] != '\t') + if (line[GIT_SHA1_HEXSZ + 1] != '\t') return 2; - i = get_sha1_hex(line, sha1); + i = get_oid_hex(line, &oid); if (i) return 3; - if (!find_merge_parent(merge_parents, sha1, NULL)) + if (!find_merge_parent(merge_parents, &oid, NULL)) return 0; /* subsumed by other parents */ origin_data = xcalloc(1, sizeof(struct origin_data)); - hashcpy(origin_data->sha1, sha1); + oidcpy(&origin_data->oid, &oid); if (line[len - 1] == '\n') line[len - 1] = 0; - line += 42; + line += GIT_SHA1_HEXSZ + 2; /* * At this point, line points at the beginning of comment e.g. @@ -338,10 +338,10 @@ static void shortlog(const char *name, struct string_list committers = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; - const unsigned char *sha1 = origin_data->sha1; + const struct object_id *oid = &origin_data->oid; int limit = opts->shortlog_len; - branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); + branch = deref_tag(parse_object(oid->hash), oid_to_hex(oid), GIT_SHA1_HEXSZ); if (!branch || branch->type != OBJ_COMMIT) return; @@ -531,7 +531,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) } static void find_merge_parents(struct merge_parents *result, - struct strbuf *in, unsigned char *head) + struct strbuf *in, struct object_id *head) { struct commit_list *parents; struct commit *head_commit; @@ -542,31 +542,31 @@ static void find_merge_parents(struct merge_parents *result, int len; char *p = in->buf + pos; char *newline = strchr(p, '\n'); - unsigned char sha1[20]; + struct object_id oid; struct commit *parent; struct object *obj; len = newline ? newline - p : strlen(p); pos += len + !!newline; - if (len < 43 || - get_sha1_hex(p, sha1) || - p[40] != '\t' || - p[41] != '\t') + if (len < GIT_SHA1_HEXSZ + 3 || + get_oid_hex(p, &oid) || + p[GIT_SHA1_HEXSZ] != '\t' || + p[GIT_SHA1_HEXSZ + 1] != '\t') continue; /* skip not-for-merge */ /* * Do not use get_merge_parent() here; we do not have * "name" here and we do not want to contaminate its * util field yet. */ - obj = parse_object(sha1); + obj = parse_object(oid.hash); parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT); if (!parent) continue; commit_list_insert(parent, &parents); - add_merge_parent(result, obj->oid.hash, parent->object.oid.hash); + add_merge_parent(result, &obj->oid, &parent->object.oid); } - head_commit = lookup_commit(head); + head_commit = lookup_commit(head->hash); if (head_commit) commit_list_insert(head_commit, &parents); parents = reduce_heads(parents); @@ -574,7 +574,7 @@ static void find_merge_parents(struct merge_parents *result, while (parents) { struct commit *cmit = pop_commit(&parents); for (i = 0; i < result->nr; i++) - if (!hashcmp(result->item[i].commit, cmit->object.oid.hash)) + if (!oidcmp(&result->item[i].commit, &cmit->object.oid)) result->item[i].used = 1; } @@ -592,7 +592,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct fmt_merge_msg_opts *opts) { int i = 0, pos = 0; - unsigned char head_sha1[20]; + struct object_id head_oid; const char *current_branch; void *current_branch_to_free; struct merge_parents merge_parents; @@ -601,13 +601,13 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, /* get current branch */ current_branch = current_branch_to_free = - resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); + resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL); if (!current_branch) die("No current branch"); if (starts_with(current_branch, "refs/heads/")) current_branch += 11; - find_merge_parents(&merge_parents, in, head_sha1); + find_merge_parents(&merge_parents, in, &head_oid); /* get a line */ while (pos < in->len) { @@ -633,7 +633,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct commit *head; struct rev_info rev; - head = lookup_commit_or_die(head_sha1, "HEAD"); + head = lookup_commit_or_die(head_oid.hash, "HEAD"); init_revisions(&rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; diff --git a/builtin/fsck.c b/builtin/fsck.c index 1a5caccd0f..f76e4163ab 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -396,13 +396,13 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, static int default_refs; -static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, +static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, unsigned long timestamp) { struct object *obj; - if (!is_null_sha1(sha1)) { - obj = lookup_object(sha1); + if (!is_null_oid(oid)) { + obj = lookup_object(oid->hash); if (obj && (obj->flags & HAS_OBJ)) { if (timestamp && name_objects) add_decoration(fsck_walk_options.object_names, @@ -411,13 +411,13 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, obj->used = 1; mark_object_reachable(obj); } else { - error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1)); + error("%s: invalid reflog entry %s", refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; } } } -static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, +static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -425,10 +425,10 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (verbose) fprintf(stderr, "Checking reflog %s->%s\n", - sha1_to_hex(osha1), sha1_to_hex(nsha1)); + oid_to_hex(ooid), oid_to_hex(noid)); - fsck_handle_reflog_sha1(refname, osha1, 0); - fsck_handle_reflog_sha1(refname, nsha1, timestamp); + fsck_handle_reflog_oid(refname, ooid, 0); + fsck_handle_reflog_oid(refname, noid, timestamp); return 0; } @@ -491,7 +491,7 @@ static void get_default_heads(void) } } -static struct object *parse_loose_object(const unsigned char *sha1, +static struct object *parse_loose_object(const struct object_id *oid, const char *path) { struct object *obj; @@ -500,27 +500,27 @@ static struct object *parse_loose_object(const unsigned char *sha1, unsigned long size; int eaten; - if (read_loose_object(path, sha1, &type, &size, &contents) < 0) + if (read_loose_object(path, oid->hash, &type, &size, &contents) < 0) return NULL; if (!contents && type != OBJ_BLOB) die("BUG: read_loose_object streamed a non-blob"); - obj = parse_object_buffer(sha1, type, size, contents, &eaten); + obj = parse_object_buffer(oid->hash, type, size, contents, &eaten); if (!eaten) free(contents); return obj; } -static int fsck_loose(const unsigned char *sha1, const char *path, void *data) +static int fsck_loose(const struct object_id *oid, const char *path, void *data) { - struct object *obj = parse_loose_object(sha1, path); + struct object *obj = parse_loose_object(oid, path); if (!obj) { errors_found |= ERROR_OBJECT; error("%s: object corrupt or missing: %s", - sha1_to_hex(sha1), path); + oid_to_hex(oid), path); return 0; /* keep checking other objects */ } @@ -619,26 +619,26 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } -static void mark_object_for_connectivity(const unsigned char *sha1) +static void mark_object_for_connectivity(const struct object_id *oid) { - struct object *obj = lookup_unknown_object(sha1); + struct object *obj = lookup_unknown_object(oid->hash); obj->flags |= HAS_OBJ; } -static int mark_loose_for_connectivity(const unsigned char *sha1, +static int mark_loose_for_connectivity(const struct object_id *oid, const char *path, void *data) { - mark_object_for_connectivity(sha1); + mark_object_for_connectivity(oid); return 0; } -static int mark_packed_for_connectivity(const unsigned char *sha1, +static int mark_packed_for_connectivity(const struct object_id *oid, struct packed_git *pack, uint32_t pos, void *data) { - mark_object_for_connectivity(sha1); + mark_object_for_connectivity(oid); return 0; } diff --git a/builtin/gc.c b/builtin/gc.c index a2b9e8924e..c2c61a57bb 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -64,17 +64,6 @@ static void report_pack_garbage(unsigned seen_bits, const char *path) string_list_append(&pack_garbage, path); } -static void git_config_date_string(const char *key, const char **output) -{ - if (git_config_get_string_const(key, output)) - return; - if (strcmp(*output, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(*output) >= now) - git_die_config(key, _("Invalid %s: '%s'"), key, *output); - } -} - static void process_log_file(void) { struct stat st; @@ -131,9 +120,9 @@ static void gc_config(void) git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); - git_config_date_string("gc.pruneexpire", &prune_expire); - git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire); - git_config_date_string("gc.logexpiry", &gc_log_expire); + git_config_get_expiry("gc.pruneexpire", &prune_expire); + git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); + git_config_get_expiry("gc.logexpiry", &gc_log_expire); git_config(git_default_config, NULL); } diff --git a/builtin/grep.c b/builtin/grep.c index 2c727ef499..3f3efa95de 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -294,17 +294,17 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) return st; } -static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) +static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size) { void *data; grep_read_lock(); - data = read_sha1_file(sha1, type, size); + data = read_sha1_file(oid->hash, type, size); grep_read_unlock(); return data; } -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, +static int grep_oid(struct grep_opt *opt, const struct object_id *oid, const char *filename, int tree_name_len, const char *path) { @@ -323,7 +323,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); strbuf_release(&pathbuf); return 0; } else @@ -332,7 +332,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); strbuf_release(&pathbuf); hit = grep_source(opt, &gs); @@ -538,7 +538,7 @@ static int grep_submodule_launch(struct grep_opt *opt, int status, i; const char *end_of_base; const char *name; - struct work_item *w = opt->output_priv; + struct strbuf child_output = STRBUF_INIT; end_of_base = strchr(gs->name, ':'); if (gs->identifier && end_of_base) @@ -593,14 +593,16 @@ static int grep_submodule_launch(struct grep_opt *opt, * child process. A '0' indicates a hit, a '1' indicates no hit and * anything else is an error. */ - status = capture_command(&cp, &w->out, 0); + status = capture_command(&cp, &child_output, 0); if (status && (status != 1)) { /* flush the buffer */ - write_or_die(1, w->out.buf, w->out.len); + write_or_die(1, child_output.buf, child_output.len); die("process for submodule '%s' failed with exit code: %d", gs->name, status); } + opt->output(opt, child_output.buf, child_output.len); + strbuf_release(&child_output); /* invert the return code to make a hit equal to 1 */ return !status; } @@ -616,7 +618,7 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, { if (!is_submodule_initialized(path)) return 0; - if (!is_submodule_populated(path)) { + if (!is_submodule_populated_gently(path, NULL)) { /* * If searching history, check for the presense of the * submodule's gitdir before skipping the submodule. @@ -641,19 +643,14 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, } else #endif { - struct work_item w; + struct grep_source gs; int hit; - grep_source_init(&w.source, GREP_SOURCE_SUBMODULE, + grep_source_init(&gs, GREP_SOURCE_SUBMODULE, filename, path, sha1); - strbuf_init(&w.out, 0); - opt->output_priv = &w; - hit = grep_submodule_launch(opt, &w.source); + hit = grep_submodule_launch(opt, &gs); - write_or_die(1, w.out.buf, w.out.len); - - grep_source_clear(&w.source); - strbuf_release(&w.out); + grep_source_clear(&gs); return hit; } } @@ -690,7 +687,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, ce_skip_worktree(ce)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; - hit |= grep_sha1(opt, ce->oid.hash, ce->name, + hit |= grep_oid(opt, &ce->oid, ce->name, 0, ce->name); } else { hit |= grep_file(opt, ce->name); @@ -750,7 +747,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, + hit |= grep_oid(opt, entry.oid, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@ -758,7 +755,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_sha1_file(entry.oid->hash, &type, &size); + data = lock_and_read_oid_file(entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(entry.oid)); @@ -787,7 +784,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name, const char *path) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->oid.hash, name, 0, path); + return grep_oid(opt, &obj->oid, name, 0, path); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -967,6 +964,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int dummy; int use_index = 1; int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED; + int allow_revs; struct option options[] = { OPT_BOOL(0, "cached", &cached, @@ -1149,26 +1147,69 @@ int cmd_grep(int argc, const char **argv, const char *prefix) compile_grep_patterns(&opt); - /* Check revs and then paths */ + /* + * We have to find "--" in a separate pass, because its presence + * influences how we will parse arguments that come before it. + */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + seen_dashdash = 1; + break; + } + } + + /* + * Resolve any rev arguments. If we have a dashdash, then everything up + * to it must resolve as a rev. If not, then we stop at the first + * non-rev and assume everything else is a path. + */ + allow_revs = use_index && !untracked; for (i = 0; i < argc; i++) { const char *arg = argv[i]; - unsigned char sha1[20]; + struct object_id oid; struct object_context oc; - /* Is it a rev? */ - if (!get_sha1_with_context(arg, 0, sha1, &oc)) { - struct object *object = parse_object_or_die(sha1, arg); - if (!seen_dashdash) - verify_non_filename(prefix, arg); - add_object_array_with_path(object, arg, &list, oc.mode, oc.path); - continue; - } + struct object *object; + if (!strcmp(arg, "--")) { i++; - seen_dashdash = 1; + break; } - break; + + if (!allow_revs) { + if (seen_dashdash) + die(_("--no-index or --untracked cannot be used with revs")); + break; + } + + if (get_sha1_with_context(arg, 0, oid.hash, &oc)) { + if (seen_dashdash) + die(_("unable to resolve revision: %s"), arg); + break; + } + + object = parse_object_or_die(oid.hash, arg); + if (!seen_dashdash) + verify_non_filename(prefix, arg); + add_object_array_with_path(object, arg, &list, oc.mode, oc.path); } + /* + * Anything left over is presumed to be a path. But in the non-dashdash + * "do what I mean" case, we verify and complain when that isn't true. + */ + if (!seen_dashdash) { + int j; + for (j = i; j < argc; j++) + verify_filename(prefix, argv[j], j == i && allow_revs); + } + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD | + (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), + prefix, argv + i); + pathspec.max_depth = opt.max_depth; + pathspec.recursive = 1; + #ifndef NO_PTHREADS if (list.nr || cached || show_in_pager) num_threads = 0; @@ -1190,20 +1231,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } #endif - /* The rest are paths */ - if (!seen_dashdash) { - int j; - for (j = i; j < argc; j++) - verify_filename(prefix, argv[j], j == i); - } - - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0), - prefix, argv + i); - pathspec.max_depth = opt.max_depth; - pathspec.recursive = 1; - if (recurse_submodules) { gitmodules_config(); compile_submodule_options(&opt, &pathspec, cached, untracked, @@ -1245,8 +1272,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; - if (list.nr) - die(_("--no-index or --untracked cannot be used with revs.")); hit = grep_directory(&opt, &pathspec, use_exclude, use_index); } else if (0 <= opt_exclude) { die(_("--[no-]exclude-standard cannot be used for tracked contents.")); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 9028e1fdcc..bbeaf20bcc 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -102,7 +102,6 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) OPT_END() }; int i; - int prefix_length = -1; const char *errstr = NULL; argc = parse_options(argc, argv, NULL, hash_object_options, @@ -113,9 +112,8 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) else prefix = setup_git_directory_gently(&nongit); - prefix_length = prefix ? strlen(prefix) : 0; if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); + vpath = xstrdup(prefix_filename(prefix, vpath)); git_config(git_default_config, NULL); @@ -144,11 +142,13 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) for (i = 0 ; i < argc; i++) { const char *arg = argv[i]; + char *to_free = NULL; - if (0 <= prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); + if (prefix) + arg = to_free = prefix_filename(prefix, arg); hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg, flags, literally); + free(to_free); } if (stdin_paths) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index f4b87c6c9f..88d205f858 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1386,7 +1386,9 @@ static void final(const char *final_pack_name, const char *curr_pack_name, unsigned char *sha1) { const char *report = "pack"; - char name[PATH_MAX]; + struct strbuf pack_name = STRBUF_INIT; + struct strbuf index_name = STRBUF_INIT; + struct strbuf keep_name_buf = STRBUF_INIT; int err; if (!from_stdin) { @@ -1402,14 +1404,13 @@ static void final(const char *final_pack_name, const char *curr_pack_name, int keep_fd, keep_msg_len = strlen(keep_msg); if (!keep_name) - keep_fd = odb_pack_keep(name, sizeof(name), sha1); - else - keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600); + keep_name = odb_pack_name(&keep_name_buf, sha1, "keep"); + keep_fd = odb_pack_keep(keep_name); if (keep_fd < 0) { if (errno != EEXIST) die_errno(_("cannot write keep file '%s'"), - keep_name ? keep_name : name); + keep_name); } else { if (keep_msg_len > 0) { write_or_die(keep_fd, keep_msg, keep_msg_len); @@ -1417,28 +1418,22 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } if (close(keep_fd) != 0) die_errno(_("cannot close written keep file '%s'"), - keep_name ? keep_name : name); + keep_name); report = "keep"; } } if (final_pack_name != curr_pack_name) { - if (!final_pack_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", - get_object_directory(), sha1_to_hex(sha1)); - final_pack_name = name; - } + if (!final_pack_name) + final_pack_name = odb_pack_name(&pack_name, sha1, "pack"); if (finalize_object_file(curr_pack_name, final_pack_name)) die(_("cannot store pack file")); } else if (from_stdin) chmod(final_pack_name, 0444); if (final_index_name != curr_index_name) { - if (!final_index_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", - get_object_directory(), sha1_to_hex(sha1)); - final_index_name = name; - } + if (!final_index_name) + final_index_name = odb_pack_name(&index_name, sha1, "idx"); if (finalize_object_file(curr_index_name, final_index_name)) die(_("cannot store index file")); } else @@ -1464,6 +1459,10 @@ static void final(const char *final_pack_name, const char *curr_pack_name, input_offset += err; } } + + strbuf_release(&index_name); + strbuf_release(&pack_name); + strbuf_release(&keep_name_buf); } static int git_index_pack_config(const char *k, const char *v, void *cb) diff --git a/builtin/log.c b/builtin/log.c index 55d20cc2d8..670229cbb4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -989,8 +989,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; - log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, - &need_8bit_cte); + log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte); for (i = 0; !need_8bit_cte && i < nr; i++) { const char *buf = get_commit_buffer(list[i], NULL); @@ -1005,6 +1004,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; + pp.rev = rev; + pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); @@ -1083,8 +1084,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) if (!output_directory) return prefix; - return xstrdup(prefix_filename(prefix, outdir_offset, - output_directory)); + return prefix_filename(prefix, output_directory); } static const char * const builtin_format_patch_usage[] = { diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index e3b62f2fc7..cfb667a594 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -11,13 +11,6 @@ static const char mailinfo_usage[] = "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; -static char *prefix_copy(const char *prefix, const char *filename) -{ - if (!prefix || is_absolute_path(filename)) - return xstrdup(filename); - return xstrdup(prefix_filename(prefix, strlen(prefix), filename)); -} - int cmd_mailinfo(int argc, const char **argv, const char *prefix) { const char *def_charset; @@ -60,8 +53,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) mi.input = stdin; mi.output = stdout; - msgfile = prefix_copy(prefix, argv[1]); - patchfile = prefix_copy(prefix, argv[2]); + msgfile = prefix_filename(prefix, argv[1]); + patchfile = prefix_filename(prefix, argv[2]); status = !!mailinfo(&mi, msgfile, patchfile); clear_mailinfo(&mi); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index b572a37c26..cfe2a796f8 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -36,12 +36,12 @@ static const char * const merge_base_usage[] = { static struct commit *get_commit_reference(const char *arg) { - unsigned char revkey[20]; + struct object_id revkey; struct commit *r; - if (get_sha1(arg, revkey)) + if (get_oid(arg, &revkey)) die("Not a valid object name %s", arg); - r = lookup_commit_reference(revkey); + r = lookup_commit_reference(revkey.hash); if (!r) die("Not a valid commit name %s", arg); @@ -113,14 +113,14 @@ struct rev_collect { unsigned int initial : 1; }; -static void add_one_commit(unsigned char *sha1, struct rev_collect *revs) +static void add_one_commit(struct object_id *oid, struct rev_collect *revs) { struct commit *commit; - if (is_null_sha1(sha1)) + if (is_null_oid(oid)) return; - commit = lookup_commit(sha1); + commit = lookup_commit(oid->hash); if (!commit || (commit->object.flags & TMP_MARK) || parse_commit(commit)) @@ -131,7 +131,7 @@ static void add_one_commit(unsigned char *sha1, struct rev_collect *revs) commit->object.flags |= TMP_MARK; } -static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, +static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *ident, unsigned long timestamp, int tz, const char *message, void *cbdata) { @@ -139,15 +139,15 @@ static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (revs->initial) { revs->initial = 0; - add_one_commit(osha1, revs); + add_one_commit(ooid, revs); } - add_one_commit(nsha1, revs); + add_one_commit(noid, revs); return 0; } static int handle_fork_point(int argc, const char **argv) { - unsigned char sha1[20]; + struct object_id oid; char *refname; const char *commitname; struct rev_collect revs; @@ -155,7 +155,7 @@ static int handle_fork_point(int argc, const char **argv) struct commit_list *bases; int i, ret = 0; - switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &refname)) { + switch (dwim_ref(argv[0], strlen(argv[0]), oid.hash, &refname)) { case 0: die("No such ref: '%s'", argv[0]); case 1: @@ -165,16 +165,16 @@ static int handle_fork_point(int argc, const char **argv) } commitname = (argc == 2) ? argv[1] : "HEAD"; - if (get_sha1(commitname, sha1)) + if (get_oid(commitname, &oid)) die("Not a valid object name: '%s'", commitname); - derived = lookup_commit_reference(sha1); + derived = lookup_commit_reference(oid.hash); memset(&revs, 0, sizeof(revs)); revs.initial = 1; for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); - if (!revs.nr && !get_sha1(refname, sha1)) - add_one_commit(sha1, &revs); + if (!revs.nr && !get_oid(refname, &oid)) + add_one_commit(&oid, &revs); for (i = 0; i < revs.nr; i++) revs.commit[i]->object.flags &= ~TMP_MARK; diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 13e22a2f0b..47dde7c39c 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,7 +28,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; - int prefixlen = 0; struct option options[] = { OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")), OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3), @@ -65,15 +64,19 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) return error_errno("failed to redirect stderr to /dev/null"); } - if (prefix) - prefixlen = strlen(prefix); - for (i = 0; i < 3; i++) { - const char *fname = prefix_filename(prefix, prefixlen, argv[i]); + char *fname; + int ret; + if (!names[i]) names[i] = argv[i]; - if (read_mmfile(mmfs + i, fname)) + + fname = prefix_filename(prefix, argv[i]); + ret = read_mmfile(mmfs + i, fname); + free(fname); + if (ret) return -1; + if (mmfs[i].size > MAX_XDIFF_SIZE || buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s", @@ -90,7 +93,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret >= 0) { const char *filename = argv[0]; - const char *fpath = prefix_filename(prefix, prefixlen, argv[0]); + char *fpath = prefix_filename(prefix, argv[0]); FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) @@ -102,6 +105,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) else if (fclose(f)) ret = error_errno("Could not close %s", filename); free(result.ptr); + free(fpath); } if (ret > 127) diff --git a/builtin/merge.c b/builtin/merge.c index a96d4fb501..7554b8d412 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -244,7 +244,7 @@ static void drop_save(void) unlink(git_path_merge_mode()); } -static int save_state(unsigned char *stash) +static int save_state(struct object_id *stash) { int len; struct child_process cp = CHILD_PROCESS_INIT; @@ -265,7 +265,7 @@ static int save_state(unsigned char *stash) else if (!len) /* no changes */ return -1; strbuf_setlen(&buffer, buffer.len-1); - if (get_sha1(buffer.buf, stash)) + if (get_oid(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); return 0; } @@ -305,18 +305,18 @@ static void reset_hard(unsigned const char *sha1, int verbose) die(_("read-tree failed")); } -static void restore_state(const unsigned char *head, - const unsigned char *stash) +static void restore_state(const struct object_id *head, + const struct object_id *stash) { struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; - if (is_null_sha1(stash)) + if (is_null_oid(stash)) return; - reset_hard(head, 1); + reset_hard(head->hash, 1); - args[2] = sha1_to_hex(stash); + args[2] = oid_to_hex(stash); /* * It is OK to ignore error here, for example when there was @@ -376,10 +376,10 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead static void finish(struct commit *head_commit, struct commit_list *remoteheads, - const unsigned char *new_head, const char *msg) + const struct object_id *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; - const unsigned char *head = head_commit->object.oid.hash; + const struct object_id *head = &head_commit->object.oid; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@ -397,7 +397,7 @@ static void finish(struct commit *head_commit, else { const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", - new_head, head, 0, + new_head->hash, head->hash, 0, UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the @@ -416,7 +416,7 @@ static void finish(struct commit *head_commit, DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; diff_setup_done(&opts); - diff_tree_sha1(head, new_head, "", &opts); + diff_tree_sha1(head->hash, new_head->hash, "", &opts); diffcore_std(&opts); diff_flush(&opts); } @@ -431,35 +431,35 @@ static void finish(struct commit *head_commit, static void merge_name(const char *remote, struct strbuf *msg) { struct commit *remote_head; - unsigned char branch_head[20]; + struct object_id branch_head; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; char *found_ref; int len, early; - strbuf_branchname(&bname, remote); + strbuf_branchname(&bname, remote, 0); remote = bname.buf; - memset(branch_head, 0, sizeof(branch_head)); + oidclr(&branch_head); remote_head = get_merge_parent(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); - if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { + if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) { if (starts_with(found_ref, "refs/heads/")) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } if (starts_with(found_ref, "refs/tags/")) { strbuf_addf(msg, "%s\t\ttag '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } if (starts_with(found_ref, "refs/remotes/")) { strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", - sha1_to_hex(branch_head), remote); + oid_to_hex(&branch_head), remote); goto cleanup; } } @@ -590,8 +590,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_diff_ui_config(k, v, cb); } -static int read_tree_trivial(unsigned char *common, unsigned char *head, - unsigned char *one) +static int read_tree_trivial(struct object_id *common, struct object_id *head, + struct object_id *one) { int i, nr_trees = 0; struct tree *trees[MAX_UNPACK_TREES]; @@ -606,13 +606,13 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head, opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; - trees[nr_trees] = parse_tree_indirect(common); + trees[nr_trees] = parse_tree_indirect(common->hash); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(head); + trees[nr_trees] = parse_tree_indirect(head->hash); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(one); + trees[nr_trees] = parse_tree_indirect(one->hash); if (!trees[nr_trees++]) return -1; opts.fn = threeway_merge; @@ -626,9 +626,9 @@ static int read_tree_trivial(unsigned char *common, unsigned char *head, return 0; } -static void write_tree_trivial(unsigned char *sha1) +static void write_tree_trivial(struct object_id *oid) { - if (write_cache_as_tree(sha1, 0, NULL)) + if (write_cache_as_tree(oid->hash, 0, NULL)) die(_("git write-tree failed to write a tree")); } @@ -781,7 +781,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { - unsigned char result_tree[20], result_commit[20]; + struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; static struct lock_file lock; @@ -792,15 +792,15 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) return error(_("Unable to write index.")); rollback_lock_file(&lock); - write_tree_trivial(result_tree); + write_tree_trivial(&result_tree); printf(_("Wonderful.\n")); pptr = commit_list_append(head, pptr); pptr = commit_list_append(remoteheads->item, pptr); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, - result_commit, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents, + result_commit.hash, NULL, sign_commit)) die(_("failed to write commit object")); - finish(head, remoteheads, result_commit, "In-index merge"); + finish(head, remoteheads, &result_commit, "In-index merge"); drop_save(); return 0; } @@ -809,12 +809,12 @@ static int finish_automerge(struct commit *head, int head_subsumed, struct commit_list *common, struct commit_list *remoteheads, - unsigned char *result_tree, + struct object_id *result_tree, const char *wt_strategy) { struct commit_list *parents = NULL; struct strbuf buf = STRBUF_INIT; - unsigned char result_commit[20]; + struct object_id result_commit; free_commit_list(common); parents = remoteheads; @@ -822,11 +822,11 @@ static int finish_automerge(struct commit *head, commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, - result_commit, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents, + result_commit.hash, NULL, sign_commit)) die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); - finish(head, remoteheads, result_commit, buf.buf); + finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; @@ -854,18 +854,18 @@ static int suggest_conflicts(void) } static struct commit *is_old_style_invocation(int argc, const char **argv, - const unsigned char *head) + const struct object_id *head) { struct commit *second_token = NULL; if (argc > 2) { - unsigned char second_sha1[20]; + struct object_id second_oid; - if (get_sha1(argv[1], second_sha1)) + if (get_oid(argv[1], &second_oid)) return NULL; - second_token = lookup_commit_reference_gently(second_sha1, 0); + second_token = lookup_commit_reference_gently(second_oid.hash, 0); if (!second_token) die(_("'%s' is not a commit"), argv[1]); - if (hashcmp(second_token->object.oid.hash, head)) + if (oidcmp(&second_token->object.oid, head)) return NULL; } return second_token; @@ -1038,7 +1038,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge die_errno(_("could not close '%s'"), filename); for (pos = 0; pos < merge_names->len; pos = npos) { - unsigned char sha1[20]; + struct object_id oid; char *ptr; struct commit *commit; @@ -1048,16 +1048,16 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge else npos = merge_names->len; - if (npos - pos < 40 + 2 || - get_sha1_hex(merge_names->buf + pos, sha1)) + if (npos - pos < GIT_SHA1_HEXSZ + 2 || + get_oid_hex(merge_names->buf + pos, &oid)) commit = NULL; /* bad */ - else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2)) + else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2)) continue; /* not-for-merge */ else { - char saved = merge_names->buf[pos + 40]; - merge_names->buf[pos + 40] = '\0'; + char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ]; + merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0'; commit = get_merge_parent(merge_names->buf + pos); - merge_names->buf[pos + 40] = saved; + merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved; } if (!commit) { if (ptr) @@ -1117,9 +1117,7 @@ static struct commit_list *collect_parents(struct commit *head_commit, int cmd_merge(int argc, const char **argv, const char *prefix) { - unsigned char result_tree[20]; - unsigned char stash[20]; - unsigned char head_sha1[20]; + struct object_id result_tree, stash, head_oid; struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; @@ -1138,13 +1136,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL); if (branch && starts_with(branch, "refs/heads/")) branch += 11; - if (!branch || is_null_sha1(head_sha1)) + if (!branch || is_null_oid(&head_oid)) head_commit = NULL; else - head_commit = lookup_commit_or_die(head_sha1, "HEAD"); + head_commit = lookup_commit_or_die(head_oid.hash, "HEAD"); init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@ -1242,7 +1240,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ - unsigned char *remote_head_sha1; + struct object_id *remote_head_oid; if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) @@ -1254,9 +1252,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("%s - not something we can merge"), argv[0]); if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); - remote_head_sha1 = remoteheads->item->object.oid.hash; - read_empty(remote_head_sha1, 0); - update_ref("initial pull", "HEAD", remote_head_sha1, + remote_head_oid = &remoteheads->item->object.oid; + read_empty(remote_head_oid->hash, 0); + update_ref("initial pull", "HEAD", remote_head_oid->hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; } @@ -1270,7 +1268,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * additional safety measure to check for it. */ if (!have_message && - is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) { + is_old_style_invocation(argc, argv, &head_commit->object.oid)) { warning("old-style 'git merge <msg> HEAD <commit>' is deprecated."); strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; @@ -1422,7 +1420,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } - finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf); + finish(head_commit, remoteheads, &commit->object.oid, msg.buf); drop_save(); goto done; } else if (!remoteheads->next && common->next) @@ -1441,9 +1439,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); - if (!read_tree_trivial(common->item->object.oid.hash, - head_commit->object.oid.hash, - remoteheads->item->object.oid.hash)) { + if (!read_tree_trivial(&common->item->object.oid, + &head_commit->object.oid, + &remoteheads->item->object.oid)) { ret = merge_trivial(head_commit, remoteheads); goto done; } @@ -1495,14 +1493,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) /* * Stash away the local changes so that we can try more than one. */ - save_state(stash)) - hashclr(stash); + save_state(&stash)) + oidclr(&stash); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@ -1547,7 +1545,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } /* Automerge succeeded. */ - write_tree_trivial(result_tree); + write_tree_trivial(&result_tree); automerge_was_ok = 1; break; } @@ -1559,7 +1557,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (automerge_was_ok) { ret = finish_automerge(head_commit, head_subsumed, common, remoteheads, - result_tree, wt_strategy); + &result_tree, wt_strategy); goto done; } @@ -1568,7 +1566,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * it up. */ if (!best_strategy) { - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); if (use_strategies_nr > 1) fprintf(stderr, _("No merge strategy handled the merge.\n")); @@ -1581,7 +1579,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ; /* We already have its result in the working tree. */ else { printf(_("Rewinding the tree to pristine...\n")); - restore_state(head_commit->object.oid.hash, stash); + restore_state(&head_commit->object.oid, &stash); printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, diff --git a/builtin/name-rev.c b/builtin/name-rev.c index cd89d48b65..8bdc3eaa6f 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -108,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) struct name_ref_data { int tags_only; int name_only; - const char *ref_filter; + struct string_list ref_filters; + struct string_list exclude_filters; }; static struct tip_table { @@ -150,18 +151,49 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; - if (data->ref_filter) { - switch (subpath_matches(path, data->ref_filter)) { - case -1: /* did not match */ - return 0; - case 0: /* matched fully */ - break; - default: /* matched subpath */ - can_abbreviate_output = 1; - break; + if (data->exclude_filters.nr) { + struct string_list_item *item; + + for_each_string_list_item(item, &data->exclude_filters) { + if (subpath_matches(path, item->string) >= 0) + return 0; } } + if (data->ref_filters.nr) { + struct string_list_item *item; + int matched = 0; + + /* See if any of the patterns match. */ + for_each_string_list_item(item, &data->ref_filters) { + /* + * Check all patterns even after finding a match, so + * that we can see if a match with a subpath exists. + * When a user asked for 'refs/tags/v*' and 'v1.*', + * both of which match, the user is showing her + * willingness to accept a shortened output by having + * the 'v1.*' in the acceptable refnames, so we + * shouldn't stop when seeing 'refs/tags/v1.4' matches + * 'refs/tags/v*'. We should show it as 'v1.4'. + */ + switch (subpath_matches(path, item->string)) { + case -1: /* did not match */ + break; + case 0: /* matched fully */ + matched = 1; + break; + default: /* matched subpath */ + matched = 1; + can_abbreviate_output = 1; + break; + } + } + + /* If none of the patterns matched, stop now */ + if (!matched) + return 0; + } + add_to_tip_table(oid->hash, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { @@ -306,12 +338,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; - struct name_ref_data data = { 0, 0, NULL }; + struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), - OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"), + OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"), N_("only use refs matching <pattern>")), + OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"), + N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), diff --git a/builtin/notes.c b/builtin/notes.c index 5248a9bad8..0513f7455d 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -681,9 +681,9 @@ static int merge_abort(struct notes_merge_options *o) * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. */ - if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0)) ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL")); - if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF)) ret += error(_("failed to delete ref NOTES_MERGE_REF")); if (notes_merge_abort(o)) ret += error(_("failed to remove 'git notes merge' worktree")); @@ -693,7 +693,7 @@ static int merge_abort(struct notes_merge_options *o) static int merge_commit(struct notes_merge_options *o) { struct strbuf msg = STRBUF_INIT; - unsigned char sha1[20], parent_sha1[20]; + struct object_id oid, parent_oid; struct notes_tree *t; struct commit *partial; struct pretty_print_context pretty_ctx; @@ -705,27 +705,27 @@ static int merge_commit(struct notes_merge_options *o) * and target notes ref from .git/NOTES_MERGE_REF. */ - if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + if (get_oid("NOTES_MERGE_PARTIAL", &oid)) die(_("failed to read ref NOTES_MERGE_PARTIAL")); - else if (!(partial = lookup_commit_reference(sha1))) + else if (!(partial = lookup_commit_reference(oid.hash))) die(_("could not find commit from NOTES_MERGE_PARTIAL.")); else if (parse_commit(partial)) die(_("could not parse commit from NOTES_MERGE_PARTIAL.")); if (partial->parents) - hashcpy(parent_sha1, partial->parents->item->object.oid.hash); + oidcpy(&parent_oid, &partial->parents->item->object.oid); else - hashclr(parent_sha1); + oidclr(&parent_oid); t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, oid.hash, NULL); if (!o->local_ref) die(_("failed to resolve NOTES_MERGE_REF")); - if (notes_merge_commit(o, t, partial, sha1)) + if (notes_merge_commit(o, t, partial, oid.hash)) die(_("failed to finalize notes merge")); /* Reuse existing commit message in reflog message */ @@ -733,8 +733,8 @@ static int merge_commit(struct notes_merge_options *o) format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); strbuf_insert(&msg, 0, "notes: ", 7); - update_ref(msg.buf, o->local_ref, sha1, - is_null_sha1(parent_sha1) ? NULL : parent_sha1, + update_ref(msg.buf, o->local_ref, oid.hash, + is_null_oid(&parent_oid) ? NULL : parent_oid.hash, 0, UPDATE_REFS_DIE_ON_ERR); free_notes(t); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 8841f8b366..16517f2637 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -894,24 +894,15 @@ static void write_pack_file(void) written, nr_result); } -static void setup_delta_attr_check(struct git_attr_check *check) -{ - static struct git_attr *attr_delta; - - if (!attr_delta) - attr_delta = git_attr("delta"); - - check[0].attr = attr_delta; -} - static int no_try_delta(const char *path) { - struct git_attr_check check[1]; + static struct attr_check *check; - setup_delta_attr_check(check); - if (git_check_attr(path, ARRAY_SIZE(check), check)) + if (!check) + check = attr_check_initl("delta", NULL); + if (git_check_attr(path, check)) return 0; - if (ATTR_FALSE(check->value)) + if (ATTR_FALSE(check->items[0].value)) return 1; return 0; } @@ -1539,6 +1530,8 @@ static int pack_offset_sort(const void *_a, const void *_b) * 2. Updating our size/type to the non-delta representation. These were * either not recorded initially (size) or overwritten with the delta type * (type) when check_object() decided to reuse the delta. + * + * 3. Resetting our delta depth, as we are now a base object. */ static void drop_reused_delta(struct object_entry *entry) { @@ -1552,6 +1545,7 @@ static void drop_reused_delta(struct object_entry *entry) p = &(*p)->delta_sibling; } entry->delta = NULL; + entry->depth = 0; oi.sizep = &entry->size; oi.typep = &entry->type; @@ -1570,39 +1564,123 @@ static void drop_reused_delta(struct object_entry *entry) * Follow the chain of deltas from this entry onward, throwing away any links * that cause us to hit a cycle (as determined by the DFS state flags in * the entries). + * + * We also detect too-long reused chains that would violate our --depth + * limit. */ static void break_delta_chains(struct object_entry *entry) { - /* If it's not a delta, it can't be part of a cycle. */ - if (!entry->delta) { - entry->dfs_state = DFS_DONE; - return; - } + /* + * The actual depth of each object we will write is stored as an int, + * as it cannot exceed our int "depth" limit. But before we break + * changes based no that limit, we may potentially go as deep as the + * number of objects, which is elsewhere bounded to a uint32_t. + */ + uint32_t total_depth; + struct object_entry *cur, *next; + + for (cur = entry, total_depth = 0; + cur; + cur = cur->delta, total_depth++) { + if (cur->dfs_state == DFS_DONE) { + /* + * We've already seen this object and know it isn't + * part of a cycle. We do need to append its depth + * to our count. + */ + total_depth += cur->depth; + break; + } + + /* + * We break cycles before looping, so an ACTIVE state (or any + * other cruft which made its way into the state variable) + * is a bug. + */ + if (cur->dfs_state != DFS_NONE) + die("BUG: confusing delta dfs state in first pass: %d", + cur->dfs_state); - switch (entry->dfs_state) { - case DFS_NONE: /* - * This is the first time we've seen the object. We mark it as - * part of the active potential cycle and recurse. + * Now we know this is the first time we've seen the object. If + * it's not a delta, we're done traversing, but we'll mark it + * done to save time on future traversals. */ - entry->dfs_state = DFS_ACTIVE; - break_delta_chains(entry->delta); - entry->dfs_state = DFS_DONE; - break; + if (!cur->delta) { + cur->dfs_state = DFS_DONE; + break; + } - case DFS_DONE: - /* object already examined, and not part of a cycle */ - break; + /* + * Mark ourselves as active and see if the next step causes + * us to cycle to another active object. It's important to do + * this _before_ we loop, because it impacts where we make the + * cut, and thus how our total_depth counter works. + * E.g., We may see a partial loop like: + * + * A -> B -> C -> D -> B + * + * Cutting B->C breaks the cycle. But now the depth of A is + * only 1, and our total_depth counter is at 3. The size of the + * error is always one less than the size of the cycle we + * broke. Commits C and D were "lost" from A's chain. + * + * If we instead cut D->B, then the depth of A is correct at 3. + * We keep all commits in the chain that we examined. + */ + cur->dfs_state = DFS_ACTIVE; + if (cur->delta->dfs_state == DFS_ACTIVE) { + drop_reused_delta(cur); + cur->dfs_state = DFS_DONE; + break; + } + } + + /* + * And now that we've gone all the way to the bottom of the chain, we + * need to clear the active flags and set the depth fields as + * appropriate. Unlike the loop above, which can quit when it drops a + * delta, we need to keep going to look for more depth cuts. So we need + * an extra "next" pointer to keep going after we reset cur->delta. + */ + for (cur = entry; cur; cur = next) { + next = cur->delta; + + /* + * We should have a chain of zero or more ACTIVE states down to + * a final DONE. We can quit after the DONE, because either it + * has no bases, or we've already handled them in a previous + * call. + */ + if (cur->dfs_state == DFS_DONE) + break; + else if (cur->dfs_state != DFS_ACTIVE) + die("BUG: confusing delta dfs state in second pass: %d", + cur->dfs_state); - case DFS_ACTIVE: /* - * We found a cycle that needs broken. It would be correct to - * break any link in the chain, but it's convenient to - * break this one. + * If the total_depth is more than depth, then we need to snip + * the chain into two or more smaller chains that don't exceed + * the maximum depth. Most of the resulting chains will contain + * (depth + 1) entries (i.e., depth deltas plus one base), and + * the last chain (i.e., the one containing entry) will contain + * whatever entries are left over, namely + * (total_depth % (depth + 1)) of them. + * + * Since we are iterating towards decreasing depth, we need to + * decrement total_depth as we go, and we need to write to the + * entry what its final depth will be after all of the + * snipping. Since we're snipping into chains of length (depth + * + 1) entries, the final depth of an entry will be its + * original depth modulo (depth + 1). Any time we encounter an + * entry whose final depth is supposed to be zero, we snip it + * from its delta base, thereby making it so. */ - drop_reused_delta(entry); - entry->dfs_state = DFS_DONE; - break; + cur->depth = (total_depth--) % (depth + 1); + if (!cur->depth) + drop_reused_delta(cur); + + cur->dfs_state = DFS_DONE; } } @@ -2534,17 +2612,17 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) free(in_pack.array); } -static int add_loose_object(const unsigned char *sha1, const char *path, +static int add_loose_object(const struct object_id *oid, const char *path, void *data) { - enum object_type type = sha1_object_info(sha1, NULL); + enum object_type type = sha1_object_info(oid->hash, NULL); if (type < 0) { warning("loose object at %s could not be examined", path); return 0; } - add_object_entry(sha1, type, "", 0); + add_object_entry(oid->hash, type, "", 0); return 0; } diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 7cf900ea07..c026299e78 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -19,12 +19,12 @@ static int prune_subdir(int nr, const char *path, void *data) return 0; } -static int prune_object(const unsigned char *sha1, const char *path, +static int prune_object(const struct object_id *oid, const char *path, void *data) { int *opts = data; - if (!has_sha1_pack(sha1)) + if (!has_sha1_pack(oid->hash)) return 0; if (*opts & PRUNE_PACKED_DRY_RUN) diff --git a/builtin/prune.c b/builtin/prune.c index 8f4f052285..42633e0c6e 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -30,7 +30,7 @@ static int prune_tmp_file(const char *fullpath) return 0; } -static int prune_object(const unsigned char *sha1, const char *fullpath, +static int prune_object(const struct object_id *oid, const char *fullpath, void *data) { struct stat st; @@ -39,7 +39,7 @@ static int prune_object(const unsigned char *sha1, const char *fullpath, * Do we know about this object? * It must have been reachable */ - if (lookup_object(sha1)) + if (lookup_object(oid->hash)) return 0; if (lstat(fullpath, &st)) { @@ -50,8 +50,8 @@ static int prune_object(const unsigned char *sha1, const char *fullpath, if (st.st_mtime > expire) return 0; if (show_only || verbose) { - enum object_type type = sha1_object_info(sha1, NULL); - printf("%s %s\n", sha1_to_hex(sha1), + enum object_type type = sha1_object_info(oid->hash, NULL); + printf("%s %s\n", oid_to_hex(oid), (type > 0) ? typename(type) : "unknown"); } if (!show_only) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 8ba64bc785..23e212ee8c 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -15,10 +15,13 @@ #include "builtin.h" #include "parse-options.h" #include "resolve-undo.h" +#include "submodule.h" +#include "submodule-config.h" static int nr_trees; static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int list_tree(unsigned char *sha1) { @@ -96,6 +99,23 @@ static int debug_merge(const struct cache_entry * const *stages, return 0; } +static int option_parse_recurse_submodules(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + recurse_submodules = RECURSE_SUBMODULES_OFF; + return 0; + } + if (arg) + recurse_submodules = + parse_update_recurse_submodules_arg(opt->long_name, + arg); + else + recurse_submodules = RECURSE_SUBMODULES_ON; + + return 0; +} + static struct lock_file lock_file; int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) @@ -137,6 +157,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) N_("skip applying sparse checkout filter")), OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, N_("debug unpack-trees")), + { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, + "checkout", "control recursive updating of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules }, OPT_END() }; @@ -152,6 +175,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) { + gitmodules_config(); + git_config(submodule_config, NULL); + set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON); + } + prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c new file mode 100644 index 0000000000..ca1ebb2fa1 --- /dev/null +++ b/builtin/rebase--helper.c @@ -0,0 +1,40 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "sequencer.h" + +static const char * const builtin_rebase_helper_usage[] = { + N_("git rebase--helper [<options>]"), + NULL +}; + +int cmd_rebase__helper(int argc, const char **argv, const char *prefix) +{ + struct replay_opts opts = REPLAY_OPTS_INIT; + enum { + CONTINUE = 1, ABORT + } command = 0; + struct option options[] = { + OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), + CONTINUE), + OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), + ABORT), + OPT_END() + }; + + git_config(git_default_config, NULL); + + opts.action = REPLAY_INTERACTIVE_REBASE; + opts.allow_ff = 1; + opts.allow_empty = 1; + + argc = parse_options(argc, argv, NULL, options, + builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); + + if (command == CONTINUE && argc == 1) + return !!sequencer_continue(&opts); + if (command == ABORT && argc == 1) + return !!sequencer_remove_state(&opts); + usage_with_options(builtin_rebase_helper_usage, options); +} diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 1dbb8a0692..fb2a090a0c 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -21,6 +21,7 @@ #include "sigchain.h" #include "fsck.h" #include "tmp-objdir.h" +#include "oidset.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -250,8 +251,9 @@ static void show_ref(const char *path, const unsigned char *sha1) } static int show_ref_cb(const char *path_full, const struct object_id *oid, - int flag, void *unused) + int flag, void *data) { + struct oidset *seen = data; const char *path = strip_namespace(path_full); if (ref_is_hidden(path, path_full)) @@ -260,37 +262,38 @@ static int show_ref_cb(const char *path_full, const struct object_id *oid, /* * Advertise refs outside our current namespace as ".have" * refs, so that the client can use them to minimize data - * transfer but will otherwise ignore them. This happens to - * cover ".have" that are thrown in by add_one_alternate_ref() - * to mark histories that are complete in our alternates as - * well. + * transfer but will otherwise ignore them. */ - if (!path) + if (!path) { + if (oidset_insert(seen, oid)) + return 0; path = ".have"; + } else { + oidset_insert(seen, oid); + } show_ref(path, oid->hash); return 0; } -static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused) +static void show_one_alternate_ref(const char *refname, + const struct object_id *oid, + void *data) { - show_ref(".have", sha1); - return 0; -} + struct oidset *seen = data; -static void collect_one_alternate_ref(const struct ref *ref, void *data) -{ - struct sha1_array *sa = data; - sha1_array_append(sa, ref->old_oid.hash); + if (oidset_insert(seen, oid)) + return; + + show_ref(".have", oid->hash); } static void write_head_info(void) { - struct sha1_array sa = SHA1_ARRAY_INIT; + static struct oidset seen = OIDSET_INIT; - for_each_alternate_ref(collect_one_alternate_ref, &sa); - sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL); - sha1_array_clear(&sa); - for_each_ref(show_ref_cb, NULL); + for_each_ref(show_ref_cb, &seen); + for_each_alternate_ref(show_one_alternate_ref, &seen); + oidset_clear(&seen); if (!sent_capabilities) show_ref("capabilities^{}", null_sha1); @@ -1125,25 +1128,22 @@ static const char *update(struct command *cmd, struct shallow_info *si) static void run_update_post_hook(struct command *commands) { struct command *cmd; - int argc; struct child_process proc = CHILD_PROCESS_INIT; const char *hook; hook = find_hook("post-update"); - for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string || cmd->did_not_exist) - continue; - argc++; - } - if (!argc || !hook) + if (!hook) return; - argv_array_push(&proc.args, hook); for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; + if (!proc.args.argc) + argv_array_push(&proc.args, hook); argv_array_push(&proc.args, cmd->ref_name); } + if (!proc.args.argc) + return; proc.no_stdin = 1; proc.stdout_to_stderr = 1; @@ -1414,7 +1414,7 @@ static void execute_commands(struct command *commands, { struct check_connected_options opt = CHECK_CONNECTED_INIT; struct command *cmd; - unsigned char sha1[20]; + struct object_id oid; struct iterate_data data; struct async muxer; int err_fd = 0; @@ -1471,7 +1471,7 @@ static void execute_commands(struct command *commands, check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL); if (use_atomic) execute_commands_atomic(commands, si); @@ -1664,8 +1664,11 @@ static const char *unpack(int err_fd, struct shallow_info *si) } tmp_objdir = tmp_objdir_create(); - if (!tmp_objdir) + if (!tmp_objdir) { + if (err_fd > 0) + close(err_fd); return "unable to create temporary object directory"; + } child.env = tmp_objdir_env(tmp_objdir); /* diff --git a/builtin/reflog.c b/builtin/reflog.c index 7a7136e53e..7472775778 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -615,7 +615,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) return status; } -static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1, +static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { diff --git a/builtin/remote.c b/builtin/remote.c index 7682206c1e..addf97ad29 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -691,7 +691,7 @@ static int mv(int argc, const char **argv) read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag); if (!(flag & REF_ISSYMREF)) continue; - if (delete_ref(item->string, NULL, REF_NODEREF)) + if (delete_ref(NULL, item->string, NULL, REF_NODEREF)) die(_("deleting '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { @@ -1250,7 +1250,7 @@ static int set_head(int argc, const char **argv) head_name = xstrdup(states.heads.items[0].string); free_remote_ref_states(&states); } else if (opt_d && !opt_a && argc == 1) { - if (delete_ref(buf.buf, NULL, REF_NODEREF)) + if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF)) result |= error(_("Could not delete %s"), buf.buf); } else usage_with_options(builtin_remote_sethead_usage, options); diff --git a/builtin/replace.c b/builtin/replace.c index b58c714cb8..f83e7b8fc1 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -88,78 +88,78 @@ static int list_replace_refs(const char *pattern, const char *format) } typedef int (*each_replace_name_fn)(const char *name, const char *ref, - const unsigned char *sha1); + const struct object_id *oid); static int for_each_replace_name(const char **argv, each_replace_name_fn fn) { const char **p, *full_hex; char ref[PATH_MAX]; int had_error = 0; - unsigned char sha1[20]; + struct object_id oid; for (p = argv; *p; p++) { - if (get_sha1(*p, sha1)) { + if (get_oid(*p, &oid)) { error("Failed to resolve '%s' as a valid ref.", *p); had_error = 1; continue; } - full_hex = sha1_to_hex(sha1); + full_hex = oid_to_hex(&oid); snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex); /* read_ref() may reuse the buffer */ full_hex = ref + strlen(git_replace_ref_base); - if (read_ref(ref, sha1)) { + if (read_ref(ref, oid.hash)) { error("replace ref '%s' not found.", full_hex); had_error = 1; continue; } - if (fn(full_hex, ref, sha1)) + if (fn(full_hex, ref, &oid)) had_error = 1; } return had_error; } static int delete_replace_ref(const char *name, const char *ref, - const unsigned char *sha1) + const struct object_id *oid) { - if (delete_ref(ref, sha1, 0)) + if (delete_ref(NULL, ref, oid->hash, 0)) return 1; printf("Deleted replace ref '%s'\n", name); return 0; } -static void check_ref_valid(unsigned char object[20], - unsigned char prev[20], +static void check_ref_valid(struct object_id *object, + struct object_id *prev, char *ref, int ref_size, int force) { if (snprintf(ref, ref_size, "%s%s", git_replace_ref_base, - sha1_to_hex(object)) > ref_size - 1) + oid_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); - if (read_ref(ref, prev)) - hashclr(prev); + if (read_ref(ref, prev->hash)) + oidclr(prev); else if (!force) die("replace ref '%s' already exists", ref); } -static int replace_object_sha1(const char *object_ref, - unsigned char object[20], +static int replace_object_oid(const char *object_ref, + struct object_id *object, const char *replace_ref, - unsigned char repl[20], + struct object_id *repl, int force) { - unsigned char prev[20]; + struct object_id prev; enum object_type obj_type, repl_type; char ref[PATH_MAX]; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - obj_type = sha1_object_info(object, NULL); - repl_type = sha1_object_info(repl, NULL); + obj_type = sha1_object_info(object->hash, NULL); + repl_type = sha1_object_info(repl->hash, NULL); if (!force && obj_type != repl_type) die("Objects must be of the same type.\n" "'%s' points to a replaced object of type '%s'\n" @@ -167,11 +167,11 @@ static int replace_object_sha1(const char *object_ref, object_ref, typename(obj_type), replace_ref, typename(repl_type)); - check_ref_valid(object, prev, ref, sizeof(ref), force); + check_ref_valid(object, &prev, ref, sizeof(ref), force); transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref, repl, prev, + ref_transaction_update(transaction, ref, repl->hash, prev.hash, 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); @@ -182,14 +182,14 @@ static int replace_object_sha1(const char *object_ref, static int replace_object(const char *object_ref, const char *replace_ref, int force) { - unsigned char object[20], repl[20]; + struct object_id object, repl; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die("Failed to resolve '%s' as a valid ref.", object_ref); - if (get_sha1(replace_ref, repl)) + if (get_oid(replace_ref, &repl)) die("Failed to resolve '%s' as a valid ref.", replace_ref); - return replace_object_sha1(object_ref, object, replace_ref, repl, force); + return replace_object_oid(object_ref, &object, replace_ref, &repl, force); } /* @@ -197,7 +197,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f * If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object(const unsigned char *sha1, enum object_type type, +static void export_object(const struct object_id *oid, enum object_type type, int raw, const char *filename) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -213,7 +213,7 @@ static void export_object(const unsigned char *sha1, enum object_type type, argv_array_push(&cmd.args, typename(type)); else argv_array_push(&cmd.args, "-p"); - argv_array_push(&cmd.args, sha1_to_hex(sha1)); + argv_array_push(&cmd.args, oid_to_hex(oid)); cmd.git_cmd = 1; cmd.out = fd; @@ -226,7 +226,7 @@ static void export_object(const unsigned char *sha1, enum object_type type, * interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object(unsigned char *sha1, enum object_type type, +static void import_object(struct object_id *oid, enum object_type type, int raw, const char *filename) { int fd; @@ -254,7 +254,7 @@ static void import_object(unsigned char *sha1, enum object_type type, if (finish_command(&cmd)) die("mktree reported failure"); - if (get_sha1_hex(result.buf, sha1) < 0) + if (get_oid_hex(result.buf, oid) < 0) die("mktree did not return an object name"); strbuf_release(&result); @@ -264,7 +264,7 @@ static void import_object(unsigned char *sha1, enum object_type type, if (fstat(fd, &st) < 0) die_errno("unable to fstat %s", filename); - if (index_fd(sha1, fd, &st, type, NULL, flags) < 0) + if (index_fd(oid->hash, fd, &st, type, NULL, flags) < 0) die("unable to write object to database"); /* index_fd close()s fd for us */ } @@ -279,29 +279,29 @@ static int edit_and_replace(const char *object_ref, int force, int raw) { char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); enum object_type type; - unsigned char old[20], new[20], prev[20]; + struct object_id old, new, prev; char ref[PATH_MAX]; - if (get_sha1(object_ref, old) < 0) + if (get_oid(object_ref, &old) < 0) die("Not a valid object name: '%s'", object_ref); - type = sha1_object_info(old, NULL); + type = sha1_object_info(old.hash, NULL); if (type < 0) - die("unable to get object type for %s", sha1_to_hex(old)); + die("unable to get object type for %s", oid_to_hex(&old)); - check_ref_valid(old, prev, ref, sizeof(ref), force); + check_ref_valid(&old, &prev, ref, sizeof(ref), force); - export_object(old, type, raw, tmpfile); + export_object(&old, type, raw, tmpfile); if (launch_editor(tmpfile, NULL, NULL) < 0) die("editing object file failed"); - import_object(new, type, raw, tmpfile); + import_object(&new, type, raw, tmpfile); free(tmpfile); - if (!hashcmp(old, new)) - return error("new object is the same as the old one: '%s'", sha1_to_hex(old)); + if (!oidcmp(&old, &new)) + return error("new object is the same as the old one: '%s'", oid_to_hex(&old)); - return replace_object_sha1(object_ref, old, "replacement", new, force); + return replace_object_oid(object_ref, &old, "replacement", &new, force); } static void replace_parents(struct strbuf *buf, int argc, const char **argv) @@ -312,7 +312,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) /* find existing parents */ parent_start = buf->buf; - parent_start += 46; /* "tree " + "hex sha1" + "\n" */ + parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */ parent_end = parent_start; while (starts_with(parent_end, "parent ")) @@ -320,11 +320,11 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) /* prepare new parents */ for (i = 0; i < argc; i++) { - unsigned char sha1[20]; - if (get_sha1(argv[i], sha1) < 0) + struct object_id oid; + if (get_oid(argv[i], &oid) < 0) die(_("Not a valid object name: '%s'"), argv[i]); - lookup_commit_or_die(sha1, argv[i]); - strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1)); + lookup_commit_or_die(oid.hash, argv[i]); + strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid)); } /* replace existing parents with new ones */ @@ -345,12 +345,12 @@ static void check_one_mergetag(struct commit *commit, { struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data; const char *ref = mergetag_data->argv[0]; - unsigned char tag_sha1[20]; + struct object_id tag_oid; struct tag *tag; int i; - hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1); - tag = lookup_tag(tag_sha1); + hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_oid.hash); + tag = lookup_tag(tag_oid.hash); if (!tag) die(_("bad mergetag in commit '%s'"), ref); if (parse_tag_buffer(tag, extra->value, extra->len)) @@ -366,7 +366,7 @@ static void check_one_mergetag(struct commit *commit, } die(_("original commit '%s' contains mergetag '%s' that is discarded; " - "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1)); + "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid)); } static void check_mergetags(struct commit *commit, int argc, const char **argv) @@ -380,16 +380,16 @@ static void check_mergetags(struct commit *commit, int argc, const char **argv) static int create_graft(int argc, const char **argv, int force) { - unsigned char old[20], new[20]; + struct object_id old, new; const char *old_ref = argv[0]; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *buffer; unsigned long size; - if (get_sha1(old_ref, old) < 0) + if (get_oid(old_ref, &old) < 0) die(_("Not a valid object name: '%s'"), old_ref); - commit = lookup_commit_or_die(old, old_ref); + commit = lookup_commit_or_die(old.hash, old_ref); buffer = get_commit_buffer(commit, &size); strbuf_add(&buf, buffer, size); @@ -404,15 +404,15 @@ static int create_graft(int argc, const char **argv, int force) check_mergetags(commit, argc, argv); - if (write_sha1_file(buf.buf, buf.len, commit_type, new)) + if (write_sha1_file(buf.buf, buf.len, commit_type, new.hash)) die(_("could not write replacement commit for: '%s'"), old_ref); strbuf_release(&buf); - if (!hashcmp(old, new)) - return error("new commit is the same as the old one: '%s'", sha1_to_hex(old)); + if (!oidcmp(&old, &new)) + return error("new commit is the same as the old one: '%s'", oid_to_hex(&old)); - return replace_object_sha1(old_ref, old, "replacement", new, force); + return replace_object_oid(old_ref, &old, "replacement", &new, force); } int cmd_replace(int argc, const char **argv, const char *prefix) diff --git a/builtin/reset.c b/builtin/reset.c index 8ab915bfcb..fc3b906c47 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -256,7 +256,7 @@ static int reset_refs(const char *rev, const struct object_id *oid) update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) - delete_ref("ORIG_HEAD", old_orig->hash, 0); + delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0); set_reflog_message(&msg, "updating HEAD", rev); update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0, UPDATE_REFS_MSG_ON_ERR); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index ff13e59e1d..9e53a1a7ca 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -12,6 +12,7 @@ #include "diff.h" #include "revision.h" #include "split-index.h" +#include "submodule.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -227,9 +228,9 @@ static int show_file(const char *arg, int output_prefix) if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { if (output_prefix) { const char *prefix = startup_info->prefix; - show(prefix_filename(prefix, - prefix ? strlen(prefix) : 0, - arg)); + char *fname = prefix_filename(prefix, arg); + show(fname); + free(fname); } else show(arg); return 1; @@ -535,6 +536,34 @@ N_("git rev-parse --parseopt [<options>] -- [<args>...]\n" "\n" "Run \"git rev-parse --parseopt -h\" for more information on the first usage."); +/* + * Parse "opt" or "opt=<value>", setting value respectively to either + * NULL or the string after "=". + */ +static int opt_with_value(const char *arg, const char *opt, const char **value) +{ + if (skip_prefix(arg, opt, &arg)) { + if (!*arg) { + *value = NULL; + return 1; + } + if (*arg++ == '=') { + *value = arg; + return 1; + } + } + return 0; +} + +static void handle_ref_opt(const char *pattern, const char *prefix) +{ + if (pattern) + for_each_glob_ref_in(show_reference, pattern, prefix, NULL); + else + for_each_ref_in(prefix, show_reference, NULL); + clear_ref_exclusion(&ref_excludes); +} + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -545,6 +574,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) unsigned int flags = 0; const char *name = NULL; struct object_context unused; + struct strbuf buf = STRBUF_INIT; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -599,7 +629,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--git-path")) { if (!argv[i + 1]) die("--git-path requires an argument"); - puts(git_path("%s", argv[i + 1])); + strbuf_reset(&buf); + puts(relative_path(git_path("%s", argv[i + 1]), + prefix, &buf)); i++; continue; } @@ -671,14 +703,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) flags |= GET_SHA1_QUIETLY; continue; } - if (!strcmp(arg, "--short") || - starts_with(arg, "--short=")) { + if (opt_with_value(arg, "--short", &arg)) { filter &= ~(DO_FLAGS|DO_NOREV); verify = 1; abbrev = DEFAULT_ABBREV; - if (!arg[7]) + if (!arg) continue; - abbrev = strtoul(arg + 8, NULL, 10); + abbrev = strtoul(arg, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; else if (40 <= abbrev) @@ -701,17 +732,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) symbolic = SHOW_SYMBOLIC_FULL; continue; } - if (starts_with(arg, "--abbrev-ref") && - (!arg[12] || arg[12] == '=')) { + if (opt_with_value(arg, "--abbrev-ref", &arg)) { abbrev_ref = 1; abbrev_ref_strict = warn_ambiguous_refs; - if (arg[12] == '=') { - if (!strcmp(arg + 13, "strict")) + if (arg) { + if (!strcmp(arg, "strict")) abbrev_ref_strict = 1; - else if (!strcmp(arg + 13, "loose")) + else if (!strcmp(arg, "loose")) abbrev_ref_strict = 0; else - die("unknown mode for %s", arg); + die("unknown mode for --abbrev-ref: %s", + arg); } continue; } @@ -719,8 +750,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } - if (starts_with(arg, "--disambiguate=")) { - for_each_abbrev(arg + 15, show_abbrev, NULL); + if (skip_prefix(arg, "--disambiguate=", &arg)) { + for_each_abbrev(arg, show_abbrev, NULL); continue; } if (!strcmp(arg, "--bisect")) { @@ -728,46 +759,24 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref_in("refs/bisect/good", anti_reference, NULL); continue; } - if (starts_with(arg, "--branches=")) { - for_each_glob_ref_in(show_reference, arg + 11, - "refs/heads/", NULL); - clear_ref_exclusion(&ref_excludes); + if (opt_with_value(arg, "--branches", &arg)) { + handle_ref_opt(arg, "refs/heads/"); continue; } - if (!strcmp(arg, "--branches")) { - for_each_branch_ref(show_reference, NULL); - clear_ref_exclusion(&ref_excludes); + if (opt_with_value(arg, "--tags", &arg)) { + handle_ref_opt(arg, "refs/tags/"); continue; } - if (starts_with(arg, "--tags=")) { - for_each_glob_ref_in(show_reference, arg + 7, - "refs/tags/", NULL); - clear_ref_exclusion(&ref_excludes); + if (skip_prefix(arg, "--glob=", &arg)) { + handle_ref_opt(arg, NULL); continue; } - if (!strcmp(arg, "--tags")) { - for_each_tag_ref(show_reference, NULL); - clear_ref_exclusion(&ref_excludes); + if (opt_with_value(arg, "--remotes", &arg)) { + handle_ref_opt(arg, "refs/remotes/"); continue; } - if (starts_with(arg, "--glob=")) { - for_each_glob_ref(show_reference, arg + 7, NULL); - clear_ref_exclusion(&ref_excludes); - continue; - } - if (starts_with(arg, "--remotes=")) { - for_each_glob_ref_in(show_reference, arg + 10, - "refs/remotes/", NULL); - clear_ref_exclusion(&ref_excludes); - continue; - } - if (!strcmp(arg, "--remotes")) { - for_each_remote_ref(show_reference, NULL); - clear_ref_exclusion(&ref_excludes); - continue; - } - if (starts_with(arg, "--exclude=")) { - add_ref_exclusion(&ref_excludes, arg + 10); + if (skip_prefix(arg, "--exclude=", &arg)) { + add_ref_exclusion(&ref_excludes, arg); continue; } if (!strcmp(arg, "--show-toplevel")) { @@ -776,6 +785,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) puts(work_tree); continue; } + if (!strcmp(arg, "--show-superproject-working-tree")) { + const char *superproject = get_superproject_working_tree(); + if (superproject) + puts(superproject); + continue; + } if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); @@ -802,17 +817,27 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) putchar('\n'); continue; } - if (!strcmp(arg, "--git-dir")) { + if (!strcmp(arg, "--git-dir") || + !strcmp(arg, "--absolute-git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); char *cwd; int len; - if (gitdir) { - puts(gitdir); - continue; - } - if (!prefix) { - puts(".git"); - continue; + if (arg[2] == 'g') { /* --git-dir */ + if (gitdir) { + puts(gitdir); + continue; + } + if (!prefix) { + puts(".git"); + continue; + } + } else { /* --absolute-git-dir */ + if (!gitdir && !prefix) + gitdir = ".git"; + if (gitdir) { + puts(real_path(gitdir)); + continue; + } } cwd = xgetcwd(); len = strlen(cwd); @@ -821,8 +846,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--git-common-dir")) { - const char *pfx = prefix ? prefix : ""; - puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir())); + strbuf_reset(&buf); + puts(relative_path(get_git_common_dir(), + prefix, &buf)); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { @@ -845,24 +871,26 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) die(_("Could not read the index")); if (the_index.split_index) { const unsigned char *sha1 = the_index.split_index->base_sha1; - puts(git_path("sharedindex.%s", sha1_to_hex(sha1))); + const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1)); + strbuf_reset(&buf); + puts(relative_path(path, prefix, &buf)); } continue; } - if (starts_with(arg, "--since=")) { - show_datestring("--max-age=", arg+8); + if (skip_prefix(arg, "--since=", &arg)) { + show_datestring("--max-age=", arg); continue; } - if (starts_with(arg, "--after=")) { - show_datestring("--max-age=", arg+8); + if (skip_prefix(arg, "--after=", &arg)) { + show_datestring("--max-age=", arg); continue; } - if (starts_with(arg, "--before=")) { - show_datestring("--min-age=", arg+9); + if (skip_prefix(arg, "--before=", &arg)) { + show_datestring("--min-age=", arg); continue; } - if (starts_with(arg, "--until=")) { - show_datestring("--min-age=", arg+8); + if (skip_prefix(arg, "--until=", &arg)) { + show_datestring("--min-age=", arg); continue; } if (show_flag(arg) && verify) @@ -897,6 +925,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; verify_filename(prefix, arg, 1); } + strbuf_release(&buf); if (verify) { if (revs_count == 1) { show_rev(type, sha1, name); diff --git a/builtin/revert.c b/builtin/revert.c index 4ca5b51544..345d9586a7 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -54,6 +54,24 @@ static int option_parse_x(const struct option *opt, return 0; } +static int option_parse_m(const struct option *opt, + const char *arg, int unset) +{ + struct replay_opts *replay = opt->value; + char *end; + + if (unset) { + replay->mainline = 0; + return 0; + } + + replay->mainline = strtol(arg, &end, 10); + if (*end || replay->mainline <= 0) + return opterror(opt, "expects a number greater than zero", 0); + + return 0; +} + LAST_ARG_MUST_BE_NULL static void verify_opt_compatible(const char *me, const char *base_opt, ...) { @@ -84,7 +102,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")), OPT_NOOP_NOARG('r', NULL), OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")), - OPT_INTEGER('m', "mainline", &opts->mainline, N_("parent number")), + OPT_CALLBACK('m', "mainline", opts, N_("parent-number"), + N_("select mainline parent"), option_parse_m), OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), OPT_STRING(0, "strategy", &opts->strategy, N_("strategy"), N_("merge strategy")), OPT_CALLBACK('X', "strategy-option", &opts, N_("option"), diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 1ff5a67538..832fd7ed0a 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -144,6 +144,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) unsigned force_update = 0; unsigned quiet = 0; int push_cert = 0; + struct string_list push_options = STRING_LIST_INIT_NODUP; unsigned use_thin_pack = 0; unsigned atomic = 0; unsigned stateless_rpc = 0; @@ -165,6 +166,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"), PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_STRING_LIST(0, "push-option", &push_options, + N_("server-specific"), + N_("option to transmit")), OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")), OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")), @@ -199,6 +203,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = use_thin_pack; args.atomic = atomic; args.stateless_rpc = stateless_rpc; + args.push_options = push_options.nr ? &push_options : NULL; if (from_stdin) { struct argv_array all_refspecs = ARGV_ARRAY_INIT; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c9585d475d..7cff1839fc 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -148,8 +148,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; - ctx.subject = ""; - ctx.after_subject = ""; + ctx.print_email_subject = 1; ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 899dc334e3..be316bbbc8 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -356,12 +356,10 @@ static void init_submodule(const char *path, const char *prefix, int quiet) strbuf_addf(&remotesb, "remote.%s.url", remote); free(remote); - if (git_config_get_string(remotesb.buf, &remoteurl)) - /* - * The repository is its own - * authoritative upstream - */ + if (git_config_get_string(remotesb.buf, &remoteurl)) { + warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf); remoteurl = xgetcwd(); + } relurl = relative_url(remoteurl, url, NULL); strbuf_release(&remotesb); free(remoteurl); @@ -579,9 +577,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) const char *name = NULL, *url = NULL, *depth = NULL; int quiet = 0; int progress = 0; - FILE *submodule_dot_git; char *p, *path = NULL, *sm_gitdir; - struct strbuf rel_path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; char *sm_alternate = NULL, *error_strategy = NULL; @@ -653,27 +649,12 @@ static int module_clone(int argc, const char **argv, const char *prefix) strbuf_reset(&sb); } - /* Write a .git file in the submodule to redirect to the superproject. */ - strbuf_addf(&sb, "%s/.git", path); - if (safe_create_leading_directories_const(sb.buf) < 0) - die(_("could not create leading directories of '%s'"), sb.buf); - submodule_dot_git = fopen(sb.buf, "w"); - if (!submodule_dot_git) - die_errno(_("cannot open file '%s'"), sb.buf); - - fprintf_or_die(submodule_dot_git, "gitdir: %s\n", - relative_path(sm_gitdir, path, &rel_path)); - if (fclose(submodule_dot_git)) - die(_("could not close file %s"), sb.buf); - strbuf_reset(&sb); - strbuf_reset(&rel_path); + /* Connect module worktree and git dir */ + connect_work_tree_and_git_dir(path, sm_gitdir); - /* Redirect the worktree of the submodule in the superproject's config */ p = git_pathdup_submodule(path, "config"); if (!p) die(_("could not get submodule directory for '%s'"), path); - git_config_set_in_file(p, "core.worktree", - relative_path(path, sm_gitdir, &rel_path)); /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ git_config_get_string("submodule.alternateLocation", &sm_alternate); @@ -689,7 +670,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) free(error_strategy); strbuf_release(&sb); - strbuf_release(&rel_path); free(sm_gitdir); free(path); free(p); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 96eed94468..70addef158 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -58,7 +58,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) die("Cannot delete %s, not a symbolic ref", argv[0]); if (!strcmp(argv[0], "HEAD")) die("deleting '%s' is not allowed", argv[0]); - return delete_ref(argv[0], NULL, REF_NODEREF); + return delete_ref(NULL, argv[0], NULL, REF_NODEREF); } switch (argc) { diff --git a/builtin/tag.c b/builtin/tag.c index e40c4a9676..ad29be6923 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -45,11 +45,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (!format) { if (filter->lines) { to_free = xstrfmt("%s %%(contents:lines=%d)", - "%(align:15)%(refname:strip=2)%(end)", + "%(align:15)%(refname:lstrip=2)%(end)", filter->lines); format = to_free; } else - format = "%(refname:strip=2)"; + format = "%(refname:lstrip=2)"; } verify_ref_format(format); @@ -97,7 +97,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, static int delete_tag(const char *name, const char *ref, const unsigned char *sha1, const void *cb_data) { - if (delete_ref(ref, sha1, 0)) + if (delete_ref(NULL, ref, sha1, 0)) return 1; printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 0; @@ -302,6 +302,54 @@ static void create_tag(const unsigned char *object, const char *tag, } } +static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb) +{ + enum object_type type; + struct commit *c; + char *buf; + unsigned long size; + int subject_len = 0; + const char *subject_start; + + char *rla = getenv("GIT_REFLOG_ACTION"); + if (rla) { + strbuf_addstr(sb, rla); + } else { + strbuf_addstr(sb, _("tag: tagging ")); + strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV); + } + + strbuf_addstr(sb, " ("); + type = sha1_object_info(sha1, NULL); + switch (type) { + default: + strbuf_addstr(sb, _("object of unknown type")); + break; + case OBJ_COMMIT: + if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) { + subject_len = find_commit_subject(buf, &subject_start); + strbuf_insert(sb, sb->len, subject_start, subject_len); + } else { + strbuf_addstr(sb, _("commit object")); + } + free(buf); + + if ((c = lookup_commit_reference(sha1)) != NULL) + strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT))); + break; + case OBJ_TREE: + strbuf_addstr(sb, _("tree object")); + break; + case OBJ_BLOB: + strbuf_addstr(sb, _("blob object")); + break; + case OBJ_TAG: + strbuf_addstr(sb, _("other tag object")); + break; + } + strbuf_addch(sb, ')'); +} + struct msg_arg { int given; struct strbuf buf; @@ -335,6 +383,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; struct strbuf ref = STRBUF_INIT; + struct strbuf reflog_msg = STRBUF_INIT; unsigned char object[20], prev[20]; const char *object_ref, *tag; struct create_tag_options opt; @@ -389,6 +438,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_END() }; + setup_ref_filter_porcelain_msg(); + git_config(git_tag_config, sorting_tail); memset(&opt, 0, sizeof(opt)); @@ -494,6 +545,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); + create_reflog_msg(object, &reflog_msg); + if (create_tag_object) { if (force_sign_annotate && !annotate) opt.sign = 1; @@ -504,7 +557,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, - NULL, &err) || + reflog_msg.buf, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); @@ -514,5 +567,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix) strbuf_release(&err); strbuf_release(&buf); strbuf_release(&ref); + strbuf_release(&reflog_msg); return 0; } diff --git a/builtin/update-index.c b/builtin/update-index.c index d530e89368..d74d72cc7f 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -1099,17 +1099,20 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) } if (split_index > 0) { - init_split_index(&the_index); - the_index.cache_changed |= SPLIT_INDEX_ORDERED; - } else if (!split_index && the_index.split_index) { - /* - * can't discard_split_index(&the_index); because that - * will destroy split_index->base->cache[], which may - * be shared with the_index.cache[]. So yeah we're - * leaking a bit here. - */ - the_index.split_index = NULL; - the_index.cache_changed |= SOMETHING_CHANGED; + if (git_config_get_split_index() == 0) + warning(_("core.splitIndex is set to false; " + "remove or change it, if you really want to " + "enable split index")); + if (the_index.split_index) + the_index.cache_changed |= SPLIT_INDEX_ORDERED; + else + add_split_index(&the_index); + } else if (!split_index) { + if (git_config_get_split_index() == 1) + warning(_("core.splitIndex is set to true; " + "remove or change it, if you really want to " + "disable split index")); + remove_split_index(&the_index); } switch (untracked_cache) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7f30d3a76f..0b2ecf41ae 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -433,7 +433,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) * For purposes of backwards compatibility, we treat * NULL_SHA1 as "don't care" here: */ - return delete_ref(refname, + return delete_ref(msg, refname, (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL, flags); else diff --git a/builtin/worktree.c b/builtin/worktree.c index 831fe058a5..9993ded41a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -318,7 +318,8 @@ static int add(int ac, const char **av, const char *prefix) { struct add_opts opts; const char *new_branch_force = NULL; - const char *path, *branch; + char *path; + const char *branch; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")), OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), @@ -338,7 +339,7 @@ static int add(int ac, const char **av, const char *prefix) if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); - path = prefix_filename(prefix, strlen(prefix), av[0]); + path = prefix_filename(prefix, av[0]); branch = ac < 2 ? "HEAD" : av[1]; if (!strcmp(branch, "-")) @@ -10,8 +10,8 @@ #include "trace.h" #include "string-list.h" #include "pack-revindex.h" +#include "hash.h" -#include SHA1_HEADER #ifndef platform_SHA_CTX /* * platform's underlying implementation of SHA-1; could be OpenSSL, @@ -518,11 +518,30 @@ extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" extern void setup_work_tree(void); +/* + * Find GIT_DIR of the repository that contains the current working directory, + * without changing the working directory or other global state. The result is + * appended to gitdir. The return value is either NULL if no repository was + * found, or pointing to the path inside gitdir's buffer. + */ +extern const char *discover_git_directory(struct strbuf *gitdir); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); -extern const char *prefix_filename(const char *prefix, int len, const char *path); + +/* + * Concatenate "prefix" (if len is non-zero) and "path", with no + * connecting characters (so "prefix" should end with a "/"). + * Unlike prefix_path, this should be used if the named file does + * not have to interact with index entry; i.e. name of a random file + * on the filesystem. + * + * The return value is always a newly allocated string (even if the + * prefix was empty). + */ +extern char *prefix_filename(const char *prefix, const char *path); + extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name, @@ -1069,8 +1088,9 @@ int adjust_shared_perm(const char *path); /* * Create the directory containing the named path, using care to be - * somewhat safe against races. Return one of the scld_error values - * to indicate success/failure. + * somewhat safe against races. Return one of the scld_error values to + * indicate success/failure. On error, set errno to describe the + * problem. * * SCLD_VANISHED indicates that one of the ancestor directories of the * path existed at one point during the function call and then @@ -1094,6 +1114,49 @@ enum scld_error { enum scld_error safe_create_leading_directories(char *path); enum scld_error safe_create_leading_directories_const(const char *path); +/* + * Callback function for raceproof_create_file(). This function is + * expected to do something that makes dirname(path) permanent despite + * the fact that other processes might be cleaning up empty + * directories at the same time. Usually it will create a file named + * path, but alternatively it could create another file in that + * directory, or even chdir() into that directory. The function should + * return 0 if the action was completed successfully. On error, it + * should return a nonzero result and set errno. + * raceproof_create_file() treats two errno values specially: + * + * - ENOENT -- dirname(path) does not exist. In this case, + * raceproof_create_file() tries creating dirname(path) + * (and any parent directories, if necessary) and calls + * the function again. + * + * - EISDIR -- the file already exists and is a directory. In this + * case, raceproof_create_file() removes the directory if + * it is empty (and recursively any empty directories that + * it contains) and calls the function again. + * + * Any other errno causes raceproof_create_file() to fail with the + * callback's return value and errno. + * + * Obviously, this function should be OK with being called again if it + * fails with ENOENT or EISDIR. In other scenarios it will not be + * called again. + */ +typedef int create_file_fn(const char *path, void *cb); + +/* + * Create a file in dirname(path) by calling fn, creating leading + * directories if necessary. Retry a few times in case we are racing + * with another process that is trying to clean up the directory that + * contains path. See the documentation for create_file_fn for more + * details. + * + * Return the value and set the errno that resulted from the most + * recent call of fn. fn is always called at least once, and will be + * called more than once if it returns ENOENT or EISDIR. + */ +int raceproof_create_file(const char *path, create_file_fn fn, void *cb); + int mkdir_in_gitdir(const char *path); extern char *expand_user_path(const char *path); const char *enter_repo(const char *path, int strict); @@ -1125,6 +1188,13 @@ extern int is_ntfs_dotgit(const char *name); */ extern char *xdg_config_home(const char *filename); +/** + * Return a newly allocated string with the evaluation of + * "$XDG_CACHE_HOME/git/$filename" if $XDG_CACHE_HOME is non-empty, otherwise + * "$HOME/.cache/git/$filename". Return NULL upon error. + */ +extern char *xdg_cache_home(const char *filename); + /* object replacement */ #define LOOKUP_REPLACE_OBJECT 1 #define LOOKUP_UNKNOWN_OBJECT 2 @@ -1226,6 +1296,9 @@ extern int has_pack_index(const unsigned char *sha1); extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect); +/* Helper to check and "touch" a file */ +extern int check_and_freshen_file(const char *fn, int freshen); + extern const signed char hexval_table[256]; static inline unsigned int hexval(unsigned char c) { @@ -1316,7 +1389,46 @@ extern char *oid_to_hex_r(char *out, const struct object_id *oid); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */ -extern int interpret_branch_name(const char *str, int len, struct strbuf *); +/* + * Parse a 40-character hexadecimal object ID starting from hex, updating the + * pointer specified by end when parsing stops. The resulting object ID is + * stored in oid. Returns 0 on success. Parsing will stop on the first NUL or + * other invalid character. end is only updated on success; otherwise, it is + * unmodified. + */ +extern int parse_oid_hex(const char *hex, struct object_id *oid, const char **end); + +/* + * This reads short-hand syntax that not only evaluates to a commit + * object name, but also can act as if the end user spelled the name + * of the branch from the command line. + * + * - "@{-N}" finds the name of the Nth previous branch we were on, and + * places the name of the branch in the given buf and returns the + * number of characters parsed if successful. + * + * - "<branch>@{upstream}" finds the name of the other ref that + * <branch> is configured to merge with (missing <branch> defaults + * to the current branch), and places the name of the branch in the + * given buf and returns the number of characters parsed if + * successful. + * + * If the input is not of the accepted format, it returns a negative + * number to signal an error. + * + * If the input was ok but there are not N branch switches in the + * reflog, it returns 0. + * + * If "allowed" is non-zero, it is a treated as a bitfield of allowable + * expansions: local branches ("refs/heads/"), remote branches + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is + * allowed, even ones to refs outside of those namespaces. + */ +#define INTERPRET_BRANCH_LOCAL (1<<0) +#define INTERPRET_BRANCH_REMOTE (1<<1) +#define INTERPRET_BRANCH_HEAD (1<<2) +extern int interpret_branch_name(const char *str, int len, struct strbuf *, + unsigned allowed); extern int get_oid_mb(const char *str, struct object_id *oid); extern int validate_headref(const char *ref); @@ -1561,6 +1673,27 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); /* + * Create a temporary file rooted in the object database directory. + */ +extern int odb_mkstemp(char *template, size_t limit, const char *pattern); + +/* + * Generate the filename to be used for a pack file with checksum "sha1" and + * extension "ext". The result is written into the strbuf "buf", overwriting + * any existing contents. A pointer to buf->buf is returned as a convenience. + * + * Example: odb_pack_name(out, sha1, "idx") => ".git/objects/pack/pack-1234..idx" + */ +extern char *odb_pack_name(struct strbuf *buf, const unsigned char *sha1, const char *ext); + +/* + * Create a pack .keep file named "name" (which should generally be the output + * of odb_pack_name). Returns a file descriptor opened for writing, or -1 on + * error. + */ +extern int odb_pack_keep(const char *name); + +/* * mmap the index file for the specified packfile (if it is not * already mmapped). Return 0 on success. */ @@ -1596,6 +1729,12 @@ extern void check_pack_index_ptr(const struct packed_git *p, const void *ptr); * error. */ extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t n); +/* + * Like nth_packed_object_sha1, but write the data into the object specified by + * the the first argument. Returns the first argument on success, and NULL on + * error. + */ +extern const struct object_id *nth_packed_object_oid(struct object_id *, struct packed_git *, uint32_t n); /* * Return the offset of the nth object within the specified packfile. @@ -1637,7 +1776,7 @@ extern int unpack_object_header(struct packed_git *, struct pack_window **, off_ * scratch buffer, but restored to its original contents before * the function returns. */ -typedef int each_loose_object_fn(const unsigned char *sha1, +typedef int each_loose_object_fn(const struct object_id *oid, const char *path, void *data); typedef int each_loose_cruft_fn(const char *basename, @@ -1663,7 +1802,7 @@ int for_each_loose_file_in_objdir_buf(struct strbuf *path, * LOCAL_ONLY flag is set). */ #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1 -typedef int each_packed_object_fn(const unsigned char *sha1, +typedef int each_packed_object_fn(const struct object_id *oid, struct packed_git *pack, uint32_t pos, void *data); @@ -1750,6 +1889,7 @@ extern int git_config_from_blob_sha1(config_fn_t fn, const char *name, const unsigned char *sha1, void *data); extern void git_config_push_parameter(const char *text); extern int git_config_from_parameters(config_fn_t fn, void *data); +extern void read_early_config(config_fn_t cb, void *data); extern void git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, struct git_config_source *config_source, @@ -1816,8 +1956,11 @@ extern int git_config_include(const char *name, const char *value, void *data); * * (i.e., what gets handed to a config_fn_t). The caller provides the section; * we return -1 if it does not match, 0 otherwise. The subsection and key - * out-parameters are filled by the function (and subsection is NULL if it is + * out-parameters are filled by the function (and *subsection is NULL if it is * missing). + * + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if + * there is no subsection at all. */ extern int parse_config_key(const char *var, const char *section, @@ -1879,6 +2022,11 @@ extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); extern int git_config_get_maybe_bool(const char *key, int *dest); extern int git_config_get_pathname(const char *key, const char **dest); extern int git_config_get_untracked_cache(void); +extern int git_config_get_split_index(void); +extern int git_config_get_max_percent_split_change(void); + +/* This dies if the configured or default date is in the future */ +extern int git_config_get_expiry(const char *key, const char **output); /* * This is a hack for test programs like test-dump-untracked-cache to @@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject) p++; if (*p) { p = skip_blank_lines(p + 2); - for (eol = p; *eol && *eol != '\n'; eol++) - ; /* do nothing */ + eol = strchrnul(p, '\n'); } else eol = p; @@ -142,21 +142,24 @@ static inline int cmit_fmt_is_mail(enum cmit_fmt fmt) return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD); } +struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ + struct pretty_print_context { /* * Callers should tweak these to change the behavior of pp_* functions. */ enum cmit_fmt fmt; int abbrev; - const char *subject; const char *after_subject; int preserve_subject; struct date_mode date_mode; unsigned date_mode_explicit:1; + int print_email_subject; int expand_tabs_in_log; int need_8bit_cte; char *notes_message; struct reflog_walk_info *reflog_info; + struct rev_info *rev; const char *output_encoding; struct string_list *mailmap; int color; @@ -175,7 +178,6 @@ struct userformat_want { }; extern int has_non_ascii(const char *text); -struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern const char *logmsg_reencode(const struct commit *commit, char **commit_encoding, const char *output_encoding); diff --git a/common-main.c b/common-main.c index c654f95551..6a689007e7 100644 --- a/common-main.c +++ b/common-main.c @@ -1,5 +1,6 @@ #include "cache.h" #include "exec_cmd.h" +#include "attr.h" /* * Many parts of Git have subprograms communicate via pipe, expect the @@ -33,6 +34,8 @@ int main(int argc, const char **argv) git_setup_gettext(); + attr_start(); + git_extract_argv0_path(argv[0]); restore_sigpipe_to_default(); @@ -13,6 +13,7 @@ #include "hashmap.h" #include "string-list.h" #include "utf8.h" +#include "dir.h" struct config_source { struct config_source *prev; @@ -170,9 +171,94 @@ static int handle_path_include(const char *path, struct config_include_data *inc return ret; } +static int prepare_include_condition_pattern(struct strbuf *pat) +{ + struct strbuf path = STRBUF_INIT; + char *expanded; + int prefix = 0; + + expanded = expand_user_path(pat->buf); + if (expanded) { + strbuf_reset(pat); + strbuf_addstr(pat, expanded); + free(expanded); + } + + if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) { + const char *slash; + + if (!cf || !cf->path) + return error(_("relative config include " + "conditionals must come from files")); + + strbuf_add_absolute_path(&path, cf->path); + slash = find_last_dir_sep(path.buf); + if (!slash) + die("BUG: how is this possible?"); + strbuf_splice(pat, 0, 1, path.buf, slash - path.buf); + prefix = slash - path.buf + 1 /* slash */; + } else if (!is_absolute_path(pat->buf)) + strbuf_insert(pat, 0, "**/", 3); + + if (pat->len && is_dir_sep(pat->buf[pat->len - 1])) + strbuf_addstr(pat, "**"); + + strbuf_release(&path); + return prefix; +} + +static int include_by_gitdir(const char *cond, size_t cond_len, int icase) +{ + struct strbuf text = STRBUF_INIT; + struct strbuf pattern = STRBUF_INIT; + int ret = 0, prefix; + + strbuf_add_absolute_path(&text, get_git_dir()); + strbuf_add(&pattern, cond, cond_len); + prefix = prepare_include_condition_pattern(&pattern); + + if (prefix < 0) + goto done; + + if (prefix > 0) { + /* + * perform literal matching on the prefix part so that + * any wildcard character in it can't create side effects. + */ + if (text.len < prefix) + goto done; + if (!icase && strncmp(pattern.buf, text.buf, prefix)) + goto done; + if (icase && strncasecmp(pattern.buf, text.buf, prefix)) + goto done; + } + + ret = !wildmatch(pattern.buf + prefix, text.buf + prefix, + icase ? WM_CASEFOLD : 0, NULL); + +done: + strbuf_release(&pattern); + strbuf_release(&text); + return ret; +} + +static int include_condition_is_true(const char *cond, size_t cond_len) +{ + + if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) + return include_by_gitdir(cond, cond_len, 0); + else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) + return include_by_gitdir(cond, cond_len, 1); + + /* unknown conditionals are always false */ + return 0; +} + int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; + const char *cond, *key; + int cond_len; int ret; /* @@ -185,6 +271,12 @@ int git_config_include(const char *var, const char *value, void *data) if (!strcmp(var, "include.path")) ret = handle_path_include(value, inc); + + if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) && + (cond && include_condition_is_true(cond, cond_len)) && + !strcmp(key, "path")) + ret = handle_path_include(value, inc); + return ret; } @@ -201,11 +293,105 @@ void git_config_push_parameter(const char *text) strbuf_release(&env); } +static inline int iskeychar(int c) +{ + return isalnum(c) || c == '-'; +} + +/* + * Auxiliary function to sanity-check and split the key into the section + * identifier and variable name. + * + * Returns 0 on success, -1 when there is an invalid character in the key and + * -2 if there is no section name in the key. + * + * store_key - pointer to char* which will hold a copy of the key with + * lowercase section and variable name + * baselen - pointer to int which will hold the length of the + * section + subsection part, can be NULL + */ +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) +{ + int i, dot, baselen; + const char *last_dot = strrchr(key, '.'); + + /* + * Since "key" actually contains the section name and the real + * key name separated by a dot, we have to know where the dot is. + */ + + if (last_dot == NULL || last_dot == key) { + if (!quiet) + error("key does not contain a section: %s", key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + if (!last_dot[1]) { + if (!quiet) + error("key does not contain variable name: %s", key); + return -CONFIG_NO_SECTION_OR_NAME; + } + + baselen = last_dot - key; + if (baselen_) + *baselen_ = baselen; + + /* + * Validate the key and while at it, lower case it for matching. + */ + if (store_key) + *store_key = xmallocz(strlen(key)); + + dot = 0; + for (i = 0; key[i]; i++) { + unsigned char c = key[i]; + if (c == '.') + dot = 1; + /* Leave the extended basename untouched.. */ + if (!dot || i > baselen) { + if (!iskeychar(c) || + (i == baselen + 1 && !isalpha(c))) { + if (!quiet) + error("invalid key: %s", key); + goto out_free_ret_1; + } + c = tolower(c); + } else if (c == '\n') { + if (!quiet) + error("invalid key (newline): %s", key); + goto out_free_ret_1; + } + if (store_key) + (*store_key)[i] = c; + } + + return 0; + +out_free_ret_1: + if (store_key) { + free(*store_key); + *store_key = NULL; + } + return -CONFIG_INVALID_KEY; +} + +int git_config_parse_key(const char *key, char **store_key, int *baselen) +{ + return git_config_parse_key_1(key, store_key, baselen, 0); +} + +int git_config_key_is_valid(const char *key) +{ + return !git_config_parse_key_1(key, NULL, NULL, 1); +} + int git_config_parse_parameter(const char *text, config_fn_t fn, void *data) { const char *value; + char *canonical_name; struct strbuf **pair; + int ret; pair = strbuf_split_str(text, '=', 2); if (!pair[0]) @@ -223,13 +409,15 @@ int git_config_parse_parameter(const char *text, strbuf_list_free(pair); return error("bogus config parameter: %s", text); } - strbuf_tolower(pair[0]); - if (fn(pair[0]->buf, value, data) < 0) { - strbuf_list_free(pair); - return -1; + + if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) { + ret = -1; + } else { + ret = (fn(canonical_name, value, data) < 0) ? -1 : 0; + free(canonical_name); } strbuf_list_free(pair); - return 0; + return ret; } int git_config_from_parameters(config_fn_t fn, void *data) @@ -356,11 +544,6 @@ static char *parse_value(void) } } -static inline int iskeychar(int c) -{ - return isalnum(c) || c == '-'; -} - static int get_value(config_fn_t fn, void *data, struct strbuf *name) { int c; @@ -1412,6 +1595,31 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) } } +void read_early_config(config_fn_t cb, void *data) +{ + struct strbuf buf = STRBUF_INIT; + + git_config_with_options(cb, data, NULL, 1); + + /* + * When setup_git_directory() was not yet asked to discover the + * GIT_DIR, we ask discover_git_directory() to figure out whether there + * is any repository config we should use (but unlike + * setup_git_directory_gently(), no global state is changed, most + * notably, the current working directory is still the same after the + * call). + */ + if (!have_git_dir() && discover_git_directory(&buf)) { + struct git_config_source repo_config; + + memset(&repo_config, 0, sizeof(repo_config)); + strbuf_addstr(&buf, "/config"); + repo_config.file = buf.buf; + git_config_with_options(cb, data, &repo_config, 1); + } + strbuf_release(&buf); +} + static void git_config_check_init(void); void git_config(config_fn_t fn, void *data) @@ -1712,6 +1920,19 @@ int git_config_get_pathname(const char *key, const char **dest) return ret; } +int git_config_get_expiry(const char *key, const char **output) +{ + int ret = git_config_get_string_const(key, output); + if (ret) + return ret; + if (strcmp(*output, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(*output) >= now) + git_die_config(key, _("Invalid %s: '%s'"), key, *output); + } + return ret; +} + int git_config_get_untracked_cache(void) { int val = -1; @@ -1728,14 +1949,39 @@ int git_config_get_untracked_cache(void) if (!strcasecmp(v, "keep")) return -1; - error("unknown core.untrackedCache value '%s'; " - "using 'keep' default value", v); + error(_("unknown core.untrackedCache value '%s'; " + "using 'keep' default value"), v); return -1; } return -1; /* default value */ } +int git_config_get_split_index(void) +{ + int val; + + if (!git_config_get_maybe_bool("core.splitindex", &val)) + return val; + + return -1; /* default value */ +} + +int git_config_get_max_percent_split_change(void) +{ + int val = -1; + + if (!git_config_get_int("splitindex.maxpercentchange", &val)) { + if (0 <= val && val <= 100) + return val; + + return error(_("splitIndex.maxPercentChange value '%d' " + "should be between 0 and 100"), val); + } + + return -1; /* default value */ +} + NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr) { @@ -1990,93 +2236,6 @@ void git_config_set(const char *key, const char *value) } /* - * Auxiliary function to sanity-check and split the key into the section - * identifier and variable name. - * - * Returns 0 on success, -1 when there is an invalid character in the key and - * -2 if there is no section name in the key. - * - * store_key - pointer to char* which will hold a copy of the key with - * lowercase section and variable name - * baselen - pointer to int which will hold the length of the - * section + subsection part, can be NULL - */ -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet) -{ - int i, dot, baselen; - const char *last_dot = strrchr(key, '.'); - - /* - * Since "key" actually contains the section name and the real - * key name separated by a dot, we have to know where the dot is. - */ - - if (last_dot == NULL || last_dot == key) { - if (!quiet) - error("key does not contain a section: %s", key); - return -CONFIG_NO_SECTION_OR_NAME; - } - - if (!last_dot[1]) { - if (!quiet) - error("key does not contain variable name: %s", key); - return -CONFIG_NO_SECTION_OR_NAME; - } - - baselen = last_dot - key; - if (baselen_) - *baselen_ = baselen; - - /* - * Validate the key and while at it, lower case it for matching. - */ - if (store_key) - *store_key = xmallocz(strlen(key)); - - dot = 0; - for (i = 0; key[i]; i++) { - unsigned char c = key[i]; - if (c == '.') - dot = 1; - /* Leave the extended basename untouched.. */ - if (!dot || i > baselen) { - if (!iskeychar(c) || - (i == baselen + 1 && !isalpha(c))) { - if (!quiet) - error("invalid key: %s", key); - goto out_free_ret_1; - } - c = tolower(c); - } else if (c == '\n') { - if (!quiet) - error("invalid key (newline): %s", key); - goto out_free_ret_1; - } - if (store_key) - (*store_key)[i] = c; - } - - return 0; - -out_free_ret_1: - if (store_key) { - free(*store_key); - *store_key = NULL; - } - return -CONFIG_INVALID_KEY; -} - -int git_config_parse_key(const char *key, char **store_key, int *baselen) -{ - return git_config_parse_key_1(key, store_key, baselen, 0); -} - -int git_config_key_is_valid(const char *key) -{ - return !git_config_parse_key_1(key, NULL, NULL, 1); -} - -/* * If value==NULL, unset in (remove from) config, * if value_regex!=NULL, disregard key/value pairs where value does not match. * if value_regex==CONFIG_REGEX_NONE, do not match any existing values @@ -2536,11 +2695,10 @@ int parse_config_key(const char *var, const char **subsection, int *subsection_len, const char **key) { - int section_len = strlen(section); const char *dot; /* Does it start with "section." ? */ - if (!starts_with(var, section) || var[section_len] != '.') + if (!skip_prefix(var, section, &var) || *var != '.') return -1; /* @@ -2552,12 +2710,16 @@ int parse_config_key(const char *var, *key = dot + 1; /* Did we have a subsection at all? */ - if (dot == var + section_len) { - *subsection = NULL; - *subsection_len = 0; + if (dot == var) { + if (subsection) { + *subsection = NULL; + *subsection_len = 0; + } } else { - *subsection = var + section_len + 1; + if (!subsection) + return -1; + *subsection = var + 1; *subsection_len = dot - *subsection; } @@ -691,6 +691,68 @@ static const char *get_ssh_command(void) return NULL; } +static int override_ssh_variant(int *port_option, int *needs_batch) +{ + char *variant; + + variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT")); + if (!variant && + git_config_get_string("ssh.variant", &variant)) + return 0; + + if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) { + *port_option = 'P'; + *needs_batch = 0; + } else if (!strcmp(variant, "tortoiseplink")) { + *port_option = 'P'; + *needs_batch = 1; + } else { + *port_option = 'p'; + *needs_batch = 0; + } + free(variant); + return 1; +} + +static void handle_ssh_variant(const char *ssh_command, int is_cmdline, + int *port_option, int *needs_batch) +{ + const char *variant; + char *p = NULL; + + if (override_ssh_variant(port_option, needs_batch)) + return; + + if (!is_cmdline) { + p = xstrdup(ssh_command); + variant = basename(p); + } else { + const char **ssh_argv; + + p = xstrdup(ssh_command); + if (split_cmdline(p, &ssh_argv)) { + variant = basename((char *)ssh_argv[0]); + /* + * At this point, variant points into the buffer + * referenced by p, hence we do not need ssh_argv + * any longer. + */ + free(ssh_argv); + } else + return; + } + + if (!strcasecmp(variant, "plink") || + !strcasecmp(variant, "plink.exe")) + *port_option = 'P'; + else if (!strcasecmp(variant, "tortoiseplink") || + !strcasecmp(variant, "tortoiseplink.exe")) { + *port_option = 'P'; + *needs_batch = 1; + } + free(p); +} + /* * This returns a dummy child_process if the transport protocol does not * need fork(2), or a struct child_process object if it does. Once done, @@ -769,7 +831,8 @@ struct child_process *git_connect(int fd[2], const char *url, conn->in = conn->out = -1; if (protocol == PROTO_SSH) { const char *ssh; - int putty = 0, tortoiseplink = 0; + int needs_batch = 0; + int port_option = 'p'; char *ssh_host = hostandport; const char *port = NULL; transport_check_allowed("ssh"); @@ -792,10 +855,10 @@ struct child_process *git_connect(int fd[2], const char *url, } ssh = get_ssh_command(); - if (!ssh) { - const char *base; - char *ssh_dup; - + if (ssh) + handle_ssh_variant(ssh, 1, &port_option, + &needs_batch); + else { /* * GIT_SSH is the no-shell version of * GIT_SSH_COMMAND (and must remain so for @@ -806,17 +869,10 @@ struct child_process *git_connect(int fd[2], const char *url, ssh = getenv("GIT_SSH"); if (!ssh) ssh = "ssh"; - - ssh_dup = xstrdup(ssh); - base = basename(ssh_dup); - - tortoiseplink = !strcasecmp(base, "tortoiseplink") || - !strcasecmp(base, "tortoiseplink.exe"); - putty = tortoiseplink || - !strcasecmp(base, "plink") || - !strcasecmp(base, "plink.exe"); - - free(ssh_dup); + else + handle_ssh_variant(ssh, 0, + &port_option, + &needs_batch); } argv_array_push(&conn->args, ssh); @@ -824,11 +880,11 @@ struct child_process *git_connect(int fd[2], const char *url, argv_array_push(&conn->args, "-4"); else if (flags & CONNECT_IPV6) argv_array_push(&conn->args, "-6"); - if (tortoiseplink) + if (needs_batch) argv_array_push(&conn->args, "-batch"); if (port) { - /* P is for PuTTY, p is for OpenSSH */ - argv_array_push(&conn->args, putty ? "-P" : "-p"); + argv_array_pushf(&conn->args, + "-%c", port_option); argv_array_push(&conn->args, port); } argv_array_push(&conn->args, ssh_host); diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 2d7f25d99f..4ba98b7eaf 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -24,3 +24,19 @@ expression n; @@ - memcpy(dst, src, n * sizeof(T)); + COPY_ARRAY(dst, src, n); + +@@ +type T; +T *ptr; +expression n; +@@ +- ptr = xmalloc(n * sizeof(*ptr)); ++ ALLOC_ARRAY(ptr, n); + +@@ +type T; +T *ptr; +expression n; +@@ +- ptr = xmalloc(n * sizeof(T)); ++ ALLOC_ARRAY(ptr, n); diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci index 63995f22ff..1d580e49b0 100644 --- a/contrib/coccinelle/strbuf.cocci +++ b/contrib/coccinelle/strbuf.cocci @@ -38,3 +38,9 @@ expression E1, E2, E3; @@ - strbuf_addstr(E1, find_unique_abbrev(E2, E3)); + strbuf_add_unique_abbrev(E1, E2, E3); + +@@ +expression E1, E2; +@@ +- strbuf_addstr(E1, real_path(E2)); ++ strbuf_add_real_path(E1, E2); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 41ee52991d..fc32286a43 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -34,21 +34,41 @@ case "$COMP_WORDBREAKS" in *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac +# Discovers the path to the git repository taking any '--git-dir=<path>' and +# '-C <path>' options into account and stores it in the $__git_repo_path +# variable. +__git_find_repo_path () +{ + if [ -n "$__git_repo_path" ]; then + # we already know where it is + return + fi + + if [ -n "${__git_C_args-}" ]; then + __git_repo_path="$(git "${__git_C_args[@]}" \ + ${__git_dir:+--git-dir="$__git_dir"} \ + rev-parse --absolute-git-dir 2>/dev/null)" + elif [ -n "${__git_dir-}" ]; then + test -d "$__git_dir" && + __git_repo_path="$__git_dir" + elif [ -n "${GIT_DIR-}" ]; then + test -d "${GIT_DIR-}" && + __git_repo_path="$GIT_DIR" + elif [ -d .git ]; then + __git_repo_path=.git + else + __git_repo_path="$(git rev-parse --git-dir 2>/dev/null)" + fi +} + +# Deprecated: use __git_find_repo_path() and $__git_repo_path instead # __gitdir accepts 0 or 1 arguments (i.e., location) # returns location of .git repo __gitdir () { if [ -z "${1-}" ]; then - if [ -n "${__git_dir-}" ]; then - echo "$__git_dir" - elif [ -n "${GIT_DIR-}" ]; then - test -d "${GIT_DIR-}" || return 1 - echo "$GIT_DIR" - elif [ -d .git ]; then - echo .git - else - git rev-parse --git-dir 2>/dev/null - fi + __git_find_repo_path || return 1 + echo "$__git_repo_path" elif [ -d "$1/.git" ]; then echo "$1/.git" else @@ -56,6 +76,14 @@ __gitdir () fi } +# Runs git with all the options given as argument, respecting any +# '--git-dir=<path>' and '-C <path>' options present on the command line +__git () +{ + git ${__git_C_args:+"${__git_C_args[@]}"} \ + ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null +} + # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@ -283,11 +311,11 @@ __gitcomp_file () __git_ls_files_helper () { if [ "$2" == "--committable" ]; then - git -C "$1" diff-index --name-only --relative HEAD + __git -C "$1" diff-index --name-only --relative HEAD else # NOTE: $2 is not quoted in order to support multiple options - git -C "$1" ls-files --exclude-standard $2 - fi 2>/dev/null + __git -C "$1" ls-files --exclude-standard $2 + fi } @@ -299,47 +327,61 @@ __git_ls_files_helper () # slash. __git_index_files () { - local dir="$(__gitdir)" root="${2-.}" file + local root="${2-.}" file - if [ -d "$dir" ]; then - __git_ls_files_helper "$root" "$1" | - while read -r file; do - case "$file" in - ?*/*) echo "${file%%/*}" ;; - *) echo "$file" ;; - esac - done | sort | uniq - fi + __git_ls_files_helper "$root" "$1" | + while read -r file; do + case "$file" in + ?*/*) echo "${file%%/*}" ;; + *) echo "$file" ;; + esac + done | sort | uniq } __git_heads () { - local dir="$(__gitdir)" - if [ -d "$dir" ]; then - git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ - refs/heads - return - fi + __git for-each-ref --format='%(refname:short)' refs/heads } __git_tags () { - local dir="$(__gitdir)" - if [ -d "$dir" ]; then - git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ - refs/tags - return - fi + __git for-each-ref --format='%(refname:short)' refs/tags } -# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments -# presence of 2nd argument means use the guess heuristic employed -# by checkout for tracking branches +# Lists refs from the local (by default) or from a remote repository. +# It accepts 0, 1 or 2 arguments: +# 1: The remote to list refs from (optional; ignored, if set but empty). +# Can be the name of a configured remote, a path, or a URL. +# 2: In addition to local refs, list unique branches from refs/remotes/ for +# 'git checkout's tracking DWIMery (optional; ignored, if set but empty). __git_refs () { - local i hash dir="$(__gitdir "${1-}")" track="${2-}" + local i hash dir track="${2-}" + local list_refs_from=path remote="${1-}" local format refs pfx - if [ -d "$dir" ]; then + + __git_find_repo_path + dir="$__git_repo_path" + + if [ -z "$remote" ]; then + if [ -z "$dir" ]; then + return + fi + else + if __git_is_configured_remote "$remote"; then + # configured remote takes precedence over a + # local directory with the same name + list_refs_from=remote + elif [ -d "$remote/.git" ]; then + dir="$remote/.git" + elif [ -d "$remote" ]; then + dir="$remote" + else + list_refs_from=url + fi + fi + + if [ "$list_refs_from" = path ]; then case "$cur" in refs|refs/*) format="refname" @@ -355,14 +397,14 @@ __git_refs () refs="refs/tags refs/heads refs/remotes" ;; esac - git --git-dir="$dir" for-each-ref --format="$pfx%($format)" \ + __git_dir="$dir" __git for-each-ref --format="$pfx%($format)" \ $refs if [ -n "$track" ]; then # employ the heuristic used by git checkout # Try to find a remote branch that matches the completion word # but only output if the branch name is unique local ref entry - git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \ + __git for-each-ref --shell --format="ref=%(refname:short)" \ "refs/remotes/" | \ while read -r entry; do eval "$entry" @@ -376,7 +418,7 @@ __git_refs () fi case "$cur" in refs|refs/*) - git ls-remote "$dir" "$cur*" 2>/dev/null | \ + __git ls-remote "$remote" "$cur*" | \ while read -r hash i; do case "$i" in *^{}) ;; @@ -385,9 +427,21 @@ __git_refs () done ;; *) - echo "HEAD" - git for-each-ref --format="%(refname:short)" -- \ - "refs/remotes/$dir/" 2>/dev/null | sed -e "s#^$dir/##" + if [ "$list_refs_from" = remote ]; then + echo "HEAD" + __git for-each-ref --format="%(refname:short)" \ + "refs/remotes/$remote/" | sed -e "s#^$remote/##" + else + __git ls-remote "$remote" HEAD \ + "refs/tags/*" "refs/heads/*" "refs/remotes/*" | + while read -r hash i; do + case "$i" in + *^{}) ;; + refs/*) echo "${i#refs/*/}" ;; + *) echo "$i" ;; # symbolic refs + esac + done + fi ;; esac } @@ -405,7 +459,7 @@ __git_refs2 () __git_refs_remotes () { local i hash - git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \ + __git ls-remote "$1" 'refs/heads/*' | \ while read -r hash i; do echo "$i:refs/remotes/$1/${i#refs/heads/}" done @@ -413,9 +467,21 @@ __git_refs_remotes () __git_remotes () { - local d="$(__gitdir)" - test -d "$d/remotes" && ls -1 "$d/remotes" - git --git-dir="$d" remote + __git_find_repo_path + test -d "$__git_repo_path/remotes" && ls -1 "$__git_repo_path/remotes" + __git remote +} + +# Returns true if $1 matches the name of a configured remote, false otherwise. +__git_is_configured_remote () +{ + local remote + for remote in $(__git_remotes); do + if [ "$remote" = "$1" ]; then + return 0 + fi + done + return 1 } __git_list_merge_strategies () @@ -469,7 +535,7 @@ __git_complete_revlist_file () *) pfx="$ref:$pfx" ;; esac - __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \ + __gitcomp_nl "$(__git ls-tree "$ls" \ | sed '/^100... blob /{ s,^.* ,, s,$, , @@ -747,7 +813,7 @@ __git_compute_porcelain_commands () __git_get_config_variables () { local section="$1" i IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --name-only --get-regexp "^$section\..*" 2>/dev/null); do + for i in $(__git config --name-only --get-regexp "^$section\..*"); do echo "${i#$section.}" done } @@ -765,8 +831,7 @@ __git_aliases () # __git_aliased_command requires 1 argument __git_aliased_command () { - local word cmdline=$(git --git-dir="$(__gitdir)" \ - config --get "alias.$1") + local word cmdline=$(__git config --get "alias.$1") for word in $cmdline; do case "$word" in \!gitk|gitk) @@ -842,7 +907,7 @@ __git_get_option_value () done if [ -n "$config_key" ] && [ -z "$result" ]; then - result="$(git --git-dir="$(__gitdir)" config "$config_key")" + result="$(__git config "$config_key")" fi echo "$result" @@ -901,8 +966,8 @@ __git_whitespacelist="nowarn warn error error-all fix" _git_am () { - local dir="$(__gitdir)" - if [ -d "$dir"/rebase-apply ]; then + __git_find_repo_path + if [ -d "$__git_repo_path"/rebase-apply ]; then __gitcomp "--skip --continue --resolved --abort" return fi @@ -990,7 +1055,8 @@ _git_bisect () local subcommands="start bad good skip reset visualize replay log run" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then - if [ -f "$(__gitdir)"/BISECT_START ]; then + __git_find_repo_path + if [ -f "$__git_repo_path"/BISECT_START ]; then __gitcomp "$subcommands" else __gitcomp "replay start" @@ -1096,8 +1162,8 @@ _git_cherry () _git_cherry_pick () { - local dir="$(__gitdir)" - if [ -f "$dir"/CHERRY_PICK_HEAD ]; then + __git_find_repo_path + if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then __gitcomp "--continue --quit --abort" return fi @@ -1192,7 +1258,7 @@ _git_commit () return esac - if git rev-parse --verify --quiet HEAD >/dev/null; then + if __git rev-parse --verify --quiet HEAD >/dev/null; then __git_complete_index_file "--committable" else # This is the first commit @@ -1207,6 +1273,7 @@ _git_describe () __gitcomp " --all --tags --contains --abbrev= --candidates= --exact-match --debug --long --match --always --first-parent + --exclude " return esac @@ -1500,10 +1567,10 @@ __git_log_date_formats="relative iso8601 rfc2822 short local default raw" _git_log () { __git_has_doubledash && return + __git_find_repo_path - local g="$(git rev-parse --git-dir 2>/dev/null)" local merge="" - if [ -f "$g/MERGE_HEAD" ]; then + if [ -f "$__git_repo_path/MERGE_HEAD" ]; then merge="--merge" fi case "$cur" in @@ -1750,11 +1817,12 @@ _git_push () _git_rebase () { - local dir="$(__gitdir)" - if [ -f "$dir"/rebase-merge/interactive ]; then + __git_find_repo_path + if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then __gitcomp "--continue --skip --abort --quit --edit-todo" return - elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then + elif [ -d "$__git_repo_path"/rebase-apply ] || \ + [ -d "$__git_repo_path"/rebase-merge ]; then __gitcomp "--continue --skip --abort --quit" return fi @@ -1802,9 +1870,7 @@ _git_send_email () { case "$prev" in --to|--cc|--bcc|--from) - __gitcomp " - $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null) - " + __gitcomp "$(__git send-email --dump-aliases)" return ;; esac @@ -1834,9 +1900,7 @@ _git_send_email () return ;; --to=*|--cc=*|--bcc=*|--from=*) - __gitcomp " - $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null) - " "" "${cur#--*=}" + __gitcomp "$(__git send-email --dump-aliases)" "" "${cur#--*=}" return ;; --*) @@ -1930,7 +1994,7 @@ __git_config_get_set_variables () c=$((--c)) done - git --git-dir="$(__gitdir)" config $config_file --name-only --list 2>/dev/null + __git config $config_file --name-only --list } _git_config () @@ -1965,9 +2029,8 @@ _git_config () remote.*.push) local remote="${prev#remote.}" remote="${remote%.push}" - __gitcomp_nl "$(git --git-dir="$(__gitdir)" \ - for-each-ref --format='%(refname):%(refname)' \ - refs/heads)" + __gitcomp_nl "$(__git for-each-ref \ + --format='%(refname):%(refname)' refs/heads)" return ;; pull.twohead|pull.octopus) @@ -2482,8 +2545,8 @@ _git_reset () _git_revert () { - local dir="$(__gitdir)" - if [ -f "$dir"/REVERT_HEAD ]; then + __git_find_repo_path + if [ -f "$__git_repo_path"/REVERT_HEAD ]; then __gitcomp "--continue --quit --abort" return fi @@ -2606,12 +2669,12 @@ _git_stash () if [ $cword -eq 3 ]; then __gitcomp_nl "$(__git_refs)"; else - __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ + __gitcomp_nl "$(__git stash list \ | sed -n -e 's/:.*//p')" fi ;; show,*|apply,*|drop,*|pop,*) - __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ + __gitcomp_nl "$(__git stash list \ | sed -n -e 's/:.*//p')" ;; *) @@ -2838,7 +2901,8 @@ _git_worktree () __git_main () { - local i c=1 command __git_dir + local i c=1 command __git_dir __git_repo_path + local __git_C_args C_args_count=0 while [ $c -lt $cword ]; do i="${words[c]}" @@ -2848,6 +2912,10 @@ __git_main () --bare) __git_dir="." ;; --help) command="help"; break ;; -c|--work-tree|--namespace) ((c++)) ;; + -C) __git_C_args[C_args_count++]=-C + ((c++)) + __git_C_args[C_args_count++]="${words[c]}" + ;; -*) ;; *) command="$i"; break ;; esac @@ -2855,6 +2923,17 @@ __git_main () done if [ -z "$command" ]; then + case "$prev" in + --git-dir|-C|--work-tree) + # these need a path argument, let's fall back to + # Bash filename completion + return + ;; + -c|--namespace) + # we don't support completing these options' arguments + return + ;; + esac case "$cur" in --*) __gitcomp " --paginate @@ -2880,13 +2959,13 @@ __git_main () fi local completion_func="_git_${command//-/_}" - declare -f $completion_func >/dev/null && $completion_func && return + declare -f $completion_func >/dev/null 2>/dev/null && $completion_func && return local expansion=$(__git_aliased_command "$command") if [ -n "$expansion" ]; then words[1]=$expansion completion_func="_git_${expansion//-/_}" - declare -f $completion_func >/dev/null && $completion_func + declare -f $completion_func >/dev/null 2>/dev/null && $completion_func fi } @@ -2894,9 +2973,11 @@ __gitk_main () { __git_has_doubledash && return - local g="$(__gitdir)" + local __git_repo_path + __git_find_repo_path + local merge="" - if [ -f "$g/MERGE_HEAD" ]; then + if [ -f "$__git_repo_path/MERGE_HEAD" ]; then merge="--merge" fi case "$cur" in diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 97eacd7832..c6cbef38c2 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -82,6 +82,7 @@ # contains relative to newer annotated tag (v1.6.3.2~35) # branch relative to newer tag or branch (master~4) # describe relative to older annotated tag (v1.6.3.1-13-gdd42c2f) +# tag relative to any older tag (v1.6.3.1-13-gdd42c2f) # default exactly matching tag # # If you would like a colored hint about the current dirty state, set @@ -443,6 +444,8 @@ __git_ps1 () git describe --contains HEAD ;; (branch) git describe --contains --all HEAD ;; + (tag) + git describe --tags HEAD ;; (describe) git describe HEAD ;; (* | default) @@ -1028,7 +1028,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len, return 1; } -static enum crlf_action git_path_check_crlf(struct git_attr_check *check) +static enum crlf_action git_path_check_crlf(struct attr_check_item *check) { const char *value = check->value; @@ -1045,7 +1045,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check) return CRLF_UNDEFINED; } -static enum eol git_path_check_eol(struct git_attr_check *check) +static enum eol git_path_check_eol(struct attr_check_item *check) { const char *value = check->value; @@ -1058,7 +1058,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check) return EOL_UNSET; } -static struct convert_driver *git_path_check_convert(struct git_attr_check *check) +static struct convert_driver *git_path_check_convert(struct attr_check_item *check) { const char *value = check->value; struct convert_driver *drv; @@ -1071,7 +1071,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec return NULL; } -static int git_path_check_ident(struct git_attr_check *check) +static int git_path_check_ident(struct attr_check_item *check) { const char *value = check->value; @@ -1085,24 +1085,19 @@ struct conv_attrs { int ident; }; -static const char *conv_attr_name[] = { - "crlf", "ident", "filter", "eol", "text", -}; -#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name) - static void convert_attrs(struct conv_attrs *ca, const char *path) { - int i; - static struct git_attr_check ccheck[NUM_CONV_ATTRS]; + static struct attr_check *check; - if (!ccheck[0].attr) { - for (i = 0; i < NUM_CONV_ATTRS; i++) - ccheck[i].attr = git_attr(conv_attr_name[i]); + if (!check) { + check = attr_check_initl("crlf", "ident", "filter", + "eol", "text", NULL); user_convert_tail = &user_convert; git_config(read_convert_config, NULL); } - if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) { + if (!git_check_attr(path, check)) { + struct attr_check_item *ccheck = check->items; ca->crlf_action = git_path_check_crlf(ccheck + 4); if (ca->crlf_action == CRLF_UNDEFINED) ca->crlf_action = git_path_check_crlf(ccheck + 0); diff --git a/credential-cache.c b/credential-cache.c index cc8a6ee192..3cbd420019 100644 --- a/credential-cache.c +++ b/credential-cache.c @@ -83,6 +83,19 @@ static void do_cache(const char *socket, const char *action, int timeout, strbuf_release(&buf); } +static char *get_socket_path(void) +{ + struct stat sb; + char *old_dir, *socket; + old_dir = expand_user_path("~/.git-credential-cache"); + if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode)) + socket = xstrfmt("%s/socket", old_dir); + else + socket = xdg_cache_home("credential/socket"); + free(old_dir); + return socket; +} + int cmd_main(int argc, const char **argv) { char *socket_path = NULL; @@ -106,7 +119,7 @@ int cmd_main(int argc, const char **argv) op = argv[0]; if (!socket_path) - socket_path = expand_user_path("~/.git-credential-cache/socket"); + socket_path = get_socket_path(); if (!socket_path) die("unable to find a suitable socket path; use --socket"); diff --git a/diff-no-index.c b/diff-no-index.c index df762fd0f7..79229382b0 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -236,7 +236,7 @@ static void fixup_paths(const char **path, struct strbuf *replacement) void diff_no_index(struct rev_info *revs, int argc, const char **argv) { - int i, prefixlen; + int i; const char *paths[2]; struct strbuf replacement = STRBUF_INIT; const char *prefix = revs->prefix; @@ -257,7 +257,6 @@ void diff_no_index(struct rev_info *revs, } } - prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < 2; i++) { const char *p = argv[argc - 2 + i]; if (!strcmp(p, "-")) @@ -266,8 +265,8 @@ void diff_no_index(struct rev_info *revs, * path that is "-", spell it as "./-". */ p = file_from_standard_input; - else if (prefixlen) - p = xstrdup(prefix_filename(prefix, prefixlen, p)); + else if (prefix) + p = prefix_filename(prefix, p); paths[i] = p; } @@ -4023,8 +4023,7 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--pickaxe-regex")) options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { - const char *path = prefix_filename(prefix, strlen(prefix), optarg); - options->orderfile = xstrdup(path); + options->orderfile = prefix_filename(prefix, optarg); return argcount; } else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) { @@ -4071,13 +4070,14 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--no-function-context")) DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { - const char *path = prefix_filename(prefix, strlen(prefix), optarg); + char *path = prefix_filename(prefix, optarg); options->file = fopen(path, "w"); if (!options->file) die_errno("Could not open '%s'", path); options->close_file = 1; if (options->use_color != GIT_COLOR_ALWAYS) options->use_color = GIT_COLOR_NEVER; + free(path); return argcount; } else return 0; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 9795ca1c15..341529b5a8 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -81,12 +81,15 @@ static unsigned int contains(mmfile_t *mf, regex_t *regexp, kwset_t kws) regmatch_t regmatch; int flags = 0; - while (*data && + while (sz && *data && !regexec_buf(regexp, data, sz, 1, ®match, flags)) { flags |= REG_NOTBOL; data += regmatch.rm_eo; - if (*data && regmatch.rm_so == regmatch.rm_eo) + sz -= regmatch.rm_eo; + if (sz && *data && regmatch.rm_so == regmatch.rm_eo) { data++; + sz--; + } cnt++; } @@ -9,6 +9,7 @@ */ #include "cache.h" #include "dir.h" +#include "attr.h" #include "refs.h" #include "wildmatch.h" #include "pathspec.h" @@ -134,7 +135,8 @@ static size_t common_prefix_len(const struct pathspec *pathspec) PATHSPEC_LITERAL | PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); + PATHSPEC_EXCLUDE | + PATHSPEC_ATTR); for (n = 0; n < pathspec->nr; n++) { size_t i = 0, len = 0, item_len; @@ -209,6 +211,36 @@ int within_depth(const char *name, int namelen, #define DO_MATCH_DIRECTORY (1<<1) #define DO_MATCH_SUBMODULE (1<<2) +static int match_attrs(const char *name, int namelen, + const struct pathspec_item *item) +{ + int i; + + git_check_attr(name, item->attr_check); + 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 @@ -261,6 +293,9 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, strncmp(item->match, name - prefix, item->prefix)) return 0; + if (item->attr_match_nr && !match_attrs(name, namelen, item)) + return 0; + /* If the match was just the prefix, we matched */ if (!*match) return MATCHED_RECURSIVELY; @@ -339,7 +374,8 @@ static int do_match_pathspec(const struct pathspec *ps, PATHSPEC_LITERAL | PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); + PATHSPEC_EXCLUDE | + PATHSPEC_ATTR); if (!ps->nr) { if (!ps->recursive || @@ -1361,7 +1397,8 @@ static int simplify_away(const char *path, int pathlen, PATHSPEC_LITERAL | PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); + PATHSPEC_EXCLUDE | + PATHSPEC_ATTR); for (i = 0; i < pathspec->nr; i++) { const struct pathspec_item *item = &pathspec->items[i]; @@ -2728,23 +2765,33 @@ void untracked_cache_add_to_index(struct index_state *istate, /* Update gitfile and core.worktree setting to connect work tree and git dir */ void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_) { - struct strbuf file_name = STRBUF_INIT; + struct strbuf gitfile_sb = STRBUF_INIT; + struct strbuf cfg_sb = STRBUF_INIT; struct strbuf rel_path = STRBUF_INIT; - char *git_dir = real_pathdup(git_dir_, 1); - char *work_tree = real_pathdup(work_tree_, 1); + char *git_dir, *work_tree; - /* Update gitfile */ - strbuf_addf(&file_name, "%s/.git", work_tree); - write_file(file_name.buf, "gitdir: %s", - relative_path(git_dir, work_tree, &rel_path)); + /* Prepare .git file */ + strbuf_addf(&gitfile_sb, "%s/.git", work_tree_); + if (safe_create_leading_directories_const(gitfile_sb.buf)) + die(_("could not create directories for %s"), gitfile_sb.buf); + + /* Prepare config file */ + strbuf_addf(&cfg_sb, "%s/config", git_dir_); + if (safe_create_leading_directories_const(cfg_sb.buf)) + die(_("could not create directories for %s"), cfg_sb.buf); + git_dir = real_pathdup(git_dir_, 1); + work_tree = real_pathdup(work_tree_, 1); + + /* Write .git file */ + write_file(gitfile_sb.buf, "gitdir: %s", + relative_path(git_dir, work_tree, &rel_path)); /* Update core.worktree setting */ - strbuf_reset(&file_name); - strbuf_addf(&file_name, "%s/config", git_dir); - git_config_set_in_file(file_name.buf, "core.worktree", + git_config_set_in_file(cfg_sb.buf, "core.worktree", relative_path(work_tree, git_dir, &rel_path)); - strbuf_release(&file_name); + strbuf_release(&gitfile_sb); + strbuf_release(&cfg_sb); strbuf_release(&rel_path); free(work_tree); free(git_dir); @@ -2,6 +2,7 @@ #include "blob.h" #include "dir.h" #include "streaming.h" +#include "submodule.h" static void create_directories(const char *path, int path_len, const struct checkout *state) @@ -146,6 +147,7 @@ static int write_entry(struct cache_entry *ce, unsigned long size; size_t wrote, newsize = 0; struct stat st; + const struct submodule *sub; if (ce_mode_s_ifmt == S_IFREG) { struct stream_filter *filter = get_stream_filter(ce->name, @@ -203,6 +205,10 @@ static int write_entry(struct cache_entry *ce, return error("cannot create temporary submodule %s", path); if (mkdir(path, 0777) < 0) return error("cannot create submodule directory %s", path); + sub = submodule_from_ce(ce); + if (sub) + return submodule_move_head(ce->name, + NULL, oid_to_hex(&ce->oid), SUBMODULE_MOVE_HEAD_FORCE); break; default: return error("unknown file mode for %s in index", path); @@ -259,7 +265,31 @@ int checkout_entry(struct cache_entry *ce, strbuf_add(&path, ce->name, ce_namelen(ce)); if (!check_path(path.buf, path.len, &st, state->base_dir_len)) { + const struct submodule *sub; unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); + /* + * Needs to be checked before !changed returns early, + * as the possibly empty directory was not changed + */ + sub = submodule_from_ce(ce); + if (sub) { + int err; + if (!is_submodule_populated_gently(ce->name, &err)) { + struct stat sb; + if (lstat(ce->name, &sb)) + die(_("could not stat file '%s'"), ce->name); + if (!(st.st_mode & S_IFDIR)) + unlink_or_warn(ce->name); + + return submodule_move_head(ce->name, + NULL, oid_to_hex(&ce->oid), + SUBMODULE_MOVE_HEAD_FORCE); + } else + return submodule_move_head(ce->name, + "HEAD", oid_to_hex(&ce->oid), + SUBMODULE_MOVE_HEAD_FORCE); + } + if (!changed) return 0; if (!state->force) { diff --git a/environment.c b/environment.c index 42dc3106d2..2fdba76222 100644 --- a/environment.c +++ b/environment.c @@ -296,18 +296,16 @@ int odb_mkstemp(char *template, size_t limit, const char *pattern) return xmkstemp_mode(template, mode); } -int odb_pack_keep(char *name, size_t namesz, const unsigned char *sha1) +int odb_pack_keep(const char *name) { int fd; - snprintf(name, namesz, "%s/pack/pack-%s.keep", - get_object_directory(), sha1_to_hex(sha1)); fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600); if (0 <= fd) return fd; /* slow path */ - safe_create_leading_directories(name); + safe_create_leading_directories_const(name); return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); } diff --git a/fast-import.c b/fast-import.c index 64fe602f0b..41a539f97d 100644 --- a/fast-import.c +++ b/fast-import.c @@ -940,41 +940,40 @@ static const char *create_index(void) static char *keep_pack(const char *curr_index_name) { - static char name[PATH_MAX]; static const char *keep_msg = "fast-import"; + struct strbuf name = STRBUF_INIT; int keep_fd; - keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1); + odb_pack_name(&name, pack_data->sha1, "keep"); + keep_fd = odb_pack_keep(name.buf); if (keep_fd < 0) die_errno("cannot create keep file"); write_or_die(keep_fd, keep_msg, strlen(keep_msg)); if (close(keep_fd)) die_errno("failed to write keep file"); - snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", - get_object_directory(), sha1_to_hex(pack_data->sha1)); - if (finalize_object_file(pack_data->pack_name, name)) + odb_pack_name(&name, pack_data->sha1, "pack"); + if (finalize_object_file(pack_data->pack_name, name.buf)) die("cannot store pack file"); - snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", - get_object_directory(), sha1_to_hex(pack_data->sha1)); - if (finalize_object_file(curr_index_name, name)) + odb_pack_name(&name, pack_data->sha1, "idx"); + if (finalize_object_file(curr_index_name, name.buf)) die("cannot store index file"); free((void *)curr_index_name); - return name; + return strbuf_detach(&name, NULL); } static void unkeep_all_packs(void) { - static char name[PATH_MAX]; + struct strbuf name = STRBUF_INIT; int k; for (k = 0; k < pack_id; k++) { struct packed_git *p = all_packs[k]; - snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", - get_object_directory(), sha1_to_hex(p->sha1)); - unlink_or_warn(name); + odb_pack_name(&name, p->sha1, "keep"); + unlink_or_warn(name.buf); } + strbuf_release(&name); } static int loosen_small_pack(const struct packed_git *p) @@ -1033,6 +1032,7 @@ static void end_packfile(void) die("core git rejected index %s", idx_name); all_packs[pack_id] = new_p; install_packed_git(new_p); + free(idx_name); /* Print the boundary */ if (pack_edges) { @@ -1752,7 +1752,7 @@ static int update_branch(struct branch *b) if (is_null_sha1(b->sha1)) { if (b->delete) - delete_ref(b->name, NULL, 0); + delete_ref(NULL, b->name, NULL, 0); return 0; } if (read_ref(b->name, old_sha1)) diff --git a/fetch-pack.c b/fetch-pack.c index 601f0779a1..d07d85ce30 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -35,6 +35,7 @@ static const char *alternate_shallow_file; #define COMMON_REF (1U << 2) #define SEEN (1U << 3) #define POPPED (1U << 4) +#define ALTERNATE (1U << 5) static int marked; @@ -67,6 +68,41 @@ static inline void print_verbose(const struct fetch_pack_args *args, fputc('\n', stderr); } +struct alternate_object_cache { + struct object **items; + size_t nr, alloc; +}; + +static void cache_one_alternate(const char *refname, + const struct object_id *oid, + void *vcache) +{ + struct alternate_object_cache *cache = vcache; + struct object *obj = parse_object(oid->hash); + + if (!obj || (obj->flags & ALTERNATE)) + return; + + obj->flags |= ALTERNATE; + ALLOC_GROW(cache->items, cache->nr + 1, cache->alloc); + cache->items[cache->nr++] = obj; +} + +static void for_each_cached_alternate(void (*cb)(struct object *)) +{ + static int initialized; + static struct alternate_object_cache cache; + size_t i; + + if (!initialized) { + for_each_alternate_ref(cache_one_alternate, &cache); + initialized = 1; + } + + for (i = 0; i < cache.nr; i++) + cb(cache.items[i]); +} + static void rev_list_push(struct commit *commit, int mark) { if (!(commit->object.flags & mark)) { @@ -253,9 +289,9 @@ static void send_request(struct fetch_pack_args *args, write_or_die(fd, buf->buf, buf->len); } -static void insert_one_alternate_ref(const struct ref *ref, void *unused) +static void insert_one_alternate_object(struct object *obj) { - rev_list_insert_ref(NULL, ref->old_oid.hash); + rev_list_insert_ref(NULL, obj->oid.hash); } #define INITIAL_FLUSH 16 @@ -298,7 +334,7 @@ static int find_common(struct fetch_pack_args *args, marked = 1; for_each_ref(rev_list_insert_ref_oid, NULL); - for_each_alternate_ref(insert_one_alternate_ref, NULL); + for_each_cached_alternate(insert_one_alternate_object); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -578,7 +614,7 @@ static void filter_refs(struct fetch_pack_args *args, break; /* definitely do not have it */ else if (cmp == 0) { keep = 1; /* definitely have it */ - sought[i]->matched = 1; + sought[i]->match_status = REF_MATCHED; } i++; } @@ -598,30 +634,32 @@ static void filter_refs(struct fetch_pack_args *args, } /* Append unmatched requests to the list */ - if ((allow_unadvertised_object_request & - (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) { - for (i = 0; i < nr_sought; i++) { - unsigned char sha1[20]; + for (i = 0; i < nr_sought; i++) { + unsigned char sha1[20]; - ref = sought[i]; - if (ref->matched) - continue; - if (get_sha1_hex(ref->name, sha1) || - ref->name[40] != '\0' || - hashcmp(sha1, ref->old_oid.hash)) - continue; + ref = sought[i]; + if (ref->match_status != REF_NOT_MATCHED) + continue; + if (get_sha1_hex(ref->name, sha1) || + ref->name[40] != '\0' || + hashcmp(sha1, ref->old_oid.hash)) + continue; - ref->matched = 1; + if ((allow_unadvertised_object_request & + (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) { + ref->match_status = REF_MATCHED; *newtail = copy_ref(ref); newtail = &(*newtail)->next; + } else { + ref->match_status = REF_UNADVERTISED_NOT_ALLOWED; } } *refs = newlist; } -static void mark_alternate_complete(const struct ref *ref, void *unused) +static void mark_alternate_complete(struct object *obj) { - mark_complete(ref->old_oid.hash); + mark_complete(obj->oid.hash); } static int everything_local(struct fetch_pack_args *args, @@ -657,7 +695,7 @@ static int everything_local(struct fetch_pack_args *args, if (!args->deepen) { for_each_ref(mark_complete_oid, NULL); - for_each_alternate_ref(mark_alternate_complete, NULL); + for_each_cached_alternate(mark_alternate_complete); commit_list_sort_by_date(&complete); if (cutoff) mark_recent_complete_commits(args, cutoff); @@ -1094,3 +1132,26 @@ struct ref *fetch_pack(struct fetch_pack_args *args, clear_shallow_info(&si); return ref_cpy; } + +int report_unmatched_refs(struct ref **sought, int nr_sought) +{ + int i, ret = 0; + + for (i = 0; i < nr_sought; i++) { + if (!sought[i]) + continue; + switch (sought[i]->match_status) { + case REF_MATCHED: + continue; + case REF_NOT_MATCHED: + error(_("no such remote ref %s"), sought[i]->name); + break; + case REF_UNADVERTISED_NOT_ALLOWED: + error(_("Server does not allow request for unadvertised object %s"), + sought[i]->name); + break; + } + ret = 1; + } + return ret; +} diff --git a/fetch-pack.h b/fetch-pack.h index c912e3d321..a2d46e6e75 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -45,4 +45,10 @@ struct ref *fetch_pack(struct fetch_pack_args *args, struct sha1_array *shallow, char **pack_lockfile); +/* + * Print an appropriate error message for each sought ref that wasn't + * matched. Return 0 if all sought refs were matched, otherwise 1. + */ +int report_unmatched_refs(struct ref **sought, int nr_sought); + #endif diff --git a/git-add--interactive.perl b/git-add--interactive.perl index f5c816e273..77b4ed53a8 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -275,20 +275,11 @@ sub list_modified { my ($only) = @_; my (%data, @return); my ($add, $del, $adddel, $file); - my @tracked = (); - - if (@ARGV) { - @tracked = map { - chomp $_; - unquote_path($_); - } run_cmd_pipe(qw(git ls-files --), @ARGV); - return if (!@tracked); - } my $reference = get_diff_reference($patch_mode_revision); for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, - '--', @tracked)) { + '--', @ARGV)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); @@ -313,7 +304,7 @@ sub list_modified { } } - for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @tracked)) { + for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @ARGV)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { $file = unquote_path($file); diff --git a/git-compat-util.h b/git-compat-util.h index e626851fe9..8a4a3f85e7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -798,8 +798,6 @@ extern FILE *xfopen(const char *path, const char *mode); extern FILE *xfdopen(int fd, const char *mode); extern int xmkstemp(char *template); extern int xmkstemp_mode(char *template, int mode); -extern int odb_mkstemp(char *template, size_t limit, const char *pattern); -extern int odb_pack_keep(char *name, size_t namesz, const unsigned char *sha1); extern char *xgetcwd(void); extern FILE *fopen_for_writing(const char *path); diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 86b2ff1e07..2b8cdba157 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -46,6 +46,8 @@ git_commit_non_empty_tree() { if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then map "$3" + elif test $# = 1 && test "$1" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904; then + : else git commit-tree "$@" fi diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 4734094a3f..2c9c0165b5 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -1069,6 +1069,10 @@ git_rebase__interactive () { case "$action" in continue) + if test ! -d "$rewritten" + then + exec git rebase--helper ${force_rebase:+--no-ff} --continue + fi # do we have anything to commit? if git diff-index --cached --quiet HEAD -- then @@ -1128,6 +1132,10 @@ first and then run 'git rebase --continue' again.")" skip) git rerere clear + if test ! -d "$rewritten" + then + exec git rebase--helper ${force_rebase:+--no-ff} --continue + fi do_rest return 0 ;; @@ -1314,6 +1322,11 @@ expand_todo_ids test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks checkout_onto +if test -z "$rebase_root" && test ! -d "$rewritten" +then + require_clean_work_tree "rebase" + exec git rebase--helper ${force_rebase:+--no-ff} --continue +fi do_rest } diff --git a/git-stash.sh b/git-stash.sh index 10c284d1aa..2fb651b2b8 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,8 +7,11 @@ USAGE="list [<options>] or: $dashless drop [-q|--quiet] [<stash>] or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>] or: $dashless branch <branchname> [<stash>] - or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] - [-u|--include-untracked] [-a|--all] [<message>]] + or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [<message>] + or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [-m <message>] + [-- <pathspec>...]] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -33,15 +36,15 @@ else fi no_changes () { - git diff-index --quiet --cached HEAD --ignore-submodules -- && - git diff-files --quiet --ignore-submodules && + git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" && + git diff-files --quiet --ignore-submodules -- "$@" && (test -z "$untracked" || test -z "$(untracked_files)") } untracked_files () { excl_opt=--exclude-standard test "$untracked" = "all" && excl_opt= - git ls-files -o -z $excl_opt + git ls-files -o -z $excl_opt -- "$@" } clear_stash () { @@ -56,11 +59,29 @@ clear_stash () { } create_stash () { - stash_msg="$1" - untracked="$2" + stash_msg= + untracked= + while test $# != 0 + do + case "$1" in + -m|--message) + shift + stash_msg=${1?"BUG: create_stash () -m requires an argument"} + ;; + -u|--include-untracked) + shift + untracked=${1?"BUG: create_stash () -u requires an argument"} + ;; + --) + shift + break + ;; + esac + shift + done git update-index -q --refresh - if no_changes + if no_changes "$@" then exit 0 fi @@ -92,7 +113,7 @@ create_stash () { # Untracked files are stored by themselves in a parentless commit, for # ease of unpacking later. u_commit=$( - untracked_files | ( + untracked_files "$@" | ( GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && rm -f "$TMPindex" && @@ -115,7 +136,7 @@ create_stash () { git read-tree --index-output="$TMPindex" -m $i_tree && GIT_INDEX_FILE="$TMPindex" && export GIT_INDEX_FILE && - git diff-index --name-only -z HEAD -- >"$TMP-stagenames" && + git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" && git update-index -z --add --remove --stdin <"$TMP-stagenames" && git write-tree && rm -f "$TMPindex" @@ -129,7 +150,7 @@ create_stash () { # find out what the user wants GIT_INDEX_FILE="$TMP-index" \ - git add--interactive --patch=stash -- && + git add--interactive --patch=stash -- "$@" && # state of the working tree w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || @@ -189,10 +210,11 @@ store_stash () { return $ret } -save_stash () { +push_stash () { keep_index= patch_mode= untracked= + stash_msg= while test $# != 0 do case "$1" in @@ -216,6 +238,11 @@ save_stash () { -a|--all) untracked=all ;; + -m|--message) + shift + test -z ${1+x} && usage + stash_msg=$1 + ;; --help) show_help ;; @@ -251,34 +278,45 @@ save_stash () { die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")" fi - stash_msg="$*" + test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1 git update-index -q --refresh - if no_changes + if no_changes "$@" then say "$(gettext "No local changes to save")" exit 0 fi + git reflog exists $ref_stash || clear_stash || die "$(gettext "Cannot initialize stash")" - create_stash "$stash_msg" $untracked + create_stash -m "$stash_msg" -u "$untracked" -- "$@" store_stash -m "$stash_msg" -q $w_commit || die "$(gettext "Cannot save the current status")" say "$(eval_gettext "Saved working directory and index state \$stash_msg")" if test -z "$patch_mode" then - git reset --hard ${GIT_QUIET:+-q} + if test $# != 0 + then + git reset -q -- "$@" + git ls-files -z --modified -- "$@" | + git checkout-index -z --force --stdin + git clean --force -q -d -- "$@" + else + git reset --hard -q + fi test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION= if test -n "$untracked" then - git clean --force --quiet -d $CLEAN_X_OPTION + git clean --force --quiet -d $CLEAN_X_OPTION -- "$@" fi if test "$keep_index" = "t" && test -n "$i_tree" then - git read-tree --reset -u $i_tree + git read-tree --reset $i_tree + git ls-files -z --modified -- "$@" | + git checkout-index -z --force --stdin fi else git apply -R < "$TMP-patch" || @@ -286,11 +324,41 @@ save_stash () { if test "$keep_index" != "t" then - git reset + git reset -q -- "$@" fi fi } +save_stash () { + push_options= + while test $# != 0 + do + case "$1" in + --) + shift + break + ;; + -*) + # pass all options through to push_stash + push_options="$push_options $1" + ;; + *) + break + ;; + esac + shift + done + + stash_msg="$*" + + if test -z "$stash_msg" + then + push_stash $push_options + else + push_stash $push_options -m "$stash_msg" + fi +} + have_stash () { git rev-parse --verify --quiet $ref_stash >/dev/null } @@ -590,18 +658,21 @@ apply_to_branch () { } } +test "$1" = "-p" && set "push" "$@" + PARSE_CACHE='--not-parsed' -# The default command is "save" if nothing but options are given +# The default command is "push" if nothing but options are given seen_non_option= for opt do case "$opt" in + --) break ;; -*) ;; *) seen_non_option=t; break ;; esac done -test -n "$seen_non_option" || set "save" "$@" +test -n "$seen_non_option" || set "push" "$@" # Main command set case "$1" in @@ -617,6 +688,10 @@ save) shift save_stash "$@" ;; +push) + shift + push_stash "$@" + ;; apply) shift apply_stash "$@" @@ -627,7 +702,7 @@ clear) ;; create) shift - create_stash "$*" && echo "$w_commit" + create_stash -m "$*" && echo "$w_commit" ;; store) shift @@ -648,7 +723,7 @@ branch) *) case $# in 0) - save_stash && + push_stash && say "$(gettext "(To restore them type \"git stash apply\")")" ;; *) diff --git a/git-svn.perl b/git-svn.perl index fa42364785..d2404184ba 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1175,10 +1175,10 @@ sub cmd_branch { ::_req_svn(); require SVN::Client; + my ($config, $baton, undef) = Git::SVN::Ra::prepare_config_once(); my $ctx = SVN::Client->new( - config => SVN::Core::config_get_config( - $Git::SVN::Ra::config_dir - ), + auth => $baton, + config => $config, log_msg => sub { ${ $_[0] } = defined $_message ? $_message @@ -473,6 +473,7 @@ static struct cmd_struct commands[] = { { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, + { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, @@ -12,6 +12,11 @@ static int grep_source_is_binary(struct grep_source *gs); static struct grep_opt grep_defaults; +static void std_output(struct grep_opt *opt, const void *buf, size_t size) +{ + fwrite(buf, size, 1, stdout); +} + /* * Initialize the grep_defaults template with hardcoded defaults. * We could let the compiler do this, but without C99 initializers @@ -42,6 +47,7 @@ void init_grep_defaults(void) color_set(opt->color_selected, ""); color_set(opt->color_sep, GIT_COLOR_CYAN); opt->color = -1; + opt->output = std_output; } static int parse_pattern_type_arg(const char *opt, const char *arg) @@ -152,6 +158,7 @@ void grep_init(struct grep_opt *opt, const char *prefix) opt->pathname = def->pathname; opt->regflags = def->regflags; opt->relative = def->relative; + opt->output = def->output; color_set(opt->color_context, def->color_context); color_set(opt->color_filename, def->color_filename); @@ -1379,11 +1386,6 @@ static int look_ahead(struct grep_opt *opt, return 0; } -static void std_output(struct grep_opt *opt, const void *buf, size_t size) -{ - fwrite(buf, size, 1, stdout); -} - static int fill_textconv_grep(struct userdiff_driver *driver, struct grep_source *gs) { diff --git a/hash.h b/hash.h new file mode 100644 index 0000000000..a11fc9233f --- /dev/null +++ b/hash.h @@ -0,0 +1,16 @@ +#ifndef HASH_H +#define HASH_H + +#if defined(SHA1_PPC) +#include "ppc/sha1.h" +#elif defined(SHA1_APPLE) +#include <CommonCrypto/CommonDigest.h> +#elif defined(SHA1_OPENSSL) +#include <openssl/sha.h> +#elif defined(SHA1_DC) +#include "sha1dc/sha1.h" +#else /* SHA1_BLK */ +#include "block-sha1/sha1.h" +#endif + +#endif @@ -53,6 +53,14 @@ int get_oid_hex(const char *hex, struct object_id *oid) return get_sha1_hex(hex, oid->hash); } +int parse_oid_hex(const char *hex, struct object_id *oid, const char **end) +{ + int ret = get_oid_hex(hex, oid); + if (!ret) + *end = hex + GIT_SHA1_HEXSZ; + return ret; +} + char *sha1_to_hex_r(char *buffer, const unsigned char *sha1) { static const char hex[] = "0123456789abcdef"; diff --git a/http-push.c b/http-push.c index 704b1c837c..f0e3108f71 100644 --- a/http-push.c +++ b/http-push.c @@ -1431,11 +1431,9 @@ static void one_remote_ref(const char *refname) */ if (repo->can_update_info_refs && !has_object_file(&ref->old_oid)) { obj = lookup_unknown_object(ref->old_oid.hash); - if (obj) { - fprintf(stderr, " fetch %s for %s\n", - oid_to_hex(&ref->old_oid), refname); - add_fetch_request(obj); - } + fprintf(stderr, " fetch %s for %s\n", + oid_to_hex(&ref->old_oid), refname); + add_fetch_request(obj); } ref->next = remote_refs; diff --git a/http-walker.c b/http-walker.c index 507c200f00..ee049cb13d 100644 --- a/http-walker.c +++ b/http-walker.c @@ -168,6 +168,11 @@ static int is_alternate_allowed(const char *url) }; int i; + if (http_follow_config != HTTP_FOLLOW_ALWAYS) { + warning("alternate disabled by http.followRedirects: %s", url); + return 0; + } + for (i = 0; i < ARRAY_SIZE(protocols); i++) { const char *end; if (skip_prefix(url, protocols[i], &end) && @@ -317,6 +322,8 @@ static void process_alternates_response(void *callback_data) while (tail->next != NULL) tail = tail->next; tail->next = newalt; + } else { + strbuf_release(&target); } } } @@ -334,9 +341,6 @@ static void fetch_alternates(struct walker *walker, const char *base) struct alternates_request alt_req; struct walker_data *cdata = walker->data; - if (http_follow_config != HTTP_FOLLOW_ALWAYS) - return; - /* * If another request has already started fetching alternates, * wait for them to arrive and return to processing this request's @@ -153,7 +153,7 @@ static void copy_email(const struct passwd *pw, struct strbuf *email, const char *ident_default_name(void) { - if (!git_default_name.len) { + if (!(ident_config_given & IDENT_NAME_GIVEN) && !git_default_name.len) { copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name); strbuf_trim(&git_default_name); } @@ -162,7 +162,7 @@ const char *ident_default_name(void) const char *ident_default_email(void) { - if (!git_default_email.len) { + if (!(ident_config_given & IDENT_MAIL_GIVEN) && !git_default_email.len) { const char *email = getenv("EMAIL"); if (email && email[0]) { @@ -203,6 +203,15 @@ static int crud(unsigned char c) c == '\''; } +static int has_non_crud(const char *str) +{ + for (; *str; str++) { + if (!crud(*str)) + return 1; + } + return 0; +} + /* * Copy over a string to the destination, but avoid special * characters ('\n', '<' and '>') and remove crud at the end @@ -351,19 +360,32 @@ const char *fmt_ident(const char *name, const char *email, int want_date = !(flag & IDENT_NO_DATE); int want_name = !(flag & IDENT_NO_NAME); + if (!email) { + if (strict && ident_use_config_only + && !(ident_config_given & IDENT_MAIL_GIVEN)) { + fputs(_(env_hint), stderr); + die(_("no email was given and auto-detection is disabled")); + } + email = ident_default_email(); + if (strict && default_email_is_bogus) { + fputs(_(env_hint), stderr); + die(_("unable to auto-detect email address (got '%s')"), email); + } + } + if (want_name) { int using_default = 0; if (!name) { if (strict && ident_use_config_only && !(ident_config_given & IDENT_NAME_GIVEN)) { fputs(_(env_hint), stderr); - die("no name was given and auto-detection is disabled"); + die(_("no name was given and auto-detection is disabled")); } name = ident_default_name(); using_default = 1; if (strict && default_name_is_bogus) { fputs(_(env_hint), stderr); - die("unable to auto-detect name (got '%s')", name); + die(_("unable to auto-detect name (got '%s')"), name); } } if (!*name) { @@ -371,24 +393,13 @@ const char *fmt_ident(const char *name, const char *email, if (strict) { if (using_default) fputs(_(env_hint), stderr); - die("empty ident name (for <%s>) not allowed", email); + die(_("empty ident name (for <%s>) not allowed"), email); } pw = xgetpwuid_self(NULL); name = pw->pw_name; } - } - - if (!email) { - if (strict && ident_use_config_only - && !(ident_config_given & IDENT_MAIL_GIVEN)) { - fputs(_(env_hint), stderr); - die("no email was given and auto-detection is disabled"); - } - email = ident_default_email(); - if (strict && default_email_is_bogus) { - fputs(_(env_hint), stderr); - die("unable to auto-detect email address (got '%s')", email); - } + if (strict && !has_non_crud(name)) + die(_("name consists only of disallowed characters: %s"), name); } strbuf_reset(&ident); @@ -403,7 +414,7 @@ const char *fmt_ident(const char *name, const char *email, strbuf_addch(&ident, ' '); if (date_str && date_str[0]) { if (parse_date(date_str, &ident) < 0) - die("invalid date format: %s", date_str); + die(_("invalid date format: %s"), date_str); } else strbuf_addstr(&ident, ident_default_date()); diff --git a/ll-merge.c b/ll-merge.c index ad8be42f91..ac0d4a5d78 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr return &ll_merge_drv[LL_TEXT_MERGE]; } -static int git_path_check_merge(const char *path, struct git_attr_check check[2]) -{ - if (!check[0].attr) { - check[0].attr = git_attr("merge"); - check[1].attr = git_attr("conflict-marker-size"); - } - return git_check_attr(path, 2, check); -} - static void normalize_file(mmfile_t *mm, const char *path) { struct strbuf strbuf = STRBUF_INIT; @@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf, mmfile_t *theirs, const char *their_label, const struct ll_merge_options *opts) { - static struct git_attr_check check[2]; + static struct attr_check *check; static const struct ll_merge_options default_opts; const char *ll_driver_name = NULL; int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; @@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf, normalize_file(ours, path); normalize_file(theirs, path); } - if (!git_path_check_merge(path, check)) { - ll_driver_name = check[0].value; - if (check[1].value) { - marker_size = atoi(check[1].value); + + if (!check) + check = attr_check_initl("merge", "conflict-marker-size", NULL); + + if (!git_check_attr(path, check)) { + ll_driver_name = check->items[0].value; + if (check->items[1].value) { + marker_size = atoi(check->items[1].value); if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; } @@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf, int ll_merge_marker_size(const char *path) { - static struct git_attr_check check; + static struct attr_check *check; int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - if (!check.attr) - check.attr = git_attr("conflict-marker-size"); - if (!git_check_attr(path, 1, &check) && check.value) { - marker_size = atoi(check.value); + if (!check) + check = attr_check_initl("conflict-marker-size", NULL); + if (!git_check_attr(path, check) && check->items[0].value) { + marker_size = atoi(check->items[0].value); if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; } diff --git a/log-tree.c b/log-tree.c index 8c2415747a..4618dd04ca 100644 --- a/log-tree.c +++ b/log-tree.c @@ -332,35 +332,31 @@ void fmt_output_commit(struct strbuf *filename, strbuf_release(&subject); } +void fmt_output_email_subject(struct strbuf *sb, struct rev_info *opt) +{ + if (opt->total > 0) { + strbuf_addf(sb, "Subject: [%s%s%0*d/%d] ", + opt->subject_prefix, + *opt->subject_prefix ? " " : "", + digits_in_number(opt->total), + opt->nr, opt->total); + } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) { + strbuf_addf(sb, "Subject: [%s] ", + opt->subject_prefix); + } else { + strbuf_addstr(sb, "Subject: "); + } +} + void log_write_email_headers(struct rev_info *opt, struct commit *commit, - const char **subject_p, const char **extra_headers_p, int *need_8bit_cte_p) { - const char *subject = NULL; const char *extra_headers = opt->extra_headers; const char *name = oid_to_hex(opt->zero_commit ? &null_oid : &commit->object.oid); *need_8bit_cte_p = 0; /* unknown */ - if (opt->total > 0) { - static char buffer[64]; - snprintf(buffer, sizeof(buffer), - "Subject: [%s%s%0*d/%d] ", - opt->subject_prefix, - *opt->subject_prefix ? " " : "", - digits_in_number(opt->total), - opt->nr, opt->total); - subject = buffer; - } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) { - static char buffer[256]; - snprintf(buffer, sizeof(buffer), - "Subject: [%s] ", - opt->subject_prefix); - subject = buffer; - } else { - subject = "Subject: "; - } fprintf(opt->diffopt.file, "From %s Mon Sep 17 00:00:00 2001\n", name); graph_show_oneline(opt->graph); @@ -417,7 +413,6 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, opt->diffopt.stat_sep = buffer; strbuf_release(&filename); } - *subject_p = subject; *extra_headers_p = extra_headers; } @@ -602,8 +597,10 @@ void show_log(struct rev_info *opt) */ if (cmit_fmt_is_mail(opt->commit_format)) { - log_write_email_headers(opt, commit, &ctx.subject, &extra_headers, + log_write_email_headers(opt, commit, &extra_headers, &ctx.need_8bit_cte); + ctx.rev = opt; + ctx.print_email_subject = 1; } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), opt->diffopt.file); if (opt->commit_format != CMIT_FMT_ONELINE) diff --git a/log-tree.h b/log-tree.h index c8116e60cd..48f11fb740 100644 --- a/log-tree.h +++ b/log-tree.h @@ -22,7 +22,6 @@ void format_decorations_extended(struct strbuf *sb, const struct commit *commit, format_decorations_extended((strbuf), (commit), (color), " (", ", ", ")") void show_decorations(struct rev_info *opt, struct commit *commit); void log_write_email_headers(struct rev_info *opt, struct commit *commit, - const char **subject_p, const char **extra_headers_p, int *need_8bit_cte_p); void load_ref_decorations(int flags); @@ -30,5 +29,6 @@ void load_ref_decorations(int flags); #define FORMAT_PATCH_NAME_MAX 64 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *); void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *); +void fmt_output_email_subject(struct strbuf *, struct rev_info *); #endif diff --git a/merge-recursive.c b/merge-recursive.c index b7ff1ada3c..62decd51cc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1061,16 +1061,20 @@ static int merge_file_one(struct merge_options *o, } static int handle_change_delete(struct merge_options *o, - const char *path, + const char *path, const char *old_path, const struct object_id *o_oid, int o_mode, - const struct object_id *a_oid, int a_mode, - const struct object_id *b_oid, int b_mode, + const struct object_id *changed_oid, + int changed_mode, + const char *change_branch, + const char *delete_branch, const char *change, const char *change_past) { - char *renamed = NULL; + char *alt_path = NULL; + const char *update_path = path; int ret = 0; + if (dir_in_way(path, !o->call_depth, 0)) { - renamed = unique_path(o, path, a_oid ? o->branch1 : o->branch2); + update_path = alt_path = unique_path(o, path, change_branch); } if (o->call_depth) { @@ -1081,43 +1085,43 @@ static int handle_change_delete(struct merge_options *o, */ ret = remove_file_from_cache(path); if (!ret) - ret = update_file(o, 0, o_oid, o_mode, - renamed ? renamed : path); - } else if (!a_oid) { - if (!renamed) { - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree."), - change, path, o->branch1, change_past, - o->branch2, o->branch2, path); - ret = update_file(o, 0, b_oid, b_mode, path); - } else { - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree at %s."), - change, path, o->branch1, change_past, - o->branch2, o->branch2, path, renamed); - ret = update_file(o, 0, b_oid, b_mode, renamed); - } + ret = update_file(o, 0, o_oid, o_mode, update_path); } else { - if (!renamed) { - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree."), - change, path, o->branch2, change_past, - o->branch1, o->branch1, path); + if (!alt_path) { + if (!old_path) { + output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " + "and %s in %s. Version %s of %s left in tree."), + change, path, delete_branch, change_past, + change_branch, change_branch, path); + } else { + output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " + "and %s to %s in %s. Version %s of %s left in tree."), + change, old_path, delete_branch, change_past, path, + change_branch, change_branch, path); + } } else { - output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " - "and %s in %s. Version %s of %s left in tree at %s."), - change, path, o->branch2, change_past, - o->branch1, o->branch1, path, renamed); - ret = update_file(o, 0, a_oid, a_mode, renamed); + if (!old_path) { + output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " + "and %s in %s. Version %s of %s left in tree at %s."), + change, path, delete_branch, change_past, + change_branch, change_branch, path, alt_path); + } else { + output(o, 1, _("CONFLICT (%s/delete): %s deleted in %s " + "and %s to %s in %s. Version %s of %s left in tree at %s."), + change, old_path, delete_branch, change_past, path, + change_branch, change_branch, path, alt_path); + } } /* - * No need to call update_file() on path when !renamed, since - * that would needlessly touch path. We could call - * update_file_flags() with update_cache=0 and update_wd=0, - * but that's a no-op. + * No need to call update_file() on path when change_branch == + * o->branch1 && !alt_path, since that would needlessly touch + * path. We could call update_file_flags() with update_cache=0 + * and update_wd=0, but that's a no-op. */ + if (change_branch != o->branch1 || alt_path) + ret = update_file(o, 0, changed_oid, changed_mode, update_path); } - free(renamed); + free(alt_path); return ret; } @@ -1125,28 +1129,17 @@ static int handle_change_delete(struct merge_options *o, static int conflict_rename_delete(struct merge_options *o, struct diff_filepair *pair, const char *rename_branch, - const char *other_branch) + const char *delete_branch) { const struct diff_filespec *orig = pair->one; const struct diff_filespec *dest = pair->two; - const struct object_id *a_oid = NULL; - const struct object_id *b_oid = NULL; - int a_mode = 0; - int b_mode = 0; - - if (rename_branch == o->branch1) { - a_oid = &dest->oid; - a_mode = dest->mode; - } else { - b_oid = &dest->oid; - b_mode = dest->mode; - } if (handle_change_delete(o, o->call_depth ? orig->path : dest->path, + o->call_depth ? NULL : orig->path, &orig->oid, orig->mode, - a_oid, a_mode, - b_oid, b_mode, + &dest->oid, dest->mode, + rename_branch, delete_branch, _("rename"), _("renamed"))) return -1; @@ -1662,11 +1655,27 @@ static int handle_modify_delete(struct merge_options *o, struct object_id *a_oid, int a_mode, struct object_id *b_oid, int b_mode) { + const char *modify_branch, *delete_branch; + struct object_id *changed_oid; + int changed_mode; + + if (a_oid) { + modify_branch = o->branch1; + delete_branch = o->branch2; + changed_oid = a_oid; + changed_mode = a_mode; + } else { + modify_branch = o->branch2; + delete_branch = o->branch1; + changed_oid = b_oid; + changed_mode = b_mode; + } + return handle_change_delete(o, - path, + path, NULL, o_oid, o_mode, - a_oid, a_mode, - b_oid, b_mode, + changed_oid, changed_mode, + modify_branch, delete_branch, _("modify"), _("modified")); } @@ -29,7 +29,7 @@ struct object_array { /* * object flag allocation: * revision.h: 0---------10 26 - * fetch-pack.c: 0---4 + * fetch-pack.c: 0---5 * walker.c: 0-2 * upload-pack.c: 4 11----------------19 * builtin/blame.c: 12-13 diff --git a/oidset.c b/oidset.c new file mode 100644 index 0000000000..ac169f05d3 --- /dev/null +++ b/oidset.c @@ -0,0 +1,49 @@ +#include "cache.h" +#include "oidset.h" + +struct oidset_entry { + struct hashmap_entry hash; + struct object_id oid; +}; + +static int oidset_hashcmp(const void *va, const void *vb, + const void *vkey) +{ + const struct oidset_entry *a = va, *b = vb; + const struct object_id *key = vkey; + return oidcmp(&a->oid, key ? key : &b->oid); +} + +int oidset_contains(const struct oidset *set, const struct object_id *oid) +{ + struct hashmap_entry key; + + if (!set->map.cmpfn) + return 0; + + hashmap_entry_init(&key, sha1hash(oid->hash)); + return !!hashmap_get(&set->map, &key, oid); +} + +int oidset_insert(struct oidset *set, const struct object_id *oid) +{ + struct oidset_entry *entry; + + if (!set->map.cmpfn) + hashmap_init(&set->map, oidset_hashcmp, 0); + + if (oidset_contains(set, oid)) + return 1; + + entry = xmalloc(sizeof(*entry)); + hashmap_entry_init(&entry->hash, sha1hash(oid->hash)); + oidcpy(&entry->oid, oid); + + hashmap_add(&set->map, entry); + return 0; +} + +void oidset_clear(struct oidset *set) +{ + hashmap_free(&set->map, 1); +} diff --git a/oidset.h b/oidset.h new file mode 100644 index 0000000000..b7eaab5b88 --- /dev/null +++ b/oidset.h @@ -0,0 +1,45 @@ +#ifndef OIDSET_H +#define OIDSET_H + +/** + * This API is similar to sha1-array, in that it maintains a set of object ids + * in a memory-efficient way. The major differences are: + * + * 1. It uses a hash, so we can do online duplicate removal, rather than + * sort-and-uniq at the end. This can reduce memory footprint if you have + * a large list of oids with many duplicates. + * + * 2. The per-unique-oid memory footprint is slightly higher due to hash + * table overhead. + */ + +/** + * A single oidset; should be zero-initialized (or use OIDSET_INIT). + */ +struct oidset { + struct hashmap map; +}; + +#define OIDSET_INIT { { NULL } } + +/** + * Returns true iff `set` contains `oid`. + */ +int oidset_contains(const struct oidset *set, const struct object_id *oid); + +/** + * Insert the oid into the set; a copy is made, so "oid" does not need + * to persist after this function is called. + * + * Returns 1 if the oid was already in the set, 0 otherwise. This can be used + * to perform an efficient check-and-add. + */ +int oidset_insert(struct oidset *set, const struct object_id *oid); + +/** + * Remove all entries from the oidset, freeing any resources associated with + * it. + */ +void oidset_clear(struct oidset *set); + +#endif /* OIDSET_H */ diff --git a/pack-objects.h b/pack-objects.h index cc9b9a9b90..03f1191659 100644 --- a/pack-objects.h +++ b/pack-objects.h @@ -30,12 +30,16 @@ struct object_entry { /* * State flags for depth-first search used for analyzing delta cycles. + * + * The depth is measured in delta-links to the base (so if A is a delta + * against B, then A has a depth of 1, and B a depth of 0). */ enum { DFS_NONE = 0, DFS_ACTIVE, DFS_DONE } dfs_state; + int depth; }; struct packing_data { @@ -43,37 +43,6 @@ static int core_pager_config(const char *var, const char *value, void *data) return 0; } -static void read_early_config(config_fn_t cb, void *data) -{ - git_config_with_options(cb, data, NULL, 1); - - /* - * Note that this is a really dirty hack that does the wrong thing in - * many cases. The crux of the problem is that we cannot run - * setup_git_directory() early on in git's setup, so we have no idea if - * we are in a repository or not, and therefore are not sure whether - * and how to read repository-local config. - * - * So if we _aren't_ in a repository (or we are but we would reject its - * core.repositoryformatversion), we'll read whatever is in .git/config - * blindly. Similarly, if we _are_ in a repository, but not at the - * root, we'll fail to find .git/config (because it's really - * ../.git/config, etc). See t7006 for a complete set of failures. - * - * However, we have historically provided this hack because it does - * work some of the time (namely when you are at the top-level of a - * valid repository), and would rarely make things worse (i.e., you do - * not generally have a .git/config file sitting around). - */ - if (!startup_info->have_repository) { - struct git_config_source repo_config; - - memset(&repo_config, 0, sizeof(repo_config)); - repo_config.file = ".git/config"; - git_config_with_options(cb, data, &repo_config, 1); - } -} - const char *git_pager(int stdout_is_tty) { const char *pager; @@ -166,9 +135,7 @@ void setup_pager(void) int pager_in_use(void) { - const char *env; - env = getenv("GIT_PAGER_IN_USE"); - return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0; + return git_env_bool("GIT_PAGER_IN_USE", 0); } /* diff --git a/parse-options.c b/parse-options.c index 4fbe924a5d..a23a1e67f0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -40,7 +40,7 @@ static void fix_filename(const char *prefix, const char **file) if (!file || !*file || !prefix || is_absolute_path(*file) || !strcmp("-", *file)) return; - *file = xstrdup(prefix_filename(prefix, strlen(prefix), *file)); + *file = prefix_filename(prefix, *file); } static int opt_command_mode_error(const struct option *opt, @@ -1272,6 +1272,21 @@ char *xdg_config_home(const char *filename) return NULL; } +char *xdg_cache_home(const char *filename) +{ + const char *home, *cache_home; + + assert(filename); + cache_home = getenv("XDG_CACHE_HOME"); + if (cache_home && *cache_home) + return mkpathdup("%s/git/%s", cache_home, filename); + + home = getenv("HOME"); + if (home) + return mkpathdup("%s/.cache/git/%s", home, filename); + return NULL; +} + GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD") GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD") GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG") diff --git a/pathspec.c b/pathspec.c index 7ababb3159..303efda837 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,6 +1,7 @@ #include "cache.h" #include "dir.h" #include "pathspec.h" +#include "attr.h" /* * Finds which of the given pathspecs match items in the index. @@ -72,6 +73,7 @@ static struct pathspec_magic { { PATHSPEC_GLOB, '\0', "glob" }, { PATHSPEC_ICASE, '\0', "icase" }, { PATHSPEC_EXCLUDE, '!', "exclude" }, + { PATHSPEC_ATTR, '\0', "attr" }, }; static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) @@ -87,6 +89,116 @@ static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) strbuf_addf(sb, ",prefix:%d)", prefixlen); } +static size_t strcspn_escaped(const char *s, const char *stop) +{ + const char *i; + + for (i = s; *i; i++) { + /* skip the escaped character */ + if (i[0] == '\\' && i[1]) { + i++; + continue; + } + + if (strchr(stop, *i)) + break; + } + return i - s; +} + +static inline int invalid_value_char(const char ch) +{ + if (isalnum(ch) || strchr(",-_", ch)) + return 0; + return -1; +} + +static char *attr_value_unescape(const char *value) +{ + const char *src; + char *dst, *ret; + + ret = xmallocz(strlen(value)); + for (src = value, dst = ret; *src; src++, dst++) { + if (*src == '\\') { + if (!src[1]) + die(_("Escape character '\\' not allowed as " + "last character in attr value")); + src++; + } + if (invalid_value_char(*src)) + die("cannot use '%c' for value matching", *src); + *dst = *src; + } + *dst = '\0'; + return ret; +} + +static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value) +{ + struct string_list_item *si; + struct string_list list = STRING_LIST_INIT_DUP; + + if (item->attr_check || item->attr_match) + die(_("Only one 'attr:' specification is allowed.")); + + if (!value || !*value) + die(_("attr spec must not be empty")); + + string_list_split(&list, value, ' ', -1); + string_list_remove_empty_items(&list, 0); + + item->attr_check = attr_check_alloc(); + item->attr_match = xcalloc(list.nr, sizeof(struct attr_match)); + + for_each_string_list_item(si, &list) { + size_t attr_len; + char *attr_name; + const struct git_attr *a; + + int j = item->attr_match_nr++; + const char *attr = si->string; + struct attr_match *am = &item->attr_match[j]; + + switch (*attr) { + case '!': + am->match_mode = MATCH_UNSPECIFIED; + attr++; + attr_len = strlen(attr); + break; + case '-': + am->match_mode = MATCH_UNSET; + attr++; + attr_len = strlen(attr); + break; + default: + attr_len = strcspn(attr, "="); + if (attr[attr_len] != '=') + am->match_mode = MATCH_SET; + else { + const char *v = &attr[attr_len + 1]; + am->match_mode = MATCH_VALUE; + am->value = attr_value_unescape(v); + } + break; + } + + attr_name = xmemdupz(attr, attr_len); + a = git_attr(attr_name); + if (!a) + die(_("invalid attribute name %s"), attr_name); + + attr_check_append(item->attr_check, a); + + free(attr_name); + } + + if (item->attr_check->nr != item->attr_match_nr) + die("BUG: should have same number of entries"); + + string_list_clear(&list, 0); +} + static inline int get_literal_global(void) { static int literal = -1; @@ -164,13 +276,14 @@ static int get_global_magic(int element_magic) * returns the position in 'elem' after all magic has been parsed */ static const char *parse_long_magic(unsigned *magic, int *prefix_len, + struct pathspec_item *item, const char *elem) { const char *pos; const char *nextat; for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) { - size_t len = strcspn(pos, ",)"); + size_t len = strcspn_escaped(pos, ",)"); int i; if (pos[len] == ',') @@ -189,6 +302,14 @@ static const char *parse_long_magic(unsigned *magic, int *prefix_len, continue; } + if (starts_with(pos, "attr:")) { + char *attr_body = xmemdupz(pos + 5, len - 5); + parse_pathspec_attr_match(item, attr_body); + *magic |= PATHSPEC_ATTR; + free(attr_body); + continue; + } + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) == len && !strncmp(pathspec_magic[i].name, pos, len)) { @@ -224,6 +345,12 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) char ch = *pos; int i; + /* Special case alias for '!' */ + if (ch == '^') { + *magic |= PATHSPEC_EXCLUDE; + continue; + } + if (!is_pathspec_magic(ch)) break; @@ -246,13 +373,14 @@ static const char *parse_short_magic(unsigned *magic, const char *elem) } static const char *parse_element_magic(unsigned *magic, int *prefix_len, + struct pathspec_item *item, const char *elem) { if (elem[0] != ':' || get_literal_global()) return elem; /* nothing to do */ else if (elem[1] == '(') /* longhand */ - return parse_long_magic(magic, prefix_len, elem); + return parse_long_magic(magic, prefix_len, item, elem); else /* shorthand */ return parse_short_magic(magic, elem); @@ -329,12 +457,17 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, char *match; int pathspec_prefix = -1; + item->attr_check = NULL; + item->attr_match = NULL; + item->attr_match_nr = 0; + /* PATHSPEC_LITERAL_PATH ignores magic */ if (flags & PATHSPEC_LITERAL_PATH) { magic = PATHSPEC_LITERAL; } else { copyfrom = parse_element_magic(&element_magic, &pathspec_prefix, + item, elt); magic |= element_magic; magic |= get_global_magic(element_magic); @@ -516,7 +649,7 @@ void parse_pathspec(struct pathspec *pathspec, } pathspec->nr = n; - ALLOC_ARRAY(pathspec->items, n); + ALLOC_ARRAY(pathspec->items, n + 1); item = pathspec->items; prefixlen = prefix ? strlen(prefix) : 0; @@ -540,10 +673,15 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->magic |= item[i].magic; } - if (nr_exclude == n) - die(_("There is nothing to exclude from by :(exclude) patterns.\n" - "Perhaps you forgot to add either ':/' or '.' ?")); - + /* + * If everything is an exclude pattern, add one positive pattern + * that matches everyting. We allocated an extra one for this. + */ + if (nr_exclude == n) { + int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen; + init_pathspec_item(item + n, 0, prefix, plen, ""); + pathspec->nr++; + } if (pathspec->magic & PATHSPEC_MAXDEPTH) { if (flags & PATHSPEC_KEEP_ORDER) @@ -554,26 +692,46 @@ void parse_pathspec(struct pathspec *pathspec, void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { - int i; + int i, j; *dst = *src; ALLOC_ARRAY(dst->items, dst->nr); COPY_ARRAY(dst->items, src->items, dst->nr); for (i = 0; i < dst->nr; i++) { - dst->items[i].match = xstrdup(src->items[i].match); - dst->items[i].original = xstrdup(src->items[i].original); + struct pathspec_item *d = &dst->items[i]; + struct pathspec_item *s = &src->items[i]; + + d->match = xstrdup(s->match); + d->original = xstrdup(s->original); + + ALLOC_ARRAY(d->attr_match, d->attr_match_nr); + COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr); + for (j = 0; j < d->attr_match_nr; j++) { + const char *value = s->attr_match[j].value; + d->attr_match[j].value = xstrdup_or_null(value); + } + + d->attr_check = attr_check_dup(s->attr_check); } } void clear_pathspec(struct pathspec *pathspec) { - int i; + int i, j; for (i = 0; i < pathspec->nr; i++) { free(pathspec->items[i].match); free(pathspec->items[i].original); + + for (j = 0; j < pathspec->items[j].attr_match_nr; j++) + free(pathspec->items[i].attr_match[j].value); + free(pathspec->items[i].attr_match); + + if (pathspec->items[i].attr_check) + attr_check_free(pathspec->items[i].attr_check); } + free(pathspec->items); pathspec->items = NULL; pathspec->nr = 0; diff --git a/pathspec.h b/pathspec.h index 49fd823ddf..55e976972c 100644 --- a/pathspec.h +++ b/pathspec.h @@ -8,13 +8,15 @@ #define PATHSPEC_GLOB (1<<3) #define PATHSPEC_ICASE (1<<4) #define PATHSPEC_EXCLUDE (1<<5) +#define PATHSPEC_ATTR (1<<6) #define PATHSPEC_ALL_MAGIC \ (PATHSPEC_FROMTOP | \ PATHSPEC_MAXDEPTH | \ PATHSPEC_LITERAL | \ PATHSPEC_GLOB | \ PATHSPEC_ICASE | \ - PATHSPEC_EXCLUDE) + PATHSPEC_EXCLUDE | \ + PATHSPEC_ATTR) #define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */ @@ -31,6 +33,17 @@ struct pathspec { int len, prefix; int nowildcard_len; int flags; + int attr_match_nr; + struct attr_match { + char *value; + enum attr_match_mode { + MATCH_SET, + MATCH_UNSET, + MATCH_VALUE, + MATCH_UNSPECIFIED + } match_mode; + } *attr_match; + struct attr_check *attr_check; } *items; }; diff --git a/preload-index.c b/preload-index.c index c1fe3a3ef9..70a4c80878 100644 --- a/preload-index.c +++ b/preload-index.c @@ -53,6 +53,8 @@ static void *preload_thread(void *_data) continue; if (ce_uptodate(ce)) continue; + if (ce_skip_worktree(ce)) + continue; if (!ce_path_match(ce, &p->pathspec, NULL)) continue; if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce))) @@ -1607,8 +1607,9 @@ void pp_title_line(struct pretty_print_context *pp, pp->preserve_subject ? "\n" : " "); strbuf_grow(sb, title.len + 1024); - if (pp->subject) { - strbuf_addstr(sb, pp->subject); + if (pp->print_email_subject) { + if (pp->rev) + fmt_output_email_subject(sb, pp->rev); if (needs_rfc2047_encoding(title.buf, title.len, RFC2047_SUBJECT)) add_rfc2047(sb, title.buf, title.len, encoding, RFC2047_SUBJECT); @@ -1818,7 +1819,7 @@ void pretty_print_commit(struct pretty_print_context *pp, } pp_header(pp, encoding, commit, &msg, sb); - if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) { + if (pp->fmt != CMIT_FMT_ONELINE && !pp->print_email_subject) { strbuf_addch(sb, '\n'); } diff --git a/reachable.c b/reachable.c index d0199cace4..a8a979bd4f 100644 --- a/reachable.c +++ b/reachable.c @@ -58,7 +58,7 @@ struct recent_data { unsigned long timestamp; }; -static void add_recent_object(const unsigned char *sha1, +static void add_recent_object(const struct object_id *oid, unsigned long mtime, struct recent_data *data) { @@ -75,37 +75,37 @@ static void add_recent_object(const unsigned char *sha1, * later processing, and the revision machinery expects * commits and tags to have been parsed. */ - type = sha1_object_info(sha1, NULL); + type = sha1_object_info(oid->hash, NULL); if (type < 0) - die("unable to get object info for %s", sha1_to_hex(sha1)); + die("unable to get object info for %s", oid_to_hex(oid)); switch (type) { case OBJ_TAG: case OBJ_COMMIT: - obj = parse_object_or_die(sha1, NULL); + obj = parse_object_or_die(oid->hash, NULL); break; case OBJ_TREE: - obj = (struct object *)lookup_tree(sha1); + obj = (struct object *)lookup_tree(oid->hash); break; case OBJ_BLOB: - obj = (struct object *)lookup_blob(sha1); + obj = (struct object *)lookup_blob(oid->hash); break; default: die("unknown object type for %s: %s", - sha1_to_hex(sha1), typename(type)); + oid_to_hex(oid), typename(type)); } if (!obj) - die("unable to lookup %s", sha1_to_hex(sha1)); + die("unable to lookup %s", oid_to_hex(oid)); add_pending_object(data->revs, obj, ""); } -static int add_recent_loose(const unsigned char *sha1, +static int add_recent_loose(const struct object_id *oid, const char *path, void *data) { struct stat st; - struct object *obj = lookup_object(sha1); + struct object *obj = lookup_object(oid->hash); if (obj && obj->flags & SEEN) return 0; @@ -119,22 +119,22 @@ static int add_recent_loose(const unsigned char *sha1, */ if (errno == ENOENT) return 0; - return error_errno("unable to stat %s", sha1_to_hex(sha1)); + return error_errno("unable to stat %s", oid_to_hex(oid)); } - add_recent_object(sha1, st.st_mtime, data); + add_recent_object(oid, st.st_mtime, data); return 0; } -static int add_recent_packed(const unsigned char *sha1, +static int add_recent_packed(const struct object_id *oid, struct packed_git *p, uint32_t pos, void *data) { - struct object *obj = lookup_object(sha1); + struct object *obj = lookup_object(oid->hash); if (obj && obj->flags & SEEN) return 0; - add_recent_object(sha1, p->mtime, data); + add_recent_object(oid, p->mtime, data); return 0; } diff --git a/read-cache.c b/read-cache.c index 9054369dd0..e447751823 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1558,10 +1558,27 @@ static void tweak_untracked_cache(struct index_state *istate) } } +static void tweak_split_index(struct index_state *istate) +{ + switch (git_config_get_split_index()) { + case -1: /* unset: do nothing */ + break; + case 0: /* false */ + remove_split_index(istate); + break; + case 1: /* true */ + add_split_index(istate); + break; + default: /* unknown value: do nothing */ + break; + } +} + static void post_read_index_from(struct index_state *istate) { check_ce_order(istate); tweak_untracked_cache(istate); + tweak_split_index(istate); } /* remember to discard_cache() before reading a different cache! */ @@ -1657,10 +1674,25 @@ unmap: die("index file corrupt"); } +/* + * Signal that the shared index is used by updating its mtime. + * + * This way, shared index can be removed if they have not been used + * for some time. + */ +static void freshen_shared_index(char *base_sha1_hex, int warn) +{ + const char *shared_index = git_path("sharedindex.%s", base_sha1_hex); + if (!check_and_freshen_file(shared_index, 1) && warn) + warning("could not freshen shared index '%s'", shared_index); +} + int read_index_from(struct index_state *istate, const char *path) { struct split_index *split_index; int ret; + char *base_sha1_hex; + const char *base_path; /* istate->initialized covers both .git/index and .git/sharedindex.xxx */ if (istate->initialized) @@ -1678,15 +1710,16 @@ int read_index_from(struct index_state *istate, const char *path) discard_index(split_index->base); else split_index->base = xcalloc(1, sizeof(*split_index->base)); - ret = do_read_index(split_index->base, - git_path("sharedindex.%s", - sha1_to_hex(split_index->base_sha1)), 1); + + base_sha1_hex = sha1_to_hex(split_index->base_sha1); + base_path = git_path("sharedindex.%s", base_sha1_hex); + ret = do_read_index(split_index->base, base_path, 1); if (hashcmp(split_index->base_sha1, split_index->base->sha1)) die("broken index, expect %s in %s, got %s", - sha1_to_hex(split_index->base_sha1), - git_path("sharedindex.%s", - sha1_to_hex(split_index->base_sha1)), + base_sha1_hex, base_path, sha1_to_hex(split_index->base->sha1)); + + freshen_shared_index(base_sha1_hex, 0); merge_base_index(istate); post_read_index_from(istate); return ret; @@ -2169,6 +2202,65 @@ static int write_split_index(struct index_state *istate, return ret; } +static const char *shared_index_expire = "2.weeks.ago"; + +static unsigned long get_shared_index_expire_date(void) +{ + static unsigned long shared_index_expire_date; + static int shared_index_expire_date_prepared; + + if (!shared_index_expire_date_prepared) { + git_config_get_expiry("splitindex.sharedindexexpire", + &shared_index_expire); + shared_index_expire_date = approxidate(shared_index_expire); + shared_index_expire_date_prepared = 1; + } + + return shared_index_expire_date; +} + +static int should_delete_shared_index(const char *shared_index_path) +{ + struct stat st; + unsigned long expiration; + + /* Check timestamp */ + expiration = get_shared_index_expire_date(); + if (!expiration) + return 0; + if (stat(shared_index_path, &st)) + return error_errno(_("could not stat '%s"), shared_index_path); + if (st.st_mtime > expiration) + return 0; + + return 1; +} + +static int clean_shared_index_files(const char *current_hex) +{ + struct dirent *de; + DIR *dir = opendir(get_git_dir()); + + if (!dir) + return error_errno(_("unable to open git dir: %s"), get_git_dir()); + + while ((de = readdir(dir)) != NULL) { + const char *sha1_hex; + const char *shared_index_path; + if (!skip_prefix(de->d_name, "sharedindex.", &sha1_hex)) + continue; + if (!strcmp(sha1_hex, current_hex)) + continue; + shared_index_path = git_path("%s", de->d_name); + if (should_delete_shared_index(shared_index_path) > 0 && + unlink(shared_index_path)) + warning_errno(_("unable to unlink: %s"), shared_index_path); + } + closedir(dir); + + return 0; +} + static struct tempfile temporary_sharedindex; static int write_shared_index(struct index_state *istate, @@ -2190,14 +2282,48 @@ static int write_shared_index(struct index_state *istate, } ret = rename_tempfile(&temporary_sharedindex, git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); - if (!ret) + if (!ret) { hashcpy(si->base_sha1, si->base->sha1); + clean_shared_index_files(sha1_to_hex(si->base->sha1)); + } + return ret; } +static const int default_max_percent_split_change = 20; + +static int too_many_not_shared_entries(struct index_state *istate) +{ + int i, not_shared = 0; + int max_split = git_config_get_max_percent_split_change(); + + switch (max_split) { + case -1: + /* not or badly configured: use the default value */ + max_split = default_max_percent_split_change; + break; + case 0: + return 1; /* 0% means always write a new shared index */ + case 100: + return 0; /* 100% means never write a new shared index */ + default: + break; /* just use the configured value */ + } + + /* Count not shared entries */ + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (!ce->index) + not_shared++; + } + + return (int64_t)istate->cache_nr * max_split < (int64_t)not_shared * 100; +} + int write_locked_index(struct index_state *istate, struct lock_file *lock, unsigned flags) { + int new_shared_index, ret; struct split_index *si = istate->split_index; if (!si || alternate_index_output || @@ -2212,13 +2338,24 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, if ((v & 15) < 6) istate->cache_changed |= SPLIT_INDEX_ORDERED; } - if (istate->cache_changed & SPLIT_INDEX_ORDERED) { - int ret = write_shared_index(istate, lock, flags); + if (too_many_not_shared_entries(istate)) + istate->cache_changed |= SPLIT_INDEX_ORDERED; + + new_shared_index = istate->cache_changed & SPLIT_INDEX_ORDERED; + + if (new_shared_index) { + ret = write_shared_index(istate, lock, flags); if (ret) return ret; } - return write_split_index(istate, lock, flags); + ret = write_split_index(istate, lock, flags); + + /* Freshen the shared index only if the split-index was written */ + if (!ret && !new_shared_index) + freshen_shared_index(sha1_to_hex(si->base_sha1), 1); + + return ret; } /* diff --git a/ref-filter.c b/ref-filter.c index 3820b21cc7..9c82b5b9d6 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -14,14 +14,51 @@ #include "git-compat-util.h" #include "version.h" #include "trailer.h" +#include "wt-status.h" +#include "commit-slab.h" + +static struct ref_msg { + const char *gone; + const char *ahead; + const char *behind; + const char *ahead_behind; +} msgs = { + /* Untranslated plumbing messages: */ + "gone", + "ahead %d", + "behind %d", + "ahead %d, behind %d" +}; + +void setup_ref_filter_porcelain_msg(void) +{ + msgs.gone = _("gone"); + msgs.ahead = _("ahead %d"); + msgs.behind = _("behind %d"); + msgs.ahead_behind = _("ahead %d, behind %d"); +} typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; +typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status; struct align { align_type position; unsigned int width; }; +struct if_then_else { + cmp_status cmp_status; + const char *str; + unsigned int then_atom_seen : 1, + else_atom_seen : 1, + condition_satisfied : 1; +}; + +struct refname_atom { + enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option; + int lstrip, rstrip; +}; + /* * An atom is a valid field atom listed below, possibly prefixed with * a "*" to denote deref_tag(). @@ -38,13 +75,24 @@ static struct used_atom { union { char color[COLOR_MAXLEN]; struct align align; - enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT } - remote_ref; + struct { + enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option; + struct refname_atom refname; + unsigned int nobracket : 1; + } remote_ref; struct { enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option; unsigned int nlines; } contents; - enum { O_FULL, O_SHORT } objectname; + struct { + cmp_status cmp_status; + const char *str; + } if_then_else; + struct { + enum { O_FULL, O_LENGTH, O_SHORT } option; + unsigned int length; + } objectname; + struct refname_atom refname; } u; } *used_atom; static int used_atom_cnt, need_tagged, need_symref; @@ -58,18 +106,58 @@ static void color_atom_parser(struct used_atom *atom, const char *color_value) die(_("unrecognized color: %%(color:%s)"), color_value); } -static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) +static void refname_atom_parser_internal(struct refname_atom *atom, + const char *arg, const char *name) { if (!arg) - atom->u.remote_ref = RR_NORMAL; + atom->option = R_NORMAL; else if (!strcmp(arg, "short")) - atom->u.remote_ref = RR_SHORTEN; - else if (!strcmp(arg, "track")) - atom->u.remote_ref = RR_TRACK; - else if (!strcmp(arg, "trackshort")) - atom->u.remote_ref = RR_TRACKSHORT; - else - die(_("unrecognized format: %%(%s)"), atom->name); + atom->option = R_SHORT; + else if (skip_prefix(arg, "lstrip=", &arg) || + skip_prefix(arg, "strip=", &arg)) { + atom->option = R_LSTRIP; + if (strtol_i(arg, 10, &atom->lstrip)) + die(_("Integer value expected refname:lstrip=%s"), arg); + } else if (skip_prefix(arg, "rstrip=", &arg)) { + atom->option = R_RSTRIP; + if (strtol_i(arg, 10, &atom->rstrip)) + die(_("Integer value expected refname:rstrip=%s"), arg); + } else + die(_("unrecognized %%(%s) argument: %s"), name, arg); +} + +static void remote_ref_atom_parser(struct used_atom *atom, const char *arg) +{ + struct string_list params = STRING_LIST_INIT_DUP; + int i; + + if (!arg) { + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); + return; + } + + atom->u.remote_ref.nobracket = 0; + string_list_split(¶ms, arg, ',', -1); + + for (i = 0; i < params.nr; i++) { + const char *s = params.items[i].string; + + if (!strcmp(s, "track")) + atom->u.remote_ref.option = RR_TRACK; + else if (!strcmp(s, "trackshort")) + atom->u.remote_ref.option = RR_TRACKSHORT; + else if (!strcmp(s, "nobracket")) + atom->u.remote_ref.nobracket = 1; + else { + atom->u.remote_ref.option = RR_REF; + refname_atom_parser_internal(&atom->u.remote_ref.refname, + arg, atom->name); + } + } + + string_list_clear(¶ms, 0); } static void body_atom_parser(struct used_atom *atom, const char *arg) @@ -116,13 +204,25 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg) static void objectname_atom_parser(struct used_atom *atom, const char *arg) { if (!arg) - atom->u.objectname = O_FULL; + atom->u.objectname.option = O_FULL; else if (!strcmp(arg, "short")) - atom->u.objectname = O_SHORT; - else + atom->u.objectname.option = O_SHORT; + else if (skip_prefix(arg, "short=", &arg)) { + atom->u.objectname.option = O_LENGTH; + if (strtoul_ui(arg, 10, &atom->u.objectname.length) || + atom->u.objectname.length == 0) + die(_("positive value expected objectname:short=%s"), arg); + if (atom->u.objectname.length < MINIMUM_ABBREV) + atom->u.objectname.length = MINIMUM_ABBREV; + } else die(_("unrecognized %%(objectname) argument: %s"), arg); } +static void refname_atom_parser(struct used_atom *atom, const char *arg) +{ + return refname_atom_parser_internal(&atom->u.refname, arg, atom->name); +} + static align_type parse_align_position(const char *s) { if (!strcmp(s, "right")) @@ -173,12 +273,27 @@ static void align_atom_parser(struct used_atom *atom, const char *arg) string_list_clear(¶ms, 0); } +static void if_atom_parser(struct used_atom *atom, const char *arg) +{ + if (!arg) { + atom->u.if_then_else.cmp_status = COMPARE_NONE; + return; + } else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) { + atom->u.if_then_else.cmp_status = COMPARE_EQUAL; + } else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) { + atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL; + } else { + die(_("unrecognized %%(if) argument: %s"), arg); + } +} + + static struct { const char *name; cmp_type cmp_type; void (*parser)(struct used_atom *atom, const char *arg); } valid_atom[] = { - { "refname" }, + { "refname" , FIELD_STR, refname_atom_parser }, { "objecttype" }, { "objectsize", FIELD_ULONG }, { "objectname", FIELD_STR, objectname_atom_parser }, @@ -208,12 +323,15 @@ static struct { { "contents", FIELD_STR, contents_atom_parser }, { "upstream", FIELD_STR, remote_ref_atom_parser }, { "push", FIELD_STR, remote_ref_atom_parser }, - { "symref" }, + { "symref", FIELD_STR, refname_atom_parser }, { "flag" }, { "HEAD" }, { "color", FIELD_STR, color_atom_parser }, { "align", FIELD_STR, align_atom_parser }, { "end" }, + { "if", FIELD_STR, if_atom_parser }, + { "then" }, + { "else" }, }; #define REF_FORMATTING_STATE_INIT { 0, NULL } @@ -221,7 +339,7 @@ static struct { struct ref_formatting_stack { struct ref_formatting_stack *prev; struct strbuf output; - void (*at_end)(struct ref_formatting_stack *stack); + void (*at_end)(struct ref_formatting_stack **stack); void *at_end_data; }; @@ -232,11 +350,9 @@ struct ref_formatting_state { struct atom_value { const char *s; - union { - struct align align; - } u; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ + struct used_atom *atom; }; /* @@ -293,7 +409,7 @@ int parse_ref_filter_atom(const char *atom, const char *ep) valid_atom[i].parser(&used_atom[at], arg); if (*atom == '*') need_tagged = 1; - if (!strcmp(used_atom[at].name, "symref")) + if (!strcmp(valid_atom[i].name, "symref")) need_symref = 1; return at; } @@ -354,13 +470,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack) *stack = prev; } -static void end_align_handler(struct ref_formatting_stack *stack) +static void end_align_handler(struct ref_formatting_stack **stack) { - struct align *align = (struct align *)stack->at_end_data; + struct ref_formatting_stack *cur = *stack; + struct align *align = (struct align *)cur->at_end_data; struct strbuf s = STRBUF_INIT; - strbuf_utf8_align(&s, align->position, align->width, stack->output.buf); - strbuf_swap(&stack->output, &s); + strbuf_utf8_align(&s, align->position, align->width, cur->output.buf); + strbuf_swap(&cur->output, &s); strbuf_release(&s); } @@ -371,7 +488,115 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s push_stack_element(&state->stack); new = state->stack; new->at_end = end_align_handler; - new->at_end_data = &atomv->u.align; + new->at_end_data = &atomv->atom->u.align; +} + +static void if_then_else_handler(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *cur = *stack; + struct ref_formatting_stack *prev = cur->prev; + struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data; + + if (!if_then_else->then_atom_seen) + die(_("format: %%(if) atom used without a %%(then) atom")); + + if (if_then_else->else_atom_seen) { + /* + * There is an %(else) atom: we need to drop one state from the + * stack, either the %(else) branch if the condition is satisfied, or + * the %(then) branch if it isn't. + */ + if (if_then_else->condition_satisfied) { + strbuf_reset(&cur->output); + pop_stack_element(&cur); + } else { + strbuf_swap(&cur->output, &prev->output); + strbuf_reset(&cur->output); + pop_stack_element(&cur); + } + } else if (!if_then_else->condition_satisfied) { + /* + * No %(else) atom: just drop the %(then) branch if the + * condition is not satisfied. + */ + strbuf_reset(&cur->output); + } + + *stack = cur; + free(if_then_else); +} + +static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *new; + struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1); + + if_then_else->str = atomv->atom->u.if_then_else.str; + if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status; + + push_stack_element(&state->stack); + new = state->stack; + new->at_end = if_then_else_handler; + new->at_end_data = if_then_else; +} + +static int is_empty(const char *s) +{ + while (*s != '\0') { + if (!isspace(*s)) + return 0; + s++; + } + return 1; +} + +static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *cur = state->stack; + struct if_then_else *if_then_else = NULL; + + if (cur->at_end == if_then_else_handler) + if_then_else = (struct if_then_else *)cur->at_end_data; + if (!if_then_else) + die(_("format: %%(then) atom used without an %%(if) atom")); + if (if_then_else->then_atom_seen) + die(_("format: %%(then) atom used more than once")); + if (if_then_else->else_atom_seen) + die(_("format: %%(then) atom used after %%(else)")); + if_then_else->then_atom_seen = 1; + /* + * If the 'equals' or 'notequals' attribute is used then + * perform the required comparison. If not, only non-empty + * strings satisfy the 'if' condition. + */ + if (if_then_else->cmp_status == COMPARE_EQUAL) { + if (!strcmp(if_then_else->str, cur->output.buf)) + if_then_else->condition_satisfied = 1; + } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) { + if (strcmp(if_then_else->str, cur->output.buf)) + if_then_else->condition_satisfied = 1; + } else if (cur->output.len && !is_empty(cur->output.buf)) + if_then_else->condition_satisfied = 1; + strbuf_reset(&cur->output); +} + +static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *prev = state->stack; + struct if_then_else *if_then_else = NULL; + + if (prev->at_end == if_then_else_handler) + if_then_else = (struct if_then_else *)prev->at_end_data; + if (!if_then_else) + die(_("format: %%(else) atom used without an %%(if) atom")); + if (!if_then_else->then_atom_seen) + die(_("format: %%(else) atom used without a %%(then) atom")); + if (if_then_else->else_atom_seen) + die(_("format: %%(else) atom used more than once")); + if_then_else->else_atom_seen = 1; + push_stack_element(&state->stack); + state->stack->at_end_data = prev->at_end_data; + state->stack->at_end = prev->at_end; } static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) @@ -381,14 +606,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta if (!current->at_end) die(_("format: %%(end) atom used without corresponding atom")); - current->at_end(current); + current->at_end(&state->stack); + + /* Stack may have been popped within at_end(), hence reset the current pointer */ + current = state->stack; /* * Perform quote formatting when the stack element is that of * a supporting atom. If nested then perform quote formatting * only on the topmost supporting atom. */ - if (!state->stack->prev->prev) { + if (!current->prev->prev) { quote_formatting(&s, current->output.buf, state->quote_style); strbuf_swap(¤t->output, &s); } @@ -465,12 +693,15 @@ static int grab_objectname(const char *name, const unsigned char *sha1, struct atom_value *v, struct used_atom *atom) { if (starts_with(name, "objectname")) { - if (atom->u.objectname == O_SHORT) { + if (atom->u.objectname.option == O_SHORT) { v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 1; - } else if (atom->u.objectname == O_FULL) { + } else if (atom->u.objectname.option == O_FULL) { v->s = xstrdup(sha1_to_hex(sha1)); return 1; + } else if (atom->u.objectname.option == O_LENGTH) { + v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length)); + return 1; } else die("BUG: unknown %%(objectname) option"); } @@ -887,50 +1118,108 @@ static inline char *copy_advance(char *dst, const char *src) return dst; } -static const char *strip_ref_components(const char *refname, const char *nr_arg) +static const char *lstrip_ref_components(const char *refname, int len) { - char *end; - long nr = strtol(nr_arg, &end, 10); - long remaining = nr; + long remaining = len; const char *start = refname; - if (nr < 1 || *end != '\0') - die(_(":strip= requires a positive integer argument")); + if (len < 0) { + int i; + const char *p = refname; - while (remaining) { + /* Find total no of '/' separated path-components */ + for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) + ; + /* + * The number of components we need to strip is now + * the total minus the components to be left (Plus one + * because we count the number of '/', but the number + * of components is one more than the no of '/'). + */ + remaining = i + len + 1; + } + + while (remaining > 0) { switch (*start++) { case '\0': - die(_("ref '%s' does not have %ld components to :strip"), - refname, nr); + return ""; case '/': remaining--; break; } } + return start; } +static const char *rstrip_ref_components(const char *refname, int len) +{ + long remaining = len; + char *start = xstrdup(refname); + + if (len < 0) { + int i; + const char *p = refname; + + /* Find total no of '/' separated path-components */ + for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) + ; + /* + * The number of components we need to strip is now + * the total minus the components to be left (Plus one + * because we count the number of '/', but the number + * of components is one more than the no of '/'). + */ + remaining = i + len + 1; + } + + while (remaining-- > 0) { + char *p = strrchr(start, '/'); + if (p == NULL) + return ""; + else + p[0] = '\0'; + } + return start; +} + +static const char *show_ref(struct refname_atom *atom, const char *refname) +{ + if (atom->option == R_SHORT) + return shorten_unambiguous_ref(refname, warn_ambiguous_refs); + else if (atom->option == R_LSTRIP) + return lstrip_ref_components(refname, atom->lstrip); + else if (atom->option == R_RSTRIP) + return rstrip_ref_components(refname, atom->rstrip); + else + return refname; +} + static void fill_remote_ref_details(struct used_atom *atom, const char *refname, struct branch *branch, const char **s) { int num_ours, num_theirs; - if (atom->u.remote_ref == RR_SHORTEN) - *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs); - else if (atom->u.remote_ref == RR_TRACK) { + if (atom->u.remote_ref.option == RR_REF) + *s = show_ref(&atom->u.remote_ref.refname, refname); + else if (atom->u.remote_ref.option == RR_TRACK) { if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - return; - - if (!num_ours && !num_theirs) + &num_theirs, NULL)) { + *s = xstrdup(msgs.gone); + } else if (!num_ours && !num_theirs) *s = ""; else if (!num_ours) - *s = xstrfmt("[behind %d]", num_theirs); + *s = xstrfmt(msgs.behind, num_theirs); else if (!num_theirs) - *s = xstrfmt("[ahead %d]", num_ours); + *s = xstrfmt(msgs.ahead, num_ours); else - *s = xstrfmt("[ahead %d, behind %d]", + *s = xstrfmt(msgs.ahead_behind, num_ours, num_theirs); - } else if (atom->u.remote_ref == RR_TRACKSHORT) { + if (!atom->u.remote_ref.nobracket && *s[0]) { + const char *to_free = *s; + *s = xstrfmt("[%s]", *s); + free((void *)to_free); + } + } else if (atom->u.remote_ref.option == RR_TRACKSHORT) { if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) return; @@ -943,8 +1232,56 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname, *s = ">"; else *s = "<>"; - } else /* RR_NORMAL */ - *s = refname; + } else + die("BUG: unhandled RR_* enum"); +} + +char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) { + if (state.detached_at) + /* TRANSLATORS: make sure this matches + "HEAD detached at " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + /* TRANSLATORS: make sure this matches + "HEAD detached from " in wt-status.c */ + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} + +static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref) +{ + if (!ref->symref) + return ""; + else + return show_ref(&atom->u.refname, ref->symref); +} + +static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref) +{ + if (ref->kind & FILTER_REFS_DETACHED_HEAD) + return get_head_description(); + return show_ref(&atom->u.refname, ref->refname); } /* @@ -961,9 +1298,9 @@ static void populate_value(struct ref_array_item *ref) ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { - unsigned char unused1[20]; + struct object_id unused1; ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, - unused1, NULL); + unused1.hash, NULL); if (!ref->symref) ref->symref = ""; } @@ -975,10 +1312,10 @@ static void populate_value(struct ref_array_item *ref) struct atom_value *v = &ref->value[i]; int deref = 0; const char *refname; - const char *formatp; struct branch *branch = NULL; v->handler = append_atom; + v->atom = atom; if (*name == '*') { deref = 1; @@ -986,9 +1323,9 @@ static void populate_value(struct ref_array_item *ref) } if (starts_with(name, "refname")) - refname = ref->refname; + refname = get_refname(atom, ref); else if (starts_with(name, "symref")) - refname = ref->symref ? ref->symref : ""; + refname = get_symref(atom, ref); else if (starts_with(name, "upstream")) { const char *branch_name; /* only local branches may have an upstream */ @@ -1043,30 +1380,27 @@ static void populate_value(struct ref_array_item *ref) v->s = " "; continue; } else if (starts_with(name, "align")) { - v->u.align = atom->u.align; v->handler = align_atom_handler; continue; } else if (!strcmp(name, "end")) { v->handler = end_atom_handler; continue; + } else if (starts_with(name, "if")) { + const char *s; + + if (skip_prefix(name, "if:", &s)) + v->s = xstrdup(s); + v->handler = if_atom_handler; + continue; + } else if (!strcmp(name, "then")) { + v->handler = then_atom_handler; + continue; + } else if (!strcmp(name, "else")) { + v->handler = else_atom_handler; + continue; } else continue; - formatp = strchr(name, ':'); - if (formatp) { - const char *arg; - - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else if (skip_prefix(formatp, "strip=", &arg)) - refname = strip_ref_components(refname, arg); - else - die(_("unknown %.*s format %s"), - (int)(formatp - name), name, formatp); - } - if (!deref) v->s = refname; else @@ -1137,10 +1471,22 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom *v = &ref->value[atom]; } +/* + * Unknown has to be "0" here, because that's the default value for + * contains_cache slab entries that have not yet been assigned. + */ enum contains_result { - CONTAINS_UNKNOWN = -1, - CONTAINS_NO = 0, - CONTAINS_YES = 1 + CONTAINS_UNKNOWN = 0, + CONTAINS_NO, + CONTAINS_YES +}; + +define_commit_slab(contains_cache, enum contains_result); + +struct ref_filter_cbdata { + struct ref_array *array; + struct ref_filter *filter; + struct contains_cache contains_cache; }; /* @@ -1171,24 +1517,24 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) * Do not recurse to find out, though, but return -1 if inconclusive. */ static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want) + const struct commit_list *want, + struct contains_cache *cache) { - /* was it previously marked as containing a want commit? */ - if (candidate->object.flags & TMP_MARK) - return 1; - /* or marked as not possibly containing a want commit? */ - if (candidate->object.flags & UNINTERESTING) - return 0; + enum contains_result *cached = contains_cache_at(cache, candidate); + + /* If we already have the answer cached, return that. */ + if (*cached) + return *cached; + /* or are we it? */ if (in_commit_list(want, candidate)) { - candidate->object.flags |= TMP_MARK; - return 1; + *cached = CONTAINS_YES; + return CONTAINS_YES; } - if (parse_commit(candidate) < 0) - return 0; - - return -1; + /* Otherwise, we don't know; prepare to recurse */ + parse_commit_or_die(candidate); + return CONTAINS_UNKNOWN; } static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack) @@ -1199,10 +1545,11 @@ static void push_to_contains_stack(struct commit *candidate, struct contains_sta } static enum contains_result contains_tag_algo(struct commit *candidate, - const struct commit_list *want) + const struct commit_list *want, + struct contains_cache *cache) { struct contains_stack contains_stack = { 0, 0, NULL }; - int result = contains_test(candidate, want); + enum contains_result result = contains_test(candidate, want, cache); if (result != CONTAINS_UNKNOWN) return result; @@ -1214,16 +1561,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate, struct commit_list *parents = entry->parents; if (!parents) { - commit->object.flags |= UNINTERESTING; + *contains_cache_at(cache, commit) = CONTAINS_NO; contains_stack.nr--; } /* * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful 0 or 1. + * therefore contains_test will return a meaningful yes/no. */ - else switch (contains_test(parents->item, want)) { + else switch (contains_test(parents->item, want, cache)) { case CONTAINS_YES: - commit->object.flags |= TMP_MARK; + *contains_cache_at(cache, commit) = CONTAINS_YES; contains_stack.nr--; break; case CONTAINS_NO: @@ -1235,13 +1582,14 @@ static enum contains_result contains_tag_algo(struct commit *candidate, } } free(contains_stack.contains_stack); - return contains_test(candidate, want); + return contains_test(candidate, want, cache); } -static int commit_contains(struct ref_filter *filter, struct commit *commit) +static int commit_contains(struct ref_filter *filter, struct commit *commit, + struct contains_cache *cache) { if (filter->with_commit_tag_algo) - return contains_tag_algo(commit, filter->with_commit); + return contains_tag_algo(commit, filter->with_commit, cache) == CONTAINS_YES; return is_descendant_of(commit, filter->with_commit); } @@ -1438,7 +1786,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, return 0; /* We perform the filtering for the '--contains' option */ if (filter->with_commit && - !commit_contains(filter, commit)) + !commit_contains(filter, commit, &ref_cbdata->contains_cache)) return 0; } @@ -1538,6 +1886,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int broken = 1; filter->kind = type & FILTER_REFS_KIND_MASK; + init_contains_cache(&ref_cbdata.contains_cache); + /* Simple per-ref filtering */ if (!filter->kind) die("filter_refs: invalid type"); @@ -1560,6 +1910,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int head_ref(ref_filter_handler, &ref_cbdata); } + clear_contains_cache(&ref_cbdata.contains_cache); /* Filters that need revision walking */ if (filter->merge_commit) @@ -1635,10 +1986,10 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting } } -void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) +void format_ref_array_item(struct ref_array_item *info, const char *format, + int quote_style, struct strbuf *final_buf) { const char *cp, *sp, *ep; - struct strbuf *final_buf; struct ref_formatting_state state = REF_FORMATTING_STATE_INIT; state.quote_style = quote_style; @@ -1668,9 +2019,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu } if (state.stack->prev) die(_("format: %%(end) atom missing")); - final_buf = &state.stack->output; - fwrite(final_buf->buf, 1, final_buf->len, stdout); + strbuf_addbuf(final_buf, &state.stack->output); pop_stack_element(&state.stack); +} + +void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) +{ + struct strbuf final_buf = STRBUF_INIT; + + format_ref_array_item(info, format, quote_style, &final_buf); + fwrite(final_buf.buf, 1, final_buf.len, stdout); + strbuf_release(&final_buf); putchar('\n'); } diff --git a/ref-filter.h b/ref-filter.h index 7b05592baf..e738c5dfd3 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -71,11 +71,6 @@ struct ref_filter { verbose; }; -struct ref_filter_cbdata { - struct ref_array *array; - struct ref_filter *filter; -}; - /* Macros for checking --merged and --no-merged options */ #define _OPT_MERGED_NO_MERGED(option, filter, h) \ { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \ @@ -100,6 +95,9 @@ int parse_ref_filter_atom(const char *atom, const char *ep); int verify_ref_format(const char *format); /* Sort the given ref_array as per the ref_sorting provided */ void ref_array_sort(struct ref_sorting *sort, struct ref_array *array); +/* Based on the given format and quote_style, fill the strbuf */ +void format_ref_array_item(struct ref_array_item *info, const char *format, + int quote_style, struct strbuf *final_buf); /* Print the ref using the given format and quote_style */ void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style); /* Callback function for parsing the sort option */ @@ -108,6 +106,10 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset); struct ref_sorting *ref_default_sorting(void); /* Function to parse --merged and --no-merged options */ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset); +/* Get the current HEAD's description */ +char *get_head_description(void); +/* Set up translated strings in the output. */ +void setup_ref_filter_porcelain_msg(void); /* * Print a single ref, outside of any ref-filter. Note that the diff --git a/reflog-walk.c b/reflog-walk.c index a246af2767..99679f5825 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -10,7 +10,7 @@ struct complete_reflogs { char *ref; const char *short_ref; struct reflog_info { - unsigned char osha1[20], nsha1[20]; + struct object_id ooid, noid; char *email; unsigned long timestamp; int tz; @@ -19,7 +19,7 @@ struct complete_reflogs { int nr, alloc; }; -static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1, +static int read_one_reflog(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -28,8 +28,8 @@ static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1, ALLOC_GROW(array->items, array->nr + 1, array->alloc); item = array->items + array->nr; - hashcpy(item->osha1, osha1); - hashcpy(item->nsha1, nsha1); + oidcpy(&item->ooid, ooid); + oidcpy(&item->noid, noid); item->email = xstrdup(email); item->timestamp = timestamp; item->tz = tz; @@ -45,11 +45,11 @@ static struct complete_reflogs *read_complete_reflog(const char *ref) reflogs->ref = xstrdup(ref); for_each_reflog_ent(ref, read_one_reflog, reflogs); if (reflogs->nr == 0) { - unsigned char sha1[20]; + struct object_id oid; const char *name; void *name_to_free; name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING, - sha1, NULL); + oid.hash, NULL); if (name) { for_each_reflog_ent(name, read_one_reflog, reflogs); free(name_to_free); @@ -172,18 +172,18 @@ int add_reflog_for_walk(struct reflog_walk_info *info, reflogs = item->util; else { if (*branch == '\0') { - unsigned char sha1[20]; + struct object_id oid; free(branch); - branch = resolve_refdup("HEAD", 0, sha1, NULL); + branch = resolve_refdup("HEAD", 0, oid.hash, NULL); if (!branch) die ("No current branch"); } reflogs = read_complete_reflog(branch); if (!reflogs || reflogs->nr == 0) { - unsigned char sha1[20]; + struct object_id oid; char *b; - if (dwim_log(branch, strlen(branch), sha1, &b) == 1) { + if (dwim_log(branch, strlen(branch), oid.hash, &b) == 1) { if (reflogs) { free(reflogs->ref); free(reflogs); @@ -238,13 +238,13 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) do { reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; commit_reflog->recno--; - logobj = parse_object(reflog->osha1); + logobj = parse_object(reflog->ooid.hash); } while (commit_reflog->recno && (logobj && logobj->type != OBJ_COMMIT)); - if (!logobj && commit_reflog->recno >= 0 && is_null_sha1(reflog->osha1)) { + if (!logobj && commit_reflog->recno >= 0 && is_null_sha1(reflog->ooid.hash)) { /* a root commit, but there are still more entries to show */ reflog = &commit_reflog->reflogs->items[commit_reflog->recno]; - logobj = parse_object(reflog->nsha1); + logobj = parse_object(reflog->noid.hash); } if (!logobj || logobj->type != OBJ_COMMIT) { @@ -3,6 +3,7 @@ */ #include "cache.h" +#include "hashmap.h" #include "lockfile.h" #include "refs.h" #include "refs/refs-internal.h" @@ -404,7 +405,7 @@ int refname_match(const char *abbrev_name, const char *full_name) static char *substitute_branch_name(const char **string, int *len) { struct strbuf buf = STRBUF_INIT; - int ret = interpret_branch_name(*string, *len, &buf); + int ret = interpret_branch_name(*string, *len, &buf, 0); if (ret == *len) { size_t size; @@ -591,8 +592,8 @@ static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1 return 0; } -int delete_ref(const char *refname, const unsigned char *old_sha1, - unsigned int flags) +int delete_ref(const char *msg, const char *refname, + const unsigned char *old_sha1, unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; @@ -603,7 +604,7 @@ int delete_ref(const char *refname, const unsigned char *old_sha1, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, refname, old_sha1, - flags, NULL, &err) || + flags, msg, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); @@ -674,7 +675,7 @@ struct read_ref_at_cb { int *cutoff_cnt; }; -static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1, +static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -698,30 +699,30 @@ static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1, * hold the values for the previous record. */ if (!is_null_sha1(cb->osha1)) { - hashcpy(cb->sha1, nsha1); - if (hashcmp(cb->osha1, nsha1)) + hashcpy(cb->sha1, noid->hash); + if (hashcmp(cb->osha1, noid->hash)) warning("Log for ref %s has gap after %s.", cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); } else if (cb->date == cb->at_time) - hashcpy(cb->sha1, nsha1); - else if (hashcmp(nsha1, cb->sha1)) + hashcpy(cb->sha1, noid->hash); + else if (hashcmp(noid->hash, cb->sha1)) warning("Log for ref %s unexpectedly ended on %s.", cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); - hashcpy(cb->osha1, osha1); - hashcpy(cb->nsha1, nsha1); + hashcpy(cb->osha1, ooid->hash); + hashcpy(cb->nsha1, noid->hash); cb->found_it = 1; return 1; } - hashcpy(cb->osha1, osha1); - hashcpy(cb->nsha1, nsha1); + hashcpy(cb->osha1, ooid->hash); + hashcpy(cb->nsha1, noid->hash); if (cb->cnt > 0) cb->cnt--; return 0; } -static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, +static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -735,9 +736,9 @@ static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, *cb->cutoff_tz = tz; if (cb->cutoff_cnt) *cb->cutoff_cnt = cb->reccnt; - hashcpy(cb->sha1, osha1); + hashcpy(cb->sha1, ooid->hash); if (is_null_sha1(cb->sha1)) - hashcpy(cb->sha1, nsha1); + hashcpy(cb->sha1, noid->hash); /* We just want the first entry */ return 1; } @@ -1034,10 +1035,10 @@ static struct string_list *hide_refs; int parse_hide_refs_config(const char *var, const char *value, const char *section) { + const char *key; if (!strcmp("transfer.hiderefs", var) || - /* NEEDSWORK: use parse_config_key() once both are merged */ - (starts_with(var, section) && var[strlen(section)] == '.' && - !strcmp(var + strlen(section), ".hiderefs"))) { + (!parse_config_key(var, section, NULL, NULL, &key) && + !strcmp(key, "hiderefs"))) { char *ref; int len; @@ -1234,10 +1235,10 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) } /* This function needs to return a meaningful errno on failure */ -static const char *resolve_ref_recursively(struct ref_store *refs, - const char *refname, - int resolve_flags, - unsigned char *sha1, int *flags) +const char *resolve_ref_recursively(struct ref_store *refs, + const char *refname, + int resolve_flags, + unsigned char *sha1, int *flags) { static struct strbuf sb_refname = STRBUF_INIT; int unused_flags; @@ -1357,62 +1358,102 @@ int resolve_gitlink_ref(const char *submodule, const char *refname, return 0; } +struct submodule_hash_entry +{ + struct hashmap_entry ent; /* must be the first member! */ + + struct ref_store *refs; + + /* NUL-terminated name of submodule: */ + char submodule[FLEX_ARRAY]; +}; + +static int submodule_hash_cmp(const void *entry, const void *entry_or_key, + const void *keydata) +{ + const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key; + const char *submodule = keydata ? keydata : e2->submodule; + + return strcmp(e1->submodule, submodule); +} + +static struct submodule_hash_entry *alloc_submodule_hash_entry( + const char *submodule, struct ref_store *refs) +{ + struct submodule_hash_entry *entry; + + FLEX_ALLOC_STR(entry, submodule, submodule); + hashmap_entry_init(entry, strhash(submodule)); + entry->refs = refs; + return entry; +} + /* A pointer to the ref_store for the main repository: */ static struct ref_store *main_ref_store; -/* A linked list of ref_stores for submodules: */ -static struct ref_store *submodule_ref_stores; +/* A hashmap of ref_stores, stored by submodule name: */ +static struct hashmap submodule_ref_stores; -void base_ref_store_init(struct ref_store *refs, - const struct ref_storage_be *be, - const char *submodule) +/* + * Return the ref_store instance for the specified submodule (or the + * main repository if submodule is NULL). If that ref_store hasn't + * been initialized yet, return NULL. + */ +static struct ref_store *lookup_ref_store(const char *submodule) +{ + struct submodule_hash_entry *entry; + + if (!submodule) + return main_ref_store; + + if (!submodule_ref_stores.tablesize) + /* It's initialized on demand in register_ref_store(). */ + return NULL; + + entry = hashmap_get_from_hash(&submodule_ref_stores, + strhash(submodule), submodule); + return entry ? entry->refs : NULL; +} + +/* + * Register the specified ref_store to be the one that should be used + * for submodule (or the main repository if submodule is NULL). It is + * a fatal error to call this function twice for the same submodule. + */ +static void register_ref_store(struct ref_store *refs, const char *submodule) { - refs->be = be; if (!submodule) { if (main_ref_store) die("BUG: main_ref_store initialized twice"); - refs->submodule = ""; - refs->next = NULL; main_ref_store = refs; } else { - if (lookup_ref_store(submodule)) + if (!submodule_ref_stores.tablesize) + hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0); + + if (hashmap_put(&submodule_ref_stores, + alloc_submodule_hash_entry(submodule, refs))) die("BUG: ref_store for submodule '%s' initialized twice", submodule); - - refs->submodule = xstrdup(submodule); - refs->next = submodule_ref_stores; - submodule_ref_stores = refs; } } -struct ref_store *ref_store_init(const char *submodule) +/* + * Create, record, and return a ref_store instance for the specified + * submodule (or the main repository if submodule is NULL). + */ +static struct ref_store *ref_store_init(const char *submodule) { const char *be_name = "files"; struct ref_storage_be *be = find_ref_storage_backend(be_name); + struct ref_store *refs; if (!be) die("BUG: reference backend %s is unknown", be_name); - if (!submodule || !*submodule) - return be->init(NULL); - else - return be->init(submodule); -} - -struct ref_store *lookup_ref_store(const char *submodule) -{ - struct ref_store *refs; - - if (!submodule || !*submodule) - return main_ref_store; - - for (refs = submodule_ref_stores; refs; refs = refs->next) { - if (!strcmp(submodule, refs->submodule)) - return refs; - } - - return NULL; + refs = be->init(submodule); + register_ref_store(refs, submodule); + return refs; } struct ref_store *get_ref_store(const char *submodule) @@ -1440,10 +1481,10 @@ struct ref_store *get_ref_store(const char *submodule) return refs; } -void assert_main_repository(struct ref_store *refs, const char *caller) +void base_ref_store_init(struct ref_store *refs, + const struct ref_storage_be *be) { - if (*refs->submodule) - die("BUG: %s called for a submodule", caller); + refs->be = be; } /* backend functions */ @@ -276,8 +276,8 @@ int reflog_exists(const char *refname); * exists, regardless of its old value. It is an error for old_sha1 to * be NULL_SHA1. flags is passed through to ref_transaction_delete(). */ -int delete_ref(const char *refname, const unsigned char *old_sha1, - unsigned int flags); +int delete_ref(const char *msg, const char *refname, + const unsigned char *old_sha1, unsigned int flags); /* * Delete the specified references. If there are any problems, emit @@ -292,7 +292,7 @@ int delete_reflog(const char *refname); /* iterate over reflog entries */ typedef int each_reflog_ent_fn( - unsigned char *old_sha1, unsigned char *new_sha1, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, unsigned long timestamp, int tz, const char *msg, void *cb_data); @@ -334,7 +334,8 @@ int create_symref(const char *refname, const char *target, const char *logmsg); * $GIT_DIR points to. * Return 0 if successful, non-zero otherwise. * */ -int set_worktree_head_symref(const char *gitdir, const char *target); +int set_worktree_head_symref(const char *gitdir, const char *target, + const char *logmsg); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, diff --git a/refs/files-backend.c b/refs/files-backend.c index c041d4ba21..50188e92f9 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -912,6 +912,14 @@ struct packed_ref_cache { */ struct files_ref_store { struct ref_store base; + + /* + * The name of the submodule represented by this object, or + * NULL if it represents the main repository's reference + * store: + */ + const char *submodule; + struct ref_entry *loose; struct packed_ref_cache *packed; }; @@ -972,12 +980,25 @@ static struct ref_store *files_ref_store_create(const char *submodule) struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; - base_ref_store_init(ref_store, &refs_be_files, submodule); + base_ref_store_init(ref_store, &refs_be_files); + + refs->submodule = xstrdup_or_null(submodule); return ref_store; } /* + * Die if refs is for a submodule (i.e., not for the main repository). + * caller is used in any necessary error messages. + */ +static void files_assert_main_repository(struct files_ref_store *refs, + const char *caller) +{ + if (refs->submodule) + die("BUG: %s called for a submodule", caller); +} + +/* * Downcast ref_store to files_ref_store. Die if ref_store is not a * files_ref_store. If submodule_allowed is not true, then also die if * files_ref_store is for a submodule (i.e., not for the main @@ -987,14 +1008,18 @@ static struct files_ref_store *files_downcast( struct ref_store *ref_store, int submodule_allowed, const char *caller) { + struct files_ref_store *refs; + if (ref_store->be != &refs_be_files) die("BUG: ref_store is type \"%s\" not \"files\" in %s", ref_store->be->name, caller); + refs = (struct files_ref_store *)ref_store; + if (!submodule_allowed) - assert_main_repository(ref_store, caller); + files_assert_main_repository(refs, caller); - return (struct files_ref_store *)ref_store; + return refs; } /* The length of a peeled reference line in packed-refs, including EOL: */ @@ -1133,8 +1158,8 @@ static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *ref { char *packed_refs_file; - if (*refs->base.submodule) - packed_refs_file = git_pathdup_submodule(refs->base.submodule, + if (refs->submodule) + packed_refs_file = git_pathdup_submodule(refs->submodule, "packed-refs"); else packed_refs_file = git_pathdup("packed-refs"); @@ -1203,8 +1228,8 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) size_t path_baselen; int err = 0; - if (*refs->base.submodule) - err = strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname); + if (refs->submodule) + err = strbuf_git_path_submodule(&path, refs->submodule, "%s", dirname); else strbuf_git_path(&path, "%s", dirname); path_baselen = path.len; @@ -1242,20 +1267,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) create_dir_entry(refs, refname.buf, refname.len, 1)); } else { - int read_ok; - - if (*refs->base.submodule) { - hashclr(sha1); - flag = 0; - read_ok = !resolve_gitlink_ref(refs->base.submodule, - refname.buf, sha1); - } else { - read_ok = !read_ref_full(refname.buf, - RESOLVE_REF_READING, - sha1, &flag); - } - - if (!read_ok) { + if (!resolve_ref_recursively(&refs->base, + refname.buf, + RESOLVE_REF_READING, + sha1, &flag)) { hashclr(sha1); flag |= REF_ISBROKEN; } else if (is_null_sha1(sha1)) { @@ -1358,8 +1373,8 @@ static int files_read_raw_ref(struct ref_store *ref_store, *type = 0; strbuf_reset(&sb_path); - if (*refs->base.submodule) - strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname); + if (refs->submodule) + strbuf_git_path_submodule(&sb_path, refs->submodule, "%s", refname); else strbuf_git_path(&sb_path, "%s", refname); @@ -1540,7 +1555,7 @@ static int lock_raw_ref(struct files_ref_store *refs, int ret = TRANSACTION_GENERIC_ERROR; assert(err); - assert_main_repository(&refs->base, "lock_raw_ref"); + files_assert_main_repository(refs, "lock_raw_ref"); *type = 0; @@ -1985,6 +2000,13 @@ static int remove_empty_directories(struct strbuf *path) return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); } +static int create_reflock(const char *path, void *cb) +{ + struct lock_file *lk = cb; + + return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0; +} + /* * Locks a ref returning the lock on success and NULL on failure. * On failure errno is set to something meaningful. @@ -2000,13 +2022,11 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs, struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; int last_errno = 0; - int lflags = LOCK_NO_DEREF; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); int resolve_flags = RESOLVE_REF_NO_RECURSE; - int attempts_remaining = 3; int resolved; - assert_main_repository(&refs->base, "lock_ref_sha1_basic"); + files_assert_main_repository(refs, "lock_ref_sha1_basic"); assert(err); lock = xcalloc(1, sizeof(struct ref_lock)); @@ -2068,35 +2088,12 @@ static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs, lock->ref_name = xstrdup(refname); - retry: - switch (safe_create_leading_directories_const(ref_file.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: + if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) { last_errno = errno; - strbuf_addf(err, "unable to create directory for '%s'", - ref_file.buf); + unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } - if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { - last_errno = errno; - if (errno == ENOENT && --attempts_remaining > 0) - /* - * Maybe somebody just deleted one of the - * directories leading to ref_file. Try - * again: - */ - goto retry; - else { - unable_to_lock_message(ref_file.buf, errno, err); - goto error_return; - } - } if (verify_lock(lock, old_sha1, mustexist, err)) { last_errno = errno; goto error_return; @@ -2152,7 +2149,7 @@ static int lock_packed_refs(struct files_ref_store *refs, int flags) static int timeout_value = 1000; struct packed_ref_cache *packed_ref_cache; - assert_main_repository(&refs->base, "lock_packed_refs"); + files_assert_main_repository(refs, "lock_packed_refs"); if (!timeout_configured) { git_config_get_int("core.packedrefstimeout", &timeout_value); @@ -2190,7 +2187,7 @@ static int commit_packed_refs(struct files_ref_store *refs) int save_errno = 0; FILE *out; - assert_main_repository(&refs->base, "commit_packed_refs"); + files_assert_main_repository(refs, "commit_packed_refs"); if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@ -2223,7 +2220,7 @@ static void rollback_packed_refs(struct files_ref_store *refs) struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs); - assert_main_repository(&refs->base, "rollback_packed_refs"); + files_assert_main_repository(refs, "rollback_packed_refs"); if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@ -2298,15 +2295,25 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data) return 0; } +enum { + REMOVE_EMPTY_PARENTS_REF = 0x01, + REMOVE_EMPTY_PARENTS_REFLOG = 0x02 +}; + /* - * Remove empty parents, but spare refs/ and immediate subdirs. - * Note: munges *name. + * Remove empty parent directories associated with the specified + * reference and/or its reflog, but spare [logs/]refs/ and immediate + * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or + * REMOVE_EMPTY_PARENTS_REFLOG. */ -static void try_remove_empty_parents(char *name) +static void try_remove_empty_parents(const char *refname, unsigned int flags) { + struct strbuf buf = STRBUF_INIT; char *p, *q; int i; - p = name; + + strbuf_addstr(&buf, refname); + p = buf.buf; for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ while (*p && *p != '/') p++; @@ -2314,19 +2321,23 @@ static void try_remove_empty_parents(char *name) while (*p == '/') p++; } - for (q = p; *q; q++) - ; - while (1) { + q = buf.buf + buf.len; + while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) { while (q > p && *q != '/') q--; while (q > p && *(q-1) == '/') q--; if (q == p) break; - *q = '\0'; - if (rmdir(git_path("%s", name))) - break; + strbuf_setlen(&buf, q - buf.buf); + if ((flags & REMOVE_EMPTY_PARENTS_REF) && + rmdir(git_path("%s", buf.buf))) + flags &= ~REMOVE_EMPTY_PARENTS_REF; + if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) && + rmdir(git_path("logs/%s", buf.buf))) + flags &= ~REMOVE_EMPTY_PARENTS_REFLOG; } + strbuf_release(&buf); } /* make sure nobody touched the ref, and unlink */ @@ -2350,7 +2361,6 @@ static void prune_ref(struct ref_to_prune *r) } ref_transaction_free(transaction); strbuf_release(&err); - try_remove_empty_parents(r->name); } static void prune_refs(struct ref_to_prune *r) @@ -2397,7 +2407,7 @@ static int repack_without_refs(struct files_ref_store *refs, struct string_list_item *refname; int ret, needs_repacking = 0, removed = 0; - assert_main_repository(&refs->base, "repack_without_refs"); + files_assert_main_repository(refs, "repack_without_refs"); assert(err); /* Look for a packed ref */ @@ -2439,24 +2449,6 @@ static int repack_without_refs(struct files_ref_store *refs, return ret; } -static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) -{ - assert(err); - - if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { - /* - * loose. The loose file name is the same as the - * lockfile name, minus ".lock": - */ - char *loose_filename = get_locked_file_path(lock->lk); - int res = unlink_or_msg(loose_filename, err); - free(loose_filename); - if (res) - return 1; - } - return 0; -} - static int files_delete_refs(struct ref_store *ref_store, struct string_list *refnames, unsigned int flags) { @@ -2489,7 +2481,7 @@ static int files_delete_refs(struct ref_store *ref_store, for (i = 0; i < refnames->nr; i++) { const char *refname = refnames->items[i].string; - if (delete_ref(refname, NULL, flags)) + if (delete_ref(NULL, refname, NULL, flags)) result |= error(_("could not remove reference %s"), refname); } @@ -2507,55 +2499,43 @@ out: */ #define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" -static int rename_tmp_log(const char *newrefname) +static int rename_tmp_log_callback(const char *path, void *cb) { - int attempts_remaining = 4; - struct strbuf path = STRBUF_INIT; - int ret = -1; + int *true_errno = cb; - retry: - strbuf_reset(&path); - strbuf_git_path(&path, "logs/%s", newrefname); - switch (safe_create_leading_directories_const(path.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: - error("unable to create directory for %s", newrefname); - goto out; + if (rename(git_path(TMP_RENAMED_LOG), path)) { + /* + * rename(a, b) when b is an existing directory ought + * to result in ISDIR, but Solaris 5.8 gives ENOTDIR. + * Sheesh. Record the true errno for error reporting, + * but report EISDIR to raceproof_create_file() so + * that it knows to retry. + */ + *true_errno = errno; + if (errno == ENOTDIR) + errno = EISDIR; + return -1; + } else { + return 0; } +} - if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { - if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { - /* - * rename(a, b) when b is an existing - * directory ought to result in ISDIR, but - * Solaris 5.8 gives ENOTDIR. Sheesh. - */ - if (remove_empty_directories(&path)) { - error("Directory not empty: logs/%s", newrefname); - goto out; - } - goto retry; - } else if (errno == ENOENT && --attempts_remaining > 0) { - /* - * Maybe another process just deleted one of - * the directories in the path to newrefname. - * Try again from the beginning. - */ - goto retry; - } else { - error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", - newrefname, strerror(errno)); - goto out; - } +static int rename_tmp_log(const char *newrefname) +{ + char *path = git_pathdup("logs/%s", newrefname); + int ret, true_errno; + + ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno); + if (ret) { + if (errno == EISDIR) + error("directory not empty: %s", path); + else + error("unable to move logfile %s to %s: %s", + git_path(TMP_RENAMED_LOG), path, + strerror(true_errno)); } - ret = 0; -out: - strbuf_release(&path); + + free(path); return ret; } @@ -2616,7 +2596,7 @@ static int files_rename_ref(struct ref_store *ref_store, return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", oldrefname, strerror(errno)); - if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) { + if (delete_ref(logmsg, oldrefname, orig_sha1, REF_NODEREF)) { error("unable to delete old %s", oldrefname); goto rollback; } @@ -2630,8 +2610,8 @@ static int files_rename_ref(struct ref_store *ref_store, */ if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, sha1, NULL) && - delete_ref(newrefname, NULL, REF_NODEREF)) { - if (errno==EISDIR) { + delete_ref(NULL, newrefname, NULL, REF_NODEREF)) { + if (errno == EISDIR) { struct strbuf path = STRBUF_INIT; int result; @@ -2740,66 +2720,89 @@ static int commit_ref(struct ref_lock *lock) return 0; } +static int open_or_create_logfile(const char *path, void *cb) +{ + int *fd = cb; + + *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666); + return (*fd < 0) ? -1 : 0; +} + /* - * Create a reflog for a ref. If force_create = 0, the reflog will - * only be created for certain refs (those for which - * should_autocreate_reflog returns non-zero. Otherwise, create it - * regardless of the ref name. Fill in *err and return -1 on failure. + * Create a reflog for a ref. If force_create = 0, only create the + * reflog for certain refs (those for which should_autocreate_reflog + * returns non-zero). Otherwise, create it regardless of the reference + * name. If the logfile already existed or was created, return 0 and + * set *logfd to the file descriptor opened for appending to the file. + * If no logfile exists and we decided not to create one, return 0 and + * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and + * return -1. */ -static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) +static int log_ref_setup(const char *refname, int force_create, + int *logfd, struct strbuf *err) { - int logfd, oflags = O_APPEND | O_WRONLY; + char *logfile = git_pathdup("logs/%s", refname); - strbuf_git_path(logfile, "logs/%s", refname); if (force_create || should_autocreate_reflog(refname)) { - if (safe_create_leading_directories(logfile->buf) < 0) { - strbuf_addf(err, "unable to create directory for '%s': " - "%s", logfile->buf, strerror(errno)); - return -1; - } - oflags |= O_CREAT; - } - - logfd = open(logfile->buf, oflags, 0666); - if (logfd < 0) { - if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) - return 0; + if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) { + if (errno == ENOENT) + strbuf_addf(err, "unable to create directory for '%s': " + "%s", logfile, strerror(errno)); + else if (errno == EISDIR) + strbuf_addf(err, "there are still logs under '%s'", + logfile); + else + strbuf_addf(err, "unable to append to '%s': %s", + logfile, strerror(errno)); - if (errno == EISDIR) { - if (remove_empty_directories(logfile)) { - strbuf_addf(err, "there are still logs under " - "'%s'", logfile->buf); - return -1; - } - logfd = open(logfile->buf, oflags, 0666); + goto error; } - - if (logfd < 0) { - strbuf_addf(err, "unable to append to '%s': %s", - logfile->buf, strerror(errno)); - return -1; + } else { + *logfd = open(logfile, O_APPEND | O_WRONLY, 0666); + if (*logfd < 0) { + if (errno == ENOENT || errno == EISDIR) { + /* + * The logfile doesn't already exist, + * but that is not an error; it only + * means that we won't write log + * entries to it. + */ + ; + } else { + strbuf_addf(err, "unable to append to '%s': %s", + logfile, strerror(errno)); + goto error; + } } } - adjust_shared_perm(logfile->buf); - close(logfd); + if (*logfd >= 0) + adjust_shared_perm(logfile); + + free(logfile); return 0; -} +error: + free(logfile); + return -1; +} static int files_create_reflog(struct ref_store *ref_store, const char *refname, int force_create, struct strbuf *err) { - int ret; - struct strbuf sb = STRBUF_INIT; + int fd; /* Check validity (but we don't need the result): */ files_downcast(ref_store, 0, "create_reflog"); - ret = log_ref_setup(refname, &sb, err, force_create); - strbuf_release(&sb); - return ret; + if (log_ref_setup(refname, force_create, &fd, err)) + return -1; + + if (fd >= 0) + close(fd); + + return 0; } static int log_ref_write_fd(int fd, const unsigned char *old_sha1, @@ -2828,59 +2831,43 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1, return 0; } -static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - struct strbuf *logfile, int flags, - struct strbuf *err) +int files_log_ref_write(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err) { - int logfd, result, oflags = O_APPEND | O_WRONLY; + int logfd, result; if (log_all_ref_updates == LOG_REFS_UNSET) log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; - result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); + result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG, + &logfd, err); if (result) return result; - logfd = open(logfile->buf, oflags); if (logfd < 0) return 0; result = log_ref_write_fd(logfd, old_sha1, new_sha1, git_committer_info(0), msg); if (result) { - strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, - strerror(errno)); + int save_errno = errno; + + strbuf_addf(err, "unable to append to '%s': %s", + git_path("logs/%s", refname), strerror(save_errno)); close(logfd); return -1; } if (close(logfd)) { - strbuf_addf(err, "unable to append to '%s': %s", logfile->buf, - strerror(errno)); + int save_errno = errno; + + strbuf_addf(err, "unable to append to '%s': %s", + git_path("logs/%s", refname), strerror(save_errno)); return -1; } return 0; } -static int log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - int flags, struct strbuf *err) -{ - return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags, - err); -} - -int files_log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - int flags, struct strbuf *err) -{ - struct strbuf sb = STRBUF_INIT; - int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags, - err); - strbuf_release(&sb); - return ret; -} - /* * Write sha1 into the open lockfile, then close the lockfile. On * errors, rollback the lockfile, fill in *err and @@ -2930,10 +2917,11 @@ static int commit_ref_update(struct files_ref_store *refs, const unsigned char *sha1, const char *logmsg, struct strbuf *err) { - assert_main_repository(&refs->base, "commit_ref_update"); + files_assert_main_repository(refs, "commit_ref_update"); clear_loose_ref_cache(refs); - if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) { + if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, + logmsg, 0, err)) { char *old_msg = strbuf_detach(err, NULL); strbuf_addf(err, "cannot update the ref '%s': %s", lock->ref_name, old_msg); @@ -2964,7 +2952,7 @@ static int commit_ref_update(struct files_ref_store *refs, if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) { struct strbuf log_err = STRBUF_INIT; - if (log_ref_write("HEAD", lock->old_oid.hash, sha1, + if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg, 0, &log_err)) { error("%s", log_err.buf); strbuf_release(&log_err); @@ -3003,7 +2991,8 @@ static void update_symref_reflog(struct ref_lock *lock, const char *refname, struct strbuf err = STRBUF_INIT; unsigned char new_sha1[20]; if (logmsg && !read_ref(target, new_sha1) && - log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) { + files_log_ref_write(refname, lock->old_oid.hash, new_sha1, + logmsg, 0, &err)) { error("%s", err.buf); strbuf_release(&err); } @@ -3055,7 +3044,7 @@ static int files_create_symref(struct ref_store *ref_store, return ret; } -int set_worktree_head_symref(const char *gitdir, const char *target) +int set_worktree_head_symref(const char *gitdir, const char *target, const char *logmsg) { static struct lock_file head_lock; struct ref_lock *lock; @@ -3083,7 +3072,7 @@ int set_worktree_head_symref(const char *gitdir, const char *target) lock->lk = &head_lock; lock->ref_name = xstrdup(head_rel); - ret = create_symref_locked(lock, head_rel, target, NULL); + ret = create_symref_locked(lock, head_rel, target, logmsg); unlock_ref(lock); /* will free lock */ strbuf_release(&head_path); @@ -3113,16 +3102,17 @@ static int files_delete_reflog(struct ref_store *ref_store, static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) { - unsigned char osha1[20], nsha1[20]; + struct object_id ooid, noid; char *email_end, *message; unsigned long timestamp; int tz; + const char *p = sb->buf; /* old SP new SP name <email> SP time TAB msg LF */ - if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || - get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || - get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || - !(email_end = strchr(sb->buf + 82, '>')) || + if (!sb->len || sb->buf[sb->len - 1] != '\n' || + parse_oid_hex(p, &ooid, &p) || *p++ != ' ' || + parse_oid_hex(p, &noid, &p) || *p++ != ' ' || + !(email_end = strchr(p, '>')) || email_end[1] != ' ' || !(timestamp = strtoul(email_end + 2, &message, 10)) || !message || message[0] != ' ' || @@ -3136,7 +3126,7 @@ static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *c message += 6; else message += 7; - return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); + return fn(&ooid, &noid, p, timestamp, tz, message, cb_data); } static char *find_beginning_of_line(char *bob, char *scan) @@ -3560,7 +3550,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, int ret; struct ref_lock *lock; - assert_main_repository(&refs->base, "lock_ref_for_update"); + files_assert_main_repository(refs, "lock_ref_for_update"); if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) update->flags |= REF_DELETING; @@ -3778,9 +3768,11 @@ static int files_transaction_commit(struct ref_store *ref_store, if (update->flags & REF_NEEDS_COMMIT || update->flags & REF_LOG_ONLY) { - if (log_ref_write(lock->ref_name, lock->old_oid.hash, - update->new_sha1, - update->msg, update->flags, err)) { + if (files_log_ref_write(lock->ref_name, + lock->old_oid.hash, + update->new_sha1, + update->msg, update->flags, + err)) { char *old_msg = strbuf_detach(err, NULL); strbuf_addf(err, "cannot update the ref '%s': %s", @@ -3810,9 +3802,14 @@ static int files_transaction_commit(struct ref_store *ref_store, if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY)) { - if (delete_ref_loose(lock, update->type, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; + if (!(update->type & REF_ISPACKED) || + update->type & REF_ISSYMREF) { + /* It is a loose reference. */ + if (unlink_or_msg(git_path("%s", lock->ref_name), err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + update->flags |= REF_DELETED_LOOSE; } if (!(update->flags & REF_ISPRUNING)) @@ -3825,16 +3822,38 @@ static int files_transaction_commit(struct ref_store *ref_store, ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } - for_each_string_list_item(ref_to_delete, &refs_to_delete) - unlink_or_warn(git_path("logs/%s", ref_to_delete->string)); + + /* Delete the reflogs of any references that were deleted: */ + for_each_string_list_item(ref_to_delete, &refs_to_delete) { + if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string))) + try_remove_empty_parents(ref_to_delete->string, + REMOVE_EMPTY_PARENTS_REFLOG); + } + clear_loose_ref_cache(refs); cleanup: transaction->state = REF_TRANSACTION_CLOSED; - for (i = 0; i < transaction->nr; i++) - if (transaction->updates[i]->backend_data) - unlock_ref(transaction->updates[i]->backend_data); + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + struct ref_lock *lock = update->backend_data; + + if (lock) + unlock_ref(lock); + + if (update->flags & REF_DELETED_LOOSE) { + /* + * The loose reference was deleted. Delete any + * empty parent directories. (Note that this + * can only work because we have already + * removed the lockfile.) + */ + try_remove_empty_parents(update->refname, + REMOVE_EMPTY_PARENTS_REF); + } + } + string_list_clear(&refs_to_delete, 0); free(head_ref); string_list_clear(&affected_refnames, 0); @@ -3936,10 +3955,10 @@ struct expire_reflog_cb { reflog_expiry_should_prune_fn *should_prune_fn; void *policy_cb; FILE *newlog; - unsigned char last_kept_sha1[20]; + struct object_id last_kept_oid; }; -static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, +static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -3947,9 +3966,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; if (cb->flags & EXPIRE_REFLOGS_REWRITE) - osha1 = cb->last_kept_sha1; + ooid = &cb->last_kept_oid; - if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, + if ((*cb->should_prune_fn)(ooid->hash, noid->hash, email, timestamp, tz, message, policy_cb)) { if (!cb->newlog) printf("would prune %s", message); @@ -3958,9 +3977,9 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, } else { if (cb->newlog) { fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), + oid_to_hex(ooid), oid_to_hex(noid), email, timestamp, tz, message); - hashcpy(cb->last_kept_sha1, nsha1); + oidcpy(&cb->last_kept_oid, noid); } if (cb->flags & EXPIRE_REFLOGS_VERBOSE) printf("keep %s", message); @@ -4047,14 +4066,14 @@ static int files_reflog_expire(struct ref_store *ref_store, */ int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && !(type & REF_ISSYMREF) && - !is_null_sha1(cb.last_kept_sha1); + !is_null_oid(&cb.last_kept_oid); if (close_lock_file(&reflog_lock)) { status |= error("couldn't write %s: %s", log_file, strerror(errno)); } else if (update && (write_in_full(get_lock_file_fd(lock->lk), - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ || write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || close_ref(lock) < 0)) { status |= error("couldn't write %s", diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 25444cf5b0..fa93c9a32e 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -56,17 +56,24 @@ #define REF_UPDATE_VIA_HEAD 0x100 /* + * Used as a flag in ref_update::flags when the loose reference has + * been deleted. + */ +#define REF_DELETED_LOOSE 0x200 + +/* * Return true iff refname is minimally safe. "Safe" here means that * deleting a loose reference by this name will not do any damage, for * example by causing a file that is not a reference to be deleted. * This function does not check that the reference name is legal; for * that, use check_refname_format(). * - * We consider a refname that starts with "refs/" to be safe as long - * as any ".." components that it might contain do not escape "refs/". - * Names that do not start with "refs/" are considered safe iff they - * consist entirely of upper case characters and '_' (like "HEAD" and - * "MERGE_HEAD" but not "config" or "FOO/BAR"). + * A refname that starts with "refs/" is considered safe iff it + * doesn't contain any "." or ".." components or consecutive '/' + * characters, end with '/', or (on Windows) contain any '\' + * characters. Names that do not start with "refs/" are considered + * safe iff they consist entirely of upper case characters and '_' + * (like "HEAD" and "MERGE_HEAD" but not "config" or "FOO/BAR"). */ int refname_is_safe(const char *refname); @@ -155,8 +162,9 @@ struct ref_update { /* * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, - * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and - * REF_UPDATE_VIA_HEAD: + * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, + * REF_UPDATE_VIA_HEAD, REF_NEEDS_COMMIT, and + * REF_DELETED_LOOSE: */ unsigned int flags; @@ -627,47 +635,14 @@ extern struct ref_storage_be refs_be_files; struct ref_store { /* The backend describing this ref_store's storage scheme: */ const struct ref_storage_be *be; - - /* - * The name of the submodule represented by this object, or - * the empty string if it represents the main repository's - * reference store: - */ - const char *submodule; - - /* - * Submodule reference store instances are stored in a linked - * list using this pointer. - */ - struct ref_store *next; }; /* - * Fill in the generic part of refs for the specified submodule and - * add it to our collection of reference stores. + * Fill in the generic part of refs and add it to our collection of + * reference stores. */ void base_ref_store_init(struct ref_store *refs, - const struct ref_storage_be *be, - const char *submodule); - -/* - * Create, record, and return a ref_store instance for the specified - * submodule (or the main repository if submodule is NULL). - * - * For backwards compatibility, submodule=="" is treated the same as - * submodule==NULL. - */ -struct ref_store *ref_store_init(const char *submodule); - -/* - * Return the ref_store instance for the specified submodule (or the - * main repository if submodule is NULL). If that ref_store hasn't - * been initialized yet, return NULL. - * - * For backwards compatibility, submodule=="" is treated the same as - * submodule==NULL. - */ -struct ref_store *lookup_ref_store(const char *submodule); + const struct ref_storage_be *be); /* * Return the ref_store instance for the specified submodule. For the @@ -681,10 +656,9 @@ struct ref_store *lookup_ref_store(const char *submodule); */ struct ref_store *get_ref_store(const char *submodule); -/* - * Die if refs is for a submodule (i.e., not for the main repository). - * caller is used in any necessary error messages. - */ -void assert_main_repository(struct ref_store *refs, const char *caller); +const char *resolve_ref_recursively(struct ref_store *refs, + const char *refname, + int resolve_flags, + unsigned char *sha1, int *flags); #endif /* REFS_REFS_INTERNAL_H */ diff --git a/remote-curl.c b/remote-curl.c index 34a97e7328..e953d06f66 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -22,6 +22,7 @@ struct options { unsigned long depth; char *deepen_since; struct string_list deepen_not; + struct string_list push_options; unsigned progress : 1, check_self_contained_and_connected : 1, cloning : 1, @@ -139,6 +140,9 @@ static int set_option(const char *name, const char *value) else return -1; return 0; + } else if (!strcmp(name, "push-option")) { + string_list_append(&options.push_options, value); + return 0; #if LIBCURL_VERSION_NUM >= 0x070a08 } else if (!strcmp(name, "family")) { @@ -943,6 +947,9 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) argv_array_push(&args, "--quiet"); else if (options.verbosity > 1) argv_array_push(&args, "--verbose"); + for (i = 0; i < options.push_options.nr; i++) + argv_array_pushf(&args, "--push-option=%s", + options.push_options.items[i].string); argv_array_push(&args, options.progress ? "--progress" : "--no-progress"); for_each_string_list_item(cas_option, &cas_options) argv_array_push(&args, cas_option->string); @@ -1028,6 +1035,7 @@ int cmd_main(int argc, const char **argv) options.progress = !!isatty(2); options.thin = 1; string_list_init(&options.deepen_not, 1); + string_list_init(&options.push_options, 1); remote = remote_get(argv[1]); @@ -693,7 +693,7 @@ static struct remote *remote_get_1(const char *name, name = get_default(current_branch, &name_given); ret = make_remote(name, 0); - if (valid_remote_nick(name)) { + if (valid_remote_nick(name) && have_git_dir()) { if (!valid_remote(ret)) read_remotes_file(ret); if (!valid_remote(ret)) @@ -89,8 +89,13 @@ struct ref { force:1, forced_update:1, expect_old_sha1:1, - deletion:1, - matched:1; + deletion:1; + + enum { + REF_NOT_MATCHED = 0, /* initial value */ + REF_MATCHED, + REF_UNADVERTISED_NOT_ALLOWED + } match_status; /* * Order is important here, as we write to FETCH_HEAD diff --git a/revision.c b/revision.c index b37dbec378..7ff61ff5f7 100644 --- a/revision.c +++ b/revision.c @@ -147,7 +147,7 @@ static void add_pending_object_with_path(struct rev_info *revs, revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { struct strbuf buf = STRBUF_INIT; - int len = interpret_branch_name(name, 0, &buf); + int len = interpret_branch_name(name, 0, &buf, 0); int st; if (0 < len && name[len] && buf.len) @@ -1196,11 +1196,11 @@ static void handle_refs(const char *submodule, struct rev_info *revs, unsigned f for_each(submodule, handle_one_ref, &cb); } -static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) +static void handle_one_reflog_commit(struct object_id *oid, void *cb_data) { struct all_refs_cb *cb = cb_data; - if (!is_null_sha1(sha1)) { - struct object *o = parse_object(sha1); + if (!is_null_oid(oid)) { + struct object *o = parse_object(oid->hash); if (o) { o->flags |= cb->all_flags; /* ??? CMDLINEFLAGS ??? */ @@ -1214,12 +1214,12 @@ static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) } } -static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, +static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - handle_one_reflog_commit(osha1, cb_data); - handle_one_reflog_commit(nsha1, cb_data); + handle_one_reflog_commit(ooid, cb_data); + handle_one_reflog_commit(noid, cb_data); return 0; } diff --git a/revision.h b/revision.h index 9fac1a607d..14886ec92b 100644 --- a/revision.h +++ b/revision.h @@ -259,8 +259,6 @@ extern void put_revision_mark(const struct rev_info *revs, extern void mark_parents_uninteresting(struct commit *commit); extern void mark_tree_uninteresting(struct tree *tree); -char *path_name(struct strbuf *path, const char *name); - extern void show_object_with_name(FILE *, struct object *, const char *); extern void add_pending_object(struct rev_info *revs, diff --git a/run-command.c b/run-command.c index 5227f78aea..574b81d3e8 100644 --- a/run-command.c +++ b/run-command.c @@ -48,7 +48,7 @@ static void cleanup_children(int sig, int in_signal) kill(p->pid, sig); - if (p->process->wait_after_clean) { + if (p->process && p->process->wait_after_clean) { p->next = children_to_wait_for; children_to_wait_for = p; } else { diff --git a/send-pack.c b/send-pack.c index 6195b43e9a..66e652f7ef 100644 --- a/send-pack.c +++ b/send-pack.c @@ -72,6 +72,7 @@ static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, stru struct child_process po = CHILD_PROCESS_INIT; FILE *po_in; int i; + int rc; i = 4; if (args->use_thin_pack) @@ -125,27 +126,44 @@ static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, stru po.out = -1; } - if (finish_command(&po)) + rc = finish_command(&po); + if (rc) { + /* + * For a normal non-zero exit, we assume pack-objects wrote + * something useful to stderr. For death by signal, though, + * we should mention it to the user. The exception is SIGPIPE + * (141), because that's a normal occurence if the remote end + * hangs up (and we'll report that by trying to read the unpack + * status). + */ + if (rc > 128 && rc != 141) + error("pack-objects died of signal %d", rc - 128); return -1; + } + return 0; +} + +static int receive_unpack_status(int in) +{ + const char *line = packet_read_line(in, NULL); + if (!skip_prefix(line, "unpack ", &line)) + return error(_("unable to parse remote unpack status: %s"), line); + if (strcmp(line, "ok")) + return error(_("remote unpack failed: %s"), line); return 0; } static int receive_status(int in, struct ref *refs) { struct ref *hint; - int ret = 0; - char *line = packet_read_line(in, NULL); - if (!starts_with(line, "unpack ")) - return error("did not receive remote status"); - if (strcmp(line, "unpack ok")) { - error("unpack failed: %s", line + 7); - ret = -1; - } + int ret; + hint = NULL; + ret = receive_unpack_status(in); while (1) { char *refname; char *msg; - line = packet_read_line(in, NULL); + char *line = packet_read_line(in, NULL); if (!line) break; if (!starts_with(line, "ok ") && !starts_with(line, "ng ")) { @@ -514,6 +532,14 @@ int send_pack(struct send_pack_args *args, } } + if (use_push_options) { + struct string_list_item *item; + + packet_buf_flush(&req_buf); + for_each_string_list_item(item, args->push_options) + packet_buf_write(&req_buf, "%s", item->string); + } + if (args->stateless_rpc) { if (!args->dry_run && (cmds_sent || is_repository_shallow())) { packet_buf_flush(&req_buf); @@ -526,18 +552,6 @@ int send_pack(struct send_pack_args *args, strbuf_release(&req_buf); strbuf_release(&cap_buf); - if (use_push_options) { - struct string_list_item *item; - struct strbuf sb = STRBUF_INIT; - - for_each_string_list_item(item, args->push_options) - packet_buf_write(&sb, "%s", item->string); - - write_or_die(out, sb.buf, sb.len); - packet_flush(out); - strbuf_release(&sb); - } - if (use_sideband && cmds_sent) { memset(&demux, 0, sizeof(demux)); demux.proc = sideband_demux; @@ -557,6 +571,14 @@ int send_pack(struct send_pack_args *args, close(out); if (git_connection_is_socket(conn)) shutdown(fd[0], SHUT_WR); + + /* + * Do not even bother with the return value; we know we + * are failing, and just want the error() side effects. + */ + if (status_report) + receive_unpack_status(in); + if (use_sideband) { close(demux.out); finish_async(&demux); diff --git a/sequencer.c b/sequencer.c index 1f729b053b..d76dc9cb2b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1997,7 +1997,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) - warning(_("stopped at %s... %.*s"), + fprintf(stderr, + _("Stopped at %s... %.*s\n"), short_commit_name(commit), item->arg_len, item->arg); return error_with_patch(commit, @@ -135,6 +135,7 @@ int path_inside_repo(const char *prefix, const char *path) int check_filename(const char *prefix, const char *arg) { const char *name; + char *to_free = NULL; struct stat st; if (starts_with(arg, ":/")) { @@ -142,13 +143,17 @@ int check_filename(const char *prefix, const char *arg) return 1; name = arg + 2; } else if (prefix) - name = prefix_filename(prefix, strlen(prefix), arg); + name = to_free = prefix_filename(prefix, arg); else name = arg; - if (!lstat(name, &st)) + if (!lstat(name, &st)) { + free(to_free); return 1; /* file exists */ - if (errno == ENOENT || errno == ENOTDIR) + } + if (errno == ENOENT || errno == ENOTDIR) { + free(to_free); return 0; /* file does not exist */ + } die_errno("failed to stat '%s'", arg); } @@ -254,7 +259,7 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir) if (!is_absolute_path(data.buf)) strbuf_addf(&path, "%s/", gitdir); strbuf_addbuf(&path, &data); - strbuf_addstr(sb, real_path(path.buf)); + strbuf_add_real_path(sb, path.buf); ret = 1; } else { strbuf_addstr(sb, gitdir); @@ -531,6 +536,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) ssize_t len; if (stat(path, &st)) { + /* NEEDSWORK: discern between ENOENT vs other errors */ error_code = READ_GITFILE_ERR_STAT_FAILED; goto cleanup_return; } @@ -721,8 +727,10 @@ static const char *setup_discovered_git_dir(const char *gitdir, if (offset == cwd->len) return NULL; - /* Make "offset" point to past the '/', and add a '/' at the end */ - offset++; + /* Make "offset" point past the '/' (already the case for root dirs) */ + if (offset != offset_1st_component(cwd->buf)) + offset++; + /* Add a '/' at the end */ strbuf_addch(cwd, '/'); return cwd->buf + offset; } @@ -816,50 +824,51 @@ static int canonicalize_ceiling_entry(struct string_list_item *item, } } +enum discovery_result { + GIT_DIR_NONE = 0, + GIT_DIR_EXPLICIT, + GIT_DIR_DISCOVERED, + GIT_DIR_BARE, + /* these are errors */ + GIT_DIR_HIT_CEILING = -1, + GIT_DIR_HIT_MOUNT_POINT = -2, + GIT_DIR_INVALID_GITFILE = -3 +}; + /* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. + * + * Also, we avoid changing any global state (such as the current working + * directory) to allow early callers. + * + * The directory where the search should start needs to be passed in via the + * `dir` parameter; upon return, the `dir` buffer will contain the path of + * the directory where the search ended, and `gitdir` will contain the path of + * the discovered .git/ directory, if any. If `gitdir` is not absolute, it + * is relative to `dir` (i.e. *not* necessarily the cwd). */ -static const char *setup_git_directory_gently_1(int *nongit_ok) +static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, + struct strbuf *gitdir, + int die_on_error) { const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; - static struct strbuf cwd = STRBUF_INIT; - const char *gitdirenv, *ret; - char *gitfile; - int offset, offset_parent, ceil_offset = -1; + const char *gitdirenv; + int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1; dev_t current_device = 0; int one_filesystem = 1; /* - * We may have read an incomplete configuration before - * setting-up the git directory. If so, clear the cache so - * that the next queries to the configuration reload complete - * configuration (including the per-repo config file that we - * ignored previously). - */ - git_config_clear(); - - /* - * Let's assume that we are in a git repository. - * If it turns out later that we are somewhere else, the value will be - * updated accordingly. - */ - if (nongit_ok) - *nongit_ok = 0; - - if (strbuf_getcwd(&cwd)) - die_errno(_("Unable to read current working directory")); - offset = cwd.len; - - /* * If GIT_DIR is set explicitly, we're not going * to do any discovery, but we still do repository * validation. */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); - if (gitdirenv) - return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok); + if (gitdirenv) { + strbuf_addstr(gitdir, gitdirenv); + return GIT_DIR_EXPLICIT; + } if (env_ceiling_dirs) { int empty_entry_found = 0; @@ -867,15 +876,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1); filter_string_list(&ceiling_dirs, 0, canonicalize_ceiling_entry, &empty_entry_found); - ceil_offset = longest_ancestor_length(cwd.buf, &ceiling_dirs); + ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs); string_list_clear(&ceiling_dirs, 0); } - if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf)) - ceil_offset = 1; + if (ceil_offset < 0) + ceil_offset = min_offset - 2; /* - * Test in the following order (relative to the cwd): + * Test in the following order (relative to the dir): * - .git (file containing "gitdir: <path>") * - .git/ * - ./ (bare) @@ -887,61 +896,155 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); if (one_filesystem) - current_device = get_device_or_die(".", NULL, 0); + current_device = get_device_or_die(dir->buf, NULL, 0); for (;;) { - gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT); - if (gitfile) - gitdirenv = gitfile = xstrdup(gitfile); - else { - if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) - gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; + int offset = dir->len, error_code = 0; + + if (offset > min_offset) + strbuf_addch(dir, '/'); + strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT); + gitdirenv = read_gitfile_gently(dir->buf, die_on_error ? + NULL : &error_code); + if (!gitdirenv) { + if (die_on_error || + error_code == READ_GITFILE_ERR_NOT_A_FILE) { + /* NEEDSWORK: fail if .git is not file nor dir */ + if (is_git_directory(dir->buf)) + gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; + } else if (error_code != READ_GITFILE_ERR_STAT_FAILED) + return GIT_DIR_INVALID_GITFILE; } - + strbuf_setlen(dir, offset); if (gitdirenv) { - ret = setup_discovered_git_dir(gitdirenv, - &cwd, offset, - nongit_ok); - free(gitfile); - return ret; + strbuf_addstr(gitdir, gitdirenv); + return GIT_DIR_DISCOVERED; } - free(gitfile); - if (is_git_directory(".")) - return setup_bare_git_dir(&cwd, offset, nongit_ok); - - offset_parent = offset; - while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/'); - if (offset_parent <= ceil_offset) - return setup_nongit(cwd.buf, nongit_ok); - if (one_filesystem) { - dev_t parent_device = get_device_or_die("..", cwd.buf, - offset); - if (parent_device != current_device) { - if (nongit_ok) { - if (chdir(cwd.buf)) - die_errno(_("Cannot come back to cwd")); - *nongit_ok = 1; - return NULL; - } - strbuf_setlen(&cwd, offset); - die(_("Not a git repository (or any parent up to mount point %s)\n" - "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)."), - cwd.buf); - } - } - if (chdir("..")) { - strbuf_setlen(&cwd, offset); - die_errno(_("Cannot change to '%s/..'"), cwd.buf); + if (is_git_directory(dir->buf)) { + strbuf_addstr(gitdir, "."); + return GIT_DIR_BARE; } - offset = offset_parent; + + if (offset <= min_offset) + return GIT_DIR_HIT_CEILING; + + while (--offset > ceil_offset && !is_dir_sep(dir->buf[offset])) + ; /* continue */ + if (offset <= ceil_offset) + return GIT_DIR_HIT_CEILING; + + strbuf_setlen(dir, offset > min_offset ? offset : min_offset); + if (one_filesystem && + current_device != get_device_or_die(dir->buf, NULL, offset)) + return GIT_DIR_HIT_MOUNT_POINT; } } +const char *discover_git_directory(struct strbuf *gitdir) +{ + struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT; + size_t gitdir_offset = gitdir->len, cwd_len; + struct repository_format candidate; + + if (strbuf_getcwd(&dir)) + return NULL; + + cwd_len = dir.len; + if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) { + strbuf_release(&dir); + return NULL; + } + + /* + * The returned gitdir is relative to dir, and if dir does not reflect + * the current working directory, we simply make the gitdir absolute. + */ + if (dir.len < cwd_len && !is_absolute_path(gitdir->buf + gitdir_offset)) { + /* Avoid a trailing "/." */ + if (!strcmp(".", gitdir->buf + gitdir_offset)) + strbuf_setlen(gitdir, gitdir_offset); + else + strbuf_addch(&dir, '/'); + strbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len); + } + + strbuf_reset(&dir); + strbuf_addf(&dir, "%s/config", gitdir->buf + gitdir_offset); + read_repository_format(&candidate, dir.buf); + strbuf_release(&dir); + + if (verify_repository_format(&candidate, &err) < 0) { + warning("ignoring git dir '%s': %s", + gitdir->buf + gitdir_offset, err.buf); + strbuf_release(&err); + return NULL; + } + + return gitdir->buf + gitdir_offset; +} + 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; - prefix = setup_git_directory_gently_1(nongit_ok); + /* + * We may have read an incomplete configuration before + * setting-up the git directory. If so, clear the cache so + * that the next queries to the configuration reload complete + * configuration (including the per-repo config file that we + * ignored previously). + */ + git_config_clear(); + + /* + * Let's assume that we are in a git repository. + * If it turns out later that we are somewhere else, the value will be + * updated accordingly. + */ + if (nongit_ok) + *nongit_ok = 0; + + if (strbuf_getcwd(&cwd)) + die_errno(_("Unable to read current working directory")); + 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, nongit_ok); + break; + case GIT_DIR_DISCOVERED: + if (dir.len < cwd.len && chdir(dir.buf)) + die(_("Cannot change to '%s'"), dir.buf); + prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len, + nongit_ok); + break; + case GIT_DIR_BARE: + if (dir.len < cwd.len && chdir(dir.buf)) + die(_("Cannot change to '%s'"), dir.buf); + prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok); + break; + case GIT_DIR_HIT_CEILING: + prefix = setup_nongit(cwd.buf, nongit_ok); + 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); + default: + die("BUG: unhandled setup_git_directory_1() result"); + } + if (prefix) setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1); else @@ -950,6 +1053,9 @@ const char *setup_git_directory_gently(int *nongit_ok) startup_info->have_repository = !nongit_ok || !*nongit_ok; startup_info->prefix = prefix; + strbuf_release(&dir); + strbuf_release(&gitdir); + return prefix; } diff --git a/sha1_file.c b/sha1_file.c index 8ce80d4481..71063890ff 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -129,8 +129,10 @@ enum scld_error safe_create_leading_directories(char *path) *slash = '\0'; if (!stat(path, &st)) { /* path exists */ - if (!S_ISDIR(st.st_mode)) + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; ret = SCLD_EXISTS; + } } else if (mkdir(path, 0777)) { if (errno == EEXIST && !stat(path, &st) && S_ISDIR(st.st_mode)) @@ -158,13 +160,85 @@ enum scld_error safe_create_leading_directories(char *path) enum scld_error safe_create_leading_directories_const(const char *path) { + int save_errno; /* path points to cache entries, so xstrdup before messing with it */ char *buf = xstrdup(path); enum scld_error result = safe_create_leading_directories(buf); + + save_errno = errno; free(buf); + errno = save_errno; return result; } +int raceproof_create_file(const char *path, create_file_fn fn, void *cb) +{ + /* + * The number of times we will try to remove empty directories + * in the way of path. This is only 1 because if another + * process is racily creating directories that conflict with + * us, we don't want to fight against them. + */ + int remove_directories_remaining = 1; + + /* + * The number of times that we will try to create the + * directories containing path. We are willing to attempt this + * more than once, because another process could be trying to + * clean up empty directories at the same time as we are + * trying to create them. + */ + int create_directories_remaining = 3; + + /* A scratch copy of path, filled lazily if we need it: */ + struct strbuf path_copy = STRBUF_INIT; + + int ret, save_errno; + + /* Sanity check: */ + assert(*path); + +retry_fn: + ret = fn(path, cb); + save_errno = errno; + if (!ret) + goto out; + + if (errno == EISDIR && remove_directories_remaining-- > 0) { + /* + * A directory is in the way. Maybe it is empty; try + * to remove it: + */ + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) + goto retry_fn; + } else if (errno == ENOENT && create_directories_remaining-- > 0) { + /* + * Maybe the containing directory didn't exist, or + * maybe it was just deleted by a process that is + * racing with us to clean up empty directories. Try + * to create it: + */ + enum scld_error scld_result; + + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + do { + scld_result = safe_create_leading_directories(path_copy.buf); + if (scld_result == SCLD_OK) + goto retry_fn; + } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); + } + +out: + strbuf_release(&path_copy); + errno = save_errno; + return ret; +} + static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1) { int i; @@ -203,31 +277,26 @@ static const char *alt_sha1_path(struct alternate_object_database *alt, return buf->buf; } -/* - * Return the name of the pack or index file with the specified sha1 - * in its filename. *base and *name are scratch space that must be - * provided by the caller. which should be "pack" or "idx". - */ -static char *sha1_get_pack_name(const unsigned char *sha1, - struct strbuf *buf, - const char *which) + char *odb_pack_name(struct strbuf *buf, + const unsigned char *sha1, + const char *ext) { strbuf_reset(buf); strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(), - sha1_to_hex(sha1), which); + sha1_to_hex(sha1), ext); return buf->buf; } char *sha1_pack_name(const unsigned char *sha1) { static struct strbuf buf = STRBUF_INIT; - return sha1_get_pack_name(sha1, &buf, "pack"); + return odb_pack_name(&buf, sha1, "pack"); } char *sha1_pack_index_name(const unsigned char *sha1) { static struct strbuf buf = STRBUF_INIT; - return sha1_get_pack_name(sha1, &buf, "idx"); + return odb_pack_name(&buf, sha1, "idx"); } struct alternate_object_database *alt_odb_list; @@ -593,7 +662,7 @@ static int freshen_file(const char *fn) * either does not exist on disk, or has a stale mtime and may be subject to * pruning). */ -static int check_and_freshen_file(const char *fn, int freshen) +int check_and_freshen_file(const char *fn, int freshen) { if (access(fn, F_OK)) return 0; @@ -2632,6 +2701,17 @@ const unsigned char *nth_packed_object_sha1(struct packed_git *p, } } +const struct object_id *nth_packed_object_oid(struct object_id *oid, + struct packed_git *p, + uint32_t n) +{ + const unsigned char *hash = nth_packed_object_sha1(p, n); + if (!hash) + return NULL; + hashcpy(oid->hash, hash); + return oid; +} + void check_pack_index_ptr(const struct packed_git *p, const void *vptr) { const unsigned char *ptr = vptr; @@ -3678,15 +3758,15 @@ static int for_each_file_in_obj_subdir(int subdir_nr, strbuf_setlen(path, baselen); strbuf_addf(path, "/%s", de->d_name); - if (strlen(de->d_name) == 38) { - char hex[41]; - unsigned char sha1[20]; + if (strlen(de->d_name) == GIT_SHA1_HEXSZ - 2) { + char hex[GIT_SHA1_HEXSZ+1]; + struct object_id oid; snprintf(hex, sizeof(hex), "%02x%s", subdir_nr, de->d_name); - if (!get_sha1_hex(hex, sha1)) { + if (!get_oid_hex(hex, &oid)) { if (obj_cb) { - r = obj_cb(sha1, path->buf, data); + r = obj_cb(&oid, path->buf, data); if (r) break; } @@ -3792,13 +3872,13 @@ static int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn c int r = 0; for (i = 0; i < p->num_objects; i++) { - const unsigned char *sha1 = nth_packed_object_sha1(p, i); + struct object_id oid; - if (!sha1) + if (!nth_packed_object_oid(&oid, p, i)) return error("unable to get sha1 of object %u in %s", i, p->pack_name); - r = cb(sha1, p, i, data); + r = cb(&oid, p, i, data); if (r) break; } diff --git a/sha1_name.c b/sha1_name.c index 73a915ff1b..cda9e49b12 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1051,7 +1051,7 @@ struct grab_nth_branch_switch_cbdata { struct strbuf buf; }; -static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, +static int grab_nth_branch_switch(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -1176,7 +1176,8 @@ static int interpret_empty_at(const char *name, int namelen, int len, struct str return 1; } -static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf) +static int reinterpret(const char *name, int namelen, int len, + struct strbuf *buf, unsigned allowed) { /* we have extra data, which might need further processing */ struct strbuf tmp = STRBUF_INIT; @@ -1184,7 +1185,7 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu int ret; strbuf_add(buf, name + len, namelen - len); - ret = interpret_branch_name(buf->buf, buf->len, &tmp); + ret = interpret_branch_name(buf->buf, buf->len, &tmp, allowed); /* that data was not interpreted, remove our cruft */ if (ret < 0) { strbuf_setlen(buf, used); @@ -1205,11 +1206,27 @@ static void set_shortened_ref(struct strbuf *buf, const char *ref) free(s); } +static int branch_interpret_allowed(const char *refname, unsigned allowed) +{ + if (!allowed) + return 1; + + if ((allowed & INTERPRET_BRANCH_LOCAL) && + starts_with(refname, "refs/heads/")) + return 1; + if ((allowed & INTERPRET_BRANCH_REMOTE) && + starts_with(refname, "refs/remotes/")) + return 1; + + return 0; +} + static int interpret_branch_mark(const char *name, int namelen, int at, struct strbuf *buf, int (*get_mark)(const char *, int), const char *(*get_data)(struct branch *, - struct strbuf *)) + struct strbuf *), + unsigned allowed) { int len; struct branch *branch; @@ -1234,64 +1251,55 @@ static int interpret_branch_mark(const char *name, int namelen, if (!value) die("%s", err.buf); + if (!branch_interpret_allowed(value, allowed)) + return -1; + set_shortened_ref(buf, value); return len + at; } -/* - * This reads short-hand syntax that not only evaluates to a commit - * object name, but also can act as if the end user spelled the name - * of the branch from the command line. - * - * - "@{-N}" finds the name of the Nth previous branch we were on, and - * places the name of the branch in the given buf and returns the - * number of characters parsed if successful. - * - * - "<branch>@{upstream}" finds the name of the other ref that - * <branch> is configured to merge with (missing <branch> defaults - * to the current branch), and places the name of the branch in the - * given buf and returns the number of characters parsed if - * successful. - * - * If the input is not of the accepted format, it returns a negative - * number to signal an error. - * - * If the input was ok but there are not N branch switches in the - * reflog, it returns 0. - */ -int interpret_branch_name(const char *name, int namelen, struct strbuf *buf) +int interpret_branch_name(const char *name, int namelen, struct strbuf *buf, + unsigned allowed) { char *at; const char *start; - int len = interpret_nth_prior_checkout(name, namelen, buf); + int len; if (!namelen) namelen = strlen(name); - if (!len) { - return len; /* syntax Ok, not enough switches */ - } else if (len > 0) { - if (len == namelen) - return len; /* consumed all */ - else - return reinterpret(name, namelen, len, buf); + if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) { + len = interpret_nth_prior_checkout(name, namelen, buf); + if (!len) { + return len; /* syntax Ok, not enough switches */ + } else if (len > 0) { + if (len == namelen) + return len; /* consumed all */ + else + return reinterpret(name, namelen, len, buf, allowed); + } } for (start = name; (at = memchr(start, '@', namelen - (start - name))); start = at + 1) { - len = interpret_empty_at(name, namelen, at - name, buf); - if (len > 0) - return reinterpret(name, namelen, len, buf); + if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) { + len = interpret_empty_at(name, namelen, at - name, buf); + if (len > 0) + return reinterpret(name, namelen, len, buf, + allowed); + } len = interpret_branch_mark(name, namelen, at - name, buf, - upstream_mark, branch_get_upstream); + upstream_mark, branch_get_upstream, + allowed); if (len > 0) return len; len = interpret_branch_mark(name, namelen, at - name, buf, - push_mark, branch_get_push); + push_mark, branch_get_push, + allowed); if (len > 0) return len; } @@ -1299,22 +1307,19 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf) return -1; } -int strbuf_branchname(struct strbuf *sb, const char *name) +void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed) { int len = strlen(name); - int used = interpret_branch_name(name, len, sb); + int used = interpret_branch_name(name, len, sb, allowed); - if (used == len) - return 0; if (used < 0) used = 0; strbuf_add(sb, name + used, len - used); - return len; } int strbuf_check_branch_ref(struct strbuf *sb, const char *name) { - strbuf_branchname(sb, name); + strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL); if (name[0] == '-') return -1; strbuf_splice(sb, 0, 0, "refs/heads/", 11); diff --git a/sha1dc/LICENSE.txt b/sha1dc/LICENSE.txt new file mode 100644 index 0000000000..4a3e6a1b15 --- /dev/null +++ b/sha1dc/LICENSE.txt @@ -0,0 +1,30 @@ +MIT License + +Copyright (c) 2017: + Marc Stevens + Cryptology Group + Centrum Wiskunde & Informatica + P.O. Box 94079, 1090 GB Amsterdam, Netherlands + marc@marc-stevens.nl + + Dan Shumow + Microsoft Research + danshu@microsoft.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sha1dc/sha1.c b/sha1dc/sha1.c new file mode 100644 index 0000000000..6dd0da3608 --- /dev/null +++ b/sha1dc/sha1.c @@ -0,0 +1,1808 @@ +/*** +* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com) +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#include "cache.h" +#include "sha1dc/sha1.h" +#include "sha1dc/ubc_check.h" + + +/* + Because Little-Endian architectures are most common, + we only set BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of these, + you will have to add whatever macros your tool chain defines to indicate Big-Endianness. + */ +#if (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) || \ + defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) + +#define BIGENDIAN (1) + +#endif /*ENDIANNESS SELECTION*/ + +#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) +#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) + +#define sha1_bswap32(x) \ + {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#if defined(BIGENDIAN) + #define sha1_load(m, t, temp) { temp = m[t]; } +#else + #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } +#endif /*define(BIGENDIAN)*/ + +#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x + +#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) +#define sha1_f2(b,c,d) ((b)^(c)^(d)) +#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) +#define sha1_f4(b,c,d) ((b)^(c)^(d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } + + +#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a,b,c,d,e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + + +static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + +void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + + + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + + + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + + + + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + + +#define SHA1_RECOMPRESS(t) \ +static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ +{ \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ + a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ + if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ +} + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) +{ + switch (step) + { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } + +} + + + +static void sha1_process(SHA1_CTX* ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) + { + if (ctx->ubc_check) + { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) + { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) + { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) + { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) + || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) + { + ctx->found_collision = 1; + + if (ctx->safe_hash) + { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void SHA1DCInit(SHA1_CTX* ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = 0; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void SHA1DCSetSafeHash(SHA1_CTX* ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + + +void SHA1DCSetUseUBC(SHA1_CTX* ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void SHA1DCSetUseDetectColl(SHA1_CTX* ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX* ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void SHA1DCSetCallback(SHA1_CTX* ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len) +{ + unsigned left, fill; + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) + { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) + { + ctx->total += 64; + sha1_process(ctx, (uint32_t*)(buf)); + buf += 64; + len -= 64; + } + if (len > 0) + { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char)(total >> 56); + ctx->buffer[57] = (unsigned char)(total >> 48); + ctx->buffer[58] = (unsigned char)(total >> 40); + ctx->buffer[59] = (unsigned char)(total >> 32); + ctx->buffer[60] = (unsigned char)(total >> 24); + ctx->buffer[61] = (unsigned char)(total >> 16); + ctx->buffer[62] = (unsigned char)(total >> 8); + ctx->buffer[63] = (unsigned char)(total); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + output[0] = (unsigned char)(ctx->ihv[0] >> 24); + output[1] = (unsigned char)(ctx->ihv[0] >> 16); + output[2] = (unsigned char)(ctx->ihv[0] >> 8); + output[3] = (unsigned char)(ctx->ihv[0]); + output[4] = (unsigned char)(ctx->ihv[1] >> 24); + output[5] = (unsigned char)(ctx->ihv[1] >> 16); + output[6] = (unsigned char)(ctx->ihv[1] >> 8); + output[7] = (unsigned char)(ctx->ihv[1]); + output[8] = (unsigned char)(ctx->ihv[2] >> 24); + output[9] = (unsigned char)(ctx->ihv[2] >> 16); + output[10] = (unsigned char)(ctx->ihv[2] >> 8); + output[11] = (unsigned char)(ctx->ihv[2]); + output[12] = (unsigned char)(ctx->ihv[3] >> 24); + output[13] = (unsigned char)(ctx->ihv[3] >> 16); + output[14] = (unsigned char)(ctx->ihv[3] >> 8); + output[15] = (unsigned char)(ctx->ihv[3]); + output[16] = (unsigned char)(ctx->ihv[4] >> 24); + output[17] = (unsigned char)(ctx->ihv[4] >> 16); + output[18] = (unsigned char)(ctx->ihv[4] >> 8); + output[19] = (unsigned char)(ctx->ihv[4]); + return ctx->found_collision; +} + +void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) +{ + if (!SHA1DCFinal(hash, ctx)) + return; + die("SHA-1 appears to be part of a collision attack: %s", + sha1_to_hex(hash)); +} + +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) +{ + const char *data = vdata; + /* We expect an unsigned long, but sha1dc only takes an int */ + while (len > INT_MAX) { + SHA1DCUpdate(ctx, data, INT_MAX); + data += INT_MAX; + len -= INT_MAX; + } + SHA1DCUpdate(ctx, data, len); +} diff --git a/sha1dc/sha1.h b/sha1dc/sha1.h new file mode 100644 index 0000000000..bd8bd928fb --- /dev/null +++ b/sha1dc/sha1.h @@ -0,0 +1,122 @@ +/*** +* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/* uses SHA-1 message expansion to expand the first 16 words of W[] to 80 words */ +/* void sha1_message_expansion(uint32_t W[80]); */ + +/* sha-1 compression function; first version takes a message block pre-parsed as 16 32-bit integers, second version takes an already expanded message) */ +/* void sha1_compression(uint32_t ihv[5], const uint32_t m[16]); +void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]); */ + +/* same as sha1_compression_W, but additionally store intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) +// where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block +// the function will return: +// ihvin: the reconstructed input chaining value +// ihvout: the reconstructed output chaining value +*/ +typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); + +/* table of sha1_recompression_step_0, ... , sha1_recompression_step_79 */ +/* extern sha1_recompression_type sha1_recompression_step[80];*/ + +/* a callback function type that can be set to be called when a collision block has been found: */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); + +/* the SHA-1 context */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* initialize SHA-1 context */ +void SHA1DCInit(SHA1_CTX*); + +/* +// function to enable safe SHA-1 hashing: +// collision attacks are thwarted by hashing a detected near-collision block 3 times +// think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: +// the best collision attacks against SHA-1 have complexity about 2^60, +// thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would 2^180 +// an attacker would be better off using a generic birthday search of complexity 2^80 +// +// enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected +// but it will result in a different SHA-1 hash for messages where a collision attack was detected +// this will automatically invalidate SHA-1 based digital signature forgeries +// enabled by default +*/ +void SHA1DCSetSafeHash(SHA1_CTX*, int); + +/* function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up) */ +/* enabled by default */ +void SHA1DCSetUseUBC(SHA1_CTX*, int); + +/* function to disable or enable the use of Collision Detection */ +/* enabled by default */ +void SHA1DCSetUseDetectColl(SHA1_CTX*, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); + +/* + * Same as SHA1DCFinal, but convert collision attack case into a verbose die(). + */ +void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); + +/* + * Same as SHA1DCUpdate, but adjust types to match git's usual interface. + */ +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); + +#define platform_SHA_CTX SHA1_CTX +#define platform_SHA1_Init SHA1DCInit +#define platform_SHA1_Update git_SHA1DCUpdate +#define platform_SHA1_Final git_SHA1DCFinal + +#if defined(__cplusplus) +} +#endif + +#endif /* SHA1DC_SHA1_H */ diff --git a/sha1dc/ubc_check.c b/sha1dc/ubc_check.c new file mode 100644 index 0000000000..089dd4743d --- /dev/null +++ b/sha1dc/ubc_check.c @@ -0,0 +1,363 @@ +/*** +* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section +*/ + +#include "git-compat-util.h" +#include "sha1dc/ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = +{ + {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } +, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } +, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } +, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } +, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } +, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } +, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } +, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } +, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } +, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } +, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } +, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } +, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } +, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } +, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } +, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } +, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } +, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } +, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } +, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } +, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } +, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } +, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } +, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } +, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } +, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } +, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } +, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } +, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } +, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } +, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } +, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } +, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} +}; +void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); + mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); + mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); + mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); + mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); + mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); + mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); + mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); + mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); + mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); + mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); + mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); + mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); + mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); + mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); + mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); + mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) + mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) + mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) + mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) + mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) + mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) + mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) + mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) + mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) + mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) + mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) + mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); + mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) + mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); + mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) + mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); + mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); + mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) + mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) + mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) + mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); + mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) + mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) + mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) + mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) + mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) + mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) + mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) + mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); + mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); + mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); + mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); + mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); + mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); + mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); + mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); + mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); + mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); + mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) + mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) + mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) + mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) + mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) + mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); +if (mask) { + + if (mask & DV_I_43_0_bit) + if ( + !((W[61]^(W[62]>>5)) & (1<<1)) + || !(!((W[59]^(W[63]>>25)) & (1<<5))) + || !((W[58]^(W[63]>>30)) & (1<<0)) + ) mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<1)) + || !(!((W[60]^(W[64]>>25)) & (1<<5))) + || !((W[59]^(W[64]>>30)) & (1<<0)) + ) mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<2)) + || !(!((W[41]^W[43]) & (1<<6))) + ) mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if ( + !((W[63]^(W[64]>>5)) & (1<<2)) + || !(!((W[48]^(W[49]<<5)) & (1<<6))) + ) mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if ( + !(!((W[49]^(W[50]<<5)) & (1<<6))) + || !((W[42]^W[50]) & (1<<1)) + || !(!((W[39]^(W[40]<<5)) & (1<<6))) + || !((W[38]^W[40]) & (1<<1)) + ) mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if ( + !(!((W[51]^(W[52]<<5)) & (1<<6))) + || !(!((W[49]^W[51]) & (1<<6))) + || !(!((W[37]^(W[37]>>5)) & (1<<1))) + || !(!((W[35]^(W[39]>>25)) & (1<<5))) + ) mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if ( + !(!((W[36]^(W[40]>>25)) & (1<<3))) + || !((W[35]^(W[40]<<2)) & (1<<30)) + ) mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if ( + !(!((W[37]^(W[41]>>25)) & (1<<3))) + || !((W[36]^(W[41]<<2)) & (1<<30)) + ) mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if ( + !(!((W[53]^(W[54]<<5)) & (1<<6))) + || !(!((W[51]^W[53]) & (1<<6))) + || !((W[50]^W[54]) & (1<<1)) + || !(!((W[45]^(W[46]<<5)) & (1<<6))) + || !(!((W[37]^(W[41]>>25)) & (1<<5))) + || !((W[36]^(W[41]>>30)) & (1<<0)) + ) mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if ( + !((W[55]^W[58]) & (1<<29)) + || !(!((W[38]^(W[42]>>25)) & (1<<3))) + || !((W[37]^(W[42]<<2)) & (1<<30)) + ) mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if ( + !(!((W[54]^(W[55]<<5)) & (1<<6))) + || !(!((W[52]^W[54]) & (1<<6))) + || !((W[51]^W[55]) & (1<<1)) + || !((W[45]^W[47]) & (1<<1)) + || !(!((W[38]^(W[42]>>25)) & (1<<5))) + || !((W[37]^(W[42]>>30)) & (1<<0)) + ) mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if ( + !(!((W[39]^(W[43]>>25)) & (1<<3))) + || !((W[38]^(W[43]<<2)) & (1<<30)) + ) mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if ( + !(!((W[55]^(W[56]<<5)) & (1<<6))) + || !(!((W[53]^W[55]) & (1<<6))) + || !((W[52]^W[56]) & (1<<1)) + || !((W[46]^W[48]) & (1<<1)) + || !(!((W[39]^(W[43]>>25)) & (1<<5))) + || !((W[38]^(W[43]>>30)) & (1<<0)) + ) mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if ( + !(!((W[59]^W[60]) & (1<<29))) + || !(!((W[40]^(W[44]>>25)) & (1<<3))) + || !(!((W[40]^(W[44]>>25)) & (1<<4))) + || !((W[39]^(W[44]<<2)) & (1<<30)) + ) mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if ( + !((W[58]^W[61]) & (1<<29)) + || !(!((W[57]^(W[61]>>25)) & (1<<4))) + || !(!((W[41]^(W[45]>>25)) & (1<<3))) + || !(!((W[41]^(W[45]>>25)) & (1<<4))) + ) mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if ( + !(!((W[58]^(W[62]>>25)) & (1<<4))) + || !(!((W[42]^(W[46]>>25)) & (1<<3))) + || !(!((W[42]^(W[46]>>25)) & (1<<4))) + ) mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if ( + !(!((W[59]^(W[63]>>25)) & (1<<4))) + || !(!((W[57]^(W[59]>>25)) & (1<<4))) + || !(!((W[43]^(W[47]>>25)) & (1<<3))) + || !(!((W[43]^(W[47]>>25)) & (1<<4))) + ) mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if ( + !(!((W[60]^(W[64]>>25)) & (1<<4))) + || !(!((W[44]^(W[48]>>25)) & (1<<3))) + || !(!((W[44]^(W[48]>>25)) & (1<<4))) + ) mask &= ~DV_II_56_0_bit; +} + + dvmask[0]=mask; +} diff --git a/sha1dc/ubc_check.h b/sha1dc/ubc_check.h new file mode 100644 index 0000000000..b64c306d77 --- /dev/null +++ b/sha1dc/ubc_check.h @@ -0,0 +1,44 @@ +/*** +* Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com> +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef UBC_CHECK_H +#define UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#define DVMASKSIZE 1 +typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#endif /* UBC_CHECK_H */ diff --git a/split-index.c b/split-index.c index 615f4cac05..f519e60f87 100644 --- a/split-index.c +++ b/split-index.c @@ -317,3 +317,25 @@ void replace_index_entry_in_base(struct index_state *istate, istate->split_index->base->cache[new->index - 1] = new; } } + +void add_split_index(struct index_state *istate) +{ + if (!istate->split_index) { + init_split_index(istate); + istate->cache_changed |= SPLIT_INDEX_ORDERED; + } +} + +void remove_split_index(struct index_state *istate) +{ + if (istate->split_index) { + /* + * can't discard_split_index(&the_index); because that + * will destroy split_index->base->cache[], which may + * be shared with the_index.cache[]. So yeah we're + * leaking a bit here. + */ + istate->split_index = NULL; + istate->cache_changed |= SOMETHING_CHANGED; + } +} diff --git a/split-index.h b/split-index.h index c1324f521a..df91c1bda8 100644 --- a/split-index.h +++ b/split-index.h @@ -31,5 +31,7 @@ void merge_base_index(struct index_state *istate); void prepare_to_write_split_index(struct index_state *istate); void finish_writing_split_index(struct index_state *istate); void discard_split_index(struct index_state *istate); +void add_split_index(struct index_state *istate); +void remove_split_index(struct index_state *istate); #endif @@ -707,6 +707,17 @@ void strbuf_add_absolute_path(struct strbuf *sb, const char *path) strbuf_addstr(sb, path); } +void strbuf_add_real_path(struct strbuf *sb, const char *path) +{ + if (sb->len) { + struct strbuf resolved = STRBUF_INIT; + strbuf_realpath(&resolved, path, 1); + strbuf_addbuf(sb, &resolved); + strbuf_release(&resolved); + } else + strbuf_realpath(sb, path, 1); +} + int printf_ln(const char *fmt, ...) { int ret; @@ -441,6 +441,20 @@ extern int strbuf_getcwd(struct strbuf *sb); */ extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path); +/** + * Canonize `path` (make it absolute, resolve symlinks, remove extra + * slashes) and append it to `sb`. Die with an informative error + * message if there is a problem. + * + * The directory part of `path` (i.e., everything up to the last + * dir_sep) must denote a valid, existing directory, but the last + * component need not exist. + * + * Callers that don't mind links should use the more lightweight + * strbuf_add_absolute_path() instead. + */ +extern void strbuf_add_real_path(struct strbuf *sb, const char *path); + /** * Normalize in-place the path contained in the strbuf. See @@ -560,7 +574,26 @@ static inline void strbuf_complete_line(struct strbuf *sb) strbuf_complete(sb, '\n'); } -extern int strbuf_branchname(struct strbuf *sb, const char *name); +/* + * Copy "name" to "sb", expanding any special @-marks as handled by + * interpret_branch_name(). The result is a non-qualified branch name + * (so "foo" or "origin/master" instead of "refs/heads/foo" or + * "refs/remotes/origin/master"). + * + * Note that the resulting name may not be a syntactically valid refname. + * + * If "allowed" is non-zero, restrict the set of allowed expansions. See + * interpret_branch_name() for details. + */ +extern void strbuf_branchname(struct strbuf *sb, const char *name, + unsigned allowed); + +/* + * Like strbuf_branchname() above, but confirm that the result is + * syntactically valid to be used as a local branch name in refs/heads/. + * + * The return value is "0" if the result is valid, and "-1" otherwise. + */ extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name); extern void strbuf_addstr_urlencode(struct strbuf *, const char *, diff --git a/submodule-config.c b/submodule-config.c index 93453909cf..4f58491ddb 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -234,6 +234,26 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) return parse_fetch_recurse(opt, arg, 1); } +static int parse_update_recurse(const char *opt, const char *arg, + int die_on_error) +{ + switch (git_config_maybe_bool(opt, arg)) { + case 1: + return RECURSE_SUBMODULES_ON; + case 0: + return RECURSE_SUBMODULES_OFF; + default: + if (die_on_error) + die("bad %s argument: %s", opt, arg); + return RECURSE_SUBMODULES_ERROR; + } +} + +int parse_update_recurse_submodules_arg(const char *opt, const char *arg) +{ + return parse_update_recurse(opt, arg, 1); +} + static int parse_push_recurse(const char *opt, const char *arg, int die_on_error) { @@ -333,7 +353,7 @@ static int parse_config(const char *var, const char *value, void *data) strcmp(value, "all") && strcmp(value, "none")) warning("Invalid parameter '%s' for config option " - "'submodule.%s.ignore'", value, var); + "'submodule.%s.ignore'", value, name.buf); else { free((void *) submodule->ignore); submodule->ignore = xstrdup(value); diff --git a/submodule-config.h b/submodule-config.h index 70f19363fd..d434ecdb45 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -22,16 +22,17 @@ struct submodule { int recommend_shallow; }; -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); -int parse_push_recurse_submodules_arg(const char *opt, const char *arg); -int parse_submodule_config_option(const char *var, const char *value); -const struct submodule *submodule_from_name(const unsigned char *commit_or_tree, - const char *name); -const struct submodule *submodule_from_path(const unsigned char *commit_or_tree, - const char *path); +extern int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); +extern int parse_update_recurse_submodules_arg(const char *opt, const char *arg); +extern int parse_push_recurse_submodules_arg(const char *opt, const char *arg); +extern int parse_submodule_config_option(const char *var, const char *value); +extern const struct submodule *submodule_from_name( + const unsigned char *commit_or_tree, const char *name); +extern const struct submodule *submodule_from_path( + const unsigned char *commit_or_tree, const char *path); extern int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, unsigned char *gitmodules_sha1, struct strbuf *rev); -void submodule_free(void); +extern void submodule_free(void); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index 0a2831d846..040f4c2287 100644 --- a/submodule.c +++ b/submodule.c @@ -17,6 +17,7 @@ #include "worktree.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; +static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int parallel_jobs = 1; static struct string_list changed_submodule_paths = STRING_LIST_INIT_NODUP; static int initialized_fetch_ref_tips; @@ -234,15 +235,12 @@ int is_submodule_initialized(const char *path) return ret; } -/* - * Determine if a submodule has been populated at a given 'path' - */ -int is_submodule_populated(const char *path) +int is_submodule_populated_gently(const char *path, int *return_error_code) { int ret = 0; char *gitdir = xstrfmt("%s/.git", path); - if (resolve_gitdir(gitdir)) + if (resolve_gitdir_gently(gitdir, return_error_code)) ret = 1; free(gitdir); @@ -358,6 +356,23 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } +static void prepare_submodule_repo_env_no_git_dir(struct argv_array *out) +{ + const char * const *var; + + for (var = local_repo_env; *var; var++) { + if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) + argv_array_push(out, *var); + } +} + +void prepare_submodule_repo_env(struct argv_array *out) +{ + prepare_submodule_repo_env_no_git_dir(out); + argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT, + DEFAULT_GIT_DIR_ENVIRONMENT); +} + /* Helper function to display the submodule header line prior to the full * summary output. If it can locate the submodule objects directory it will * attempt to lookup both the left and right commits and put them into the @@ -545,6 +560,27 @@ void set_config_fetch_recurse_submodules(int value) config_fetch_recurse_submodules = value; } +void set_config_update_recurse_submodules(int value) +{ + config_update_recurse_submodules = value; +} + +int should_update_submodules(void) +{ + return config_update_recurse_submodules == RECURSE_SUBMODULES_ON; +} + +const struct submodule *submodule_from_ce(const struct cache_entry *ce) +{ + if (!S_ISGITLINK(ce->ce_mode)) + return NULL; + + if (!should_update_submodules()) + return NULL; + + return submodule_from_path(null_sha1, ce->name); +} + static int has_remote(const char *refname, const struct object_id *oid, int flags, void *cb_data) { @@ -1203,6 +1239,151 @@ out: return ret; } +static const char *get_super_prefix_or_empty(void) +{ + const char *s = get_super_prefix(); + if (!s) + s = ""; + return s; +} + +static int submodule_has_dirty_index(const struct submodule *sub) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + prepare_submodule_repo_env_no_git_dir(&cp.env_array); + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "diff-index", "--quiet", + "--cached", "HEAD", NULL); + cp.no_stdin = 1; + cp.no_stdout = 1; + cp.dir = sub->path; + if (start_command(&cp)) + die("could not recurse into submodule '%s'", sub->path); + + return finish_command(&cp); +} + +static void submodule_reset_index(const char *path) +{ + struct child_process cp = CHILD_PROCESS_INIT; + prepare_submodule_repo_env_no_git_dir(&cp.env_array); + + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; + + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + get_super_prefix_or_empty(), path); + argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL); + + argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX); + + if (run_command(&cp)) + die("could not reset submodule index"); +} + +/** + * Moves a submodule at a given path from a given head to another new head. + * For edge cases (a submodule coming into existence or removing a submodule) + * pass NULL for old or new respectively. + */ +int submodule_move_head(const char *path, + const char *old, + const char *new, + unsigned flags) +{ + int ret = 0; + struct child_process cp = CHILD_PROCESS_INIT; + const struct submodule *sub; + + sub = submodule_from_path(null_sha1, path); + + if (!sub) + die("BUG: could not get submodule information for '%s'", path); + + if (old && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) { + /* Check if the submodule has a dirty index. */ + if (submodule_has_dirty_index(sub)) + return error(_("submodule '%s' has dirty index"), path); + } + + if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) { + if (old) { + if (!submodule_uses_gitfile(path)) + absorb_git_dir_into_superproject("", path, + ABSORB_GITDIR_RECURSE_SUBMODULES); + } else { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s/modules/%s", + get_git_common_dir(), sub->name); + connect_work_tree_and_git_dir(path, sb.buf); + strbuf_release(&sb); + + /* make sure the index is clean as well */ + submodule_reset_index(path); + } + } + + prepare_submodule_repo_env_no_git_dir(&cp.env_array); + + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; + + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + get_super_prefix_or_empty(), path); + argv_array_pushl(&cp.args, "read-tree", NULL); + + if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN) + argv_array_push(&cp.args, "-n"); + else + argv_array_push(&cp.args, "-u"); + + if (flags & SUBMODULE_MOVE_HEAD_FORCE) + argv_array_push(&cp.args, "--reset"); + else + argv_array_push(&cp.args, "-m"); + + argv_array_push(&cp.args, old ? old : EMPTY_TREE_SHA1_HEX); + argv_array_push(&cp.args, new ? new : EMPTY_TREE_SHA1_HEX); + + if (run_command(&cp)) { + ret = -1; + goto out; + } + + if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) { + if (new) { + struct child_process cp1 = CHILD_PROCESS_INIT; + /* also set the HEAD accordingly */ + cp1.git_cmd = 1; + cp1.no_stdin = 1; + cp1.dir = path; + + argv_array_pushl(&cp1.args, "update-ref", "HEAD", + new ? new : EMPTY_TREE_SHA1_HEX, NULL); + + if (run_command(&cp1)) { + ret = -1; + goto out; + } + } else { + struct strbuf sb = STRBUF_INIT; + + strbuf_addf(&sb, "%s/.git", path); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + + if (is_empty_dir(path)) + rmdir_or_warn(path); + } + } +out: + return ret; +} + static int find_first_merges(struct object_array *result, const char *path, struct commit *a, struct commit *b) { @@ -1371,18 +1552,6 @@ int parallel_submodules(void) return parallel_jobs; } -void prepare_submodule_repo_env(struct argv_array *out) -{ - const char * const *var; - - for (var = local_repo_env; *var; var++) { - if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) - argv_array_push(out, *var); - } - argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT, - DEFAULT_GIT_DIR_ENVIRONMENT); -} - /* * Embeds a single submodules git directory into the superprojects git dir, * non recursively. @@ -1414,11 +1583,8 @@ static void relocate_single_git_dir_into_superproject(const char *prefix, die(_("could not create directory '%s'"), new_git_dir); real_new_git_dir = real_pathdup(new_git_dir, 1); - if (!prefix) - prefix = get_super_prefix(); - fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"), - prefix ? prefix : "", path, + get_super_prefix_or_empty(), path, real_old_git_dir, real_new_git_dir); relocate_gitdir(path, real_old_git_dir, real_new_git_dir); @@ -1445,8 +1611,6 @@ void absorb_git_dir_into_superproject(const char *prefix, /* Not populated? */ if (!sub_git_dir) { - char *real_new_git_dir; - const char *new_git_dir; const struct submodule *sub; if (err_code == READ_GITFILE_ERR_STAT_FAILED) { @@ -1469,13 +1633,8 @@ void absorb_git_dir_into_superproject(const char *prefix, sub = submodule_from_path(null_sha1, path); if (!sub) die(_("could not lookup name for submodule '%s'"), path); - new_git_dir = git_path("modules/%s", sub->name); - if (safe_create_leading_directories_const(new_git_dir) < 0) - die(_("could not create directory '%s'"), new_git_dir); - real_new_git_dir = real_pathdup(new_git_dir, 1); - connect_work_tree_and_git_dir(path, real_new_git_dir); - - free(real_new_git_dir); + connect_work_tree_and_git_dir(path, + git_path("modules/%s", sub->name)); } else { /* Is it already absorbed into the superprojects git dir? */ char *real_sub_git_dir = real_pathdup(sub_git_dir, 1); @@ -1496,8 +1655,7 @@ void absorb_git_dir_into_superproject(const char *prefix, if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES) die("BUG: we don't know how to pass the flags down?"); - if (get_super_prefix()) - strbuf_addstr(&sb, get_super_prefix()); + strbuf_addstr(&sb, get_super_prefix_or_empty()); strbuf_addstr(&sb, path); strbuf_addch(&sb, '/'); @@ -1514,3 +1672,85 @@ void absorb_git_dir_into_superproject(const char *prefix, strbuf_release(&sb); } } + +const char *get_superproject_working_tree(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + const char *one_up = real_path_if_valid("../"); + const char *cwd = xgetcwd(); + const char *ret = NULL; + const char *subpath; + int code; + ssize_t len; + + if (!is_inside_work_tree()) + /* + * FIXME: + * We might have a superproject, but it is harder + * to determine. + */ + return NULL; + + if (!one_up) + return NULL; + + subpath = relative_path(cwd, one_up, &sb); + + prepare_submodule_repo_env(&cp.env_array); + argv_array_pop(&cp.env_array); + + argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..", + "ls-files", "-z", "--stage", "--full-name", "--", + subpath, NULL); + strbuf_reset(&sb); + + cp.no_stdin = 1; + cp.no_stderr = 1; + cp.out = -1; + cp.git_cmd = 1; + + if (start_command(&cp)) + die(_("could not start ls-files in ..")); + + len = strbuf_read(&sb, cp.out, PATH_MAX); + close(cp.out); + + if (starts_with(sb.buf, "160000")) { + int super_sub_len; + int cwd_len = strlen(cwd); + char *super_sub, *super_wt; + + /* + * There is a superproject having this repo as a submodule. + * The format is <mode> SP <hash> SP <stage> TAB <full name> \0, + * We're only interested in the name after the tab. + */ + super_sub = strchr(sb.buf, '\t') + 1; + super_sub_len = sb.buf + sb.len - super_sub - 1; + + if (super_sub_len > cwd_len || + strcmp(&cwd[cwd_len - super_sub_len], super_sub)) + die (_("BUG: returned path string doesn't match cwd?")); + + super_wt = xstrdup(cwd); + super_wt[cwd_len - super_sub_len] = '\0'; + + ret = real_path(super_wt); + free(super_wt); + } + strbuf_release(&sb); + + code = finish_command(&cp); + + if (code == 128) + /* '../' is not a git repository */ + return NULL; + if (code == 0 && len == 0) + /* There is an unrelated git repository at '../' */ + return NULL; + if (code) + die(_("ls-tree returned unexpected return code %d"), code); + + return ret; +} diff --git a/submodule.h b/submodule.h index 05ab674f06..8a8bc49dc9 100644 --- a/submodule.h +++ b/submodule.h @@ -41,7 +41,13 @@ extern int submodule_config(const char *var, const char *value, void *cb); extern void gitmodules_config(void); extern void gitmodules_config_sha1(const unsigned char *commit_sha1); extern int is_submodule_initialized(const char *path); -extern int is_submodule_populated(const char *path); +/* + * Determine if a submodule has been populated at a given 'path' by checking if + * the <path>/.git resolves to a valid git repository. + * If return_error_code is NULL, die on error. + * Otherwise the return error code is the same as of resolve_gitdir_gently. + */ +extern int is_submodule_populated_gently(const char *path, int *return_error_code); extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); @@ -58,6 +64,14 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, const char *del, const char *add, const char *reset, const struct diff_options *opt); extern void set_config_fetch_recurse_submodules(int value); +extern void set_config_update_recurse_submodules(int value); +/* Check if we want to update any submodule.*/ +extern int should_update_submodules(void); +/* + * Returns the submodule struct if the given ce entry is a submodule + * and it should be updated. Returns NULL otherwise. + */ +extern const struct submodule *submodule_from_ce(const struct cache_entry *ce); extern void check_for_new_submodule_commits(unsigned char new_sha1[20]); extern int fetch_populated_submodules(const struct argv_array *options, const char *prefix, int command_line_option, @@ -82,6 +96,13 @@ extern int push_unpushed_submodules(struct sha1_array *commits, extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); extern int parallel_submodules(void); +#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0) +#define SUBMODULE_MOVE_HEAD_FORCE (1<<1) +extern int submodule_move_head(const char *path, + const char *old, + const char *new, + unsigned flags); + /* * Prepare the "env_array" parameter of a "struct child_process" for executing * a submodule by clearing any repo-specific envirionment variables, but @@ -93,4 +114,12 @@ extern void prepare_submodule_repo_env(struct argv_array *out); extern void absorb_git_dir_into_superproject(const char *prefix, const char *path, unsigned flags); + +/* + * Return the absolute path of the working tree of the superproject, which this + * project is a submodule of. If this repository is not a submodule of + * another repository, return NULL. + */ +extern const char *get_superproject_working_tree(void); + #endif diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index d5dab5a94f..006d2a8152 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -110,7 +110,12 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || { } perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || { - skip_all='skipping gitweb tests, CGI module unusable' + skip_all='skipping gitweb tests, CGI & CGI::Util & CGI::Carp modules not available' + test_done +} + +perl -mTime::HiRes -e 0 >/dev/null 2>&1 || { + skip_all='skipping gitweb tests, Time::HiRes module not available' test_done } diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 83a4f2ab86..8e3ed6a76c 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -66,6 +66,16 @@ static int iterate_cb(const char *var, const char *value, void *data) return 0; } +static int early_config_cb(const char *var, const char *value, void *vdata) +{ + const char *key = vdata; + + if (!strcmp(key, var)) + printf("%s\n", value); + + return 0; +} + int cmd_main(int argc, const char **argv) { int i, val; @@ -73,6 +83,11 @@ int cmd_main(int argc, const char **argv) const struct string_list *strptr; struct config_set cs; + if (argc == 3 && !strcmp(argv[1], "read_early_config")) { + read_early_config(early_config_cb, (void *)argv[2]); + return 0; + } + setup_git_directory(); git_configset_init(&cs); diff --git a/t/interop/.gitignore b/t/interop/.gitignore new file mode 100644 index 0000000000..49c78d3dba --- /dev/null +++ b/t/interop/.gitignore @@ -0,0 +1,4 @@ +/trash directory*/ +/test-results/ +/.prove/ +/build/ diff --git a/t/interop/Makefile b/t/interop/Makefile new file mode 100644 index 0000000000..31a4bbc716 --- /dev/null +++ b/t/interop/Makefile @@ -0,0 +1,16 @@ +-include ../../config.mak +export GIT_TEST_OPTIONS + +SHELL_PATH ?= $(SHELL) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +T = $(sort $(wildcard i[0-9][0-9][0-9][0-9]-*.sh)) + +all: $(T) + +$(T): + @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +clean: + rm -rf build "trash directory".* test-results + +.PHONY: all clean $(T) diff --git a/t/interop/README b/t/interop/README new file mode 100644 index 0000000000..72d42bd856 --- /dev/null +++ b/t/interop/README @@ -0,0 +1,85 @@ +Git version interoperability tests +================================== + +This directory has interoperability tests for git. Each script is +similar to the normal test scripts found in t/, but with the added twist +that two special versions of git, "git.a" and "git.b", are available in +the PATH. Individual tests can then check the interaction between the +two versions. + +When you add a feature that handles backwards compatibility between git +versions, it's encouraged to add a test here to make sure it behaves as +you expect. + + +Running Tests +------------- + +The easiest way to run tests is to say "make". This runs all +the tests against their default versions. + +You can run a single test like: + + $ ./i0000-basic.sh + ok 1 - bare git is forbidden + ok 2 - git.a version (v1.6.6.3) + ok 3 - git.b version (v2.11.1) + # passed all 3 test(s) + 1..3 + +Each test contains default versions to run against. You may override +these by setting `GIT_TEST_VERSION_A` and `GIT_TEST_VERSION_B` in the +environment. Note that not all combinations will give sensible outcomes +for all tests (e.g., a test checking for a specific old/new interaction +may want something "old" enough" and something "new" enough; see +individual tests for details). + +Version names should be resolvable as revisions in the current +repository. They will be exported and built as needed using the +config.mak files found at the root of your working tree. + +The exception is the special version "." which uses the currently-built +contents of your working tree. + +You can set the following variables (in the environment or in your config.mak): + + GIT_INTEROP_MAKE_OPTS + Options to pass to `make` when building a git version (e.g., + `-j8`). + +You can also pass any command-line options taken by ordinary git tests (e.g., +"-v"). + + +Naming Tests +------------ + +The interop test files are named like: + + iNNNN-short-description.sh + +where N is a decimal digit. The same conventions for choosing NNNN as +for normal tests apply. + + +Writing Tests +------------- + +An interop test script starts like a normal script, declaring a few +variables and then including interop-lib.sh (which includes test-lib.sh). +Besides test_description, you should also set the $VERSION_A and $VERSION_B +variables to give the default versions to test against. See t0000-basic.sh for +an example. + +You can then use test_expect_success as usual, with a few differences: + + 1. The special commands "git.a" and "git.b" correspond to the + two versions. + + 2. You cannot call a bare "git". This is to prevent accidents where + you meant "git.a" or "git.b". + + 3. The trash directory is _not_ a git repository by default. You + should create one with the appropriate version of git. + +At the end of the script, call test_done as usual. diff --git a/t/interop/i0000-basic.sh b/t/interop/i0000-basic.sh new file mode 100755 index 0000000000..903e9193f8 --- /dev/null +++ b/t/interop/i0000-basic.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Note that this test only works on real version numbers, +# as it depends on matching the output to "git version". +VERSION_A=v1.6.6.3 +VERSION_B=v2.11.1 + +test_description='sanity test interop library' +. ./interop-lib.sh + +test_expect_success 'bare git is forbidden' ' + test_must_fail git version +' + +test_expect_success "git.a version ($VERSION_A)" ' + echo git version ${VERSION_A#v} >expect && + git.a version >actual && + test_cmp expect actual +' + +test_expect_success "git.b version ($VERSION_B)" ' + echo git version ${VERSION_B#v} >expect && + git.b version >actual && + test_cmp expect actual +' + +test_done diff --git a/t/interop/i5500-git-daemon.sh b/t/interop/i5500-git-daemon.sh new file mode 100755 index 0000000000..1daf69420b --- /dev/null +++ b/t/interop/i5500-git-daemon.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +VERSION_A=. +VERSION_B=v1.0.0 + +: ${LIB_GIT_DAEMON_PORT:=5500} +LIB_GIT_DAEMON_COMMAND='git.a daemon' + +test_description='clone and fetch by older client' +. ./interop-lib.sh +. "$TEST_DIRECTORY"/lib-git-daemon.sh + +start_git_daemon --export-all + +repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo + +test_expect_success "create repo served by $VERSION_A" ' + git.a init "$repo" && + git.a -C "$repo" commit --allow-empty -m one +' + +test_expect_success "clone with $VERSION_B" ' + git.b clone "$GIT_DAEMON_URL/repo" child && + echo one >expect && + git.a -C child log -1 --format=%s >actual && + test_cmp expect actual +' + +test_expect_success "fetch with $VERSION_B" ' + git.a -C "$repo" commit --allow-empty -m two && + ( + cd child && + git.b fetch + ) && + echo two >expect && + git.a -C child log -1 --format=%s FETCH_HEAD >actual && + test_cmp expect actual +' + +stop_git_daemon +test_done diff --git a/t/interop/interop-lib.sh b/t/interop/interop-lib.sh new file mode 100644 index 0000000000..3e0a2911d4 --- /dev/null +++ b/t/interop/interop-lib.sh @@ -0,0 +1,92 @@ +# Interoperability testing framework. Each script should source +# this after setting default $VERSION_A and $VERSION_B variables. + +. ../../GIT-BUILD-OPTIONS +INTEROP_ROOT=$(pwd) +BUILD_ROOT=$INTEROP_ROOT/build + +build_version () { + if test -z "$1" + then + echo >&2 "error: test script did not set default versions" + return 1 + fi + + if test "$1" = "." + then + git rev-parse --show-toplevel + return 0 + fi + + sha1=$(git rev-parse "$1^{tree}") || return 1 + dir=$BUILD_ROOT/$sha1 + + if test -e "$dir/.built" + then + echo "$dir" + return 0 + fi + + echo >&2 "==> Building $1..." + + mkdir -p "$dir" || return 1 + + (cd "$(git rev-parse --show-cdup)" && git archive --format=tar "$sha1") | + (cd "$dir" && tar x) || + return 1 + + for config in config.mak config.mak.autogen config.status + do + if test -e "$INTEROP_ROOT/../../$config" + then + cp "$INTEROP_ROOT/../../$config" "$dir/" || return 1 + fi + done + + ( + cd "$dir" && + make $GIT_INTEROP_MAKE_OPTS >&2 && + touch .built + ) || return 1 + + echo "$dir" +} + +# Old versions of git don't have bin-wrappers, so let's give a rough emulation. +wrap_git () { + write_script "$1" <<-EOF + GIT_EXEC_PATH="$2" + export GIT_EXEC_PATH + PATH="$2:\$PATH" + export GIT_EXEC_PATH + exec git "\$@" + EOF +} + +generate_wrappers () { + mkdir -p .bin && + wrap_git .bin/git.a "$DIR_A" && + wrap_git .bin/git.b "$DIR_B" && + write_script .bin/git <<-\EOF && + echo >&2 fatal: test tried to run generic git + exit 1 + EOF + PATH=$(pwd)/.bin:$PATH +} + +VERSION_A=${GIT_TEST_VERSION_A:-$VERSION_A} +VERSION_B=${GIT_TEST_VERSION_B:-$VERSION_B} + +if ! DIR_A=$(build_version "$VERSION_A") || + ! DIR_B=$(build_version "$VERSION_B") +then + echo >&2 "fatal: unable to build git versions" + exit 1 +fi + +TEST_DIRECTORY=$INTEROP_ROOT/.. +TEST_OUTPUT_DIRECTORY=$INTEROP_ROOT +TEST_NO_CREATE_REPO=t +. "$TEST_DIRECTORY"/test-lib.sh + +generate_wrappers || die "unable to set up interop test environment" diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index f9cbd47931..987d40680b 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -46,7 +46,8 @@ start_git_daemon() { say >&3 "Starting git daemon ..." mkfifo git_daemon_output - git daemon --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \ + ${LIB_GIT_DAEMON_COMMAND:-git daemon} \ + --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \ --reuseaddr --verbose \ --base-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 915eb4a7c6..fb4f7b014e 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -4,6 +4,7 @@ # - New submodule (no_submodule => add_sub1) # - Removed submodule (add_sub1 => remove_sub1) # - Updated submodule (add_sub1 => modify_sub1) +# - Updated submodule recursively (add_nested_sub => modify_sub1_recursively) # - Submodule updated to invalid commit (add_sub1 => invalid_sub1) # - Submodule updated from invalid commit (invalid_sub1 => valid_sub1) # - Submodule replaced by tracked files in directory (add_sub1 => @@ -15,23 +16,50 @@ # - Tracked file replaced by submodule (replace_sub1_with_file => # replace_file_with_sub1) # -# --O-----O -# / ^ replace_directory_with_sub1 -# / replace_sub1_with_directory -# /----O -# / ^ -# / modify_sub1 -# O------O-------O -# ^ ^\ ^ -# | | \ remove_sub1 -# | | -----O-----O -# | | \ ^ replace_file_with_sub1 -# | | \ replace_sub1_with_file -# | add_sub1 --O-----O -# no_submodule ^ valid_sub1 -# invalid_sub1 +# ----O +# / ^ +# / remove_sub1 +# / +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub +# | / modify_sub1 +# v/ +# O------O-----------O---------O +# ^ \ ^ replace_directory_with_sub1 +# | \ replace_sub1_with_directory +# no_submodule \ +# --------O---------O +# \ ^ replace_file_with_sub1 +# \ replace_sub1_with_file +# \ +# ----O---------O +# ^ valid_sub1 +# invalid_sub1 # + create_lib_submodule_repo () { + git init submodule_update_sub1 && + ( + cd submodule_update_sub1 && + echo "expect" >>.gitignore && + echo "actual" >>.gitignore && + echo "x" >file1 && + echo "y" >file2 && + git add .gitignore file1 file2 && + git commit -m "Base inside first submodule" && + git branch "no_submodule" + ) && + git init submodule_update_sub2 && + ( + cd submodule_update_sub2 + echo "expect" >>.gitignore && + echo "actual" >>.gitignore && + echo "x" >file1 && + echo "y" >file2 && + git add .gitignore file1 file2 && + git commit -m "nested submodule base" && + git branch "no_submodule" + ) && git init submodule_update_repo && ( cd submodule_update_repo && @@ -44,15 +72,16 @@ create_lib_submodule_repo () { git branch "no_submodule" && git checkout -b "add_sub1" && - git submodule add ./. sub1 && + git submodule add ../submodule_update_sub1 sub1 && git config -f .gitmodules submodule.sub1.ignore all && git config submodule.sub1.ignore all && git add .gitmodules && git commit -m "Add sub1" && - git checkout -b remove_sub1 && + + git checkout -b remove_sub1 add_sub1 && git revert HEAD && - git checkout -b "modify_sub1" "add_sub1" && + git checkout -b modify_sub1 add_sub1 && git submodule update && ( cd sub1 && @@ -67,7 +96,27 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b add_nested_sub modify_sub1 && + git -C sub1 checkout -b "add_nested_sub" && + git -C sub1 submodule add --branch no_submodule ../submodule_update_sub2 sub2 && + git -C sub1 commit -a -m "add a nested submodule" && + git add sub1 && + git commit -a -m "update submodule, that updates a nested submodule" && + git checkout -b modify_sub1_recursively && + git -C sub1 checkout -b modify_sub1_recursively && + git -C sub1/sub2 checkout -b modify_sub1_recursively && + echo change >sub1/sub2/file3 && + git -C sub1/sub2 add file3 && + git -C sub1/sub2 commit -m "make a change in nested sub" && + git -C sub1 add sub2 && + git -C sub1 commit -m "update nested sub" && + git add sub1 && + git commit -m "update sub1, that updates nested sub" && + git -C sub1 push origin modify_sub1_recursively && + git -C sub1/sub2 push origin modify_sub1_recursively && + git -C sub1 submodule deinit -f --all && + + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && git -C sub1 checkout modifications && git rm --cached sub1 && @@ -75,22 +124,25 @@ create_lib_submodule_repo () { git config -f .gitmodules --remove-section "submodule.sub1" && git add .gitmodules sub1/* && git commit -m "Replace sub1 with directory" && + git checkout -b replace_directory_with_sub1 && git revert HEAD && - git checkout -b "replace_sub1_with_file" "add_sub1" && + git checkout -b replace_sub1_with_file add_sub1 && git rm sub1 && echo "content" >sub1 && git add sub1 && git commit -m "Replace sub1 with file" && + git checkout -b replace_file_with_sub1 && git revert HEAD && - git checkout -b "invalid_sub1" "add_sub1" && + git checkout -b invalid_sub1 add_sub1 && git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 sub1 && git commit -m "Invalid sub1 commit" && git checkout -b valid_sub1 && git revert HEAD && + git checkout master ) } @@ -130,6 +182,15 @@ test_git_directory_is_unchanged () { ) } +test_git_directory_exists() { + test -e ".git/modules/$1" && + if test -f sub1/.git + then + # does core.worktree point at the right place? + test "$(git -C .git/modules/$1 config core.worktree)" = "../../../$1" + fi +} + # Helper function to be executed at the start of every test below, it sets up # the submodule repo if it doesn't exist and configures the most problematic # settings for diff.ignoreSubmodules. @@ -151,15 +212,36 @@ reset_work_tree_to () { git checkout -f "$1" && git status -u -s >actual && test_must_be_empty actual && - sha1=$(git rev-parse --revs-only HEAD:sub1) && - if test -n "$sha1" && - test $(cd "sub1" && git rev-parse --verify "$sha1^{commit}") + hash=$(git rev-parse --revs-only HEAD:sub1) && + if test -n "$hash" && + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$hash^{commit}") then git submodule update --init --recursive "sub1" fi ) } +reset_work_tree_to_interested () { + reset_work_tree_to $1 && + # make the submodule git dirs available + if ! test -d submodule_update/.git/modules/sub1 + then + mkdir -p submodule_update/.git/modules && + cp -r submodule_update_repo/.git/modules/sub1 submodule_update/.git/modules/sub1 + GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1 config --unset core.worktree + fi && + if ! test -d submodule_update/.git/modules/sub1/modules/sub2 + 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 + fi && + # indicate we are interested in the submodule: + git -C submodule_update config submodule.sub1.url "bogus" && + # sub1 might not be checked out, so use the git dir + git -C submodule_update/.git/modules/sub1 config submodule.sub2.url "bogus" +} + # Test that the superproject contains the content according to commit "$1" # (the work tree must match the index for everything but submodules but the # index must exactly match the given commit including any submodule SHA-1s). @@ -173,6 +255,11 @@ test_superproject_content () { # Test that the given submodule at path "$1" contains the content according # to the submodule commit recorded in the superproject's commit "$2" test_submodule_content () { + if test x"$1" = "x-C" + then + cd "$2" + shift; shift; + fi if test $# != 2 then echo "test_submodule_content needs two arguments" @@ -675,3 +762,464 @@ test_submodule_forced_switch () { ) ' } + +# Test that submodule contents are correctly updated when switching +# between commits that change a submodule. +# Test that the following transitions are correctly handled: +# (These tests are also above in the case where we expect no change +# in the submodule) +# - Updated submodule +# - New submodule +# - Removed submodule +# - Directory containing tracked files replaced by submodule +# - Submodule replaced by tracked files in directory +# - Submodule replaced by tracked file with the same name +# - tracked file replaced by submodule +# +# New test cases +# - Removing a submodule with a git directory absorbs the submodules +# git directory first into the superproject. + +test_submodule_switch_recursing () { + command="$1" + RESULTDS=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULTDS=failure + fi + RESULTR=success + if test "$KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED" = 1 + then + RESULTR=failure + fi + RESULTOI=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULTOI=failure + fi + ######################### Appearing submodule ######################### + # Switching to a commit letting a submodule appear checks it out ... + test_expect_success "$command: added submodule is checked out" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... ignoring an empty existing directory ... + test_expect_success "$command: added submodule is checked out in empty dir" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + mkdir sub1 && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... unless there is an untracked file in its place. + test_expect_success "$command: added submodule doesn't remove untracked file with same name" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + : >sub1 && + test_must_fail $command add_sub1 && + test_superproject_content origin/no_submodule && + test_must_be_empty sub1 + ) + ' + # ... but an ignored file is fine. + test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" ' + test_when_finished "rm submodule_update/.git/info/exclude" && + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + : >sub1 && + echo sub1 >.git/info/exclude + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing a tracked file with a submodule produces a checked out submodule + test_expect_success "$command: replace tracked file with submodule checks out submodule" ' + prolog && + reset_work_tree_to_interested replace_sub1_with_file && + ( + cd submodule_update && + git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && + $command replace_file_with_sub1 && + test_superproject_content origin/replace_file_with_sub1 && + test_submodule_content sub1 origin/replace_file_with_sub1 + ) + ' + # ... as does removing a directory with tracked files with a submodule. + test_expect_success "$command: replace directory with submodule" ' + prolog && + reset_work_tree_to_interested replace_sub1_with_directory && + ( + cd submodule_update && + git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && + $command replace_directory_with_sub1 && + test_superproject_content origin/replace_directory_with_sub1 && + test_submodule_content sub1 origin/replace_directory_with_sub1 + ) + ' + + ######################## Disappearing submodule ####################### + # Removing a submodule removes its work tree ... + test_expect_success "$command: removed submodule removes submodules working tree" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + ! test -e sub1 + ) + ' + # ... absorbing a .git directory along the way. + test_expect_success "$command: removed submodule absorbs submodules .git directory" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + ! test -e sub1 && + test_git_directory_exists sub1 + ) + ' + # Replacing a submodule with files in a directory must succeeds + # when the submodule is clean + test_expect_$RESULTDS "$command: replace submodule with a directory" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_git_directory_exists sub1 + ) + ' + + # Replacing it with a file ... + test_expect_success "$command: replace submodule with a file" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file && + test -f sub1 + ) + ' + + # ... must check its local work tree for untracked files + test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/untrackedfile && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + + # ... and ignored files are ignored + test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" ' + test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" && + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/ignored && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file && + test -f sub1 + ) + ' + + ########################## Modified submodule ######################### + # Updating a submodule sha1 updates the submodule's work tree + test_expect_success "$command: modified submodule updates submodule work tree" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + + # Updating a submodule to an invalid sha1 doesn't update the + # superproject nor the submodule's work tree. + test_expect_success "$command: updating to a missing submodule commit fails" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t invalid_sub1 origin/invalid_sub1 && + test_must_fail $command invalid_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + + # recursing deeper than one level doesn't work yet. + test_expect_$RESULTR "$command: modified submodule updates submodule recursively" ' + prolog && + reset_work_tree_to_interested add_nested_sub && + ( + cd submodule_update && + git branch -t modify_sub1_recursively origin/modify_sub1_recursively && + $command modify_sub1_recursively && + test_superproject_content origin/modify_sub1_recursively && + test_submodule_content sub1 origin/modify_sub1_recursively && + test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively + ) + ' +} + +# Test that submodule contents are updated when switching between commits +# that change a submodule, but throwing away local changes in +# the superproject as well as the submodule is allowed. +test_submodule_forced_switch_recursing () { + command="$1" + RESULT=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULT=failure + fi + ######################### Appearing submodule ######################### + # Switching to a commit letting a submodule appear creates empty dir ... + test_expect_success "$command: added submodule is checked out" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... and doesn't care if it already exists ... + test_expect_success "$command: added submodule ignores empty directory" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + mkdir sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... not caring about an untracked file either + test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing a tracked file with a submodule checks out the submodule + test_expect_success "$command: replace tracked file with submodule populates the submodule" ' + prolog && + reset_work_tree_to_interested replace_sub1_with_file && + ( + cd submodule_update && + git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && + $command replace_file_with_sub1 && + test_superproject_content origin/replace_file_with_sub1 && + test_submodule_content sub1 origin/replace_file_with_sub1 + ) + ' + # ... as does removing a directory with tracked files with a + # submodule. + test_expect_success "$command: replace directory with submodule" ' + prolog && + reset_work_tree_to_interested replace_sub1_with_directory && + ( + cd submodule_update && + git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && + $command replace_directory_with_sub1 && + test_superproject_content origin/replace_directory_with_sub1 && + test_submodule_content sub1 origin/replace_directory_with_sub1 + ) + ' + + ######################## Disappearing submodule ####################### + # Removing a submodule doesn't remove its work tree ... + test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + ! test -e sub1 + ) + ' + # ... especially when it contains a .git directory. + test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules/sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + test_git_directory_exists sub1 && + ! test -e sub1 + ) + ' + # Replacing a submodule with files in a directory ... + test_expect_success "$command: replace submodule with a directory" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$command: replace submodule containing a .git directory with a directory must fail" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules/sub1 && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_submodule_content sub1 origin/modify_sub1 + test_git_directory_exists sub1 + ) + ' + # Replacing it with a file + test_expect_success "$command: replace submodule with a file" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file + ) + ' + + # ... even if the submodule contains ignored files + test_expect_success "$command: replace submodule with a file ignoring ignored files" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/expect && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file + ) + ' + + # ... but stops for untracked files that would be lost + test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/untracked_file && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test -f sub1/untracked_file + ) + ' + + ########################## Modified submodule ######################### + # Updating a submodule sha1 updates the submodule's work tree + test_expect_success "$command: modified submodule updates submodule work tree" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + # Updating a submodule to an invalid sha1 doesn't update the + # submodule's work tree, subsequent update will fail + test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t invalid_sub1 origin/invalid_sub1 && + test_must_fail $command invalid_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Updating a submodule from an invalid sha1 updates + test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" ' + prolog && + reset_work_tree_to_interested invalid_sub1 && + ( + cd submodule_update && + git branch -t valid_sub1 origin/valid_sub1 && + test_must_fail $command valid_sub1 && + test_superproject_content origin/invalid_sub1 + ) + ' +} diff --git a/t/perf/p0001-rev-list.sh b/t/perf/p0001-rev-list.sh index 16359d51ae..ebf172401b 100755 --- a/t/perf/p0001-rev-list.sh +++ b/t/perf/p0001-rev-list.sh @@ -15,7 +15,8 @@ test_perf 'rev-list --all --objects' ' ' test_expect_success 'create new unreferenced commit' ' - commit=$(git commit-tree HEAD^{tree} -p HEAD) + commit=$(git commit-tree HEAD^{tree} -p HEAD) && + test_export commit ' test_perf 'rev-list $commit --not --all' ' diff --git a/t/perf/p7000-filter-branch.sh b/t/perf/p7000-filter-branch.sh index 15ee5d1d53..b029586ccb 100755 --- a/t/perf/p7000-filter-branch.sh +++ b/t/perf/p7000-filter-branch.sh @@ -16,4 +16,9 @@ test_perf 'noop filter' ' git filter-branch -f base..HEAD ' +test_perf 'noop prune-empty' ' + git checkout --detach tip && + git filter-branch -f --prune-empty base..HEAD +' + test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 46f08ee087..ab4b8b06ae 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -83,7 +83,7 @@ test_perf_create_repo_from () { error "bug in the test script: not 2 parameters to test-create-repo" repo="$1" source="$2" - source_git="$(git -C "$source" rev-parse --git-dir)" + source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)" objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)" mkdir -p "$repo/.git" ( @@ -102,7 +102,7 @@ test_perf_create_repo_from () { ) && ( cd "$repo" && - git init -q && { + "$MODERN_GIT" init -q && { test_have_prereq SYMLINKS || git config core.symlinks false } && diff --git a/t/perf/run b/t/perf/run index e8adedadfd..c788d713ae 100755 --- a/t/perf/run +++ b/t/perf/run @@ -63,6 +63,9 @@ run_dirs_helper () { unset GIT_TEST_INSTALLED else GIT_TEST_INSTALLED="$mydir/bin-wrappers" + # Older versions of git lacked bin-wrappers; fallback to the + # files in the root. + test -d "$GIT_TEST_INSTALLED" || GIT_TEST_INSTALLED=$mydir export GIT_TEST_INSTALLED fi run_one_dir "$@" diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index f0fbb42554..f19ae4f8cc 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -13,10 +13,31 @@ attr_check () { test_line_count = 0 err } +attr_check_quote () { + + path="$1" + quoted_path="$2" + expect="$3" + + git check-attr test -- "$path" >actual && + echo "\"$quoted_path\": test: $expect" >expect && + test_cmp expect actual + +} + +test_expect_success 'open-quoted pathname' ' + echo "\"a test=a" >.gitattributes && + test_must_fail attr_check a a +' + + test_expect_success 'setup' ' mkdir -p a/b/d a/c b && ( echo "[attr]notest !test" + echo "\" d \" test=d" + echo " e test=e" + echo " e\" test=e" echo "f test=f" echo "a/i test=a/i" echo "onoff test -test" @@ -69,6 +90,11 @@ test_expect_success 'command line checks' ' ' test_expect_success 'attribute test' ' + + attr_check " d " d && + attr_check e e && + attr_check_quote e\" e\\\" e && + attr_check f f && attr_check a/f f && attr_check a/c/f f && diff --git a/t/t0013-sha1dc.sh b/t/t0013-sha1dc.sh new file mode 100755 index 0000000000..6d655cb161 --- /dev/null +++ b/t/t0013-sha1dc.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='test sha1 collision detection' +. ./test-lib.sh +TEST_DATA="$TEST_DIRECTORY/t0013" + +if test -z "$DC_SHA1" +then + skip_all='skipping sha1 collision tests, DC_SHA1 not set' + test_done +fi + +test_expect_success 'test-sha1 detects shattered pdf' ' + test_must_fail test-sha1 <"$TEST_DATA/shattered-1.pdf" 2>err && + test_i18ngrep collision err && + grep 38762cf7f55934b34d179ae6a4c80cadccbb7f0a err +' + +test_done diff --git a/t/t0013/shattered-1.pdf b/t/t0013/shattered-1.pdf Binary files differnew file mode 100644 index 0000000000..ba9aaa145c --- /dev/null +++ b/t/t0013/shattered-1.pdf diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh index e0a6940232..58c0b7e9b6 100755 --- a/t/t0100-previous.sh +++ b/t/t0100-previous.sh @@ -56,5 +56,13 @@ test_expect_success 'merge @{-100} before checking out that many branches yet' ' test_must_fail git merge @{-100} ' +test_expect_success 'log -g @{-1}' ' + git checkout -b last_branch && + git checkout -b new_branch && + echo "last_branch@{0}" >expect && + git log -g --format=%gd @{-1} >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index 82c8411210..fd92533acf 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -12,7 +12,100 @@ test -z "$NO_UNIX_SOCKETS" || { # don't leave a stale daemon running trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT +# test that the daemon works with no special setup helper_test cache + +test_expect_success 'socket defaults to ~/.cache/git/credential/socket' ' + test_when_finished " + git credential-cache exit && + rmdir -p .cache/git/credential/ + " && + test_path_is_missing "$HOME/.git-credential-cache" && + test -S "$HOME/.cache/git/credential/socket" +' + +XDG_CACHE_HOME="$HOME/xdg" +export XDG_CACHE_HOME +# test behavior when XDG_CACHE_HOME is set +helper_test cache + +test_expect_success "use custom XDG_CACHE_HOME if set and default sockets are not created" ' + test_when_finished "git credential-cache exit" && + test -S "$XDG_CACHE_HOME/git/credential/socket" && + test_path_is_missing "$HOME/.git-credential-cache/socket" && + test_path_is_missing "$HOME/.cache/git/credential/socket" +' +unset XDG_CACHE_HOME + +test_expect_success 'credential-cache --socket option overrides default location' ' + test_when_finished " + git credential-cache exit --socket \"\$HOME/dir/socket\" && + rmdir \"\$HOME/dir\" + " && + check approve "cache --socket \"\$HOME/dir/socket\"" <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/dir/socket" +' + +test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" ' + test_when_finished " + git credential-cache exit && + sane_unset XDG_CACHE_HOME + " && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.cache/git/credential/socket" && + XDG_CACHE_HOME="$HOME/xdg" && + export XDG_CACHE_HOME && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$XDG_CACHE_HOME/git/credential/socket" +' + +test_expect_success 'use user socket if user directory exists' ' + test_when_finished " + git credential-cache exit && + rmdir \"\$HOME/.git-credential-cache/\" + " && + mkdir -p -m 700 "$HOME/.git-credential-cache/" && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.git-credential-cache/socket" +' + +test_expect_success SYMLINKS 'use user socket if user directory is a symlink to a directory' ' + test_when_finished " + git credential-cache exit && + rmdir \"\$HOME/dir/\" && + rm \"\$HOME/.git-credential-cache\" + " && + mkdir -p -m 700 "$HOME/dir/" && + ln -s "$HOME/dir" "$HOME/.git-credential-cache" && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.git-credential-cache/socket" +' + helper_test_timeout cache --timeout=1 # we can't rely on our "trap" above working after test_done, diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index c5245c5cb4..532682f51c 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -134,6 +134,16 @@ test_expect_success 'gitattributes also work in a subdirectory' ' ) ' +test_expect_success '--path works in a subdirectory' ' + ( + cd subdir && + path1_sha=$(git hash-object --path=../file1 ../file0) && + path0_sha=$(git hash-object --path=../file0 ../file1) && + test "$file0_sha" = "$path0_sha" && + test "$file1_sha" = "$path1_sha" + ) +' + test_expect_success 'check that --no-filters option works' ' nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index 20526aed34..de1ba02dc5 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -5,6 +5,14 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 + +test_submodule_switch_recursing "git read-tree --recurse-submodules -u -m" + +test_submodule_forced_switch_recursing "git read-tree --recurse-submodules -u --reset" + test_submodule_switch "git read-tree -u -m" test_submodule_forced_switch "git read-tree -u --reset" diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 923bfc5a26..afcca0d52c 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1097,6 +1097,68 @@ test_expect_success 'multiple git -c appends config' ' test_cmp expect actual ' +test_expect_success 'last one wins: two level vars' ' + + # sec.var and sec.VAR are the same variable, as the first + # and the last level of a configuration variable name is + # case insensitive. + + echo VAL >expect && + + git -c sec.var=val -c sec.VAR=VAL config --get sec.var >actual && + test_cmp expect actual && + git -c SEC.var=val -c sec.var=VAL config --get sec.var >actual && + test_cmp expect actual && + + git -c sec.var=val -c sec.VAR=VAL config --get SEC.var >actual && + test_cmp expect actual && + git -c SEC.var=val -c sec.var=VAL config --get sec.VAR >actual && + test_cmp expect actual +' + +test_expect_success 'last one wins: three level vars' ' + + # v.a.r and v.A.r are not the same variable, as the middle + # level of a three-level configuration variable name is + # case sensitive. + + echo val >expect && + git -c v.a.r=val -c v.A.r=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c v.A.r=VAL config --get V.a.R >actual && + test_cmp expect actual && + + # v.a.r and V.a.R are the same variable, as the first + # and the last level of a configuration variable name is + # case insensitive. + + echo VAL >expect && + git -c v.a.r=val -c v.a.R=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c V.a.r=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c v.a.R=VAL config --get V.a.R >actual && + test_cmp expect actual && + git -c v.a.r=val -c V.a.r=VAL config --get V.a.R >actual && + test_cmp expect actual +' + +for VAR in a .a a. a.0b a."b c". a."b c".0d +do + test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" ' + test_must_fail git -c "$VAR=VAL" config -l + ' +done + +for VAR in a.b a."b c".d +do + test_expect_success "git -c $VAR=VAL works with valid '$VAR'" ' + echo VAL >expect && + git -c "$VAR=VAL" config --get "$VAR" >actual && + test_cmp expect actual + ' +done + test_expect_success 'git -c is not confused by empty environment' ' GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list ' @@ -1177,6 +1239,111 @@ test_expect_success 'urlmatch' ' test_cmp expect actual ' +test_expect_success 'urlmatch favors more specific URLs' ' + cat >.git/config <<-\EOF && + [http "https://example.com/"] + cookieFile = /tmp/root.txt + [http "https://example.com/subdirectory"] + cookieFile = /tmp/subdirectory.txt + [http "https://user@example.com/"] + cookieFile = /tmp/user.txt + [http "https://averylonguser@example.com/"] + cookieFile = /tmp/averylonguser.txt + [http "https://preceding.example.com"] + cookieFile = /tmp/preceding.txt + [http "https://*.example.com"] + cookieFile = /tmp/wildcard.txt + [http "https://*.example.com/wildcardwithsubdomain"] + cookieFile = /tmp/wildcardwithsubdomain.txt + [http "https://trailing.example.com"] + cookieFile = /tmp/trailing.txt + [http "https://user@*.example.com/"] + cookieFile = /tmp/wildcardwithuser.txt + [http "https://sub.example.com/"] + cookieFile = /tmp/sub.txt + EOF + + echo http.cookiefile /tmp/root.txt >expect && + git config --get-urlmatch HTTP https://example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/user.txt >expect && + git config --get-urlmatch HTTP https://user@example.com/ >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://averylonguser@example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/preceding.txt >expect && + git config --get-urlmatch HTTP https://preceding.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/wildcard.txt >expect && + git config --get-urlmatch HTTP https://wildcard.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/trailing.txt >expect && + git config --get-urlmatch HTTP https://trailing.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://user@sub.example.com >actual && + test_cmp expect actual +' + +test_expect_success 'urlmatch with wildcard' ' + cat >.git/config <<-\EOF && + [http] + sslVerify + [http "https://*.example.com"] + sslVerify = false + cookieFile = /tmp/cookie.txt + EOF + + test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual && + test_must_be_empty actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://good-example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.sslverify https://deep.nested.example.com >actual && + test_cmp expect actual && + + echo false >expect && + git config --bool --get-urlmatch http.sslverify https://good.example.com >actual && + test_cmp expect actual && + + { + echo http.cookiefile /tmp/cookie.txt && + echo http.sslverify false + } >expect && + git config --get-urlmatch HTTP https://good.example.com >actual && + test_cmp expect actual && + + echo http.sslverify >expect && + git config --get-urlmatch HTTP https://more.example.com.au >actual && + test_cmp expect actual +' + # good section hygiene test_expect_failure 'unsetting the last key in a section removes header' ' cat >.git/config <<-\EOF && diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 9ba2ba11c3..e833939320 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -102,7 +102,7 @@ test_expect_success 'config modification does not affect includes' ' test_expect_success 'missing include files are ignored' ' cat >.gitconfig <<-\EOF && - [include]path = foo + [include]path = non-existent [test]value = yes EOF echo yes >expect && @@ -152,6 +152,62 @@ test_expect_success 'relative includes from stdin line fail' ' test_must_fail git config --file - test.one ' +test_expect_success 'conditional include, both unanchored' ' + git init foo && + ( + cd foo && + echo "[includeIf \"gitdir:foo/\"]path=bar" >>.git/config && + echo "[test]one=1" >.git/bar && + echo 1 >expect && + git config test.one >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, $HOME expansion' ' + ( + cd foo && + echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config && + echo "[test]two=2" >.git/bar2 && + echo 2 >expect && + git config test.two >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, full pattern' ' + ( + cd foo && + echo "[includeIf \"gitdir:**/foo/**\"]path=bar3" >>.git/config && + echo "[test]three=3" >.git/bar3 && + echo 3 >expect && + git config test.three >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, relative path' ' + echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >>.gitconfig && + echo "[test]four=4" >bar4 && + ( + cd foo && + echo 4 >expect && + git config test.four >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, both unanchored, icase' ' + ( + cd foo && + echo "[includeIf \"gitdir/i:FOO/\"]path=bar5" >>.git/config && + echo "[test]five=5" >.git/bar5 && + echo 5 >expect && + git config test.five >actual && + test_cmp expect actual + ) +' + test_expect_success 'include cycles are detected' ' cat >.gitconfig <<-\EOF && [test]value = gitconfig diff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh new file mode 100755 index 0000000000..b97357b8ab --- /dev/null +++ b/t/t1309-early-config.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +test_description='Test read_early_config()' + +. ./test-lib.sh + +test_expect_success 'read early config' ' + test_config early.config correct && + test-config read_early_config early.config >output && + test correct = "$(cat output)" +' + +test_expect_success 'in a sub-directory' ' + test_config early.config sub && + mkdir -p sub && + ( + cd sub && + test-config read_early_config early.config + ) >output && + test sub = "$(cat output)" +' + +test_expect_success 'ceiling' ' + test_config early.config ceiling && + mkdir -p sub && + ( + GIT_CEILING_DIRECTORIES="$PWD" && + export GIT_CEILING_DIRECTORIES && + cd sub && + test-config read_early_config early.config + ) >output && + test -z "$(cat output)" +' + +test_expect_success 'ceiling #2' ' + mkdir -p xdg/git && + git config -f xdg/git/config early.config xdg && + test_config early.config ceiling && + mkdir -p sub && + ( + XDG_CONFIG_HOME="$PWD"/xdg && + GIT_CEILING_DIRECTORIES="$PWD" && + export GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME && + cd sub && + test-config read_early_config early.config + ) >output && + test xdg = "$(cat output)" +' + +test_with_config () { + rm -rf throwaway && + git init throwaway && + ( + cd throwaway && + echo "$*" >.git/config && + test-config read_early_config early.config + ) +} + +test_expect_success 'ignore .git/ with incompatible repository version' ' + test_with_config "[core]repositoryformatversion = 999999" 2>err && + grep "warning:.* Expected git repo version <= [1-9]" err +' + +test_expect_failure 'ignore .git/ with invalid repository version' ' + test_with_config "[core]repositoryformatversion = invalid" +' + + +test_expect_failure 'ignore .git/ with invalid config' ' + test_with_config "[" +' + +test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b0ffc0b573..a23cd7b576 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -40,7 +40,7 @@ test_expect_success \ "git update-ref $m $A && test $A"' = $(cat .git/'"$m"')' test_expect_success \ - "create $m" \ + "create $m with oldvalue verification" \ "git update-ref $m $B $A && test $B"' = $(cat .git/'"$m"')' test_expect_success "fail to delete $m with stale ref" ' @@ -48,31 +48,31 @@ test_expect_success "fail to delete $m with stale ref" ' test $B = "$(cat .git/$m)" ' test_expect_success "delete $m" ' + test_when_finished "rm -f .git/$m" && git update-ref -d $m $B && - ! test -f .git/$m + test_path_is_missing .git/$m ' -rm -f .git/$m -test_expect_success "delete $m without oldvalue verification" " +test_expect_success "delete $m without oldvalue verification" ' + test_when_finished "rm -f .git/$m" && git update-ref $m $A && - test $A = \$(cat .git/$m) && + test $A = $(cat .git/$m) && git update-ref -d $m && - ! test -f .git/$m -" -rm -f .git/$m + test_path_is_missing .git/$m +' -test_expect_success \ - "fail to create $n" \ - "touch .git/$n_dir && - test_must_fail git update-ref $n $A >out 2>err" -rm -f .git/$n_dir out err +test_expect_success "fail to create $n" ' + test_when_finished "rm -f .git/$n_dir" && + touch .git/$n_dir && + test_must_fail git update-ref $n $A +' test_expect_success \ "create $m (by HEAD)" \ "git update-ref HEAD $A && test $A"' = $(cat .git/'"$m"')' test_expect_success \ - "create $m (by HEAD)" \ + "create $m (by HEAD) with oldvalue verification" \ "git update-ref HEAD $B $A && test $B"' = $(cat .git/'"$m"')' test_expect_success "fail to delete $m (by HEAD) with stale ref" ' @@ -80,10 +80,28 @@ test_expect_success "fail to delete $m (by HEAD) with stale ref" ' test $B = $(cat .git/$m) ' test_expect_success "delete $m (by HEAD)" ' + test_when_finished "rm -f .git/$m" && git update-ref -d HEAD $B && - ! test -f .git/$m + test_path_is_missing .git/$m +' + +test_expect_success "deleting current branch adds message to HEAD's log" ' + test_when_finished "rm -f .git/$m" && + git update-ref $m $A && + git symbolic-ref HEAD $m && + git update-ref -m delete-$m -d $m && + test_path_is_missing .git/$m && + grep "delete-$m$" .git/logs/HEAD +' + +test_expect_success "deleting by HEAD adds message to HEAD's log" ' + test_when_finished "rm -f .git/$m" && + git update-ref $m $A && + git symbolic-ref HEAD $m && + git update-ref -m delete-by-head -d HEAD && + test_path_is_missing .git/$m && + grep "delete-by-head$" .git/logs/HEAD ' -rm -f .git/$m test_expect_success 'update-ref does not create reflogs by default' ' test_when_finished "git update-ref -d $outside" && @@ -170,28 +188,29 @@ test_expect_success \ "git update-ref HEAD $B $A && test $B"' = $(cat .git/'"$m"')' test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" ' + test_when_finished "rm -f .git/$m" && git update-ref -d HEAD $B && ! grep "$m" .git/packed-refs && - ! test -f .git/$m + test_path_is_missing .git/$m ' -rm -f .git/$m cp -f .git/HEAD .git/HEAD.orig test_expect_success "delete symref without dereference" ' + test_when_finished "cp -f .git/HEAD.orig .git/HEAD" && git update-ref --no-deref -d HEAD && - ! test -f .git/HEAD + test_path_is_missing .git/HEAD ' -cp -f .git/HEAD.orig .git/HEAD test_expect_success "delete symref without dereference when the referred ref is packed" ' + test_when_finished "cp -f .git/HEAD.orig .git/HEAD" && echo foo >foo.c && git add foo.c && git commit -m foo && git pack-refs --all && git update-ref --no-deref -d HEAD && - ! test -f .git/HEAD + test_path_is_missing .git/HEAD ' -cp -f .git/HEAD.orig .git/HEAD + git update-ref -d $m test_expect_success 'update-ref -d is not confused by self-reference' ' @@ -223,10 +242,10 @@ test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' test_expect_success '(not) create HEAD with old sha1' " test_must_fail git update-ref HEAD $A $B " -test_expect_success "(not) prior created .git/$m" " - ! test -f .git/$m -" -rm -f .git/$m +test_expect_success "(not) prior created .git/$m" ' + test_when_finished "rm -f .git/$m" && + test_path_is_missing .git/$m +' test_expect_success \ "create HEAD" \ @@ -234,56 +253,84 @@ test_expect_success \ test_expect_success '(not) change HEAD with wrong SHA1' " test_must_fail git update-ref HEAD $B $Z " -test_expect_success "(not) changed .git/$m" " - ! test $B"' = $(cat .git/'"$m"') +test_expect_success "(not) changed .git/$m" ' + test_when_finished "rm -f .git/$m" && + ! test $B = $(cat .git/$m) ' -rm -f .git/$m rm -f .git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ + 'test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:30" \ git update-ref --create-reflog HEAD '"$A"' -m "Initial Creation" && test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:31" \ + 'test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:31" \ git update-ref HEAD'" $B $A "'-m "Switch" && test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:41" \ + 'test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:41" \ git update-ref HEAD'" $A && test $A"' = $(cat .git/'"$m"')' +test_expect_success "empty directory removal" ' + git branch d1/d2/r1 HEAD && + git branch d1/r2 HEAD && + test_path_is_file .git/refs/heads/d1/d2/r1 && + test_path_is_file .git/logs/refs/heads/d1/d2/r1 && + git branch -d d1/d2/r1 && + test_path_is_missing .git/refs/heads/d1/d2 && + test_path_is_missing .git/logs/refs/heads/d1/d2 && + test_path_is_file .git/refs/heads/d1/r2 && + test_path_is_file .git/logs/refs/heads/d1/r2 +' + +test_expect_success "symref empty directory removal" ' + git branch e1/e2/r1 HEAD && + git branch e1/r2 HEAD && + git checkout e1/e2/r1 && + test_when_finished "git checkout master" && + test_path_is_file .git/refs/heads/e1/e2/r1 && + test_path_is_file .git/logs/refs/heads/e1/e2/r1 && + git update-ref -d HEAD && + test_path_is_missing .git/refs/heads/e1/e2 && + test_path_is_missing .git/logs/refs/heads/e1/e2 && + test_path_is_file .git/refs/heads/e1/r2 && + test_path_is_file .git/logs/refs/heads/e1/r2 && + test_path_is_file .git/logs/HEAD +' + cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF -test_expect_success \ - "verifying $m's log" \ - "test_cmp expect .git/logs/$m" -rm -rf .git/$m .git/logs expect - -test_expect_success \ - 'enable core.logAllRefUpdates' \ - 'git config core.logAllRefUpdates true && - test true = $(git config --bool --get core.logAllRefUpdates)' +test_expect_success "verifying $m's log (logged by touch)" ' + test_when_finished "rm -rf .git/$m .git/logs expect" && + test_cmp expect .git/logs/$m +' test_expect_success \ "create $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:32" \ + 'test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:32" \ git update-ref HEAD'" $A "'-m "Initial Creation" && test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:33" \ + 'test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:33" \ git update-ref HEAD'" $B $A "'-m "Switch" && test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:43" \ + 'test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:43" \ git update-ref HEAD '"$A && test $A"' = $(cat .git/'"$m"')' @@ -293,9 +340,9 @@ $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000 EOF test_expect_success \ - "verifying $m's log" \ - 'test_cmp expect .git/logs/$m' -rm -f .git/$m .git/logs/$m expect + "verifying $m's log (logged by config)" \ + 'test_when_finished "rm -f .git/$m .git/logs/$m expect" && + test_cmp expect .git/logs/$m' git update-ref $m $D cat >.git/logs/$m <<EOF @@ -311,55 +358,55 @@ gd="Thu, 26 May 2005 18:33:00 -0500" ld="Thu, 26 May 2005 18:43:00 -0500" test_expect_success \ 'Query "master@{May 25 2005}" (before history)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 25 2005}" >o 2>e && test '"$C"' = $(cat o) && test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"' test_expect_success \ "Query master@{2005-05-25} (before history)" \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify master@{2005-05-25} >o 2>e && test '"$C"' = $(cat o) && echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && test '"$C"' = $(cat o) && test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && test '"$C"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e && test '"$A"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && test '"$B"' = $(cat o) && test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && test '"$Z"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && test '"$E"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-28}" (past end of history)' \ - 'rm -f o e && + 'test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-28}" >o 2>e && test '"$D"' = $(cat o) && test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"' @@ -369,7 +416,8 @@ rm -f .git/$m .git/logs/$m expect test_expect_success \ 'creating initial files' \ - 'echo TEST >F && + 'test_when_finished rm -f M && + echo TEST >F && git add F && GIT_AUTHOR_DATE="2005-05-26 23:30" \ GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a && @@ -387,8 +435,7 @@ test_expect_success \ echo $h_TEST >.git/MERGE_HEAD && GIT_AUTHOR_DATE="2005-05-26 23:45" \ GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M && - h_MERGED=$(git rev-parse --verify HEAD) && - rm -f M' + h_MERGED=$(git rev-parse --verify HEAD)' cat >expect <<EOF $Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit (initial): add diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 038e24c401..03d3c7f6d6 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -3,7 +3,7 @@ test_description='test git rev-parse' . ./test-lib.sh -# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir +# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir test_rev_parse () { d= bare= @@ -29,7 +29,8 @@ test_rev_parse () { --is-inside-git-dir \ --is-inside-work-tree \ --show-prefix \ - --git-dir + --git-dir \ + --absolute-git-dir do test $# -eq 0 && break expect="$1" @@ -62,29 +63,71 @@ test_expect_success 'setup' ' cp -R .git repo.git ' -test_rev_parse toplevel false false true '' .git +test_rev_parse toplevel false false true '' .git "$ROOT/.git" -test_rev_parse -C .git .git/ false true false '' . -test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git" +test_rev_parse -C .git .git/ false true false '' . "$ROOT/.git" +test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git" "$ROOT/.git" -test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" +test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" "$ROOT/.git" test_rev_parse -b t 'core.bare = true' true false false test_rev_parse -b u 'core.bare undefined' false false true -test_rev_parse -C work -g ../.git -b f 'GIT_DIR=../.git, core.bare = false' false false true '' +test_rev_parse -C work -g ../.git -b f 'GIT_DIR=../.git, core.bare = false' false false true '' "../.git" "$ROOT/.git" test_rev_parse -C work -g ../.git -b t 'GIT_DIR=../.git, core.bare = true' true false false '' test_rev_parse -C work -g ../.git -b u 'GIT_DIR=../.git, core.bare undefined' false false true '' -test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true '' +test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true '' "../repo.git" "$ROOT/repo.git" test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = true' true false false '' test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true '' +test_expect_success 'git-common-dir from worktree root' ' + echo .git >expect && + git rev-parse --git-common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-common-dir inside sub-dir' ' + mkdir -p path/to/child && + test_when_finished "rm -rf path" && + echo "$(git -C path/to/child rev-parse --show-cdup).git" >expect && + git -C path/to/child rev-parse --git-common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-path from worktree root' ' + echo .git/objects >expect && + git rev-parse --git-path objects >actual && + test_cmp expect actual +' + +test_expect_success 'git-path inside sub-dir' ' + mkdir -p path/to/child && + test_when_finished "rm -rf path" && + echo "$(git -C path/to/child rev-parse --show-cdup).git/objects" >expect && + git -C path/to/child rev-parse --git-path objects >actual && + test_cmp expect actual +' + +test_expect_success 'showing the superproject correctly' ' + git rev-parse --show-superproject-working-tree >out && + test_must_be_empty out && + + test_create_repo super && + test_commit -C super test_commit && + test_create_repo sub && + test_commit -C sub test_commit && + git -C super submodule add ../sub dir/sub && + echo $(pwd)/super >expect && + git -C super/dir/sub rev-parse --show-superproject-working-tree >out && + test_cmp expect out +' + test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index 292a0720fc..af3ec0da5a 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -8,6 +8,7 @@ test_description='split index mode tests' sane_unset GIT_TEST_SPLIT_INDEX test_expect_success 'enable split index' ' + git config splitIndex.maxPercentChange 100 && git update-index --split-index && test-dump-split-index .git/index >actual && indexversion=$(test-index-version <.git/index) && @@ -19,12 +20,12 @@ test_expect_success 'enable split index' ' own=8299b0bcd1ac364e5f1d7768efb62fa2da79a339 base=39d890139ee5356c7ef572216cebcd27aa41f9df fi && - cat >expect <<EOF && -own $own -base $base -replacements: -deletions: -EOF + cat >expect <<-EOF && + own $own + base $base + replacements: + deletions: + EOF test_cmp expect actual ' @@ -32,51 +33,51 @@ test_expect_success 'add one file' ' : >one && git update-index --add one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 $EMPTY_BLOB 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -base $base -100644 $EMPTY_BLOB 0 one -replacements: -deletions: -EOF + cat >expect <<-EOF && + base $base + 100644 $EMPTY_BLOB 0 one + replacements: + deletions: + EOF test_cmp expect actual ' test_expect_success 'disable split index' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 $EMPTY_BLOB 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && BASE=$(test-dump-split-index .git/index | grep "^own" | sed "s/own/base/") && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -not a split index -EOF + cat >expect <<-EOF && + not a split index + EOF test_cmp expect actual ' test_expect_success 'enable split index again, "one" now belongs to base index"' ' git update-index --split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 $EMPTY_BLOB 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -replacements: -deletions: -EOF + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF test_cmp expect actual ' @@ -84,18 +85,18 @@ test_expect_success 'modify original file, base index untouched' ' echo modified >one && git update-index one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -replacements: 0 -deletions: -EOF + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + replacements: 0 + deletions: + EOF test_cmp expect actual ' @@ -103,54 +104,54 @@ test_expect_success 'add another file, which stays index' ' : >two && git update-index --add two && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -100644 $EMPTY_BLOB 0 two -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -100644 $EMPTY_BLOB 0 two -replacements: 0 -deletions: -EOF + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + 100644 $EMPTY_BLOB 0 two + replacements: 0 + deletions: + EOF test_cmp expect actual ' test_expect_success 'remove file not in base index' ' git update-index --force-remove two && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -replacements: 0 -deletions: -EOF + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + replacements: 0 + deletions: + EOF test_cmp expect actual ' test_expect_success 'remove file in base index' ' git update-index --force-remove one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -EOF + cat >ls-files.expect <<-EOF && + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -replacements: -deletions: 0 -EOF + cat >expect <<-EOF && + $BASE + replacements: + deletions: 0 + EOF test_cmp expect actual ' @@ -158,18 +159,18 @@ test_expect_success 'add original file back' ' : >one && git update-index --add one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 $EMPTY_BLOB 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -100644 $EMPTY_BLOB 0 one -replacements: -deletions: 0 -EOF + cat >expect <<-EOF && + $BASE + 100644 $EMPTY_BLOB 0 one + replacements: + deletions: 0 + EOF test_cmp expect actual ' @@ -177,27 +178,196 @@ test_expect_success 'add new file' ' : >two && git update-index --add two && git ls-files --stage >actual && - cat >expect <<EOF && -100644 $EMPTY_BLOB 0 one -100644 $EMPTY_BLOB 0 two -EOF + cat >expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp expect actual ' test_expect_success 'unify index, two files remain' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 $EMPTY_BLOB 0 one -100644 $EMPTY_BLOB 0 two -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp ls-files.expect ls-files.actual && test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -not a split index -EOF + cat >expect <<-EOF && + not a split index + EOF test_cmp expect actual ' +test_expect_success 'rev-parse --shared-index-path' ' + test_create_repo split-index && + ( + cd split-index && + git update-index --split-index && + echo .git/sharedindex* >expect && + git rev-parse --shared-index-path >actual && + test_cmp expect actual && + mkdir subdirectory && + cd subdirectory && + echo ../.git/sharedindex* >expect && + git rev-parse --shared-index-path >actual && + test_cmp expect actual + ) +' + +test_expect_success 'set core.splitIndex config variable to true' ' + git config core.splitIndex true && + : >three && + git update-index --add three && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<-EOF && + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 three + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two + EOF + test_cmp ls-files.expect ls-files.actual && + BASE=$(test-dump-split-index .git/index | grep "^base") && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'set core.splitIndex config variable to false' ' + git config core.splitIndex false && + git update-index --force-remove three && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<-EOF && + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two + EOF + test_cmp ls-files.expect ls-files.actual && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + not a split index + EOF + test_cmp expect actual +' + +test_expect_success 'set core.splitIndex config variable to true' ' + git config core.splitIndex true && + : >three && + git update-index --add three && + BASE=$(test-dump-split-index .git/index | grep "^base") && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >four && + git update-index --add four && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 four + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'check behavior with splitIndex.maxPercentChange unset' ' + git config --unset splitIndex.maxPercentChange && + : >five && + git update-index --add five && + BASE=$(test-dump-split-index .git/index | grep "^base") && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >six && + git update-index --add six && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 six + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'check splitIndex.maxPercentChange set to 0' ' + git config splitIndex.maxPercentChange 0 && + : >seven && + git update-index --add seven && + BASE=$(test-dump-split-index .git/index | grep "^base") && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >eight && + git update-index --add eight && + BASE=$(test-dump-split-index .git/index | grep "^base") && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'shared index files expire after 2 weeks by default' ' + : >ten && + git update-index --add ten && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_under_2_weeks_ago=$((5-14*86400)) && + test-chmtime =$just_under_2_weeks_ago .git/sharedindex.* && + : >eleven && + git update-index --add eleven && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_over_2_weeks_ago=$((-1-14*86400)) && + test-chmtime =$just_over_2_weeks_ago .git/sharedindex.* && + : >twelve && + git update-index --add twelve && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + +test_expect_success 'check splitIndex.sharedIndexExpire set to 16 days' ' + git config splitIndex.sharedIndexExpire "16.days.ago" && + test-chmtime =$just_over_2_weeks_ago .git/sharedindex.* && + : >thirteen && + git update-index --add thirteen && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_over_16_days_ago=$((-1-16*86400)) && + test-chmtime =$just_over_16_days_ago .git/sharedindex.* && + : >fourteen && + git update-index --add fourteen && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + +test_expect_success 'check splitIndex.sharedIndexExpire set to "never" and "now"' ' + git config splitIndex.sharedIndexExpire never && + just_10_years_ago=$((-365*10*86400)) && + test-chmtime =$just_10_years_ago .git/sharedindex.* && + : >fifteen && + git update-index --add fifteen && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + git config splitIndex.sharedIndexExpire now && + just_1_second_ago=-1 && + test-chmtime =$just_1_second_ago .git/sharedindex.* && + : >sixteen && + git update-index --add sixteen && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + test_done diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index 6847f75822..e8f70b806f 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -63,6 +63,12 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 +test_submodule_switch_recursing "git checkout --recurse-submodules" + +test_submodule_forced_switch_recursing "git checkout -f --recurse-submodules" + test_submodule_switch "git checkout" test_submodule_forced_switch "git checkout -f" diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh index 465eeeacd3..848da5f368 100755 --- a/t/t2027-worktree-list.sh +++ b/t/t2027-worktree-list.sh @@ -14,10 +14,18 @@ test_expect_success 'rev-parse --git-common-dir on main worktree' ' test_cmp expected actual && mkdir sub && git -C sub rev-parse --git-common-dir >actual2 && - echo sub/.git >expected2 && + echo ../.git >expected2 && test_cmp expected2 actual2 ' +test_expect_success 'rev-parse --git-path objects linked worktree' ' + echo "$(git rev-parse --show-toplevel)/.git/objects" >expect && + test_when_finished "rm -rf linked-tree && git worktree prune" && + git worktree add --detach linked-tree master && + git -C linked-tree rev-parse --git-path objects >actual && + test_cmp expect actual +' + test_expect_success '"list" all worktrees from main' ' echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && test_when_finished "rm -rf here && git worktree prune" && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 8a833f354e..9f353c0efc 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -139,6 +139,12 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou test $(git rev-parse --abbrev-ref HEAD) = bam ' +test_expect_success 'git branch -M baz bam should add entries to .git/logs/HEAD' ' + msg="Branch: renamed refs/heads/baz to refs/heads/bam" && + grep " 0\{40\}.*$msg$" .git/logs/HEAD && + grep "^0\{40\}.*$msg$" .git/logs/HEAD +' + test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' ' git checkout master && git worktree add -b baz bazdir && @@ -207,6 +213,31 @@ test_expect_success 'git branch --list -d t should fail' ' test_path_is_missing .git/refs/heads/t ' +test_expect_success 'git branch --list -v with --abbrev' ' + test_when_finished "git branch -D t" && + git branch t && + git branch -v --list t >actual.default && + git branch -v --list --abbrev t >actual.abbrev && + test_cmp actual.default actual.abbrev && + + git branch -v --list --no-abbrev t >actual.noabbrev && + git branch -v --list --abbrev=0 t >actual.0abbrev && + test_cmp actual.noabbrev actual.0abbrev && + + git branch -v --list --abbrev=36 t >actual.36abbrev && + # how many hexdigits are used? + read name objdefault rest <actual.abbrev && + read name obj36 rest <actual.36abbrev && + objfull=$(git rev-parse --verify t) && + + # are we really getting abbreviations? + test "$obj36" != "$objdefault" && + expr "$obj36" : "$objdefault" >/dev/null && + test "$objfull" != "$obj36" && + expr "$objfull" : "$obj36" >/dev/null + +' + test_expect_success 'git branch --column' ' COLUMNS=81 git branch --column=column >actual && cat >expected <<\EOF && diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 52283dfc8c..5778c0afe1 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -194,7 +194,7 @@ test_expect_success 'local-branch symrefs shortened properly' ' git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one && cat >expect <<-\EOF && ref-to-branch -> branch-one - ref-to-remote -> refs/remotes/origin/branch-one + ref-to-remote -> origin/branch-one EOF git branch >actual.raw && grep ref-to <actual.raw >actual && @@ -225,4 +225,18 @@ test_expect_success 'sort branches, ignore case' ' ) ' +test_expect_success 'git branch --format option' ' + cat >expect <<-\EOF && + Refname is (HEAD detached from fromtag) + Refname is refs/heads/ambiguous + Refname is refs/heads/branch-one + Refname is refs/heads/branch-two + Refname is refs/heads/master + Refname is refs/heads/ref-to-branch + Refname is refs/heads/ref-to-remote + EOF + git branch --format="Refname is %(refname)" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3204-branch-name-interpretation.sh b/t/t3204-branch-name-interpretation.sh new file mode 100755 index 0000000000..698d9cc4f3 --- /dev/null +++ b/t/t3204-branch-name-interpretation.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +test_description='interpreting exotic branch name arguments + +Branch name arguments are usually names which are taken to be inside of +refs/heads/, but we interpret some magic syntax like @{-1}, @{upstream}, etc. +This script aims to check the behavior of those corner cases. +' +. ./test-lib.sh + +expect_branch() { + git log -1 --format=%s "$1" >actual && + echo "$2" >expect && + test_cmp expect actual +} + +expect_deleted() { + test_must_fail git rev-parse --verify "$1" +} + +test_expect_success 'set up repo' ' + test_commit one && + test_commit two && + git remote add origin foo.git +' + +test_expect_success 'update branch via @{-1}' ' + git branch previous one && + + git checkout previous && + git checkout master && + + git branch -f @{-1} two && + expect_branch previous two +' + +test_expect_success 'update branch via local @{upstream}' ' + git branch local one && + git branch --set-upstream-to=local && + + git branch -f @{upstream} two && + expect_branch local two +' + +test_expect_success 'disallow updating branch via remote @{upstream}' ' + git update-ref refs/remotes/origin/remote one && + git branch --set-upstream-to=origin/remote && + + test_must_fail git branch -f @{upstream} two +' + +test_expect_success 'create branch with pseudo-qualified name' ' + git branch refs/heads/qualified two && + expect_branch refs/heads/refs/heads/qualified two +' + +test_expect_success 'delete branch via @{-1}' ' + git branch previous-del && + + git checkout previous-del && + git checkout master && + + git branch -D @{-1} && + expect_deleted previous-del +' + +test_expect_success 'delete branch via local @{upstream}' ' + git branch local-del && + git branch --set-upstream-to=local-del && + + git branch -D @{upstream} && + expect_deleted local-del +' + +test_expect_success 'delete branch via remote @{upstream}' ' + git update-ref refs/remotes/origin/remote-del two && + git branch --set-upstream-to=origin/remote-del && + + git branch -r -D @{upstream} && + expect_deleted origin/remote-del +' + +# Note that we create two oddly named local branches here. We want to make +# sure that we do not accidentally delete either of them, even if +# shorten_unambiguous_ref() tweaks the name to avoid ambiguity. +test_expect_success 'delete @{upstream} expansion matches -r option' ' + git update-ref refs/remotes/origin/remote-del two && + git branch --set-upstream-to=origin/remote-del && + git update-ref refs/heads/origin/remote-del two && + git update-ref refs/heads/remotes/origin/remote-del two && + + test_must_fail git branch -D @{upstream} && + expect_branch refs/heads/origin/remote-del two && + expect_branch refs/heads/remotes/origin/remote-del two +' + +test_expect_success 'disallow deleting remote branch via @{-1}' ' + git update-ref refs/remotes/origin/previous one && + + git checkout -b origin/previous two && + git checkout master && + + test_must_fail git branch -r -D @{-1} && + expect_branch refs/remotes/origin/previous one && + expect_branch refs/heads/origin/previous two +' + +# The thing we are testing here is that "@" is the real branch refs/heads/@, +# and not refs/heads/HEAD. These tests should not imply that refs/heads/@ is a +# sane thing, but it _is_ technically allowed for now. If we disallow it, these +# can be switched to test_must_fail. +test_expect_success 'create branch named "@"' ' + git branch -f @ one && + expect_branch refs/heads/@ one +' + +test_expect_success 'delete branch named "@"' ' + git update-ref refs/heads/@ two && + git branch -D @ && + expect_deleted refs/heads/@ +' + +test_expect_success 'checkout does not treat remote @{upstream} as a branch' ' + git update-ref refs/remotes/origin/checkout one && + git branch --set-upstream-to=origin/checkout && + git update-ref refs/heads/origin/checkout two && + git update-ref refs/heads/remotes/origin/checkout two && + + git checkout @{upstream} && + expect_branch HEAD one +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index e2f18d11f6..33d392ba11 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -556,7 +556,7 @@ test_expect_success 'clean error after failed "exec"' ' echo "edited again" > file7 && git add file7 && test_must_fail git rebase --continue 2>error && - test_i18ngrep "You have staged changes in your working tree." error + test_i18ngrep "you have staged changes in your working tree" error ' test_expect_success 'rebase a detached HEAD' ' diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh index e37547f41a..b1602718f8 100755 --- a/t/t3502-cherry-pick-merge.sh +++ b/t/t3502-cherry-pick-merge.sh @@ -31,6 +31,15 @@ test_expect_success setup ' ' +test_expect_success 'cherry-pick -m complains of bogus numbers' ' + # expect 129 here to distinguish between cases where + # there was nothing to cherry-pick + test_expect_code 129 git cherry-pick -m && + test_expect_code 129 git cherry-pick -m foo b && + test_expect_code 129 git cherry-pick -m -1 b && + test_expect_code 129 git cherry-pick -m 0 b +' + test_expect_success 'cherry-pick a non-merge with -m should fail' ' git reset --hard && diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 5aa6db584c..3c63455729 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -658,7 +658,7 @@ test_expect_success 'rm of a populated nested submodule with nested untracked fi test_cmp expect actual ' -test_expect_success 'rm of a populated nested submodule with a nested .git directory fails even when forced' ' +test_expect_success "rm absorbs submodule's nested .git directory" ' git reset --hard && git submodule update --recursive && (cd submod/subsubmod && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index aaa258daa3..f9528fa00c 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -412,4 +412,47 @@ test_expect_success 'patch-mode via -i prompts for files' ' test_cmp expect actual ' +test_expect_success 'add -p handles globs' ' + git reset --hard && + + mkdir -p subdir && + echo base >one.c && + echo base >subdir/two.c && + git add "*.c" && + git commit -m base && + + echo change >one.c && + echo change >subdir/two.c && + git add -p "*.c" <<-\EOF && + y + y + EOF + + cat >expect <<-\EOF && + one.c + subdir/two.c + EOF + git diff --cached --name-only >actual && + test_cmp expect actual +' + +test_expect_success 'add -p does not expand argument lists' ' + git reset --hard && + + echo content >not-changed && + git add not-changed && + git commit -m "add not-changed file" && + + echo change >file && + GIT_TRACE=$(pwd)/trace.out git add -p . <<-\EOF && + y + EOF + + # we know that "file" must be mentioned since we actually + # update it, but we want to be sure that our "." pathspec + # was not expanded into the argument list of any command. + # So look only for "not-changed". + ! grep not-changed trace.out +' + test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 2de3e18ce6..b71d1e659e 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -274,9 +274,7 @@ test_expect_success 'stash --invalid-option' ' git add file2 && test_must_fail git stash --invalid-option && test_must_fail git stash save --invalid-option && - test bar5,bar6 = $(cat file),$(cat file2) && - git stash -- -message-starting-with-dash && - test bar,bar2 = $(cat file),$(cat file2) + test bar5,bar6 = $(cat file),$(cat file2) ' test_expect_success 'stash an added file' ' @@ -665,7 +663,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu sane_unset GIT_MERGE_VERBOSITY && git stash apply ) | - sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..." + sed -e 1d >actual && # drop "Saved..." test_i18ncmp expect actual ' @@ -775,4 +773,152 @@ test_expect_success 'stash is not confused by partial renames' ' test_path_is_missing file ' +test_expect_success 'push -m shows right message' ' + >foo && + git add foo && + git stash push -m "test message" && + echo "stash@{0}: On master: test message" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'create stores correct message' ' + >foo && + git add foo && + STASH_ID=$(git stash create "create test message") && + echo "On master: create test message" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + +test_expect_success 'create with multiple arguments for the message' ' + >foo && + git add foo && + STASH_ID=$(git stash create test untracked) && + echo "On master: test untracked" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + +test_expect_success 'stash -- <pathspec> stashes and restores the file' ' + >foo && + >bar && + git add foo bar && + git stash push -- foo && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash with multiple pathspec arguments' ' + >foo && + >bar && + >extra && + git add foo bar extra && + git stash push -- foo bar && + test_path_is_missing bar && + test_path_is_missing foo && + test_path_is_file extra && + git stash pop && + test_path_is_file foo && + test_path_is_file bar && + test_path_is_file extra +' + +test_expect_success 'stash with file including $IFS character' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash push -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash with pathspec matching multiple paths' ' + echo original >file && + echo original >other-file && + git commit -m "two" file other-file && + echo modified >file && + echo modified >other-file && + git stash push -- "*file" && + echo original >expect && + test_cmp expect file && + test_cmp expect other-file && + git stash pop && + echo modified >expect && + test_cmp expect file && + test_cmp expect other-file +' + +test_expect_success 'stash push -p with pathspec shows no changes only once' ' + >foo && + git add foo && + git commit -m "tmp" && + git stash push -p foo >actual && + echo "No local changes to save" >expect && + git reset --hard HEAD~ && + test_cmp expect actual +' + +test_expect_success 'stash push with pathspec shows no changes when there are none' ' + >foo && + git add foo && + git commit -m "tmp" && + git stash push foo >actual && + echo "No local changes to save" >expect && + git reset --hard HEAD~ && + test_cmp expect actual +' + +test_expect_success 'stash push with pathspec not in the repository errors out' ' + >untracked && + test_must_fail git stash push untracked && + test_path_is_file untracked +' + +test_expect_success 'untracked files are left in place when -u is not given' ' + >file && + git add file && + >untracked && + git stash push file && + test_path_is_file untracked +' + +test_expect_success 'stash without verb with pathspec' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash -k -- <pathspec> leaves unstaged files intact' ' + git reset && + >foo && + >bar && + git add foo bar && + git commit -m "test" && + echo "foo" >foo && + echo "bar" >bar && + git stash -k -- foo && + test "",bar = $(cat foo),$(cat bar) && + git stash pop && + test foo,bar = $(cat foo),$(cat bar) +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index 38e730090f..83744f8c93 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -77,6 +77,14 @@ test_expect_success 'git stash --no-keep-index -p' ' verify_state dir/foo work index ' +test_expect_success 'stash -p --no-keep-index -- <pathspec> does not unstage other files' ' + set_state HEAD HEADfile_work HEADfile_index && + set_state dir/foo work index && + echo y | git stash push -p --no-keep-index -- HEAD && + verify_state HEAD committed committed && + verify_state dir/foo work index +' + test_expect_success 'none of this moved HEAD' ' verify_saved_head ' diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index f372fc8ca8..193adc7b68 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -185,4 +185,30 @@ test_expect_success 'stash save --all is stash poppable' ' test -s .gitignore ' +test_expect_success 'stash push --include-untracked with pathspec' ' + >foo && + >bar && + git stash push --include-untracked -- foo && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file bar && + test_path_is_file foo +' + +test_expect_success 'stash push with $IFS character' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash push --include-untracked -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + test_done diff --git a/t/t4062-diff-pickaxe.sh b/t/t4062-diff-pickaxe.sh index f0bf50bda7..7c4903f497 100755 --- a/t/t4062-diff-pickaxe.sh +++ b/t/t4062-diff-pickaxe.sh @@ -19,4 +19,9 @@ test_expect_success '-G matches' ' test 4096-zeroes.txt = "$(cat out)" ' +test_expect_success '-S --pickaxe-regex' ' + git diff --name-only -S0 --pickaxe-regex HEAD^ >out && + verbose test 4096-zeroes.txt = "$(cat out)" +' + test_done diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh new file mode 100755 index 0000000000..37143ea0ac --- /dev/null +++ b/t/t5316-pack-delta-depth.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='pack-objects breaks long cross-pack delta chains' +. ./test-lib.sh + +# This mirrors a repeated push setup: +# +# 1. A client repeatedly modifies some files, makes a +# commit, and pushes the result. It does this N times +# before we get around to repacking. +# +# 2. Each push generates a thin pack with the new version of +# various objects. Let's consider some file in the root tree +# which is updated in each commit. +# +# When generating push number X, we feed commit X-1 (and +# thus blob X-1) as a preferred base. The resulting pack has +# blob X as a thin delta against blob X-1. +# +# On the receiving end, "index-pack --fix-thin" will +# complete the pack with a base copy of blob X-1. +# +# 3. In older versions of git, if we used the delta from +# pack X, then we'd always find blob X-1 as a base in the +# same pack (and generate a fresh delta). +# +# But with the pack mru, we jump from delta to delta +# following the traversal order: +# +# a. We grab blob X from pack X as a delta, putting it at +# the tip of our mru list. +# +# b. Eventually we move onto commit X-1. We need other +# objects which are only in pack X-1 (in the test code +# below, it's the containing tree). That puts pack X-1 +# at the tip of our mru list. +# +# c. Eventually we look for blob X-1, and we find the +# version in pack X-1 (because it's the mru tip). +# +# Now we have blob X as a delta against X-1, which is a delta +# against X-2, and so forth. +# +# In the real world, these small pushes would get exploded by +# unpack-objects rather than "index-pack --fix-thin", but the +# same principle applies to larger pushes (they only need one +# repeatedly-modified file to generate the delta chain). + +test_expect_success 'create series of packs' ' + test-genrandom foo 4096 >content && + prev= && + for i in $(test_seq 1 10) + do + cat content >file && + echo $i >>file && + git add file && + git commit -m $i && + cur=$(git rev-parse HEAD^{tree}) && + { + test -n "$prev" && echo "-$prev" + echo $cur + echo "$(git rev-parse :file) file" + } | git pack-objects --stdout >tmp && + git index-pack --stdin --fix-thin <tmp || return 1 + prev=$cur + done +' + +max_chain() { + git index-pack --verify-stat-only "$1" >output && + perl -lne ' + /chain length = (\d+)/ and $len = $1; + END { print $len } + ' output +} + +# Note that this whole setup is pretty reliant on the current +# packing heuristics. We double-check that our test case +# actually produces a long chain. If it doesn't, it should be +# adjusted (or scrapped if the heuristics have become too unreliable) +test_expect_success 'packing produces a long delta' ' + # Use --window=0 to make sure we are seeing reused deltas, + # not computing a new long chain. + pack=$(git pack-objects --all --window=0 </dev/null pack) && + test 9 = "$(max_chain pack-$pack.pack)" +' + +test_expect_success '--depth limits depth' ' + pack=$(git pack-objects --all --depth=5 </dev/null pack) && + test 5 = "$(max_chain pack-$pack.pack)" +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 305ca7a930..3331e0f534 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -255,4 +255,42 @@ test_expect_success 'deny pushing to delete current branch' ' ) ' +extract_ref_advertisement () { + perl -lne ' + # \\ is there to skip capabilities after \0 + /push< ([^\\]+)/ or next; + exit 0 if $1 eq "0000"; + print $1; + ' +} + +test_expect_success 'receive-pack de-dupes .have lines' ' + git init shared && + git -C shared commit --allow-empty -m both && + git clone -s shared fork && + ( + cd shared && + git checkout -b only-shared && + git commit --allow-empty -m only-shared && + git update-ref refs/heads/foo HEAD + ) && + + # Notable things in this expectation: + # - local refs are not de-duped + # - .have does not duplicate locals + # - .have does not duplicate itself + local=$(git -C fork rev-parse HEAD) && + shared=$(git -C shared rev-parse only-shared) && + cat >expect <<-EOF && + $local refs/heads/master + $local refs/remotes/origin/HEAD + $local refs/remotes/origin/master + $shared .have + EOF + + GIT_TRACE_PACKET=$(pwd)/trace git push fork HEAD:foo && + extract_ref_advertisement <trace >refs && + test_cmp expect refs +' + test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 505e1b4a7f..b5865b385d 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -484,7 +484,7 @@ test_expect_success 'test lonely missing ref' ' cd client && test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy ) >/dev/null 2>error-m && - test_cmp expect-error error-m + test_i18ncmp expect-error error-m ' test_expect_success 'test missing ref after existing' ' @@ -492,7 +492,7 @@ test_expect_success 'test missing ref after existing' ' cd client && test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy ) >/dev/null 2>error-em && - test_cmp expect-error error-em + test_i18ncmp expect-error error-em ' test_expect_success 'test missing ref before existing' ' @@ -500,7 +500,7 @@ test_expect_success 'test missing ref before existing' ' cd client && test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A ) >/dev/null 2>error-me && - test_cmp expect-error error-me + test_i18ncmp expect-error error-me ' test_expect_success 'test --all, --depth, and explicit head' ' diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 3ea27107c2..a6c0178f3a 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -744,7 +744,7 @@ test_expect_success 'rename a remote' ' ( cd four && git remote rename origin upstream && - rmdir .git/refs/remotes/origin && + test -z "$(git for-each-ref refs/remotes/origin)" && test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" && test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" && test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" && diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 55fc83fc06..94fc9be9ce 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -248,4 +248,13 @@ test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-complian test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git ' +test_expect_success 'ls-remote works outside repository' ' + # It is important for this repo to be inside the nongit + # area, as we want a repo name that does not include + # slashes (because those inhibit some of our configuration + # lookups). + nongit git init --bare dst.git && + nongit git ls-remote dst.git +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 0fc5a7c596..177897ea0b 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1098,7 +1098,8 @@ test_expect_success 'fetch exact SHA1' ' test_must_fail git cat-file -t $the_commit && # fetching the hidden object should fail by default - test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy && + test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy 2>err && + test_i18ngrep "Server does not allow request for unadvertised object" err && test_must_fail git rev-parse --verify refs/heads/copy && # the server side can allow it to succeed diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index 9a57a7d8f2..97065e62b8 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -102,17 +102,46 @@ test_expect_success 'two push options work' ' test_cmp expect upstream/.git/hooks/post-receive.push_options ' -test_expect_success 'push option denied properly by http remote helper' '\ +test_expect_success 'push option denied properly by http server' ' + test_when_finished "rm -rf test_http_clone" && + test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && mk_repo_pair && git -C upstream config receive.advertisePushOptions false && git -C upstream config http.receivepack true && cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && git clone "$HTTPD_URL"/smart/upstream test_http_clone && test_commit -C test_http_clone one && - test_must_fail git -C test_http_clone push --push-option=asdf origin master && + test_must_fail git -C test_http_clone push --push-option=asdf origin master 2>actual && + test_i18ngrep "the receiving end does not support push options" actual && git -C test_http_clone push origin master ' +test_expect_success 'push options work properly across http' ' + test_when_finished "rm -rf test_http_clone" && + test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + git -C upstream config http.receivepack true && + cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && + git clone "$HTTPD_URL"/smart/upstream test_http_clone && + + test_commit -C test_http_clone one && + git -C test_http_clone push origin master && + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual && + + test_commit -C test_http_clone two && + git -C test_http_clone push --push-option=asdf --push-option="more structured text" origin master && + printf "asdf\nmore structured text\n" >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/post-receive.push_options && + + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual +' + stop_httpd test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 2d3b1e9f93..87308cdced 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -34,6 +34,15 @@ test_expect_success 'clone http repository' ' test_cmp file clone/file ' +test_expect_success 'list refs from outside any repository' ' + cat >expect <<-EOF && + $(git rev-parse master) HEAD + $(git rev-parse master) refs/heads/master + EOF + nongit git ls-remote "$HTTPD_URL/dumb/repo.git" >actual && + test_cmp expect actual +' + test_expect_success 'create password-protected repository' ' mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" && cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 4241ea5b32..b52b8acf98 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -386,6 +386,47 @@ test_expect_success 'tortoiseplink is like putty, with extra arguments' ' expect_ssh "-batch -P 123" myhost src ' +test_expect_success 'double quoted plink.exe in GIT_SSH_COMMAND' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" && + GIT_SSH_COMMAND="\"$TRASH_DIRECTORY/plink.exe\" -v" \ + git clone "[myhost:123]:src" ssh-bracket-clone-plink-3 && + expect_ssh "-v -P 123" myhost src +' + +SQ="'" +test_expect_success 'single quoted plink.exe in GIT_SSH_COMMAND' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" && + GIT_SSH_COMMAND="$SQ$TRASH_DIRECTORY/plink.exe$SQ -v" \ + git clone "[myhost:123]:src" ssh-bracket-clone-plink-4 && + expect_ssh "-v -P 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink detection' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + GIT_SSH_VARIANT=ssh \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-1 && + expect_ssh "-p 123" myhost src +' + +test_expect_success 'ssh.variant overrides plink detection' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + git -c ssh.variant=ssh \ + clone "[myhost:123]:src" ssh-bracket-clone-variant-2 && + expect_ssh "-p 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' ' + GIT_SSH_VARIANT=plink \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 && + expect_ssh "-P 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' ' + GIT_SSH_VARIANT=tortoiseplink \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 && + expect_ssh "-batch -P 123" myhost src +' + # Reset the GIT_SSH environment variable for clone tests. setup_ssh_wrapper diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh index 26ebb0375d..d2d883f3a1 100755 --- a/t/t5615-alternate-env.sh +++ b/t/t5615-alternate-env.sh @@ -77,6 +77,7 @@ test_expect_success 'mix of quoted and unquoted alternates' ' check_obj "$quoted:$unquoted" <<-EOF $one blob $two blob + EOF ' test_expect_success !MINGW 'broken quoting falls back to interpreting raw' ' diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index 1408b608eb..2959745196 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -99,6 +99,44 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' ' test_cmp actual.named expect ' +test_expect_success 'name-rev multiple --refs combine inclusive' ' + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \ + <actual >actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +<tags/F +EOF + +test_expect_success 'name-rev --refs excludes non-matched patterns' ' + git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/F" \ + <actual >actual.named && + test_cmp actual.named expect +' + +cat >expect <<EOF +<tags/F +EOF + +test_expect_success 'name-rev --exclude excludes matched patterns' ' + git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \ + <actual >actual.named && + test_cmp actual.named expect +' + +test_expect_success 'name-rev --no-refs clears the refs list' ' + git rev-list --left-right --cherry-pick F...E -- bar >expect && + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ + <expect >actual && + test_cmp actual expect +' + cat >expect <<EOF +tags/F =tags/D diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 3d5c238c81..97a07655a0 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -44,7 +44,7 @@ b1 [ahead 1, behind 1] d b2 [ahead 1, behind 1] d b3 [behind 1] b b4 [ahead 2] f -b5 g +b5 [gone] g b6 c EOF diff --git a/t/t6045-merge-rename-delete.sh b/t/t6045-merge-rename-delete.sh new file mode 100755 index 0000000000..5d33577d2f --- /dev/null +++ b/t/t6045-merge-rename-delete.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='Merge-recursive rename/delete conflict message' +. ./test-lib.sh + +test_expect_success 'rename/delete' ' + echo foo >A && + git add A && + git commit -m "initial" && + + git checkout -b rename && + git mv A B && + git commit -m "rename" && + + git checkout master && + git rm A && + git commit -m "delete" && + + test_must_fail git merge --strategy=recursive rename >output && + test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output +' + +test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 85f269411c..16952e44fc 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -182,6 +182,10 @@ check_describe "test2-lightweight-*" --tags --match="test2-*" check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ +check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^ + +check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^ + test_expect_success 'name-rev with exact tags' ' echo A >expect && tag_object=$(git rev-parse refs/tags/A) && @@ -206,4 +210,47 @@ test_expect_success 'describe --contains with the exact tags' ' test_cmp expect actual ' +test_expect_success 'describe --contains and --match' ' + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + test_must_fail git describe --contains --match="B" $tagged_commit && + git describe --contains --match="B" --match="A" $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'describe --exclude' ' + echo "c~1" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + test_must_fail git describe --contains --match="B" $tagged_commit && + git describe --contains --match="?" --exclude="A" $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'describe --contains and --no-match' ' + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git describe --contains --match="B" --no-match $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'setup and absorb a submodule' ' + test_create_repo sub1 && + test_commit -C sub1 initial && + git submodule add ./sub1 && + git submodule absorbgitdirs && + git commit -a -m "add submodule" && + git describe --dirty >expect && + git describe --broken >out && + test_cmp expect out +' + +test_expect_success 'describe chokes on severly broken submodules' ' + mv .git/modules/sub1/ .git/modules/sub_moved && + test_must_fail git describe --dirty +' +test_expect_success 'describe ignoring a borken submodule' ' + git describe --broken >out && + grep broken out +' + test_done diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index d51595cf6b..9dd5cde5fc 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -25,8 +25,10 @@ EOF test_cmp expect actual ' -test_expect_success 'exclude only should error out' ' - test_must_fail git log --oneline --format=%s -- ":(exclude)sub" +test_expect_success 'exclude only no longer errors out' ' + git log --oneline --format=%s -- . ":(exclude)sub" >expect && + git log --oneline --format=%s -- ":(exclude)sub" >actual && + test_cmp expect actual ' test_expect_success 't_e_i() exclude sub' ' diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh new file mode 100755 index 0000000000..77b8cef661 --- /dev/null +++ b/t/t6135-pathspec-with-attrs.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +test_description='test labels in pathspecs' +. ./test-lib.sh + +test_expect_success 'setup a tree' ' + cat <<-\EOF >expect && + fileA + fileAB + fileAC + fileB + fileBC + fileC + fileNoLabel + fileSetLabel + fileUnsetLabel + fileValue + fileWrongLabel + sub/fileA + sub/fileAB + sub/fileAC + sub/fileB + sub/fileBC + sub/fileC + sub/fileNoLabel + sub/fileSetLabel + sub/fileUnsetLabel + sub/fileValue + sub/fileWrongLabel + EOF + mkdir sub && + while read path + do + : >$path && + git add $path || return 1 + done <expect && + git commit -m "initial commit" && + git ls-files >actual && + test_cmp expect actual +' + +test_expect_success 'pathspec with no attr' ' + test_must_fail git ls-files ":(attr:)" +' + +test_expect_success 'pathspec with labels and non existent .gitattributes' ' + git ls-files ":(attr:label)" >actual && + test_must_be_empty actual +' + +test_expect_success 'setup .gitattributes' ' + cat <<-\EOF >.gitattributes && + fileA labelA + fileB labelB + fileC labelC + fileAB labelA labelB + fileAC labelA labelC + fileBC labelB labelC + fileUnsetLabel -label + fileSetLabel label + fileValue label=foo + fileWrongLabel label☺ + EOF + git add .gitattributes && + git commit -m "add attributes" +' + +test_expect_success 'check specific set attr' ' + cat <<-\EOF >expect && + fileSetLabel + sub/fileSetLabel + EOF + git ls-files ":(attr:label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific unset attr' ' + cat <<-\EOF >expect && + fileUnsetLabel + sub/fileUnsetLabel + EOF + git ls-files ":(attr:-label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific value attr' ' + cat <<-\EOF >expect && + fileValue + sub/fileValue + EOF + git ls-files ":(attr:label=foo)" >actual && + test_cmp expect actual && + git ls-files ":(attr:label=bar)" >actual && + test_must_be_empty actual +' + +test_expect_success 'check unspecified attr' ' + cat <<-\EOF >expect && + .gitattributes + fileA + fileAB + fileAC + fileB + fileBC + fileC + fileNoLabel + fileWrongLabel + sub/fileA + sub/fileAB + sub/fileAC + sub/fileB + sub/fileBC + sub/fileC + sub/fileNoLabel + sub/fileWrongLabel + EOF + git ls-files ":(attr:!label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check multiple unspecified attr' ' + cat <<-\EOF >expect && + .gitattributes + fileC + fileNoLabel + fileWrongLabel + sub/fileC + sub/fileNoLabel + sub/fileWrongLabel + EOF + git ls-files ":(attr:!labelB !labelA !label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check label with more labels but excluded path' ' + cat <<-\EOF >expect && + fileAB + fileB + fileBC + EOF + git ls-files ":(attr:labelB)" ":(exclude)sub/" >actual && + test_cmp expect actual +' + +test_expect_success 'check label excluding other labels' ' + cat <<-\EOF >expect && + fileAB + fileB + fileBC + sub/fileAB + sub/fileB + EOF + git ls-files ":(attr:labelB)" ":(exclude,attr:labelC)sub/" >actual && + test_cmp expect actual +' + +test_expect_success 'fail on multiple attr specifiers in one pathspec item' ' + test_must_fail git ls-files . ":(attr:labelB,attr:labelC)" 2>actual && + test_i18ngrep "Only one" actual +' + +test_expect_success 'fail if attr magic is used places not implemented' ' + # The main purpose of this test is to check that we actually fail + # when you attempt to use attr magic in commands that do not implement + # attr magic. This test does not advocate git-add to stay that way, + # though, but git-add is convenient as it has its own internal pathspec + # parsing. + test_must_fail git add ":(attr:labelB)" 2>actual && + test_i18ngrep "unsupported magic" actual +' + +test_expect_success 'abort on giving invalid label on the command line' ' + test_must_fail git ls-files . ":(attr:☺)" +' + +test_expect_success 'abort on asking for wrong magic' ' + test_must_fail git ls-files . ":(attr:-label=foo)" && + test_must_fail git ls-files . ":(attr:!label=foo)" +' + +test_expect_success 'check attribute list' ' + cat <<-EOF >>.gitattributes && + * whitespace=indent,trail,space + EOF + git ls-files ":(attr:whitespace=indent\,trail\,space)" >actual && + git ls-files >expect && + test_cmp expect actual +' + +test_expect_success 'backslash cannot be the last character' ' + test_must_fail git ls-files ":(attr:label=foo\\ labelA=bar)" 2>actual && + test_i18ngrep "not allowed as last character in attr value" actual +' + +test_expect_success 'backslash cannot be used as a value' ' + test_must_fail git ls-files ":(attr:label=f\\\oo)" 2>actual && + test_i18ngrep "for value matching" actual +' + +test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index a468041c50..834a9ed168 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -38,6 +38,7 @@ test_atom() { case "$1" in head) ref=refs/heads/master ;; tag) ref=refs/tags/testtag ;; + sym) ref=refs/heads/sym ;; *) ref=$1 ;; esac printf '%s\n' "$3" >expected @@ -50,16 +51,40 @@ test_atom() { test_atom head refname refs/heads/master test_atom head refname:short master +test_atom head refname:lstrip=1 heads/master +test_atom head refname:lstrip=2 master +test_atom head refname:lstrip=-1 master +test_atom head refname:lstrip=-2 heads/master +test_atom head refname:rstrip=1 refs/heads +test_atom head refname:rstrip=2 refs +test_atom head refname:rstrip=-1 refs +test_atom head refname:rstrip=-2 refs/heads test_atom head refname:strip=1 heads/master test_atom head refname:strip=2 master +test_atom head refname:strip=-1 master +test_atom head refname:strip=-2 heads/master test_atom head upstream refs/remotes/origin/master test_atom head upstream:short origin/master +test_atom head upstream:lstrip=2 origin/master +test_atom head upstream:lstrip=-2 origin/master +test_atom head upstream:rstrip=2 refs/remotes +test_atom head upstream:rstrip=-2 refs/remotes +test_atom head upstream:strip=2 origin/master +test_atom head upstream:strip=-2 origin/master test_atom head push refs/remotes/myfork/master test_atom head push:short myfork/master +test_atom head push:lstrip=1 remotes/myfork/master +test_atom head push:lstrip=-1 master +test_atom head push:rstrip=1 refs/remotes/myfork +test_atom head push:rstrip=-1 refs +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 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) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master) test_atom head tree $(git rev-parse refs/heads/master^{tree}) test_atom head parent '' test_atom head numparent 0 @@ -99,6 +124,8 @@ test_atom tag objecttype tag test_atom tag objectsize 154 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) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master) test_atom tag tree '' test_atom tag parent '' test_atom tag numparent '' @@ -134,16 +161,6 @@ test_expect_success 'Check invalid atoms names are errors' ' test_must_fail git for-each-ref --format="%(INVALID)" refs/heads ' -test_expect_success 'arguments to :strip must be positive integers' ' - test_must_fail git for-each-ref --format="%(refname:strip=0)" && - test_must_fail git for-each-ref --format="%(refname:strip=-1)" && - test_must_fail git for-each-ref --format="%(refname:strip=foo)" -' - -test_expect_success 'stripping refnames too far gives an error' ' - test_must_fail git for-each-ref --format="%(refname:strip=3)" -' - test_expect_success 'Check format specifiers are ignored in naming date atoms' ' git for-each-ref --format="%(authordate)" refs/heads && git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && @@ -164,6 +181,12 @@ test_expect_success 'Check invalid format specifiers are errors' ' test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads ' +test_expect_success 'arguments to %(objectname:short=) must be positive integers' ' + test_must_fail git for-each-ref --format="%(objectname:short=0)" && + test_must_fail git for-each-ref --format="%(objectname:short=-1)" && + test_must_fail git for-each-ref --format="%(objectname:short=foo)" +' + test_date () { f=$1 && committer_date=$2 && @@ -362,6 +385,8 @@ test_expect_success 'setup for upstream:track[short]' ' test_atom head upstream:track '[ahead 1]' test_atom head upstream:trackshort '>' +test_atom head upstream:track,nobracket 'ahead 1' +test_atom head upstream:nobracket,track 'ahead 1' test_atom head push:track '[ahead 1]' test_atom head push:trackshort '>' @@ -372,7 +397,7 @@ test_expect_success 'Check that :track[short] cannot be used with other atoms' ' test_expect_success 'Check that :track[short] works when upstream is invalid' ' cat >expected <<-\EOF && - + [gone] EOF test_when_finished "git config branch.master.merge refs/heads/master" && @@ -554,6 +579,7 @@ test_expect_success 'Verify sort with multiple keys' ' test_cmp expected actual ' + test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' test_when_finished "git checkout master" && git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && @@ -588,4 +614,52 @@ test_expect_success 'basic atom: head contents:trailers' ' test_cmp expect actual.clean ' +test_expect_success 'Add symbolic ref for the following tests' ' + git symbolic-ref refs/heads/sym refs/heads/master +' + +cat >expected <<EOF +refs/heads/master +EOF + +test_expect_success 'Verify usage of %(symref) atom' ' + git for-each-ref --format="%(symref)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +heads/master +EOF + +test_expect_success 'Verify usage of %(symref:short) atom' ' + git for-each-ref --format="%(symref:short)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +master +heads/master +EOF + +test_expect_success 'Verify usage of %(symref:lstrip) atom' ' + git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual && + + git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + +cat >expected <<EOF +refs +refs/heads +EOF + +test_expect_success 'Verify usage of %(symref:rstrip) atom' ' + git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + test_done diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index d0ab09f4bd..a09a1a46ef 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -327,4 +327,98 @@ test_expect_success 'reverse version sort' ' test_cmp expect actual ' +test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' ' + test_must_fail git for-each-ref --format="%(if)" && + test_must_fail git for-each-ref --format="%(then) %(end)" && + test_must_fail git for-each-ref --format="%(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" && + test_must_fail git for-each-ref --format="%(then) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(else)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(end)" +' + +test_expect_success 'check %(if)...%(then)...%(end) atoms' ' + git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual && + cat >expect <<-\EOF && + refs/heads/master Author: A U Thor + refs/heads/side Author: A U Thor + refs/odd/spot Author: A U Thor + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag + refs/tags/foo1.10 Author: A U Thor + refs/tags/foo1.3 Author: A U Thor + refs/tags/foo1.6 Author: A U Thor + refs/tags/four Author: A U Thor + refs/tags/one Author: A U Thor + refs/tags/signed-tag + refs/tags/three Author: A U Thor + refs/tags/two Author: A U Thor + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' ' + git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual && + cat >expect <<-\EOF && + A U Thor: refs/heads/master + A U Thor: refs/heads/side + A U Thor: refs/odd/spot + No author: refs/tags/annotated-tag + No author: refs/tags/doubly-annotated-tag + No author: refs/tags/doubly-signed-tag + A U Thor: refs/tags/foo1.10 + A U Thor: refs/tags/foo1.3 + A U Thor: refs/tags/foo1.6 + A U Thor: refs/tags/four + A U Thor: refs/tags/one + No author: refs/tags/signed-tag + A U Thor: refs/tags/three + A U Thor: refs/tags/two + EOF + test_cmp expect actual +' +test_expect_success 'ignore spaces in %(if) atom usage' ' + git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual && + cat >expect <<-\EOF && + master: Head ref + side: Not Head ref + odd/spot: Not Head ref + annotated-tag: Not Head ref + doubly-annotated-tag: Not Head ref + doubly-signed-tag: Not Head ref + foo1.10: Not Head ref + foo1.3: Not Head ref + foo1.6: Not Head ref + four: Not Head ref + one: Not Head ref + signed-tag: Not Head ref + three: Not Head ref + two: Not Head ref + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if:equals=<string>)' ' + git for-each-ref --format="%(if:equals=master)%(refname:short)%(then)Found master%(else)Not master%(end)" refs/heads/ >actual && + cat >expect <<-\EOF && + Found master + Not master + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if:notequals=<string>)' ' + git for-each-ref --format="%(if:notequals=master)%(refname:short)%(then)Not master%(else)Found master%(end)" refs/heads/ >actual && + cat >expect <<-\EOF && + Found master + Not master + EOF + test_cmp expect actual +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index cb8fbd8e5e..7cb60799be 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -313,6 +313,27 @@ test_expect_success 'Tag name filtering allows slashes in tag names' ' git cat-file tag X/2 > actual && test_cmp expect actual ' +test_expect_success 'setup --prune-empty comparisons' ' + git checkout --orphan master-no-a && + git rm -rf . && + unset test_tick && + test_tick && + GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" && + test_commit --notick B B.t B Bx && + git checkout -b branch-no-a Bx && + test_commit D D.t D Dx && + mkdir dir && + test_commit dir/D dir/D.t dir/D dir/Dx && + test_commit E E.t E Ex && + git checkout master-no-a && + test_commit C C.t C Cx && + git checkout branch-no-a && + git merge Cx -m "Merge tag '\''C'\'' into branch" && + git tag Fx && + test_commit G G.t G Gx && + test_commit H H.t H Hx && + git checkout branch +' test_expect_success 'Prune empty commits' ' git rev-list HEAD > expect && @@ -341,6 +362,22 @@ test_expect_success 'prune empty works even without index/tree filters' ' test_cmp expect actual ' +test_expect_success '--prune-empty is able to prune root commit' ' + git rev-list branch-no-a >expect && + git branch testing H && + git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t" testing && + git rev-list testing >actual && + git branch -D testing && + test_cmp expect actual +' + +test_expect_success '--prune-empty is able to prune entire branch' ' + git branch prune-entire B && + git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire && + test_path_is_missing .git/refs/heads/prune-entire && + test_must_fail git reflog exists refs/heads/prune-entire +' + test_expect_success '--remap-to-ancestor with filename filters' ' git checkout master && git reset --hard A && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 072e6c6b88..772dc9ed96 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -81,9 +81,25 @@ test_expect_success 'creating a tag using default HEAD should succeed' ' ' test_expect_success 'creating a tag with --create-reflog should create reflog' ' + git log -1 \ + --format="format:tag: tagging %h (%s, %cd)%n" \ + --date=format:%Y-%m-%d >expected && test_when_finished "git tag -d tag_with_reflog" && git tag --create-reflog tag_with_reflog && - git reflog exists refs/tags/tag_with_reflog + git reflog exists refs/tags/tag_with_reflog && + sed -e "s/^.* //" .git/logs/refs/tags/tag_with_reflog >actual && + test_cmp expected actual +' + +test_expect_success 'annotated tag with --create-reflog has correct message' ' + git log -1 \ + --format="format:tag: tagging %h (%s, %cd)%n" \ + --date=format:%Y-%m-%d >expected && + test_when_finished "git tag -d tag_with_reflog" && + git tag -m "annotated tag" --create-reflog tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog && + sed -e "s/^.* //" .git/logs/refs/tags/tag_with_reflog >actual && + test_cmp expected actual ' test_expect_success '--create-reflog does not create reflog on failure' ' @@ -879,18 +895,16 @@ test_expect_success GPG 'verifying a forged tag should fail' ' test_must_fail git tag -v forged-tag ' -test_expect_success 'verifying a proper tag with --format pass and format accordingly' ' - cat >expect <<-\EOF +test_expect_success GPG 'verifying a proper tag with --format pass and format accordingly' ' + cat >expect <<-\EOF && tagname : signed-tag - EOF && + EOF git tag -v --format="tagname : %(tag)" "signed-tag" >actual && test_cmp expect actual ' -test_expect_success 'verifying a forged tag with --format fail and format accordingly' ' - cat >expect <<-\EOF - tagname : forged-tag - EOF && +test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' + >expect && test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual && test_cmp expect actual ' diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index c8dc665f2f..4f3794d415 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -360,27 +360,37 @@ test_pager_choices 'git aliasedlog' test_default_pager expect_success 'git -p aliasedlog' test_PAGER_overrides expect_success 'git -p aliasedlog' test_core_pager_overrides expect_success 'git -p aliasedlog' -test_core_pager_subdir expect_failure 'git -p aliasedlog' +test_core_pager_subdir expect_success 'git -p aliasedlog' test_GIT_PAGER_overrides expect_success 'git -p aliasedlog' test_default_pager expect_success 'git -p true' test_PAGER_overrides expect_success 'git -p true' test_core_pager_overrides expect_success 'git -p true' -test_core_pager_subdir expect_failure 'git -p true' +test_core_pager_subdir expect_success 'git -p true' test_GIT_PAGER_overrides expect_success 'git -p true' test_default_pager expect_success test_must_fail 'git -p request-pull' test_PAGER_overrides expect_success test_must_fail 'git -p request-pull' test_core_pager_overrides expect_success test_must_fail 'git -p request-pull' -test_core_pager_subdir expect_failure test_must_fail 'git -p request-pull' +test_core_pager_subdir expect_success test_must_fail 'git -p request-pull' test_GIT_PAGER_overrides expect_success test_must_fail 'git -p request-pull' test_default_pager expect_success test_must_fail 'git -p' test_PAGER_overrides expect_success test_must_fail 'git -p' test_local_config_ignored expect_failure test_must_fail 'git -p' -test_no_local_config_subdir expect_success test_must_fail 'git -p' test_GIT_PAGER_overrides expect_success test_must_fail 'git -p' +test_expect_success TTY 'core.pager in repo config works and retains cwd' ' + sane_unset GIT_PAGER && + test_config core.pager "cat >cwd-retained" && + ( + cd sub && + rm -f cwd-retained && + test_terminal git -p rev-parse HEAD && + test_path_is_file cwd-retained + ) +' + test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense' test_pager_choices 'git shortlog' diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index d62ccbb98e..b4b49eeb08 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -125,18 +125,16 @@ test_expect_success GPG 'verify multiple tags' ' test_cmp expect.stderr actual.stderr ' -test_expect_success 'verifying tag with --format' ' - cat >expect <<-\EOF +test_expect_success GPG 'verifying tag with --format' ' + cat >expect <<-\EOF && tagname : fourth-signed - EOF && + EOF git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual && test_cmp expect actual ' -test_expect_success 'verifying a forged tag with --format fail and format accordingly' ' - cat >expect <<-\EOF - tagname : 7th forged-signed - EOF && +test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' + >expect && test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && test_cmp expect actual-forged ' diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 347857fa7c..4ac386d98b 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -442,11 +442,11 @@ test_expect_success 'submodule update - command in .git/config catches failure - ' test_expect_success 'submodule update - command run for initial population of submodule' ' - cat <<-\ EOF >expect + cat >expect <<-EOF && Execution of '\''false $submodulesha1'\'' failed in submodule path '\''submodule'\'' - EOF && + EOF rm -rf super/submodule && - test_must_fail git -C super submodule update >../actual && + test_must_fail git -C super submodule update 2>actual && test_cmp expect actual && git -C super submodule update --checkout ' diff --git a/t/t7518-ident-corner-cases.sh b/t/t7518-ident-corner-cases.sh new file mode 100755 index 0000000000..b22f631261 --- /dev/null +++ b/t/t7518-ident-corner-cases.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='corner cases in ident strings' +. ./test-lib.sh + +# confirm that we do not segfault _and_ that we do not say "(null)", as +# glibc systems will quietly handle our NULL pointer +# +# Note also that we can't use "env" here because we need to unset a variable, +# and "-u" is not portable. +test_expect_success 'empty name and missing email' ' + ( + sane_unset GIT_AUTHOR_EMAIL && + GIT_AUTHOR_NAME= && + test_must_fail git commit --allow-empty -m foo 2>err && + test_i18ngrep ! null err + ) +' + +test_expect_success 'commit rejects all-crud name' ' + test_must_fail env GIT_AUTHOR_NAME=" .;<>" \ + git commit --allow-empty -m foo +' + +# We must test the actual error message here, as an unwanted +# auto-detection could fail for other reasons. +test_expect_success 'empty configured name does not auto-detect' ' + ( + sane_unset GIT_AUTHOR_NAME && + test_must_fail \ + git -c user.name= commit --allow-empty -m foo 2>err && + test_i18ngrep "empty ident name" err + ) +' + +test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 25241f4096..0e7f30db2d 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -428,7 +428,7 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' git difftool --dir-diff $symlinks --extcmd ls branch >output && # "sub" must only exist in "right" # "file" and "file2" must be listed in both "left" and "right" - grep sub output > sub-output && + grep sub output >sub-output && test_line_count = 1 sub-output && grep file"$" output >file-output && test_line_count = 2 file-output && @@ -591,6 +591,7 @@ test_expect_success 'difftool --no-symlinks detects conflict ' ' ' test_expect_success 'difftool properly honors gitlink and core.worktree' ' + test_when_finished rm -rf submod/ule && git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -600,11 +601,13 @@ test_expect_success 'difftool properly honors gitlink and core.worktree' ' cd submod/ule && echo good >expect && git difftool --tool=checktrees --dir-diff HEAD~ >actual && - test_cmp expect actual + test_cmp expect actual && + rm -f expect actual ) ' test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' + test_when_finished git reset --hard && git init dirlinks && ( cd dirlinks && @@ -623,4 +626,64 @@ test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' ) ' +test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' ' + test_when_finished git reset --hard && + touch b && + ln -s b c && + git add b c && + test_tick && + git commit -m initial && + touch d && + rm c && + ln -s d c && + cat >expect <<-EOF && + b + c + + c + EOF + git difftool --symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + # The left side contains symlink "c" that points to "b" + test_config difftool.cat.cmd "cat \$LOCAL/c" && + printf "%s\n" b >expect && + + git difftool --symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + git difftool --symlinks --no-symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + # The right side contains symlink "c" that points to "d" + test_config difftool.cat.cmd "cat \$REMOTE/c" && + printf "%s\n" d >expect && + + git difftool --symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + # Deleted symlinks + rm -f c && + cat >expect <<-EOF && + b + c + + EOF + git difftool --symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 19f0108f8a..cee42097b0 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -982,6 +982,72 @@ test_expect_success 'grep -e -- -- path' ' test_cmp expected actual ' +test_expect_success 'dashdash disambiguates rev as rev' ' + test_when_finished "rm -f master" && + echo content >master && + echo master:hello.c >expect && + git grep -l o master -- hello.c >actual && + test_cmp expect actual +' + +test_expect_success 'dashdash disambiguates pathspec as pathspec' ' + test_when_finished "git rm -f master" && + echo content >master && + git add master && + echo master:content >expect && + git grep o -- master >actual && + test_cmp expect actual +' + +test_expect_success 'report bogus arg without dashdash' ' + test_must_fail git grep o does-not-exist +' + +test_expect_success 'report bogus rev with dashdash' ' + test_must_fail git grep o hello.c -- +' + +test_expect_success 'allow non-existent path with dashdash' ' + # We need a real match so grep exits with success. + tree=$(git ls-tree HEAD | + sed s/hello.c/not-in-working-tree/ | + git mktree) && + git grep o "$tree" -- not-in-working-tree +' + +test_expect_success 'grep --no-index pattern -- path' ' + rm -fr non && + mkdir -p non/git && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + echo hello >hello && + echo goodbye >goodbye && + echo hello:hello >expect && + git grep --no-index o -- hello >actual && + test_cmp expect actual + ) +' + +test_expect_success 'grep --no-index complains of revs' ' + test_must_fail git grep --no-index o master -- 2>err && + test_i18ngrep "cannot be used with revs" err +' + +test_expect_success 'grep --no-index prefers paths to revs' ' + test_when_finished "rm -f master" && + echo content >master && + echo master:content >expect && + git grep --no-index o master >actual && + test_cmp expect actual +' + +test_expect_success 'grep --no-index does not "diagnose" revs' ' + test_must_fail git grep --no-index o :1:hello.c 2>err && + test_i18ngrep ! -i "did you mean" err +' + cat >expected <<EOF hello.c:int main(int argc, const char **argv) hello.c: printf("Hello world.\n"); diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index bb879a527d..1319415ba8 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -18,6 +18,11 @@ then test_done fi +if ! test_have_prereq NOT_ROOT; then + skip_all='When cvs is compiled with CVS_BADROOT commits as root fail' + test_done +fi + CVSROOT=$PWD/tmpcvsroot CVSWORK=$PWD/cvswork GIT_DIR=$PWD/.git diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 4c384ff023..804ce3850f 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -3,6 +3,11 @@ test_description='git cvsimport basic tests' . ./lib-cvs.sh +if ! test_have_prereq NOT_ROOT; then + skip_all='When cvs is compiled with CVS_BADROOT commits as root fail' + test_done +fi + test_expect_success PERL 'setup cvsroot environment' ' CVSROOT=$(pwd)/cvsroot && export CVSROOT diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index a34e55f874..d711bef240 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -98,7 +98,7 @@ test_gitcomp () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp "$@" && print_comp && @@ -113,7 +113,7 @@ test_gitcomp_nl () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp_nl "$@" && print_comp && @@ -124,140 +124,280 @@ invalid_variable_name='${foo.bar}' actual="$TRASH_DIRECTORY/actual" -test_expect_success 'setup for __gitdir tests' ' +if test_have_prereq MINGW +then + ROOT="$(pwd -W)" +else + ROOT="$(pwd)" +fi + +test_expect_success 'setup for __git_find_repo_path/__gitdir tests' ' mkdir -p subdir/subsubdir && + mkdir -p non-repo && git init otherrepo ' -test_expect_success '__gitdir - from command line (through $__git_dir)' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' ' + echo "$ROOT/otherrepo/.git" >expected && ( - __git_dir="$TRASH_DIRECTORY/otherrepo/.git" && - __gitdir >"$actual" + __git_dir="$ROOT/otherrepo/.git" && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - repo as argument' ' - echo "otherrepo/.git" >expected && - __gitdir "otherrepo" >"$actual" && - test_cmp expected "$actual" -' - -test_expect_success '__gitdir - remote as argument' ' - echo "remote" >expected && - __gitdir "remote" >"$actual" && - test_cmp expected "$actual" -' - -test_expect_success '__gitdir - .git directory in cwd' ' +test_expect_success '__git_find_repo_path - .git directory in cwd' ' echo ".git" >expected && - __gitdir >"$actual" && + ( + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - .git directory in parent' ' - echo "$(pwd -P)/.git" >expected && +test_expect_success '__git_find_repo_path - .git directory in parent' ' + echo "$ROOT/.git" >expected && ( cd subdir/subsubdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - cwd is a .git directory' ' +test_expect_success '__git_find_repo_path - cwd is a .git directory' ' echo "." >expected && ( cd .git && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - parent is a .git directory' ' - echo "$(pwd -P)/.git" >expected && +test_expect_success '__git_find_repo_path - parent is a .git directory' ' + echo "$ROOT/.git" >expected && ( cd .git/refs/heads && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + GIT_DIR="$ROOT/otherrepo/.git" && export GIT_DIR && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in parent' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + GIT_DIR="$ROOT/otherrepo/.git" && export GIT_DIR && cd subdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - from command line while "git -C"' ' + echo "$ROOT/.git" >expected && + ( + __git_dir="$ROOT/.git" && + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - non-existing $GIT_DIR' ' +test_expect_success '__git_find_repo_path - relative dir from command line and "git -C"' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/non-existing" && + cd subdir && + __git_dir="otherrepo/.git" && + __git_C_args=(-C ..) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - $GIT_DIR set while "git -C"' ' + echo "$ROOT/.git" >expected && + ( + GIT_DIR="$ROOT/.git" && export GIT_DIR && - test_must_fail __gitdir - ) + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" ' -function pwd_P_W () { - if test_have_prereq MINGW - then - pwd -W - else - pwd -P - fi -} +test_expect_success '__git_find_repo_path - relative dir in $GIT_DIR and "git -C"' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd subdir && + GIT_DIR="otherrepo/.git" && + export GIT_DIR && + __git_C_args=(-C ..) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' -test_expect_success '__gitdir - gitfile in cwd' ' - echo "$(pwd_P_W)/otherrepo/.git" >expected && - echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && +test_expect_success '__git_find_repo_path - "git -C" while .git directory in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - "git -C" while cwd is a .git directory' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd .git && + __git_C_args=(-C .. -C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - "git -C" while .git directory in parent' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd subdir && + __git_C_args=(-C .. -C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing path in "git -C"' ' + ( + __git_C_args=(-C non-existing) && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing path in $__git_dir' ' + ( + __git_dir="non-existing" && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing $GIT_DIR' ' + ( + GIT_DIR="$ROOT/non-existing" && + export GIT_DIR && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - gitfile in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && + echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - gitfile in parent' ' - echo "$(pwd_P_W)/otherrepo/.git" >expected && - echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && +test_expect_success '__git_find_repo_path - gitfile in parent' ' + echo "$ROOT/otherrepo/.git" >expected && + echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir/subsubdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' ' - echo "$(pwd -P)/otherrepo/.git" >expected && +test_expect_success SYMLINKS '__git_find_repo_path - resulting path avoids symlinks' ' + echo "$ROOT/otherrepo/.git" >expected && mkdir otherrepo/dir && test_when_finished "rm -rf otherrepo/dir" && ln -s otherrepo/dir link && test_when_finished "rm -f link" && ( cd link && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - not a git repository' ' + ( + cd non-repo && + GIT_CEILING_DIRECTORIES="$ROOT" && + export GIT_CEILING_DIRECTORIES && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__gitdir - finds repo' ' + echo "$ROOT/.git" >expected && + ( + cd subdir/subsubdir && __gitdir >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - not a git repository' ' - nongit test_must_fail __gitdir + +test_expect_success '__gitdir - returns error when cant find repo' ' + ( + __git_dir="non-existing" && + test_must_fail __gitdir >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__gitdir - repo as argument' ' + echo "otherrepo/.git" >expected && + ( + __gitdir "otherrepo" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - remote as argument' ' + echo "remote" >expected && + ( + __gitdir "remote" >"$actual" + ) && + test_cmp expected "$actual" ' test_expect_success '__gitcomp - trailing space - options' ' @@ -361,10 +501,286 @@ test_expect_success '__git_remotes - list remotes from $GIT_DIR/remotes and from git remote add remote_in_config_1 git://remote_1 && test_when_finished "git remote remove remote_in_config_2" && git remote add remote_in_config_2 git://remote_2 && - __git_remotes >actual && + ( + __git_remotes >actual + ) && test_cmp expect actual ' +test_expect_success '__git_is_configured_remote' ' + test_when_finished "git remote remove remote_1" && + git remote add remote_1 git://remote_1 && + test_when_finished "git remote remove remote_2" && + git remote add remote_2 git://remote_2 && + ( + verbose __git_is_configured_remote remote_2 && + test_must_fail __git_is_configured_remote non-existent + ) +' + +test_expect_success 'setup for ref completion' ' + git commit --allow-empty -m initial && + git branch matching-branch && + git tag matching-tag && + ( + cd otherrepo && + git commit --allow-empty -m initial && + git branch -m master master-in-other && + git branch branch-in-other && + git tag tag-in-other + ) && + git remote add other "$ROOT/otherrepo/.git" && + git fetch --no-tags other && + rm -f .git/FETCH_HEAD && + git init thirdrepo +' + +test_expect_success '__git_refs - simple' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + other/branch-in-other + other/master-in-other + matching-tag + EOF + ( + cur= && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - full refs' ' + cat >expected <<-EOF && + refs/heads/master + refs/heads/matching-branch + EOF + ( + cur=refs/heads/ && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - repo given on the command line' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + __git_dir="$ROOT/otherrepo/.git" && + cur= && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - remote on local file system' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + cur= && + __git_refs otherrepo >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - remote on local file system - full refs' ' + cat >expected <<-EOF && + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs otherrepo >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + ( + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - full refs' ' + cat >expected <<-EOF && + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - repo given on the command line' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + ( + cd thirdrepo && + __git_dir="$ROOT/.git" && + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - full refs - repo given on the command line' ' + cat >expected <<-EOF && + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cd thirdrepo && + __git_dir="$ROOT/.git" && + cur=refs/ && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - remote name matches a directory' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + mkdir other && + test_when_finished "rm -rf other" && + ( + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - URL remote' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + cur= && + __git_refs "file://$ROOT/otherrepo/.git" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - URL remote - full refs' ' + cat >expected <<-EOF && + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs "file://$ROOT/otherrepo/.git" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - non-existing remote' ' + ( + cur= && + __git_refs non-existing >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing remote - full refs' ' + ( + cur=refs/ && + __git_refs non-existing >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing URL remote' ' + ( + cur= && + __git_refs "file://$ROOT/non-existing" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing URL remote - full refs' ' + ( + cur=refs/ && + __git_refs "file://$ROOT/non-existing" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - not in a git repository' ' + ( + GIT_CEILING_DIRECTORIES="$ROOT" && + export GIT_CEILING_DIRECTORIES && + cd subdir && + cur= && + __git_refs >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - unique remote branches for git checkout DWIMery' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + other/ambiguous + other/branch-in-other + other/master-in-other + remote/ambiguous + remote/branch-in-remote + matching-tag + branch-in-other + branch-in-remote + master-in-other + EOF + for remote_ref in refs/remotes/other/ambiguous \ + refs/remotes/remote/ambiguous \ + refs/remotes/remote/branch-in-remote + do + git update-ref $remote_ref master && + test_when_finished "git update-ref -d $remote_ref" + done && + ( + cur= && + __git_refs "" 1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'teardown after ref completion' ' + git branch -d matching-branch && + git tag -d matching-tag && + git remote remove other +' + test_expect_success '__git_get_config_variables' ' cat >expect <<-EOF && name-1 @@ -475,7 +891,12 @@ test_expect_success 'general options plus command' ' test_completion "git --namespace=foo check" "checkout " && test_completion "git --paginate check" "checkout " && test_completion "git --info-path check" "checkout " && - test_completion "git --no-replace-objects check" "checkout " + test_completion "git --no-replace-objects check" "checkout " && + test_completion "git --git-dir some/path check" "checkout " && + test_completion "git -c conf.var=value check" "checkout " && + test_completion "git -C some/path check" "checkout " && + test_completion "git --work-tree some/path check" "checkout " && + test_completion "git --namespace name/space check" "checkout " ' test_expect_success 'git --help completion' ' @@ -483,10 +904,10 @@ test_expect_success 'git --help completion' ' test_completion "git --help core" "core-tutorial " ' -test_expect_success 'setup for ref completion' ' +test_expect_success 'setup for integration tests' ' echo content >file1 && echo more >file2 && - git add . && + git add file1 file2 && git commit -m one && git branch mybranch && git tag mytag @@ -500,6 +921,12 @@ test_expect_success 'checkout completes ref names' ' EOF ' +test_expect_success 'git -C <path> checkout uses the right repo' ' + test_completion "git -C subdir -C subsubdir -C .. -C ../otherrepo checkout b" <<-\EOF + branch-in-other Z + EOF +' + test_expect_success 'show completes all refs' ' test_completion "git show m" <<-\EOF master Z @@ -517,7 +944,7 @@ test_expect_success '<ref>: completes paths' ' test_expect_success 'complete tree filename with spaces' ' echo content >"name with spaces" && - git add . && + git add "name with spaces" && git commit -m spaces && test_completion "git show HEAD:nam" <<-\EOF name with spaces Z @@ -526,7 +953,7 @@ test_expect_success 'complete tree filename with spaces' ' test_expect_success 'complete tree filename with metacharacters' ' echo content >"name with \${meta}" && - git add . && + git add "name with \${meta}" && git commit -m meta && test_completion "git show HEAD:nam" <<-\EOF name with ${meta} Z diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index bd357704cc..5ee124332a 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -136,17 +136,12 @@ test_tick () { export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } -# Stop execution and start a shell. This is useful for debugging tests and -# only makes sense together with "-v". +# Stop execution and start a shell. This is useful for debugging tests. # # Be sure to remove all invocations of this command before submitting. test_pause () { - if test "$verbose" = t; then - "$SHELL_PATH" <&6 >&3 2>&4 - else - error >&5 "test_pause requires --verbose" - fi + "$SHELL_PATH" <&6 >&5 2>&7 } # Wrap git in gdb. Adding this to a command can make it easier to @@ -154,7 +149,7 @@ test_pause () { # # Example: "debug git checkout master". debug () { - GIT_TEST_GDB=1 "$@" + GIT_TEST_GDB=1 "$@" <&6 >&5 2>&7 } # Call test_commit with the arguments diff --git a/t/test-lib.sh b/t/test-lib.sh index 86d77c16dd..13b5696822 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -342,6 +342,7 @@ fi exec 5>&1 exec 6<&0 +exec 7>&2 if test "$verbose_log" = "t" then exec 3>>"$GIT_TEST_TEE_OUTPUT_FILE" 4>&3 @@ -624,9 +625,9 @@ test_run_ () { trace= # 117 is magic because it is unlikely to match the exit # code of other programs - test_eval_ "(exit 117) && $1" - if test "$?" != 117; then - error "bug in the test script: broken &&-chain: $1" + if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" + then + error "bug in the test script: broken &&-chain or run-away HERE-DOC: $1" fi trace=$trace_tmp fi diff --git a/transport-helper.c b/transport-helper.c index 1258d6aedd..dc90a1fb76 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -124,8 +124,9 @@ static struct child_process *get_helper(struct transport *transport) helper->git_cmd = 0; helper->silent_exec_failure = 1; - argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT, - get_git_dir()); + if (have_git_dir()) + argv_array_pushf(&helper->env_array, "%s=%s", + GIT_DIR_ENVIRONMENT, get_git_dir()); code = start_command(helper); if (code < 0 && errno == ENOENT) diff --git a/transport.c b/transport.c index b6c5652d6c..417ed7f19f 100644 --- a/transport.c +++ b/transport.c @@ -204,6 +204,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus static int fetch_refs_via_pack(struct transport *transport, int nr_heads, struct ref **to_fetch) { + int ret = 0; struct git_transport_data *data = transport->data; struct ref *refs; char *dest = xstrdup(transport->url); @@ -241,19 +242,22 @@ static int fetch_refs_via_pack(struct transport *transport, &transport->pack_lockfile); close(data->fd[0]); close(data->fd[1]); - if (finish_connect(data->conn)) { - free_refs(refs); - refs = NULL; - } + if (finish_connect(data->conn)) + ret = -1; data->conn = NULL; data->got_remote_heads = 0; data->options.self_contained_and_connected = args.self_contained_and_connected; + if (refs == NULL) + ret = -1; + if (report_unmatched_refs(to_fetch, nr_heads)) + ret = -1; + free_refs(refs_tmp); free_refs(refs); free(dest); - return (refs ? 0 : -1); + return ret; } static int push_had_errors(struct ref *ref) @@ -299,7 +303,7 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v if (verbose) fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); if (ref->deletion) { - delete_ref(rs.dst, NULL, 0); + delete_ref(NULL, rs.dst, NULL, 0); } else update_ref("update by push", rs.dst, ref->new_oid.hash, NULL, 0, 0); @@ -467,11 +471,11 @@ void transport_print_push_status(const char *dest, struct ref *refs, { struct ref *ref; int n = 0; - unsigned char head_sha1[20]; + struct object_id head_oid; char *head; int summary_width = transport_summary_width(refs); - head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) @@ -1206,6 +1210,42 @@ literal_copy: return xstrdup(url); } +static void read_alternate_refs(const char *path, + alternate_ref_fn *cb, + void *data) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf line = STRBUF_INIT; + FILE *fh; + + cmd.git_cmd = 1; + argv_array_pushf(&cmd.args, "--git-dir=%s", path); + argv_array_push(&cmd.args, "for-each-ref"); + argv_array_push(&cmd.args, "--format=%(objectname) %(refname)"); + cmd.env = local_repo_env; + cmd.out = -1; + + if (start_command(&cmd)) + return; + + fh = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&line, fh) != EOF) { + struct object_id oid; + + if (get_oid_hex(line.buf, &oid) || + line.buf[GIT_SHA1_HEXSZ] != ' ') { + warning("invalid line while parsing alternate refs: %s", + line.buf); + break; + } + + cb(line.buf + GIT_SHA1_HEXSZ + 1, &oid, data); + } + + fclose(fh); + finish_command(&cmd); +} + struct alternate_refs_data { alternate_ref_fn *fn; void *data; @@ -1214,34 +1254,26 @@ struct alternate_refs_data { static int refs_from_alternate_cb(struct alternate_object_database *e, void *data) { - char *other; - size_t len; - struct remote *remote; - struct transport *transport; - const struct ref *extra; + struct strbuf path = STRBUF_INIT; + size_t base_len; struct alternate_refs_data *cb = data; - other = real_pathdup(e->path, 1); - len = strlen(other); - - while (other[len-1] == '/') - other[--len] = '\0'; - if (len < 8 || memcmp(other + len - 8, "/objects", 8)) + if (!strbuf_realpath(&path, e->path, 0)) + goto out; + if (!strbuf_strip_suffix(&path, "/objects")) goto out; + base_len = path.len; + /* Is this a git repository with refs? */ - memcpy(other + len - 8, "/refs", 6); - if (!is_directory(other)) + strbuf_addstr(&path, "/refs"); + if (!is_directory(path.buf)) goto out; - other[len - 8] = '\0'; - remote = remote_get(other); - transport = transport_get(remote, other); - for (extra = transport_get_remote_refs(transport); - extra; - extra = extra->next) - cb->fn(extra, cb->data); - transport_disconnect(transport); + strbuf_setlen(&path, base_len); + + read_alternate_refs(path.buf, cb->fn, cb->data); + out: - free(other); + strbuf_release(&path); return 0; } diff --git a/transport.h b/transport.h index e597b31b38..bc5571574b 100644 --- a/transport.h +++ b/transport.h @@ -255,6 +255,6 @@ int transport_refs_pushed(struct ref *ref); void transport_print_push_status(const char *dest, struct ref *refs, int verbose, int porcelain, unsigned int *reject_reasons); -typedef void alternate_ref_fn(const struct ref *, void *); +typedef void alternate_ref_fn(const char *refname, const struct object_id *oid, void *); extern void for_each_alternate_ref(alternate_ref_fn, void *); #endif diff --git a/unpack-trees.c b/unpack-trees.c index 3a8ee19fe8..8333da2cc9 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -10,6 +10,8 @@ #include "attr.h" #include "split-index.h" #include "dir.h" +#include "submodule.h" +#include "submodule-config.h" /* * Error messages expected by scripts out of plumbing commands such as @@ -45,6 +47,9 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */ "Working tree file '%s' would be removed by sparse checkout update.", + + /* ERROR_WOULD_LOSE_SUBMODULE */ + "Submodule '%s' cannot checkout new HEAD.", }; #define ERRORMSG(o,type) \ @@ -161,6 +166,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, _("The following working tree files would be overwritten by sparse checkout update:\n%s"); msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] = _("The following working tree files would be removed by sparse checkout update:\n%s"); + msgs[ERROR_WOULD_LOSE_SUBMODULE] = + _("Submodule '%s' cannot checkout new HEAD"); opts->show_all_errors = 1; /* rejected paths may not have a static buffer */ @@ -240,12 +247,75 @@ static void display_error_msgs(struct unpack_trees_options *o) fprintf(stderr, _("Aborting\n")); } +static int check_submodule_move_head(const struct cache_entry *ce, + const char *old_id, + const char *new_id, + struct unpack_trees_options *o) +{ + const struct submodule *sub = submodule_from_ce(ce); + if (!sub) + return 0; + + switch (sub->update_strategy.type) { + case SM_UPDATE_UNSPECIFIED: + case SM_UPDATE_CHECKOUT: + if (submodule_move_head(ce->name, old_id, new_id, SUBMODULE_MOVE_HEAD_DRY_RUN)) + return o->gently ? -1 : + add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name); + return 0; + case SM_UPDATE_NONE: + return 0; + case SM_UPDATE_REBASE: + case SM_UPDATE_MERGE: + case SM_UPDATE_COMMAND: + default: + warning(_("submodule update strategy not supported for submodule '%s'"), ce->name); + return -1; + } +} + +static void reload_gitmodules_file(struct index_state *index, + struct checkout *state) +{ + int i; + for (i = 0; i < index->cache_nr; i++) { + struct cache_entry *ce = index->cache[i]; + if (ce->ce_flags & CE_UPDATE) { + int r = strcmp(ce->name, ".gitmodules"); + if (r < 0) + continue; + else if (r == 0) { + submodule_free(); + checkout_entry(ce, state, NULL); + gitmodules_config(); + git_config(submodule_config, NULL); + } else + break; + } + } +} + /* * Unlink the last component and schedule the leading directories for * removal, such that empty directories get removed. */ static void unlink_entry(const struct cache_entry *ce) { + const struct submodule *sub = submodule_from_ce(ce); + if (sub) { + switch (sub->update_strategy.type) { + case SM_UPDATE_UNSPECIFIED: + case SM_UPDATE_CHECKOUT: + case SM_UPDATE_REBASE: + case SM_UPDATE_MERGE: + submodule_move_head(ce->name, "HEAD", NULL, + SUBMODULE_MOVE_HEAD_FORCE); + break; + case SM_UPDATE_NONE: + case SM_UPDATE_COMMAND: + return; /* Do not touch the submodule. */ + } + } if (!check_leading_path(ce->name, ce_namelen(ce))) return; if (remove_or_warn(ce->ce_mode, ce->name)) @@ -301,6 +371,9 @@ static int check_updates(struct unpack_trees_options *o) remove_marked_cache_entries(index); remove_scheduled_dirs(); + if (should_update_submodules() && o->update && !o->dry_run) + reload_gitmodules_file(index, &state); + for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; @@ -1358,17 +1431,26 @@ static int verify_uptodate_1(const struct cache_entry *ce, if (!lstat(ce->name, &st)) { int flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE; unsigned changed = ie_match_stat(o->src_index, ce, &st, flags); + + if (submodule_from_ce(ce)) { + int r = check_submodule_move_head(ce, + "HEAD", oid_to_hex(&ce->oid), o); + if (r) + return o->gently ? -1 : + add_rejected_path(o, error_type, ce->name); + return 0; + } + if (!changed) return 0; /* - * NEEDSWORK: the current default policy is to allow - * submodule to be out of sync wrt the superproject - * index. This needs to be tightened later for - * submodules that are marked to be automatically - * checked out. + * Historic default policy was to allow submodule to be out + * of sync wrt the superproject index. If the submodule was + * not considered interesting above, we don't care here. */ if (S_ISGITLINK(ce->ce_mode)) return 0; + errno = 0; } if (errno == ENOENT) @@ -1407,11 +1489,16 @@ static void invalidate_ce_path(const struct cache_entry *ce, * Currently, git does not checkout subprojects during a superproject * checkout, so it is not going to overwrite anything. */ -static int verify_clean_submodule(const struct cache_entry *ce, +static int verify_clean_submodule(const char *old_sha1, + const struct cache_entry *ce, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { - return 0; + if (!submodule_from_ce(ce)) + return 0; + + return check_submodule_move_head(ce, old_sha1, + oid_to_hex(&ce->oid), o); } static int verify_clean_subdirectory(const struct cache_entry *ce, @@ -1427,16 +1514,18 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, struct dir_struct d; char *pathbuf; int cnt = 0; - unsigned char sha1[20]; - if (S_ISGITLINK(ce->ce_mode) && - resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) { - /* If we are not going to update the submodule, then + if (S_ISGITLINK(ce->ce_mode)) { + unsigned char sha1[20]; + int sub_head = resolve_gitlink_ref(ce->name, "HEAD", sha1); + /* + * If we are not going to update the submodule, then * we don't care. */ - if (!hashcmp(sha1, ce->oid.hash)) + if (!sub_head && !hashcmp(sha1, ce->oid.hash)) return 0; - return verify_clean_submodule(ce, error_type, o); + return verify_clean_submodule(sub_head ? NULL : sha1_to_hex(sha1), + ce, error_type, o); } /* @@ -1575,9 +1664,15 @@ static int verify_absent_1(const struct cache_entry *ce, path = xmemdupz(ce->name, len); if (lstat(path, &st)) ret = error_errno("cannot stat '%s'", path); - else - ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL, - &st, error_type, o); + else { + if (submodule_from_ce(ce)) + ret = check_submodule_move_head(ce, + oid_to_hex(&ce->oid), + NULL, o); + else + ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL, + &st, error_type, o); + } free(path); return ret; } else if (lstat(ce->name, &st)) { @@ -1585,6 +1680,10 @@ static int verify_absent_1(const struct cache_entry *ce, return error_errno("cannot stat '%s'", ce->name); return 0; } else { + if (submodule_from_ce(ce)) + return check_submodule_move_head(ce, oid_to_hex(&ce->oid), + NULL, o); + return check_ok_to_remove(ce->name, ce_namelen(ce), ce_to_dtype(ce), ce, &st, error_type, o); @@ -1640,6 +1739,15 @@ static int merged_entry(const struct cache_entry *ce, return -1; } invalidate_ce_path(merge, o); + + if (submodule_from_ce(ce)) { + int ret = check_submodule_move_head(ce, NULL, + oid_to_hex(&ce->oid), + o); + if (ret) + return ret; + } + } else if (!(old->ce_flags & CE_CONFLICTED)) { /* * See if we can re-use the old CE directly? @@ -1660,6 +1768,14 @@ static int merged_entry(const struct cache_entry *ce, update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE); invalidate_ce_path(old, o); } + + if (submodule_from_ce(ce)) { + int ret = check_submodule_move_head(ce, oid_to_hex(&old->oid), + oid_to_hex(&ce->oid), + o); + if (ret) + return ret; + } } else { /* * Previously unmerged entry left as an existence diff --git a/unpack-trees.h b/unpack-trees.h index 36a73a6d00..6c48117b84 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -21,6 +21,7 @@ enum unpack_trees_error_types { ERROR_SPARSE_NOT_UPTODATE_FILE, ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN, ERROR_WOULD_LOSE_ORPHANED_REMOVED, + ERROR_WOULD_LOSE_SUBMODULE, NB_UNPACK_TREES_ERROR_TYPES }; diff --git a/urlmatch.c b/urlmatch.c index 132d342bc1..4bbde924e8 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -63,7 +63,50 @@ static int append_normalized_escapes(struct strbuf *buf, return 1; } -char *url_normalize(const char *url, struct url_info *out_info) +static const char *end_of_token(const char *s, int c, size_t n) +{ + const char *next = memchr(s, c, n); + if (!next) + next = s + n; + return next; +} + +static int match_host(const struct url_info *url_info, + const struct url_info *pattern_info) +{ + const char *url = url_info->url + url_info->host_off; + const char *pat = pattern_info->url + pattern_info->host_off; + int url_len = url_info->host_len; + int pat_len = pattern_info->host_len; + + while (url_len && pat_len) { + const char *url_next = end_of_token(url, '.', url_len); + const char *pat_next = end_of_token(pat, '.', pat_len); + + if (pat_next == pat + 1 && pat[0] == '*') + /* wildcard matches anything */ + ; + else if ((pat_next - pat) == (url_next - url) && + !memcmp(url, pat, url_next - url)) + /* the components are the same */ + ; + else + return 0; /* found an unmatch */ + + if (url_next < url + url_len) + url_next++; + url_len -= url_next - url; + url = url_next; + if (pat_next < pat + pat_len) + pat_next++; + pat_len -= pat_next - pat; + pat = pat_next; + } + + return (!url_len && !pat_len); +} + +static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs) { /* * Normalize NUL-terminated url using the following rules: @@ -104,7 +147,7 @@ char *url_normalize(const char *url, struct url_info *out_info) struct strbuf norm; size_t spanned; size_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0; - size_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len; + size_t host_off=0, host_len=0, port_off=0, port_len=0, path_off, path_len, result_len; const char *slash_ptr, *at_ptr, *colon_ptr, *path_start; char *result; @@ -191,7 +234,12 @@ char *url_normalize(const char *url, struct url_info *out_info) strbuf_release(&norm); return NULL; } - spanned = strspn(url, URL_HOST_CHARS); + + if (allow_globs) + spanned = strspn(url, URL_HOST_CHARS "*"); + else + spanned = strspn(url, URL_HOST_CHARS); + if (spanned < colon_ptr - url) { /* Host name has invalid characters */ if (out_info) { @@ -258,6 +306,7 @@ char *url_normalize(const char *url, struct url_info *out_info) return NULL; } strbuf_addch(&norm, ':'); + port_off = norm.len; strbuf_add(&norm, url, slash_ptr - url); port_len = slash_ptr - url; } @@ -265,7 +314,7 @@ char *url_normalize(const char *url, struct url_info *out_info) url = slash_ptr; } if (host_off) - host_len = norm.len - host_off; + host_len = norm.len - host_off - (port_len ? port_len + 1 : 0); /* @@ -373,6 +422,7 @@ char *url_normalize(const char *url, struct url_info *out_info) out_info->passwd_len = passwd_len; out_info->host_off = host_off; out_info->host_len = host_len; + out_info->port_off = port_off; out_info->port_len = port_len; out_info->path_off = path_off; out_info->path_len = path_len; @@ -380,6 +430,11 @@ char *url_normalize(const char *url, struct url_info *out_info) return result; } +char *url_normalize(const char *url, struct url_info *out_info) +{ + return url_normalize_1(url, out_info, 0); +} + static size_t url_match_prefix(const char *url, const char *url_prefix, size_t url_prefix_len) @@ -414,7 +469,7 @@ static size_t url_match_prefix(const char *url, static int match_urls(const struct url_info *url, const struct url_info *url_prefix, - int *exactusermatch) + struct urlmatch_item *match) { /* * url_prefix matches url if the scheme, host and port of url_prefix @@ -433,8 +488,8 @@ static int match_urls(const struct url_info *url, * contained a user name or false if url_prefix did not have a * user name. If there is no match *exactusermatch is left untouched. */ - int usermatched = 0; - int pathmatchlen; + char usermatched = 0; + size_t pathmatchlen; if (!url || !url_prefix || !url->url || !url_prefix->url) return 0; @@ -454,33 +509,53 @@ static int match_urls(const struct url_info *url, usermatched = 1; } - /* check the host and port */ - if (url_prefix->host_len != url->host_len || - strncmp(url->url + url->host_off, - url_prefix->url + url_prefix->host_off, url->host_len)) - return 0; /* host names and/or ports do not match */ + /* check the host */ + if (!match_host(url, url_prefix)) + return 0; /* host names do not match */ + + /* check the port */ + if (url_prefix->port_len != url->port_len || + strncmp(url->url + url->port_off, + url_prefix->url + url_prefix->port_off, url->port_len)) + return 0; /* ports do not match */ /* check the path */ pathmatchlen = url_match_prefix( url->url + url->path_off, url_prefix->url + url_prefix->path_off, url_prefix->url_len - url_prefix->path_off); + if (!pathmatchlen) + return 0; /* paths do not match */ - if (pathmatchlen && exactusermatch) - *exactusermatch = usermatched; - return pathmatchlen; + if (match) { + match->hostmatch_len = url_prefix->host_len; + match->pathmatch_len = pathmatchlen; + match->user_matched = usermatched; + } + + return 1; +} + +static int cmp_matches(const struct urlmatch_item *a, + const struct urlmatch_item *b) +{ + if (a->hostmatch_len != b->hostmatch_len) + return a->hostmatch_len < b->hostmatch_len ? -1 : 1; + if (a->pathmatch_len != b->pathmatch_len) + return a->pathmatch_len < b->pathmatch_len ? -1 : 1; + if (a->user_matched != b->user_matched) + return b->user_matched ? -1 : 1; + return 0; } int urlmatch_config_entry(const char *var, const char *value, void *cb) { struct string_list_item *item; struct urlmatch_config *collect = cb; - struct urlmatch_item *matched; + struct urlmatch_item matched = {0}; struct url_info *url = &collect->url; const char *key, *dot; struct strbuf synthkey = STRBUF_INIT; - size_t matched_len = 0; - int user_matched = 0; int retval; if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') { @@ -494,13 +569,13 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) struct url_info norm_info; config_url = xmemdupz(key, dot - key); - norm_url = url_normalize(config_url, &norm_info); + norm_url = url_normalize_1(config_url, &norm_info, 1); free(config_url); if (!norm_url) return 0; - matched_len = match_urls(url, &norm_info, &user_matched); + retval = match_urls(url, &norm_info, &matched); free(norm_url); - if (!matched_len) + if (!retval) return 0; key = dot + 1; } @@ -510,24 +585,18 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) item = string_list_insert(&collect->vars, key); if (!item->util) { - matched = xcalloc(1, sizeof(*matched)); - item->util = matched; + item->util = xcalloc(1, sizeof(matched)); } else { - matched = item->util; - /* - * Is our match shorter? Is our match the same - * length, and without user while the current - * candidate is with user? Then we cannot use it. - */ - if (matched_len < matched->matched_len || - ((matched_len == matched->matched_len) && - (!user_matched && matched->user_matched))) + if (cmp_matches(&matched, item->util) < 0) + /* + * Our match is worse than the old one, + * we cannot use it. + */ return 0; /* Otherwise, replace it with this one. */ } - matched->matched_len = matched_len; - matched->user_matched = user_matched; + memcpy(item->util, &matched, sizeof(matched)); strbuf_addstr(&synthkey, collect->section); strbuf_addch(&synthkey, '.'); strbuf_addstr(&synthkey, key); diff --git a/urlmatch.h b/urlmatch.h index 528862adc5..37ee5da85e 100644 --- a/urlmatch.h +++ b/urlmatch.h @@ -18,11 +18,12 @@ struct url_info { size_t passwd_len; /* length of passwd; if passwd_off != 0 but passwd_len == 0, an empty passwd was given */ size_t host_off; /* offset into url to start of host name (0 => none) */ - size_t host_len; /* length of host name; this INCLUDES any ':portnum'; + size_t host_len; /* length of host name; * file urls may have host_len == 0 */ - size_t port_len; /* if a portnum is present (port_len != 0), it has - * this length (excluding the leading ':') at the - * end of the host name (always 0 for file urls) */ + size_t port_off; /* offset into url to start of port number (0 => none) */ + size_t port_len; /* if a portnum is present (port_off != 0), it has + * this length (excluding the leading ':') starting + * from port_off (always 0 for file urls) */ size_t path_off; /* offset into url to the start of the url path; * this will always point to a '/' character * after the url has been normalized */ @@ -33,7 +34,8 @@ struct url_info { extern char *url_normalize(const char *, struct url_info *); struct urlmatch_item { - size_t matched_len; + size_t hostmatch_len; + size_t pathmatch_len; char user_matched; }; diff --git a/userdiff.c b/userdiff.c index 2125d6da26..8b732e40bc 100644 --- a/userdiff.c +++ b/userdiff.c @@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) { struct userdiff_driver *userdiff_find_by_path(const char *path) { - static struct git_attr *attr; - struct git_attr_check check; - - if (!attr) - attr = git_attr("diff"); - check.attr = attr; + static struct attr_check *check; + if (!check) + check = attr_check_initl("diff", NULL); if (!path) return NULL; - if (git_check_attr(path, 1, &check)) + if (git_check_attr(path, check)) return NULL; - if (ATTR_TRUE(check.value)) + if (ATTR_TRUE(check->items[0].value)) return &driver_true; - if (ATTR_FALSE(check.value)) + if (ATTR_FALSE(check->items[0].value)) return &driver_false; - if (ATTR_UNSET(check.value)) + if (ATTR_UNSET(check->items[0].value)) return NULL; - return userdiff_find_by_name(check.value); + return userdiff_find_by_name(check->items[0].value); } struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver) diff --git a/worktree.c b/worktree.c index 0486e31ad4..bae787cf8d 100644 --- a/worktree.c +++ b/worktree.c @@ -175,7 +175,7 @@ struct worktree **get_worktrees(unsigned flags) struct dirent *d; int counter = 0, alloc = 2; - list = xmalloc(alloc * sizeof(struct worktree *)); + ALLOC_ARRAY(list, alloc); list[counter++] = get_main_worktree(); @@ -250,16 +250,19 @@ struct worktree *find_worktree(struct worktree **list, { struct worktree *wt; char *path; + char *to_free = NULL; if ((wt = find_worktree_by_suffix(list, arg))) return wt; - arg = prefix_filename(prefix, strlen(prefix), arg); + if (prefix) + arg = to_free = prefix_filename(prefix, arg); path = real_pathdup(arg, 1); for (; *list; list++) if (!fspathcmp(path, real_path((*list)->path))) break; free(path); + free(to_free); return *list; } @@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string) return rule; } -static void setup_whitespace_attr_check(struct git_attr_check *check) -{ - static struct git_attr *attr_whitespace; - - if (!attr_whitespace) - attr_whitespace = git_attr("whitespace"); - check[0].attr = attr_whitespace; -} - unsigned whitespace_rule(const char *pathname) { - struct git_attr_check attr_whitespace_rule; + static struct attr_check *attr_whitespace_rule; + + if (!attr_whitespace_rule) + attr_whitespace_rule = attr_check_initl("whitespace", NULL); - setup_whitespace_attr_check(&attr_whitespace_rule); - if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) { + if (!git_check_attr(pathname, attr_whitespace_rule)) { const char *value; - value = attr_whitespace_rule.value; + value = attr_whitespace_rule->items[0].value; if (ATTR_TRUE(value)) { /* true (whitespace) */ unsigned all_rule = ws_tab_width(whitespace_rule_cfg); diff --git a/wt-status.c b/wt-status.c index 234e77a6d6..308cf3779e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -121,7 +121,7 @@ static void status_printf_more(struct wt_status *s, const char *color, void wt_status_prepare(struct wt_status *s) { - unsigned char sha1[20]; + struct object_id oid; memset(s, 0, sizeof(*s)); memcpy(s->color_palette, default_wt_status_colors, @@ -129,7 +129,7 @@ void wt_status_prepare(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = -1; s->relative_paths = 1; - s->branch = resolve_refdup("HEAD", 0, sha1, NULL); + s->branch = resolve_refdup("HEAD", 0, oid.hash, NULL); s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); @@ -1115,16 +1115,16 @@ static void abbrev_sha1_in_line(struct strbuf *line) split = strbuf_split_max(line, ' ', 3); if (split[0] && split[1]) { - unsigned char sha1[20]; + struct object_id oid; /* * strbuf_split_max left a space. Trim it and re-add * it after abbreviation. */ strbuf_trim(split[1]); - if (!get_sha1(split[1]->buf, sha1)) { + if (!get_oid(split[1]->buf, &oid)) { strbuf_reset(split[1]); - strbuf_add_unique_abbrev(split[1], sha1, + strbuf_add_unique_abbrev(split[1], oid.hash, DEFAULT_ABBREV); strbuf_addch(split[1], ' '); strbuf_reset(line); @@ -1340,7 +1340,7 @@ static void show_bisect_in_progress(struct wt_status *s, static char *get_branch(const struct worktree *wt, const char *path) { struct strbuf sb = STRBUF_INIT; - unsigned char sha1[20]; + struct object_id oid; const char *branch_name; if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0) @@ -1354,9 +1354,9 @@ static char *get_branch(const struct worktree *wt, const char *path) strbuf_remove(&sb, 0, branch_name - sb.buf); else if (starts_with(sb.buf, "refs/")) ; - else if (!get_sha1_hex(sb.buf, sha1)) { + else if (!get_oid_hex(sb.buf, &oid)) { strbuf_reset(&sb); - strbuf_add_unique_abbrev(&sb, sha1, DEFAULT_ABBREV); + strbuf_add_unique_abbrev(&sb, oid.hash, DEFAULT_ABBREV); } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */ goto got_nothing; else /* bisect */ @@ -1370,10 +1370,10 @@ got_nothing: struct grab_1st_switch_cbdata { struct strbuf buf; - unsigned char nsha1[20]; + struct object_id noid; }; -static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, +static int grab_1st_switch(struct object_id *ooid, struct object_id *noid, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { @@ -1387,13 +1387,13 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, return 0; target += strlen(" to "); strbuf_reset(&cb->buf); - hashcpy(cb->nsha1, nsha1); + oidcpy(&cb->noid, noid); end = strchrnul(target, '\n'); strbuf_add(&cb->buf, target, end - target); if (!strcmp(cb->buf.buf, "HEAD")) { /* HEAD is relative. Resolve it to the right reflog entry. */ strbuf_reset(&cb->buf); - strbuf_add_unique_abbrev(&cb->buf, nsha1, DEFAULT_ABBREV); + strbuf_add_unique_abbrev(&cb->buf, noid->hash, DEFAULT_ABBREV); } return 1; } @@ -1402,7 +1402,7 @@ static void wt_status_get_detached_from(struct wt_status_state *state) { struct grab_1st_switch_cbdata cb; struct commit *commit; - unsigned char sha1[20]; + struct object_id oid; char *ref = NULL; strbuf_init(&cb.buf, 0); @@ -1411,22 +1411,22 @@ static void wt_status_get_detached_from(struct wt_status_state *state) return; } - if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 && + if (dwim_ref(cb.buf.buf, cb.buf.len, oid.hash, &ref) == 1 && /* sha1 is a commit? match without further lookup */ - (!hashcmp(cb.nsha1, sha1) || + (!oidcmp(&cb.noid, &oid) || /* perhaps sha1 is a tag, try to dereference to a commit */ - ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL && - !hashcmp(cb.nsha1, commit->object.oid.hash)))) { + ((commit = lookup_commit_reference_gently(oid.hash, 1)) != NULL && + !oidcmp(&cb.noid, &commit->object.oid)))) { const char *from = ref; if (!skip_prefix(from, "refs/tags/", &from)) skip_prefix(from, "refs/remotes/", &from); state->detached_from = xstrdup(from); } else state->detached_from = - xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV)); - hashcpy(state->detached_sha1, cb.nsha1); - state->detached_at = !get_sha1("HEAD", sha1) && - !hashcmp(sha1, state->detached_sha1); + xstrdup(find_unique_abbrev(cb.noid.hash, DEFAULT_ABBREV)); + hashcpy(state->detached_sha1, cb.noid.hash); + state->detached_at = !get_oid("HEAD", &oid) && + !hashcmp(oid.hash, state->detached_sha1); free(ref); strbuf_release(&cb.buf); @@ -1476,22 +1476,22 @@ void wt_status_get_state(struct wt_status_state *state, int get_detached_from) { struct stat st; - unsigned char sha1[20]; + struct object_id oid; if (!stat(git_path_merge_head(), &st)) { state->merge_in_progress = 1; } else if (wt_status_check_rebase(NULL, state)) { ; /* all set */ } else if (!stat(git_path_cherry_pick_head(), &st) && - !get_sha1("CHERRY_PICK_HEAD", sha1)) { + !get_oid("CHERRY_PICK_HEAD", &oid)) { state->cherry_pick_in_progress = 1; - hashcpy(state->cherry_pick_head_sha1, sha1); + hashcpy(state->cherry_pick_head_sha1, oid.hash); } wt_status_check_bisect(NULL, state); if (!stat(git_path_revert_head(), &st) && - !get_sha1("REVERT_HEAD", sha1)) { + !get_oid("REVERT_HEAD", &oid)) { state->revert_in_progress = 1; - hashcpy(state->revert_head_sha1, sha1); + hashcpy(state->revert_head_sha1, oid.hash); } if (get_detached_from) @@ -1785,34 +1785,24 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) static void wt_shortstatus_print(struct wt_status *s) { - int i; + struct string_list_item *it; if (s->show_branch) wt_shortstatus_print_tracking(s); - for (i = 0; i < s->change.nr; i++) { - struct wt_status_change_data *d; - struct string_list_item *it; + for_each_string_list_item(it, &s->change) { + struct wt_status_change_data *d = it->util; - it = &(s->change.items[i]); - d = it->util; if (d->stagemask) wt_shortstatus_unmerged(it, s); else wt_shortstatus_status(it, s); } - for (i = 0; i < s->untracked.nr; i++) { - struct string_list_item *it; - - it = &(s->untracked.items[i]); + for_each_string_list_item(it, &s->untracked) wt_shortstatus_other(it, s, "??"); - } - for (i = 0; i < s->ignored.nr; i++) { - struct string_list_item *it; - it = &(s->ignored.items[i]); + for_each_string_list_item(it, &s->ignored) wt_shortstatus_other(it, s, "!!"); - } } static void wt_porcelain_print(struct wt_status *s) diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 7389ce4102..8c88dbde38 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -183,16 +183,14 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, /* * We don't need additional context if - * a whole function was added, possibly - * starting with empty lines. + * a whole function was added. */ - while (i2 < xe->xdf2.nrec && - is_empty_rec(&xe->xdf2, i2)) + while (i2 < xe->xdf2.nrec) { + if (match_func_rec(&xe->xdf2, xecfg, i2, + dummy, sizeof(dummy)) >= 0) + goto post_context_calculation; i2++; - if (i2 < xe->xdf2.nrec && - match_func_rec(&xe->xdf2, xecfg, i2, - dummy, sizeof(dummy)) >= 0) - goto post_context_calculation; + } /* * Otherwise get more context from the |