diff options
216 files changed, 9460 insertions, 4475 deletions
diff --git a/.gitignore b/.gitignore index 6722f78f9a..b1020b875f 100644 --- a/.gitignore +++ b/.gitignore @@ -118,7 +118,6 @@ /git-rebase--merge /git-receive-pack /git-reflog -/git-relink /git-remote /git-remote-http /git-remote-https @@ -192,6 +192,8 @@ Philippe Bruhat <book@cpan.org> Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com> Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk> René Scharfe <l.s.r@web.de> <rene.scharfe@lsrfire.ath.cx> +Richard Hansen <rhansen@rhansen.org> <hansenr@google.com> +Richard Hansen <rhansen@rhansen.org> <rhansen@bbn.com> Robert Fitzsimons <robfitz@273k.net> Robert Shearman <robertshearman@gmail.com> <rob@codeweavers.com> Robert Zeh <robert.a.zeh@gmail.com> diff --git a/Documentation/Makefile b/Documentation/Makefile index b43d66eae6..a9fb497b83 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -337,7 +337,7 @@ manpage-base-url.xsl: manpage-base-url.xsl.in user-manual.xml: user-manual.txt user-manual.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ - $(TXT_TO_XML) -d article -o $@+ $< && \ + $(TXT_TO_XML) -d book -o $@+ $< && \ mv $@+ $@ technical/api-index.txt: technical/api-index-skel.txt \ diff --git a/Documentation/RelNotes/2.12.0.txt b/Documentation/RelNotes/2.12.0.txt new file mode 100644 index 0000000000..001745ee75 --- /dev/null +++ b/Documentation/RelNotes/2.12.0.txt @@ -0,0 +1,394 @@ +Git 2.12 Release Notes +====================== + +Backward compatibility notes. + + * Use of an empty string 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. + + +Updates since v2.11 +------------------- + +UI, Workflows & Features + + * Various updates to "git p4". + + * "git p4" didn't interact with the internal of .git directory + correctly in the modern "git-worktree"-enabled world. + + * "git branch --list" and friends learned "--ignore-case" option to + optionally sort branches and tags case insensitively. + + * In addition to %(subject), %(body), "log --pretty=format:..." + learned a new placeholder %(trailers). + + * "git rebase" learned "--quit" option, which allows a user to + remove the metadata left by an earlier "git rebase" that was + manually aborted without using "git rebase --abort". + + * "git clone --reference $there --recurse-submodules $super" has been + taught to guess repositories usable as references for submodules of + $super that are embedded in $there while making a clone of the + superproject borrow objects from $there; extend the mechanism to + also allow submodules of these submodules to borrow repositories + embedded in these clones of the submodules embedded in the clone of + the superproject. + + * Porcelain scripts written in Perl are getting internationalized. + + * "git merge --continue" has been added as a synonym to "git commit" + to conclude a merge that has stopped due to conflicts. + + * Finer-grained control of what protocols are allowed for transports + during clone/fetch/push have been enabled via a new configuration + mechanism. + + * "git shortlog" learned "--committer" option to group commits by + committer, instead of author. + + * GitLFS integration with "git p4" has been updated. + + * The isatty() emulation for Windows has been updated to eradicate + the previous hack that depended on internals of (older) MSVC + runtime. + + * Some platforms no longer understand "latin-1" that is still seen in + the wild in e-mail headers; replace them with "iso-8859-1" that is + more widely known when conversion fails from/to it. + (merge df3755888b jc/latin-1 later to maint). + + * "git grep" has been taught to optionally recurse into submodules. + + * "git rm" used to refuse to remove a submodule when it has its own + git repository embedded in its working tree. It learned to move + the repository away to $GIT_DIR/modules/ of the superproject + instead, and allow the submodule to be deleted (as long as there + will be no loss of local modifications, that is). + + * A recent updates to "git p4" was not usable for older p4 but it + could be made to work with minimum changes. Do so. + + * "git diff" learned diff.interHunkContext configuration variable + that gives the default value for its --inter-hunk-context option. + + * The prereleaseSuffix feature of version comparison that is used in + "git tag -l" did not correctly when two or more prereleases for the + same release were present (e.g. when 2.0, 2.0-beta1, and 2.0-beta2 + are there and the code needs to compare 2.0-beta1 and 2.0-beta2). + + * "git submodule push" learned "--recurse-submodules=only option to + push submodules out without pushing the top-level superproject. + + * "git tag" and "git verify-tag" learned to put GPG verification + status in their "--format=<placeholders>" output format. + + * An ancient repository conversion tool left in contrib/ has been + removed. + + * "git show-ref HEAD" used with "--verify" because the user is not + interested in seeing refs/remotes/origin/HEAD, and used with + "--head" because the user does not want HEAD to be filtered out, + i.e. "git show-ref --head --verify HEAD", did not work as expected. + + +Performance, Internal Implementation, Development Support etc. + + * Commands that operate on a log message and add lines to the trailer + blocks, such as "format-patch -s", "cherry-pick (-x|-s)", and + "commit -s", have been taught to use the logic of and share the + code with "git interpret-trailer". + + * The default Travis-CI configuration specifies newer P4 and GitLFS. + + * The "fast hash" that had disastrous performance issues in some + corner cases has been retired from the internal diff. + + * The character width table has been updated to match Unicode 9.0 + + * Update the procedure to generate "tags" for developer support. + (merge 046e4c1c09 jk/make-tags-find-sources-tweak later to maint). + + * The codeflow of setting NOATIME and CLOEXEC on file descriptors Git + opens has been simplified. + (merge b4d065df03 jc/git-open-cloexec later to maint). + + * "git diff" and its family had two experimental heuristics to shift + the contents of a hunk to make the patch easier to read. One of + them turns out to be better than the other, so leave only the + "--indent-heuristic" option and remove the other one. + (merge 3cde4e02ee jc/retire-compaction-heuristics later to maint). + + * A new submodule helper "git submodule embedgitdirs" to make it + easier to move embedded .git/ directory for submodules in a + superproject to .git/modules/ (and point the latter with the former + that is turned into a "gitdir:" file) has been added. + + * "git push \\server\share\dir" has recently regressed and then + fixed. A test has retroactively been added for this breakage. + + * Build updates for Cygwin. + + * The implementation of "real_path()" was to go there with chdir(2) + and call getcwd(3), but this obviously wouldn't be usable in a + threaded environment. Rewrite it to manually resolve relative + paths including symbolic links in path components. + + * Adjust documentation to help AsciiDoctor render better while not + breaking the rendering done by AsciiDoc. + + * The sequencer machinery has been further enhanced so that a later + set of patches can start using it to reimplement "rebase -i". + + * Update the definition of the MacOSX test environment used by + TravisCI. + (merge 672f51cb83 ls/travis-p4-on-macos later to maint). + + * Rewrite a scripted porcelain "git difftool" in C. + (merge 94d3997ecc js/difftool-builtin later to maint). + + +Also contains various documentation updates and code clean-ups. + +Fixes since v2.10 +----------------- + +Unless otherwise noted, all the fixes since v2.9 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * We often decide if a session is interactive by checking if the + standard I/O streams are connected to a TTY, but isatty() that + comes with Windows incorrectly returned true if it is used on NUL + (i.e. an equivalent to /dev/null). This has been fixed. + + * "git svn" did not work well with path components that are "0", and + some configuration variable it uses were not documented. + (merge ea9a93dcc2 ew/svn-fixes later to maint). + + * "git rev-parse --symbolic" failed with a more recent notation like + "HEAD^-1" and "HEAD^!". + + * An empty directory in a working tree that can simply be nuked used + to interfere while merging or cherry-picking a change to create a + submodule directory there, which has been fixed.. + + * The code in "git push" to compute if any commit being pushed in the + superproject binds a commit in a submodule that hasn't been pushed + out was overly inefficient, making it unusable even for a small + project that does not have any submodule but have a reasonable + number of refs. + + * "git push --dry-run --recurse-submodule=on-demand" wasn't + "--dry-run" in the submodules. + + * The output from "git worktree list" was made in readdir() order, + and was unstable. + + * mergetool.<tool>.trustExitCode configuration variable did not apply + to built-in tools, but now it does. + + * "git p4" LFS support was broken when LFS stores an empty blob. + + * A corner case in merge-recursive regression that crept in + during 2.10 development cycle has been fixed. + + * Transport with dumb http can be fooled into following foreign URLs + that the end user does not intend to, especially with the server + side redirects and http-alternates mechanism, which can lead to + security issues. Tighten the redirection and make it more obvious + to the end user when it happens. + + * Update the error messages from the dumb-http client when it fails + to obtain loose objects; we used to give sensible error message + only upon 404 but we now forbid unexpected redirects that needs to + be reported with something sensible. + + * When diff.renames configuration is on (and with Git 2.9 and later, + it is enabled by default, which made it worse), "git stash" + misbehaved if a file is removed and another file with a very + similar content is added. + + * "git diff --no-index" did not take "--no-abbrev" option. + + * "git difftool --dir-diff" had a minor regression when started from + a subdirectory, which has been fixed. + + * "git commit --allow-empty --only" (no pathspec) with dirty index + ought to be an acceptable way to create a new commit that does not + change any paths, but it was forbidden, perhaps because nobody + needed it so far. + + * Git 2.11 had a minor regression in "merge --ff-only" that competed + with another process that simultanously attempted to update the + index. We used to explain what went wrong with an error message, + but the new code silently failed. The error message has been + resurrected. + + * A pathname that begins with "//" or "\\" on Windows is special but + path normalization logic was unaware of it. + + * "git pull --rebase", when there is no new commits on our side since + we forked from the upstream, should be able to fast-forward without + invoking "git rebase", but it didn't. + + * The way to specify hotkeys to "xxdiff" that is used by "git + mergetool" has been modernized to match recent versions of xxdiff. + + * Unlike "git am --abort", "git cherry-pick --abort" moved HEAD back + to where cherry-pick started while picking multiple changes, when + the cherry-pick stopped to ask for help from the user, and the user + did "git reset --hard" to a different commit in order to re-attempt + the operation. + + * Code cleanup in shallow boundary computation. + + * A recent update to receive-pack to make it easier to drop garbage + objects made it clear that GIT_ALTERNATE_OBJECT_DIRECTORIES cannot + have a pathname with a colon in it (no surprise!), and this in turn + made it impossible to push into a repository at such a path. This + has been fixed by introducing a quoting mechanism used when + appending such a path to the colon-separated list. + + * The function usage_msg_opt() has been updated to say "fatal:" + before the custom message programs give, when they want to die + with a message about wrong command line options followed by the + standard usage string. + + * "git index-pack --stdin" needs an access to an existing repository, + but "git index-pack file.pack" to generate an .idx file that + corresponds to a packfile does not. + + * Fix for NDEBUG builds. + + * A lazy "git push" without refspec did not internally use a fully + specified refspec to perform 'current', 'simple', or 'upstream' + push, causing unnecessary "ambiguous ref" errors. + + * "git p4" misbehaved when swapping a directory and a symbolic link. + + * Even though an fix was attempted in Git 2.9.3 days, but running + "git difftool --dir-diff" from a subdirectory never worked. This + has been fixed. + + * "git p4" that tracks multile p4 paths imported a single changelist + that touches files in these multiple paths as one commit, followed + by many empty commits. This has been fixed. + + * A potential but unlikely buffer overflow in Windows port has been + fixed. + + * When the http server gives an incomplete response to a smart-http + rpc call, it could lead to client waiting for a full response that + will never come. Teach the client side to notice this condition + and abort the transfer. + + * Compression setting for producing packfiles were spread across + three codepaths, one of which did not honor any configuration. + Unify these so that all of them honor core.compression and + pack.compression variables the same way. + (merge 8de7eeb54b jc/compression-config later to maint). + + * "git fast-import" sometimes mishandled while rebalancing notes + tree, which has been fixed. + (merge 405d7f4af6 mh/fast-import-notes-fix-new later to maint). + + * Recent update to the default abbreviation length that auto-scales + lacked documentation update, which has been corrected. + (merge 48d5014dd4 jc/abbrev-autoscale-config later to maint). + + * Leakage of lockfiles in the config subsystem has been fixed. + (merge c06fa62dfc nd/config-misc-fixes later to maint). + + * It is natural that "git gc --auto" may not attempt to pack + everything into a single pack, and there is no point in warning + when the user has configured the system to use the pack bitmap, + leading to disabling further "gc". + (merge 1c409a705c dt/disable-bitmap-in-auto-gc later to maint). + + * "git archive" did not read the standard configuration files, and + failed to notice a file that is marked as binary via the userdiff + driver configuration. + (merge 965cba2e7e jk/archive-zip-userdiff-config later to maint). + + * "git blame --porcelain" misidentified the "previous" <commit, path> + pair (aka "source") when contents came from two or more files. + (merge 4e76832984 jk/blame-fixes later to maint). + + * "git rebase -i" with a recent update started showing an incorrect + count when squashing more than 10 commits. + (merge 356b8ecff1 jk/rebase-i-squash-count-fix later to maint). + + * "git <cmd> @{push}" on a detached HEAD used to segfault; it has + been corrected to error out with a message. + (merge b10731f43d km/branch-get-push-while-detached later to maint). + + * Running "git add a/b" when "a" is a submodule correctly errored + out, but without a meaningful error message. + (merge 2d81c48fa7 sb/pathspec-errors later to maint). + + * Typing ^C to pager, which usually does not kill it, killed Git and + took the pager down as a collateral damage in certain process-tree + structure. This has been fixed. + (merge 46df6906f3 jk/execv-dashed-external later to maint). + + * "git mergetool" without any pathspec on the command line that is + run from a subdirectory became no-op in Git v2.11 by mistake, which + has been fixed. + + * Retire long unused/unmaintained gitview from the contrib/ area. + (merge 3120925c25 sb/remove-gitview later to maint). + + * Tighten a test to avoid mistaking an extended ERE regexp engine as + a PRE regexp engine. + (merge 7675c7bd01 jk/grep-e-could-be-extended-beyond-posix later to maint). + + * An error message with an ASCII control character like '\r' in it + can alter the message to hide its early part, which is problematic + when a remote side gives such an error message that the local side + will relay with a "remote: " prefix. + (merge f290089879 jk/vreport-sanitize later to maint). + + * "git fsck" inspects loose objects more carefully now. + (merge cce044df7f jk/loose-object-fsck later to maint). + + * A crashing bug introduced in v2.11 timeframe has been found (it is + triggerable only in fast-import) and fixed. + (merge abd5a00268 jk/clear-delta-base-cache-fix later to maint). + + * With an anticipatory tweak for remotes defined in ~/.gitconfig + (e.g. "remote.origin.prune" set to true, even though there may or + may not actually be "origin" remote defined in a particular Git + repository), "git remote rename" and other commands misinterpreted + and behaved as if such a non-existing remote actually existed. + (merge e459b073fb js/remote-rename-with-half-configured-remote later to maint). + + * A few codepaths had to rely on a global variable when sorting + elements of an array because sort(3) API does not allow extra data + to be passed to the comparison function. Use qsort_s() when + natively available, and a fallback implementation of it when not, + to eliminate the need, which is a prerequisite for making the + codepath reentrant. + (merge 83fc4d64fe rs/qsort-s later to maint). + + * "git fsck --connectivity-check" was not working at all. + (merge a2b22854bd jk/fsck-connectivity-check-fix later to maint). + + * Other minor doc, test and build updates and code cleanups. + (merge f2627d9b19 sb/submodule-config-cleanup later to maint). + (merge 384f1a167b sb/unpack-trees-cleanup later to maint). + (merge 3f05402ac0 ad/bisect-terms later to maint). + (merge 874444b704 rh/diff-orderfile-doc later to maint). + (merge c68d2d7c2b ws/request-pull-code-cleanup later to maint). + (merge 007ac54401 js/exec-path-coverity-workaround later to maint). + (merge 1797dc5176 jk/coding-guidelines-update later to maint). + (merge 1d3f065e0e js/mingw-isatty later to maint). + (merge 830c912a0e sb/in-core-index-doc later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 08352deaae..3faf7eb884 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -216,12 +216,11 @@ that it will be postponed. Exception: If your mailer is mangling patches then someone may ask you to re-send them using MIME, that is OK. -Do not PGP sign your patch, at least for now. Most likely, your -maintainer or other people on the list would not have your PGP -key and would not bother obtaining it anyway. Your patch is not -judged by who you are; a good patch from an unknown origin has a -far better chance of being accepted than a patch from a known, -respected origin that is done poorly or does incorrect things. +Do not PGP sign your patch. Most likely, your maintainer or other people on the +list would not have your PGP key and would not bother obtaining it anyway. +Your patch is not judged by who you are; a good patch from an unknown origin +has a far better chance of being accepted than a patch from a known, respected +origin that is done poorly or does incorrect things. If you really really really really want to do a PGP signed patch, format it as "multipart/signed", not a text/plain message @@ -246,7 +245,7 @@ patch. *2* The mailing list: git@vger.kernel.org -(5) Sign your work +(5) Certify your work by adding your "Signed-off-by: " line To improve tracking of who did what, we've borrowed the "sign-off" procedure from the Linux kernel project on patches diff --git a/Documentation/config.txt b/Documentation/config.txt index 1fee83ca42..af2ae4cc02 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2321,6 +2321,52 @@ pretty.<name>:: Note that an alias with the same name as a built-in format will be silently ignored. +protocol.allow:: + If set, provide a user defined default policy for all protocols which + don't explicitly have a policy (`protocol.<name>.allow`). By default, + if unset, known-safe protocols (http, https, git, ssh, file) have a + default policy of `always`, known-dangerous protocols (ext) have a + default policy of `never`, and all other protocols have a default + policy of `user`. Supported policies: ++ +-- + +* `always` - protocol is always able to be used. + +* `never` - protocol is never able to be used. + +* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is + either unset or has a value of 1. This policy should be used when you want a + protocol to be directly usable by the user but don't want it used by commands which + execute clone/fetch/push commands without user input, e.g. recursive + submodule initialization. + +-- + +protocol.<name>.allow:: + Set a policy to be used by protocol `<name>` with clone/fetch/push + commands. See `protocol.allow` above for the available policies. ++ +The protocol names currently used by git are: ++ +-- + - `file`: any local file-based path (including `file://` URLs, + or local paths) + + - `git`: the anonymous git protocol over a direct TCP + connection (or proxy, if configured) + + - `ssh`: git over ssh (including `host:path` syntax, + `ssh://`, etc). + + - `http`: git over http, both "smart http" and "dumb http". + Note that this does _not_ include `https`; if you want to configure + both, you must do so individually. + + - any external helpers are named by their protocol (e.g., use + `hg` to allow the `git-remote-hg` helper) +-- + pull.ff:: By default, Git does not create an extra merge commit when merging a commit that is a descendant of the current commit. Instead, the @@ -3067,17 +3113,39 @@ user.signingKey:: This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. -versionsort.prereleaseSuffix:: - When version sort is used in linkgit:git-tag[1], prerelease - tags (e.g. "1.0-rc1") may appear after the main release - "1.0". By specifying the suffix "-rc" in this variable, - "1.0-rc1" will appear before "1.0". -+ -This variable can be specified multiple times, once per suffix. The -order of suffixes in the config file determines the sorting order -(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX -is sorted before 1.0-rcXX). The sorting order between different -suffixes is undefined if they are in multiple config files. +versionsort.prereleaseSuffix (deprecated):: + Deprecated alias for `versionsort.suffix`. Ignored if + `versionsort.suffix` is set. + +versionsort.suffix:: + Even when version sort is used in linkgit:git-tag[1], tagnames + with the same base version but different suffixes are still sorted + lexicographically, resulting e.g. in prerelease tags appearing + after the main release (e.g. "1.0-rc1" after "1.0"). This + variable can be specified to determine the sorting order of tags + with different suffixes. ++ +By specifying a single suffix in this variable, any tagname containing +that suffix will appear before the corresponding main release. E.g. if +the variable is set to "-rc", then all "1.0-rcX" tags will appear before +"1.0". If specified multiple times, once per suffix, then the order of +suffixes in the configuration will determine the sorting order of tagnames +with those suffixes. E.g. if "-pre" appears before "-rc" in the +configuration, then all "1.0-preX" tags will be listed before any +"1.0-rcX" tags. The placement of the main release tag relative to tags +with various suffixes can be determined by specifying the empty suffix +among those other suffixes. E.g. if the suffixes "-rc", "", "-ck" and +"-bfs" appear in the configuration in this order, then all "v4.8-rcX" tags +are listed first, followed by "v4.8", then "v4.8-ckX" and finally +"v4.8-bfsX". ++ +If more than one suffixes match the same tagname, then that tagname will +be sorted according to the suffix which starts at the earliest position in +the tagname. If more than one different matching suffixes start at +that earliest position, then that tagname will be sorted according to the +longest of those suffixes. +The sorting order between different suffixes is undefined if they are +in multiple config files. web.browser:: Specify a web browser that may be used by some commands. diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 58f4bd6afa..cbce8ec638 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -60,6 +60,12 @@ diff.context:: Generate diffs with <n> lines of context instead of the default of 3. This value is overridden by the -U option. +diff.interHunkContext:: + Show the context between diff hunks, up to the specified number + of lines, thereby fusing the hunks that are close to each other. + This value serves as the default for the `--inter-hunk-context` + command line option. + diff.external:: If this config variable is set, diff generation is not performed using the internal diff machinery, but using the @@ -99,9 +105,10 @@ diff.noprefix:: If set, 'git diff' does not show any source or destination prefix. diff.orderFile:: - File indicating how to order files within a diff, using - one shell glob pattern per line. - Can be overridden by the '-O' option to linkgit:git-diff[1]. + File indicating how to order files within a diff. + See the '-O' option to linkgit:git-diff[1] for details. + If `diff.orderFile` is a relative pathname, it is treated as + relative to the top of the working tree. diff.renameLimit:: The number of files to consider when performing the copy/rename @@ -172,10 +179,8 @@ diff.tool:: include::mergetools-diff.txt[] diff.indentHeuristic:: -diff.compactionHeuristic:: - Set one of these options to `true` to enable one of two - experimental heuristics that shift diff hunk boundaries to - make patches easier to read. + Set this option to `true` to enable experimental heuristics + that shift diff hunk boundaries to make patches easier to read. diff.algorithm:: Choose a diff algorithm. The variants are as follows: diff --git a/Documentation/diff-heuristic-options.txt b/Documentation/diff-heuristic-options.txt index 36cb549df9..d4f3d95505 100644 --- a/Documentation/diff-heuristic-options.txt +++ b/Documentation/diff-heuristic-options.txt @@ -1,7 +1,5 @@ --indent-heuristic:: --no-indent-heuristic:: ---compaction-heuristic:: ---no-compaction-heuristic:: These are to help debugging and tuning experimental heuristics (which are off by default) that shift diff hunk boundaries to make patches easier to read. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e6215c372c..d91ddbd5fe 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -466,11 +466,41 @@ information. endif::git-format-patch[] -O<orderfile>:: - Output the patch in the order specified in the - <orderfile>, which has one shell glob pattern per line. + Control the order in which files appear in the output. This overrides the `diff.orderFile` configuration variable (see linkgit:git-config[1]). To cancel `diff.orderFile`, use `-O/dev/null`. ++ +The output order is determined by the order of glob patterns in +<orderfile>. +All files with pathnames that match the first pattern are output +first, all files with pathnames that match the second pattern (but not +the first) are output next, and so on. +All files with pathnames that do not match any pattern are output +last, as if there was an implicit match-all pattern at the end of the +file. +If multiple pathnames have the same rank (they match the same pattern +but no earlier patterns), their output order relative to each other is +the normal order. ++ +<orderfile> is parsed as follows: ++ +-- + - Blank lines are ignored, so they can be used as separators for + readability. + + - Lines starting with a hash ("`#`") are ignored, so they can be used + for comments. Add a backslash ("`\`") to the beginning of the + pattern if it starts with a hash. + + - Each other line contains a single pattern. +-- ++ +Patterns have the same syntax and semantics as patterns used for +fnmantch(3) without the FNM_PATHNAME flag, except a pathname also +matches a pattern if removing any number of the final pathname +components matches the pattern. For example, the pattern "`foo*bar`" +matches "`fooasdfbar`" and "`foo/bar/baz/asdf`" but not "`foobarx`". ifndef::git-format-patch[] -R:: @@ -511,6 +541,8 @@ endif::git-format-patch[] --inter-hunk-context=<lines>:: Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. + Defaults to `diff.interHunkContext` or 0 if the config option + is unset. -W:: --function-context:: diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 1fe73448f3..5516a47b54 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -118,6 +118,10 @@ OPTIONS default to color output. Same as `--color=never`. +-i:: +--ignore-case:: + Sorting and filtering branches are case insensitive. + --column[=<options>]:: --no-column:: Display branch listing in columns. See configuration variable diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index f57e69bc83..abe13f3bed 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -79,6 +79,9 @@ OPTIONS Only list refs which contain the specified commit (HEAD if not specified). +--ignore-case:: + Sorting and filtering refs are case insensitive. + FIELD NAMES ----------- @@ -165,6 +168,8 @@ of all lines of the commit message up to the first blank line. The next line is 'contents:body', where body is all of the lines after the first blank line. The optional GPG signature is `contents:signature`. The first `N` lines of the message is obtained using `contents:lines=N`. +Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1] +are obtained as 'contents:trailers'. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`). diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 0ecea6e491..71f32f3508 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -26,6 +26,7 @@ SYNOPSIS [--threads <num>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] + [--recurse-submodules] [--parent-basename <basename>] [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...] [--] [<pathspec>...] @@ -88,6 +89,19 @@ OPTIONS mechanism. Only useful when searching files in the current directory with `--no-index`. +--recurse-submodules:: + Recursively search in each submodule that has been initialized and + checked out in the repository. When used in combination with the + <tree> option the prefix of all submodule output will be the name of + the parent project's <tree> object. + +--parent-basename <basename>:: + For internal use only. In order to produce uniform output with the + --recurse-submodules option, this option can be used to provide the + basename of a parent's <tree> object to a submodule so the submodule + can prefix its output with the parent's name rather than the SHA1 of + the submodule. + -a:: --text:: Process binary files as if they were text. diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt index c1a3e8bf07..5f93f8003d 100644 --- a/Documentation/git-gui.txt +++ b/Documentation/git-gui.txt @@ -35,7 +35,7 @@ blame:: browser:: Start a tree browser showing all files in the specified - commit (or `HEAD` by default). Files selected through the + commit. Files selected through the browser are opened in the blame viewer. citool:: diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index b758d5556c..ca3c27b88a 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -15,6 +15,7 @@ SYNOPSIS [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...] 'git merge' <msg> HEAD <commit>... 'git merge' --abort +'git merge' --continue DESCRIPTION ----------- @@ -61,6 +62,8 @@ reconstruct the original (pre-merge) changes. Therefore: discouraged: while possible, it may leave you in a state that is hard to back out of in the case of a conflict. +The fourth syntax ("`git merge --continue`") can only be run after the +merge has resulted in conflicts. OPTIONS ------- @@ -99,6 +102,11 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--continue:: + After a 'git merge' stops due to conflicts you can conclude the + merge by running 'git merge --continue' (see "HOW TO RESOLVE + CONFLICTS" section below). + <commit>...:: Commits, usually other branch heads, to merge into our branch. Specifying more than one commit will create a merge with diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index c83aaf39c3..7436c64a95 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -303,6 +303,15 @@ These options can be used to modify 'git p4 submit' behavior. submit manually or revert. This option always stops after the first (oldest) commit. Git tags are not exported to p4. +--shelve:: + Instead of submitting create a series of shelved changelists. + After creating each shelve, the relevant files are reverted/deleted. + If you have multiple commits pending multiple shelves will be created. + +--update-shelve CHANGELIST:: + Update an existing shelved changelist with this commit. Implies + --shelve. + --conflict=(ask|skip|quit):: Conflicts can occur when applying a commit to p4. When this happens, the default behavior ("ask") is to prompt whether to @@ -467,6 +476,12 @@ git-p4.client:: Client specified as an option to all p4 commands, with '-c <client>', including the client spec. +git-p4.retries:: + Specifies the number of times to retry a p4 command (notably, + 'p4 sync') if the network times out. The default value is 3. + Set the value to 0 to disable retries or if your p4 version + does not support retries (pre 2012.2). + Clone and sync variables ~~~~~~~~~~~~~~~~~~~~~~~~ git-p4.syncFromOrigin:: diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index de222c81af..67d48e6883 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -12,7 +12,7 @@ SYNOPSIS [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>] -'git rebase' --continue | --skip | --abort | --edit-todo +'git rebase' --continue | --skip | --abort | --quit | --edit-todo DESCRIPTION ----------- @@ -252,6 +252,11 @@ leave out at most one of A and B, in which case it defaults to HEAD. will be reset to where it was when the rebase operation was started. +--quit:: + Abort the rebase operation but HEAD is not reset back to the + original branch. The index and working tree are also left + unchanged as a result. + --keep-empty:: Keep the commits that do not change anything from its parents in the result. diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt deleted file mode 100644 index 3b33c99510..0000000000 --- a/Documentation/git-relink.txt +++ /dev/null @@ -1,30 +0,0 @@ -git-relink(1) -============= - -NAME ----- -git-relink - Hardlink common objects in local repositories - -SYNOPSIS --------- -[verse] -'git relink' [--safe] <dir>... <master_dir> - -DESCRIPTION ------------ -This will scan 1 or more object repositories and look for objects in common -with a master repository. Objects not already hardlinked to the master -repository will be replaced with a hardlink to the master repository. - -OPTIONS -------- ---safe:: - Stops if two objects with the same hash exist but have different sizes. - Default is to warn and continue. - -<dir>:: - Directories containing a .git/objects/ subdirectory. - -GIT ---- -Part of the linkgit:git[1] suite diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index b6c6326cdc..7241e96893 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -91,7 +91,8 @@ repository. For example: ---- prefix=$(git rev-parse --show-prefix) cd "$(git rev-parse --show-toplevel)" -eval "set -- $(git rev-parse --sq --prefix "$prefix" "$@")" +# rev-parse provides the -- needed for 'set' +eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" ---- --verify:: diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 31af7f2736..ee6c5476c1 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -47,6 +47,10 @@ OPTIONS Each pretty-printed commit will be rewrapped before it is shown. +-c:: +--committer:: + Collect and show committer identities instead of authors. + -w[<width>[,<indent1>[,<indent2>]]]:: Linewrap the output by wrapping each line at `width`. The first line of each entry is indented by `indent1` spaces, and the second diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index d841573475..918bd1d1bd 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -22,6 +22,7 @@ SYNOPSIS [commit] [--] [<path>...] 'git submodule' [--quiet] foreach [--recursive] <command> 'git submodule' [--quiet] sync [--recursive] [--] [<path>...] +'git submodule' [--quiet] absorbgitdirs [--] [<path>...] DESCRIPTION @@ -245,6 +246,20 @@ sync:: If `--recursive` is specified, this command will recurse into the registered submodules, and sync any nested submodules within. +absorbgitdirs:: + If a git directory of a submodule is inside the submodule, + move the git directory of the submodule into its superprojects + `$GIT_DIR/modules` path and then connect the git directory and + its working directory by setting the `core.worktree` and adding + a .git file pointing to the git directory embedded in the + superprojects git directory. ++ +A repository that was cloned independently and later added as a submodule or +old setups have the submodules git directory inside the submodule instead of +embedded into the superprojects git directory. ++ +This command is recursive by default. + OPTIONS ------- -q:: diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 80019c584b..8e70c5b6a4 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -15,7 +15,7 @@ SYNOPSIS 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>] [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...] -'git tag' -v <tagname>... +'git tag' -v [--format=<format>] <tagname>... DESCRIPTION ----------- @@ -101,13 +101,17 @@ OPTIONS multiple times, in which case the last key becomes the primary key. Also supports "version:refname" or "v:refname" (tag names are treated as versions). The "version:refname" sort - order can also be affected by the - "versionsort.prereleaseSuffix" configuration variable. + order can also be affected by the "versionsort.suffix" + configuration variable. The keys supported are the same as those in `git for-each-ref`. Sort order defaults to the value configured for the `tag.sort` variable if it exists, or lexicographic order otherwise. See linkgit:git-config[1]. +-i:: +--ignore-case:: + Sorting and filtering tags are case insensitive. + --column[=<options>]:: --no-column:: Display tag listing in columns. See configuration variable diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index d590edcebd..0b8075dad9 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -8,7 +8,7 @@ git-verify-tag - Check the GPG signature of tags SYNOPSIS -------- [verse] -'git verify-tag' <tag>... +'git verify-tag' [--format=<format>] <tag>... DESCRIPTION ----------- diff --git a/Documentation/git.txt b/Documentation/git.txt index bdab261515..4f208fab92 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -1162,30 +1162,20 @@ of clones and fetches. cloning a repository to make a backup). `GIT_ALLOW_PROTOCOL`:: - If set, provide a colon-separated list of protocols which are - allowed to be used with fetch/push/clone. This is useful to - restrict recursive submodule initialization from an untrusted - repository. Any protocol not mentioned will be disallowed (i.e., - this is a whitelist, not a blacklist). If the variable is not - set at all, all protocols are enabled. The protocol names - currently used by git are: - - - `file`: any local file-based path (including `file://` URLs, - or local paths) - - - `git`: the anonymous git protocol over a direct TCP - connection (or proxy, if configured) - - - `ssh`: git over ssh (including `host:path` syntax, - `ssh://`, etc). - - - `http`: git over http, both "smart http" and "dumb http". - Note that this does _not_ include `https`; if you want both, - you should specify both as `http:https`. - - - any external helpers are named by their protocol (e.g., use - `hg` to allow the `git-remote-hg` helper) - + If set to a colon-separated list of protocols, behave as if + `protocol.allow` is set to `never`, and each of the listed + protocols has `protocol.<name>.allow` set to `always` + (overriding any existing configuration). In other words, any + protocol not mentioned will be disallowed (i.e., this is a + whitelist, not a blacklist). See the description of + `protocol.allow` in linkgit:git-config[1] for more details. + +`GIT_PROTOCOL_FROM_USER`:: + Set to 0 to prevent protocols used by fetch/push/clone which are + configured to the `user` state. This is useful to restrict recursive + submodule initialization from an untrusted repository or for programs + which feed potentially-untrusted URLS to git commands. See + linkgit:git-config[1] for more details. Discussion[[Discussion]] ------------------------ diff --git a/Documentation/giteveryday.txt b/Documentation/giteveryday.txt index 35473ad02f..10c8ff93c0 100644 --- a/Documentation/giteveryday.txt +++ b/Documentation/giteveryday.txt @@ -307,9 +307,16 @@ master or exposed as a part of a stable branch. <9> backport a critical fix. <10> create a signed tag. <11> make sure master was not accidentally rewound beyond that -already pushed out. `ko` shorthand points at the Git maintainer's +already pushed out. +<12> In the output from `git show-branch`, `master` should have +everything `ko/master` has, and `next` should have +everything `ko/next` has, etc. +<13> push out the bleeding edge, together with new tags that point +into the pushed history. + +In this example, the `ko` shorthand points at the Git maintainer's repository at kernel.org, and looks like this: -+ + ------------ (in .git/config) [remote "ko"] @@ -320,12 +327,6 @@ repository at kernel.org, and looks like this: push = +refs/heads/pu push = refs/heads/maint ------------ -+ -<12> In the output from `git show-branch`, `master` should have -everything `ko/master` has, and `next` should have -everything `ko/next` has, etc. -<13> push out the bleeding edge, together with new tags that point -into the pushed history. Repository Administration[[ADMINISTRATION]] diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index e382dd96df..ca96c281d1 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -178,19 +178,21 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to History ------- Gitk was the first graphical repository browser. It's written in -tcl/tk and started off in a separate repository but was later merged -into the main Git repository. +tcl/tk. +'gitk' is actually maintained as an independent project, but stable +versions are distributed as part of the Git suite for the convenience +of end users. + +gitk-git/ comes from Paul Mackerras's gitk project: + + git://ozlabs.org/~paulus/gitk SEE ALSO -------- 'qgit(1)':: A repository browser written in C++ using Qt. -'gitview(1)':: - A repository browser written in Python using Gtk. It's based on - 'bzrk(1)' and distributed in the contrib area of the Git repository. - 'tig(1)':: A minimal repository browser and Git tool output highlighter written in C using Ncurses. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 3bcee2ddb1..47b286b33e 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -199,6 +199,8 @@ endif::git-rev-list[] than given and there are spaces on its left, use those spaces - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)' respectively, but padding both sides (i.e. the text is centered) +-%(trailers): display the trailers of the body as interpreted by + linkgit:git-interpret-trailers[1] NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt index 28f5a8b715..a3f020cd9e 100644 --- a/Documentation/technical/api-hashmap.txt +++ b/Documentation/technical/api-hashmap.txt @@ -188,7 +188,9 @@ Returns the removed entry, or NULL if not found. `void *hashmap_iter_next(struct hashmap_iter *iter)`:: `void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter)`:: - Used to iterate over all entries of a hashmap. + Used to iterate over all entries of a hashmap. Note that it is + not safe to add or remove entries to the hashmap while + iterating. + `hashmap_iter_init` initializes a `hashmap_iter` structure. + diff --git a/Documentation/technical/api-setup.txt b/Documentation/technical/api-setup.txt index 540e455689..eb1fa9853e 100644 --- a/Documentation/technical/api-setup.txt +++ b/Documentation/technical/api-setup.txt @@ -27,8 +27,6 @@ parse_pathspec(). This function takes several arguments: - prefix and args come from cmd_* functions -get_pathspec() is obsolete and should never be used in new code. - parse_pathspec() helps catch unsupported features and reject them politely. At a lower level, different pathspec-related functions may not support the same set of features. Such pathspec-sensitive diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt index 941fa178dd..3dce003fda 100644 --- a/Documentation/technical/api-submodule-config.txt +++ b/Documentation/technical/api-submodule-config.txt @@ -47,16 +47,20 @@ Functions Can be passed to the config parsing infrastructure to parse local (worktree) submodule configurations. -`const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path)`:: +`const struct submodule *submodule_from_path(const unsigned char *treeish_name, const char *path)`:: - Lookup values for one submodule by its commit_sha1 and path. + Given a tree-ish in the superproject and a path, return the + submodule that is bound at the path in the named tree. -`const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name)`:: +`const struct submodule *submodule_from_name(const unsigned char *treeish_name, const char *name)`:: The same as above but lookup by name. -If given the null_sha1 as commit_sha1 the local configuration of a -submodule will be returned (e.g. consolidated values from local git +Whenever a submodule configuration is parsed in `parse_submodule_config_option` +via e.g. `gitmodules_config()`, it will overwrite the null_sha1 entry. +So in the normal case, when HEAD:.gitmodules is parsed first and then overlayed +with the repository configuration, the null_sha1 entry contains the local +configuration of a submodule (e.g. consolidated values from local git configuration and the .gitmodules file in the worktree). For an example usage see test-submodule-config.c. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 5e07454572..bc29298678 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4395,6 +4395,10 @@ itself! Git Glossary ============ +[[git-explained]] +Git explained +------------- + include::glossary-content.txt[] [[git-quick-start]] @@ -4636,6 +4640,10 @@ $ git gc Appendix B: Notes and todo list for this manual =============================================== +[[todo-list]] +Todo list +--------- + This is a work in progress. The basic requirements: diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 706946c7cd..f95b04bb36 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.11.1 +DEF_VER=v2.11.GIT LF=' ' @@ -12,7 +12,7 @@ if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d ${GIT_DIR:-.git} -o -f .git && - VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) && + VN=$(git describe --match "v[0-9]*" HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) @@ -279,6 +279,9 @@ all:: # is a simplified version of the merge sort used in glibc. This is # recommended if Git triggers O(n^2) behavior in your platform's qsort(). # +# Define HAVE_ISO_QSORT_S if your platform provides a qsort_s() that's +# compatible with the one described in C11 Annex K. +# # Define UNRELIABLE_FSTAT if your system's fstat does not return the same # information on a not yet closed file that lstat would return for the same # file after it was closed. @@ -338,11 +341,6 @@ all:: # # Define NATIVE_CRLF if your platform uses CRLF for line endings. # -# Define XDL_FAST_HASH to use an alternative line-hashing method in -# the diff algorithm. It gives a nice speedup if your processor has -# fast unaligned word loads. Does NOT work on big-endian systems! -# Enabled by default on x86_64. -# # Define GIT_USER_AGENT if you want to change how git identifies itself during # network interactions. The default is "git/$(GIT_VERSION)". # @@ -527,12 +525,10 @@ SCRIPT_LIB += git-sh-setup SCRIPT_LIB += git-sh-i18n SCRIPT_PERL += git-add--interactive.perl -SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl SCRIPT_PERL += git-cvsserver.perl -SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl @@ -888,6 +884,7 @@ BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/difftool.o BUILTIN_OBJS += builtin/fast-export.o BUILTIN_OBJS += builtin/fetch-pack.o BUILTIN_OBJS += builtin/fetch.o @@ -1423,6 +1420,11 @@ ifdef INTERNAL_QSORT COMPAT_CFLAGS += -DINTERNAL_QSORT COMPAT_OBJS += compat/qsort.o endif +ifdef HAVE_ISO_QSORT_S + COMPAT_CFLAGS += -DHAVE_ISO_QSORT_S +else + COMPAT_OBJS += compat/qsort_s.o +endif ifdef RUNTIME_PREFIX COMPAT_CFLAGS += -DRUNTIME_PREFIX endif @@ -1485,10 +1487,6 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS MSGFMT += --check --statistics endif -ifneq (,$(XDL_FAST_HASH)) - BASIC_CFLAGS += -DXDL_FAST_HASH -endif - ifdef GMTIME_UNRELIABLE_ERRORS COMPAT_OBJS += compat/gmtime.o BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS @@ -1825,7 +1823,7 @@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES git.res: git.rc GIT-VERSION-FILE $(QUIET_RC)$(RC) \ $(join -DMAJOR= -DMINOR=, $(wordlist 1,2,$(subst -, ,$(subst ., ,$(GIT_VERSION))))) \ - -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" $< -o $@ + -DGIT_VERSION="\\\"$(GIT_VERSION)\\\"" -i $< -o $@ # This makes sure we depend on the NO_PERL setting itself. $(SCRIPT_PERL_GEN): GIT-BUILD-OPTIONS @@ -2055,7 +2053,7 @@ git-%$X: %.o GIT-LDFLAGS $(GITLIBS) git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) $(IMAP_SEND_LDFLAGS) + $(IMAP_SEND_LDFLAGS) $(LIBS) git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ @@ -2114,7 +2112,8 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ --keyword=_ --keyword=N_ --keyword="Q_:1,2" XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ --keyword=gettextln --keyword=eval_gettextln -XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl +XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ + --keyword=__ --keyword=N__ --keyword="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh @@ -1 +1 @@ -Documentation/RelNotes/2.11.1.txt
\ No newline at end of file +Documentation/RelNotes/2.12.0.txt
\ No newline at end of file @@ -11,46 +11,83 @@ int is_directory(const char *path) return (!stat(path, &st) && S_ISDIR(st.st_mode)); } +/* removes the last path component from 'path' except if 'path' is root */ +static void strip_last_component(struct strbuf *path) +{ + size_t offset = offset_1st_component(path->buf); + size_t len = path->len; + + /* Find start of the last component */ + while (offset < len && !is_dir_sep(path->buf[len - 1])) + len--; + /* Skip sequences of multiple path-separators */ + while (offset < len && is_dir_sep(path->buf[len - 1])) + len--; + + strbuf_setlen(path, len); +} + +/* get (and remove) the next component in 'remaining' and place it in 'next' */ +static void get_next_component(struct strbuf *next, struct strbuf *remaining) +{ + char *start = NULL; + char *end = NULL; + + strbuf_reset(next); + + /* look for the next component */ + /* Skip sequences of multiple path-separators */ + for (start = remaining->buf; is_dir_sep(*start); start++) + ; /* nothing */ + /* Find end of the path component */ + for (end = start; *end && !is_dir_sep(*end); end++) + ; /* nothing */ + + strbuf_add(next, start, end - start); + /* remove the component from 'remaining' */ + strbuf_remove(remaining, 0, end - remaining->buf); +} + +/* copies root part from remaining to resolved, canonicalizing it on the way */ +static void get_root_part(struct strbuf *resolved, struct strbuf *remaining) +{ + int offset = offset_1st_component(remaining->buf); + + strbuf_reset(resolved); + strbuf_add(resolved, remaining->buf, offset); +#ifdef GIT_WINDOWS_NATIVE + convert_slashes(resolved->buf); +#endif + strbuf_remove(remaining, 0, offset); +} + /* We allow "recursive" symbolic links. Only within reason, though. */ -#define MAXDEPTH 5 +#ifndef MAXSYMLINKS +#define MAXSYMLINKS 32 +#endif /* * Return the real path (i.e., absolute path, with symlinks resolved * and extra slashes removed) equivalent to the specified path. (If * you want an absolute path but don't mind links, use - * absolute_path().) The return value is a pointer to a static - * buffer. + * absolute_path().) Places the resolved realpath in the provided strbuf. * - * The input and all intermediate paths must be shorter than MAX_PATH. * 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. If die_on_error is set, then die with an * informative error message if there is a problem. Otherwise, return * NULL on errors (without generating any output). - * - * If path is our buffer, then return path, as it's already what the - * user wants. */ -static const char *real_path_internal(const char *path, int die_on_error) +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error) { - static struct strbuf sb = STRBUF_INIT; + struct strbuf remaining = STRBUF_INIT; + struct strbuf next = STRBUF_INIT; + struct strbuf symlink = STRBUF_INIT; char *retval = NULL; - - /* - * If we have to temporarily chdir(), store the original CWD - * here so that we can chdir() back to it at the end of the - * function: - */ - struct strbuf cwd = STRBUF_INIT; - - int depth = MAXDEPTH; - char *last_elem = NULL; + int num_symlinks = 0; struct stat st; - /* We've already done it */ - if (path == sb.buf) - return path; - if (!*path) { if (die_on_error) die("The empty string is not a valid path"); @@ -58,86 +95,136 @@ static const char *real_path_internal(const char *path, int die_on_error) goto error_out; } - strbuf_reset(&sb); - strbuf_addstr(&sb, path); - - while (depth--) { - if (!is_directory(sb.buf)) { - char *last_slash = find_last_dir_sep(sb.buf); - if (last_slash) { - last_elem = xstrdup(last_slash + 1); - strbuf_setlen(&sb, last_slash - sb.buf + 1); - } else { - last_elem = xmemdupz(sb.buf, sb.len); - strbuf_reset(&sb); - } + strbuf_addstr(&remaining, path); + get_root_part(resolved, &remaining); + + if (!resolved->len) { + /* relative path; can use CWD as the initial resolved path */ + if (strbuf_getcwd(resolved)) { + if (die_on_error) + die_errno("unable to get current working directory"); + else + goto error_out; } + } - if (sb.len) { - if (!cwd.len && strbuf_getcwd(&cwd)) { + /* Iterate over the remaining path components */ + while (remaining.len > 0) { + get_next_component(&next, &remaining); + + if (next.len == 0) { + continue; /* empty component */ + } else if (next.len == 1 && !strcmp(next.buf, ".")) { + continue; /* '.' component */ + } else if (next.len == 2 && !strcmp(next.buf, "..")) { + /* '..' component; strip the last path component */ + strip_last_component(resolved); + continue; + } + + /* append the next component and resolve resultant path */ + if (!is_dir_sep(resolved->buf[resolved->len - 1])) + strbuf_addch(resolved, '/'); + strbuf_addbuf(resolved, &next); + + if (lstat(resolved->buf, &st)) { + /* error out unless this was the last component */ + if (errno != ENOENT || remaining.len) { if (die_on_error) - die_errno("Could not get current working directory"); + die_errno("Invalid path '%s'", + resolved->buf); else goto error_out; } + } else if (S_ISLNK(st.st_mode)) { + ssize_t len; + strbuf_reset(&symlink); + + if (num_symlinks++ > MAXSYMLINKS) { + errno = ELOOP; - if (chdir(sb.buf)) { if (die_on_error) - die_errno("Could not switch to '%s'", - sb.buf); + die("More than %d nested symlinks " + "on path '%s'", MAXSYMLINKS, path); else goto error_out; } - } - if (strbuf_getcwd(&sb)) { - if (die_on_error) - die_errno("Could not get current working directory"); - else - goto error_out; - } - if (last_elem) { - if (sb.len && !is_dir_sep(sb.buf[sb.len - 1])) - strbuf_addch(&sb, '/'); - strbuf_addstr(&sb, last_elem); - free(last_elem); - last_elem = NULL; - } - - if (!lstat(sb.buf, &st) && S_ISLNK(st.st_mode)) { - struct strbuf next_sb = STRBUF_INIT; - ssize_t len = strbuf_readlink(&next_sb, sb.buf, 0); + len = strbuf_readlink(&symlink, resolved->buf, + st.st_size); if (len < 0) { if (die_on_error) die_errno("Invalid symlink '%s'", - sb.buf); + resolved->buf); else goto error_out; } - strbuf_swap(&sb, &next_sb); - strbuf_release(&next_sb); - } else - break; + + if (is_absolute_path(symlink.buf)) { + /* absolute symlink; set resolved to root */ + get_root_part(resolved, &symlink); + } else { + /* + * relative symlink + * strip off the last component since it will + * be replaced with the contents of the symlink + */ + strip_last_component(resolved); + } + + /* + * if there are still remaining components to resolve + * then append them to symlink + */ + if (remaining.len) { + strbuf_addch(&symlink, '/'); + strbuf_addbuf(&symlink, &remaining); + } + + /* + * use the symlink as the remaining components that + * need to be resloved + */ + strbuf_swap(&symlink, &remaining); + } } - retval = sb.buf; + retval = resolved->buf; + error_out: - free(last_elem); - if (cwd.len && chdir(cwd.buf)) - die_errno("Could not change back to '%s'", cwd.buf); - strbuf_release(&cwd); + strbuf_release(&remaining); + strbuf_release(&next); + strbuf_release(&symlink); + + if (!retval) + strbuf_reset(resolved); return retval; } const char *real_path(const char *path) { - return real_path_internal(path, 1); + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 1); } const char *real_path_if_valid(const char *path) { - return real_path_internal(path, 0); + static struct strbuf realpath = STRBUF_INIT; + return strbuf_realpath(&realpath, path, 0); +} + +char *real_pathdup(const char *path) +{ + struct strbuf realpath = STRBUF_INIT; + char *retval = NULL; + + if (strbuf_realpath(&realpath, path, 0)) + retval = strbuf_detach(&realpath, NULL); + + strbuf_release(&realpath); + + return retval; } /* @@ -152,6 +239,13 @@ const char *absolute_path(const char *path) return sb.buf; } +char *absolute_pathdup(const char *path) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_add_absolute_path(&sb, 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 @@ -4688,7 +4688,7 @@ static int apply_patch(struct apply_state *state, state->index_file, LOCK_DIE_ON_ERROR); else - state->newfd = hold_locked_index(state->lock_file, 1); + state->newfd = hold_locked_index(state->lock_file, LOCK_DIE_ON_ERROR); } if (state->check_index && read_apply_cache(state) < 0) { @@ -747,7 +747,7 @@ static void handle_bad_merge_base(void) exit(3); } - fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n" + fprintf(stderr, _("Some %s revs are not ancestors of the %s rev.\n" "git bisect cannot work properly in this case.\n" "Maybe you mistook %s and %s revs?\n"), term_good, term_bad, term_good, term_bad); @@ -60,6 +60,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_difftool(int argc, const char **argv, const char *prefix); extern int cmd_fast_export(int argc, const char **argv, const char *prefix); extern int cmd_fetch(int argc, const char **argv, const char *prefix); extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index e8fb80b36e..9f53f020d0 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -361,7 +361,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_new_files = !take_worktree_changes && !refresh_only; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | diff --git a/builtin/am.c b/builtin/am.c index 826f18ba12..31fb60578f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1119,7 +1119,7 @@ static void refresh_and_write_cache(void) { struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write index file")); @@ -1976,7 +1976,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) return -1; lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); @@ -2016,7 +2016,7 @@ static int merge_tree(struct tree *tree) return -1; lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; diff --git a/builtin/blame.c b/builtin/blame.c index 3aae19a0f9..126b8c9e5b 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2601,8 +2601,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * and are only included here to get included in the "-h" * output: */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental indent-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, - { OPTION_LOWLEVEL_CALLBACK, 0, "compaction-heuristic", NULL, NULL, N_("Use an experimental blank-line-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), @@ -2650,7 +2649,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); - xdl_opts |= revs.diffopt.xdl_opts & (XDF_COMPACTION_HEURISTIC | XDF_INDENT_HEURISTIC); + xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); diff --git a/builtin/branch.c b/builtin/branch.c index 475707528a..9d30f55b0b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -512,15 +512,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - /* - * If no sorting parameter is given then we default to sorting - * by 'refname'. This would give us an alphabetically sorted - * array with the 'HEAD' ref at the beginning followed by - * local branches 'refs/heads/...' and finally remote-tacking - * branches 'refs/remotes/...'. - */ - if (!sorting) - sorting = ref_default_sorting(); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) @@ -645,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) const char *new_upstream = NULL; enum branch_track track; struct ref_filter filter; + int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { @@ -686,6 +678,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), 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_END(), }; @@ -723,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (filter.abbrev == -1) filter.abbrev = DEFAULT_ABBREV; + filter.ignore_case = icase; + finalize_colopts(&colopts, -1); if (filter.verbose) { if (explicitly_enable_column(colopts)) @@ -744,6 +739,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) filter.kind |= FILTER_REFS_DETACHED_HEAD; filter.name_patterns = argv; + /* + * If no sorting parameter is given then we default to sorting + * by 'refname'. This would give us an alphabetically sorted + * array with the 'HEAD' ref at the beginning followed by + * local branches 'refs/heads/...' and finally remote-tacking + * branches 'refs/remotes/...'. + */ + if (!sorting) + sorting = ref_default_sorting(); + sorting->ignore_case = icase; print_ref_list(&filter, sorting); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 30a49d9f42..07631d0c9c 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -205,7 +205,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (index_opt && !state.base_dir_len && !to_tempfile) { state.refresh_cache = 1; state.istate = &the_index; - newfd = hold_locked_index(&lock_file, 1); + newfd = hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); } /* Check out named files first */ diff --git a/builtin/checkout.c b/builtin/checkout.c index 512492aad9..bfe685c198 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -274,7 +274,7 @@ static int checkout_paths(const struct checkout_opts *opts, lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(&opts->pathspec) < 0) return error(_("index file corrupt")); @@ -467,7 +467,7 @@ static int merge_working_tree(const struct checkout_opts *opts, int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(NULL) < 0) return error(_("index file corrupt")); diff --git a/builtin/clean.c b/builtin/clean.c index 0371010afb..d6bc3aaaea 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -287,11 +287,11 @@ static void pretty_print_menus(struct string_list *menu_list) static void prompt_help_cmd(int singleton) { clean_print_color(CLEAN_COLOR_HELP); - printf_ln(singleton ? + printf(singleton ? _("Prompt help:\n" "1 - select a numbered item\n" "foo - select item based on unique prefix\n" - " - (empty) select nothing") : + " - (empty) select nothing\n") : _("Prompt help:\n" "1 - select a single item\n" "3-5 - select a range of items\n" @@ -299,7 +299,7 @@ static void prompt_help_cmd(int singleton) "foo - select item based on unique prefix\n" "-... - unselect specified items\n" "* - choose all items\n" - " - (empty) finish selecting")); + " - (empty) finish selecting\n")); clean_print_color(CLEAN_COLOR_RESET); } @@ -508,7 +508,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf_ln(_("Huh (%s)?"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), (*ptr)->buf); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -774,7 +774,7 @@ static int ask_each_cmd(void) static int quit_cmd(void) { string_list_clear(&del_list, 0); - printf_ln(_("Bye.")); + printf(_("Bye.\n")); return MENU_RETURN_NO_LOOP; } diff --git a/builtin/clone.c b/builtin/clone.c index 6c76a6ed66..3f63edbbf9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -99,7 +99,7 @@ static struct option builtin_clone_options[] = { OPT_STRING(0, "shallow-since", &option_since, N_("time"), N_("create a shallow clone since a specific time")), OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"), - N_("deepen history of shallow clone by excluding rev")), + N_("deepen history of shallow clone, excluding rev")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules, @@ -170,7 +170,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) strbuf_addstr(&path, repo); raw = get_repo_path_1(&path, is_bundle); - canon = raw ? xstrdup(absolute_path(raw)) : NULL; + canon = raw ? absolute_pathdup(raw) : NULL; strbuf_release(&path); return canon; } @@ -711,7 +711,7 @@ static int checkout(int submodule_progress) setup_work_tree(); lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof opts); opts.update = 1; @@ -894,7 +894,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) - repo = xstrdup(absolute_path(repo_name)); + repo = absolute_pathdup(repo_name); else if (!strchr(repo_name, ':')) die(_("repository '%s' does not exist"), repo_name); else diff --git a/builtin/commit.c b/builtin/commit.c index 276c74034e..711f96cc43 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -351,7 +351,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (interactive) { char *old_index_env = NULL; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); @@ -396,7 +396,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * (B) on failure, rollback the real index. */ if (all || (also && pathspec.nr)) { - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); @@ -416,7 +416,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * We still need to refresh the index here. */ if (!only && !pathspec.nr) { - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); if (active_cache_changed || !cache_tree_fully_valid(active_cache_tree)) @@ -468,7 +468,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (read_cache() < 0) die(_("cannot read the index")); - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); update_main_cache_tree(WRITE_TREE_SILENT); @@ -790,7 +790,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, strbuf_stripspace(&sb, 0); if (signoff) - append_signoff(&sb, ignore_non_trailer(&sb), 0); + append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0); if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); diff --git a/builtin/difftool.c b/builtin/difftool.c new file mode 100644 index 0000000000..b5e85ab079 --- /dev/null +++ b/builtin/difftool.c @@ -0,0 +1,692 @@ +/* + * "git difftool" builtin command + * + * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible + * git-difftool--helper script. + * + * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git. + * The GIT_DIFF* variables are exported for use by git-difftool--helper. + * + * Any arguments that are unknown to this script are forwarded to 'git diff'. + * + * Copyright (C) 2016 Johannes Schindelin + */ +#include "cache.h" +#include "builtin.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "parse-options.h" +#include "argv-array.h" +#include "strbuf.h" +#include "lockfile.h" +#include "dir.h" + +static char *diff_gui_tool; +static int trust_exit_code; + +static const char *const builtin_difftool_usage[] = { + N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"), + NULL +}; + +static int difftool_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.guitool")) { + diff_gui_tool = xstrdup(value); + return 0; + } + + if (!strcmp(var, "difftool.trustexitcode")) { + trust_exit_code = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static int print_tool_help(void) +{ + const char *argv[] = { "mergetool", "--tool-help=diff", NULL }; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int parse_index_info(char *p, int *mode1, int *mode2, + struct object_id *oid1, struct object_id *oid2, + char *status) +{ + if (*p != ':') + return error("expected ':', got '%c'", *p); + *mode1 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *mode2 = (int)strtol(p + 1, &p, 8); + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid1)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + if (get_oid_hex(++p, oid2)) + return error("expected object ID, got '%s'", p + 1); + p += GIT_SHA1_HEXSZ; + if (*p != ' ') + return error("expected ' ', got '%c'", *p); + *status = *++p; + if (!*status) + return error("missing status"); + if (p[1] && !isdigit(p[1])) + return error("unexpected trailer: '%s'", p + 1); + return 0; +} + +/* + * Remove any trailing slash from $workdir + * before starting to avoid double slashes in symlink targets. + */ +static void add_path(struct strbuf *buf, size_t base_len, const char *path) +{ + strbuf_setlen(buf, base_len); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addch(buf, '/'); + strbuf_addstr(buf, path); +} + +/* + * Determine whether we can simply reuse the file in the worktree. + */ +static int use_wt_file(const char *workdir, const char *name, + struct object_id *oid) +{ + struct strbuf buf = STRBUF_INIT; + struct stat st; + int use = 0; + + strbuf_addstr(&buf, workdir); + add_path(&buf, buf.len, name); + + if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) { + struct object_id wt_oid; + int fd = open(buf.buf, O_RDONLY); + + if (fd >= 0 && + !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + if (is_null_oid(oid)) { + oidcpy(oid, &wt_oid); + use = 1; + } else if (!oidcmp(oid, &wt_oid)) + use = 1; + } + } + + strbuf_release(&buf); + + return use; +} + +struct working_tree_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int working_tree_entry_cmp(struct working_tree_entry *a, + struct working_tree_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +/* + * The `left` and `right` entries hold paths for the symlinks hashmap, + * and a SHA-1 surrounded by brief text for submodules. + */ +struct pair_entry { + struct hashmap_entry entry; + char left[PATH_MAX], right[PATH_MAX]; + const char path[FLEX_ARRAY]; +}; + +static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +{ + return strcmp(a->path, b->path); +} + +static void add_left_or_right(struct hashmap *map, const char *path, + const char *content, int is_right) +{ + struct pair_entry *e, *existing; + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(e, strhash(path)); + existing = hashmap_get(map, e, NULL); + if (existing) { + free(e); + e = existing; + } else { + e->left[0] = e->right[0] = '\0'; + hashmap_add(map, e); + } + strlcpy(is_right ? e->right : e->left, content, PATH_MAX); +} + +struct path_entry { + struct hashmap_entry entry; + char path[FLEX_ARRAY]; +}; + +static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +{ + return strcmp(a->path, key ? key : b->path); +} + +static void changed_files(struct hashmap *result, const char *index_path, + const char *workdir) +{ + struct child_process update_index = CHILD_PROCESS_INIT; + struct child_process diff_files = CHILD_PROCESS_INIT; + struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()), *env[] = { + NULL, NULL + }; + FILE *fp; + + strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); + env[0] = index_env.buf; + + argv_array_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); + update_index.no_stdin = 1; + update_index.no_stdout = 1; + update_index.no_stderr = 1; + update_index.git_cmd = 1; + update_index.use_shell = 0; + update_index.clean_on_exit = 1; + update_index.dir = workdir; + update_index.env = env; + /* Ignore any errors of update-index */ + run_command(&update_index); + + argv_array_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); + diff_files.no_stdin = 1; + diff_files.git_cmd = 1; + diff_files.use_shell = 0; + diff_files.clean_on_exit = 1; + diff_files.out = -1; + diff_files.dir = workdir; + diff_files.env = env; + if (start_command(&diff_files)) + die("could not obtain raw diff"); + fp = xfdopen(diff_files.out, "r"); + while (!strbuf_getline_nul(&buf, fp)) { + struct path_entry *entry; + FLEX_ALLOC_STR(entry, path, buf.buf); + hashmap_entry_init(entry, strhash(buf.buf)); + hashmap_add(result, entry); + } + if (finish_command(&diff_files)) + die("diff-files did not exit properly"); + strbuf_release(&index_env); + strbuf_release(&buf); +} + +static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, tmpdir); + remove_dir_recursively(&buf, 0); + if (exit_code) + warning(_("failed: %d"), exit_code); + exit(exit_code); +} + +static int ensure_leading_directories(char *path) +{ + switch (safe_create_leading_directories(path)) { + case SCLD_OK: + case SCLD_EXISTS: + return 0; + default: + return error(_("could not create leading directories " + "of '%s'"), path); + } +} + +static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, + int argc, const char **argv) +{ + char tmpdir[PATH_MAX]; + struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; + struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; + struct strbuf wtdir = STRBUF_INIT; + size_t ldir_len, rdir_len, wtdir_len; + struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1); + const char *workdir, *tmp; + int ret = 0, i; + FILE *fp; + 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; + struct child_process child = CHILD_PROCESS_INIT; + const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; + struct hashmap wt_modified, tmp_modified; + int indices_loaded = 0; + + workdir = get_git_work_tree(); + + /* Setup temp directories */ + tmp = getenv("TMPDIR"); + xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); + if (!mkdtemp(tmpdir)) + return error("could not create '%s'", tmpdir); + strbuf_addf(&ldir, "%s/left/", tmpdir); + strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_addstr(&wtdir, workdir); + if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) + strbuf_addch(&wtdir, '/'); + mkdir(ldir.buf, 0700); + mkdir(rdir.buf, 0700); + + memset(&wtindex, 0, sizeof(wtindex)); + + memset(&lstate, 0, sizeof(lstate)); + lstate.base_dir = ldir.buf; + lstate.base_dir_len = ldir.len; + lstate.force = 1; + memset(&rstate, 0, sizeof(rstate)); + rstate.base_dir = rdir.buf; + rstate.base_dir_len = rdir.len; + rstate.force = 1; + + ldir_len = ldir.len; + rdir_len = rdir.len; + wtdir_len = wtdir.len; + + hashmap_init(&working_tree_dups, + (hashmap_cmp_fn)working_tree_entry_cmp, 0); + hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + + child.no_stdin = 1; + child.git_cmd = 1; + child.use_shell = 0; + child.clean_on_exit = 1; + child.dir = prefix; + child.out = -1; + argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); + for (i = 0; i < argc; i++) + argv_array_push(&child.args, argv[i]); + if (start_command(&child)) + die("could not obtain raw diff"); + fp = xfdopen(child.out, "r"); + + /* Build index info for left and right sides of the diff */ + i = 0; + while (!strbuf_getline_nul(&info, fp)) { + int lmode, rmode; + struct object_id loid, roid; + char status; + const char *src_path, *dst_path; + size_t src_path_len, dst_path_len; + + if (starts_with(info.buf, "::")) + die(N_("combined diff formats('-c' and '--cc') are " + "not supported in\n" + "directory diff mode('-d' and '--dir-diff').")); + + if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, + &status)) + break; + if (strbuf_getline_nul(&lpath, fp)) + break; + src_path = lpath.buf; + src_path_len = lpath.len; + + i++; + if (status != 'C' && status != 'R') { + dst_path = src_path; + dst_path_len = src_path_len; + } else { + if (strbuf_getline_nul(&rpath, fp)) + break; + dst_path = rpath.buf; + dst_path_len = rpath.len; + } + + if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&loid)); + add_left_or_right(&submodules, src_path, buf.buf, 0); + strbuf_reset(&buf); + strbuf_addf(&buf, "Subproject commit %s", + oid_to_hex(&roid)); + if (!oidcmp(&loid, &roid)) + strbuf_addstr(&buf, "-dirty"); + add_left_or_right(&submodules, dst_path, buf.buf, 1); + continue; + } + + if (S_ISLNK(lmode)) { + char *content = read_sha1_file(loid.hash, &type, &size); + add_left_or_right(&symlinks2, src_path, content, 0); + free(content); + } + + if (S_ISLNK(rmode)) { + char *content = read_sha1_file(roid.hash, &type, &size); + add_left_or_right(&symlinks2, dst_path, content, 1); + free(content); + } + + if (lmode && status != 'C') { + ce->ce_mode = lmode; + oidcpy(&ce->oid, &loid); + strcpy(ce->name, src_path); + ce->ce_namelen = src_path_len; + if (checkout_entry(ce, &lstate, NULL)) + return error("could not write '%s'", src_path); + } + + if (rmode) { + struct working_tree_entry *entry; + + /* Avoid duplicate working_tree entries */ + FLEX_ALLOC_STR(entry, path, dst_path); + hashmap_entry_init(entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, entry, NULL)) { + free(entry); + continue; + } + hashmap_add(&working_tree_dups, entry); + + if (!use_wt_file(workdir, dst_path, &roid)) { + ce->ce_mode = rmode; + oidcpy(&ce->oid, &roid); + strcpy(ce->name, dst_path); + ce->ce_namelen = dst_path_len; + if (checkout_entry(ce, &rstate, NULL)) + return error("could not write '%s'", + dst_path); + } else if (!is_null_oid(&roid)) { + /* + * Changes in the working tree need special + * treatment since they are not part of the + * index. + */ + struct cache_entry *ce2 = + make_cache_entry(rmode, roid.hash, + dst_path, 0, 0); + + add_index_entry(&wtindex, ce2, + ADD_CACHE_JUST_APPEND); + + add_path(&rdir, rdir_len, dst_path); + if (ensure_leading_directories(rdir.buf)) + return error("could not create " + "directory for '%s'", + dst_path); + add_path(&wtdir, wtdir_len, dst_path); + if (symlinks) { + if (symlink(wtdir.buf, rdir.buf)) { + ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } else { + struct stat st; + if (stat(wtdir.buf, &st)) + st.st_mode = 0644; + if (copy_file(rdir.buf, wtdir.buf, + st.st_mode)) { + ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); + goto finish; + } + } + } + } + } + + if (finish_command(&child)) { + ret = error("error occurred running diff --raw"); + goto finish; + } + + if (!i) + return 0; + + /* + * Changes to submodules require special treatment.This loop writes a + * temporary file to both the left and right directories to show the + * change in the recorded SHA1 for the submodule. + */ + hashmap_iter_init(&submodules, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + /* + * Symbolic links require special treatment.The standard "git diff" + * shows only the link itself, not the contents of the link target. + * This loop replicates that behavior. + */ + hashmap_iter_init(&symlinks2, &iter); + while ((entry = hashmap_iter_next(&iter))) { + if (*entry->left) { + add_path(&ldir, ldir_len, entry->path); + ensure_leading_directories(ldir.buf); + write_file(ldir.buf, "%s", entry->left); + } + if (*entry->right) { + add_path(&rdir, rdir_len, entry->path); + ensure_leading_directories(rdir.buf); + write_file(rdir.buf, "%s", entry->right); + } + } + + strbuf_release(&buf); + + strbuf_setlen(&ldir, ldir_len); + helper_argv[1] = ldir.buf; + strbuf_setlen(&rdir, rdir_len); + helper_argv[2] = rdir.buf; + + if (extcmd) { + helper_argv[0] = extcmd; + flags = 0; + } else + setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); + rc = run_command_v_opt(helper_argv, flags); + + /* + * If the diff includes working copy files and those + * files were modified during the diff, then the changes + * should be copied back to the working tree. + * Do not copy back files when symlinks are used and the + * external tool did not replace the original link with a file. + * + * These hashes are loaded lazily since they aren't needed + * in the common case of --symlinks and the difftool updating + * files through the symlink. + */ + hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, + wtindex.cache_nr); + + for (i = 0; i < wtindex.cache_nr; i++) { + struct hashmap_entry dummy; + const char *name = wtindex.cache[i]->name; + struct stat st; + + add_path(&rdir, rdir_len, name); + if (lstat(rdir.buf, &st)) + continue; + + if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) + continue; + + if (!indices_loaded) { + static struct lock_file lock; + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/wtindex", tmpdir); + if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || + write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { + ret = error("could not write %s", buf.buf); + rollback_lock_file(&lock); + goto finish; + } + changed_files(&wt_modified, buf.buf, workdir); + strbuf_setlen(&rdir, rdir_len); + changed_files(&tmp_modified, buf.buf, rdir.buf); + add_path(&rdir, rdir_len, name); + indices_loaded = 1; + } + + hashmap_entry_init(&dummy, strhash(name)); + if (hashmap_get(&tmp_modified, &dummy, name)) { + add_path(&wtdir, wtdir_len, name); + if (hashmap_get(&wt_modified, &dummy, name)) { + warning(_("both files modified: '%s' and '%s'."), + wtdir.buf, rdir.buf); + warning(_("working tree file has been left.")); + warning("%s", ""); + err = 1; + } else if (unlink(wtdir.buf) || + copy_file(wtdir.buf, rdir.buf, st.st_mode)) + warning_errno(_("could not copy '%s' to '%s'"), + rdir.buf, wtdir.buf); + } + } + + if (err) { + warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("you may want to cleanup or recover these.")); + exit(1); + } else + exit_cleanup(tmpdir, rc); + +finish: + free(ce); + strbuf_release(&ldir); + strbuf_release(&rdir); + strbuf_release(&wtdir); + strbuf_release(&buf); + + return ret; +} + +static int run_file_diff(int prompt, const char *prefix, + int argc, const char **argv) +{ + struct argv_array args = ARGV_ARRAY_INIT; + const char *env[] = { + "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, + NULL + }; + int ret = 0, i; + + if (prompt > 0) + env[2] = "GIT_DIFFTOOL_PROMPT=true"; + else if (!prompt) + env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + + + argv_array_push(&args, "diff"); + for (i = 0; i < argc; i++) + argv_array_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); + exit(ret); +} + +int cmd_difftool(int argc, const char **argv, const char *prefix) +{ + int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, + tool_help = 0; + static char *difftool_cmd = NULL, *extcmd = NULL; + struct option builtin_difftool_options[] = { + OPT_BOOL('g', "gui", &use_gui_tool, + N_("use `diff.guitool` instead of `diff.tool`")), + OPT_BOOL('d', "dir-diff", &dir_diff, + N_("perform a full-directory diff")), + { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + N_("do not prompt before launching a diff tool"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, + NULL, 1 }, + OPT_BOOL(0, "symlinks", &symlinks, + N_("use symlinks in dir-diff mode")), + OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + N_("use the specified diff tool")), + OPT_BOOL(0, "tool-help", &tool_help, + N_("print a list of diff tools that may be used with " + "`--tool`")), + OPT_BOOL(0, "trust-exit-code", &trust_exit_code, + N_("make 'git-difftool' exit when an invoked diff " + "tool returns a non - zero exit code")), + OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + N_("specify a custom command for viewing diffs")), + OPT_END() + }; + + /* NEEDSWORK: once we no longer spawn anything, remove this */ + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + + git_config(difftool_config, NULL); + symlinks = has_symlinks; + + argc = parse_options(argc, argv, prefix, builtin_difftool_options, + builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + + if (tool_help) + return print_tool_help(); + + if (use_gui_tool && diff_gui_tool && *diff_gui_tool) + setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + else if (difftool_cmd) { + if (*difftool_cmd) + setenv("GIT_DIFF_TOOL", difftool_cmd, 1); + else + die(_("no <tool> given for --tool=<tool>")); + } + + if (extcmd) { + if (*extcmd) + setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1); + else + die(_("no <cmd> given for --extcmd=<cmd>")); + } + + setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE", + trust_exit_code ? "true" : "false", 1); + + /* + * In directory diff mode, 'git-difftool--helper' is called once + * to compare the a / b directories. In file diff mode, 'git diff' + * will invoke a separate instance of 'git-difftool--helper' for + * each file that changed. + */ + if (dir_diff) + return run_dir_diff(extcmd, symlinks, prefix, argc, argv); + return run_file_diff(prompt, prefix, argc, argv); +} diff --git a/builtin/fetch.c b/builtin/fetch.c index 1d77e58a00..b5ad09d046 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -122,7 +122,7 @@ static struct option builtin_fetch_options[] = { OPT_STRING(0, "shallow-since", &deepen_since, N_("time"), N_("deepen history of shallow repository based on time")), OPT_STRING_LIST(0, "shallow-exclude", &deepen_not, N_("revision"), - N_("deepen history of shallow clone by excluding rev")), + N_("deepen history of shallow clone, excluding rev")), OPT_INTEGER(0, "deepen", &deepen_relative, N_("deepen history of shallow clone")), { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL, @@ -1177,7 +1177,7 @@ static int add_remote_or_group(const char *name, struct string_list *list) git_config(get_remote_group, &g); if (list->nr == prev_nr) { struct remote *remote = remote_get(name); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 0)) return 0; string_list_append(list, remote->name); } diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 4e9f6c29bf..df41fa0350 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -18,7 +18,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) int i; const char *format = "%(objectname) %(objecttype)\t%(refname)"; struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - int maxcount = 0, quote_style = 0; + int maxcount = 0, quote_style = 0, icase = 0; struct ref_array array; struct ref_filter filter; @@ -43,6 +43,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_MERGED(&filter, N_("print only refs that are merged")), OPT_NO_MERGED(&filter, N_("print only refs that are not merged")), OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")), + OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END(), }; @@ -63,6 +64,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); + sorting->ignore_case = icase; + filter.ignore_case = icase; /* for warn_ambiguous_refs */ git_config(git_default_config, NULL); diff --git a/builtin/fsck.c b/builtin/fsck.c index f01b81eebf..1a5caccd0f 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -56,6 +56,23 @@ static const char *describe_object(struct object *obj) return buf.buf; } +static const char *printable_type(struct object *obj) +{ + const char *ret; + + if (obj->type == OBJ_NONE) { + enum object_type type = sha1_object_info(obj->oid.hash, NULL); + if (type > 0) + object_as_type(obj, type, 0); + } + + ret = typename(obj->type); + if (!ret) + ret = "unknown"; + + return ret; +} + static int fsck_config(const char *var, const char *value, void *cb) { if (strcmp(var, "fsck.skiplist") == 0) { @@ -83,7 +100,7 @@ static void objreport(struct object *obj, const char *msg_type, const char *err) { fprintf(stderr, "%s in %s %s: %s\n", - msg_type, typename(obj->type), describe_object(obj), err); + msg_type, printable_type(obj), describe_object(obj), err); } static int objerror(struct object *obj, const char *err) @@ -114,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf("broken link from %7s %s\n", (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); errors_found |= ERROR_REACHABLE; @@ -131,9 +148,9 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", - typename(parent->type), describe_object(parent)); + printable_type(parent), describe_object(parent)); printf(" to %7s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; } return 1; @@ -205,9 +222,7 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (has_sha1_pack(obj->oid.hash)) return; /* it is in pack - forget about it */ - if (connectivity_only && has_object_file(&obj->oid)) - return; - printf("missing %s %s\n", typename(obj->type), + printf("missing %s %s\n", printable_type(obj), describe_object(obj)); errors_found |= ERROR_REACHABLE; return; @@ -225,7 +240,7 @@ static void check_unreachable_object(struct object *obj) * to complain about it being unreachable (since it does * not exist). */ - if (!obj->parsed) + if (!(obj->flags & HAS_OBJ)) return; /* @@ -233,7 +248,7 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), + printf("unreachable %s %s\n", printable_type(obj), describe_object(obj)); return; } @@ -252,7 +267,7 @@ static void check_unreachable_object(struct object *obj) */ if (!obj->used) { if (show_dangling) - printf("dangling %s %s\n", typename(obj->type), + printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", @@ -326,7 +341,7 @@ static int fsck_obj(struct object *obj) if (verbose) fprintf(stderr, "Checking %s %s\n", - typename(obj->type), describe_object(obj)); + printable_type(obj), describe_object(obj)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); @@ -352,7 +367,7 @@ static int fsck_obj(struct object *obj) struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), + printf("tagged %s %s", printable_type(tag->tagged), describe_object(tag->tagged)); printf(" (%s) in %s\n", tag->tag, describe_object(&tag->object)); @@ -362,18 +377,6 @@ static int fsck_obj(struct object *obj) return 0; } -static int fsck_sha1(const unsigned char *sha1) -{ - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } - obj->flags |= HAS_OBJ; - return fsck_obj(obj); -} - static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten) { @@ -400,7 +403,7 @@ static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1, if (!is_null_sha1(sha1)) { obj = lookup_object(sha1); - if (obj) { + if (obj && (obj->flags & HAS_OBJ)) { if (timestamp && name_objects) add_decoration(fsck_walk_options.object_names, obj, @@ -488,9 +491,41 @@ static void get_default_heads(void) } } +static struct object *parse_loose_object(const unsigned char *sha1, + const char *path) +{ + struct object *obj; + void *contents; + enum object_type type; + unsigned long size; + int eaten; + + if (read_loose_object(path, sha1, &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); + + if (!eaten) + free(contents); + return obj; +} + static int fsck_loose(const unsigned char *sha1, const char *path, void *data) { - if (fsck_sha1(sha1)) + struct object *obj = parse_loose_object(sha1, path); + + if (!obj) { + errors_found |= ERROR_OBJECT; + error("%s: object corrupt or missing: %s", + sha1_to_hex(sha1), path); + return 0; /* keep checking other objects */ + } + + obj->flags = HAS_OBJ; + if (fsck_obj(obj)) errors_found |= ERROR_OBJECT; return 0; } @@ -584,6 +619,29 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } +static void mark_object_for_connectivity(const unsigned char *sha1) +{ + struct object *obj = lookup_unknown_object(sha1); + obj->flags |= HAS_OBJ; +} + +static int mark_loose_for_connectivity(const unsigned char *sha1, + const char *path, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + +static int mark_packed_for_connectivity(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + mark_object_for_connectivity(sha1); + return 0; +} + static char const * const fsck_usage[] = { N_("git fsck [<options>] [<object>...]"), NULL @@ -640,38 +698,41 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) git_config(fsck_config, NULL); fsck_head_link(); - if (!connectivity_only) { + if (connectivity_only) { + for_each_loose_object(mark_loose_for_connectivity, NULL, 0); + for_each_packed_object(mark_packed_for_connectivity, NULL, 0); + } else { fsck_object_dir(get_object_directory()); prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) fsck_object_dir(alt->path); - } - if (check_full) { - struct packed_git *p; - uint32_t total = 0, count = 0; - struct progress *progress = NULL; + if (check_full) { + struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; - prepare_packed_git(); + prepare_packed_git(); - if (show_progress) { + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress(_("Checking objects"), total); + } for (p = packed_git; p; p = p->next) { - if (open_pack_index(p)) - continue; - total += p->num_objects; + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } - - progress = start_progress(_("Checking objects"), total); + stop_progress(&progress); } - for (p = packed_git; p; p = p->next) { - /* verify gives error messages itself */ - if (verify_pack(p, fsck_obj_buffer, - progress, count)) - errors_found |= ERROR_PACK; - count += p->num_objects; - } - stop_progress(&progress); } heads = 0; @@ -681,9 +742,11 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (!get_sha1(arg, sha1)) { struct object *obj = lookup_object(sha1); - /* Error is printed by lookup_object(). */ - if (!obj) + if (!obj || !(obj->flags & HAS_OBJ)) { + error("%s: object missing", sha1_to_hex(sha1)); + errors_found |= ERROR_OBJECT; continue; + } obj->used = 1; if (name_objects) @@ -694,6 +757,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; } error("invalid parameter: expected sha1, got '%s'", arg); + errors_found |= ERROR_OBJECT; } /* @@ -701,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) * default ones from .git/refs. We also consider the index file * in this case (ie this implies --cache). */ - if (!heads) { + if (!argc) { get_default_heads(); keep_cache_objects = 1; } diff --git a/builtin/grep.c b/builtin/grep.c index 8887b6addb..2c727ef499 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -18,12 +18,22 @@ #include "quote.h" #include "dir.h" #include "pathspec.h" +#include "submodule.h" +#include "submodule-config.h" static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; +static const char *super_prefix; +static int recurse_submodules; +static struct argv_array submodule_options = ARGV_ARRAY_INIT; +static const char *parent_basename; + +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs); + #define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; @@ -174,7 +184,10 @@ static void *run(void *arg) break; opt->output_priv = w; - hit |= grep_source(opt, &w->source); + if (w->source.type == GREP_SOURCE_SUBMODULE) + hit |= grep_submodule_launch(opt, &w->source); + else + hit |= grep_source(opt, &w->source); grep_source_clear_data(&w->source); work_done(w); } @@ -300,6 +313,10 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, if (opt->relative && opt->prefix_length) { quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); + } else if (super_prefix) { + strbuf_add(&pathbuf, filename, tree_name_len); + strbuf_addstr(&pathbuf, super_prefix); + strbuf_addstr(&pathbuf, filename + tree_name_len); } else { strbuf_addstr(&pathbuf, filename); } @@ -328,10 +345,13 @@ static int grep_file(struct grep_opt *opt, const char *filename) { struct strbuf buf = STRBUF_INIT; - if (opt->relative && opt->prefix_length) + if (opt->relative && opt->prefix_length) { quote_path_relative(filename, opt->prefix, &buf); - else + } else { + if (super_prefix) + strbuf_addstr(&buf, super_prefix); strbuf_addstr(&buf, filename); + } #ifndef NO_PTHREADS if (num_threads) { @@ -378,31 +398,310 @@ static void run_pager(struct grep_opt *opt, const char *prefix) exit(status); } -static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) +static void compile_submodule_options(const struct grep_opt *opt, + const struct pathspec *pathspec, + int cached, int untracked, + int opt_exclude, int use_index, + int pattern_type_arg) +{ + struct grep_pat *pattern; + int i; + + if (recurse_submodules) + argv_array_push(&submodule_options, "--recurse-submodules"); + + if (cached) + argv_array_push(&submodule_options, "--cached"); + if (!use_index) + argv_array_push(&submodule_options, "--no-index"); + if (untracked) + argv_array_push(&submodule_options, "--untracked"); + if (opt_exclude > 0) + argv_array_push(&submodule_options, "--exclude-standard"); + + if (opt->invert) + argv_array_push(&submodule_options, "-v"); + if (opt->ignore_case) + argv_array_push(&submodule_options, "-i"); + if (opt->word_regexp) + argv_array_push(&submodule_options, "-w"); + switch (opt->binary) { + case GREP_BINARY_NOMATCH: + argv_array_push(&submodule_options, "-I"); + break; + case GREP_BINARY_TEXT: + argv_array_push(&submodule_options, "-a"); + break; + default: + break; + } + if (opt->allow_textconv) + argv_array_push(&submodule_options, "--textconv"); + if (opt->max_depth != -1) + argv_array_pushf(&submodule_options, "--max-depth=%d", + opt->max_depth); + if (opt->linenum) + argv_array_push(&submodule_options, "-n"); + if (!opt->pathname) + argv_array_push(&submodule_options, "-h"); + if (!opt->relative) + argv_array_push(&submodule_options, "--full-name"); + if (opt->name_only) + argv_array_push(&submodule_options, "-l"); + if (opt->unmatch_name_only) + argv_array_push(&submodule_options, "-L"); + if (opt->null_following_name) + argv_array_push(&submodule_options, "-z"); + if (opt->count) + argv_array_push(&submodule_options, "-c"); + if (opt->file_break) + argv_array_push(&submodule_options, "--break"); + if (opt->heading) + argv_array_push(&submodule_options, "--heading"); + if (opt->pre_context) + argv_array_pushf(&submodule_options, "--before-context=%d", + opt->pre_context); + if (opt->post_context) + argv_array_pushf(&submodule_options, "--after-context=%d", + opt->post_context); + if (opt->funcname) + argv_array_push(&submodule_options, "-p"); + if (opt->funcbody) + argv_array_push(&submodule_options, "-W"); + if (opt->all_match) + argv_array_push(&submodule_options, "--all-match"); + if (opt->debug) + argv_array_push(&submodule_options, "--debug"); + if (opt->status_only) + argv_array_push(&submodule_options, "-q"); + + switch (pattern_type_arg) { + case GREP_PATTERN_TYPE_BRE: + argv_array_push(&submodule_options, "-G"); + break; + case GREP_PATTERN_TYPE_ERE: + argv_array_push(&submodule_options, "-E"); + break; + case GREP_PATTERN_TYPE_FIXED: + argv_array_push(&submodule_options, "-F"); + break; + case GREP_PATTERN_TYPE_PCRE: + argv_array_push(&submodule_options, "-P"); + break; + case GREP_PATTERN_TYPE_UNSPECIFIED: + break; + } + + for (pattern = opt->pattern_list; pattern != NULL; + pattern = pattern->next) { + switch (pattern->token) { + case GREP_PATTERN: + argv_array_pushf(&submodule_options, "-e%s", + pattern->pattern); + break; + case GREP_AND: + case GREP_OPEN_PAREN: + case GREP_CLOSE_PAREN: + case GREP_NOT: + case GREP_OR: + argv_array_push(&submodule_options, pattern->pattern); + break; + /* BODY and HEAD are not used by git-grep */ + case GREP_PATTERN_BODY: + case GREP_PATTERN_HEAD: + break; + } + } + + /* + * Limit number of threads for child process to use. + * This is to prevent potential fork-bomb behavior of git-grep as each + * submodule process has its own thread pool. + */ + argv_array_pushf(&submodule_options, "--threads=%d", + (num_threads + 1) / 2); + + /* Add Pathspecs */ + argv_array_push(&submodule_options, "--"); + for (i = 0; i < pathspec->nr; i++) + argv_array_push(&submodule_options, + pathspec->items[i].original); +} + +/* + * Launch child process to grep contents of a submodule + */ +static int grep_submodule_launch(struct grep_opt *opt, + const struct grep_source *gs) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status, i; + const char *end_of_base; + const char *name; + struct work_item *w = opt->output_priv; + + end_of_base = strchr(gs->name, ':'); + if (gs->identifier && end_of_base) + name = end_of_base + 1; + else + name = gs->name; + + prepare_submodule_repo_env(&cp.env_array); + argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT); + + /* Add super prefix */ + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + super_prefix ? super_prefix : "", + name); + argv_array_push(&cp.args, "grep"); + + /* + * Add basename of parent project + * When performing grep on a tree object the filename is prefixed + * with the object's name: 'tree-name:filename'. In order to + * provide uniformity of output we want to pass the name of the + * parent project's object name to the submodule so the submodule can + * prefix its output with the parent's name and not its own SHA1. + */ + if (gs->identifier && end_of_base) + argv_array_pushf(&cp.args, "--parent-basename=%.*s", + (int) (end_of_base - gs->name), + gs->name); + + /* Add options */ + for (i = 0; i < submodule_options.argc; i++) { + /* + * If there is a tree identifier for the submodule, add the + * rev after adding the submodule options but before the + * pathspecs. To do this we listen for the '--' and insert the + * sha1 before pushing the '--' onto the child process argv + * array. + */ + if (gs->identifier && + !strcmp("--", submodule_options.argv[i])) { + argv_array_push(&cp.args, sha1_to_hex(gs->identifier)); + } + + argv_array_push(&cp.args, submodule_options.argv[i]); + } + + cp.git_cmd = 1; + cp.dir = gs->path; + + /* + * Capture output to output buffer and check the return code from the + * 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); + if (status && (status != 1)) { + /* flush the buffer */ + write_or_die(1, w->out.buf, w->out.len); + die("process for submodule '%s' failed with exit code: %d", + gs->name, status); + } + + /* invert the return code to make a hit equal to 1 */ + return !status; +} + +/* + * Prep grep structures for a submodule grep + * sha1: the sha1 of the submodule or NULL if using the working tree + * filename: name of the submodule including tree name of parent + * path: location of the submodule + */ +static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, + const char *filename, const char *path) +{ + if (!is_submodule_initialized(path)) + return 0; + if (!is_submodule_populated(path)) { + /* + * If searching history, check for the presense of the + * submodule's gitdir before skipping the submodule. + */ + if (sha1) { + const struct submodule *sub = + submodule_from_path(null_sha1, path); + if (sub) + path = git_path("modules/%s", sub->name); + + if (!(is_directory(path) && is_git_directory(path))) + return 0; + } else { + return 0; + } + } + +#ifndef NO_PTHREADS + if (num_threads) { + add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1); + return 0; + } else +#endif + { + struct work_item w; + int hit; + + grep_source_init(&w.source, GREP_SOURCE_SUBMODULE, + filename, path, sha1); + strbuf_init(&w.out, 0); + opt->output_priv = &w; + hit = grep_submodule_launch(opt, &w.source); + + write_or_die(1, w.out.buf, w.out.len); + + grep_source_clear(&w.source); + strbuf_release(&w.out); + return hit; + } +} + +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, + int cached) { int hit = 0; int nr; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + name_base_len = strlen(super_prefix); + strbuf_addstr(&name, super_prefix); + } + read_cache(); for (nr = 0; nr < active_nr; nr++) { const struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!ce_path_match(ce, pathspec, NULL)) + strbuf_setlen(&name, name_base_len); + strbuf_addstr(&name, ce->name); + + if (S_ISREG(ce->ce_mode) && + match_pathspec(pathspec, name.buf, name.len, 0, NULL, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) { + /* + * If CE_VALID is on, we assume worktree file and its + * cache entry are identical, even if worktree file has + * been modified, so use cache version instead + */ + if (cached || (ce->ce_flags & CE_VALID) || + ce_skip_worktree(ce)) { + if (ce_stage(ce) || ce_intent_to_add(ce)) + continue; + hit |= grep_sha1(opt, ce->oid.hash, ce->name, + 0, ce->name); + } else { + hit |= grep_file(opt, ce->name); + } + } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(pathspec, name.buf, NULL)) { + hit |= grep_submodule(opt, NULL, ce->name, ce->name); + } else { continue; - /* - * If CE_VALID is on, we assume worktree file and its cache entry - * are identical, even if worktree file has been modified, so use - * cache version instead - */ - if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce) || ce_intent_to_add(ce)) - continue; - hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0, - ce->name); } - else - hit |= grep_file(opt, ce->name); + if (ce_stage(ce)) { do { nr++; @@ -413,6 +712,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -424,12 +725,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, enum interesting match = entry_not_interesting; struct name_entry entry; int old_baselen = base->len; + struct strbuf name = STRBUF_INIT; + int name_base_len = 0; + if (super_prefix) { + strbuf_addstr(&name, super_prefix); + name_base_len = name.len; + } while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(&entry); if (match != all_entries_interesting) { - match = tree_entry_interesting(&entry, base, tn_len, pathspec); + strbuf_addstr(&name, base->buf + tn_len); + match = tree_entry_interesting(&entry, &name, + 0, pathspec); + strbuf_setlen(&name, name_base_len); + if (match == all_entries_not_interesting) break; if (match == entry_not_interesting) @@ -441,8 +752,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (S_ISREG(entry.mode)) { hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); - } - else if (S_ISDIR(entry.mode)) { + } else if (S_ISDIR(entry.mode)) { enum object_type type; struct tree_desc sub; void *data; @@ -458,12 +768,18 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, hit |= grep_tree(opt, pathspec, &sub, base, tn_len, check_attr); free(data); + } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { + hit |= grep_submodule(opt, entry.oid->hash, base->buf, + base->buf + tn_len); } + strbuf_setlen(base, old_baselen); if (hit && opt->status_only) break; } + + strbuf_release(&name); return hit; } @@ -487,6 +803,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); + /* Use parent's name as base when recursing submodules */ + if (recurse_submodules && parent_basename) + name = parent_basename; + len = name ? strlen(name) : 0; strbuf_init(&base, PATH_MAX + len + 1); if (len) { @@ -513,6 +833,12 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; real_obj = deref_tag(list->objects[i].item, NULL, 0); + + /* load the gitmodules file for this rev */ + if (recurse_submodules) { + submodule_free(); + gitmodules_config_sha1(real_obj->oid.hash); + } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { hit = 1; if (opt->status_only) @@ -651,6 +977,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, N_("ignore files specified via '.gitignore'"), 1), + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("recursivley search in each submodule")), + OPT_STRING(0, "parent-basename", &parent_basename, + N_("basename"), + N_("prepend parent project's basename to output")), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -755,6 +1086,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); + super_prefix = get_super_prefix(); /* * If there is no -- then the paths must exist in the working @@ -872,6 +1204,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; + if (recurse_submodules) { + gitmodules_config(); + compile_submodule_options(&opt, &pathspec, cached, untracked, + opt_exclude, use_index, + pattern_type_arg); + } + if (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); @@ -895,6 +1234,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } + if (recurse_submodules && (!use_index || untracked)) + die(_("option not supported with --recurse-submodules.")); + if (!show_in_pager && !opt.status_only) setup_pager(); diff --git a/builtin/init-db.c b/builtin/init-db.c index 2399b97d90..76d68fad00 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -338,7 +338,7 @@ int init_db(const char *git_dir, const char *real_git_dir, { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; - char *original_git_dir = xstrdup(real_path(git_dir)); + char *original_git_dir = real_pathdup(git_dir); if (real_git_dir) { struct stat st; @@ -489,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); if (real_git_dir && !is_absolute_path(real_git_dir)) - real_git_dir = xstrdup(real_path(real_git_dir)); + real_git_dir = real_pathdup(real_git_dir); if (argc == 1) { int mkdir_tried = 0; @@ -560,7 +560,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *git_dir_parent = strrchr(git_dir, '/'); if (git_dir_parent) { char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(real_path(rel)); + git_work_tree_cfg = real_pathdup(rel); free(rel); } if (!git_work_tree_cfg) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 0e30d86230..d7ebeb4ce6 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -31,21 +31,18 @@ static const char * const ls_tree_usage[] = { static int show_recursive(const char *base, int baselen, const char *pathname) { - const char **s; + int i; if (ls_options & LS_RECURSIVE) return 1; - s = pathspec._raw; - if (!s) + if (!pathspec.nr) return 0; - for (;;) { - const char *spec = *s++; + for (i = 0; i < pathspec.nr; i++) { + const char *spec = pathspec.items[i].match; int len, speclen; - if (!spec) - return 0; if (strncmp(base, spec, baselen)) continue; len = strlen(pathname); @@ -59,6 +56,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) continue; return 1; } + return 0; } static int show_tree(const unsigned char *sha1, struct strbuf *base, @@ -175,8 +173,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * cannot be lifted until it is converted to use * match_pathspec() or tree_entry_interesting() */ - parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE | - PATHSPEC_EXCLUDE, + parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & + ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), PATHSPEC_PREFER_CWD, prefix, argv + 1); for (i = 0; i < pathspec.nr; i++) diff --git a/builtin/merge.c b/builtin/merge.c index b65eeaa87d..a96d4fb501 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -46,6 +46,7 @@ static const char * const builtin_merge_usage[] = { N_("git merge [<options>] [<commit>...]"), N_("git merge [<options>] <msg> HEAD <commit>"), N_("git merge --abort"), + N_("git merge --continue"), NULL }; @@ -65,6 +66,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; @@ -223,6 +225,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "continue", &continue_current_merge, + N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), @@ -634,7 +638,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, { static struct lock_file lock; - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (active_cache_changed && write_locked_index(&the_index, &lock, COMMIT_LOCK)) @@ -671,7 +675,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (clean < 0) @@ -781,7 +785,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) struct commit_list *parents, **pptr = &parents; static struct lock_file lock; - hold_locked_index(&lock, 1); + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); if (active_cache_changed && write_locked_index(&the_index, &lock, COMMIT_LOCK)) @@ -1125,6 +1129,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list *remoteheads, *p; void *branch_to_free; + int orig_argc = argc; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1158,6 +1163,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + if (orig_argc != 2) + usage_msg_opt(_("--abort expects no arguments"), + builtin_merge_usage, builtin_merge_options); + if (!file_exists(git_path_merge_head())) die(_("There is no merge to abort (MERGE_HEAD missing).")); @@ -1166,6 +1175,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (continue_current_merge) { + int nargc = 1; + const char *nargv[] = {"commit", NULL}; + + if (orig_argc != 2) + usage_msg_opt(_("--continue expects no arguments"), + builtin_merge_usage, builtin_merge_options); + + if (!file_exists(git_path_merge_head())) + die(_("There is no merge in progress (MERGE_HEAD missing).")); + + /* Invoke 'git commit' */ + ret = cmd_commit(nargc, nargv, prefix); + goto done; + } + if (read_cache_unmerged()) die_resolve_conflict("merge"); diff --git a/builtin/mv.c b/builtin/mv.c index 2f43877bc9..61d20037ad 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Johannes Schindelin */ #include "builtin.h" +#include "pathspec.h" #include "lockfile.h" #include "dir.h" #include "cache-tree.h" @@ -19,31 +20,42 @@ static const char * const builtin_mv_usage[] = { #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 -static const char **internal_copy_pathspec(const char *prefix, - const char **pathspec, - int count, unsigned flags) +static const char **internal_prefix_pathspec(const char *prefix, + const char **pathspec, + int count, unsigned flags) { int i; const char **result; + int prefixlen = prefix ? strlen(prefix) : 0; ALLOC_ARRAY(result, count + 1); - COPY_ARRAY(result, pathspec, count); - result[count] = NULL; + + /* Create an intermediate copy of the pathspec based on the flags */ for (i = 0; i < count; i++) { - int length = strlen(result[i]); + int length = strlen(pathspec[i]); int to_copy = length; + char *it; while (!(flags & KEEP_TRAILING_SLASH) && - to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) + to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1])) to_copy--; - if (to_copy != length || flags & DUP_BASENAME) { - char *it = xmemdupz(result[i], to_copy); - if (flags & DUP_BASENAME) { - result[i] = xstrdup(basename(it)); - free(it); - } else - result[i] = it; + + it = xmemdupz(pathspec[i], to_copy); + if (flags & DUP_BASENAME) { + result[i] = xstrdup(basename(it)); + free(it); + } else { + result[i] = it; } } - return get_pathspec(prefix, result); + result[count] = NULL; + + /* Prefix the pathspec and free the old intermediate strings */ + for (i = 0; i < count; i++) { + const char *match = prefix_path(prefix, prefixlen, result[i]); + free((char *) result[i]); + result[i] = match; + } + + return result; } static const char *add_slash(const char *path) @@ -126,11 +138,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die(_("index file corrupt")); - source = internal_copy_pathspec(prefix, argv, argc, 0); + source = internal_prefix_pathspec(prefix, argv, argc, 0); modes = xcalloc(argc, sizeof(enum update_mode)); /* * Keep trailing slash, needed to let @@ -140,16 +152,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags = KEEP_TRAILING_SLASH; if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1])) flags = 0; - dest_path = internal_copy_pathspec(prefix, argv + argc, 1, flags); + dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags); submodule_gitfile = xcalloc(argc, sizeof(char *)); if (dest_path[0][0] == '\0') /* special case: "." was normalized to "" */ - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); - destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME); + destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME); } else { if (argc != 1) die(_("destination '%s' is not a directory"), dest_path[0]); diff --git a/builtin/push.c b/builtin/push.c index 9307ad56a9..5c22e9f2e5 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -568,6 +568,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; if (tags) add_refspec("refs/tags/*"); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 9bd1fd755e..fa6edb35b2 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -150,7 +150,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e6b3879a5b..1dbb8a0692 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -795,8 +795,8 @@ static char *refuse_unconfigured_deny_msg = "with what you pushed, and will require 'git reset --hard' to match\n" "the work tree to HEAD.\n" "\n" - "You can set 'receive.denyCurrentBranch' configuration variable to\n" - "'ignore' or 'warn' in the remote repository to allow pushing into\n" + "You can set the 'receive.denyCurrentBranch' configuration variable\n" + "to 'ignore' or 'warn' in the remote repository to allow pushing into\n" "its current branch; however, this is not recommended unless you\n" "arranged to update its work tree to match what you pushed in some\n" "other way.\n" @@ -1942,8 +1942,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) run_receive_hook(commands, "post-receive", 1, &push_options); run_update_post_hook(commands); - if (push_options.nr) - string_list_clear(&push_options, 0); + string_list_clear(&push_options, 0); if (auto_gc) { const char *argv_gc_auto[] = { "gc", "--auto", "--quiet", NULL, diff --git a/builtin/remote.c b/builtin/remote.c index e52cf3925b..5339ed6ad1 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -186,7 +186,7 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote_is_configured(remote)) + if (remote_is_configured(remote, 1)) die(_("remote %s already exists."), name); strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); @@ -618,14 +618,14 @@ static int mv(int argc, const char **argv) rename.remote_branches = &remote_branches; oldremote = remote_get(rename.old); - if (!remote_is_configured(oldremote)) + if (!remote_is_configured(oldremote, 1)) die(_("No such remote: %s"), rename.old); if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new); - if (remote_is_configured(newremote)) + if (remote_is_configured(newremote, 1)) die(_("remote %s already exists."), rename.new); strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); @@ -753,7 +753,7 @@ static int rm(int argc, const char **argv) usage_with_options(builtin_remote_rm_usage, options); remote = remote_get(argv[1]); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote: %s"), argv[1]); known_remotes.to_delete = remote; @@ -1415,7 +1415,7 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { @@ -1469,7 +1469,7 @@ static int get_url(int argc, const char **argv) remotename = argv[0]; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); url_nr = 0; @@ -1537,7 +1537,7 @@ static int set_url(int argc, const char **argv) oldurl = newurl; remote = remote_get(remotename); - if (!remote_is_configured(remote)) + if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); if (push_mode) { diff --git a/builtin/reset.c b/builtin/reset.c index c04ac076dc..8ab915bfcb 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -354,7 +354,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type != SOFT) { struct lock_file *lock = xcalloc(1, sizeof(*lock)); - hold_locked_index(lock, 1); + hold_locked_index(lock, LOCK_DIE_ON_ERROR); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) diff --git a/builtin/rm.c b/builtin/rm.c index 3f3e24eb36..452170a3ab 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -59,27 +59,9 @@ static void print_error_files(struct string_list *files_list, } } -static void error_removing_concrete_submodules(struct string_list *files, int *errs) -{ - print_error_files(files, - Q_("the following submodule (or one of its nested " - "submodules)\n" - "uses a .git directory:", - "the following submodules (or one of their nested " - "submodules)\n" - "use a .git directory:", files->nr), - _("\n(use 'rm -rf' if you really want to remove " - "it including all of its history)"), - errs); - string_list_clear(files, 0); -} - -static int check_submodules_use_gitfiles(void) +static void submodules_absorb_gitdir_if_needed(const char *prefix) { int i; - int errs = 0; - struct string_list files = STRING_LIST_INIT_NODUP; - for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; @@ -99,12 +81,9 @@ static int check_submodules_use_gitfiles(void) continue; if (!submodule_uses_gitfile(name)) - string_list_append(&files, name); + absorb_git_dir_into_superproject(prefix, name, + ABSORB_GITDIR_RECURSE_SUBMODULES); } - - error_removing_concrete_submodules(&files, &errs); - - return errs; } static int check_local_mod(struct object_id *head, int index_only) @@ -120,7 +99,6 @@ static int check_local_mod(struct object_id *head, int index_only) int errs = 0; struct string_list files_staged = STRING_LIST_INIT_NODUP; struct string_list files_cached = STRING_LIST_INIT_NODUP; - struct string_list files_submodule = STRING_LIST_INIT_NODUP; struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_oid(head); @@ -187,7 +165,9 @@ static int check_local_mod(struct object_id *head, int index_only) */ if (ce_match_stat(ce, &st, 0) || (S_ISGITLINK(ce->ce_mode) && - !ok_to_remove_submodule(ce->name))) + bad_to_remove_submodule(ce->name, + SUBMODULE_REMOVAL_DIE_ON_ERROR | + SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))) local_changes = 1; /* @@ -217,13 +197,8 @@ static int check_local_mod(struct object_id *head, int index_only) else if (!index_only) { if (staged_changes) string_list_append(&files_cached, name); - if (local_changes) { - if (S_ISGITLINK(ce->ce_mode) && - !submodule_uses_gitfile(name)) - string_list_append(&files_submodule, name); - else - string_list_append(&files_local, name); - } + if (local_changes) + string_list_append(&files_local, name); } } print_error_files(&files_staged, @@ -245,8 +220,6 @@ static int check_local_mod(struct object_id *head, int index_only) &errs); string_list_clear(&files_cached, 0); - error_removing_concrete_submodules(&files_submodule, &errs); - print_error_files(&files_local, Q_("the following file has local modifications:", "the following files have local modifications:", @@ -292,7 +265,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); - hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die(_("index file corrupt")); @@ -340,6 +313,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) exit(0); } + if (!index_only) + submodules_absorb_gitdir_if_needed(prefix); + /* * If not forced, the file, the index and the HEAD (if exists) * must match; but the file can already been removed, since @@ -356,9 +332,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) oidclr(&oid); if (check_local_mod(&oid, index_only)) exit(1); - } else if (!index_only) { - if (check_submodules_use_gitfiles()) - exit(1); } /* @@ -387,32 +360,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix) */ if (!index_only) { int removed = 0, gitmodules_modified = 0; - struct strbuf buf = STRBUF_INIT; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { - if (is_empty_dir(path)) { - if (!rmdir(path)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - continue; - } - } else { - strbuf_reset(&buf); - strbuf_addstr(&buf, path); - if (!remove_dir_recursively(&buf, 0)) { - removed = 1; - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - strbuf_release(&buf); - continue; - } else if (!file_exists(path)) - /* Submodule was removed by user */ - if (!remove_path_from_gitmodules(path)) - gitmodules_modified = 1; - /* Fallthrough and let remove_path() fail. */ - } + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&buf, path); + if (remove_dir_recursively(&buf, 0)) + die(_("could not remove '%s'"), path); + strbuf_release(&buf); + + removed = 1; + if (!remove_path_from_gitmodules(path)) + gitmodules_modified = 1; + continue; } if (!remove_path(path)) { removed = 1; @@ -421,7 +382,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!removed) die_errno("git rm: '%s'", path); } - strbuf_release(&buf); if (gitmodules_modified) stage_updated_gitmodules(); } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index ba0e1154a9..c9585d475d 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -117,11 +117,15 @@ static void read_from_stdin(struct shortlog *log) { struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; + static const char *author_match[2] = { "Author: ", "author " }; + static const char *committer_match[2] = { "Commit: ", "committer " }; + const char **match; + match = log->committer ? committer_match : author_match; while (strbuf_getline_lf(&author, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, "Author: ", &v) && - !skip_prefix(author.buf, "author ", &v)) + if (!skip_prefix(author.buf, match[0], &v) && + !skip_prefix(author.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -140,6 +144,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) struct strbuf author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; struct pretty_print_context ctx = {0}; + const char *fmt; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; @@ -148,7 +153,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - format_commit_message(commit, "%an <%ae>", &author, &ctx); + fmt = log->committer ? "%cn <%ce>" : "%an <%ae>"; + + format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); @@ -238,6 +245,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { + OPT_BOOL('c', "committer", &log.committer, + N_("Group by committer rather than author")), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 6d4e669002..013d241abc 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -19,19 +19,34 @@ static const char *exclude_existing_arg; static void show_one(const char *refname, const struct object_id *oid) { - const char *hex = find_unique_abbrev(oid->hash, abbrev); + const char *hex; + struct object_id peeled; + + if (!has_sha1_file(oid->hash)) + die("git show-ref: bad ref %s (%s)", refname, + oid_to_hex(oid)); + + if (quiet) + return; + + hex = find_unique_abbrev(oid->hash, abbrev); if (hash_only) printf("%s\n", hex); else printf("%s %s\n", hex, refname); + + if (!deref_tags) + return; + + if (!peel_ref(refname, peeled.hash)) { + hex = find_unique_abbrev(peeled.hash, abbrev); + printf("%s %s^{}\n", hex, refname); + } } static int show_ref(const char *refname, const struct object_id *oid, int flag, void *cbdata) { - const char *hex; - struct object_id peeled; - if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -54,9 +69,6 @@ static int show_ref(const char *refname, const struct object_id *oid, continue; if (len == reflen) goto match; - /* "--verify" requires an exact match */ - if (verify) - continue; if (refname[reflen - len - 1] == '/') goto match; } @@ -66,26 +78,8 @@ static int show_ref(const char *refname, const struct object_id *oid, match: found_match++; - /* This changes the semantics slightly that even under quiet we - * detect and return error if the repository is corrupt and - * ref points at a nonexistent object. - */ - if (!has_sha1_file(oid->hash)) - die("git show-ref: bad ref %s (%s)", refname, - oid_to_hex(oid)); - - if (quiet) - return 0; - show_one(refname, oid); - if (!deref_tags) - return 0; - - if (!peel_ref(refname, peeled.hash)) { - hex = find_unique_abbrev(peeled.hash, abbrev); - printf("%s %s^{}\n", hex, refname); - } return 0; } @@ -202,10 +196,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) while (*pattern) { struct object_id oid; - if (starts_with(*pattern, "refs/") && + if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) && !read_ref(*pattern, oid.hash)) { - if (!quiet) - show_one(*pattern, &oid); + show_one(*pattern, &oid); } else if (!quiet) die("'%s' - not a valid ref", *pattern); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 4beeda5f9f..899dc334e3 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -317,8 +317,12 @@ static void init_submodule(const char *path, const char *prefix, int quiet) /* Only loads from .gitmodules, no overlay with .git/config */ gitmodules_config(); - if (prefix) { - strbuf_addf(&sb, "%s%s", prefix, path); + if (prefix && get_super_prefix()) + die("BUG: cannot have prefix and superprefix"); + else if (prefix) + displaypath = xstrdup(relative_path(path, prefix, &sb)); + else if (get_super_prefix()) { + strbuf_addf(&sb, "%s%s", get_super_prefix(), path); displaypath = strbuf_detach(&sb, NULL); } else displaypath = xstrdup(path); @@ -403,9 +407,6 @@ static int module_init(int argc, const char **argv, const char *prefix) int i; struct option module_init_options[] = { - OPT_STRING(0, "prefix", &prefix, - N_("path"), - N_("alternative anchor for relative paths")), OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), OPT_END() }; @@ -498,9 +499,9 @@ static int add_possible_reference_from_superproject( /* * If the alternate object store is another repository, try the - * standard layout with .git/modules/<name>/objects + * standard layout with .git/(modules/<name>)+/objects */ - if (ends_with(alt->path, ".git/objects")) { + if (ends_with(alt->path, "/objects")) { char *sm_alternate; struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; @@ -583,6 +584,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) 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; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -624,7 +626,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) module_clone_options); strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); - sm_gitdir = xstrdup(absolute_path(sb.buf)); + sm_gitdir = absolute_pathdup(sb.buf); strbuf_reset(&sb); if (!is_absolute_path(path)) { @@ -672,6 +674,20 @@ static int module_clone(int argc, const char **argv, const char *prefix) 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); + if (sm_alternate) + git_config_set_in_file(p, "submodule.alternateLocation", + sm_alternate); + git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); + if (error_strategy) + git_config_set_in_file(p, "submodule.alternateErrorStrategy", + error_strategy); + + free(sm_alternate); + free(error_strategy); + strbuf_release(&sb); strbuf_release(&rel_path); free(sm_gitdir); @@ -1076,21 +1092,62 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, return 0; } +static int absorb_git_dirs(int argc, const char **argv, const char *prefix) +{ + int i; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES; + + struct option embed_gitdir_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"), + ABSORB_GITDIR_RECURSE_SUBMODULES), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper embed-git-dir [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, embed_gitdir_options, + git_submodule_helper_usage, 0); + + gitmodules_config(); + git_config(submodule_config, NULL); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + for (i = 0; i < list.nr; i++) + absorb_git_dir_into_superproject(prefix, + list.entries[i]->name, flags); + + return 0; +} + +#define SUPPORT_SUPER_PREFIX (1<<0) + struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); + unsigned option; }; static struct cmd_struct commands[] = { - {"list", module_list}, - {"name", module_name}, - {"clone", module_clone}, - {"update-clone", update_clone}, - {"relative-path", resolve_relative_path}, - {"resolve-relative-url", resolve_relative_url}, - {"resolve-relative-url-test", resolve_relative_url_test}, - {"init", module_init}, - {"remote-branch", resolve_remote_submodule_branch} + {"list", module_list, 0}, + {"name", module_name, 0}, + {"clone", module_clone, 0}, + {"update-clone", update_clone, 0}, + {"relative-path", resolve_relative_path, 0}, + {"resolve-relative-url", resolve_relative_url, 0}, + {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"remote-branch", resolve_remote_submodule_branch, 0}, + {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) @@ -1100,9 +1157,15 @@ int cmd_submodule__helper(int argc, const char **argv, const char *prefix) die(_("submodule--helper subcommand must be " "called with a subcommand")); - for (i = 0; i < ARRAY_SIZE(commands); i++) - if (!strcmp(argv[1], commands[i].cmd)) + for (i = 0; i < ARRAY_SIZE(commands); i++) { + if (!strcmp(argv[1], commands[i].cmd)) { + if (get_super_prefix() && + !(commands[i].option & SUPPORT_SUPER_PREFIX)) + die(_("%s doesn't support --super-prefix"), + commands[i].cmd); return commands[i].fn(argc - 1, argv + 1, prefix); + } + } die(_("'%s' is not a valid submodule--helper " "subcommand"), argv[1]); diff --git a/builtin/tag.c b/builtin/tag.c index 50e4ae5678..e40c4a9676 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -24,7 +24,7 @@ static const char * const git_tag_usage[] = { N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), - N_("git tag -v <tagname>..."), + N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -66,9 +66,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const unsigned char *sha1); + const unsigned char *sha1, const void *cb_data); -static int for_each_tag_name(const char **argv, each_tag_name_fn fn) +static int for_each_tag_name(const char **argv, each_tag_name_fn fn, + const void *cb_data) { const char **p; char ref[PATH_MAX]; @@ -87,14 +88,14 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn) had_error = 1; continue; } - if (fn(*p, ref, sha1)) + if (fn(*p, ref, sha1, cb_data)) had_error = 1; } return had_error; } static int delete_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { if (delete_ref(ref, sha1, 0)) return 1; @@ -103,9 +104,22 @@ static int delete_tag(const char *name, const char *ref, } static int verify_tag(const char *name, const char *ref, - const unsigned char *sha1) + const unsigned char *sha1, const void *cb_data) { - return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE); + int flags; + const char *fmt_pretty = cb_data; + flags = GPG_VERIFY_VERBOSE; + + if (fmt_pretty) + flags = GPG_VERIFY_OMIT_STATUS; + + if (gpg_verify_tag(sha1, name, flags)) + return -1; + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); + + return 0; } static int do_sign(struct strbuf *buffer) @@ -335,6 +349,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct ref_filter filter; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; const char *format = NULL; + int icase = 0; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), @@ -370,6 +385,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("print only tags of the object"), 0, parse_opt_object_name }, OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END() }; @@ -401,6 +417,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); + sorting->ignore_case = icase; + filter.ignore_case = icase; if (cmdmode == 'l') { int ret; if (column_active(colopts)) { @@ -424,9 +442,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.merge_commit) die(_("--merged and --no-merged option are only allowed with -l")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag); - if (cmdmode == 'v') - return for_each_tag_name(argv, verify_tag); + return for_each_tag_name(argv, delete_tag, NULL); + if (cmdmode == 'v') { + if (format) + verify_ref_format(format); + return for_each_tag_name(argv, verify_tag, format); + } if (msg.given || msgfile) { if (msg.given && msgfile) diff --git a/builtin/update-index.c b/builtin/update-index.c index f3f07e7f1c..d530e89368 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -1012,6 +1012,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ lock_file = xcalloc(1, sizeof(struct lock_file)); + /* we will diagnose later if it turns out that we need to update it */ newfd = hold_locked_index(lock_file, 0); if (newfd < 0) lock_error = errno; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 99f8148cf7..5199553d91 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -12,9 +12,10 @@ #include <signal.h> #include "parse-options.h" #include "gpg-interface.h" +#include "ref-filter.h" static const char * const verify_tag_usage[] = { - N_("git verify-tag [-v | --verbose] <tag>..."), + N_("git verify-tag [-v | --verbose] [--format=<format>] <tag>..."), NULL }; @@ -30,9 +31,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; + char *fmt_pretty = NULL; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), + OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -46,13 +49,26 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; + if (fmt_pretty) { + verify_ref_format(fmt_pretty); + flags |= GPG_VERIFY_OMIT_STATUS; + } + while (i < argc) { unsigned char sha1[20]; const char *name = argv[i++]; - if (get_sha1(name, sha1)) + if (get_sha1(name, sha1)) { had_error = !!error("tag '%s' not found.", name); - else if (gpg_verify_tag(sha1, name, flags)) + continue; + } + + if (gpg_verify_tag(sha1, name, flags)) { had_error = 1; + continue; + } + + if (fmt_pretty) + pretty_print_ref(name, sha1, fmt_pretty); } return had_error; } @@ -514,7 +514,6 @@ extern void set_git_work_tree(const char *tree); #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" -extern const char **get_pathspec(const char *prefix, const char **pathspec); extern void setup_work_tree(void); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); @@ -631,7 +630,7 @@ extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce); extern int index_name_is_other(const struct index_state *, const char *, int); -extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *); +extern void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *); /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 @@ -1096,9 +1095,13 @@ static inline int is_absolute_path(const char *path) return is_dir_sep(path[0]) || has_dos_drive_prefix(path); } int is_directory(const char *); +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error); const char *real_path(const char *path); const char *real_path_if_valid(const char *path); +char *real_pathdup(const char *path); const char *absolute_path(const char *path); +char *absolute_pathdup(const char *path); const char *remove_leading_path(const char *in, const char *prefix); const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); int normalize_path_copy_len(char *dst, const char *src, int *prefix_len); @@ -1157,7 +1160,8 @@ extern int write_sha1_file(const void *buf, unsigned long len, const char *type, extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); -extern int git_open(const char *name); +extern int git_open_cloexec(const char *name, int flags); +#define git_open(name) git_open_cloexec(name, O_RDONLY) extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size); extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); extern int parse_sha1_header(const char *hdr, unsigned long *sizep); @@ -1172,6 +1176,19 @@ extern int finalize_object_file(const char *tmpfile, const char *filename); extern int has_sha1_pack(const unsigned char *sha1); /* + * Open the loose object at path, check its sha1, and return the contents, + * type, and size. If the object is a blob, then "contents" may return NULL, + * to allow streaming of large blobs. + * + * Returns 0 on success, negative on error (details may be written to stderr). + */ +int read_loose_object(const char *path, + const unsigned char *expected_sha1, + enum object_type *type, + unsigned long *size, + void **contents); + +/* * Return true iff we have an object named sha1, whether local or in * an alternate object database, and whether packed or loose. This * function does not respect replace references. @@ -1722,6 +1739,8 @@ extern int git_default_config(const char *, const char *, void *); extern int git_config_from_file(config_fn_t fn, const char *, void *); extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type, const char *name, const char *buf, size_t len, void *data); +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 git_config(config_fn_t fn, void *); diff --git a/command-list.txt b/command-list.txt index 2a94137bbb..a1fad28fd8 100644 --- a/command-list.txt +++ b/command-list.txt @@ -107,7 +107,6 @@ git-read-tree plumbingmanipulators git-rebase mainporcelain history git-receive-pack synchelpers git-reflog ancillarymanipulators -git-relink ancillarymanipulators git-remote ancillarymanipulators git-repack ancillarymanipulators git-replace ancillarymanipulators @@ -1649,7 +1649,7 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len } /* - * Inspect sb and determine the true "end" of the log message, in + * Inspect the given string and determine the true "end" of the log message, in * order to find where to put a new Signed-off-by: line. Ignored are * trailing comment lines and blank lines, and also the traditional * "Conflicts:" block that is not commented out, so that we can use @@ -1659,37 +1659,37 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len * Returns the number of bytes from the tail to ignore, to be fed as * the second parameter to append_signoff(). */ -int ignore_non_trailer(struct strbuf *sb) +int ignore_non_trailer(const char *buf, size_t len) { int boc = 0; int bol = 0; int in_old_conflicts_block = 0; - while (bol < sb->len) { - char *next_line; + while (bol < len) { + const char *next_line = memchr(buf + bol, '\n', len - bol); - if (!(next_line = memchr(sb->buf + bol, '\n', sb->len - bol))) - next_line = sb->buf + sb->len; + if (!next_line) + next_line = buf + len; else next_line++; - if (sb->buf[bol] == comment_line_char || sb->buf[bol] == '\n') { + if (buf[bol] == comment_line_char || buf[bol] == '\n') { /* is this the first of the run of comments? */ if (!boc) boc = bol; /* otherwise, it is just continuing */ - } else if (starts_with(sb->buf + bol, "Conflicts:\n")) { + } else if (starts_with(buf + bol, "Conflicts:\n")) { in_old_conflicts_block = 1; if (!boc) boc = bol; - } else if (in_old_conflicts_block && sb->buf[bol] == '\t') { + } else if (in_old_conflicts_block && buf[bol] == '\t') { ; /* a pathname in the conflicts block */ } else if (boc) { /* the previous was not trailing comment */ boc = 0; in_old_conflicts_block = 0; } - bol = next_line - sb->buf; + bol = next_line - buf; } - return boc ? sb->len - boc : 0; + return boc ? len - boc : 0; } @@ -355,7 +355,7 @@ extern const char *find_commit_header(const char *msg, const char *key, size_t *out_len); /* Find the end of the log message, the right place for a new trailer. */ -extern int ignore_non_trailer(struct strbuf *sb); +extern int ignore_non_trailer(const char *buf, size_t len); typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra, void *cb_data); diff --git a/compat/qsort_s.c b/compat/qsort_s.c new file mode 100644 index 0000000000..52d1f0a73d --- /dev/null +++ b/compat/qsort_s.c @@ -0,0 +1,69 @@ +#include "../git-compat-util.h" + +/* + * A merge sort implementation, simplified from the qsort implementation + * by Mike Haertel, which is a part of the GNU C Library. + * Added context pointer, safety checks and return value. + */ + +static void msort_with_tmp(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *, void *), + char *t, void *ctx) +{ + char *tmp; + char *b1, *b2; + size_t n1, n2; + + if (n <= 1) + return; + + n1 = n / 2; + n2 = n - n1; + b1 = b; + b2 = (char *)b + (n1 * s); + + msort_with_tmp(b1, n1, s, cmp, t, ctx); + msort_with_tmp(b2, n2, s, cmp, t, ctx); + + tmp = t; + + while (n1 > 0 && n2 > 0) { + if (cmp(b1, b2, ctx) <= 0) { + memcpy(tmp, b1, s); + tmp += s; + b1 += s; + --n1; + } else { + memcpy(tmp, b2, s); + tmp += s; + b2 += s; + --n2; + } + } + if (n1 > 0) + memcpy(tmp, b1, n1 * s); + memcpy(b, t, (n - n2) * s); +} + +int git_qsort_s(void *b, size_t n, size_t s, + int (*cmp)(const void *, const void *, void *), void *ctx) +{ + const size_t size = st_mult(n, s); + char buf[1024]; + + if (!n) + return 0; + if (!b || !cmp) + return -1; + + if (size < sizeof(buf)) { + /* The temporary array fits on the small on-stack buffer. */ + msort_with_tmp(b, n, s, cmp, buf, ctx); + } else { + /* It's somewhat large, so malloc it. */ + char *tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp, ctx); + free(tmp); + } + return 0; +} @@ -1236,10 +1236,10 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ return do_config_from(&top, fn, data); } -static int git_config_from_blob_sha1(config_fn_t fn, - const char *name, - const unsigned char *sha1, - void *data) +int git_config_from_blob_sha1(config_fn_t fn, + const char *name, + const unsigned char *sha1, + void *data) { enum object_type type; char *buf; diff --git a/config.mak.uname b/config.mak.uname index b232908f8c..447f36ac2e 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -17,9 +17,6 @@ endif # because maintaining the nesting to match is a pain. If # we had "elif" things would have been much nicer... -ifeq ($(uname_M),x86_64) - XDL_FAST_HASH = YesPlease -endif ifeq ($(uname_S),OSF1) # Need this for u_short definitions et al BASIC_CFLAGS += -D_OSF_SOURCE diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci index 3fceef132b..8e05d1ca4b 100644 --- a/contrib/coccinelle/xstrdup_or_null.cocci +++ b/contrib/coccinelle/xstrdup_or_null.cocci @@ -5,3 +5,9 @@ expression V; - if (E) - V = xstrdup(E); + V = xstrdup_or_null(E); + +@@ +expression E; +@@ +- xstrdup(absolute_path(E)) ++ absolute_pathdup(E) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 21016bf8df..6721ff80fb 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1552,7 +1552,7 @@ _git_merge () case "$cur" in --*) __gitcomp "$__git_merge_options - --rerere-autoupdate --no-rerere-autoupdate --abort" + --rerere-autoupdate --no-rerere-autoupdate --abort --continue" return esac __gitcomp_nl "$(__git_refs)" @@ -1734,10 +1734,10 @@ _git_rebase () { local dir="$(__gitdir)" if [ -f "$dir"/rebase-merge/interactive ]; then - __gitcomp "--continue --skip --abort --edit-todo" + __gitcomp "--continue --skip --abort --quit --edit-todo" return elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then - __gitcomp "--continue --skip --abort" + __gitcomp "--continue --skip --abort --quit" return fi __git_complete_strategy && return diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c deleted file mode 100644 index f3b57bf1d2..0000000000 --- a/contrib/convert-objects/convert-objects.c +++ /dev/null @@ -1,329 +0,0 @@ -#include "cache.h" -#include "blob.h" -#include "commit.h" -#include "tree.h" - -struct entry { - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - int converted; -}; - -#define MAXOBJECTS (1000000) - -static struct entry *convert[MAXOBJECTS]; -static int nr_convert; - -static struct entry * convert_entry(unsigned char *sha1); - -static struct entry *insert_new(unsigned char *sha1, int pos) -{ - struct entry *new = xcalloc(1, sizeof(struct entry)); - hashcpy(new->old_sha1, sha1); - memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); - convert[pos] = new; - nr_convert++; - if (nr_convert == MAXOBJECTS) - die("you're kidding me - hit maximum object limit"); - return new; -} - -static struct entry *lookup_entry(unsigned char *sha1) -{ - int low = 0, high = nr_convert; - - while (low < high) { - int next = (low + high) / 2; - struct entry *n = convert[next]; - int cmp = hashcmp(sha1, n->old_sha1); - if (!cmp) - return n; - if (cmp < 0) { - high = next; - continue; - } - low = next+1; - } - return insert_new(sha1, low); -} - -static void convert_binary_sha1(void *buffer) -{ - struct entry *entry = convert_entry(buffer); - hashcpy(buffer, entry->new_sha1); -} - -static void convert_ascii_sha1(void *buffer) -{ - unsigned char sha1[20]; - struct entry *entry; - - if (get_sha1_hex(buffer, sha1)) - die("expected sha1, got '%s'", (char *) buffer); - entry = convert_entry(sha1); - memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); -} - -static unsigned int convert_mode(unsigned int mode) -{ - unsigned int newmode; - - newmode = mode & S_IFMT; - if (S_ISREG(mode)) - newmode |= (mode & 0100) ? 0755 : 0644; - return newmode; -} - -static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1) -{ - char *new = xmalloc(size); - unsigned long newlen = 0; - unsigned long used; - - used = 0; - while (size) { - int len = 21 + strlen(buffer); - char *path = strchr(buffer, ' '); - unsigned char *sha1; - unsigned int mode; - char *slash, *origpath; - - if (!path || strtoul_ui(buffer, 8, &mode)) - die("bad tree conversion"); - mode = convert_mode(mode); - path++; - if (memcmp(path, base, baselen)) - break; - origpath = path; - path += baselen; - slash = strchr(path, '/'); - if (!slash) { - newlen += sprintf(new + newlen, "%o %s", mode, path); - new[newlen++] = '\0'; - hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20); - newlen += 20; - - used += len; - size -= len; - buffer = (char *) buffer + len; - continue; - } - - newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path); - new[newlen++] = 0; - sha1 = (unsigned char *)(new + newlen); - newlen += 20; - - len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1); - - used += len; - size -= len; - buffer = (char *) buffer + len; - } - - write_sha1_file(new, newlen, tree_type, result_sha1); - free(new); - return used; -} - -static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - while (size) { - size_t len = 1+strlen(buffer); - - convert_binary_sha1((char *) buffer + len); - - len += 20; - if (len > size) - die("corrupt tree object"); - size -= len; - buffer = (char *) buffer + len; - } - - write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1); -} - -static unsigned long parse_oldstyle_date(const char *buf) -{ - char c, *p; - char buffer[100]; - struct tm tm; - const char *formats[] = { - "%c", - "%a %b %d %T", - "%Z", - "%Y", - " %Y", - NULL - }; - /* We only ever did two timezones in the bad old format .. */ - const char *timezones[] = { - "PDT", "PST", "CEST", NULL - }; - const char **fmt = formats; - - p = buffer; - while (isspace(c = *buf)) - buf++; - while ((c = *buf++) != '\n') - *p++ = c; - *p++ = 0; - buf = buffer; - memset(&tm, 0, sizeof(tm)); - do { - const char *next = strptime(buf, *fmt, &tm); - if (next) { - if (!*next) - return mktime(&tm); - buf = next; - } else { - const char **p = timezones; - while (isspace(*buf)) - buf++; - while (*p) { - if (!memcmp(buf, *p, strlen(*p))) { - buf += strlen(*p); - break; - } - p++; - } - } - fmt++; - } while (*buf && *fmt); - printf("left: %s\n", buf); - return mktime(&tm); -} - -static int convert_date_line(char *dst, void **buf, unsigned long *sp) -{ - unsigned long size = *sp; - char *line = *buf; - char *next = strchr(line, '\n'); - char *date = strchr(line, '>'); - int len; - - if (!next || !date) - die("missing or bad author/committer line %s", line); - next++; date += 2; - - *buf = next; - *sp = size - (next - line); - - len = date - line; - memcpy(dst, line, len); - dst += len; - - /* Is it already in new format? */ - if (isdigit(*date)) { - int datelen = next - date; - memcpy(dst, date, datelen); - return len + datelen; - } - - /* - * Hacky hacky: one of the sparse old-style commits does not have - * any date at all, but we can fake it by using the committer date. - */ - if (*date == '\n' && strchr(next, '>')) - date = strchr(next, '>')+2; - - return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date)); -} - -static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - char *new = xmalloc(size + 100); - unsigned long newlen = 0; - - /* "tree <sha1>\n" */ - memcpy(new + newlen, buffer, 46); - newlen += 46; - buffer = (char *) buffer + 46; - size -= 46; - - /* "parent <sha1>\n" */ - while (!memcmp(buffer, "parent ", 7)) { - memcpy(new + newlen, buffer, 48); - newlen += 48; - buffer = (char *) buffer + 48; - size -= 48; - } - - /* "author xyz <xyz> date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - /* "committer xyz <xyz> date" */ - newlen += convert_date_line(new + newlen, &buffer, &size); - - /* Rest */ - memcpy(new + newlen, buffer, size); - newlen += size; - - write_sha1_file(new, newlen, commit_type, result_sha1); - free(new); -} - -static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1) -{ - void *orig_buffer = buffer; - unsigned long orig_size = size; - - if (memcmp(buffer, "tree ", 5)) - die("Bad commit '%s'", (char *) buffer); - convert_ascii_sha1((char *) buffer + 5); - buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ - while (!memcmp(buffer, "parent ", 7)) { - convert_ascii_sha1((char *) buffer + 7); - buffer = (char *) buffer + 48; - } - convert_date(orig_buffer, orig_size, result_sha1); -} - -static struct entry * convert_entry(unsigned char *sha1) -{ - struct entry *entry = lookup_entry(sha1); - enum object_type type; - void *buffer, *data; - unsigned long size; - - if (entry->converted) - return entry; - data = read_sha1_file(sha1, &type, &size); - if (!data) - die("unable to read object %s", sha1_to_hex(sha1)); - - buffer = xmalloc(size); - memcpy(buffer, data, size); - - if (type == OBJ_BLOB) { - write_sha1_file(buffer, size, blob_type, entry->new_sha1); - } else if (type == OBJ_TREE) - convert_tree(buffer, size, entry->new_sha1); - else if (type == OBJ_COMMIT) - convert_commit(buffer, size, entry->new_sha1); - else - die("unknown object type %d in %s", type, sha1_to_hex(sha1)); - entry->converted = 1; - free(buffer); - free(data); - return entry; -} - -int main(int argc, char **argv) -{ - unsigned char sha1[20]; - struct entry *entry; - - setup_git_directory(); - - if (argc != 2) - usage("git-convert-objects <sha1>"); - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - entry = convert_entry(sha1); - printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1)); - return 0; -} diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt deleted file mode 100644 index f871880cfb..0000000000 --- a/contrib/convert-objects/git-convert-objects.txt +++ /dev/null @@ -1,29 +0,0 @@ -git-convert-objects(1) -====================== - -NAME ----- -git-convert-objects - Converts old-style git repository - - -SYNOPSIS --------- -[verse] -'git-convert-objects' - -DESCRIPTION ------------ -Converts old-style git repository to the latest format - - -Author ------- -Written by Linus Torvalds <torvalds@osdl.org> - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. - -GIT ---- -Part of the linkgit:git[7] suite diff --git a/git-difftool.perl b/contrib/examples/git-difftool.perl index e26294feab..df59bdfe97 100755 --- a/git-difftool.perl +++ b/contrib/examples/git-difftool.perl @@ -22,6 +22,7 @@ use File::Path qw(mkpath rmtree); use File::Temp qw(tempdir); use Getopt::Long qw(:config pass_through); use Git; +use Git::I18N; sub usage { @@ -128,7 +129,7 @@ sub setup_dir_diff my $i = 0; while ($i < $#rawdiff) { if ($rawdiff[$i] =~ /^::/) { - warn << 'EOF'; + warn __ <<'EOF'; Combined diff formats ('-c' and '--cc') are not supported in directory diff mode ('-d' and '--dir-diff'). EOF @@ -346,7 +347,7 @@ sub main if (length($opts{difftool_cmd}) > 0) { $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd}; } else { - print "No <tool> given for --tool=<tool>\n"; + print __("No <tool> given for --tool=<tool>\n"); usage(1); } } @@ -354,7 +355,7 @@ sub main if (length($opts{extcmd}) > 0) { $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd}; } else { - print "No <cmd> given for --extcmd=<cmd>\n"; + print __("No <cmd> given for --extcmd=<cmd>\n"); usage(1); } } @@ -428,11 +429,11 @@ sub dir_diff } if (exists $wt_modified{$file} and exists $tmp_modified{$file}) { - my $errmsg = "warning: Both files modified: "; - $errmsg .= "'$worktree/$file' and '$b/$file'.\n"; - $errmsg .= "warning: Working tree file has been left.\n"; - $errmsg .= "warning:\n"; - warn $errmsg; + warn sprintf(__( + "warning: Both files modified:\n" . + "'%s/%s' and '%s/%s'.\n" . + "warning: Working tree file has been left.\n" . + "warning:\n"), $worktree, $file, $b, $file); $error = 1; } elsif (exists $tmp_modified{$file}) { my $mode = stat("$b/$file")->mode; @@ -444,8 +445,9 @@ sub dir_diff } } if ($error) { - warn "warning: Temporary files exist in '$tmpdir'.\n"; - warn "warning: You may want to cleanup or recover these.\n"; + warn sprintf(__( + "warning: Temporary files exist in '%s'.\n" . + "warning: You may want to cleanup or recover these.\n"), $tmpdir); exit(1); } else { exit_cleanup($tmpdir, $rc); diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview deleted file mode 100755 index 4e23c650fe..0000000000 --- a/contrib/gitview/gitview +++ /dev/null @@ -1,1305 +0,0 @@ -#! /usr/bin/env python - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. - -""" gitview -GUI browser for git repository -This program is based on bzrk by Scott James Remnant <scott@ubuntu.com> -""" -__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P." -__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com" -__author__ = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>" - - -import sys -import os -import gtk -import pygtk -import pango -import re -import time -import gobject -import cairo -import math -import string -import fcntl - -have_gtksourceview2 = False -have_gtksourceview = False -try: - import gtksourceview2 - have_gtksourceview2 = True -except ImportError: - try: - import gtksourceview - have_gtksourceview = True - except ImportError: - print "Running without gtksourceview2 or gtksourceview module" - -re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})') - -def list_to_string(args, skip): - count = len(args) - i = skip - str_arg=" " - while (i < count ): - str_arg = str_arg + args[i] - str_arg = str_arg + " " - i = i+1 - - return str_arg - -def show_date(epoch, tz): - secs = float(epoch) - tzsecs = float(tz[1:3]) * 3600 - tzsecs += float(tz[3:5]) * 60 - if (tz[0] == "+"): - secs += tzsecs - else: - secs -= tzsecs - - return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs)) - -def get_source_buffer_and_view(): - if have_gtksourceview2: - buffer = gtksourceview2.Buffer() - slm = gtksourceview2.LanguageManager() - gsl = slm.get_language("diff") - buffer.set_highlight_syntax(True) - buffer.set_language(gsl) - view = gtksourceview2.View(buffer) - elif have_gtksourceview: - buffer = gtksourceview.SourceBuffer() - slm = gtksourceview.SourceLanguagesManager() - gsl = slm.get_language_from_mime_type("text/x-patch") - buffer.set_highlight(True) - buffer.set_language(gsl) - view = gtksourceview.SourceView(buffer) - else: - buffer = gtk.TextBuffer() - view = gtk.TextView(buffer) - return (buffer, view) - - -class CellRendererGraph(gtk.GenericCellRenderer): - """Cell renderer for directed graph. - - This module contains the implementation of a custom GtkCellRenderer that - draws part of the directed graph based on the lines suggested by the code - in graph.py. - - Because we're shiny, we use Cairo to do this, and because we're naughty - we cheat and draw over the bits of the TreeViewColumn that are supposed to - just be for the background. - - Properties: - node (column, colour, [ names ]) tuple to draw revision node, - in_lines (start, end, colour) tuple list to draw inward lines, - out_lines (start, end, colour) tuple list to draw outward lines. - """ - - __gproperties__ = { - "node": ( gobject.TYPE_PYOBJECT, "node", - "revision node instruction", - gobject.PARAM_WRITABLE - ), - "in-lines": ( gobject.TYPE_PYOBJECT, "in-lines", - "instructions to draw lines into the cell", - gobject.PARAM_WRITABLE - ), - "out-lines": ( gobject.TYPE_PYOBJECT, "out-lines", - "instructions to draw lines out of the cell", - gobject.PARAM_WRITABLE - ), - } - - def do_set_property(self, property, value): - """Set properties from GObject properties.""" - if property.name == "node": - self.node = value - elif property.name == "in-lines": - self.in_lines = value - elif property.name == "out-lines": - self.out_lines = value - else: - raise AttributeError, "no such property: '%s'" % property.name - - def box_size(self, widget): - """Calculate box size based on widget's font. - - Cache this as it's probably expensive to get. It ensures that we - draw the graph at least as large as the text. - """ - try: - return self._box_size - except AttributeError: - pango_ctx = widget.get_pango_context() - font_desc = widget.get_style().font_desc - metrics = pango_ctx.get_metrics(font_desc) - - ascent = pango.PIXELS(metrics.get_ascent()) - descent = pango.PIXELS(metrics.get_descent()) - - self._box_size = ascent + descent + 6 - return self._box_size - - def set_colour(self, ctx, colour, bg, fg): - """Set the context source colour. - - Picks a distinct colour based on an internal wheel; the bg - parameter provides the value that should be assigned to the 'zero' - colours and the fg parameter provides the multiplier that should be - applied to the foreground colours. - """ - colours = [ - ( 1.0, 0.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 0.0, 1.0, 1.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ] - - colour %= len(colours) - red = (colours[colour][0] * fg) or bg - green = (colours[colour][1] * fg) or bg - blue = (colours[colour][2] * fg) or bg - - ctx.set_source_rgb(red, green, blue) - - def on_get_size(self, widget, cell_area): - """Return the size we need for this cell. - - Each cell is drawn individually and is only as wide as it needs - to be, we let the TreeViewColumn take care of making them all - line up. - """ - box_size = self.box_size(widget) - - cols = self.node[0] - for start, end, colour in self.in_lines + self.out_lines: - cols = int(max(cols, start, end)) - - (column, colour, names) = self.node - names_len = 0 - if (len(names) != 0): - for item in names: - names_len += len(item) - - width = box_size * (cols + 1 ) + names_len - height = box_size - - # FIXME I have no idea how to use cell_area properly - return (0, 0, width, height) - - def on_render(self, window, widget, bg_area, cell_area, exp_area, flags): - """Render an individual cell. - - Draws the cell contents using cairo, taking care to clip what we - do to within the background area so we don't draw over other cells. - Note that we're a bit naughty there and should really be drawing - in the cell_area (or even the exposed area), but we explicitly don't - want any gutter. - - We try and be a little clever, if the line we need to draw is going - to cross other columns we actually draw it as in the .---' style - instead of a pure diagonal ... this reduces confusion by an - incredible amount. - """ - ctx = window.cairo_create() - ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height) - ctx.clip() - - box_size = self.box_size(widget) - - ctx.set_line_width(box_size / 8) - ctx.set_line_cap(cairo.LINE_CAP_SQUARE) - - # Draw lines into the cell - for start, end, colour in self.in_lines: - ctx.move_to(cell_area.x + box_size * start + box_size / 2, - bg_area.y - bg_area.height / 2) - - if start - end > 1: - ctx.line_to(cell_area.x + box_size * start, bg_area.y) - ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y) - elif start - end < -1: - ctx.line_to(cell_area.x + box_size * start + box_size, - bg_area.y) - ctx.line_to(cell_area.x + box_size * end, bg_area.y) - - ctx.line_to(cell_area.x + box_size * end + box_size / 2, - bg_area.y + bg_area.height / 2) - - self.set_colour(ctx, colour, 0.0, 0.65) - ctx.stroke() - - # Draw lines out of the cell - for start, end, colour in self.out_lines: - ctx.move_to(cell_area.x + box_size * start + box_size / 2, - bg_area.y + bg_area.height / 2) - - if start - end > 1: - ctx.line_to(cell_area.x + box_size * start, - bg_area.y + bg_area.height) - ctx.line_to(cell_area.x + box_size * end + box_size, - bg_area.y + bg_area.height) - elif start - end < -1: - ctx.line_to(cell_area.x + box_size * start + box_size, - bg_area.y + bg_area.height) - ctx.line_to(cell_area.x + box_size * end, - bg_area.y + bg_area.height) - - ctx.line_to(cell_area.x + box_size * end + box_size / 2, - bg_area.y + bg_area.height / 2 + bg_area.height) - - self.set_colour(ctx, colour, 0.0, 0.65) - ctx.stroke() - - # Draw the revision node in the right column - (column, colour, names) = self.node - ctx.arc(cell_area.x + box_size * column + box_size / 2, - cell_area.y + cell_area.height / 2, - box_size / 4, 0, 2 * math.pi) - - - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.stroke_preserve() - - self.set_colour(ctx, colour, 0.5, 1.0) - ctx.fill_preserve() - - if (len(names) != 0): - name = " " - for item in names: - name = name + item + " " - - ctx.set_font_size(13) - if (flags & 1): - self.set_colour(ctx, colour, 0.5, 1.0) - else: - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.show_text(name) - -class Commit(object): - """ This represent a commit object obtained after parsing the git-rev-list - output """ - - __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer', - 'commit_date', 'commit_sha1', 'parent_sha1'] - - children_sha1 = {} - - def __init__(self, commit_lines): - self.message = "" - self.author = "" - self.date = "" - self.committer = "" - self.commit_date = "" - self.commit_sha1 = "" - self.parent_sha1 = [ ] - self.parse_commit(commit_lines) - - - def parse_commit(self, commit_lines): - - # First line is the sha1 lines - line = string.strip(commit_lines[0]) - sha1 = re.split(" ", line) - self.commit_sha1 = sha1[0] - self.parent_sha1 = sha1[1:] - - #build the child list - for parent_id in self.parent_sha1: - try: - Commit.children_sha1[parent_id].append(self.commit_sha1) - except KeyError: - Commit.children_sha1[parent_id] = [self.commit_sha1] - - # IF we don't have parent - if (len(self.parent_sha1) == 0): - self.parent_sha1 = [0] - - for line in commit_lines[1:]: - m = re.match("^ ", line) - if (m != None): - # First line of the commit message used for short log - if self.message == "": - self.message = string.strip(line) - continue - - m = re.match("tree", line) - if (m != None): - continue - - m = re.match("parent", line) - if (m != None): - continue - - m = re_ident.match(line) - if (m != None): - date = show_date(m.group('epoch'), m.group('tz')) - if m.group(1) == "author": - self.author = m.group('ident') - self.date = date - elif m.group(1) == "committer": - self.committer = m.group('ident') - self.commit_date = date - - continue - - def get_message(self, with_diff=0): - if (with_diff == 1): - message = self.diff_tree() - else: - fp = os.popen("git cat-file commit " + self.commit_sha1) - message = fp.read() - fp.close() - - return message - - def diff_tree(self): - fp = os.popen("git diff-tree --pretty --cc -v -p --always " + self.commit_sha1) - diff = fp.read() - fp.close() - return diff - -class AnnotateWindow(object): - """Annotate window. - This object represents and manages a single window containing the - annotate information of the file - """ - - def __init__(self): - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser annotation window") - self.prev_read = "" - - # Use two thirds of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.66) - height = int(monitor.height * 0.66) - self.window.set_default_size(width, height) - - def add_file_data(self, filename, commit_sha1, line_num): - fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename) - i = 1; - for line in fp.readlines(): - line = string.rstrip(line) - self.model.append(None, ["HEAD", filename, line, i]) - i = i+1 - fp.close() - - # now set the cursor position - self.treeview.set_cursor(line_num-1) - self.treeview.grab_focus() - - def _treeview_cursor_cb(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - commit_sha1 = self.model[path][0] - commit_msg = "" - fp = os.popen("git cat-file commit " + commit_sha1) - for line in fp.readlines(): - commit_msg = commit_msg + line - fp.close() - - self.commit_buffer.set_text(commit_msg) - - def _treeview_row_activated(self, *args): - """Callback for when the treeview row gets selected.""" - (path, col) = self.treeview.get_cursor() - commit_sha1 = self.model[path][0] - filename = self.model[path][1] - line_num = self.model[path][3] - - window = AnnotateWindow(); - fp = os.popen("git rev-parse "+ commit_sha1 + "~1") - commit_sha1 = string.strip(fp.readline()) - fp.close() - window.annotate(filename, commit_sha1, line_num) - - def data_ready(self, source, condition): - while (1): - try : - # A simple readline doesn't work - # a readline bug ?? - buffer = source.read(100) - - except: - # resource temporary not available - return True - - if (len(buffer) == 0): - gobject.source_remove(self.io_watch_tag) - source.close() - return False - - if (self.prev_read != ""): - buffer = self.prev_read + buffer - self.prev_read = "" - - if (buffer[len(buffer) -1] != '\n'): - try: - newline_index = buffer.rindex("\n") - except ValueError: - newline_index = 0 - - self.prev_read = buffer[newline_index:(len(buffer))] - buffer = buffer[0:newline_index] - - for buff in buffer.split("\n"): - annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$') - m = annotate_line.match(buff) - if not m: - annotate_line = re.compile('^(filename) (.+)$') - m = annotate_line.match(buff) - if not m: - continue - filename = m.group(2) - else: - self.commit_sha1 = m.group(1) - self.source_line = int(m.group(2)) - self.result_line = int(m.group(3)) - self.count = int(m.group(4)) - #set the details only when we have the file name - continue - - while (self.count > 0): - # set at result_line + count-1 the sha1 as commit_sha1 - self.count = self.count - 1 - iter = self.model.iter_nth_child(None, self.result_line + self.count-1) - self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line) - - - def annotate(self, filename, commit_sha1, line_num): - # verify the commit_sha1 specified has this filename - - fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename) - line = string.strip(fp.readline()) - if line == '': - # pop up the message the file is not there as a part of the commit - fp.close() - dialog = gtk.MessageDialog(parent=None, flags=0, - type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, - message_format=None) - dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1)) - dialog.run() - dialog.destroy() - return - - fp.close() - - vpan = gtk.VPaned(); - self.window.add(vpan); - vpan.show() - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vpan.pack1(scrollwin, True, True); - scrollwin.show() - - self.model = gtk.TreeStore(str, str, str, int) - self.treeview = gtk.TreeView(self.model) - self.treeview.set_rules_hint(True) - self.treeview.set_search_column(0) - self.treeview.connect("cursor-changed", self._treeview_cursor_cb) - self.treeview.connect("row-activated", self._treeview_row_activated) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 10) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Commit") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 0) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("File Name") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 1) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Data") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 2) - self.treeview.append_column(column) - - # The commit message window - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vpan.pack2(scrollwin, True, True); - scrollwin.show() - - commit_text = gtk.TextView() - self.commit_buffer = gtk.TextBuffer() - commit_text.set_buffer(self.commit_buffer) - scrollwin.add(commit_text) - commit_text.show() - - self.window.show() - - self.add_file_data(filename, commit_sha1, line_num) - - fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1) - flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL) - fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) - self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready) - - -class DiffWindow(object): - """Diff window. - This object represents and manages a single window containing the - differences between two revisions on a branch. - """ - - def __init__(self): - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser diff window") - - # Use two thirds of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.66) - height = int(monitor.height * 0.66) - self.window.set_default_size(width, height) - - - self.construct() - - def construct(self): - """Construct the window contents.""" - vbox = gtk.VBox() - self.window.add(vbox) - vbox.show() - - menu_bar = gtk.MenuBar() - save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE) - save_menu.connect("activate", self.save_menu_response, "save") - save_menu.show() - menu_bar.append(save_menu) - vbox.pack_start(menu_bar, expand=False, fill=True) - menu_bar.show() - - hpan = gtk.HPaned() - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - hpan.pack1(scrollwin, True, True) - scrollwin.show() - - (self.buffer, sourceview) = get_source_buffer_and_view() - - sourceview.set_editable(False) - sourceview.modify_font(pango.FontDescription("Monospace")) - scrollwin.add(sourceview) - sourceview.show() - - # The file hierarchy: a scrollable treeview - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - scrollwin.set_size_request(20, -1) - hpan.pack2(scrollwin, True, True) - scrollwin.show() - - self.model = gtk.TreeStore(str, str, str) - self.treeview = gtk.TreeView(self.model) - self.treeview.set_search_column(1) - self.treeview.connect("cursor-changed", self._treeview_clicked) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 20) - column = gtk.TreeViewColumn("Select to annotate") - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 0) - self.treeview.append_column(column) - - vbox.pack_start(hpan, expand=True, fill=True) - hpan.show() - - def _treeview_clicked(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - specific_file = self.model[path][1] - commit_sha1 = self.model[path][2] - if specific_file == None : - return - elif specific_file == "" : - specific_file = None - - window = AnnotateWindow(); - window.annotate(specific_file, commit_sha1, 1) - - - def commit_files(self, commit_sha1, parent_sha1): - self.model.clear() - add = self.model.append(None, [ "Added", None, None]) - dele = self.model.append(None, [ "Deleted", None, None]) - mod = self.model.append(None, [ "Modified", None, None]) - diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$') - fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1) - while 1: - line = string.strip(fp.readline()) - if line == '': - break - m = diff_tree.match(line) - if not m: - continue - - attr = m.group(5) - filename = m.group(6) - if attr == "A": - self.model.append(add, [filename, filename, commit_sha1]) - elif attr == "D": - self.model.append(dele, [filename, filename, commit_sha1]) - elif attr == "M": - self.model.append(mod, [filename, filename, commit_sha1]) - fp.close() - - self.treeview.expand_all() - - def set_diff(self, commit_sha1, parent_sha1, encoding): - """Set the differences showed by this window. - Compares the two trees and populates the window with the - differences. - """ - # Diff with the first commit or the last commit shows nothing - if (commit_sha1 == 0 or parent_sha1 == 0 ): - return - - fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1) - self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8')) - fp.close() - self.commit_files(commit_sha1, parent_sha1) - self.window.show() - - def save_menu_response(self, widget, string): - dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_SAVE, gtk.RESPONSE_OK)) - dialog.set_default_response(gtk.RESPONSE_OK) - response = dialog.run() - if response == gtk.RESPONSE_OK: - patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(), - self.buffer.get_end_iter()) - fp = open(dialog.get_filename(), "w") - fp.write(patch_buffer) - fp.close() - dialog.destroy() - -class GitView(object): - """ This is the main class - """ - version = "0.9" - - def __init__(self, with_diff=0): - self.with_diff = with_diff - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_border_width(0) - self.window.set_title("Git repository browser") - - self.get_encoding() - self.get_bt_sha1() - - # Use three-quarters of the screen by default - screen = self.window.get_screen() - monitor = screen.get_monitor_geometry(0) - width = int(monitor.width * 0.75) - height = int(monitor.height * 0.75) - self.window.set_default_size(width, height) - - # FIXME AndyFitz! - icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON) - self.window.set_icon(icon) - - self.accel_group = gtk.AccelGroup() - self.window.add_accel_group(self.accel_group) - self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh); - self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize); - self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen); - self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen); - - self.window.add(self.construct()) - - def refresh(self, widget, event=None, *arguments, **keywords): - self.get_encoding() - self.get_bt_sha1() - Commit.children_sha1 = {} - self.set_branch(sys.argv[without_diff:]) - self.window.show() - return True - - def maximize(self, widget, event=None, *arguments, **keywords): - self.window.maximize() - return True - - def fullscreen(self, widget, event=None, *arguments, **keywords): - self.window.fullscreen() - return True - - def unfullscreen(self, widget, event=None, *arguments, **keywords): - self.window.unfullscreen() - return True - - def get_bt_sha1(self): - """ Update the bt_sha1 dictionary with the - respective sha1 details """ - - self.bt_sha1 = { } - ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$'); - fp = os.popen('git ls-remote "${GIT_DIR-.git}"') - while 1: - line = string.strip(fp.readline()) - if line == '': - break - m = ls_remote.match(line) - if not m: - continue - (sha1, name) = (m.group(1), m.group(2)) - if not self.bt_sha1.has_key(sha1): - self.bt_sha1[sha1] = [] - self.bt_sha1[sha1].append(name) - fp.close() - - def get_encoding(self): - fp = os.popen("git config --get i18n.commitencoding") - self.encoding=string.strip(fp.readline()) - fp.close() - if (self.encoding == ""): - self.encoding = "utf-8" - - - def construct(self): - """Construct the window contents.""" - vbox = gtk.VBox() - paned = gtk.VPaned() - paned.pack1(self.construct_top(), resize=False, shrink=True) - paned.pack2(self.construct_bottom(), resize=False, shrink=True) - menu_bar = gtk.MenuBar() - menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL) - help_menu = gtk.MenuItem("Help") - menu = gtk.Menu() - about_menu = gtk.MenuItem("About") - menu.append(about_menu) - about_menu.connect("activate", self.about_menu_response, "about") - about_menu.show() - help_menu.set_submenu(menu) - help_menu.show() - menu_bar.append(help_menu) - menu_bar.show() - vbox.pack_start(menu_bar, expand=False, fill=True) - vbox.pack_start(paned, expand=True, fill=True) - paned.show() - vbox.show() - return vbox - - - def construct_top(self): - """Construct the top-half of the window.""" - vbox = gtk.VBox(spacing=6) - vbox.set_border_width(12) - vbox.show() - - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vbox.pack_start(scrollwin, expand=True, fill=True) - scrollwin.show() - - self.treeview = gtk.TreeView() - self.treeview.set_rules_hint(True) - self.treeview.set_search_column(4) - self.treeview.connect("cursor-changed", self._treeview_cursor_cb) - scrollwin.add(self.treeview) - self.treeview.show() - - cell = CellRendererGraph() - column = gtk.TreeViewColumn() - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "node", 1) - column.add_attribute(cell, "in-lines", 2) - column.add_attribute(cell, "out-lines", 3) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 65) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Message") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 4) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("width-chars", 40) - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Author") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 5) - self.treeview.append_column(column) - - cell = gtk.CellRendererText() - cell.set_property("ellipsize", pango.ELLIPSIZE_END) - column = gtk.TreeViewColumn("Date") - column.set_resizable(True) - column.pack_start(cell, expand=True) - column.add_attribute(cell, "text", 6) - self.treeview.append_column(column) - - return vbox - - def about_menu_response(self, widget, string): - dialog = gtk.AboutDialog() - dialog.set_name("Gitview") - dialog.set_version(GitView.version) - dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"]) - dialog.set_website("http://www.kernel.org/pub/software/scm/git/") - dialog.set_copyright("Use and distribute under the terms of the GNU General Public License") - dialog.set_wrap_license(True) - dialog.run() - dialog.destroy() - - - def construct_bottom(self): - """Construct the bottom half of the window.""" - vbox = gtk.VBox(False, spacing=6) - vbox.set_border_width(12) - (width, height) = self.window.get_size() - vbox.set_size_request(width, int(height / 2.5)) - vbox.show() - - self.table = gtk.Table(rows=4, columns=4) - self.table.set_row_spacings(6) - self.table.set_col_spacings(6) - vbox.pack_start(self.table, expand=False, fill=True) - self.table.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("<b>Revision:</b>") - align.add(label) - self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.revid_label = gtk.Label() - self.revid_label.set_selectable(True) - align.add(self.revid_label) - self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.revid_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("<b>Committer:</b>") - align.add(label) - self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.committer_label = gtk.Label() - self.committer_label.set_selectable(True) - align.add(self.committer_label) - self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.committer_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("<b>Timestamp:</b>") - align.add(label) - self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL) - label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - self.timestamp_label = gtk.Label() - self.timestamp_label.set_selectable(True) - align.add(self.timestamp_label) - self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL) - self.timestamp_label.show() - align.show() - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("<b>Parents:</b>") - align.add(label) - self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL) - label.show() - align.show() - self.parents_widgets = [] - - align = gtk.Alignment(0.0, 0.5) - label = gtk.Label() - label.set_markup("<b>Children:</b>") - align.add(label) - self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL) - label.show() - align.show() - self.children_widgets = [] - - scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrollwin.set_shadow_type(gtk.SHADOW_IN) - vbox.pack_start(scrollwin, expand=True, fill=True) - scrollwin.show() - - (self.message_buffer, sourceview) = get_source_buffer_and_view() - - sourceview.set_editable(False) - sourceview.modify_font(pango.FontDescription("Monospace")) - scrollwin.add(sourceview) - sourceview.show() - - return vbox - - def _treeview_cursor_cb(self, *args): - """Callback for when the treeview cursor changes.""" - (path, col) = self.treeview.get_cursor() - commit = self.model[path][0] - - if commit.committer is not None: - committer = commit.committer - timestamp = commit.commit_date - message = commit.get_message(self.with_diff) - revid_label = commit.commit_sha1 - else: - committer = "" - timestamp = "" - message = "" - revid_label = "" - - self.revid_label.set_text(revid_label) - self.committer_label.set_text(committer) - self.timestamp_label.set_text(timestamp) - self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8')) - - for widget in self.parents_widgets: - self.table.remove(widget) - - self.parents_widgets = [] - self.table.resize(4 + len(commit.parent_sha1) - 1, 4) - for idx, parent_id in enumerate(commit.parent_sha1): - self.table.set_row_spacing(idx + 3, 0) - - align = gtk.Alignment(0.0, 0.0) - self.parents_widgets.append(align) - self.table.attach(align, 1, 2, idx + 3, idx + 4, - gtk.EXPAND | gtk.FILL, gtk.FILL) - align.show() - - hbox = gtk.HBox(False, 0) - align.add(hbox) - hbox.show() - - label = gtk.Label(parent_id) - label.set_selectable(True) - hbox.pack_start(label, expand=False, fill=True) - label.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.connect("clicked", self._go_clicked_cb, parent_id) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.set_sensitive(True) - button.connect("clicked", self._show_clicked_cb, - commit.commit_sha1, parent_id, self.encoding) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - # Populate with child details - for widget in self.children_widgets: - self.table.remove(widget) - - self.children_widgets = [] - try: - child_sha1 = Commit.children_sha1[commit.commit_sha1] - except KeyError: - # We don't have child - child_sha1 = [ 0 ] - - if ( len(child_sha1) > len(commit.parent_sha1)): - self.table.resize(4 + len(child_sha1) - 1, 4) - - for idx, child_id in enumerate(child_sha1): - self.table.set_row_spacing(idx + 3, 0) - - align = gtk.Alignment(0.0, 0.0) - self.children_widgets.append(align) - self.table.attach(align, 3, 4, idx + 3, idx + 4, - gtk.EXPAND | gtk.FILL, gtk.FILL) - align.show() - - hbox = gtk.HBox(False, 0) - align.add(hbox) - hbox.show() - - label = gtk.Label(child_id) - label.set_selectable(True) - hbox.pack_start(label, expand=False, fill=True) - label.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.connect("clicked", self._go_clicked_cb, child_id) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - image = gtk.Image() - image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU) - image.show() - - button = gtk.Button() - button.add(image) - button.set_relief(gtk.RELIEF_NONE) - button.set_sensitive(True) - button.connect("clicked", self._show_clicked_cb, - child_id, commit.commit_sha1, self.encoding) - hbox.pack_start(button, expand=False, fill=True) - button.show() - - def _destroy_cb(self, widget): - """Callback for when a window we manage is destroyed.""" - self.quit() - - - def quit(self): - """Stop the GTK+ main loop.""" - gtk.main_quit() - - def run(self, args): - self.set_branch(args) - self.window.connect("destroy", self._destroy_cb) - self.window.show() - gtk.main() - - def set_branch(self, args): - """Fill in different windows with info from the reposiroty""" - fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1)) - git_rev_list_cmd = fp.read() - fp.close() - fp = os.popen("git rev-list --header --topo-order --parents " + git_rev_list_cmd) - self.update_window(fp) - - def update_window(self, fp): - commit_lines = [] - - self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str) - - # used for cursor positioning - self.index = {} - - self.colours = {} - self.nodepos = {} - self.incomplete_line = {} - self.commits = [] - - index = 0 - last_colour = 0 - last_nodepos = -1 - out_line = [] - input_line = fp.readline() - while (input_line != ""): - # The commit header ends with '\0' - # This NULL is immediately followed by the sha1 of the - # next commit - if (input_line[0] != '\0'): - commit_lines.append(input_line) - input_line = fp.readline() - continue; - - commit = Commit(commit_lines) - if (commit != None ): - self.commits.append(commit) - - # Skip the '\0 - commit_lines = [] - commit_lines.append(input_line[1:]) - input_line = fp.readline() - - fp.close() - - for commit in self.commits: - (out_line, last_colour, last_nodepos) = self.draw_graph(commit, - index, out_line, - last_colour, - last_nodepos) - self.index[commit.commit_sha1] = index - index += 1 - - self.treeview.set_model(self.model) - self.treeview.show() - - def draw_graph(self, commit, index, out_line, last_colour, last_nodepos): - in_line=[] - - # | -> outline - # X - # |\ <- inline - - # Reset nodepostion - if (last_nodepos > 5): - last_nodepos = -1 - - # Add the incomplete lines of the last cell in this - try: - colour = self.colours[commit.commit_sha1] - except KeyError: - self.colours[commit.commit_sha1] = last_colour+1 - last_colour = self.colours[commit.commit_sha1] - colour = self.colours[commit.commit_sha1] - - try: - node_pos = self.nodepos[commit.commit_sha1] - except KeyError: - self.nodepos[commit.commit_sha1] = last_nodepos+1 - last_nodepos = self.nodepos[commit.commit_sha1] - node_pos = self.nodepos[commit.commit_sha1] - - #The first parent always continue on the same line - try: - # check we already have the value - tmp_node_pos = self.nodepos[commit.parent_sha1[0]] - except KeyError: - self.colours[commit.parent_sha1[0]] = colour - self.nodepos[commit.parent_sha1[0]] = node_pos - - for sha1 in self.incomplete_line.keys(): - if (sha1 != commit.commit_sha1): - self.draw_incomplete_line(sha1, node_pos, - out_line, in_line, index) - else: - del self.incomplete_line[sha1] - - - for parent_id in commit.parent_sha1: - try: - tmp_node_pos = self.nodepos[parent_id] - except KeyError: - self.colours[parent_id] = last_colour+1 - last_colour = self.colours[parent_id] - self.nodepos[parent_id] = last_nodepos+1 - last_nodepos = self.nodepos[parent_id] - - in_line.append((node_pos, self.nodepos[parent_id], - self.colours[parent_id])) - self.add_incomplete_line(parent_id) - - try: - branch_tag = self.bt_sha1[commit.commit_sha1] - except KeyError: - branch_tag = [ ] - - - node = (node_pos, colour, branch_tag) - - self.model.append([commit, node, out_line, in_line, - commit.message, commit.author, commit.date]) - - return (in_line, last_colour, last_nodepos) - - def add_incomplete_line(self, sha1): - try: - self.incomplete_line[sha1].append(self.nodepos[sha1]) - except KeyError: - self.incomplete_line[sha1] = [self.nodepos[sha1]] - - def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index): - for idx, pos in enumerate(self.incomplete_line[sha1]): - if(pos == node_pos): - #remove the straight line and add a slash - if ((pos, pos, self.colours[sha1]) in out_line): - out_line.remove((pos, pos, self.colours[sha1])) - out_line.append((pos, pos+0.5, self.colours[sha1])) - self.incomplete_line[sha1][idx] = pos = pos+0.5 - try: - next_commit = self.commits[index+1] - if (next_commit.commit_sha1 == sha1 and pos != int(pos)): - # join the line back to the node point - # This need to be done only if we modified it - in_line.append((pos, pos-0.5, self.colours[sha1])) - continue; - except IndexError: - pass - in_line.append((pos, pos, self.colours[sha1])) - - - def _go_clicked_cb(self, widget, revid): - """Callback for when the go button for a parent is clicked.""" - try: - self.treeview.set_cursor(self.index[revid]) - except KeyError: - dialog = gtk.MessageDialog(parent=None, flags=0, - type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE, - message_format=None) - dialog.set_markup("Revision <b>%s</b> not present in the list" % revid) - # revid == 0 is the parent of the first commit - if (revid != 0 ): - dialog.format_secondary_text("Try running gitview without any options") - dialog.run() - dialog.destroy() - - self.treeview.grab_focus() - - def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding): - """Callback for when the show button for a parent is clicked.""" - window = DiffWindow() - window.set_diff(commit_sha1, parent_sha1, encoding) - self.treeview.grab_focus() - -without_diff = 0 -if __name__ == "__main__": - - if (len(sys.argv) > 1 ): - if (sys.argv[1] == "--without-diff"): - without_diff = 1 - - view = GitView( without_diff != 1) - view.run(sys.argv[without_diff:]) diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt deleted file mode 100644 index 7b5f9002b9..0000000000 --- a/contrib/gitview/gitview.txt +++ /dev/null @@ -1,57 +0,0 @@ -gitview(1) -========== - -NAME ----- -gitview - A GTK based repository browser for git - -SYNOPSIS --------- -[verse] -'gitview' [options] [args] - -DESCRIPTION ---------- - -Dependencies: - -* Python 2.4 -* PyGTK 2.8 or later -* PyCairo 1.0 or later - -OPTIONS -------- ---without-diff:: - - If the user doesn't want to list the commit diffs in the main window. - This may speed up the repository browsing. - -<args>:: - - All the valid option for linkgit:git-rev-list[1]. - -Key Bindings ------------- -F4:: - To maximize the window - -F5:: - To reread references. - -F11:: - Full screen - -F12:: - Leave full screen - -EXAMPLES --------- - -gitview v2.6.12.. include/scsi drivers/scsi:: - - Show as the changes since version v2.6.12 that changed any file in the - include/scsi or drivers/scsi subdirectories - -gitview --since=2.weeks.ago:: - - Show the changes during the last two weeks @@ -28,11 +28,11 @@ static int diff_detect_rename_default; static int diff_indent_heuristic; /* experimental */ -static int diff_compaction_heuristic; /* experimental */ static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; static int diff_context_default = 3; +static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; static const char *diff_order_file_cfg; @@ -223,16 +223,8 @@ void init_diff_ui_defaults(void) int git_diff_heuristic_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "diff.indentheuristic")) { + if (!strcmp(var, "diff.indentheuristic")) diff_indent_heuristic = git_config_bool(var, value); - if (diff_indent_heuristic) - diff_compaction_heuristic = 0; - } - if (!strcmp(var, "diff.compactionheuristic")) { - diff_compaction_heuristic = git_config_bool(var, value); - if (diff_compaction_heuristic) - diff_indent_heuristic = 0; - } return 0; } @@ -248,6 +240,12 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return -1; return 0; } + if (!strcmp(var, "diff.interhunkcontext")) { + diff_interhunk_context_default = git_config_int(var, value); + if (diff_interhunk_context_default < 0) + return -1; + return 0; + } if (!strcmp(var, "diff.renames")) { diff_detect_rename_default = git_config_rename(var, value); return 0; @@ -3371,6 +3369,7 @@ void diff_setup(struct diff_options *options) options->rename_limit = -1; options->dirstat_permille = diff_dirstat_permille_default; options->context = diff_context_default; + options->interhunkcontext = diff_interhunk_context_default; options->ws_error_highlight = ws_error_highlight_default; DIFF_OPT_SET(options, RENAME_EMPTY); @@ -3382,8 +3381,6 @@ void diff_setup(struct diff_options *options) options->xdl_opts |= diff_algorithm; if (diff_indent_heuristic) DIFF_XDL_SET(options, INDENT_HEURISTIC); - else if (diff_compaction_heuristic) - DIFF_XDL_SET(options, COMPACTION_HEURISTIC); options->orderfile = diff_order_file_cfg; @@ -3878,16 +3875,10 @@ int diff_opt_parse(struct diff_options *options, DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--ignore-blank-lines")) DIFF_XDL_SET(options, IGNORE_BLANK_LINES); - else if (!strcmp(arg, "--indent-heuristic")) { + else if (!strcmp(arg, "--indent-heuristic")) DIFF_XDL_SET(options, INDENT_HEURISTIC); - DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); - } else if (!strcmp(arg, "--no-indent-heuristic")) - DIFF_XDL_CLR(options, INDENT_HEURISTIC); - else if (!strcmp(arg, "--compaction-heuristic")) { - DIFF_XDL_SET(options, COMPACTION_HEURISTIC); + else if (!strcmp(arg, "--no-indent-heuristic")) DIFF_XDL_CLR(options, INDENT_HEURISTIC); - } else if (!strcmp(arg, "--no-compaction-heuristic")) - DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); else if (!strcmp(arg, "--patience")) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) @@ -16,11 +16,6 @@ #include "varint.h" #include "ewah/ewok.h" -struct path_simplify { - int len; - const char *path; -}; - /* * Tells read_directory_recursive how a file or directory should be treated. * Values are ordered by significance, e.g. if a directory contains both @@ -50,7 +45,7 @@ struct cached_dir { static enum path_treatment read_directory_recursive(struct dir_struct *dir, const char *path, int len, struct untracked_cache_dir *untracked, - int check_only, const struct path_simplify *simplify); + int check_only, const struct pathspec *pathspec); static int get_dtype(struct dirent *de, const char *path, int len); int fspathcmp(const char *a, const char *b) @@ -179,17 +174,21 @@ char *common_prefix(const struct pathspec *pathspec) int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) { - size_t len; + char *prefix; + size_t prefix_len; /* * Calculate common prefix for the pathspec, and * use that to optimize the directory walk */ - len = common_prefix_len(pathspec); + prefix = common_prefix(pathspec); + prefix_len = prefix ? strlen(prefix) : 0; /* Read the directory and prune it */ - read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec); - return len; + read_directory(dir, prefix, prefix_len, pathspec); + + free(prefix); + return prefix_len; } int within_depth(const char *name, int namelen, @@ -1312,7 +1311,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) static enum path_treatment treat_directory(struct dir_struct *dir, struct untracked_cache_dir *untracked, const char *dirname, int len, int baselen, int exclude, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { /* The "len-1" is to strip the final '/' */ switch (directory_exists_in_index(dirname, len-1)) { @@ -1341,7 +1340,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, untracked = lookup_untracked(dir->untracked, untracked, dirname + baselen, len - baselen); return read_directory_recursive(dir, dirname, len, - untracked, 1, simplify); + untracked, 1, pathspec); } /* @@ -1349,24 +1348,33 @@ static enum path_treatment treat_directory(struct dir_struct *dir, * reading - if the path cannot possibly be in the pathspec, * return true, and we'll skip it early. */ -static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify) +static int simplify_away(const char *path, int pathlen, + const struct pathspec *pathspec) { - if (simplify) { - for (;;) { - const char *match = simplify->path; - int len = simplify->len; + int i; - if (!match) - break; - if (len > pathlen) - len = pathlen; - if (!memcmp(path, match, len)) - return 0; - simplify++; - } - return 1; + if (!pathspec || !pathspec->nr) + return 0; + + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE | + PATHSPEC_EXCLUDE); + + for (i = 0; i < pathspec->nr; i++) { + const struct pathspec_item *item = &pathspec->items[i]; + int len = item->nowildcard_len; + + if (len > pathlen) + len = pathlen; + if (!ps_strncmp(item, item->match, path, len)) + return 0; } - return 0; + + return 1; } /* @@ -1380,19 +1388,33 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli * 2. the path is a directory prefix of some element in the * pathspec */ -static int exclude_matches_pathspec(const char *path, int len, - const struct path_simplify *simplify) -{ - if (simplify) { - for (; simplify->path; simplify++) { - if (len == simplify->len - && !memcmp(path, simplify->path, len)) - return 1; - if (len < simplify->len - && simplify->path[len] == '/' - && !memcmp(path, simplify->path, len)) - return 1; - } +static int exclude_matches_pathspec(const char *path, int pathlen, + const struct pathspec *pathspec) +{ + int i; + + if (!pathspec || !pathspec->nr) + return 0; + + GUARD_PATHSPEC(pathspec, + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ICASE | + PATHSPEC_EXCLUDE); + + for (i = 0; i < pathspec->nr; i++) { + const struct pathspec_item *item = &pathspec->items[i]; + int len = item->nowildcard_len; + + if (len == pathlen && + !ps_strncmp(item, item->match, path, pathlen)) + return 1; + if (len > pathlen && + item->match[pathlen] == '/' && + !ps_strncmp(item, item->match, path, pathlen)) + return 1; } return 0; } @@ -1460,7 +1482,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct strbuf *path, int baselen, - const struct path_simplify *simplify, + const struct pathspec *pathspec, int dtype, struct dirent *de) { int exclude; @@ -1512,7 +1534,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, case DT_DIR: strbuf_addch(path, '/'); return treat_directory(dir, untracked, path->buf, path->len, - baselen, exclude, simplify); + baselen, exclude, pathspec); case DT_REG: case DT_LNK: return exclude ? path_excluded : path_untracked; @@ -1524,7 +1546,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, struct cached_dir *cdir, struct strbuf *path, int baselen, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { strbuf_setlen(path, baselen); if (!cdir->ucd) { @@ -1541,7 +1563,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, * with check_only set. */ return read_directory_recursive(dir, path->buf, path->len, - cdir->ucd, 1, simplify); + cdir->ucd, 1, pathspec); /* * We get path_recurse in the first run when * directory_exists_in_index() returns index_nonexistent. We @@ -1556,23 +1578,23 @@ static enum path_treatment treat_path(struct dir_struct *dir, struct cached_dir *cdir, struct strbuf *path, int baselen, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { int dtype; struct dirent *de = cdir->de; if (!de) return treat_path_fast(dir, untracked, cdir, path, - baselen, simplify); + baselen, pathspec); if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) return path_none; strbuf_setlen(path, baselen); strbuf_addstr(path, de->d_name); - if (simplify_away(path->buf, path->len, simplify)) + if (simplify_away(path->buf, path->len, pathspec)) return path_none; dtype = DTYPE(de); - return treat_one_path(dir, untracked, path, baselen, simplify, dtype, de); + return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de); } static void add_untracked(struct untracked_cache_dir *dir, const char *name) @@ -1703,7 +1725,7 @@ static void close_cached_dir(struct cached_dir *cdir) static enum path_treatment read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, struct untracked_cache_dir *untracked, int check_only, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { struct cached_dir cdir; enum path_treatment state, subdir_state, dir_state = path_none; @@ -1719,7 +1741,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, while (!read_cached_dir(&cdir)) { /* check how the file or directory should be treated */ - state = treat_path(dir, untracked, &cdir, &path, baselen, simplify); + state = treat_path(dir, untracked, &cdir, &path, + baselen, pathspec); if (state > dir_state) dir_state = state; @@ -1731,8 +1754,9 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, path.buf + baselen, path.len - baselen); subdir_state = - read_directory_recursive(dir, path.buf, path.len, - ud, check_only, simplify); + read_directory_recursive(dir, path.buf, + path.len, ud, + check_only, pathspec); if (subdir_state > dir_state) dir_state = subdir_state; } @@ -1756,7 +1780,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || ((dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path.buf, path.len, - simplify))) + pathspec))) dir_add_ignored(dir, path.buf, path.len); break; @@ -1787,36 +1811,9 @@ static int cmp_name(const void *p1, const void *p2) return name_compare(e1->name, e1->len, e2->name, e2->len); } -static struct path_simplify *create_simplify(const char **pathspec) -{ - int nr, alloc = 0; - struct path_simplify *simplify = NULL; - - if (!pathspec) - return NULL; - - for (nr = 0 ; ; nr++) { - const char *match; - ALLOC_GROW(simplify, nr + 1, alloc); - match = *pathspec++; - if (!match) - break; - simplify[nr].path = match; - simplify[nr].len = simple_length(match); - } - simplify[nr].path = NULL; - simplify[nr].len = 0; - return simplify; -} - -static void free_simplify(struct path_simplify *simplify) -{ - free(simplify); -} - static int treat_leading_path(struct dir_struct *dir, const char *path, int len, - const struct path_simplify *simplify) + const struct pathspec *pathspec) { struct strbuf sb = STRBUF_INIT; int baselen, rc = 0; @@ -1840,9 +1837,9 @@ static int treat_leading_path(struct dir_struct *dir, strbuf_add(&sb, path, baselen); if (!is_directory(sb.buf)) break; - if (simplify_away(sb.buf, sb.len, simplify)) + if (simplify_away(sb.buf, sb.len, pathspec)) break; - if (treat_one_path(dir, NULL, &sb, baselen, simplify, + if (treat_one_path(dir, NULL, &sb, baselen, pathspec, DT_DIR, NULL) == path_none) break; /* do not recurse into it */ if (len <= baselen) { @@ -2010,33 +2007,14 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return root; } -int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec) +int read_directory(struct dir_struct *dir, const char *path, + int len, const struct pathspec *pathspec) { - struct path_simplify *simplify; struct untracked_cache_dir *untracked; - /* - * Check out create_simplify() - */ - if (pathspec) - GUARD_PATHSPEC(pathspec, - PATHSPEC_FROMTOP | - PATHSPEC_MAXDEPTH | - PATHSPEC_LITERAL | - PATHSPEC_GLOB | - PATHSPEC_ICASE | - PATHSPEC_EXCLUDE); - if (has_symlink_leading_path(path, len)) return dir->nr; - /* - * exclude patterns are treated like positive ones in - * create_simplify. Usually exclude patterns should be a - * subset of positive ones, which has no impacts on - * create_simplify(). - */ - simplify = create_simplify(pathspec ? pathspec->_raw : NULL); untracked = validate_untracked_cache(dir, len, pathspec); if (!untracked) /* @@ -2044,9 +2022,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru * e.g. prep_exclude() */ dir->untracked = NULL; - if (!len || treat_leading_path(dir, path, len, simplify)) - read_directory_recursive(dir, path, len, untracked, 0, simplify); - free_simplify(simplify); + if (!len || treat_leading_path(dir, path, len, pathspec)) + read_directory_recursive(dir, path, len, untracked, 0, pathspec); QSORT(dir->entries, dir->nr, cmp_name); QSORT(dir->ignored, dir->ignored_nr, cmp_name); if (dir->untracked) { @@ -2748,3 +2725,40 @@ void untracked_cache_add_to_index(struct index_state *istate, { untracked_cache_invalidate_path(istate, path); } + +/* 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 rel_path = STRBUF_INIT; + char *git_dir = real_pathdup(git_dir_); + char *work_tree = real_pathdup(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)); + + /* 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", + relative_path(work_tree, git_dir, &rel_path)); + + strbuf_release(&file_name); + strbuf_release(&rel_path); + free(work_tree); + free(git_dir); +} + +/* + * Migrate the git directory of the given path from old_git_dir to new_git_dir. + */ +void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_git_dir) +{ + if (rename(old_git_dir, new_git_dir) < 0) + die_errno(_("could not migrate git directory from '%s' to '%s'"), + old_git_dir, new_git_dir); + + connect_work_tree_and_git_dir(path, new_git_dir); +} @@ -335,4 +335,8 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked); void add_untracked_cache(struct index_state *istate); void remove_untracked_cache(struct index_state *istate); +extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +extern void relocate_gitdir(const char *path, + const char *old_git_dir, + const char *new_git_dir); #endif diff --git a/environment.c b/environment.c index 4bce3eebfa..8a83101d04 100644 --- a/environment.c +++ b/environment.c @@ -259,7 +259,7 @@ void set_git_work_tree(const char *new_work_tree) return; } git_work_tree_initialized = 1; - work_tree = xstrdup(real_path(new_work_tree)); + work_tree = real_pathdup(new_work_tree); } const char *get_git_work_tree(void) @@ -458,6 +458,10 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options) { if (!obj) return -1; + + if (obj->type == OBJ_NONE) + parse_object(obj->oid.hash); + switch (obj->type) { case OBJ_BLOB: return 0; diff --git a/git-add--interactive.perl b/git-add--interactive.perl index ee3d812695..cf6fc926ab 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -4,6 +4,7 @@ use 5.008; use strict; use warnings; use Git; +use Git::I18N; binmode(STDOUT, ":raw"); @@ -46,7 +47,6 @@ my $normal_color = $repo->get_color("", "reset"); my $diff_algorithm = $repo->config('diff.algorithm'); my $diff_indent_heuristic = $repo->config_bool('diff.indentheuristic'); -my $diff_compaction_heuristic = $repo->config_bool('diff.compactionheuristic'); my $diff_filter = $repo->config('interactive.difffilter'); my $use_readkey = 0; @@ -92,6 +92,7 @@ sub colored { } # command line options +my $cmd; my $patch_mode; my $patch_mode_revision; @@ -104,9 +105,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stage', - TARGET => '', - PARTICIPLE => 'staging', FILTER => 'file-only', IS_REVERSE => 0, }, @@ -114,9 +112,6 @@ my %patch_modes = ( DIFF => 'diff-index -p HEAD', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stash', - TARGET => '', - PARTICIPLE => 'stashing', FILTER => undef, IS_REVERSE => 0, }, @@ -124,9 +119,6 @@ my %patch_modes = ( DIFF => 'diff-index -p --cached', APPLY => sub { apply_patch 'apply -R --cached', @_; }, APPLY_CHECK => 'apply -R --cached', - VERB => 'Unstage', - TARGET => '', - PARTICIPLE => 'unstaging', FILTER => 'index-only', IS_REVERSE => 1, }, @@ -134,9 +126,6 @@ my %patch_modes = ( DIFF => 'diff-index -R -p --cached', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Apply', - TARGET => ' to index', - PARTICIPLE => 'applying', FILTER => 'index-only', IS_REVERSE => 0, }, @@ -144,9 +133,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply -R', @_; }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from worktree', - PARTICIPLE => 'discarding', FILTER => 'file-only', IS_REVERSE => 1, }, @@ -154,9 +140,6 @@ my %patch_modes = ( DIFF => 'diff-index -p', APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from index and worktree', - PARTICIPLE => 'discarding', FILTER => undef, IS_REVERSE => 1, }, @@ -164,15 +147,13 @@ my %patch_modes = ( DIFF => 'diff-index -R -p', APPLY => sub { apply_patch_for_checkout_commit '', @_ }, APPLY_CHECK => 'apply', - VERB => 'Apply', - TARGET => ' to index and worktree', - PARTICIPLE => 'applying', FILTER => undef, IS_REVERSE => 0, }, ); -my %patch_mode_flavour = %{$patch_modes{stage}}; +$patch_mode = 'stage'; +my %patch_mode_flavour = %{$patch_modes{$patch_mode}}; sub run_cmd_pipe { if ($^O eq 'MSWin32') { @@ -253,8 +234,9 @@ sub list_untracked { run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } -my $status_fmt = '%12s %12s %s'; -my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); +# TRANSLATORS: you can adjust this to align "git add -i" status menu +my $status_fmt = __('%12s %12s %s'); +my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')); { my $initial; @@ -312,7 +294,7 @@ sub list_modified { my ($change, $bin); $file = unquote_path($file); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -321,7 +303,7 @@ sub list_modified { $data{$file} = { INDEX => $change, BINARY => $bin, - FILE => 'nothing', + FILE => __('nothing'), } } elsif (($adddel, $file) = @@ -337,7 +319,7 @@ sub list_modified { $file = unquote_path($file); my ($change, $bin); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -357,7 +339,7 @@ sub list_modified { $file = unquote_path($2); if (!exists $data{$file}) { $data{$file} = +{ - INDEX => 'unchanged', + INDEX => __('unchanged'), BINARY => 0, }; } @@ -372,10 +354,10 @@ sub list_modified { if ($only) { if ($only eq 'index-only') { - next if ($it->{INDEX} eq 'unchanged'); + next if ($it->{INDEX} eq __('unchanged')); } if ($only eq 'file-only') { - next if ($it->{FILE} eq 'nothing'); + next if ($it->{FILE} eq __('nothing')); } } push @return, +{ @@ -613,12 +595,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -637,7 +619,7 @@ sub list_and_choose { } sub singleton_prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a numbered item foo - select item based on unique prefix @@ -646,7 +628,7 @@ EOF } sub prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a single item 3-5 - select a range of items @@ -667,12 +649,18 @@ sub status_cmd { sub say_n_paths { my $did = shift @_; my $cnt = scalar @_; - print "$did "; - if (1 < $cnt) { - print "$cnt paths\n"; - } - else { - print "one path\n"; + if ($did eq 'added') { + printf(__n("added %d path\n", "added %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'updated') { + printf(__n("updated %d path\n", "updated %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'reverted') { + printf(__n("reverted %d path\n", "reverted %d paths\n", + $cnt), $cnt); + } else { + printf(__n("touched %d path\n", "touched %d paths\n", + $cnt), $cnt); } } @@ -680,7 +668,7 @@ sub update_cmd { my @mods = list_modified('file-only'); return if (!@mods); - my @update = list_and_choose({ PROMPT => 'Update', + my @update = list_and_choose({ PROMPT => __('Update'), HEADER => $status_head, }, @mods); if (@update) { @@ -692,7 +680,7 @@ sub update_cmd { } sub revert_cmd { - my @update = list_and_choose({ PROMPT => 'Revert', + my @update = list_and_choose({ PROMPT => __('Revert'), HEADER => $status_head, }, list_modified()); if (@update) { @@ -715,7 +703,7 @@ sub revert_cmd { $_->{INDEX_ADDDEL} eq 'create') { system(qw(git update-index --force-remove --), $_->{VALUE}); - print "note: $_->{VALUE} is untracked now.\n"; + printf(__("note: %s is untracked now.\n"), $_->{VALUE}); } } } @@ -726,13 +714,13 @@ sub revert_cmd { } sub add_untracked_cmd { - my @add = list_and_choose({ PROMPT => 'Add untracked' }, + my @add = list_and_choose({ PROMPT => __('Add untracked') }, list_untracked()); if (@add) { system(qw(git update-index --add --), @add); say_n_paths('added', @add); } else { - print "No untracked files.\n"; + print __("No untracked files.\n"); } print "\n"; } @@ -753,8 +741,6 @@ sub parse_diff { } if ($diff_indent_heuristic) { splice @diff_cmd, 1, 0, "--indent-heuristic"; - } elsif ($diff_compaction_heuristic) { - splice @diff_cmd, 1, 0, "--compaction-heuristic"; } if (defined $patch_mode_revision) { push @diff_cmd, get_diff_reference($patch_mode_revision); @@ -1048,29 +1034,55 @@ sub color_diff { } @_; } +my %edit_hunk_manually_modes = ( + stage => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for staging."), + stash => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for stashing."), + reset_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for unstaging."), + reset_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), + checkout_index => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding"), + checkout_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding."), + checkout_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), +); + sub edit_hunk_manually { my ($oldtext) = @_; my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff"; my $fh; open $fh, '>', $hunkfile - or die "failed to open hunk edit file for writing: " . $!; - print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; + or die sprintf(__("failed to open hunk edit file for writing: %s"), $!); + print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n"); print $fh @$oldtext; - my $participle = $patch_mode_flavour{PARTICIPLE}; my $is_reverse = $patch_mode_flavour{IS_REVERSE}; my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); - print $fh <<EOF; -# --- -# To remove '$remove_minus' lines, make them ' ' lines (context). -# To remove '$remove_plus' lines, delete them. -# Lines starting with # will be removed. -# -# If the patch applies cleanly, the edited hunk will immediately be -# marked for $participle. If it does not apply cleanly, you will be given -# an opportunity to edit again. If all lines of the hunk are removed, -# then the edit is aborted and the hunk is left unchanged. + my $comment_line_char = Git::get_comment_line_char; + print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char), +--- +To remove '%s' lines, make them ' ' lines (context). +To remove '%s' lines, delete them. +Lines starting with %s will be removed. EOF +__($edit_hunk_manually_modes{$patch_mode}), +# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages. +__ <<EOF2 ; +If it does not apply cleanly, you will be given an opportunity to +edit again. If all lines of the hunk are removed, then the edit is +aborted and the hunk is left unchanged. +EOF2 close $fh; chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR))); @@ -1081,8 +1093,8 @@ EOF } open $fh, '<', $hunkfile - or die "failed to open hunk edit file for reading: " . $!; - my @newtext = grep { !/^#/ } <$fh>; + or die sprintf(__("failed to open hunk edit file for reading: %s"), $!); + my @newtext = grep { !/^$comment_line_char/ } <$fh>; close $fh; unlink $hunkfile; @@ -1166,22 +1178,66 @@ sub edit_hunk_loop { } else { prompt_yesno( - 'Your edited hunk does not apply. Edit again ' - . '(saying "no" discards!) [y/n]? ' + # TRANSLATORS: do not translate [y/n] + # The program will only accept that input + # at this point. + # Consider translating (saying "no" discards!) as + # (saying "n" for "no" discards!) if the translation + # of the word "no" does not start with n. + __('Your edited hunk does not apply. Edit again ' + . '(saying "no" discards!) [y/n]? ') ) or return undef; } } } +my %help_patch_modes = ( + stage => N__( +"y - stage this hunk +n - do not stage this hunk +q - quit; do not stage this hunk or any of the remaining ones +a - stage this hunk and all later hunks in the file +d - do not stage this hunk or any of the later hunks in the file"), + stash => N__( +"y - stash this hunk +n - do not stash this hunk +q - quit; do not stash this hunk or any of the remaining ones +a - stash this hunk and all later hunks in the file +d - do not stash this hunk or any of the later hunks in the file"), + reset_head => N__( +"y - unstage this hunk +n - do not unstage this hunk +q - quit; do not unstage this hunk or any of the remaining ones +a - unstage this hunk and all later hunks in the file +d - do not unstage this hunk or any of the later hunks in the file"), + reset_nothead => N__( +"y - apply this hunk to index +n - do not apply this hunk to index +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), + checkout_index => N__( +"y - discard this hunk from worktree +n - do not discard this hunk from worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_head => N__( +"y - discard this hunk from index and worktree +n - do not discard this hunk from index and worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_nothead => N__( +"y - apply this hunk to index and worktree +n - do not apply this hunk to index and worktree +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), +); + sub help_patch_cmd { - my $verb = lc $patch_mode_flavour{VERB}; - my $target = $patch_mode_flavour{TARGET}; - print colored $help_color, <<EOF ; -y - $verb this hunk$target -n - do not $verb this hunk$target -q - quit; do not $verb this hunk or any of the remaining ones -a - $verb this hunk and all later hunks in the file -d - do not $verb this hunk or any of the later hunks in the file + print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ; g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1213,11 +1269,11 @@ sub apply_patch_for_checkout_commit { run_git_apply 'apply '.$reverse, @_; return 1; } elsif (!$applies_index) { - print colored $error_color, "The selected hunks do not apply to the index!\n"; - if (prompt_yesno "Apply them to the worktree anyway? ") { + print colored $error_color, __("The selected hunks do not apply to the index!\n"); + if (prompt_yesno __("Apply them to the worktree anyway? ")) { return run_git_apply 'apply '.$reverse, @_; } else { - print colored $error_color, "Nothing was applied.\n"; + print colored $error_color, __("Nothing was applied.\n"); return 0; } } else { @@ -1228,7 +1284,7 @@ sub apply_patch_for_checkout_commit { sub patch_update_cmd { my @all_mods = list_modified($patch_mode_flavour{FILTER}); - error_msg "ignoring unmerged: $_->{VALUE}\n" + error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE}) for grep { $_->{UNMERGED} } @all_mods; @all_mods = grep { !$_->{UNMERGED} } @all_mods; @@ -1237,9 +1293,9 @@ sub patch_update_cmd { if (!@mods) { if (@all_mods) { - print STDERR "Only binary files changed.\n"; + print STDERR __("Only binary files changed.\n"); } else { - print STDERR "No changes.\n"; + print STDERR __("No changes.\n"); } return 0; } @@ -1247,7 +1303,7 @@ sub patch_update_cmd { @them = @mods; } else { - @them = list_and_choose({ PROMPT => 'Patch update', + @them = list_and_choose({ PROMPT => __('Patch update'), HEADER => $status_head, }, @mods); } @@ -1297,6 +1353,44 @@ sub display_hunks { return $i; } +my %patch_update_prompt_modes = ( + stage => { + mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "), + }, + stash => { + mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_head => { + mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_nothead => { + mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), + }, + checkout_index => { + mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_head => { + mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_nothead => { + mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), + }, +); + sub patch_update_file { my $quit = 0; my ($ix, $num); @@ -1369,12 +1463,9 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, $patch_mode_flavour{VERB}, - ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : - $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : - ' this hunk'), - $patch_mode_flavour{TARGET}, - " [y,n,q,a,d,/$other,?]? "; + print colored $prompt_color, + sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other); + my $line = prompt_single_character; last unless defined $line; if ($line) { @@ -1397,12 +1488,12 @@ sub patch_update_file { my $response = $1; my $no = $ix > 10 ? $ix - 10 : 0; while ($response eq '') { - my $extra = ""; $no = display_hunks(\@hunk, $no); if ($no < $num) { - $extra = " (<ret> to see more)"; + print __("go to which hunk (<ret> to see more)? "); + } else { + print __("go to which hunk? "); } - print "go to which hunk$extra? "; $response = <STDIN>; if (!defined $response) { $response = ''; @@ -1410,11 +1501,13 @@ sub patch_update_file { chomp $response; } if ($response !~ /^\s*\d+\s*$/) { - error_msg "Invalid number: '$response'\n"; + error_msg sprintf(__("Invalid number: '%s'\n"), + $response); } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - error_msg "Sorry, only $num hunks available.\n"; + error_msg sprintf(__n("Sorry, only %d hunk available.\n", + "Sorry, only %d hunks available.\n", $num), $num); } next; } @@ -1439,7 +1532,7 @@ sub patch_update_file { elsif ($line =~ m|^/(.*)|) { my $regex = $1; if ($1 eq "") { - print colored $prompt_color, "search for regex? "; + print colored $prompt_color, __("search for regex? "); $regex = <STDIN>; if (defined $regex) { chomp $regex; @@ -1452,7 +1545,7 @@ sub patch_update_file { if ($@) { my ($err,$exp) = ($@, $1); $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; - error_msg "Malformed search regexp $exp: $err\n"; + error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err); next; } my $iy = $ix; @@ -1462,7 +1555,7 @@ sub patch_update_file { $iy++; $iy = 0 if ($iy >= $num); if ($ix == $iy) { - error_msg "No hunk matches the given pattern\n"; + error_msg __("No hunk matches the given pattern\n"); last; } } @@ -1474,7 +1567,7 @@ sub patch_update_file { $ix--; } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } @@ -1483,7 +1576,7 @@ sub patch_update_file { $ix++; } else { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); } next; } @@ -1496,21 +1589,23 @@ sub patch_update_file { } } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } elsif ($line =~ /^j/) { if ($other !~ /j/) { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); next; } } elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { - print colored $header_color, "Split into ", - scalar(@split), " hunks.\n"; + print colored $header_color, sprintf( + __n("Split into %d hunk.\n", + "Split into %d hunks.\n", + scalar(@split)), scalar(@split)); } splice (@hunk, $ix, 1, @split); $num = scalar @hunk; @@ -1560,23 +1655,25 @@ sub diff_cmd { my @mods = list_modified('index-only'); @mods = grep { !($_->{BINARY}) } @mods; return if (!@mods); - my (@them) = list_and_choose({ PROMPT => 'Review diff', + my (@them) = list_and_choose({ PROMPT => __('Review diff'), IMMEDIATE => 1, HEADER => $status_head, }, @mods); return if (!@them); - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD'; system(qw(git diff -p --cached), $reference, '--', map { $_->{VALUE} } @them); } sub quit_cmd { - print "Bye.\n"; + print __("Bye.\n"); exit(0); } sub help_cmd { - print colored $help_color, <<\EOF ; +# TRANSLATORS: please do not translate the command names +# 'status', 'update', 'revert', etc. + print colored $help_color, __ <<'EOF' ; status - show paths with changes update - add working tree state to the staged set of changes revert - revert staged set of changes back to the HEAD version @@ -1594,39 +1691,40 @@ sub process_args { if ($1 eq 'reset') { $patch_mode = 'reset_head'; $patch_mode_revision = 'HEAD'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg ne '--') { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'reset_head' : 'reset_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'checkout') { - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg eq '--') { $patch_mode = 'checkout_index'; } else { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'checkout_head' : 'checkout_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'stage' or $1 eq 'stash') { $patch_mode = $1; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } else { - die "unknown --patch mode: $1"; + die sprintf(__("unknown --patch mode: %s"), $1); } } else { $patch_mode = 'stage'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } - die "invalid argument $arg, expecting --" - unless $arg eq "--"; + die sprintf(__("invalid argument %s, expecting --"), + $arg) unless $arg eq "--"; %patch_mode_flavour = %{$patch_modes{$patch_mode}}; + $cmd = 1; } elsif ($arg ne "--") { - die "invalid argument $arg, expecting --"; + die sprintf(__("invalid argument %s, expecting --"), $arg); } } @@ -1641,10 +1739,10 @@ sub main_loop { [ 'help', \&help_cmd, ], ); while (1) { - my ($it) = list_and_choose({ PROMPT => 'What now', + my ($it) = list_and_choose({ PROMPT => __('What now'), SINGLETON => 1, LIST_FLAT => 4, - HEADER => '*** Commands ***', + HEADER => __('*** Commands ***'), ON_EOF => \&quit_cmd, IMMEDIATE => 1 }, @cmd); if ($it) { @@ -1660,7 +1758,7 @@ sub main_loop { process_args(); refresh(); -if ($patch_mode) { +if ($cmd) { patch_update_cmd(); } else { diff --git a/git-compat-util.h b/git-compat-util.h index 87237b092b..f46d40e4a4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -988,6 +988,17 @@ static inline void sane_qsort(void *base, size_t nmemb, size_t size, qsort(base, nmemb, size, compar); } +#ifndef HAVE_ISO_QSORT_S +int git_qsort_s(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *ctx); +#define qsort_s git_qsort_s +#endif + +#define QSORT_S(base, n, compar, ctx) do { \ + if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \ + die("BUG: qsort_s() failed"); \ +} while (0) + #ifndef REG_STARTEND #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd" #endif diff --git a/git-mergetool.sh b/git-mergetool.sh index e52b4e4f24..c062e3de3a 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -421,7 +421,7 @@ main () { prompt=true ;; -O*) - orderfile="$1" + orderfile="${1#-O}" ;; --) shift @@ -454,6 +454,17 @@ main () { merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)" merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" + prefix=$(git rev-parse --show-prefix) || exit 1 + cd_to_toplevel + + if test -n "$orderfile" + then + orderfile=$( + git rev-parse --prefix "$prefix" -- "$orderfile" | + sed -e 1d + ) + fi + if test $# -eq 0 && test -e "$GIT_DIR/MERGE_RR" then set -- $(git rerere remaining) @@ -461,13 +472,15 @@ main () { then print_noop_and_exit fi + elif test $# -ge 0 + then + # rev-parse provides the -- needed for 'set' + eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" fi files=$(git -c core.quotePath=false \ diff --name-only --diff-filter=U \ - ${orderfile:+"$orderfile"} -- "$@") - - cd_to_toplevel + ${orderfile:+"-O$orderfile"} -- "$@") if test -z "$files" then @@ -79,6 +79,13 @@ def p4_build_cmd(cmd): if len(client) > 0: real_cmd += ["-c", client] + retries = gitConfigInt("git-p4.retries") + if retries is None: + # Perform 3 retries by default + retries = 3 + if retries > 0: + # Provide a way to not pass this option by setting git-p4.retries to 0 + real_cmd += ["-r", str(retries)] if isinstance(cmd,basestring): real_cmd = ' '.join(real_cmd) + ' ' + cmd @@ -86,6 +93,16 @@ def p4_build_cmd(cmd): real_cmd += cmd return real_cmd +def git_dir(path): + """ Return TRUE if the given path is a git directory (/path/to/dir/.git). + This won't automatically add ".git" to a directory. + """ + d = read_pipe(["git", "--git-dir", path, "rev-parse", "--git-dir"], True).strip() + if not d or len(d) == 0: + return None + else: + return d + def chdir(path, is_client_path=False): """Do chdir to the given path, and set the PWD environment variable for use by P4. It does not look at getcwd() output. @@ -263,6 +280,10 @@ def p4_revert(f): def p4_reopen(type, f): p4_system(["reopen", "-t", type, wildcard_encode(f)]) +def p4_reopen_in_change(changelist, files): + cmd = ["reopen", "-c", str(changelist)] + files + p4_system(cmd) + def p4_move(src, dest): p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)]) @@ -564,10 +585,7 @@ def currentGitBranch(): return read_pipe(["git", "name-rev", "HEAD"]).split(" ")[1].strip() def isValidGitDir(path): - if (os.path.exists(path + "/HEAD") - and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")): - return True; - return False + return git_dir(path) != None def parseRevision(ref): return read_pipe("git rev-parse %s" % ref).strip() @@ -656,7 +674,7 @@ def gitConfigInt(key): def gitConfigList(key): if not _gitConfig.has_key(key): s = read_pipe(["git", "config", "--get-all", key], ignore_error=True) - _gitConfig[key] = s.strip().split(os.linesep) + _gitConfig[key] = s.strip().splitlines() if _gitConfig[key] == ['']: _gitConfig[key] = [] return _gitConfig[key] @@ -1104,10 +1122,10 @@ class GitLFS(LargeFileSystem): '# Git LFS (see https://git-lfs.github.com/)\n', '#\n', ] + - ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n' + ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n' for f in sorted(gitConfigList('git-p4.largeFileExtensions')) ] + - ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n' + ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs diff=lfs merge=lfs -text\n' for f in sorted(self.largeFiles) if not self.hasLargeFileExtension(f) ] ) @@ -1295,6 +1313,12 @@ class P4Submit(Command, P4UserMap): optparse.make_option("--conflict", dest="conflict_behavior", choices=self.conflict_behavior_choices), optparse.make_option("--branch", dest="branch"), + optparse.make_option("--shelve", dest="shelve", action="store_true", + help="Shelve instead of submit. Shelved files are reverted, " + "restoring the workspace to the state before the shelve"), + optparse.make_option("--update-shelve", dest="update_shelve", action="store", type="int", + metavar="CHANGELIST", + help="update an existing shelved changelist, implies --shelve") ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -1302,6 +1326,8 @@ class P4Submit(Command, P4UserMap): self.detectRenames = False self.preserveUser = gitConfigBool("git-p4.preserveUser") self.dry_run = False + self.shelve = False + self.update_shelve = None self.prepare_p4_only = False self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") @@ -1470,7 +1496,7 @@ class P4Submit(Command, P4UserMap): return 1 return 0 - def prepareSubmitTemplate(self): + def prepareSubmitTemplate(self, changelist=None): """Run "p4 change -o" to grab a change specification template. This does not use "p4 -G", as it is nice to keep the submission template in original order, since a human might edit it. @@ -1482,7 +1508,11 @@ class P4Submit(Command, P4UserMap): template = "" inFilesSection = False - for line in p4_read_pipe_lines(['change', '-o']): + args = ['change', '-o'] + if changelist: + args.append(str(changelist)) + + for line in p4_read_pipe_lines(args): if line.endswith("\r\n"): line = line[:-2] + "\n" if inFilesSection: @@ -1589,11 +1619,14 @@ class P4Submit(Command, P4UserMap): pureRenameCopy = set() symlinks = set() filesToChangeExecBit = {} + all_files = list() for line in diff: diff = parseDiffTreeEntry(line) modifier = diff['status'] path = diff['src'] + all_files.append(path) + if modifier == "M": p4_edit(path) if isModeExecChanged(diff['src_mode'], diff['dst_mode']): @@ -1724,6 +1757,10 @@ class P4Submit(Command, P4UserMap): mode = filesToChangeExecBit[f] setP4ExecBit(f, mode) + if self.update_shelve: + print("all_files = %s" % str(all_files)) + p4_reopen_in_change(self.update_shelve, all_files) + # # Build p4 change description, starting with the contents # of the git commit message. @@ -1732,7 +1769,7 @@ class P4Submit(Command, P4UserMap): logMessage = logMessage.strip() (logMessage, jobs) = self.separate_jobs_from_description(logMessage) - template = self.prepareSubmitTemplate() + template = self.prepareSubmitTemplate(self.update_shelve) submitTemplate = self.prepareLogMessage(template, logMessage, jobs) if self.preserveUser: @@ -1804,7 +1841,17 @@ class P4Submit(Command, P4UserMap): if self.isWindows: message = message.replace("\r\n", "\n") submitTemplate = message[:message.index(separatorLine)] - p4_write_pipe(['submit', '-i'], submitTemplate) + + if self.update_shelve: + p4_write_pipe(['shelve', '-r', '-i'], submitTemplate) + elif self.shelve: + p4_write_pipe(['shelve', '-i'], submitTemplate) + else: + p4_write_pipe(['submit', '-i'], submitTemplate) + # The rename/copy happened by applying a patch that created a + # new file. This leaves it writable, which confuses p4. + for f in pureRenameCopy: + p4_sync(f, "-f") if self.preserveUser: if p4User: @@ -1814,23 +1861,20 @@ class P4Submit(Command, P4UserMap): changelist = self.lastP4Changelist() self.modifyChangelistUser(changelist, p4User) - # The rename/copy happened by applying a patch that created a - # new file. This leaves it writable, which confuses p4. - for f in pureRenameCopy: - p4_sync(f, "-f") submitted = True finally: # skip this patch - if not submitted: - print "Submission cancelled, undoing p4 changes." - for f in editedFiles: + if not submitted or self.shelve: + if self.shelve: + print ("Reverting shelved files.") + else: + print ("Submission cancelled, undoing p4 changes.") + for f in editedFiles | filesToDelete: p4_revert(f) for f in filesToAdd: p4_revert(f) os.remove(f) - for f in filesToDelete: - p4_revert(f) os.remove(fileName) return submitted @@ -1926,6 +1970,9 @@ class P4Submit(Command, P4UserMap): if len(self.origin) == 0: self.origin = upstream + if self.update_shelve: + self.shelve = True + if self.preserveUser: if not self.canChangeChangelists(): die("Cannot preserve user names without p4 super-user or admin permissions") @@ -2086,13 +2133,13 @@ class P4Submit(Command, P4UserMap): break chdir(self.oldWorkingDirectory) - + shelved_applied = "shelved" if self.shelve else "applied" if self.dry_run: pass elif self.prepare_p4_only: pass elif len(commits) == len(applied): - print "All commits applied!" + print ("All commits {0}!".format(shelved_applied)) sync = P4Sync() if self.branch: @@ -2104,9 +2151,9 @@ class P4Submit(Command, P4UserMap): else: if len(applied) == 0: - print "No commits applied." + print ("No commits {0}.".format(shelved_applied)) else: - print "Applied only the commits marked with '*':" + print ("{0} only the commits marked with '*':".format(shelved_applied.capitalize())) for c in commits: if c in applied: star = "*" @@ -3701,6 +3748,7 @@ def main(): if cmd.gitdir == None: cmd.gitdir = os.path.abspath(".git") if not isValidGitDir(cmd.gitdir): + # "rev-parse --git-dir" without arguments will try $PWD/.git cmd.gitdir = read_pipe("git rev-parse --git-dir").strip() if os.path.exists(cmd.gitdir): cdup = read_pipe("git rev-parse --show-cdup").strip() @@ -3713,6 +3761,7 @@ def main(): else: die("fatal: cannot locate git repository at %s" % cmd.gitdir) + # so git commands invoked from the P4 workspace will succeed os.environ["GIT_DIR"] = cmd.gitdir if not cmd.run(args): diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index f5f58c483a..4734094a3f 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -437,7 +437,8 @@ update_squash_messages () { }' <"$squash_msg".bak } >"$squash_msg" else - commit_message HEAD > "$fixup_msg" || die "$(gettext "Cannot write \$fixup_msg")" + commit_message HEAD >"$fixup_msg" || + die "$(eval_gettext "Cannot write \$fixup_msg")" count=2 { printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")" diff --git a/git-rebase.sh b/git-rebase.sh index 04f6e44bc8..48d7c5ded4 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -43,6 +43,7 @@ continue! continue abort! abort and check out the original branch skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase +quit! abort but keep HEAD where it is " . git-sh-setup set_reflog_action rebase @@ -241,7 +242,7 @@ do --verify) ok_to_skip_pre_rebase= ;; - --continue|--skip|--abort|--edit-todo) + --continue|--skip|--abort|--quit|--edit-todo) test $total_argc -eq 2 || usage action=${1##--} ;; @@ -399,6 +400,9 @@ abort) finish_rebase exit ;; +quit) + exec rm -rf "$state_dir" + ;; edit-todo) run_specific_rebase ;; diff --git a/git-relink.perl b/git-relink.perl deleted file mode 100755 index 236a3521a1..0000000000 --- a/git-relink.perl +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/perl -# Copyright 2005, Ryan Anderson <ryan@michonline.com> -# Distribution permitted under the GPL v2, as distributed -# by the Free Software Foundation. -# Later versions of the GPL at the discretion of Linus Torvalds -# -# Scan two git object-trees, and hardlink any common objects between them. - -use 5.008; -use strict; -use warnings; -use Getopt::Long; - -sub get_canonical_form($); -sub do_scan_directory($$$); -sub compare_two_files($$); -sub usage(); -sub link_two_files($$); - -# stats -my $total_linked = 0; -my $total_already = 0; -my ($linked,$already); - -my $fail_on_different_sizes = 0; -my $help = 0; -GetOptions("safe" => \$fail_on_different_sizes, - "help" => \$help); - -usage() if $help; - -my (@dirs) = @ARGV; - -usage() if (!defined $dirs[0] || !defined $dirs[1]); - -$_ = get_canonical_form($_) foreach (@dirs); - -my $master_dir = pop @dirs; - -opendir(D,$master_dir . "objects/") - or die "Failed to open $master_dir/objects/ : $!"; - -my @hashdirs = grep { ($_ eq 'pack') || /^[0-9a-f]{2}$/ } readdir(D); - -foreach my $repo (@dirs) { - $linked = 0; - $already = 0; - printf("Searching '%s' and '%s' for common objects and hardlinking them...\n", - $master_dir,$repo); - - foreach my $hashdir (@hashdirs) { - do_scan_directory($master_dir, $hashdir, $repo); - } - - printf("Linked %d files, %d were already linked.\n",$linked, $already); - - $total_linked += $linked; - $total_already += $already; -} - -printf("Totals: Linked %d files, %d were already linked.\n", - $total_linked, $total_already); - - -sub do_scan_directory($$$) { - my ($srcdir, $subdir, $dstdir) = @_; - - my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir); - my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir); - - opendir(S,$sfulldir) - or die "Failed to opendir $sfulldir: $!"; - - foreach my $file (grep(!/\.{1,2}$/, readdir(S))) { - my $sfilename = $sfulldir . $file; - my $dfilename = $dfulldir . $file; - - compare_two_files($sfilename,$dfilename); - - } - closedir(S); -} - -sub compare_two_files($$) { - my ($sfilename, $dfilename) = @_; - - # Perl's stat returns relevant information as follows: - # 0 = dev number - # 1 = inode number - # 7 = size - my @sstatinfo = stat($sfilename); - my @dstatinfo = stat($dfilename); - - if (@sstatinfo == 0 && @dstatinfo == 0) { - die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!); - - } elsif (@dstatinfo == 0) { - return; - } - - if ( ($sstatinfo[0] == $dstatinfo[0]) && - ($sstatinfo[1] != $dstatinfo[1])) { - if ($sstatinfo[7] == $dstatinfo[7]) { - link_two_files($sfilename, $dfilename); - - } else { - my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n", - $sfilename, $dfilename); - if ($fail_on_different_sizes) { - die $err; - } else { - warn $err; - } - } - - } elsif ( ($sstatinfo[0] == $dstatinfo[0]) && - ($sstatinfo[1] == $dstatinfo[1])) { - $already++; - } -} - -sub get_canonical_form($) { - my $dir = shift; - my $original = $dir; - - die "$dir is not a directory." unless -d $dir; - - $dir .= "/" unless $dir =~ m#/$#; - $dir .= ".git/" unless $dir =~ m#\.git/$#; - - die "$original does not have a .git/ subdirectory.\n" unless -d $dir; - - return $dir; -} - -sub link_two_files($$) { - my ($sfilename, $dfilename) = @_; - my $tmpdname = sprintf("%s.old",$dfilename); - rename($dfilename,$tmpdname) - or die sprintf("Failure renaming %s to %s: %s", - $dfilename, $tmpdname, $!); - - if (! link($sfilename,$dfilename)) { - my $failtxt = ""; - unless (rename($tmpdname,$dfilename)) { - $failtxt = sprintf( - "Git Repository containing %s is probably corrupted, " . - "please copy '%s' to '%s' to fix.\n", - $tmpdname, $dfilename); - } - - die sprintf("Failed to link %s to %s: %s\n%s" . - $sfilename, $dfilename, - $!, $dfilename, $failtxt); - } - - unlink($tmpdname) - or die sprintf("Unlink of %s failed: %s\n", - $dfilename, $!); - - $linked++; -} - - -sub usage() { - print("usage: git relink [--safe] <dir>... <master_dir> \n"); - print("All directories should contain a .git/objects/ subdirectory.\n"); - print("Options\n"); - print("\t--safe\t" . - "Stops if two objects with the same hash exist but " . - "have different sizes. Default is to warn and continue.\n"); - exit(1); -} diff --git a/git-request-pull.sh b/git-request-pull.sh index d5500fde46..eebd33276d 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -4,9 +4,6 @@ # This file is licensed under the GPL v2, or a later version # at the discretion of Linus Torvalds. -USAGE='<start> <url> [<end>]' -LONG_USAGE='Summarizes the changes between two commits to the standard output, -and includes the given URL in the generated summary.' SUBDIRECTORY_OK='Yes' OPTIONS_KEEPDASHDASH= OPTIONS_STUCKLONG= diff --git a/git-send-email.perl b/git-send-email.perl index da81be40cb..068d60b3e6 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -28,6 +28,7 @@ use File::Temp qw/ tempdir tempfile /; use File::Spec::Functions qw(catfile); use Error qw(:try); use Git; +use Git::I18N; Getopt::Long::Configure qw/ pass_through /; @@ -117,20 +118,20 @@ sub format_2822_time { my $localmin = $localtm[1] + $localtm[2] * 60; my $gmtmin = $gmttm[1] + $gmttm[2] * 60; if ($localtm[0] != $gmttm[0]) { - die "local zone differs from GMT by a non-minute interval\n"; + die __("local zone differs from GMT by a non-minute interval\n"); } if ((($gmttm[6] + 1) % 7) == $localtm[6]) { $localmin += 1440; } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) { $localmin -= 1440; } elsif ($gmttm[6] != $localtm[6]) { - die "local time offset greater than or equal to 24 hours\n"; + die __("local time offset greater than or equal to 24 hours\n"); } my $offset = $localmin - $gmtmin; my $offhour = $offset / 60; my $offmin = abs($offset % 60); if (abs($offhour) >= 24) { - die ("local time offset greater than or equal to 24 hours\n"); + die __("local time offset greater than or equal to 24 hours\n"); } return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d", @@ -198,13 +199,13 @@ sub do_edit { map { system('sh', '-c', $editor.' "$@"', $editor, $_); if (($? & 127) || ($? >> 8)) { - die("the editor exited uncleanly, aborting everything"); + die(__("the editor exited uncleanly, aborting everything")); } } @_; } else { system('sh', '-c', $editor.' "$@"', $editor, @_); if (($? & 127) || ($? >> 8)) { - die("the editor exited uncleanly, aborting everything"); + die(__("the editor exited uncleanly, aborting everything")); } } } @@ -278,10 +279,13 @@ sub signal_handler { # tmp files from --compose if (defined $compose_filename) { if (-e $compose_filename) { - print "'$compose_filename' contains an intermediate version of the email you were composing.\n"; + printf __("'%s' contains an intermediate version ". + "of the email you were composing.\n"), + $compose_filename; } if (-e ($compose_filename . ".final")) { - print "'$compose_filename.final' contains the composed email.\n" + printf __("'%s.final' contains the composed email.\n"), + $compose_filename; } } @@ -298,7 +302,7 @@ my $help; my $rc = GetOptions("h" => \$help, "dump-aliases" => \$dump_aliases); usage() unless $rc; -die "--dump-aliases incompatible with other options\n" +die __("--dump-aliases incompatible with other options\n") if !$help and $dump_aliases and @ARGV; $rc = GetOptions( "sender|from=s" => \$sender, @@ -361,7 +365,7 @@ unless ($rc) { usage(); } -die "Cannot run git format-patch from outside a repository\n" +die __("Cannot run git format-patch from outside a repository\n") if $format_patch and not $repo; # Now, let's fill any that aren't set in with defaults: @@ -430,7 +434,7 @@ $smtp_encryption = '' unless (defined $smtp_encryption); my(%suppress_cc); if (@suppress_cc) { foreach my $entry (@suppress_cc) { - die "Unknown --suppress-cc field: '$entry'\n" + die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry) unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/; $suppress_cc{$entry} = 1; } @@ -459,7 +463,7 @@ my $confirm_unconfigured = !defined $confirm; if ($confirm_unconfigured) { $confirm = scalar %suppress_cc ? 'compose' : 'auto'; }; -die "Unknown --confirm setting: '$confirm'\n" +die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm) unless $confirm =~ /^(?:auto|cc|compose|always|never)/; # Debugging, print out the suppressions. @@ -491,16 +495,16 @@ my %aliases; sub parse_sendmail_alias { local $_ = shift; if (/"/) { - print STDERR "warning: sendmail alias with quotes is not supported: $_\n"; + printf STDERR __("warning: sendmail alias with quotes is not supported: %s\n"), $_; } elsif (/:include:/) { - print STDERR "warning: `:include:` not supported: $_\n"; + printf STDERR __("warning: `:include:` not supported: %s\n"), $_; } elsif (/[\/|]/) { - print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n"; + printf STDERR __("warning: `/file` or `|pipe` redirection not supported: %s\n"), $_; } elsif (/^(\S+?)\s*:\s*(.+)$/) { my ($alias, $addr) = ($1, $2); $aliases{$alias} = [ split_addrs($addr) ]; } else { - print STDERR "warning: sendmail line is not recognized: $_\n"; + printf STDERR __("warning: sendmail line is not recognized: %s\n"), $_; } } @@ -581,11 +585,11 @@ sub is_format_patch_arg { if (defined($format_patch)) { return $format_patch; } - die(<<EOF); -File '$f' exists but it could also be the range of commits + die sprintf(__ <<EOF, $f, $f); +File '%s' exists but it could also be the range of commits to produce patches for. Please disambiguate by... - * Saying "./$f" if you mean a file; or + * Saying "./%s" if you mean a file; or * Giving --format-patch option if you mean a range. EOF } catch Git::Error::Command with { @@ -603,7 +607,7 @@ while (defined(my $f = shift @ARGV)) { @ARGV = (); } elsif (-d $f and !is_format_patch_arg($f)) { opendir my $dh, $f - or die "Failed to opendir $f: $!"; + or die sprintf(__("Failed to opendir %s: %s"), $f, $!); push @files, grep { -f $_ } map { catfile($f, $_) } sort readdir $dh; @@ -616,7 +620,7 @@ while (defined(my $f = shift @ARGV)) { } if (@rev_list_opts) { - die "Cannot run git format-patch from outside a repository\n" + die __("Cannot run git format-patch from outside a repository\n") unless $repo; push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts); } @@ -627,7 +631,8 @@ if ($validate) { foreach my $f (@files) { unless (-p $f) { my $error = validate_patch($f); - $error and die "fatal: $f: $error\nwarning: no patches were sent\n"; + $error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"), + $f, $error); } } } @@ -637,7 +642,7 @@ if (@files) { print $_,"\n" for (@files); } } else { - print STDERR "\nNo patch files specified!\n\n"; + print STDERR __("\nNo patch files specified!\n\n"); usage(); } @@ -650,7 +655,7 @@ sub get_patch_subject { return "GIT: $1\n"; } close $fh; - die "No subject line in $fn ?"; + die sprintf(__("No subject line in %s?"), $fn); } if ($compose) { @@ -660,25 +665,27 @@ if ($compose) { tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; open my $c, ">", $compose_filename - or die "Failed to open for writing $compose_filename: $!"; + or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!); my $tpl_sender = $sender || $repoauthor || $repocommitter || ''; my $tpl_subject = $initial_subject || ''; my $tpl_reply_to = $initial_reply_to || ''; - print $c <<EOT; + print $c <<EOT1, Git::prefix_lines("GIT: ", __ <<EOT2), <<EOT3; From $tpl_sender # This line is ignored. -GIT: Lines beginning in "GIT:" will be removed. -GIT: Consider including an overall diffstat or table of contents -GIT: for the patch you are writing. -GIT: -GIT: Clear the body content if you don't wish to send a summary. +EOT1 +Lines beginning in "GIT:" will be removed. +Consider including an overall diffstat or table of contents +for the patch you are writing. + +Clear the body content if you don't wish to send a summary. +EOT2 From: $tpl_sender Subject: $tpl_subject In-Reply-To: $tpl_reply_to -EOT +EOT3 for my $f (@files) { print $c get_patch_subject($f); } @@ -691,10 +698,10 @@ EOT } open my $c2, ">", $compose_filename . ".final" - or die "Failed to open $compose_filename.final : " . $!; + or die sprintf(__("Failed to open %s.final: %s"), $compose_filename, $!); open $c, "<", $compose_filename - or die "Failed to open $compose_filename : " . $!; + or die sprintf(__("Failed to open %s: %s"), $compose_filename, $!); my $need_8bit_cte = file_has_nonascii($compose_filename); my $in_body = 0; @@ -729,7 +736,7 @@ EOT $sender = $1; next; } elsif (/^(?:To|Cc|Bcc):/i) { - print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"; + print __("To/Cc/Bcc fields are not interpreted yet, they have been ignored\n"); next; } print $c2 $_; @@ -738,7 +745,7 @@ EOT close $c2; if ($summary_empty) { - print "Summary email is empty, skipping it\n"; + print __("Summary email is empty, skipping it\n"); $compose = -1; } } elsif ($annotate) { @@ -768,7 +775,9 @@ sub ask { return $resp; } if ($confirm_only) { - my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? "); + my $yesno = $term->readline( + # TRANSLATORS: please keep [y/N] as is. + sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp)); if (defined $yesno && $yesno =~ /y/i) { return $resp; } @@ -797,12 +806,12 @@ foreach my $f (@files) { } if (!defined $auto_8bit_encoding && scalar %broken_encoding) { - print "The following files are 8bit, but do not declare " . - "a Content-Transfer-Encoding.\n"; + print __("The following files are 8bit, but do not declare " . + "a Content-Transfer-Encoding.\n"); foreach my $f (sort keys %broken_encoding) { print " $f\n"; } - $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ", + $auto_8bit_encoding = ask(__("Which 8bit encoding should I declare [UTF-8]? "), valid_re => qr/.{4}/, confirm_only => 1, default => "UTF-8"); } @@ -810,9 +819,9 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { if (!$force) { for my $f (@files) { if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) { - die "Refusing to send because the patch\n\t$f\n" + die sprintf(__("Refusing to send because the patch\n\t%s\n" . "has the template subject '*** SUBJECT HERE ***'. " - . "Pass --force if you really want to send.\n"; + . "Pass --force if you really want to send.\n"), $f); } } } @@ -829,7 +838,7 @@ if (defined $sender) { # But it's a no-op to run sanitize_address on an already sanitized address. $sender = sanitize_address($sender); -my $to_whom = "To whom should the emails be sent (if anyone)?"; +my $to_whom = __("To whom should the emails be sent (if anyone)?"); my $prompting = 0; if (!@initial_to && !defined $to_cmd) { my $to = ask("$to_whom ", @@ -847,7 +856,7 @@ my %EXPANDED_ALIASES; sub expand_one_alias { my $alias = shift; if ($EXPANDED_ALIASES{$alias}) { - die "fatal: alias '$alias' expands to itself\n"; + die sprintf(__("fatal: alias '%s' expands to itself\n"), $alias); } local $EXPANDED_ALIASES{$alias} = 1; return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias; @@ -859,7 +868,7 @@ sub expand_one_alias { if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to = ask( - "Message-ID to be used as In-Reply-To for the first email (if any)? ", + __("Message-ID to be used as In-Reply-To for the first email (if any)? "), default => "", valid_re => qr/\@.*\./, confirm_only => 1); } @@ -909,7 +918,7 @@ sub extract_valid_address { sub extract_valid_address_or_die { my $address = shift; $address = extract_valid_address($address); - die "error: unable to extract a valid address from: $address\n" + die sprintf(__("error: unable to extract a valid address from: %s\n"), $address) if !$address; return $address; } @@ -917,8 +926,11 @@ sub extract_valid_address_or_die { sub validate_address { my $address = shift; while (!extract_valid_address($address)) { - print STDERR "error: unable to extract a valid address from: $address\n"; - $_ = ask("What to do with this address? ([q]uit|[d]rop|[e]dit): ", + printf STDERR __("error: unable to extract a valid address from: %s\n"), $address; + # TRANSLATORS: Make sure to include [q] [d] [e] in your + # translation. The program will only accept English input + # at this point. + $_ = ask(__("What to do with this address? ([q]uit|[d]rop|[e]dit): "), valid_re => qr/^(?:quit|q|drop|d|edit|e)/i, default => 'q'); if (/^d/i) { @@ -1219,7 +1231,7 @@ sub ssl_verify_params { return (SSL_verify_mode => SSL_VERIFY_PEER(), SSL_ca_file => $smtp_ssl_cert_path); } else { - die "CA path \"$smtp_ssl_cert_path\" does not exist"; + die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path); } } @@ -1293,20 +1305,26 @@ Message-Id: $message_id if ($needs_confirm eq "inform") { $confirm_unconfigured = 0; # squelch this message for the rest of this run $ask_default = "y"; # assume yes on EOF since user hasn't explicitly asked for confirmation - print " The Cc list above has been expanded by additional\n"; - print " addresses found in the patch commit message. By default\n"; - print " send-email prompts before sending whenever this occurs.\n"; - print " This behavior is controlled by the sendemail.confirm\n"; - print " configuration setting.\n"; - print "\n"; - print " For additional information, run 'git send-email --help'.\n"; - print " To retain the current behavior, but squelch this message,\n"; - print " run 'git config --global sendemail.confirm auto'.\n\n"; + print __ <<EOF ; + The Cc list above has been expanded by additional + addresses found in the patch commit message. By default + send-email prompts before sending whenever this occurs. + This behavior is controlled by the sendemail.confirm + configuration setting. + + For additional information, run 'git send-email --help'. + To retain the current behavior, but squelch this message, + run 'git config --global sendemail.confirm auto'. + +EOF } - $_ = ask("Send this email? ([y]es|[n]o|[q]uit|[a]ll): ", + # TRANSLATORS: Make sure to include [y] [n] [q] [a] in your + # translation. The program will only accept English input + # at this point. + $_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "), valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i, default => $ask_default); - die "Send this email reply required" unless defined $_; + die __("Send this email reply required") unless defined $_; if (/^n/i) { return 0; } elsif (/^q/i) { @@ -1332,7 +1350,7 @@ Message-Id: $message_id } else { if (!defined $smtp_server) { - die "The required SMTP server is not properly defined." + die __("The required SMTP server is not properly defined.") } if ($smtp_encryption eq 'ssl') { @@ -1376,14 +1394,14 @@ Message-Id: $message_id # supported commands $smtp->hello($smtp_domain); } else { - die "Server does not support STARTTLS! ".$smtp->message; + die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message); } } } if (!$smtp) { - die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", - "VALUES: server=$smtp_server ", + die __("Unable to initialize SMTP properly. Check config and use --smtp-debug."), + " VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", "hello=$smtp_domain", defined $smtp_server_port ? " port=$smtp_server_port" : ""; @@ -1400,12 +1418,12 @@ Message-Id: $message_id $smtp->datasend("$line") or die $smtp->message; } $smtp->dataend() or die $smtp->message; - $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message; + $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; } if ($quiet) { - printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); + printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject); } else { - print (($dry_run ? "Dry-" : "")."OK. Log says:\n"); + print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n")); if (!file_name_is_absolute($smtp_server)) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; @@ -1417,10 +1435,10 @@ Message-Id: $message_id } print $header, "\n"; if ($smtp) { - print "Result: ", $smtp->code, ' ', + print __("Result: "), $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n"; } else { - print "Result: OK\n"; + print __("Result: OK\n"); } } @@ -1433,7 +1451,7 @@ $subject = $initial_subject; $message_num = 0; foreach my $t (@files) { - open my $fh, "<", $t or die "can't open file $t"; + open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t); my $author = undef; my $sauthor = undef; @@ -1480,13 +1498,13 @@ foreach my $t (@files) { $sauthor = sanitize_address($author); next if $suppress_cc{'author'}; next if $suppress_cc{'self'} and $sauthor eq $sender; - printf("(mbox) Adding cc: %s from line '%s'\n", + printf(__("(mbox) Adding cc: %s from line '%s'\n"), $1, $_) unless $quiet; push @cc, $1; } elsif (/^To:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { - printf("(mbox) Adding to: %s from line '%s'\n", + printf(__("(mbox) Adding to: %s from line '%s'\n"), $addr, $_) unless $quiet; push @to, $addr; } @@ -1500,7 +1518,7 @@ foreach my $t (@files) { } else { next if ($suppress_cc{'cc'}); } - printf("(mbox) Adding cc: %s from line '%s'\n", + printf(__("(mbox) Adding cc: %s from line '%s'\n"), $addr, $_) unless $quiet; push @cc, $addr; } @@ -1534,7 +1552,7 @@ foreach my $t (@files) { # So let's support that, too. $input_format = 'lots'; if (@cc == 0 && !$suppress_cc{'cc'}) { - printf("(non-mbox) Adding cc: %s from line '%s'\n", + printf(__("(non-mbox) Adding cc: %s from line '%s'\n"), $_, $_) unless $quiet; push @cc, $_; } elsif (!defined $subject) { @@ -1557,7 +1575,7 @@ foreach my $t (@files) { next if $suppress_cc{'bodycc'} and $what =~ /Cc/i; } push @cc, $c; - printf("(body) Adding cc: %s from line '%s'\n", + printf(__("(body) Adding cc: %s from line '%s'\n"), $c, $_) unless $quiet; } } @@ -1655,18 +1673,18 @@ sub recipients_cmd { my @addresses = (); open my $fh, "-|", "$cmd \Q$file\E" - or die "($prefix) Could not execute '$cmd'"; + or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd); while (my $address = <$fh>) { $address =~ s/^\s*//g; $address =~ s/\s*$//g; $address = sanitize_address($address); next if ($address eq $sender and $suppress_cc{'self'}); push @addresses, $address; - printf("($prefix) Adding %s: %s from: '%s'\n", - $what, $address, $cmd) unless $quiet; + printf(__("(%s) Adding %s: %s from: '%s'\n"), + $prefix, $what, $address, $cmd) unless $quiet; } close $fh - or die "($prefix) failed to close pipe to '$cmd'"; + or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd); return @addresses; } @@ -1693,7 +1711,7 @@ sub apply_transfer_encoding { $message = MIME::Base64::decode($message) if ($from eq 'base64'); - die "cannot send message as 7bit" + die __("cannot send message as 7bit") if ($to eq '7bit' and $message =~ /[^[:ascii:]]/); return $message if ($to eq '7bit' or $to eq '8bit'); @@ -1701,7 +1719,7 @@ sub apply_transfer_encoding { if ($to eq 'quoted-printable'); return MIME::Base64::encode($message, "\n") if ($to eq 'base64'); - die "invalid transfer encoding"; + die __("invalid transfer encoding"); } sub unique_email_list { @@ -1720,10 +1738,10 @@ sub unique_email_list { sub validate_patch { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { if (length($line) > 998) { - return "$.: patch contains a line longer than 998 characters"; + return sprintf(__("%s: patch contains a line longer than 998 characters"), $.); } } return; @@ -1739,10 +1757,11 @@ sub handle_backup { (substr($file, 0, $lastlen) eq $last) && ($suffix = substr($file, $lastlen)) !~ /^[a-z0-9]/i) { if (defined $known_suffix && $suffix eq $known_suffix) { - print "Skipping $file with backup suffix '$known_suffix'.\n"; + printf(__("Skipping %s with backup suffix '%s'.\n"), $file, $known_suffix); $skip = 1; } else { - my $answer = ask("Do you really want to send $file? (y|N): ", + # TRANSLATORS: please keep "[y|N]" as is. + my $answer = ask(sprintf(__("Do you really want to send %s? [y|N]: "), $file), valid_re => qr/^(?:y|n)/i, default => 'n'); $skip = ($answer ne 'y'); @@ -1770,7 +1789,7 @@ sub handle_backup_files { sub file_has_nonascii { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { return 1 if $line =~ /[^[:ascii:]]/; } @@ -1780,7 +1799,7 @@ sub file_has_nonascii { sub body_or_subject_has_nonascii { my $fn = shift; open(my $fh, '<', $fn) - or die "unable to open $fn: $!\n"; + or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { last if $line =~ /^$/; return 1 if $line =~ /^Subject.*[^[:ascii:]]/; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 240c7ebcd1..378928518b 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -196,14 +196,14 @@ require_work_tree_exists () { if test "z$(git rev-parse --is-bare-repository)" != zfalse then program_name=$0 - die "$(gettext "fatal: \$program_name cannot be used without a working tree.")" + die "$(eval_gettext "fatal: \$program_name cannot be used without a working tree.")" fi } require_work_tree () { test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || { program_name=$0 - die "$(gettext "fatal: \$program_name cannot be used without a working tree.")" + die "$(eval_gettext "fatal: \$program_name cannot be used without a working tree.")" } } diff --git a/git-submodule.sh b/git-submodule.sh index a024a135d6..123ac104c6 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -12,7 +12,8 @@ USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <re or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> - or: $dashless [--quiet] sync [--recursive] [--] [<path>...]" + or: $dashless [--quiet] sync [--recursive] [--] [<path>...] + or: $dashless [--quiet] absorbgitdirs [--] [<path>...]" OPTIONS_SPEC= SUBDIRECTORY_OK=Yes . git-sh-setup @@ -21,14 +22,10 @@ require_work_tree wt_prefix=$(git rev-parse --show-prefix) cd_to_toplevel -# Restrict ourselves to a vanilla subset of protocols; the URLs -# we get are under control of a remote repository, and we do not -# want them kicking off arbitrary git-remote-* programs. -# -# If the user has already specified a set of allowed protocols, -# we assume they know what they're doing and use that instead. -: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh} -export GIT_ALLOW_PROTOCOL +# Tell the rest of git that any URLs we get don't come +# directly from the user, so it can apply policy as appropriate. +GIT_PROTOCOL_FROM_USER=0 +export GIT_PROTOCOL_FROM_USER command= branch= @@ -207,8 +204,14 @@ cmd_add() tstart s|/*$|| ') - git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && - die "$(eval_gettext "'\$sm_path' already exists in the index")" + if test -z "$force" + then + git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index")" + else + git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")" + fi if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1 then @@ -374,7 +377,7 @@ cmd_init() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} "$@" + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper init ${GIT_QUIET:+--quiet} "$@" } # @@ -1131,6 +1134,11 @@ cmd_sync() done } +cmd_absorbgitdirs() +{ + git submodule--helper absorb-git-dirs --prefix "$wt_prefix" "$@" +} + # This loop parses the command line arguments to find the # subcommand name to dispatch. Parsing of the subcommand specific # options are primarily done by the subcommand implementations. @@ -1140,7 +1148,7 @@ cmd_sync() while test $# != 0 && test -z "$command" do case "$1" in - add | foreach | init | deinit | update | status | summary | sync) + add | foreach | init | deinit | update | status | summary | sync | absorbgitdirs) command=$1 ;; -q|--quiet) @@ -424,6 +424,7 @@ static struct cmd_struct commands[] = { { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "difftool", cmd_difftool, RUN_SETUP | NEED_WORK_TREE }, { "fast-export", cmd_fast_export, RUN_SETUP }, { "fetch", cmd_fetch, RUN_SETUP }, { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, @@ -434,7 +435,7 @@ static struct cmd_struct commands[] = { { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP_GENTLY }, + { "grep", cmd_grep, RUN_SETUP_GENTLY | SUPPORT_SUPER_PREFIX }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, @@ -493,7 +494,7 @@ static struct cmd_struct commands[] = { { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, - { "submodule--helper", cmd_submodule__helper, RUN_SETUP }, + { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX}, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tag", cmd_tag, RUN_SETUP }, { "unpack-file", cmd_unpack_file, RUN_SETUP }, @@ -575,8 +576,7 @@ static void handle_builtin(int argc, const char **argv) static void execv_dashed_external(const char **argv) { - struct strbuf cmd = STRBUF_INIT; - const char *tmp; + struct child_process cmd = CHILD_PROCESS_INIT; int status; if (get_super_prefix()) @@ -586,30 +586,25 @@ static void execv_dashed_external(const char **argv) use_pager = check_pager_config(argv[0]); commit_pager_choice(); - strbuf_addf(&cmd, "git-%s", argv[0]); + argv_array_pushf(&cmd.args, "git-%s", argv[0]); + argv_array_pushv(&cmd.args, argv + 1); + cmd.clean_on_exit = 1; + cmd.wait_after_clean = 1; + cmd.silent_exec_failure = 1; - /* - * argv[0] must be the git command, but the argv array - * belongs to the caller, and may be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ - tmp = argv[0]; - argv[0] = cmd.buf; - - trace_argv_printf(argv, "trace: exec:"); + trace_argv_printf(cmd.args.argv, "trace: exec:"); /* - * if we fail because the command is not found, it is - * OK to return. Otherwise, we just pass along the status code. + * If we fail because the command is not found, it is + * OK to return. Otherwise, we just pass along the status code, + * or our usual generic code if we were not even able to exec + * the program. */ - status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE | RUN_CLEAN_ON_EXIT); - if (status >= 0 || errno != ENOENT) + status = run_command(&cmd); + if (status >= 0) exit(status); - - argv[0] = tmp; - - strbuf_release(&cmd); + else if (errno != ENOENT) + exit(128); } static int run_argv(int *argcp, const char ***argv) diff --git a/gitk-git/Makefile b/gitk-git/Makefile index 5acdc900ab..5bdd52a6eb 100644 --- a/gitk-git/Makefile +++ b/gitk-git/Makefile @@ -50,6 +50,7 @@ endif all:: gitk-wish $(ALL_MSGFILES) install:: all + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -m 755 gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(msgsdir_SQ)' $(foreach p,$(ALL_MSGFILES), $(INSTALL) -m 644 $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true diff --git a/gitk-git/gitk b/gitk-git/gitk index 805a1c7030..a14d7a16b2 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright © 2005-2014 Paul Mackerras. All rights reserved. +# Copyright © 2005-2016 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. @@ -588,7 +588,7 @@ proc updatecommits {} { proc reloadcommits {} { global curview viewcomplete selectedline currentid thickerline global showneartags treediffs commitinterest cached_commitrow - global targetid + global targetid commitinfo set selid {} if {$selectedline ne {}} { @@ -609,6 +609,7 @@ proc reloadcommits {} { getallcommits } clear_display + unset -nocomplain commitinfo unset -nocomplain commitinterest unset -nocomplain cached_commitrow unset -nocomplain targetid @@ -1315,7 +1316,7 @@ proc commitonrow {row} { proc closevarcs {v} { global varctok varccommits varcid parents children - global cmitlisted commitidx vtokmod + global cmitlisted commitidx vtokmod curview numcommits set missing_parents 0 set scripts {} @@ -1340,6 +1341,9 @@ proc closevarcs {v} { } lappend varccommits($v,$b) $p incr commitidx($v) + if {$v == $curview} { + set numcommits $commitidx($v) + } set scripts [check_interest $p $scripts] } } @@ -2265,7 +2269,7 @@ proc makewindow {} { set h [expr {[font metrics uifont -linespace] + 2}] set progresscanv .tf.bar.progress canvas $progresscanv -relief sunken -height $h -borderwidth 2 - set progressitem [$progresscanv create rect -1 0 0 $h -fill lime] + set progressitem [$progresscanv create rect -1 0 0 $h -fill "#00ff00"] set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] } @@ -2403,7 +2407,7 @@ proc makewindow {} { set ctext .bleft.bottom.ctext text $ctext -background $bgcolor -foreground $fgcolor \ - -state disabled -font textfont \ + -state disabled -undo 0 -font textfont \ -yscrollcommand scrolltext -wrap none \ -xscrollcommand ".bleft.bottom.sbhorizontal set" if {$have_tk85} { @@ -2664,6 +2668,7 @@ proc makewindow {} { set headctxmenu .headctxmenu makemenu $headctxmenu { {mc "Check out this branch" command cobranch} + {mc "Rename this branch" command mvbranch} {mc "Remove this branch" command rmbranch} {mc "Copy branch name" command {clipboard clear; clipboard append $headmenuhead}} } @@ -3033,7 +3038,7 @@ proc about {} { message $w.m -text [mc " Gitk - a commit viewer for git -Copyright \u00a9 2005-2014 Paul Mackerras +Copyright \u00a9 2005-2016 Paul Mackerras Use and redistribute under the terms of the GNU General Public License"] \ -justify center -aspect 400 -border 2 -bg $bgcolor -relief groove @@ -3397,7 +3402,7 @@ set rectmask { 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00}; } -image create bitmap reficon-H -background black -foreground lime \ +image create bitmap reficon-H -background black -foreground "#00ff00" \ -data $rectdata -maskdata $rectmask image create bitmap reficon-o -background black -foreground "#ddddff" \ -data $rectdata -maskdata $rectmask @@ -8069,7 +8074,11 @@ proc getblobdiffline {bdf ids} { $ctext conf -state normal while {[incr nr] <= 1000 && [gets $bdf line] >= 0} { if {$ids != $diffids || $bdf != $blobdifffd($ids)} { + # Older diff read. Abort it. catch {close $bdf} + if {$ids != $diffids} { + array unset blobdifffd $ids + } return 0 } parseblobdiffline $ids $line @@ -8078,6 +8087,7 @@ proc getblobdiffline {bdf ids} { blobdiffmaybeseehere [eof $bdf] if {[eof $bdf]} { catch {close $bdf} + array unset blobdifffd $ids return 0 } return [expr {$nr >= 1000? 2: 1}] @@ -9452,26 +9462,63 @@ proc wrcomcan {} { } proc mkbranch {} { - global rowmenuid mkbrtop NS + global NS rowmenuid + + set top .branchdialog + + set val(name) "" + set val(id) $rowmenuid + set val(command) [list mkbrgo $top] + + set ui(title) [mc "Create branch"] + set ui(accept) [mc "Create"] + + branchdia $top val ui +} + +proc mvbranch {} { + global NS + global headmenuid headmenuhead + + set top .branchdialog + + set val(name) $headmenuhead + set val(id) $headmenuid + set val(command) [list mvbrgo $top $headmenuhead] + + set ui(title) [mc "Rename branch %s" $headmenuhead] + set ui(accept) [mc "Rename"] + + branchdia $top val ui +} + +proc branchdia {top valvar uivar} { + global NS commitinfo + upvar $valvar val $uivar ui - set top .makebranch catch {destroy $top} ttk_toplevel $top make_transient $top . - ${NS}::label $top.title -text [mc "Create new branch"] + ${NS}::label $top.title -text $ui(title) grid $top.title - -pady 10 ${NS}::label $top.id -text [mc "ID:"] ${NS}::entry $top.sha1 -width 40 - $top.sha1 insert 0 $rowmenuid + $top.sha1 insert 0 $val(id) $top.sha1 conf -state readonly grid $top.id $top.sha1 -sticky w + ${NS}::entry $top.head -width 60 + $top.head insert 0 [lindex $commitinfo($val(id)) 0] + $top.head conf -state readonly + grid x $top.head -sticky ew + grid columnconfigure $top 1 -weight 1 ${NS}::label $top.nlab -text [mc "Name:"] ${NS}::entry $top.name -width 40 + $top.name insert 0 $val(name) grid $top.nlab $top.name -sticky w ${NS}::frame $top.buts - ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top] + ${NS}::button $top.buts.go -text $ui(accept) -command $val(command) ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}" - bind $top <Key-Return> [list mkbrgo $top] + bind $top <Key-Return> $val(command) bind $top <Key-Escape> "catch {destroy $top}" grid $top.buts.go $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a @@ -9526,6 +9573,46 @@ proc mkbrgo {top} { } } +proc mvbrgo {top prevname} { + global headids idheads mainhead mainheadid + + set name [$top.name get] + set id [$top.sha1 get] + set cmdargs {} + if {$name eq $prevname} { + catch {destroy $top} + return + } + if {$name eq {}} { + error_popup [mc "Please specify a new name for the branch"] $top + return + } + catch {destroy $top} + lappend cmdargs -m $prevname $name + nowbusy renamebranch + update + if {[catch { + eval exec git branch $cmdargs + } err]} { + notbusy renamebranch + error_popup $err + } else { + notbusy renamebranch + removehead $id $prevname + removedhead $id $prevname + set headids($name) $id + lappend idheads($id) $name + addedhead $id $name + if {$prevname eq $mainhead} { + set mainhead $name + set mainheadid $id + } + redrawtags $id + dispneartags 0 + run refill_reflist + } +} + proc exec_citool {tool_args {baseid {}}} { global commitinfo env @@ -9751,20 +9838,25 @@ proc readresetstat {fd} { # context menu for a head proc headmenu {x y id head} { - global headmenuid headmenuhead headctxmenu mainhead + global headmenuid headmenuhead headctxmenu mainhead headids stopfinding set headmenuid $id set headmenuhead $head - set state normal + array set state {0 normal 1 normal 2 normal} if {[string match "remotes/*" $head]} { - set state disabled + set localhead [string range $head [expr [string last / $head] + 1] end] + if {[info exists headids($localhead)]} { + set state(0) disabled + } + array set state {1 disabled 2 disabled} } if {$head eq $mainhead} { - set state disabled + array set state {0 disabled 2 disabled} + } + foreach i {0 1 2} { + $headctxmenu entryconfigure $i -state $state($i) } - $headctxmenu entryconfigure 0 -state $state - $headctxmenu entryconfigure 1 -state $state tk_popup $headctxmenu $x $y } @@ -9773,11 +9865,27 @@ proc cobranch {} { global showlocalchanges # check the tree is clean first?? + set newhead $headmenuhead + set command [list | git checkout] + if {[string match "remotes/*" $newhead]} { + set remote $newhead + set newhead [string range $newhead [expr [string last / $newhead] + 1] end] + # The following check is redundant - the menu option should + # be disabled to begin with... + if {[info exists headids($newhead)]} { + error_popup [mc "A local branch named %s exists already" $newhead] + return + } + lappend command -b $newhead --track $remote + } else { + lappend command $newhead + } + lappend command 2>@1 nowbusy checkout [mc "Checking out"] update dohidelocalchanges if {[catch { - set fd [open [list | git checkout $headmenuhead 2>@1] r] + set fd [open $command r] } err]} { notbusy checkout error_popup $err @@ -9785,12 +9893,12 @@ proc cobranch {} { dodiffindex } } else { - filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid] + filerun $fd [list readcheckoutstat $fd $newhead $headmenuid] } } proc readcheckoutstat {fd newhead newheadid} { - global mainhead mainheadid headids showlocalchanges progresscoords + global mainhead mainheadid headids idheads showlocalchanges progresscoords global viewmainheadid curview if {[gets $fd line] >= 0} { @@ -9805,8 +9913,14 @@ proc readcheckoutstat {fd newhead newheadid} { notbusy checkout if {[catch {close $fd} err]} { error_popup $err + return } set oldmainid $mainheadid + if {! [info exists headids($newhead)]} { + set headids($newhead) $newheadid + lappend idheads($newheadid) $newhead + addedhead $newheadid $newhead + } set mainhead $newhead set mainheadid $newheadid set viewmainheadid($curview) $newheadid @@ -12188,7 +12302,7 @@ if {[tk windowingsystem] eq "aqua"} { set extdifftool "meld" } -set colors {lime red blue magenta darkgrey brown orange} +set colors {"#00ff00" red blue magenta darkgrey brown orange} if {[tk windowingsystem] eq "win32"} { set uicolor SystemButtonFace set uifgcolor SystemButtonText @@ -12206,12 +12320,12 @@ if {[tk windowingsystem] eq "win32"} { } set diffcolors {red "#00a000" blue} set diffcontext 3 -set mergecolors {red blue lime purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"} +set mergecolors {red blue "#00ff00" purple brown "#009090" magenta "#808000" "#009000" "#ff0080" cyan "#b07070" "#70b0f0" "#70f0b0" "#f0b070" "#ff70b0"} set ignorespace 0 set worddiff "" set markbgcolor "#e0e0ff" -set headbgcolor lime +set headbgcolor "#00ff00" set headfgcolor black set headoutlinecolor black set remotebgcolor #ffddaa @@ -12226,7 +12340,7 @@ set linehoverfgcolor black set linehoveroutlinecolor black set mainheadcirclecolor yellow set workingfilescirclecolor red -set indexcirclecolor lime +set indexcirclecolor "#00ff00" set circlecolors {white blue gray blue blue} set linkfgcolor blue set circleoutlinecolor $fgcolor diff --git a/gitk-git/po/bg.po b/gitk-git/po/bg.po index 99aa77aa63..407d5550b1 100644 --- a/gitk-git/po/bg.po +++ b/gitk-git/po/bg.po @@ -371,14 +371,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk — визуализация на подаванията в Git\n" "\n" -"Авторски права: © 2005-2014 Paul Mackerras\n" +"Авторски права: © 2005-2016 Paul Mackerras\n" "\n" "Използвайте и разпространявайте при условията на ОПЛ на ГНУ" diff --git a/gitk-git/po/ca.po b/gitk-git/po/ca.po index 5ad066f7ce..87dfc18b44 100644 --- a/gitk-git/po/ca.po +++ b/gitk-git/po/ca.po @@ -1,5 +1,5 @@ # Translation of gitk -# Copyright (C) 2005-2014 Paul Mackerras +# Copyright (C) 2005-2016 Paul Mackerras # This file is distributed under the same license as the gitk package. # Alex Henrie <alexhenrie24@gmail.com>, 2015. # @@ -365,14 +365,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - visualitzador de comissions per al git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Useu-lo i redistribuïu-lo sota els termes de la Llicència Pública General GNU" diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po index bde749ed8a..5db3824828 100644 --- a/gitk-git/po/de.po +++ b/gitk-git/po/de.po @@ -363,14 +363,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - eine Visualisierung der Git-Historie\n" "\n" -"Copyright \\u00a9 2005-2014 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public " "License" diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po index ddcb0a5f68..fef3bbafee 100644 --- a/gitk-git/po/es.po +++ b/gitk-git/po/es.po @@ -370,14 +370,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizador de revisiones para git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Uso y redistribución permitidos según los términos de la Licencia Pública " "General de GNU (GNU GPL)" diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po index c44f994fa5..e4fac932e5 100644 --- a/gitk-git/po/fr.po +++ b/gitk-git/po/fr.po @@ -372,14 +372,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - visualisateur de commit pour git\n" "\n" -"Copyright \\u00a9 2005-2014 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Utilisation et redistribution soumises aux termes de la GNU General Public License" diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po index 66fd75ba5b..79ec5a5656 100644 --- a/gitk-git/po/hu.po +++ b/gitk-git/po/hu.po @@ -366,14 +366,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - commit nézegető a githez\n" "\n" -"Szerzői jog \\u00a9 2005-2010 Paul Mackerras\n" +"Szerzői jog \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Használd és terjeszd a GNU General Public License feltételei mellett" diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po index b5f002db7d..b58d23eb2b 100644 --- a/gitk-git/po/it.po +++ b/gitk-git/po/it.po @@ -367,14 +367,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - un visualizzatore di revisioni per git\n" "\n" -"Copyright \\u00a9 2005-2010 Paul Mackerras\n" +"Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " "License" diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po index f143753db0..ca3c29b457 100644 --- a/gitk-git/po/ja.po +++ b/gitk-git/po/ja.po @@ -2,16 +2,17 @@ # Copyright (C) 2005-2015 Paul Mackerras # This file is distributed under the same license as the gitk package. # -# YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>, 2015. # Mizar <mizar.jp@gmail.com>, 2009. # Junio C Hamano <gitster@pobox.com>, 2009. +# YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>, 2015. +# Satoshi Yasushima <s.yasushima@gmail.com>, 2016. msgid "" msgstr "" "Project-Id-Version: gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-11-12 13:00+0900\n" -"Last-Translator: YOKOTA Hiroshi <yokota@netlab.cs.tsukuba.ac.jp>\n" +"Last-Translator: Satoshi Yasushima <s.yasushima@gmail.com>\n" "Language-Team: Japanese\n" "Language: ja\n" "MIME-Version: 1.0\n" @@ -314,11 +315,11 @@ msgstr "マークを付けたコミットと比較する" #: gitk:2630 gitk:2641 msgid "Diff this -> marked commit" -msgstr "これと選択したコミットのdiffを見る" +msgstr "これとマークを付けたコミットのdiffを見る" #: gitk:2631 gitk:2642 msgid "Diff marked commit -> this" -msgstr "選択したコミットとこれのdiffを見る" +msgstr "マークを付けたコミットとこれのdiffを見る" #: gitk:2632 msgid "Revert this commit" @@ -373,14 +374,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - gitコミットビューア\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "使用および再配布は GNU General Public License に従ってください" diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po index 3f78f1b748..1feb34854b 100644 --- a/gitk-git/po/pt_br.po +++ b/gitk-git/po/pt_br.po @@ -368,14 +368,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - um visualizador de revisões para o git \n" "\n" -"Copyright ©9 2005-2010 Paul Mackerras\n" +"Copyright ©9 2005-2016 Paul Mackerras\n" "\n" "Uso e distribuição segundo os termos da Licença Pública Geral GNU" diff --git a/gitk-git/po/pt_pt.po b/gitk-git/po/pt_pt.po new file mode 100644 index 0000000000..f680ea86aa --- /dev/null +++ b/gitk-git/po/pt_pt.po @@ -0,0 +1,1376 @@ +# Portuguese translations for gitk package. +# Copyright (C) 2016 Paul Mackerras +# This file is distributed under the same license as the gitk package. +# Vasco Almeida <vascomalmeida@sapo.pt>, 2016. +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-15 16:52+0000\n" +"PO-Revision-Date: 2016-05-06 15:35+0000\n" +"Last-Translator: Vasco Almeida <vascomalmeida@sapo.pt>\n" +"Language-Team: Portuguese\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" + +#: gitk:140 +msgid "Couldn't get list of unmerged files:" +msgstr "Não foi possível obter lista de ficheiros não integrados:" + +#: gitk:212 gitk:2399 +msgid "Color words" +msgstr "Colorir palavras" + +#: gitk:217 gitk:2399 gitk:8239 gitk:8272 +msgid "Markup words" +msgstr "Marcar palavras" + +#: gitk:324 +msgid "Error parsing revisions:" +msgstr "Erro ao analisar revisões:" + +#: gitk:380 +msgid "Error executing --argscmd command:" +msgstr "Erro ao executar o comando de --argscmd:" + +#: gitk:393 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " +"integrar." + +#: gitk:396 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " +"integrar ao nível de ficheiro." + +#: gitk:418 gitk:566 +msgid "Error executing git log:" +msgstr "Erro ao executar git log:" + +#: gitk:436 gitk:582 +msgid "Reading" +msgstr "A ler" + +#: gitk:496 gitk:4544 +msgid "Reading commits..." +msgstr "A ler commits..." + +#: gitk:499 gitk:1637 gitk:4547 +msgid "No commits selected" +msgstr "Nenhum commit selecionado" + +#: gitk:1445 gitk:4064 gitk:12469 +msgid "Command line" +msgstr "Linha de comandos" + +#: gitk:1511 +msgid "Can't parse git log output:" +msgstr "Não é possível analisar a saída de git log:" + +#: gitk:1740 +msgid "No commit information available" +msgstr "Não há informação disponível sobre o commit" + +#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554 +msgid "OK" +msgstr "OK" + +#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704 +#: gitk:11275 gitk:11555 +msgid "Cancel" +msgstr "Cancelar" + +#: gitk:2083 +msgid "&Update" +msgstr "At&ualizar" + +#: gitk:2084 +msgid "&Reload" +msgstr "&Recarregar" + +#: gitk:2085 +msgid "Reread re&ferences" +msgstr "Reler re&ferências" + +#: gitk:2086 +msgid "&List references" +msgstr "&Listar referências" + +#: gitk:2088 +msgid "Start git &gui" +msgstr "Iniciar git &gui" + +#: gitk:2090 +msgid "&Quit" +msgstr "&Sair" + +#: gitk:2082 +msgid "&File" +msgstr "&Ficheiro" + +#: gitk:2094 +msgid "&Preferences" +msgstr "&Preferências" + +#: gitk:2093 +msgid "&Edit" +msgstr "&Editar" + +#: gitk:2098 +msgid "&New view..." +msgstr "&Nova vista..." + +#: gitk:2099 +msgid "&Edit view..." +msgstr "&Editar vista..." + +#: gitk:2100 +msgid "&Delete view" +msgstr "Elimina&r vista" + +#: gitk:2102 +msgid "&All files" +msgstr "&Todos os ficheiros" + +#: gitk:2097 +msgid "&View" +msgstr "&Ver" + +#: gitk:2107 gitk:2117 +msgid "&About gitk" +msgstr "&Sobre gitk" + +#: gitk:2108 gitk:2122 +msgid "&Key bindings" +msgstr "&Atalhos" + +#: gitk:2106 gitk:2121 +msgid "&Help" +msgstr "&Ajuda" + +#: gitk:2199 gitk:8671 +msgid "SHA1 ID:" +msgstr "ID SHA1:" + +#: gitk:2243 +msgid "Row" +msgstr "Linha" + +#: gitk:2281 +msgid "Find" +msgstr "Procurar" + +#: gitk:2309 +msgid "commit" +msgstr "commit" + +#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846 +#: gitk:6931 +msgid "containing:" +msgstr "contendo:" + +#: gitk:2316 gitk:3545 gitk:3550 gitk:4782 +msgid "touching paths:" +msgstr "altera os caminhos:" + +#: gitk:2317 gitk:4796 +msgid "adding/removing string:" +msgstr "adiciona/remove a cadeia:" + +#: gitk:2318 gitk:4798 +msgid "changing lines matching:" +msgstr "altera linhas com:" + +#: gitk:2327 gitk:2329 gitk:4785 +msgid "Exact" +msgstr "Exato" + +#: gitk:2329 gitk:4873 gitk:6742 +msgid "IgnCase" +msgstr "IgnMaiúsculas" + +#: gitk:2329 gitk:4755 gitk:4871 gitk:6738 +msgid "Regexp" +msgstr "Expr. regular" + +#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935 +msgid "All fields" +msgstr "Todos os campos" + +#: gitk:2332 gitk:4890 gitk:4923 gitk:6805 +msgid "Headline" +msgstr "Cabeçalho" + +#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408 +msgid "Comments" +msgstr "Comentários" + +#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849 +#: gitk:8864 +msgid "Author" +msgstr "Autor" + +#: gitk:2333 gitk:4890 gitk:6805 gitk:7345 +msgid "Committer" +msgstr "Committer" + +#: gitk:2367 +msgid "Search" +msgstr "Pesquisar" + +#: gitk:2375 +msgid "Diff" +msgstr "Diff" + +#: gitk:2377 +msgid "Old version" +msgstr "Versão antiga" + +#: gitk:2379 +msgid "New version" +msgstr "Versão nova" + +#: gitk:2382 +msgid "Lines of context" +msgstr "Linhas de contexto" + +#: gitk:2392 +msgid "Ignore space change" +msgstr "Ignorar espaços" + +#: gitk:2396 gitk:2398 gitk:7978 gitk:8225 +msgid "Line diff" +msgstr "Diff de linha" + +#: gitk:2463 +msgid "Patch" +msgstr "Patch" + +#: gitk:2465 +msgid "Tree" +msgstr "Árvore" + +#: gitk:2635 gitk:2656 +msgid "Diff this -> selected" +msgstr "Diff este -> seleção" + +#: gitk:2636 gitk:2657 +msgid "Diff selected -> this" +msgstr "Diff seleção -> este" + +#: gitk:2637 gitk:2658 +msgid "Make patch" +msgstr "Gerar patch" + +#: gitk:2638 gitk:9273 +msgid "Create tag" +msgstr "Criar tag" + +#: gitk:2639 +msgid "Copy commit summary" +msgstr "Copiar sumário do commit" + +#: gitk:2640 gitk:9404 +msgid "Write commit to file" +msgstr "Escrever commit num ficheiro" + +#: gitk:2641 gitk:9461 +msgid "Create new branch" +msgstr "Criar novo ramo" + +#: gitk:2642 +msgid "Cherry-pick this commit" +msgstr "Efetuar cherry-pick deste commit" + +#: gitk:2643 +msgid "Reset HEAD branch to here" +msgstr "Repor ramo HEAD para aqui" + +#: gitk:2644 +msgid "Mark this commit" +msgstr "Marcar este commit" + +#: gitk:2645 +msgid "Return to mark" +msgstr "Voltar à marca" + +#: gitk:2646 +msgid "Find descendant of this and mark" +msgstr "Encontrar descendeste deste e da marca" + +#: gitk:2647 +msgid "Compare with marked commit" +msgstr "Comparar com o commit marcado" + +#: gitk:2648 gitk:2659 +msgid "Diff this -> marked commit" +msgstr "Diff este -> commit marcado" + +#: gitk:2649 gitk:2660 +msgid "Diff marked commit -> this" +msgstr "Diff commit marcado -> este" + +#: gitk:2650 +msgid "Revert this commit" +msgstr "Reverter este commit" + +#: gitk:2666 +msgid "Check out this branch" +msgstr "Extrair este ramo" + +#: gitk:2667 +msgid "Remove this branch" +msgstr "Remover este ramo" + +#: gitk:2668 +msgid "Copy branch name" +msgstr "Copiar nome do ramo" + +#: gitk:2675 +msgid "Highlight this too" +msgstr "Realçar este também" + +#: gitk:2676 +msgid "Highlight this only" +msgstr "Realçar apenas este" + +#: gitk:2677 +msgid "External diff" +msgstr "Diff externo" + +#: gitk:2678 +msgid "Blame parent commit" +msgstr "Culpar commit pai" + +#: gitk:2679 +msgid "Copy path" +msgstr "Copiar caminho" + +#: gitk:2686 +msgid "Show origin of this line" +msgstr "Mostrar origem deste ficheiro" + +#: gitk:2687 +msgid "Run git gui blame on this line" +msgstr "Executar git gui blame sobre esta linha" + +#: gitk:3031 +msgid "About gitk" +msgstr "Sobre gitk" + +#: gitk:3033 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2016 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - um visualizador de commits do git\n" +"\n" +"Copyright © 2005-2016 Paul Mackerras\n" +"\n" +"Use e redistribua sob os termos da GNU General Public License" + +#: gitk:3041 gitk:3108 gitk:9890 +msgid "Close" +msgstr "Fechar" + +#: gitk:3062 +msgid "Gitk key bindings" +msgstr "Atalhos do gitk" + +#: gitk:3065 +msgid "Gitk key bindings:" +msgstr "Atalhos do gitk:" + +#: gitk:3067 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tSair" + +#: gitk:3068 +#, tcl-format +msgid "<%s-W>\t\tClose window" +msgstr "<%s-W>\t\tFechar janela" + +#: gitk:3069 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\tMover para o primeiro commit" + +#: gitk:3070 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\tMover para o último commit" + +#: gitk:3071 +msgid "<Up>, p, k\tMove up one commit" +msgstr "<Cima>, p, k\tMover para o commit acima" + +#: gitk:3072 +msgid "<Down>, n, j\tMove down one commit" +msgstr "<Baixo>, n, j\tMover para o commit abaixo" + +#: gitk:3073 +msgid "<Left>, z, h\tGo back in history list" +msgstr "<Esquerda>, z, h\tRecuar no histórico" + +#: gitk:3074 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Direita>, x, l\tAvançar no histórico" + +#: gitk:3075 +#, tcl-format +msgid "<%s-n>\tGo to n-th parent of current commit in history list" +msgstr "<%s-n>\tIr para o n-ésimo pai do commit atual no histórico" + +#: gitk:3076 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tMover a lista de commits uma página para cima" + +#: gitk:3077 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tMover a lista de commits uma página para baixo" + +#: gitk:3078 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tDeslocar para o topo da lista" + +#: gitk:3079 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tDeslocar para o fim da lista" + +#: gitk:3080 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Cima>\tDeslocar a lista de commits uma linha para cima" + +#: gitk:3081 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Baixo>\tDeslocar a lista de commits uma linha para baixo" + +#: gitk:3082 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tDeslocar a lista de commits uma página para cima" + +#: gitk:3083 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tDeslocar a lista de commits uma página para baixo" + +#: gitk:3084 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Shift-Cima>\tProcurar para trás (para cima, commits posteriores)" + +#: gitk:3085 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Shift-Baixo>\tProcurar para a frente (para baixo, commits anteriores)" + +#: gitk:3086 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tDeslocar vista diff uma página para cima" + +#: gitk:3087 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Retrocesso>\tDeslocar vista diff uma página para cima" + +#: gitk:3088 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Espaço>\tDeslocar vista diff uma página para baixo" + +#: gitk:3089 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tDeslocar vista diff 18 linhas para cima" + +#: gitk:3090 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tDeslocar vista diff 18 linhas para baixo" + +#: gitk:3091 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tProcurar" + +#: gitk:3092 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tMover para a ocorrência seguinte" + +#: gitk:3093 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\tMover para a ocorrência seguinte" + +#: gitk:3094 +msgid "g\t\tGo to commit" +msgstr "g\t\tIr para o commit" + +#: gitk:3095 +msgid "/\t\tFocus the search box" +msgstr "/\t\tFocar a caixa de pesquisa" + +#: gitk:3096 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tMover para a ocorrência anterior" + +#: gitk:3097 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tDeslocar vista diff para o ficheiro seguinte" + +#: gitk:3098 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tProcurar pela ocorrência seguinte na vista diff" + +#: gitk:3099 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tProcurar pela ocorrência anterior na vista diff" + +#: gitk:3100 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tAumentar o tamanho da letra" + +#: gitk:3101 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-mais>\tAumentar o tamanho da letra" + +#: gitk:3102 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tDiminuir o tamanho da letra" + +#: gitk:3103 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-menos>\tDiminuir o tamanho da letra" + +#: gitk:3104 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tAtualizar" + +#: gitk:3569 gitk:3578 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Erro ao criar ficheiro temporário %s:" + +#: gitk:3591 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Erro ao obter \"%s\" de %s:" + +#: gitk:3654 +msgid "command failed:" +msgstr "o comando falhou:" + +#: gitk:3803 +msgid "No such commit" +msgstr "Commit inexistente" + +#: gitk:3817 +msgid "git gui blame: command failed:" +msgstr "git gui blame: o comando falhou:" + +#: gitk:3848 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Não foi possível ler a cabeça de integração: %s" + +#: gitk:3856 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Erro ao ler o índice: %s" + +#: gitk:3881 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Não foi possível iniciar git blame: %s" + +#: gitk:3884 gitk:6773 +msgid "Searching" +msgstr "A procurar" + +#: gitk:3916 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Erro ao executar git blame: %s" + +#: gitk:3944 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Essa linha provém do commit %s, que não está nesta vista" + +#: gitk:3958 +msgid "External diff viewer failed:" +msgstr "Visualizador diff externo falhou:" + +#: gitk:4062 +msgid "All files" +msgstr "Todos os ficheiros" + +#: gitk:4086 +msgid "View" +msgstr "Vista" + +#: gitk:4089 +msgid "Gitk view definition" +msgstr "Definição de vistas do gitk" + +#: gitk:4093 +msgid "Remember this view" +msgstr "Recordar esta vista" + +#: gitk:4094 +msgid "References (space separated list):" +msgstr "Referências (lista separada por espaço):" + +#: gitk:4095 +msgid "Branches & tags:" +msgstr "Ramos e tags:" + +#: gitk:4096 +msgid "All refs" +msgstr "Todas as referências" + +#: gitk:4097 +msgid "All (local) branches" +msgstr "Todos os ramos (locais)" + +#: gitk:4098 +msgid "All tags" +msgstr "Todas as tags" + +#: gitk:4099 +msgid "All remote-tracking branches" +msgstr "Todos os ramos remotos de monitorização" + +#: gitk:4100 +msgid "Commit Info (regular expressions):" +msgstr "Informação Sobre o Commit (expressões regulares):" + +#: gitk:4101 +msgid "Author:" +msgstr "Autor:" + +#: gitk:4102 +msgid "Committer:" +msgstr "Committer:" + +#: gitk:4103 +msgid "Commit Message:" +msgstr "Mensagem de Commit:" + +#: gitk:4104 +msgid "Matches all Commit Info criteria" +msgstr "Corresponde a todos os critérios da Informação Sobre o Commit" + +#: gitk:4105 +msgid "Matches no Commit Info criteria" +msgstr "Não corresponde a nenhum critério da Informação Sobre o Commit" + +#: gitk:4106 +msgid "Changes to Files:" +msgstr "Alterações nos Ficheiros:" + +#: gitk:4107 +msgid "Fixed String" +msgstr "Cadeia Fixa" + +#: gitk:4108 +msgid "Regular Expression" +msgstr "Expressão Regular" + +#: gitk:4109 +msgid "Search string:" +msgstr "Procurar pela cadeia:" + +#: gitk:4110 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Datas de Commit (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:4111 +msgid "Since:" +msgstr "Desde:" + +#: gitk:4112 +msgid "Until:" +msgstr "Até:" + +#: gitk:4113 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):" + +#: gitk:4114 +msgid "Number to show:" +msgstr "Número a mostrar:" + +#: gitk:4115 +msgid "Number to skip:" +msgstr "Número a ignorar:" + +#: gitk:4116 +msgid "Miscellaneous options:" +msgstr "Opções diversas:" + +#: gitk:4117 +msgid "Strictly sort by date" +msgstr "Ordenar estritamente pela data" + +#: gitk:4118 +msgid "Mark branch sides" +msgstr "Marcar lado dos ramos" + +#: gitk:4119 +msgid "Limit to first parent" +msgstr "Restringir ao primeiro pai" + +#: gitk:4120 +msgid "Simple history" +msgstr "Histórico simples" + +#: gitk:4121 +msgid "Additional arguments to git log:" +msgstr "Argumentos adicionais ao git log:" + +#: gitk:4122 +msgid "Enter files and directories to include, one per line:" +msgstr "Introduza ficheiros e diretórios para incluir, um por linha:" + +#: gitk:4123 +msgid "Command to generate more commits to include:" +msgstr "Comando para gerar mais commits para incluir:" + +#: gitk:4247 +msgid "Gitk: edit view" +msgstr "Gitk: editar vista" + +#: gitk:4255 +msgid "-- criteria for selecting revisions" +msgstr "-- critério para selecionar revisões" + +#: gitk:4260 +msgid "View Name" +msgstr "Nome da Vista" + +#: gitk:4335 +msgid "Apply (F5)" +msgstr "Aplicar (F5)" + +#: gitk:4373 +msgid "Error in commit selection arguments:" +msgstr "Erro nos argumentos de seleção de commits:" + +#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411 +msgid "None" +msgstr "Nenhum" + +#: gitk:5040 gitk:5045 +msgid "Descendant" +msgstr "Descendente" + +#: gitk:5041 +msgid "Not descendant" +msgstr "Não descendente" + +#: gitk:5048 gitk:5053 +msgid "Ancestor" +msgstr "Antecessor" + +#: gitk:5049 +msgid "Not ancestor" +msgstr "Não antecessor" + +#: gitk:5343 +msgid "Local changes checked in to index but not committed" +msgstr "Alterações locais preparadas no índice mas não submetidas" + +#: gitk:5379 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Alterações locais não submetidas, não preparadas no índice" + +#: gitk:7153 +msgid "and many more" +msgstr "e muitos mais" + +#: gitk:7156 +msgid "many" +msgstr "muitos" + +#: gitk:7347 +msgid "Tags:" +msgstr "Tags:" + +#: gitk:7364 gitk:7370 gitk:8844 +msgid "Parent" +msgstr "Pai" + +#: gitk:7375 +msgid "Child" +msgstr "Filho" + +#: gitk:7384 +msgid "Branch" +msgstr "Ramo" + +#: gitk:7387 +msgid "Follows" +msgstr "Sucede" + +#: gitk:7390 +msgid "Precedes" +msgstr "Precede" + +#: gitk:7985 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Erro ao obter diferenças: %s" + +#: gitk:8669 +msgid "Goto:" +msgstr "Ir para:" + +#: gitk:8690 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "O id SHA1 abreviado %s é ambíguo" + +#: gitk:8697 +#, tcl-format +msgid "Revision %s is not known" +msgstr "A revisão %s não é conhecida" + +#: gitk:8707 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "O id SHA1 %s não é conhecido" + +#: gitk:8709 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "A revisão %s não se encontra na vista atual" + +#: gitk:8851 gitk:8866 +msgid "Date" +msgstr "Data" + +#: gitk:8854 +msgid "Children" +msgstr "Filhos" + +#: gitk:8917 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Repor o ramo %s para aqui" + +#: gitk:8919 +msgid "Detached head: can't reset" +msgstr "Cabeça destacada: não é possível repor" + +#: gitk:9024 gitk:9030 +msgid "Skipping merge commit " +msgstr "A ignorar commit de integração " + +#: gitk:9039 gitk:9044 +msgid "Error getting patch ID for " +msgstr "Erro ao obter ID de patch de " + +#: gitk:9040 gitk:9045 +msgid " - stopping\n" +msgstr " - a interromper\n" + +#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084 +msgid "Commit " +msgstr "Commit " + +#: gitk:9054 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" é o mesmo patch que\n" +" " + +#: gitk:9062 +msgid "" +" differs from\n" +" " +msgstr "" +" difere de\n" +" " + +#: gitk:9064 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Diferença dos commits:\n" +"\n" + +#: gitk:9076 gitk:9085 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " tem %s filhos - a interromper\n" + +#: gitk:9104 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Erro ao escrever commit no ficheiro: %s" + +#: gitk:9110 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Erro ao calcular as diferenças dos commits: %s" + +#: gitk:9156 +msgid "Top" +msgstr "Topo" + +#: gitk:9157 +msgid "From" +msgstr "De" + +#: gitk:9162 +msgid "To" +msgstr "Para" + +#: gitk:9186 +msgid "Generate patch" +msgstr "Gerar patch" + +#: gitk:9188 +msgid "From:" +msgstr "De:" + +#: gitk:9197 +msgid "To:" +msgstr "Para:" + +#: gitk:9206 +msgid "Reverse" +msgstr "Reverter" + +#: gitk:9208 gitk:9418 +msgid "Output file:" +msgstr "Ficheiro de saída:" + +#: gitk:9214 +msgid "Generate" +msgstr "Gerar" + +#: gitk:9252 +msgid "Error creating patch:" +msgstr "Erro ao criar patch:" + +#: gitk:9275 gitk:9406 gitk:9463 +msgid "ID:" +msgstr "ID:" + +#: gitk:9284 +msgid "Tag name:" +msgstr "Nome da tag:" + +#: gitk:9287 +msgid "Tag message is optional" +msgstr "A mensagem da tag é opcional" + +#: gitk:9289 +msgid "Tag message:" +msgstr "Mensagem da tag:" + +#: gitk:9293 gitk:9472 +msgid "Create" +msgstr "Criar" + +#: gitk:9311 +msgid "No tag name specified" +msgstr "Nenhum nome de tag especificado" + +#: gitk:9315 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "A tag \"%s\" já existe" + +#: gitk:9325 +msgid "Error creating tag:" +msgstr "Erro ao criar tag:" + +#: gitk:9415 +msgid "Command:" +msgstr "Comando:" + +#: gitk:9423 +msgid "Write" +msgstr "Escrever" + +#: gitk:9441 +msgid "Error writing commit:" +msgstr "Erro ao escrever commit:" + +#: gitk:9468 +msgid "Name:" +msgstr "Nome:" + +#: gitk:9491 +msgid "Please specify a name for the new branch" +msgstr "Especifique um nome para o novo ramo" + +#: gitk:9496 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "O ramo '%s' já existe. Substituí-lo?" + +#: gitk:9563 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "O commit %s já está incluído no ramo %s -- reaplicá-lo mesmo assim?" + +#: gitk:9568 +msgid "Cherry-picking" +msgstr "A efetuar cherry-pick" + +#: gitk:9577 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"Falha ao efetuar cherry-pick devido a alterações locais no ficheiro '%s'.\n" +"Submeta, empilhe ou reponha as alterações e tente de novo." + +#: gitk:9583 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Falha ao efetuar cherry-pick devido a conflito de integração.\n" +"Deseja executar git citool para resolvê-lo?" + +#: gitk:9599 gitk:9657 +msgid "No changes committed" +msgstr "Não foi submetida nenhum alteração" + +#: gitk:9626 +#, tcl-format +msgid "Commit %s is not included in branch %s -- really revert it?" +msgstr "O commit %s não está incluído no ramo %s -- revertê-lo mesmo assim?" + +#: gitk:9631 +msgid "Reverting" +msgstr "A reverter" + +#: gitk:9639 +#, tcl-format +msgid "" +"Revert failed because of local changes to the following files:%s Please " +"commit, reset or stash your changes and try again." +msgstr "" +"Falha ao reverter devido a alterações locais nos seguintes ficheiros:%s " +"Submeta, empilhe ou reponha as alterações e tente de novo." + +#: gitk:9643 +msgid "" +"Revert failed because of merge conflict.\n" +" Do you wish to run git citool to resolve it?" +msgstr "" +"Falha ao reverter devido a conflito de integração.\n" +"Deseja executar git citool para resolvê-lo?" + +#: gitk:9686 +msgid "Confirm reset" +msgstr "Confirmar reposição" + +#: gitk:9688 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Repor o ramo %s para %s?" + +#: gitk:9690 +msgid "Reset type:" +msgstr "Tipo de reposição:" + +#: gitk:9693 +msgid "Soft: Leave working tree and index untouched" +msgstr "Suave: Deixar a árvore de trabalho e o índice intactos" + +#: gitk:9696 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "Misto: Deixar a árvore de trabalho intacta, repor índice" + +#: gitk:9699 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Forte: Repor árvore de trabalho e índice\n" +"(descartar TODAS as alterações locais)" + +#: gitk:9716 +msgid "Resetting" +msgstr "A repor" + +#: gitk:9776 +msgid "Checking out" +msgstr "A extrair" + +#: gitk:9829 +msgid "Cannot delete the currently checked-out branch" +msgstr "Não é possível eliminar o ramo atual extraído" + +#: gitk:9835 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"Os commits no ramo %s não estão presentes em mais nenhum ramo.\n" +"Eliminar o ramo %s mesmo assim?" + +#: gitk:9866 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Tags e cabeças: %s" + +#: gitk:9883 +msgid "Filter" +msgstr "Filtrar" + +#: gitk:10179 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Erro ao ler informação de topologia do commit; a informação do ramo e da tag " +"precedente/seguinte ficará incompleta." + +#: gitk:11156 +msgid "Tag" +msgstr "Tag" + +#: gitk:11160 +msgid "Id" +msgstr "Id" + +#: gitk:11243 +msgid "Gitk font chooser" +msgstr "Escolha de tipo de letra do gitk" + +#: gitk:11260 +msgid "B" +msgstr "B" + +#: gitk:11263 +msgid "I" +msgstr "I" + +#: gitk:11381 +msgid "Commit list display options" +msgstr "Opções de visualização da lista de commits" + +#: gitk:11384 +msgid "Maximum graph width (lines)" +msgstr "Largura máxima do gráfico (linhas)" + +#: gitk:11388 +#, no-tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Largura máxima do gráfico (% do painel)" + +#: gitk:11391 +msgid "Show local changes" +msgstr "Mostrar alterações locais" + +#: gitk:11394 +msgid "Auto-select SHA1 (length)" +msgstr "Selecionar automaticamente SHA1 (largura)" + +#: gitk:11398 +msgid "Hide remote refs" +msgstr "Ocultar referências remotas" + +#: gitk:11402 +msgid "Diff display options" +msgstr "Opções de visualização de diferenças" + +#: gitk:11404 +msgid "Tab spacing" +msgstr "Espaçamento da tabulação" + +#: gitk:11407 +msgid "Display nearby tags/heads" +msgstr "Mostrar tags/cabeças próximas" + +#: gitk:11410 +msgid "Maximum # tags/heads to show" +msgstr "Nº máximo de tags/cabeças a mostrar" + +#: gitk:11413 +msgid "Limit diffs to listed paths" +msgstr "Limitar diferenças aos caminhos listados" + +#: gitk:11416 +msgid "Support per-file encodings" +msgstr "Suportar codificação por cada ficheiro" + +#: gitk:11422 gitk:11569 +msgid "External diff tool" +msgstr "Ferramenta diff externa" + +#: gitk:11423 +msgid "Choose..." +msgstr "Escolher..." + +#: gitk:11428 +msgid "General options" +msgstr "Opções gerais" + +#: gitk:11431 +msgid "Use themed widgets" +msgstr "Usar widgets com estilo" + +#: gitk:11433 +msgid "(change requires restart)" +msgstr "(alteração exige reiniciar)" + +#: gitk:11435 +msgid "(currently unavailable)" +msgstr "(não disponível de momento)" + +#: gitk:11446 +msgid "Colors: press to choose" +msgstr "Cores: pressione para escolher" + +#: gitk:11449 +msgid "Interface" +msgstr "Interface" + +#: gitk:11450 +msgid "interface" +msgstr "interface" + +#: gitk:11453 +msgid "Background" +msgstr "Fundo" + +#: gitk:11454 gitk:11484 +msgid "background" +msgstr "fundo" + +#: gitk:11457 +msgid "Foreground" +msgstr "Primeiro plano" + +#: gitk:11458 +msgid "foreground" +msgstr "primeiro plano" + +#: gitk:11461 +msgid "Diff: old lines" +msgstr "Diff: linhas antigas" + +#: gitk:11462 +msgid "diff old lines" +msgstr "diff linhas antigas" + +#: gitk:11466 +msgid "Diff: new lines" +msgstr "Diff: linhas novas" + +#: gitk:11467 +msgid "diff new lines" +msgstr "diff linhas novas" + +#: gitk:11471 +msgid "Diff: hunk header" +msgstr "Diff: cabeçalho do excerto" + +#: gitk:11473 +msgid "diff hunk header" +msgstr "diff cabeçalho do excerto" + +#: gitk:11477 +msgid "Marked line bg" +msgstr "Fundo da linha marcada" + +#: gitk:11479 +msgid "marked line background" +msgstr "fundo da linha marcada" + +#: gitk:11483 +msgid "Select bg" +msgstr "Selecionar fundo" + +#: gitk:11492 +msgid "Fonts: press to choose" +msgstr "Tipo de letra: pressione para escolher" + +#: gitk:11494 +msgid "Main font" +msgstr "Tipo de letra principal" + +#: gitk:11495 +msgid "Diff display font" +msgstr "Tipo de letra ao mostrar diferenças" + +#: gitk:11496 +msgid "User interface font" +msgstr "Tipo de letra da interface de utilizador" + +#: gitk:11518 +msgid "Gitk preferences" +msgstr "Preferências do gitk" + +#: gitk:11527 +msgid "General" +msgstr "Geral" + +#: gitk:11528 +msgid "Colors" +msgstr "Cores" + +#: gitk:11529 +msgid "Fonts" +msgstr "Tipos de letra" + +#: gitk:11579 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: escolher cor de %s" + +#: gitk:12092 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"Não é possível executar o gitk com esta versão do Tcl/Tk.\n" +"O gitk requer pelo menos Tcl/Tk 8.4." + +#: gitk:12302 +msgid "Cannot find a git repository here." +msgstr "Não foi encontrado nenhum repositório git aqui." + +#: gitk:12349 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Argumento '%s' ambíguo: pode ser uma revisão ou um ficheiro" + +#: gitk:12361 +msgid "Bad arguments to gitk:" +msgstr "Argumentos do gitk incorretos:" diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po index 17ed026aa7..9b08c263ea 100644 --- a/gitk-git/po/ru.po +++ b/gitk-git/po/ru.po @@ -3,15 +3,15 @@ # Translators: # 0xAX <kuleshovmail@gmail.com>, 2014 # Alex Riesen <raa.lkml@gmail.com>, 2015 -# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015 +# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015-2016 # Dmitry Potapov <dpotapov@gmail.com>, 2009 # Skip <bsvskip@rambler.ru>, 2011 msgid "" msgstr "" "Project-Id-Version: Git Russian Localization Project\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-05-17 14:32+1000\n" -"PO-Revision-Date: 2015-10-12 10:14+0000\n" +"POT-Creation-Date: 2016-12-15 00:18+0200\n" +"PO-Revision-Date: 2016-12-14 22:23+0000\n" "Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n" "Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n" "MIME-Version: 1.0\n" @@ -24,11 +24,11 @@ msgstr "" msgid "Couldn't get list of unmerged files:" msgstr "Невозможно получить список файлов незавершённой операции слияния:" -#: gitk:212 gitk:2381 +#: gitk:212 gitk:2403 msgid "Color words" msgstr "Цветные слова" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 +#: gitk:217 gitk:2403 gitk:8249 gitk:8282 msgid "Markup words" msgstr "Помеченые слова" @@ -58,1272 +58,1314 @@ msgstr "Ошибка запуска git log:" msgid "Reading" msgstr "Чтение" -#: gitk:496 gitk:4525 +#: gitk:496 gitk:4549 msgid "Reading commits..." msgstr "Чтение коммитов..." -#: gitk:499 gitk:1637 gitk:4528 +#: gitk:499 gitk:1641 gitk:4552 msgid "No commits selected" msgstr "Ничего не выбрано" -#: gitk:1445 gitk:4045 gitk:12432 +#: gitk:1449 gitk:4069 gitk:12583 msgid "Command line" msgstr "Командная строка" -#: gitk:1511 +#: gitk:1515 msgid "Can't parse git log output:" msgstr "Ошибка обработки вывода команды git log:" -#: gitk:1740 +#: gitk:1744 msgid "No commit information available" msgstr "Нет информации о коммите" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 +#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668 msgid "OK" msgstr "Ok" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 +#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791 +#: gitk:11389 gitk:11669 msgid "Cancel" msgstr "Отмена" -#: gitk:2069 +#: gitk:2087 msgid "&Update" msgstr "Обновить" -#: gitk:2070 +#: gitk:2088 msgid "&Reload" msgstr "Перечитать" -#: gitk:2071 +#: gitk:2089 msgid "Reread re&ferences" msgstr "Обновить список ссылок" -#: gitk:2072 +#: gitk:2090 msgid "&List references" msgstr "Список ссылок" -#: gitk:2074 +#: gitk:2092 msgid "Start git &gui" msgstr "Запустить git gui" -#: gitk:2076 +#: gitk:2094 msgid "&Quit" msgstr "Завершить" -#: gitk:2068 +#: gitk:2086 msgid "&File" msgstr "Файл" -#: gitk:2080 +#: gitk:2098 msgid "&Preferences" msgstr "Настройки" -#: gitk:2079 +#: gitk:2097 msgid "&Edit" msgstr "Редактировать" -#: gitk:2084 +#: gitk:2102 msgid "&New view..." msgstr "Новое представление..." -#: gitk:2085 +#: gitk:2103 msgid "&Edit view..." msgstr "Редактировать представление..." -#: gitk:2086 +#: gitk:2104 msgid "&Delete view" msgstr "Удалить представление" -#: gitk:2088 gitk:4043 +#: gitk:2106 msgid "&All files" msgstr "Все файлы" -#: gitk:2083 gitk:4067 +#: gitk:2101 msgid "&View" msgstr "Представление" -#: gitk:2093 gitk:2103 gitk:3012 +#: gitk:2111 gitk:2121 msgid "&About gitk" msgstr "О gitk" -#: gitk:2094 gitk:2108 +#: gitk:2112 gitk:2126 msgid "&Key bindings" msgstr "Назначения клавиатуры" -#: gitk:2092 gitk:2107 +#: gitk:2110 gitk:2125 msgid "&Help" msgstr "Подсказка" -#: gitk:2185 gitk:8652 +#: gitk:2203 gitk:8681 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 +#: gitk:2247 msgid "Row" msgstr "Строка" -#: gitk:2267 +#: gitk:2285 msgid "Find" msgstr "Поиск" -#: gitk:2295 +#: gitk:2313 msgid "commit" msgstr "коммит" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 +#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851 +#: gitk:6936 msgid "containing:" msgstr "содержащее:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 +#: gitk:2320 gitk:3550 gitk:3555 gitk:4787 msgid "touching paths:" msgstr "касательно файлов:" -#: gitk:2303 gitk:4777 +#: gitk:2321 gitk:4801 msgid "adding/removing string:" msgstr "добавив/удалив строку:" -#: gitk:2304 gitk:4779 +#: gitk:2322 gitk:4803 msgid "changing lines matching:" msgstr "изменяя совпадающие строки:" -#: gitk:2313 gitk:2315 gitk:4766 +#: gitk:2331 gitk:2333 gitk:4790 msgid "Exact" msgstr "Точно" -#: gitk:2315 gitk:4854 gitk:6723 +#: gitk:2333 gitk:4878 gitk:6747 msgid "IgnCase" msgstr "Игнорировать большие/маленькие" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 +#: gitk:2333 gitk:4760 gitk:4876 gitk:6743 msgid "Regexp" msgstr "Регулярные выражения" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 +#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940 msgid "All fields" msgstr "Во всех полях" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 +#: gitk:2336 gitk:4895 gitk:4928 gitk:6810 msgid "Headline" msgstr "Заголовок" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 +#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413 msgid "Comments" msgstr "Комментарии" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 +#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859 +#: gitk:8874 msgid "Author" msgstr "Автор" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 +#: gitk:2337 gitk:4895 gitk:6810 gitk:7350 msgid "Committer" msgstr "Коммитер" -#: gitk:2350 +#: gitk:2371 msgid "Search" msgstr "Найти" -#: gitk:2358 +#: gitk:2379 msgid "Diff" msgstr "Сравнить" -#: gitk:2360 +#: gitk:2381 msgid "Old version" msgstr "Старая версия" -#: gitk:2362 +#: gitk:2383 msgid "New version" msgstr "Новая версия" -#: gitk:2364 +#: gitk:2386 msgid "Lines of context" msgstr "Строк контекста" -#: gitk:2374 +#: gitk:2396 msgid "Ignore space change" msgstr "Игнорировать пробелы" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 +#: gitk:2400 gitk:2402 gitk:7983 gitk:8235 msgid "Line diff" msgstr "Изменения строк" -#: gitk:2445 +#: gitk:2467 msgid "Patch" msgstr "Патч" -#: gitk:2447 +#: gitk:2469 msgid "Tree" msgstr "Файлы" -#: gitk:2617 gitk:2637 +#: gitk:2639 gitk:2660 msgid "Diff this -> selected" msgstr "Сравнить этот коммит с выделенным" -#: gitk:2618 gitk:2638 +#: gitk:2640 gitk:2661 msgid "Diff selected -> this" msgstr "Сравнить выделенный с этим коммитом" -#: gitk:2619 gitk:2639 +#: gitk:2641 gitk:2662 msgid "Make patch" msgstr "Создать патч" -#: gitk:2620 gitk:9254 +#: gitk:2642 gitk:9283 msgid "Create tag" msgstr "Создать метку" -#: gitk:2621 gitk:9371 +#: gitk:2643 +msgid "Copy commit summary" +msgstr "Копировать информацию о коммите" + +#: gitk:2644 gitk:9414 msgid "Write commit to file" msgstr "Сохранить коммит в файл" -#: gitk:2622 gitk:9428 +#: gitk:2645 msgid "Create new branch" msgstr "Создать ветку" -#: gitk:2623 +#: gitk:2646 msgid "Cherry-pick this commit" -msgstr "Отбор лучшего для этого коммита" +msgstr "Копировать этот коммит в текущую ветку" -#: gitk:2624 +#: gitk:2647 msgid "Reset HEAD branch to here" msgstr "Установить HEAD на этот коммит" -#: gitk:2625 +#: gitk:2648 msgid "Mark this commit" msgstr "Пометить этот коммит" -#: gitk:2626 +#: gitk:2649 msgid "Return to mark" msgstr "Вернуться на пометку" -#: gitk:2627 +#: gitk:2650 msgid "Find descendant of this and mark" msgstr "Найти и пометить потомка этого коммита" -#: gitk:2628 +#: gitk:2651 msgid "Compare with marked commit" msgstr "Сравнить с помеченным коммитом" -#: gitk:2629 gitk:2640 +#: gitk:2652 gitk:2663 msgid "Diff this -> marked commit" msgstr "Сравнить выделенное с помеченным коммитом" -#: gitk:2630 gitk:2641 +#: gitk:2653 gitk:2664 msgid "Diff marked commit -> this" msgstr "Сравнить помеченный с этим коммитом" -#: gitk:2631 +#: gitk:2654 msgid "Revert this commit" -msgstr "Возврат этого коммита" +msgstr "Обратить изменения этого коммита" -#: gitk:2647 +#: gitk:2670 msgid "Check out this branch" msgstr "Перейти на эту ветку" -#: gitk:2648 +#: gitk:2671 +msgid "Rename this branch" +msgstr "Переименовать эту ветку" + +#: gitk:2672 msgid "Remove this branch" msgstr "Удалить эту ветку" -#: gitk:2649 +#: gitk:2673 msgid "Copy branch name" msgstr "Копировать имя ветки" -#: gitk:2656 +#: gitk:2680 msgid "Highlight this too" msgstr "Подсветить этот тоже" -#: gitk:2657 +#: gitk:2681 msgid "Highlight this only" msgstr "Подсветить только этот" -#: gitk:2658 +#: gitk:2682 msgid "External diff" msgstr "Программа сравнения" -#: gitk:2659 +#: gitk:2683 msgid "Blame parent commit" msgstr "Авторы изменений родительского коммита" -#: gitk:2660 +#: gitk:2684 msgid "Copy path" msgstr "Копировать путь" -#: gitk:2667 +#: gitk:2691 msgid "Show origin of this line" msgstr "Показать источник этой строки" -#: gitk:2668 +#: gitk:2692 msgid "Run git gui blame on this line" msgstr "Запустить git gui blame для этой строки" -#: gitk:3014 +#: gitk:3036 +msgid "About gitk" +msgstr "О gitk" + +#: gitk:3038 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" -msgstr "\nGitk - программа просмотра истории репозиториев git\n\n© 2005-2014 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" +msgstr "\nGitk — программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" -#: gitk:3022 gitk:3089 gitk:9857 +#: gitk:3046 gitk:3113 gitk:10004 msgid "Close" msgstr "Закрыть" -#: gitk:3043 +#: gitk:3067 msgid "Gitk key bindings" msgstr "Назначения клавиатуры в Gitk" -#: gitk:3046 +#: gitk:3070 msgid "Gitk key bindings:" msgstr "Назначения клавиатуры в Gitk:" -#: gitk:3048 +#: gitk:3072 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tЗавершить" -#: gitk:3049 +#: gitk:3073 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tЗакрыть окно" -#: gitk:3050 +#: gitk:3074 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tПерейти к первому коммиту" -#: gitk:3051 +#: gitk:3075 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tПерейти к последнему коммиту" -#: gitk:3052 +#: gitk:3076 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\tПерейти на один коммит вверх" -#: gitk:3053 +#: gitk:3077 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\tПерейти на один коммит вниз" -#: gitk:3054 +#: gitk:3078 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\tПоказать ранее посещённое состояние" -#: gitk:3055 +#: gitk:3079 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tПоказать следующий посещённый коммит" -#: gitk:3056 +#: gitk:3080 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tПерейти на n родителя от текущего коммита" -#: gitk:3057 +#: gitk:3081 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tПерейти на страницу выше в списке коммитов" -#: gitk:3058 +#: gitk:3082 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tПерейти на страницу ниже в списке коммитов" -#: gitk:3059 +#: gitk:3083 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tПерейти на начало списка коммитов" -#: gitk:3060 +#: gitk:3084 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tПерейти на конец списка коммитов" -#: gitk:3061 +#: gitk:3085 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tПровернуть список коммитов вверх" -#: gitk:3062 +#: gitk:3086 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tПровернуть список коммитов вниз" -#: gitk:3063 +#: gitk:3087 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tПровернуть список коммитов на страницу вверх" -#: gitk:3064 +#: gitk:3088 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tПровернуть список коммитов на страницу вниз" -#: gitk:3065 +#: gitk:3089 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых коммитов)" -#: gitk:3066 +#: gitk:3090 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\tПоиск (вниз, среди старых коммитов)" -#: gitk:3067 +#: gitk:3091 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tПрокрутить список изменений на страницу выше" -#: gitk:3068 +#: gitk:3092 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tПрокрутить список изменений на страницу выше" -#: gitk:3069 +#: gitk:3093 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\t\tПрокрутить список изменений на страницу ниже" -#: gitk:3070 +#: gitk:3094 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tПрокрутить список изменений на 18 строк вверх" -#: gitk:3071 +#: gitk:3095 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tПрокрутить список изменений на 18 строк вниз" -#: gitk:3072 +#: gitk:3096 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tПоиск" -#: gitk:3073 +#: gitk:3097 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tПерейти к следующему найденному коммиту" -#: gitk:3074 +#: gitk:3098 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tПерейти к следующему найденному коммиту" -#: gitk:3075 +#: gitk:3099 msgid "g\t\tGo to commit" msgstr "g\t\tПерейти на коммит" -#: gitk:3076 +#: gitk:3100 msgid "/\t\tFocus the search box" msgstr "/\t\tПерейти к полю поиска" -#: gitk:3077 +#: gitk:3101 msgid "?\t\tMove to previous find hit" msgstr "?\t\tПерейти к предыдущему найденному коммиту" -#: gitk:3078 +#: gitk:3102 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tПрокрутить список изменений к следующему файлу" -#: gitk:3079 +#: gitk:3103 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tПродолжить поиск в списке изменений" -#: gitk:3080 +#: gitk:3104 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений" -#: gitk:3081 +#: gitk:3105 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tУвеличить размер шрифта" -#: gitk:3082 +#: gitk:3106 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tУвеличить размер шрифта" -#: gitk:3083 +#: gitk:3107 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tУменьшить размер шрифта" -#: gitk:3084 +#: gitk:3108 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tУменьшить размер шрифта" -#: gitk:3085 +#: gitk:3109 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tОбновить" -#: gitk:3550 gitk:3559 +#: gitk:3574 gitk:3583 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Ошибка создания временного каталога %s:" -#: gitk:3572 +#: gitk:3596 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Ошибка получения «%s» из %s:" -#: gitk:3635 +#: gitk:3659 msgid "command failed:" msgstr "ошибка выполнения команды:" -#: gitk:3784 +#: gitk:3808 msgid "No such commit" msgstr "Коммит не найден" -#: gitk:3798 +#: gitk:3822 msgid "git gui blame: command failed:" msgstr "git gui blame: ошибка выполнения команды:" -#: gitk:3829 +#: gitk:3853 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Ошибка чтения MERGE_HEAD: %s" -#: gitk:3837 +#: gitk:3861 #, tcl-format msgid "Error reading index: %s" msgstr "Ошибка чтения индекса: %s" -#: gitk:3862 +#: gitk:3886 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Ошибка запуска git blame: %s" -#: gitk:3865 gitk:6754 +#: gitk:3889 gitk:6778 msgid "Searching" msgstr "Поиск" -#: gitk:3897 +#: gitk:3921 #, tcl-format msgid "Error running git blame: %s" msgstr "Ошибка выполнения git blame: %s" -#: gitk:3925 +#: gitk:3949 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Эта строка принадлежит коммиту %s, который не показан в этом представлении" -#: gitk:3939 +#: gitk:3963 msgid "External diff viewer failed:" msgstr "Ошибка выполнения программы сравнения:" -#: gitk:4070 +#: gitk:4067 +msgid "All files" +msgstr "Все файлы" + +#: gitk:4091 +msgid "View" +msgstr "Представление" + +#: gitk:4094 msgid "Gitk view definition" msgstr "Gitk определение представлений" -#: gitk:4074 +#: gitk:4098 msgid "Remember this view" msgstr "Запомнить представление" -#: gitk:4075 +#: gitk:4099 msgid "References (space separated list):" msgstr "Ссылки (разделённые пробелом):" -#: gitk:4076 +#: gitk:4100 msgid "Branches & tags:" msgstr "Ветки и метки" -#: gitk:4077 +#: gitk:4101 msgid "All refs" msgstr "Все ссылки" -#: gitk:4078 +#: gitk:4102 msgid "All (local) branches" msgstr "Все (локальные) ветки" -#: gitk:4079 +#: gitk:4103 msgid "All tags" msgstr "Все метки" -#: gitk:4080 +#: gitk:4104 msgid "All remote-tracking branches" msgstr "Все внешние отслеживаемые ветки" -#: gitk:4081 +#: gitk:4105 msgid "Commit Info (regular expressions):" msgstr "Информация о коммите (регулярные выражения):" -#: gitk:4082 +#: gitk:4106 msgid "Author:" msgstr "Автор:" -#: gitk:4083 +#: gitk:4107 msgid "Committer:" msgstr "Коммитер:" -#: gitk:4084 +#: gitk:4108 msgid "Commit Message:" msgstr "Сообщение коммита:" -#: gitk:4085 +#: gitk:4109 msgid "Matches all Commit Info criteria" msgstr "Совпадает со всеми условиями информации о коммите" -#: gitk:4086 +#: gitk:4110 msgid "Matches no Commit Info criteria" msgstr "Не совпадает с условиями информации о коммите" -#: gitk:4087 +#: gitk:4111 msgid "Changes to Files:" msgstr "Изменения файлов:" -#: gitk:4088 +#: gitk:4112 msgid "Fixed String" msgstr "Обычная строка" -#: gitk:4089 +#: gitk:4113 msgid "Regular Expression" msgstr "Регулярное выражение:" -#: gitk:4090 +#: gitk:4114 msgid "Search string:" msgstr "Строка для поиска:" -#: gitk:4091 +#: gitk:4115 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 15:27:38»):" -#: gitk:4092 +#: gitk:4116 msgid "Since:" msgstr "С даты:" -#: gitk:4093 +#: gitk:4117 msgid "Until:" msgstr "По дату:" -#: gitk:4094 +#: gitk:4118 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Ограничить и/или пропустить количество редакций (положительное число):" -#: gitk:4095 +#: gitk:4119 msgid "Number to show:" msgstr "Показать количество:" -#: gitk:4096 +#: gitk:4120 msgid "Number to skip:" msgstr "Пропустить количество:" -#: gitk:4097 +#: gitk:4121 msgid "Miscellaneous options:" msgstr "Различные опции:" -#: gitk:4098 +#: gitk:4122 msgid "Strictly sort by date" msgstr "Строгая сортировка по дате" -#: gitk:4099 +#: gitk:4123 msgid "Mark branch sides" msgstr "Отметить стороны веток" -#: gitk:4100 +#: gitk:4124 msgid "Limit to first parent" msgstr "Ограничить первым предком" -#: gitk:4101 +#: gitk:4125 msgid "Simple history" msgstr "Упрощенная история" -#: gitk:4102 +#: gitk:4126 msgid "Additional arguments to git log:" msgstr "Дополнительные аргументы для git log:" -#: gitk:4103 +#: gitk:4127 msgid "Enter files and directories to include, one per line:" msgstr "Файлы и каталоги для ограничения истории, по одному на строку:" -#: gitk:4104 +#: gitk:4128 msgid "Command to generate more commits to include:" msgstr "Дополнительная команда для списка коммитов:" -#: gitk:4228 +#: gitk:4252 msgid "Gitk: edit view" msgstr "Gitk: изменить представление" -#: gitk:4236 +#: gitk:4260 msgid "-- criteria for selecting revisions" msgstr "— критерий поиска редакций" -#: gitk:4241 +#: gitk:4265 msgid "View Name" msgstr "Имя представления" -#: gitk:4316 +#: gitk:4340 msgid "Apply (F5)" msgstr "Применить (F5)" -#: gitk:4354 +#: gitk:4378 msgid "Error in commit selection arguments:" msgstr "Ошибка в параметрах выбора коммитов:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 +#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525 msgid "None" msgstr "Ни одного" -#: gitk:5021 gitk:5026 +#: gitk:5045 gitk:5050 msgid "Descendant" msgstr "Порождённое" -#: gitk:5022 +#: gitk:5046 msgid "Not descendant" msgstr "Не порождённое" -#: gitk:5029 gitk:5034 +#: gitk:5053 gitk:5058 msgid "Ancestor" msgstr "Предок" -#: gitk:5030 +#: gitk:5054 msgid "Not ancestor" msgstr "Не предок" -#: gitk:5324 +#: gitk:5348 msgid "Local changes checked in to index but not committed" msgstr "Проиндексированные изменения" -#: gitk:5360 +#: gitk:5384 msgid "Local uncommitted changes, not checked in to index" msgstr "Непроиндексированные изменения" -#: gitk:7134 +#: gitk:7158 msgid "and many more" msgstr "и многое другое" -#: gitk:7137 +#: gitk:7161 msgid "many" msgstr "много" -#: gitk:7328 +#: gitk:7352 msgid "Tags:" msgstr "Метки:" -#: gitk:7345 gitk:7351 gitk:8825 +#: gitk:7369 gitk:7375 gitk:8854 msgid "Parent" msgstr "Предок" -#: gitk:7356 +#: gitk:7380 msgid "Child" msgstr "Потомок" -#: gitk:7365 +#: gitk:7389 msgid "Branch" msgstr "Ветка" -#: gitk:7368 +#: gitk:7392 msgid "Follows" msgstr "Следует за" -#: gitk:7371 +#: gitk:7395 msgid "Precedes" msgstr "Предшествует" -#: gitk:7966 +#: gitk:7990 #, tcl-format msgid "Error getting diffs: %s" msgstr "Ошибка получения изменений: %s" -#: gitk:8650 +#: gitk:8679 msgid "Goto:" msgstr "Перейти к:" -#: gitk:8671 +#: gitk:8700 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Сокращённый SHA1 идентификатор %s неоднозначен" -#: gitk:8678 +#: gitk:8707 #, tcl-format msgid "Revision %s is not known" msgstr "Редакция %s не найдена" -#: gitk:8688 +#: gitk:8717 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1 идентификатор %s не найден" -#: gitk:8690 +#: gitk:8719 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Редакция %s не найдена в текущем представлении" -#: gitk:8832 gitk:8847 +#: gitk:8861 gitk:8876 msgid "Date" msgstr "Дата" -#: gitk:8835 +#: gitk:8864 msgid "Children" msgstr "Потомки" -#: gitk:8898 +#: gitk:8927 #, tcl-format msgid "Reset %s branch to here" msgstr "Сбросить ветку %s на этот коммит" -#: gitk:8900 +#: gitk:8929 msgid "Detached head: can't reset" msgstr "Коммит не принадлежит ни одной ветке, сбросить невозможно" -#: gitk:9005 gitk:9011 +#: gitk:9034 gitk:9040 msgid "Skipping merge commit " msgstr "Пропускаю коммит-слияние" -#: gitk:9020 gitk:9025 +#: gitk:9049 gitk:9054 msgid "Error getting patch ID for " msgstr "Не удалось получить идентификатор патча для " -#: gitk:9021 gitk:9026 +#: gitk:9050 gitk:9055 msgid " - stopping\n" msgstr " — останов\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 +#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094 msgid "Commit " msgstr "Коммит" -#: gitk:9035 +#: gitk:9064 msgid "" " is the same patch as\n" " " msgstr " такой же патч, как и\n " -#: gitk:9043 +#: gitk:9072 msgid "" " differs from\n" " " msgstr " отличается от\n " -#: gitk:9045 +#: gitk:9074 msgid "" "Diff of commits:\n" "\n" msgstr "Различия коммитов:\n\n" -#: gitk:9057 gitk:9066 +#: gitk:9086 gitk:9095 #, tcl-format msgid " has %s children - stopping\n" msgstr " является %s потомком — останов\n" -#: gitk:9085 +#: gitk:9114 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Произошла ошибка при записи коммита в файл: %s" -#: gitk:9091 +#: gitk:9120 #, tcl-format msgid "Error diffing commits: %s" msgstr "Произошла ошибка при выводе различий коммитов: %s" -#: gitk:9137 +#: gitk:9166 msgid "Top" msgstr "Верх" -#: gitk:9138 +#: gitk:9167 msgid "From" msgstr "От" -#: gitk:9143 +#: gitk:9172 msgid "To" msgstr "До" -#: gitk:9167 +#: gitk:9196 msgid "Generate patch" msgstr "Создать патч" -#: gitk:9169 +#: gitk:9198 msgid "From:" msgstr "От:" -#: gitk:9178 +#: gitk:9207 msgid "To:" msgstr "До:" -#: gitk:9187 +#: gitk:9216 msgid "Reverse" msgstr "В обратном порядке" -#: gitk:9189 gitk:9385 +#: gitk:9218 gitk:9428 msgid "Output file:" msgstr "Файл для сохранения:" -#: gitk:9195 +#: gitk:9224 msgid "Generate" msgstr "Создать" -#: gitk:9233 +#: gitk:9262 msgid "Error creating patch:" msgstr "Ошибка создания патча:" -#: gitk:9256 gitk:9373 gitk:9430 +#: gitk:9285 gitk:9416 gitk:9504 msgid "ID:" msgstr "ID:" -#: gitk:9265 +#: gitk:9294 msgid "Tag name:" msgstr "Имя метки:" -#: gitk:9268 +#: gitk:9297 msgid "Tag message is optional" msgstr "Описание метки указывать не обязательно" -#: gitk:9270 +#: gitk:9299 msgid "Tag message:" msgstr "Описание метки:" -#: gitk:9274 gitk:9439 +#: gitk:9303 gitk:9474 msgid "Create" msgstr "Создать" -#: gitk:9292 +#: gitk:9321 msgid "No tag name specified" msgstr "Не задано имя метки" -#: gitk:9296 +#: gitk:9325 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Метка «%s» уже существует" -#: gitk:9306 +#: gitk:9335 msgid "Error creating tag:" msgstr "Ошибка создания метки:" -#: gitk:9382 +#: gitk:9425 msgid "Command:" msgstr "Команда:" -#: gitk:9390 +#: gitk:9433 msgid "Write" msgstr "Запись" -#: gitk:9408 +#: gitk:9451 msgid "Error writing commit:" msgstr "Произошла ошибка при записи коммита:" -#: gitk:9435 +#: gitk:9473 +msgid "Create branch" +msgstr "Создать ветку" + +#: gitk:9489 +#, tcl-format +msgid "Rename branch %s" +msgstr "Переименовать ветку %s" + +#: gitk:9490 +msgid "Rename" +msgstr "Переименовать" + +#: gitk:9514 msgid "Name:" msgstr "Имя:" -#: gitk:9458 +#: gitk:9538 msgid "Please specify a name for the new branch" msgstr "Укажите имя для новой ветки" -#: gitk:9463 +#: gitk:9543 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Ветка «%s» уже существует. Переписать?" -#: gitk:9530 +#: gitk:9587 +msgid "Please specify a new name for the branch" +msgstr "Укажите имя для новой ветки" + +#: gitk:9650 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "Коммит %s уже включён в ветку %s. Продолжить операцию?" -#: gitk:9535 +#: gitk:9655 msgid "Cherry-picking" -msgstr "Копирование изменений" +msgstr "Копирование коммита" -#: gitk:9544 +#: gitk:9664 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." -msgstr "Отбор лучшего невозможен из-за изменений в файле «%s».\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "Копирование коммита невозможно из-за изменений в файле «%s».\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9550 +#: gitk:9670 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" msgstr "Копирование изменений невозможно из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" -#: gitk:9566 gitk:9624 +#: gitk:9686 gitk:9744 msgid "No changes committed" msgstr "Изменения не закоммичены" -#: gitk:9593 +#: gitk:9713 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Коммит %s не включён в ветку %s. Продолжить операцию?" -#: gitk:9598 +#: gitk:9718 msgid "Reverting" -msgstr "Возврат изменений" +msgstr "Обращение изменений" -#: gitk:9606 +#: gitk:9726 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " "commit, reset or stash your changes and try again." -msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакомитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9610 +#: gitk:9730 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" msgstr "Возврат изменений невозможен из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" -#: gitk:9653 +#: gitk:9773 msgid "Confirm reset" msgstr "Подтвердите операцию перехода" -#: gitk:9655 +#: gitk:9775 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Сбросить ветку %s на коммит %s?" -#: gitk:9657 +#: gitk:9777 msgid "Reset type:" msgstr "Тип операции перехода:" -#: gitk:9660 +#: gitk:9780 msgid "Soft: Leave working tree and index untouched" msgstr "Лёгкий: оставить рабочий каталог и индекс неизменными" -#: gitk:9663 +#: gitk:9783 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Смешанный: оставить рабочий каталог неизменным, установить индекс" -#: gitk:9666 +#: gitk:9786 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" msgstr "Жесткий: переписать индекс и рабочий каталог\n(все изменения в рабочем каталоге будут потеряны)" -#: gitk:9683 +#: gitk:9803 msgid "Resetting" msgstr "Сброс" -#: gitk:9743 +#: gitk:9876 +#, tcl-format +msgid "A local branch named %s exists already" +msgstr "Локальная ветка с именем %s уже существует" + +#: gitk:9884 msgid "Checking out" msgstr "Переход" -#: gitk:9796 +#: gitk:9943 msgid "Cannot delete the currently checked-out branch" msgstr "Активная ветка не может быть удалена" -#: gitk:9802 +#: gitk:9949 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" "Really delete branch %s?" msgstr "Коммиты из ветки %s не принадлежат больше никакой другой ветке.\nДействительно удалить ветку %s?" -#: gitk:9833 +#: gitk:9980 #, tcl-format msgid "Tags and heads: %s" msgstr "Метки и ветки: %s" -#: gitk:9850 +#: gitk:9997 msgid "Filter" msgstr "Фильтровать" -#: gitk:10146 +#: gitk:10293 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." msgstr "Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток (до/после) может быть неполной." -#: gitk:11123 +#: gitk:11270 msgid "Tag" msgstr "Метка" -#: gitk:11127 +#: gitk:11274 msgid "Id" msgstr "Id" -#: gitk:11210 +#: gitk:11357 msgid "Gitk font chooser" msgstr "Шрифт Gitk" -#: gitk:11227 +#: gitk:11374 msgid "B" msgstr "Ж" -#: gitk:11230 +#: gitk:11377 msgid "I" msgstr "К" -#: gitk:11348 +#: gitk:11495 msgid "Commit list display options" msgstr "Параметры показа списка коммитов" -#: gitk:11351 +#: gitk:11498 msgid "Maximum graph width (lines)" msgstr "Макс. ширина графа (строк)" -#: gitk:11355 +#: gitk:11502 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Макс. ширина графа (% ширины панели)" -#: gitk:11358 +#: gitk:11505 msgid "Show local changes" msgstr "Показывать изменения в рабочем каталоге" -#: gitk:11361 +#: gitk:11508 msgid "Auto-select SHA1 (length)" msgstr "Автоматически выделить SHA1 (длинна)" -#: gitk:11365 +#: gitk:11512 msgid "Hide remote refs" msgstr "Скрыть внешние ссылки" -#: gitk:11369 +#: gitk:11516 msgid "Diff display options" msgstr "Параметры показа изменений" -#: gitk:11371 +#: gitk:11518 msgid "Tab spacing" msgstr "Ширина табуляции" -#: gitk:11374 +#: gitk:11521 msgid "Display nearby tags/heads" msgstr "Показывать близкие метки/ветки" -#: gitk:11377 +#: gitk:11524 msgid "Maximum # tags/heads to show" msgstr "Показывать максимальное количество меток/веток" -#: gitk:11380 +#: gitk:11527 msgid "Limit diffs to listed paths" msgstr "Ограничить показ изменений выбранными файлами" -#: gitk:11383 +#: gitk:11530 msgid "Support per-file encodings" msgstr "Поддержка кодировок в отдельных файлах" -#: gitk:11389 gitk:11536 +#: gitk:11536 gitk:11683 msgid "External diff tool" msgstr "Программа для показа изменений" -#: gitk:11390 +#: gitk:11537 msgid "Choose..." msgstr "Выберите..." -#: gitk:11395 +#: gitk:11542 msgid "General options" msgstr "Общие опции" -#: gitk:11398 +#: gitk:11545 msgid "Use themed widgets" msgstr "Использовать стили виджетов" -#: gitk:11400 +#: gitk:11547 msgid "(change requires restart)" msgstr "(изменение потребует перезапуск)" -#: gitk:11402 +#: gitk:11549 msgid "(currently unavailable)" msgstr "(недоступно в данный момент)" -#: gitk:11413 +#: gitk:11560 msgid "Colors: press to choose" msgstr "Цвета: нажмите для выбора" -#: gitk:11416 +#: gitk:11563 msgid "Interface" msgstr "Интерфейс" -#: gitk:11417 +#: gitk:11564 msgid "interface" msgstr "интерфейс" -#: gitk:11420 +#: gitk:11567 msgid "Background" msgstr "Фон" -#: gitk:11421 gitk:11451 +#: gitk:11568 gitk:11598 msgid "background" msgstr "фон" -#: gitk:11424 +#: gitk:11571 msgid "Foreground" msgstr "Передний план" -#: gitk:11425 +#: gitk:11572 msgid "foreground" msgstr "передний план" -#: gitk:11428 +#: gitk:11575 msgid "Diff: old lines" msgstr "Изменения: старый текст" -#: gitk:11429 +#: gitk:11576 msgid "diff old lines" msgstr "старый текст изменения" -#: gitk:11433 +#: gitk:11580 msgid "Diff: new lines" msgstr "Изменения: новый текст" -#: gitk:11434 +#: gitk:11581 msgid "diff new lines" msgstr "новый текст изменения" -#: gitk:11438 +#: gitk:11585 msgid "Diff: hunk header" msgstr "Изменения: заголовок блока" -#: gitk:11440 +#: gitk:11587 msgid "diff hunk header" msgstr "заголовок блока изменений" -#: gitk:11444 +#: gitk:11591 msgid "Marked line bg" msgstr "Фон выбранной строки" -#: gitk:11446 +#: gitk:11593 msgid "marked line background" msgstr "фон выбранной строки" -#: gitk:11450 +#: gitk:11597 msgid "Select bg" msgstr "Выберите фон" -#: gitk:11459 +#: gitk:11606 msgid "Fonts: press to choose" msgstr "Шрифт: нажмите для выбора" -#: gitk:11461 +#: gitk:11608 msgid "Main font" msgstr "Основной шрифт" -#: gitk:11462 +#: gitk:11609 msgid "Diff display font" msgstr "Шрифт показа изменений" -#: gitk:11463 +#: gitk:11610 msgid "User interface font" msgstr "Шрифт интерфейса" -#: gitk:11485 +#: gitk:11632 msgid "Gitk preferences" msgstr "Настройки Gitk" -#: gitk:11494 +#: gitk:11641 msgid "General" msgstr "Общие" -#: gitk:11495 +#: gitk:11642 msgid "Colors" msgstr "Цвета" -#: gitk:11496 +#: gitk:11643 msgid "Fonts" msgstr "Шрифты" -#: gitk:11546 +#: gitk:11693 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: выберите цвет для %s" -#: gitk:12059 +#: gitk:12206 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "К сожалению gitk не может работать с этой версий Tcl/Tk.\nТребуется как минимум Tcl/Tk 8.4." -#: gitk:12269 +#: gitk:12416 msgid "Cannot find a git repository here." msgstr "Git-репозитарий не найден в текущем каталоге." -#: gitk:12316 +#: gitk:12463 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Неоднозначный аргумент «%s»: существует как редакция и как имя файла" -#: gitk:12328 +#: gitk:12475 msgid "Bad arguments to gitk:" msgstr "Неправильные аргументы для gitk:" diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index d9d4e87a44..2a06fe5bbc 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po @@ -374,14 +374,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - en incheckningsvisare för git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" @@ -1385,21 +1385,6 @@ msgstr "Felaktiga argument till gitk:" #~ msgid "mc" #~ msgstr "mc" -#~ msgid "" -#~ "\n" -#~ "Gitk - a commit viewer for git\n" -#~ "\n" -#~ "Copyright © 2005-2015 Paul Mackerras\n" -#~ "\n" -#~ "Use and redistribute under the terms of the GNU General Public License" -#~ msgstr "" -#~ "\n" -#~ "Gitk - en incheckningsvisare för git\n" -#~ "\n" -#~ "Copyright © 2005-2015 Paul Mackerras\n" -#~ "\n" -#~ "Använd och vidareförmedla enligt villkoren i GNU General Public License" - #~ msgid "next" #~ msgstr "nästa" diff --git a/gitk-git/po/vi.po b/gitk-git/po/vi.po index 8966812368..5967498660 100644 --- a/gitk-git/po/vi.po +++ b/gitk-git/po/vi.po @@ -363,14 +363,14 @@ msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2014 Paul Mackerras\n" +"Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk - ứng dụng để xem các lần chuyển giao dành cho git\n" "\n" -"Bản quyền © 2005-2014 Paul Mackerras\n" +"Bản quyền © 2005-2016 Paul Mackerras\n" "\n" "Dùng và phân phối lại phần mềm này theo các điều khoản của Giấy Phép Công GNU" diff --git a/gpg-interface.h b/gpg-interface.h index ea68885ad5..d2d4fd3a65 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -1,8 +1,9 @@ #ifndef GPG_INTERFACE_H #define GPG_INTERFACE_H -#define GPG_VERIFY_VERBOSE 1 -#define GPG_VERIFY_RAW 2 +#define GPG_VERIFY_VERBOSE 1 +#define GPG_VERIFY_RAW 2 +#define GPG_VERIFY_OMIT_STATUS 4 struct signature_check { char *payload; @@ -1735,12 +1735,23 @@ void grep_source_init(struct grep_source *gs, enum grep_source_type type, case GREP_SOURCE_FILE: gs->identifier = xstrdup(identifier); break; + case GREP_SOURCE_SUBMODULE: + if (!identifier) { + gs->identifier = NULL; + break; + } + /* + * FALL THROUGH + * If the identifier is non-NULL (in the submodule case) it + * will be a SHA1 that needs to be copied. + */ case GREP_SOURCE_SHA1: gs->identifier = xmalloc(20); hashcpy(gs->identifier, identifier); break; case GREP_SOURCE_BUF: gs->identifier = NULL; + break; } } @@ -1760,6 +1771,7 @@ void grep_source_clear_data(struct grep_source *gs) switch (gs->type) { case GREP_SOURCE_FILE: case GREP_SOURCE_SHA1: + case GREP_SOURCE_SUBMODULE: free(gs->buf); gs->buf = NULL; gs->size = 0; @@ -1831,8 +1843,10 @@ static int grep_source_load(struct grep_source *gs) return grep_source_load_sha1(gs); case GREP_SOURCE_BUF: return gs->buf ? 0 : -1; + case GREP_SOURCE_SUBMODULE: + break; } - die("BUG: invalid grep_source type"); + die("BUG: invalid grep_source type to load"); } void grep_source_load_driver(struct grep_source *gs) @@ -161,6 +161,7 @@ struct grep_source { GREP_SOURCE_SHA1, GREP_SOURCE_FILE, GREP_SOURCE_BUF, + GREP_SOURCE_SUBMODULE, } type; void *identifier; @@ -105,7 +105,22 @@ static int is_executable(const char *name) return 0; #if defined(GIT_WINDOWS_NATIVE) -{ /* cannot trust the executable bit, peek into the file instead */ + /* + * On Windows there is no executable bit. The file extension + * indicates whether it can be run as an executable, and Git + * has special-handling to detect scripts and launch them + * through the indicated script interpreter. We test for the + * file extension first because virus scanners may make + * it quite expensive to open many files. + */ + if (ends_with(name, ".exe")) + return S_IXUSR; + +{ + /* + * Now that we know it does not have an executable extension, + * peek into the file instead. + */ char buf[3] = { 0 }; int n; int fd = open(name, O_RDONLY); @@ -113,8 +128,8 @@ static int is_executable(const char *name) if (fd >= 0) { n = read(fd, buf, 2); if (n == 2) - /* DOS executables start with "MZ" */ - if (!strcmp(buf, "#!") || !strcmp(buf, "MZ")) + /* look for a she-bang */ + if (!strcmp(buf, "#!")) st.st_mode |= S_IXUSR; close(fd); } diff --git a/http-walker.c b/http-walker.c index c2f81cd6af..b34b6ace7c 100644 --- a/http-walker.c +++ b/http-walker.c @@ -3,6 +3,7 @@ #include "walker.h" #include "http.h" #include "list.h" +#include "transport.h" struct alt_base { char *base; @@ -160,6 +161,32 @@ static void prefetch(struct walker *walker, unsigned char *sha1) #endif } +static int is_alternate_allowed(const char *url) +{ + const char *protocols[] = { + "http", "https", "ftp", "ftps" + }; + int i; + + for (i = 0; i < ARRAY_SIZE(protocols); i++) { + const char *end; + if (skip_prefix(url, protocols[i], &end) && + starts_with(end, "://")) + break; + } + + if (i >= ARRAY_SIZE(protocols)) { + warning("ignoring alternate with unknown protocol: %s", url); + return 0; + } + if (!is_transport_allowed(protocols[i], 0)) { + warning("ignoring alternate with restricted protocol: %s", url); + return 0; + } + + return 1; +} + static void process_alternates_response(void *callback_data) { struct alternates_request *alt_req = @@ -274,17 +301,20 @@ static void process_alternates_response(void *callback_data) struct strbuf target = STRBUF_INIT; strbuf_add(&target, base, serverlen); strbuf_add(&target, data + i, posn - i - 7); - warning("adding alternate object store: %s", - target.buf); - newalt = xmalloc(sizeof(*newalt)); - newalt->next = NULL; - newalt->base = strbuf_detach(&target, NULL); - newalt->got_indices = 0; - newalt->packs = NULL; - - while (tail->next != NULL) - tail = tail->next; - tail->next = newalt; + + if (is_alternate_allowed(target.buf)) { + warning("adding alternate object store: %s", + target.buf); + newalt = xmalloc(sizeof(*newalt)); + newalt->next = NULL; + newalt->base = strbuf_detach(&target, NULL); + newalt->got_indices = 0; + newalt->packs = NULL; + + while (tail->next != NULL) + tail = tail->next; + tail->next = newalt; + } } } i = posn + 1; @@ -636,11 +636,25 @@ void setup_curl_trace(CURL *handle) curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL); } +static long get_curl_allowed_protocols(int from_user) +{ + long allowed_protocols = 0; + + if (is_transport_allowed("http", from_user)) + allowed_protocols |= CURLPROTO_HTTP; + if (is_transport_allowed("https", from_user)) + allowed_protocols |= CURLPROTO_HTTPS; + if (is_transport_allowed("ftp", from_user)) + allowed_protocols |= CURLPROTO_FTP; + if (is_transport_allowed("ftps", from_user)) + allowed_protocols |= CURLPROTO_FTPS; + + return allowed_protocols; +} static CURL *get_curl_handle(void) { CURL *result = curl_easy_init(); - long allowed_protocols = 0; if (!result) die("curl_easy_init failed"); @@ -736,20 +750,13 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_POST301, 1); #endif #if LIBCURL_VERSION_NUM >= 0x071304 - if (is_transport_allowed("http")) - allowed_protocols |= CURLPROTO_HTTP; - if (is_transport_allowed("https")) - allowed_protocols |= CURLPROTO_HTTPS; - if (is_transport_allowed("ftp")) - allowed_protocols |= CURLPROTO_FTP; - if (is_transport_allowed("ftps")) - allowed_protocols |= CURLPROTO_FTPS; - curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols); - curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols); + curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, + get_curl_allowed_protocols(0)); + curl_easy_setopt(result, CURLOPT_PROTOCOLS, + get_curl_allowed_protocols(-1)); #else - if (transport_restrict_protocols()) - warning("protocol restrictions not applied to curl redirects because\n" - "your curl version is too old (>= 7.19.4)"); + warning("protocol restrictions not applied to curl redirects because\n" + "your curl version is too old (>= 7.19.4)"); #endif if (getenv("GIT_CURL_VERBOSE")) curl_easy_setopt(result, CURLOPT_VERBOSE, 1L); diff --git a/lockfile.c b/lockfile.c index 9268cdf325..aa69210d8b 100644 --- a/lockfile.c +++ b/lockfile.c @@ -174,8 +174,16 @@ int hold_lock_file_for_update_timeout(struct lock_file *lk, const char *path, int flags, long timeout_ms) { int fd = lock_file_timeout(lk, path, flags, timeout_ms); - if (fd < 0 && (flags & LOCK_DIE_ON_ERROR)) - unable_to_lock_die(path, errno); + if (fd < 0) { + if (flags & LOCK_DIE_ON_ERROR) + unable_to_lock_die(path, errno); + if (flags & LOCK_REPORT_ON_ERROR) { + struct strbuf buf = STRBUF_INIT; + unable_to_lock_message(path, errno, &buf); + error("%s", buf.buf); + strbuf_release(&buf); + } + } return fd; } diff --git a/lockfile.h b/lockfile.h index d26ad27b2b..7b715f9e77 100644 --- a/lockfile.h +++ b/lockfile.h @@ -129,11 +129,17 @@ struct lock_file { /* * If a lock is already taken for the file, `die()` with an error * message. If this flag is not specified, trying to lock a file that - * is already locked returns -1 to the caller. + * is already locked silently returns -1 to the caller, or ... */ #define LOCK_DIE_ON_ERROR 1 /* + * ... this flag can be passed instead to return -1 and give the usual + * error message upon an error. + */ +#define LOCK_REPORT_ON_ERROR 4 + +/* * Usually symbolic links in the destination path are resolved. This * means that (1) the lockfile is created by adding ".lock" to the * resolved path, and (2) upon commit, the resolved path is diff --git a/merge-recursive.c b/merge-recursive.c index 23d6992f40..d327209443 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -2133,7 +2133,7 @@ int merge_recursive_generic(struct merge_options *o, } } - hold_locked_index(lock, 1); + hold_locked_index(lock, LOCK_DIE_ON_ERROR); clean = merge_recursive(o, head_commit, next_commit, ca, result); if (clean < 0) @@ -57,7 +57,7 @@ int checkout_fast_forward(const unsigned char *head, refresh_cache(REFRESH_QUIET); - if (hold_locked_index(lock_file, 0) < 0) + if (hold_locked_index(lock_file, LOCK_REPORT_ON_ERROR) < 0) return -1; memset(&trees, 0, sizeof(trees)); diff --git a/pathspec.c b/pathspec.c index 22ca74a126..7ababb3159 100644 --- a/pathspec.c +++ b/pathspec.c @@ -67,20 +67,19 @@ static struct pathspec_magic { char mnemonic; /* this cannot be ':'! */ const char *name; } pathspec_magic[] = { - { PATHSPEC_FROMTOP, '/', "top" }, - { PATHSPEC_LITERAL, 0, "literal" }, - { PATHSPEC_GLOB, '\0', "glob" }, - { PATHSPEC_ICASE, '\0', "icase" }, - { PATHSPEC_EXCLUDE, '!', "exclude" }, + { PATHSPEC_FROMTOP, '/', "top" }, + { PATHSPEC_LITERAL, '\0', "literal" }, + { PATHSPEC_GLOB, '\0', "glob" }, + { PATHSPEC_ICASE, '\0', "icase" }, + { PATHSPEC_EXCLUDE, '!', "exclude" }, }; -static void prefix_short_magic(struct strbuf *sb, int prefixlen, - unsigned short_magic) +static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic) { int i; strbuf_addstr(sb, ":("); for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (short_magic & pathspec_magic[i].bit) { + if (magic & pathspec_magic[i].bit) { if (sb->buf[sb->len - 1] != '(') strbuf_addch(sb, ','); strbuf_addstr(sb, pathspec_magic[i].name); @@ -88,54 +87,61 @@ static void prefix_short_magic(struct strbuf *sb, int prefixlen, strbuf_addf(sb, ",prefix:%d)", prefixlen); } -/* - * Take an element of a pathspec and check for magic signatures. - * Append the result to the prefix. Return the magic bitmap. - * - * For now, we only parse the syntax and throw out anything other than - * "top" magic. - * - * NEEDSWORK: This needs to be rewritten when we start migrating - * get_pathspec() users to use the "struct pathspec" interface. For - * example, a pathspec element may be marked as case-insensitive, but - * the prefix part must always match literally, and a single stupid - * string cannot express such a case. - */ -static unsigned prefix_pathspec(struct pathspec_item *item, - unsigned *p_short_magic, - const char **raw, unsigned flags, - const char *prefix, int prefixlen, - const char *elt) +static inline int get_literal_global(void) { - static int literal_global = -1; - static int glob_global = -1; - static int noglob_global = -1; - static int icase_global = -1; - unsigned magic = 0, short_magic = 0, global_magic = 0; - const char *copyfrom = elt, *long_magic_end = NULL; - char *match; - int i, pathspec_prefix = -1; + static int literal = -1; + + if (literal < 0) + literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); + + return literal; +} + +static inline int get_glob_global(void) +{ + static int glob = -1; + + if (glob < 0) + glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); + + return glob; +} + +static inline int get_noglob_global(void) +{ + static int noglob = -1; + + if (noglob < 0) + noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); + + return noglob; +} + +static inline int get_icase_global(void) +{ + static int icase = -1; + + if (icase < 0) + icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); + + return icase; +} + +static int get_global_magic(int element_magic) +{ + int global_magic = 0; - if (literal_global < 0) - literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); - if (literal_global) + if (get_literal_global()) global_magic |= PATHSPEC_LITERAL; - if (glob_global < 0) - glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); - if (glob_global) + /* --glob-pathspec is overridden by :(literal) */ + if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL)) global_magic |= PATHSPEC_GLOB; - if (noglob_global < 0) - noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); - - if (glob_global && noglob_global) + if (get_glob_global() && get_noglob_global()) die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); - - if (icase_global < 0) - icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); - if (icase_global) + if (get_icase_global()) global_magic |= PATHSPEC_ICASE; if ((global_magic & PATHSPEC_LITERAL) && @@ -143,84 +149,198 @@ static unsigned prefix_pathspec(struct pathspec_item *item, die(_("global 'literal' pathspec setting is incompatible " "with all other global pathspec settings")); - if (flags & PATHSPEC_LITERAL_PATH) - global_magic = 0; + /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ + if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB)) + global_magic |= PATHSPEC_LITERAL; - if (elt[0] != ':' || literal_global || - (flags & PATHSPEC_LITERAL_PATH)) { - ; /* nothing to do */ - } else if (elt[1] == '(') { - /* longhand */ - const char *nextat; - for (copyfrom = elt + 2; - *copyfrom && *copyfrom != ')'; - copyfrom = nextat) { - size_t len = strcspn(copyfrom, ",)"); - if (copyfrom[len] == ',') - nextat = copyfrom + len + 1; - else - /* handle ')' and '\0' */ - nextat = copyfrom + len; - if (!len) - continue; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { - if (strlen(pathspec_magic[i].name) == len && - !strncmp(pathspec_magic[i].name, copyfrom, len)) { - magic |= pathspec_magic[i].bit; - break; - } - if (starts_with(copyfrom, "prefix:")) { - char *endptr; - pathspec_prefix = strtol(copyfrom + 7, - &endptr, 10); - if (endptr - copyfrom != len) - die(_("invalid parameter for pathspec magic 'prefix'")); - /* "i" would be wrong, but it does not matter */ - break; - } + return global_magic; +} + +/* + * Parse the pathspec element looking for long magic + * + * saves all magic in 'magic' + * if prefix magic is used, save the prefix length in 'prefix_len' + * returns the position in 'elem' after all magic has been parsed + */ +static const char *parse_long_magic(unsigned *magic, int *prefix_len, + const char *elem) +{ + const char *pos; + const char *nextat; + + for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) { + size_t len = strcspn(pos, ",)"); + int i; + + if (pos[len] == ',') + nextat = pos + len + 1; /* handle ',' */ + else + nextat = pos + len; /* handle ')' and '\0' */ + + if (!len) + continue; + + if (starts_with(pos, "prefix:")) { + char *endptr; + *prefix_len = strtol(pos + 7, &endptr, 10); + if (endptr - pos != len) + die(_("invalid parameter for pathspec magic 'prefix'")); + continue; + } + + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + if (strlen(pathspec_magic[i].name) == len && + !strncmp(pathspec_magic[i].name, pos, len)) { + *magic |= pathspec_magic[i].bit; + break; } - if (ARRAY_SIZE(pathspec_magic) <= i) - die(_("Invalid pathspec magic '%.*s' in '%s'"), - (int) len, copyfrom, elt); } - if (*copyfrom != ')') - die(_("Missing ')' at the end of pathspec magic in '%s'"), elt); - long_magic_end = copyfrom; - copyfrom++; - } else { - /* shorthand */ - for (copyfrom = elt + 1; - *copyfrom && *copyfrom != ':'; - copyfrom++) { - char ch = *copyfrom; - if (!is_pathspec_magic(ch)) + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Invalid pathspec magic '%.*s' in '%s'"), + (int) len, pos, elem); + } + + if (*pos != ')') + die(_("Missing ')' at the end of pathspec magic in '%s'"), + elem); + pos++; + + return pos; +} + +/* + * Parse the pathspec element looking for short magic + * + * saves all magic in 'magic' + * returns the position in 'elem' after all magic has been parsed + */ +static const char *parse_short_magic(unsigned *magic, const char *elem) +{ + const char *pos; + + for (pos = elem + 1; *pos && *pos != ':'; pos++) { + char ch = *pos; + int i; + + if (!is_pathspec_magic(ch)) + break; + + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + if (pathspec_magic[i].mnemonic == ch) { + *magic |= pathspec_magic[i].bit; break; - for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) - if (pathspec_magic[i].mnemonic == ch) { - short_magic |= pathspec_magic[i].bit; - break; - } - if (ARRAY_SIZE(pathspec_magic) <= i) - die(_("Unimplemented pathspec magic '%c' in '%s'"), - ch, elt); + } } - if (*copyfrom == ':') - copyfrom++; + + if (ARRAY_SIZE(pathspec_magic) <= i) + die(_("Unimplemented pathspec magic '%c' in '%s'"), + ch, elem); } - magic |= short_magic; - *p_short_magic = short_magic; + if (*pos == ':') + pos++; - /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ - if (noglob_global && !(magic & PATHSPEC_GLOB)) - global_magic |= PATHSPEC_LITERAL; + return pos; +} - /* --glob-pathspec is overridden by :(literal) */ - if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL)) - global_magic &= ~PATHSPEC_GLOB; +static const char *parse_element_magic(unsigned *magic, int *prefix_len, + 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); + else + /* shorthand */ + return parse_short_magic(magic, elem); +} + +static void strip_submodule_slash_cheap(struct pathspec_item *item) +{ + if (item->len >= 1 && item->match[item->len - 1] == '/') { + int i = cache_name_pos(item->match, item->len - 1); + + if (i >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) { + item->len--; + item->match[item->len] = '\0'; + } + } +} + +static void strip_submodule_slash_expensive(struct pathspec_item *item) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len <= ce_len || item->match[ce_len] != '/' || + memcmp(ce->name, item->match, ce_len)) + continue; + + if (item->len == ce_len + 1) { + /* strip trailing slash */ + item->len--; + item->match[item->len] = '\0'; + } else { + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } + } +} + +static void die_inside_submodule_path(struct pathspec_item *item) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + if (item->len < ce_len || + !(item->match[ce_len] == '/' || item->match[ce_len] == '\0') || + memcmp(ce->name, item->match, ce_len)) + continue; - magic |= global_magic; + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } +} + +/* + * Perform the initialization of a pathspec_item based on a pathspec element. + */ +static void init_pathspec_item(struct pathspec_item *item, unsigned flags, + const char *prefix, int prefixlen, + const char *elt) +{ + unsigned magic = 0, element_magic = 0; + const char *copyfrom = elt; + char *match; + int pathspec_prefix = -1; + + /* PATHSPEC_LITERAL_PATH ignores magic */ + if (flags & PATHSPEC_LITERAL_PATH) { + magic = PATHSPEC_LITERAL; + } else { + copyfrom = parse_element_magic(&element_magic, + &pathspec_prefix, + elt); + magic |= element_magic; + magic |= get_global_magic(element_magic); + } + + item->magic = magic; if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) @@ -229,6 +349,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item, if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB)) die(_("%s: 'literal' and 'glob' are incompatible"), elt); + /* Create match string which will be used for pathspec matching */ if (pathspec_prefix >= 0) { match = xstrdup(copyfrom); prefixlen = pathspec_prefix; @@ -236,69 +357,47 @@ static unsigned prefix_pathspec(struct pathspec_item *item, match = xstrdup(copyfrom); prefixlen = 0; } else { - match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); + match = prefix_path_gently(prefix, prefixlen, + &prefixlen, copyfrom); if (!match) die(_("%s: '%s' is outside repository"), elt, copyfrom); } - *raw = item->match = match; + + item->match = match; + item->len = strlen(item->match); + item->prefix = prefixlen; + /* * Prefix the pathspec (keep all magic) and assign to * original. Useful for passing to another command. */ - if (flags & PATHSPEC_PREFIX_ORIGIN) { + if ((flags & PATHSPEC_PREFIX_ORIGIN) && + prefixlen && !get_literal_global()) { struct strbuf sb = STRBUF_INIT; - if (prefixlen && !literal_global) { - /* Preserve the actual prefix length of each pattern */ - if (short_magic) - prefix_short_magic(&sb, prefixlen, short_magic); - else if (long_magic_end) { - strbuf_add(&sb, elt, long_magic_end - elt); - strbuf_addf(&sb, ",prefix:%d)", prefixlen); - } else - strbuf_addf(&sb, ":(prefix:%d)", prefixlen); - } + + /* Preserve the actual prefix length of each pattern */ + prefix_magic(&sb, prefixlen, element_magic); + strbuf_addstr(&sb, match); item->original = strbuf_detach(&sb, NULL); - } else - item->original = elt; - item->len = strlen(item->match); - item->prefix = prefixlen; - - if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && - (item->len >= 1 && item->match[item->len - 1] == '/') && - (i = cache_name_pos(item->match, item->len - 1)) >= 0 && - S_ISGITLINK(active_cache[i]->ce_mode)) { - item->len--; - match[item->len] = '\0'; + } else { + item->original = xstrdup(elt); } + if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) + strip_submodule_slash_cheap(item); + if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len <= ce_len || match[ce_len] != '/' || - memcmp(ce->name, match, ce_len)) - continue; - if (item->len == ce_len + 1) { - /* strip trailing slash */ - item->len--; - match[item->len] = '\0'; - } else - die (_("Pathspec '%s' is in submodule '%.*s'"), - elt, ce_len, ce->name); - } + strip_submodule_slash_expensive(item); - if (magic & PATHSPEC_LITERAL) + if (magic & PATHSPEC_LITERAL) { item->nowildcard_len = item->len; - else { + } else { item->nowildcard_len = simple_length(item->match); if (item->nowildcard_len < prefixlen) item->nowildcard_len = prefixlen; } + item->flags = 0; if (magic & PATHSPEC_GLOB) { /* @@ -313,9 +412,18 @@ static unsigned prefix_pathspec(struct pathspec_item *item, } /* sanity checks, pathspec matchers assume these are sane */ - assert(item->nowildcard_len <= item->len && - item->prefix <= item->len); - return magic; + if (item->nowildcard_len > item->len || + item->prefix > item->len) { + /* + * This case can be triggered by the user pointing us to a + * pathspec inside a submodule, which is an input error. + * Detect that here and complain, but fallback in the + * non-submodule case to a BUG, as we have no idea what + * would trigger that. + */ + die_inside_submodule_path(item); + die ("BUG: item->nowildcard_len > item->len || item->prefix > item->len)"); + } } static int pathspec_item_cmp(const void *a_, const void *b_) @@ -328,22 +436,22 @@ static int pathspec_item_cmp(const void *a_, const void *b_) } static void NORETURN unsupported_magic(const char *pattern, - unsigned magic, - unsigned short_magic) + unsigned magic) { struct strbuf sb = STRBUF_INIT; - int i, n; - for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { + int i; + for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { const struct pathspec_magic *m = pathspec_magic + i; if (!(magic & m->bit)) continue; if (sb.len) - strbuf_addch(&sb, ' '); - if (short_magic & m->bit) - strbuf_addf(&sb, "'%c'", m->mnemonic); + strbuf_addstr(&sb, ", "); + + if (m->mnemonic) + strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"), + m->name, m->mnemonic); else strbuf_addf(&sb, "'%s'", m->name); - n++; } /* * We may want to substitute "this command" with a command @@ -381,8 +489,6 @@ void parse_pathspec(struct pathspec *pathspec, /* No arguments with prefix -> prefix pathspec */ if (!entry) { - static const char *raw[2]; - if (flags & PATHSPEC_PREFER_FULL) return; @@ -390,14 +496,11 @@ void parse_pathspec(struct pathspec *pathspec, die("BUG: PATHSPEC_PREFER_CWD requires arguments"); pathspec->items = item = xcalloc(1, sizeof(*item)); - item->match = prefix; - item->original = prefix; + item->match = xstrdup(prefix); + item->original = xstrdup(prefix); item->nowildcard_len = item->len = strlen(prefix); item->prefix = item->len; - raw[0] = prefix; - raw[1] = NULL; pathspec->nr = 1; - pathspec->_raw = raw; return; } @@ -415,25 +518,17 @@ void parse_pathspec(struct pathspec *pathspec, pathspec->nr = n; ALLOC_ARRAY(pathspec->items, n); item = pathspec->items; - pathspec->_raw = argv; prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < n; i++) { - unsigned short_magic; entry = argv[i]; - item[i].magic = prefix_pathspec(item + i, &short_magic, - argv + i, flags, - prefix, prefixlen, entry); - if ((flags & PATHSPEC_LITERAL_PATH) && - !(magic_mask & PATHSPEC_LITERAL)) - item[i].magic |= PATHSPEC_LITERAL; + init_pathspec_item(item + i, flags, prefix, prefixlen, entry); + if (item[i].magic & PATHSPEC_EXCLUDE) nr_exclude++; if (item[i].magic & magic_mask) - unsupported_magic(entry, - item[i].magic & magic_mask, - short_magic); + unsupported_magic(entry, item[i].magic & magic_mask); if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) && has_symlink_leading_path(item[i].match, item[i].len)) { @@ -457,45 +552,29 @@ void parse_pathspec(struct pathspec *pathspec, } } -/* - * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" - * based interface - see pathspec.c:parse_pathspec(). - * - * Arguments: - * - prefix - a path relative to the root of the working tree - * - pathspec - a list of paths underneath the prefix path - * - * Iterates over pathspec, prepending each path with prefix, - * and return the resulting list. - * - * If pathspec is empty, return a singleton list containing prefix. - * - * If pathspec and prefix are both empty, return an empty list. - * - * This is typically used by built-in commands such as add.c, in order - * to normalize argv arguments provided to the built-in into a list of - * paths to process, all relative to the root of the working tree. - */ -const char **get_pathspec(const char *prefix, const char **pathspec) -{ - struct pathspec ps; - parse_pathspec(&ps, - PATHSPEC_ALL_MAGIC & - ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL), - PATHSPEC_PREFER_CWD, - prefix, pathspec); - return ps._raw; -} - void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { + int i; + *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); + } } void clear_pathspec(struct pathspec *pathspec) { + int i; + + for (i = 0; i < pathspec->nr; i++) { + free(pathspec->items[i].match); + free(pathspec->items[i].original); + } free(pathspec->items); pathspec->items = NULL; + pathspec->nr = 0; } diff --git a/pathspec.h b/pathspec.h index 59809e4793..49fd823ddf 100644 --- a/pathspec.h +++ b/pathspec.h @@ -19,15 +19,14 @@ #define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */ struct pathspec { - const char **_raw; /* get_pathspec() result, not freed by clear_pathspec() */ int nr; unsigned int has_wildcard:1; unsigned int recursive:1; unsigned magic; int max_depth; struct pathspec_item { - const char *match; - const char *original; + char *match; + char *original; unsigned magic; int len, prefix; int nowildcard_len; diff --git a/perl/Git.pm b/perl/Git.pm index b2732822af..bfce1f795d 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -1438,6 +1438,44 @@ sub END { } # %TEMP_* Lexical Context +=item prefix_lines ( PREFIX, STRING [, STRING... ]) + +Prefixes lines in C<STRING> with C<PREFIX>. + +=cut + +sub prefix_lines { + my $prefix = shift; + my $string = join("\n", @_); + $string =~ s/^/$prefix/mg; + return $string; +} + +=item get_comment_line_char ( ) + +Gets the core.commentchar configuration value. +The value falls-back to '#' if core.commentchar is set to 'auto'. + +=cut + +sub get_comment_line_char { + my $comment_line_char = config("core.commentchar") || '#'; + $comment_line_char = '#' if ($comment_line_char eq 'auto'); + $comment_line_char = '#' if (length($comment_line_char) != 1); + return $comment_line_char; +} + +=item comment_lines ( STRING [, STRING... ]) + +Comments lines following core.commentchar configuration. + +=cut + +sub comment_lines { + my $comment_line_char = get_comment_line_char; + return prefix_lines("$comment_line_char ", @_); +} + =back =head1 ERROR HANDLING diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm index f889fd6da9..c41425c8d0 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -13,7 +13,7 @@ BEGIN { } } -our @EXPORT = qw(__); +our @EXPORT = qw(__ __n N__); our @EXPORT_OK = @EXPORT; sub __bootstrap_locale_messages { @@ -44,6 +44,7 @@ BEGIN eval { __bootstrap_locale_messages(); *__ = \&Locale::Messages::gettext; + *__n = \&Locale::Messages::ngettext; 1; } or do { # Tell test.pl that we couldn't load the gettext library. @@ -51,7 +52,10 @@ BEGIN # Just a fall-through no-op *__ = sub ($) { $_[0] }; + *__n = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] }; }; + + sub N__($) { return shift; } } 1; @@ -70,6 +74,9 @@ Git::I18N - Perl interface to Git's Gettext localizations printf __("The following error occurred: %s\n"), $error; + printf __n("commited %d file\n", "commited %d files\n", $files), $files; + + =head1 DESCRIPTION Git's internal Perl interface to gettext via L<Locale::Messages>. If @@ -87,6 +94,16 @@ it. L<Locale::Messages>'s gettext function if all goes well, otherwise our passthrough fallback function. +=head2 __n($$$) + +L<Locale::Messages>'s ngettext function or passthrough fallback function. + +=head2 N__($) + +No-operation that only returns its argument. Use this if you want xgettext to +extract the text to the pot template but do not want to trigger retrival of the +translation at run time. + =head1 AUTHOR E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com> @@ -10,6 +10,7 @@ #include "color.h" #include "reflog-walk.h" #include "gpg-interface.h" +#include "trailer.h" static char *user_format; static struct cmt_fmt_map { @@ -889,6 +890,16 @@ const char *format_subject(struct strbuf *sb, const char *msg, return msg; } +static void format_trailers(struct strbuf *sb, const char *msg) +{ + struct trailer_info info; + + trailer_info_get(&info, msg); + strbuf_add(sb, info.trailer_start, + info.trailer_end - info.trailer_start); + trailer_info_release(&info); +} + static void parse_commit_message(struct format_commit_context *c) { const char *msg = c->message + c->message_off; @@ -1292,6 +1303,12 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, msg + c->body_off); return 1; } + + if (starts_with(placeholder, "(trailers)")) { + format_trailers(sb, msg + c->subject_off); + return strlen("(trailers)"); + } + return 0; /* unknown placeholder */ } diff --git a/read-cache.c b/read-cache.c index 585ff02dc3..9054369dd0 100644 --- a/read-cache.c +++ b/read-cache.c @@ -156,14 +156,7 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) static int ce_compare_data(const struct cache_entry *ce, struct stat *st) { int match = -1; - static int cloexec = O_CLOEXEC; - int fd = open(ce->name, O_RDONLY | cloexec); - - if ((cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { - /* Try again w/o O_CLOEXEC: the kernel might not support it */ - cloexec &= ~O_CLOEXEC; - fd = open(ce->name, O_RDONLY | cloexec); - } + int fd = git_open_cloexec(ce->name, O_RDONLY); if (fd >= 0) { unsigned char sha1[20]; @@ -1424,12 +1417,9 @@ static int read_index_extension(struct index_state *istate, return 0; } -int hold_locked_index(struct lock_file *lk, int die_on_error) +int hold_locked_index(struct lock_file *lk, int lock_flags) { - return hold_lock_file_for_update(lk, get_index_file(), - die_on_error - ? LOCK_DIE_ON_ERROR - : 0); + return hold_lock_file_for_update(lk, get_index_file(), lock_flags); } int read_index(struct index_state *istate) @@ -2294,7 +2284,8 @@ int index_name_is_other(const struct index_state *istate, const char *name, return 1; } -void *read_blob_data_from_index(struct index_state *istate, const char *path, unsigned long *size) +void *read_blob_data_from_index(const struct index_state *istate, + const char *path, unsigned long *size) { int pos, len; unsigned long sz; diff --git a/ref-filter.c b/ref-filter.c index f5f7a70c6d..3820b21cc7 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -13,6 +13,7 @@ #include "utf8.h" #include "git-compat-util.h" #include "version.h" +#include "trailer.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -40,7 +41,7 @@ static struct used_atom { enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT } remote_ref; struct { - enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB } option; + 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; @@ -85,6 +86,13 @@ static void subject_atom_parser(struct used_atom *atom, const char *arg) atom->u.contents.option = C_SUB; } +static void trailers_atom_parser(struct used_atom *atom, const char *arg) +{ + if (arg) + die(_("%%(trailers) does not take arguments")); + atom->u.contents.option = C_TRAILERS; +} + static void contents_atom_parser(struct used_atom *atom, const char *arg) { if (!arg) @@ -95,6 +103,8 @@ static void contents_atom_parser(struct used_atom *atom, const char *arg) atom->u.contents.option = C_SIG; else if (!strcmp(arg, "subject")) atom->u.contents.option = C_SUB; + else if (!strcmp(arg, "trailers")) + atom->u.contents.option = C_TRAILERS; else if (skip_prefix(arg, "lines=", &arg)) { atom->u.contents.option = C_LINES; if (strtoul_ui(arg, 10, &atom->u.contents.nlines)) @@ -194,6 +204,7 @@ static struct { { "creatordate", FIELD_TIME }, { "subject", FIELD_STR, subject_atom_parser }, { "body", FIELD_STR, body_atom_parser }, + { "trailers", FIELD_STR, trailers_atom_parser }, { "contents", FIELD_STR, contents_atom_parser }, { "upstream", FIELD_STR, remote_ref_atom_parser }, { "push", FIELD_STR, remote_ref_atom_parser }, @@ -785,6 +796,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj name++; if (strcmp(name, "subject") && strcmp(name, "body") && + strcmp(name, "trailers") && !starts_with(name, "contents")) continue; if (!subpos) @@ -808,6 +820,14 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj /* Size is the length of the message after removing the signature */ append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines); v->s = strbuf_detach(&s, NULL); + } else if (atom->u.contents.option == C_TRAILERS) { + struct trailer_info info; + + /* Search for trailer info */ + trailer_info_get(&info, subpos); + v->s = xmemdupz(info.trailer_start, + info.trailer_end - info.trailer_start); + trailer_info_release(&info); } else if (atom->u.contents.option == C_BARE) v->s = xstrdup(subpos); } @@ -1231,8 +1251,14 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit) * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref * matches "refs/heads/mas*", too). */ -static int match_pattern(const char **patterns, const char *refname) +static int match_pattern(const struct ref_filter *filter, const char *refname) { + const char **patterns = filter->name_patterns; + unsigned flags = 0; + + if (filter->ignore_case) + flags |= WM_CASEFOLD; + /* * When no '--format' option is given we need to skip the prefix * for matching refs of tags and branches. @@ -1243,7 +1269,7 @@ static int match_pattern(const char **patterns, const char *refname) skip_prefix(refname, "refs/", &refname)); for (; *patterns; patterns++) { - if (!wildmatch(*patterns, refname, 0, NULL)) + if (!wildmatch(*patterns, refname, flags, NULL)) return 1; } return 0; @@ -1255,9 +1281,15 @@ static int match_pattern(const char **patterns, const char *refname) * matches a pattern "refs/heads/" but not "refs/heads/m") or a * wildcard (e.g. the same ref matches "refs/heads/m*", too). */ -static int match_name_as_path(const char **pattern, const char *refname) +static int match_name_as_path(const struct ref_filter *filter, const char *refname) { + const char **pattern = filter->name_patterns; int namelen = strlen(refname); + unsigned flags = WM_PATHNAME; + + if (filter->ignore_case) + flags |= WM_CASEFOLD; + for (; *pattern; pattern++) { const char *p = *pattern; int plen = strlen(p); @@ -1280,8 +1312,8 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) if (!*filter->name_patterns) return 1; /* No pattern always matches */ if (filter->match_as_path) - return match_name_as_path(filter->name_patterns, refname); - return match_pattern(filter->name_patterns, refname); + return match_name_as_path(filter, refname); + return match_pattern(filter, refname); } /* @@ -1329,7 +1361,7 @@ static struct ref_array_item *new_ref_array_item(const char *refname, return ref; } -static int filter_ref_kind(struct ref_filter *filter, const char *refname) +static int ref_kind_from_refname(const char *refname) { unsigned int i; @@ -1342,11 +1374,7 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) { "refs/tags/", FILTER_REFS_TAGS} }; - if (filter->kind == FILTER_REFS_BRANCHES || - filter->kind == FILTER_REFS_REMOTES || - filter->kind == FILTER_REFS_TAGS) - return filter->kind; - else if (!strcmp(refname, "HEAD")) + if (!strcmp(refname, "HEAD")) return FILTER_REFS_DETACHED_HEAD; for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { @@ -1357,6 +1385,15 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) return FILTER_REFS_OTHERS; } +static int filter_ref_kind(struct ref_filter *filter, const char *refname) +{ + if (filter->kind == FILTER_REFS_BRANCHES || + filter->kind == FILTER_REFS_REMOTES || + filter->kind == FILTER_REFS_TAGS) + return filter->kind; + return ref_kind_from_refname(refname); +} + /* * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. @@ -1536,18 +1573,20 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru struct atom_value *va, *vb; int cmp; cmp_type cmp_type = used_atom[s->atom].type; + int (*cmp_fn)(const char *, const char *); get_ref_atom_value(a, s->atom, &va); get_ref_atom_value(b, s->atom, &vb); + cmp_fn = s->ignore_case ? strcasecmp : strcmp; if (s->version) cmp = versioncmp(va->s, vb->s); else if (cmp_type == FIELD_STR) - cmp = strcmp(va->s, vb->s); + cmp = cmp_fn(va->s, vb->s); else { if (va->ul < vb->ul) cmp = -1; else if (va->ul == vb->ul) - cmp = strcmp(a->refname, b->refname); + cmp = cmp_fn(a->refname, b->refname); else cmp = 1; } @@ -1555,8 +1594,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru return (s->reverse) ? -cmp : cmp; } -static struct ref_sorting *ref_sorting; -static int compare_refs(const void *a_, const void *b_) +static int compare_refs(const void *a_, const void *b_, void *ref_sorting) { struct ref_array_item *a = *((struct ref_array_item **)a_); struct ref_array_item *b = *((struct ref_array_item **)b_); @@ -1572,8 +1610,7 @@ static int compare_refs(const void *a_, const void *b_) void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) { - ref_sorting = sorting; - QSORT(array->items, array->nr, compare_refs); + QSORT_S(array->items, array->nr, compare_refs, sorting); } static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) @@ -1637,6 +1674,16 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu putchar('\n'); } +void pretty_print_ref(const char *name, const unsigned char *sha1, + const char *format) +{ + struct ref_array_item *ref_item; + ref_item = new_ref_array_item(name, sha1, 0); + ref_item->kind = ref_kind_from_refname(name); + show_ref_array_item(ref_item, format, 0); + free_array_item(ref_item); +} + /* If no sorting option is given, use refname to sort as default */ struct ref_sorting *ref_default_sorting(void) { diff --git a/ref-filter.h b/ref-filter.h index 14d435e2cc..7b05592baf 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -29,6 +29,7 @@ struct ref_sorting { struct ref_sorting *next; int atom; /* index into used_atom array (internal) */ unsigned reverse : 1, + ignore_case : 1, version : 1; }; @@ -62,6 +63,7 @@ struct ref_filter { unsigned int with_commit_tag_algo : 1, match_as_path : 1, + ignore_case : 1, detached : 1; unsigned int kind, lines; @@ -107,4 +109,11 @@ 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); +/* + * Print a single ref, outside of any ref-filter. Note that the + * name must be a fully qualified refname. + */ +void pretty_print_ref(const char *name, const unsigned char *sha1, + const char *format); + #endif /* REF_FILTER_H */ @@ -255,6 +255,7 @@ static void read_remotes_file(struct remote *remote) if (!f) return; + remote->configured_in_repo = 1; remote->origin = REMOTE_REMOTES; while (strbuf_getline(&buf, f) != EOF) { const char *v; @@ -289,6 +290,7 @@ static void read_branches_file(struct remote *remote) return; } + remote->configured_in_repo = 1; remote->origin = REMOTE_BRANCHES; /* @@ -371,6 +373,8 @@ static int handle_config(const char *key, const char *value, void *cb) } remote = make_remote(name, namelen); remote->origin = REMOTE_CONFIG; + if (current_config_scope() == CONFIG_SCOPE_REPO) + remote->configured_in_repo = 1; if (!strcmp(subkey, "mirror")) remote->mirror = git_config_bool(key, value); else if (!strcmp(subkey, "skipdefaultupdate")) @@ -714,9 +718,13 @@ struct remote *pushremote_get(const char *name) return remote_get_1(name, pushremote_for_branch); } -int remote_is_configured(struct remote *remote) +int remote_is_configured(struct remote *remote, int in_repo) { - return remote && remote->origin; + if (!remote) + return 0; + if (in_repo) + return remote->configured_in_repo; + return !!remote->origin; } int for_each_remote(each_remote_fn fn, void *priv) @@ -15,7 +15,7 @@ struct remote { struct hashmap_entry ent; /* must be first */ const char *name; - int origin; + int origin, configured_in_repo; const char *foreign_vcs; @@ -60,7 +60,7 @@ struct remote { struct remote *remote_get(const char *name); struct remote *pushremote_get(const char *name); -int remote_is_configured(struct remote *remote); +int remote_is_configured(struct remote *remote, int in_repo); typedef int each_remote_fn(struct remote *remote, void *priv); int for_each_remote(each_remote_fn fn, void *priv); @@ -708,7 +708,7 @@ static void update_paths(struct string_list *update) { int i; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); for (i = 0; i < update->nr; i++) { struct string_list_item *item = &update->items[i]; diff --git a/run-command.c b/run-command.c index ca905a9e80..5227f78aea 100644 --- a/run-command.c +++ b/run-command.c @@ -29,6 +29,8 @@ static int installed_child_cleanup_handler; static void cleanup_children(int sig, int in_signal) { + struct child_to_clean *children_to_wait_for = NULL; + while (children_to_clean) { struct child_to_clean *p = children_to_clean; children_to_clean = p->next; @@ -45,6 +47,23 @@ static void cleanup_children(int sig, int in_signal) } kill(p->pid, sig); + + if (p->process->wait_after_clean) { + p->next = children_to_wait_for; + children_to_wait_for = p; + } else { + if (!in_signal) + free(p); + } + } + + while (children_to_wait_for) { + struct child_to_clean *p = children_to_wait_for; + children_to_wait_for = p->next; + + while (waitpid(p->pid, NULL, 0) < 0 && errno == EINTR) + ; /* spin waiting for process exit or error */ + if (!in_signal) free(p); } @@ -852,8 +871,14 @@ const char *find_hook(const char *name) strbuf_reset(&path); strbuf_git_path(&path, "hooks/%s", name); - if (access(path.buf, X_OK) < 0) + if (access(path.buf, X_OK) < 0) { +#ifdef STRIP_EXTENSION + strbuf_addstr(&path, STRIP_EXTENSION); + if (access(path.buf, X_OK) >= 0) + return path.buf; +#endif return NULL; + } return path.buf; } diff --git a/run-command.h b/run-command.h index dd1c78c28d..4fa8f65adb 100644 --- a/run-command.h +++ b/run-command.h @@ -43,6 +43,7 @@ struct child_process { unsigned stdout_to_stderr:1; unsigned use_shell:1; unsigned clean_on_exit:1; + unsigned wait_after_clean:1; void (*clean_on_exit_handler)(struct child_process *process); void *clean_on_exit_handler_cbdata; }; diff --git a/sequencer.c b/sequencer.c index 0b78f3149f..1f729b053b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,6 +16,9 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" +#include "trailer.h" +#include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -29,6 +32,59 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The file to keep track of how many commands were already processed (e.g. + * for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +/* + * The file to keep track of how many commands are to be processed in total + * (e.g. for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -36,51 +92,60 @@ static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") /* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the abbreviated commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") +/* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") -/* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } -static int is_rfc2822_line(const char *buf, int len) -{ - int i; - - for (i = 0; i < len; i++) { - int ch = buf[i]; - if (ch == ':') - return 1; - if (!isalnum(ch) && ch != '-') - break; - } - - return 0; -} - -static int is_cherry_picked_from_line(const char *buf, int len) -{ - /* - * We only care that it looks roughly like (cherry picked from ...) - */ - return len > strlen(cherry_picked_prefix) + 1 && - starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')'; -} - /* * Returns 0 for non-conforming footer * Returns 1 for conforming footer @@ -90,49 +155,25 @@ static int is_cherry_picked_from_line(const char *buf, int len) static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, int ignore_footer) { - char prev; - int i, k; - int len = sb->len - ignore_footer; - const char *buf = sb->buf; - int found_sob = 0; + struct trailer_info info; + int i; + int found_sob = 0, found_sob_last = 0; - /* footer must end with newline */ - if (!len || buf[len - 1] != '\n') - return 0; + trailer_info_get(&info, sb->buf); - prev = '\0'; - for (i = len - 1; i > 0; i--) { - char ch = buf[i]; - if (prev == '\n' && ch == '\n') /* paragraph break */ - break; - prev = ch; - } - - /* require at least one blank line */ - if (prev != '\n' || buf[i] != '\n') + if (info.trailer_start == info.trailer_end) return 0; - /* advance to start of last paragraph */ - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - int found_rfc2822; - - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; + for (i = 0; i < info.trailer_nr; i++) + if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) { + found_sob = 1; + if (i == info.trailer_nr - 1) + found_sob_last = 1; + } - found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); - if (found_rfc2822 && sob && - !strncmp(buf + i, sob->buf, sob->len)) - found_sob = k; + trailer_info_release(&info); - if (!(found_rfc2822 || - is_cherry_picked_from_line(buf + i, k - i - 1))) - return 0; - } - if (found_sob == i) + if (found_sob_last) return 3; if (found_sob) return 2; @@ -169,7 +210,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -386,7 +435,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, char **xopt; static struct lock_file index_lock; - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); read_cache(); @@ -394,6 +443,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; + if (is_rebase_i(opts)) + o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -405,13 +456,17 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); + if (is_rebase_i(opts) && clean <= 0) + fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -456,19 +511,64 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + int res; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); + strbuf_release(&buf); + return res; +} + /* - * Read the author-script file into an environment block, ready for use in - * run_command(), that can be free()d afterwards. + * Read a list of environment variable assignments (such as the author-script + * file) into an environment block. Returns -1 on error, 0 otherwise. */ -static char **read_author_script(void) +static int read_env_script(struct argv_array *env) { struct strbuf script = STRBUF_INIT; int i, count = 0; - char *p, *p2, **env; - size_t env_size; + char *p, *p2; if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) - return NULL; + return -1; for (p = script.buf; *p; p++) if (skip_prefix(p, "'\\\\''", (const char **)&p2)) @@ -480,19 +580,12 @@ static char **read_author_script(void) count++; } - env_size = (count + 1) * sizeof(*env); - strbuf_grow(&script, env_size); - memmove(script.buf + env_size, script.buf, script.len); - p = script.buf + env_size; - env = (char **)strbuf_detach(&script, NULL); - - for (i = 0; i < count; i++) { - env[i] = p; + for (i = 0, p = script.buf; i < count; i++) { + argv_array_push(env, p); p += strlen(p) + 1; } - env[count] = NULL; - return env; + return 0; } static const char staged_changes_advice[] = @@ -525,14 +618,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, int allow_empty, int edit, int amend, int cleanup_commit_message) { - char **env = NULL; - struct argv_array array; - int rc; + struct child_process cmd = CHILD_PROCESS_INIT; const char *value; + cmd.git_cmd = 1; + if (is_rebase_i(opts)) { - env = read_author_script(); - if (!env) { + if (!edit) { + cmd.stdout_to_stderr = 1; + cmd.err = -1; + } + + if (read_env_script(&cmd.env_array)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); return error(_(staged_changes_advice), @@ -540,39 +637,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, } } - argv_array_init(&array); - argv_array_push(&array, "commit"); - argv_array_push(&array, "-n"); + argv_array_push(&cmd.args, "commit"); + argv_array_push(&cmd.args, "-n"); if (amend) - argv_array_push(&array, "--amend"); + argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) - argv_array_pushf(&array, "-S%s", opts->gpg_sign); + argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); if (opts->signoff) - argv_array_push(&array, "-s"); + argv_array_push(&cmd.args, "-s"); if (defmsg) - argv_array_pushl(&array, "-F", defmsg, NULL); + argv_array_pushl(&cmd.args, "-F", defmsg, NULL); if (cleanup_commit_message) - argv_array_push(&array, "--cleanup=strip"); + argv_array_push(&cmd.args, "--cleanup=strip"); if (edit) - argv_array_push(&array, "-e"); + argv_array_push(&cmd.args, "-e"); else if (!cleanup_commit_message && !opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); + argv_array_push(&cmd.args, "--cleanup=verbatim"); if (allow_empty) - argv_array_push(&array, "--allow-empty"); + argv_array_push(&cmd.args, "--allow-empty"); if (opts->allow_empty_message) - argv_array_push(&array, "--allow-empty-message"); + argv_array_push(&cmd.args, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, - (const char *const *)env); - argv_array_clear(&array); - free(env); + if (cmd.err == -1) { + /* hide stderr on success */ + struct strbuf buf = STRBUF_INIT; + int rc = pipe_command(&cmd, + NULL, 0, + /* stdout is already redirected */ + NULL, 0, + &buf, 0); + if (rc) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return rc; + } - return rc; + return run_command(&cmd); } static int is_original_commit_empty(struct commit *commit) @@ -633,33 +738,202 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * below, it is also divided into several sections that matter. When adding + * new commands, make sure you add it in the right section. + */ enum todo_command { + /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT + TODO_REVERT, + TODO_EDIT, + TODO_REWORD, + TODO_FIXUP, + TODO_SQUASH, + /* commands that do something else than handling a single commit */ + TODO_EXEC, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP, + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; -static const char *todo_command_strings[] = { - "pick", - "revert" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'r', "reword" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" }, + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if (command < TODO_COMMENT) + return todo_command_info[command].str; die("Unknown command: %d", command); } +static int is_noop(const enum todo_command command) +{ + return TODO_NOOP <= command; +} + +static int is_fixup(enum todo_command command) +{ + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count, res; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + struct strbuf header = STRBUF_INIT; + char *eol, *p; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("could not read '%s'"), + rebase_path_squash_msg()); + + p = buf.buf + 1; + eol = strchrnul(buf.buf, '\n'); + if (buf.buf[0] != comment_line_char || + (p += strcspn(p, "0123456789\n")) == eol) + return error(_("unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + count = strtol(p, NULL, 10); + + if (count < 1) + return error(_("invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + + strbuf_addf(&header, "%c ", comment_line_char); + strbuf_addf(&header, + _("This is a combination of %d commits."), ++count); + strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); + strbuf_release(&header); + } else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + return error(_("could not read HEAD's commit message")); + + find_commit_subject(head_message, &body); + if (write_message(body, strlen(body), + rebase_path_fixup_msg(), 0)) { + unuse_commit_buffer(head_commit, head_message); + return error(_("cannot write '%s'"), + rebase_path_fixup_msg()); + } + + count = 2; + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, _("This is a combination of %d commits."), + count); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addstr(&buf, _("This is the 1st commit message:")); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("could not read commit message of %s"), + oid_to_hex(&commit->object.oid)); + find_commit_subject(message, &body); + + if (command == TODO_SQUASH) { + unlink(rebase_path_fixup_msg()); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("This is the commit message #%d:"), count); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + } else if (command == TODO_FIXUP) { + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("The commit message #%d will be skipped:"), + count); + strbuf_addstr(&buf, "\n\n"); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } else + return error(_("unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); + strbuf_release(&buf); + return res; +} + +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } + strbuf_release(&buf); +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) + struct replay_opts *opts, int final_fixup) { + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -679,9 +953,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } discard_cache(); - if (!commit->parents) { + if (!commit->parents) parent = NULL; - } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; @@ -705,11 +978,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && - ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); + if (get_message(commit, &msg) != 0) + return error(_("cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && + ((parent && !hashcmp(parent->object.oid.hash, head)) || + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res = fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be a "todo" command like "revert" or "pick", the second %s a SHA1. */ @@ -717,10 +1002,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -751,14 +1032,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, next = commit; next_label = msg.label; - /* - * Append the commit log message to msgbuf; it starts - * after the tree, parent, author, committer - * information followed by "\n\n". - */ - p = strstr(msg.message, "\n\n"); - if (p) - strbuf_addstr(&msgbuf, skip_blank_lines(p + 2)); + /* Append the commit log message to msgbuf. */ + if (find_commit_subject(msg.message, &p)) + strbuf_addstr(&msgbuf, p); if (opts->record_origin) { if (!has_conforming_footer(&msgbuf, NULL, 0)) @@ -769,7 +1045,32 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res = -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) @@ -824,8 +1125,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); +fast_forward_edit: + res = run_git_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -884,6 +1191,7 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; + int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -911,20 +1219,45 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_COMMENT; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + + for (i = 0; i < TODO_COMMENT; i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { + item->command = i; + break; + } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + if (i >= TODO_COMMENT) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -945,7 +1278,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -960,14 +1293,32 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error(_("cannot '%s' without a previous commit"), + command_to_string(item->command)); + else if (!is_noop(item->command)) + fixup_okay = 1; } - if (!todo_list->nr) - return error(_("no commits parsed.")); + return res; } +static int count_commands(struct todo_list *todo_list) +{ + int count = 0, i; + + for (i = 0; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_COMMENT) + count++; + + return count; +} + static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -985,8 +1336,16 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) + if (res) { + if (is_rebase_i(opts)) + return error(_("please fix this using " + "'git rebase --edit-todo'.")); return error(_("unusable instruction sheet: '%s'"), todo_file); + } + + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("no commits parsed.")); if (!is_rebase_i(opts)) { enum todo_command valid = @@ -1002,6 +1361,26 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("cannot revert during a cherry-pick.")); } + if (is_rebase_i(opts)) { + struct todo_list done = TODO_LIST_INIT; + FILE *f = fopen(rebase_path_msgtotal(), "w"); + + if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && + !parse_insn_buffer(done.buf.buf, &done)) + todo_list->done_nr = count_commands(&done); + else + todo_list->done_nr = 0; + + todo_list->total_nr = todo_list->done_nr + + count_commands(todo_list); + todo_list_release(&done); + + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } + } + return 0; } @@ -1050,6 +1429,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } +static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) +{ + int i; + + strbuf_reset(buf); + if (!read_oneliner(buf, rebase_path_strategy(), 0)) + return; + opts->strategy = strbuf_detach(buf, NULL); + if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) + return; + + opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) { + const char *arg = opts->xopts[i]; + + skip_prefix(arg, "--", &arg); + opts->xopts[i] = xstrdup(arg); + } +} + static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@ -1063,6 +1462,11 @@ static int read_populate_opts(struct replay_opts *opts) opts->gpg_sign = xstrdup(buf.buf + 2); } } + + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + + read_strategy_opts(opts, &buf); strbuf_release(&buf); return 0; @@ -1087,7 +1491,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) @@ -1118,8 +1522,7 @@ static int create_seq_dir(void) error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; - } - else if (mkdir(git_path_seq_dir(), 0777) < 0) + } else if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; @@ -1252,6 +1655,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + /* + * rebase -i writes "git-rebase-todo" without the currently executing + * command, appending it to "done" instead. + */ + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); @@ -1262,6 +1672,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (fd >= 0 && offset > prev_offset && + write_in_full(fd, todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) { + close(fd); + return error_errno(_("could not write to '%s'"), + done_path); + } + if (fd >= 0) + close(fd); + } return 0; } @@ -1300,9 +1727,228 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *subject, *p; + int res = 0; + + p = short_commit_name(commit); + if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno(_("could not open '%s'"), buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + const char *commit_buffer = get_commit_buffer(commit, NULL); + find_commit_subject(commit_buffer, &subject); + res |= write_message(subject, strlen(subject), buf.buf, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + char *p; + + if (get_sha1("HEAD", head)) + return error(_("cannot read HEAD")); + + p = sha1_to_hex(head); + return write_message(p, strlen(p), rebase_path_amend(), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error(_("could not copy '%s' to '%s'"), + rebase_path_message(), git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning(_("execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), + command_line, + dirty ? N_("and made changes to the index and/or the " + "working tree\n") : ""); + if (status == 127) + /* command not found */ + status = 1; + } else if (dirty) { + warning(_("execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), command_line); + status = 1; + } + + return status; +} + +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (!is_noop(todo_list->items[i].command)) + break; + return 1; +} + +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (!is_noop(todo_list->items[i].command)) + return todo_list->items[i].command; + + return -1; +} + +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1315,12 +1961,179 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (is_rebase_i(opts)) { + if (item->command != TODO_COMMENT) { + FILE *f = fopen(rebase_path_msgnum(), "w"); + + todo_list->done_nr++; + + if (f) { + fprintf(f, "%d\n", todo_list->done_nr); + fclose(f); + } + fprintf(stderr, "Rebasing (%d/%d)%s", + todo_list->done_nr, + todo_list->total_nr, + opts->verbose ? "\n" : "\r"); + } + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); + res = do_pick_commit(item->command, item->commit, + opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning(_("stopped at %s... %.*s"), + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); + } else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; + } else if (!is_noop(item->command)) + return error(_("unknown command %d"), item->command); + todo_list->current++; if (res) return res; } + if (is_rebase_i(opts)) { + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; + + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + const char *msg; + unsigned char head[20], orig[20]; + int res; + + if (get_sha1("HEAD", head)) { + res = error(_("cannot read HEAD")); +cleanup_head_ref: + strbuf_release(&head_ref); + strbuf_release(&buf); + return res; + } + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) { + res = error(_("could not read orig-head")); + goto cleanup_head_ref; + } + if (!read_oneliner(&buf, rebase_path_onto(), 0)) { + res = error(_("could not read 'onto'")); + goto cleanup_head_ref; + } + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { + res = error(_("could not update %s"), + head_ref.buf); + goto cleanup_head_ref; + } + msg = reflog_message(opts, "finish", "returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, msg)) { + res = error(_("could not update HEAD to %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + } + + if (opts->verbose) { + struct rev_info log_tree_opt; + struct object_id orig, head; + + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = + DIFF_FORMAT_DIFFSTAT; + log_tree_opt.disable_stdin = 1; + + if (read_oneliner(&buf, rebase_path_orig_head(), 0) && + !get_sha1(buf.buf, orig.hash) && + !get_sha1("HEAD", head.hash)) { + diff_tree_sha1(orig.hash, head.hash, + "", &log_tree_opt.diffopt); + log_tree_diff_flush(&log_tree_opt); + } + } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + hook.stdout_to_stderr = 1; + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } + } + apply_autostash(opts); + + fprintf(stderr, "Successfully rebased and updated %s.\n", + head_ref.buf); + + strbuf_release(&buf); + strbuf_release(&head_ref); + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory @@ -1338,6 +2151,47 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); + return 0; + } + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error(_("cannot amend non-existing commit")); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error(_("invalid file: '%s'"), rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error(_("invalid contents: '%s'"), + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error(_("\nYou have uncommitted changes in your " + "working tree. Please, commit them\n" + "first and then run 'git rebase " + "--continue' again.")); + + strbuf_release(&rev); + amend = 1; + } + + if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error(_("could not commit staged changes.")); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1346,25 +2200,39 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - if (!file_exists(get_todo_path(opts))) + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; + } else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) && + !get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, peek_command(&todo_list, 0)); + strbuf_release(&buf); } - if (index_differs_from("HEAD", 0, 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); @@ -1375,7 +2243,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) diff --git a/sequencer.h b/sequencer.h index 7a513c576b..f885b68395 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { @@ -23,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; @@ -256,8 +256,10 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir) strbuf_addbuf(&path, &data); strbuf_addstr(sb, real_path(path.buf)); ret = 1; - } else + } else { strbuf_addstr(sb, gitdir); + } + strbuf_release(&data); strbuf_release(&path); return ret; @@ -692,7 +694,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { if (offset != cwd->len && !is_absolute_path(gitdir)) - gitdir = xstrdup(real_path(gitdir)); + gitdir = real_pathdup(gitdir); if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); return setup_explicit_git_dir(gitdir, cwd, nongit_ok); @@ -800,11 +802,12 @@ static int canonicalize_ceiling_entry(struct string_list_item *item, /* Keep entry but do not canonicalize it */ return 1; } else { - const char *real_path = real_path_if_valid(ceil); - if (!real_path) + char *real_path = real_pathdup(ceil); + if (!real_path) { return 0; + } free(item->string); - item->string = xstrdup(real_path); + item->string = real_path; return 1; } } diff --git a/sha1_file.c b/sha1_file.c index 1173071859..ec957db5e1 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -28,14 +28,6 @@ #include "mergesort.h" #include "quote.h" -#ifndef O_NOATIME -#if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) -#define O_NOATIME 01000000 -#else -#define O_NOATIME 0 -#endif -#endif - #define SZ_FMT PRIuMAX static inline uintmax_t sz_fmt(size_t s) { return s; } @@ -292,7 +284,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base, struct strbuf pathbuf = STRBUF_INIT; if (!is_absolute_path(entry) && relative_base) { - strbuf_addstr(&pathbuf, real_path(relative_base)); + strbuf_realpath(&pathbuf, relative_base, 1); strbuf_addch(&pathbuf, '/'); } strbuf_addstr(&pathbuf, entry); @@ -1611,66 +1603,81 @@ int check_sha1_signature(const unsigned char *sha1, void *map, return hashcmp(sha1, real_sha1) ? -1 : 0; } -int git_open(const char *name) +int git_open_cloexec(const char *name, int flags) { - static int sha1_file_open_flag = O_NOATIME | O_CLOEXEC; - - for (;;) { - int fd; - - errno = 0; - fd = open(name, O_RDONLY | sha1_file_open_flag); - if (fd >= 0) - return fd; + int fd; + static int o_cloexec = O_CLOEXEC; + fd = open(name, flags | o_cloexec); + if ((o_cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { /* Try again w/o O_CLOEXEC: the kernel might not support it */ - if ((sha1_file_open_flag & O_CLOEXEC) && errno == EINVAL) { - sha1_file_open_flag &= ~O_CLOEXEC; - continue; - } + o_cloexec &= ~O_CLOEXEC; + fd = open(name, flags | o_cloexec); + } - /* Might the failure be due to O_NOATIME? */ - if (errno != ENOENT && (sha1_file_open_flag & O_NOATIME)) { - sha1_file_open_flag &= ~O_NOATIME; - continue; +#if defined(F_GETFL) && defined(F_SETFL) && defined(FD_CLOEXEC) + { + static int fd_cloexec = FD_CLOEXEC; + + if (!o_cloexec && 0 <= fd && fd_cloexec) { + /* Opened w/o O_CLOEXEC? try with fcntl(2) to add it */ + int flags = fcntl(fd, F_GETFL); + if (fcntl(fd, F_SETFL, flags | fd_cloexec)) + fd_cloexec = 0; } - return -1; } +#endif + return fd; } -static int stat_sha1_file(const unsigned char *sha1, struct stat *st) +/* + * Find "sha1" as a loose object in the local repository or in an alternate. + * Returns 0 on success, negative on failure. + * + * The "path" out-parameter will give the path of the object we found (if any). + * Note that it may point to static storage and is only valid until another + * call to sha1_file_name(), etc. + */ +static int stat_sha1_file(const unsigned char *sha1, struct stat *st, + const char **path) { struct alternate_object_database *alt; - if (!lstat(sha1_file_name(sha1), st)) + *path = sha1_file_name(sha1); + if (!lstat(*path, st)) return 0; prepare_alt_odb(); errno = ENOENT; for (alt = alt_odb_list; alt; alt = alt->next) { - const char *path = alt_sha1_path(alt, sha1); - if (!lstat(path, st)) + *path = alt_sha1_path(alt, sha1); + if (!lstat(*path, st)) return 0; } return -1; } -static int open_sha1_file(const unsigned char *sha1) +/* + * Like stat_sha1_file(), but actually open the object and return the + * descriptor. See the caveats on the "path" parameter above. + */ +static int open_sha1_file(const unsigned char *sha1, const char **path) { int fd; struct alternate_object_database *alt; int most_interesting_errno; - fd = git_open(sha1_file_name(sha1)); + *path = sha1_file_name(sha1); + fd = git_open(*path); if (fd >= 0) return fd; most_interesting_errno = errno; prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { - const char *path = alt_sha1_path(alt, sha1); - fd = git_open(path); + *path = alt_sha1_path(alt, sha1); + fd = git_open(*path); if (fd >= 0) return fd; if (most_interesting_errno == ENOENT) @@ -1680,12 +1687,21 @@ static int open_sha1_file(const unsigned char *sha1) return -1; } -void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +/* + * Map the loose object at "path" if it is not NULL, or the path found by + * searching for a loose object named "sha1". + */ +static void *map_sha1_file_1(const char *path, + const unsigned char *sha1, + unsigned long *size) { void *map; int fd; - fd = open_sha1_file(sha1); + if (path) + fd = git_open(path); + else + fd = open_sha1_file(sha1, &path); map = NULL; if (fd >= 0) { struct stat st; @@ -1694,7 +1710,7 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) *size = xsize_t(st.st_size); if (!*size) { /* mmap() is forbidden on empty files */ - error("object file %s is empty", sha1_file_name(sha1)); + error("object file %s is empty", path); return NULL; } map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -1704,6 +1720,11 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +{ + return map_sha1_file_1(NULL, sha1, size); +} + unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { @@ -2350,11 +2371,10 @@ static inline void release_delta_base_cache(struct delta_base_cache_entry *ent) void clear_delta_base_cache(void) { - struct hashmap_iter iter; - struct delta_base_cache_entry *entry; - for (entry = hashmap_iter_first(&delta_base_cache, &iter); - entry; - entry = hashmap_iter_next(&iter)) { + struct list_head *lru, *tmp; + list_for_each_safe(lru, tmp, &delta_base_cache_lru) { + struct delta_base_cache_entry *entry = + list_entry(lru, struct delta_base_cache_entry, lru); release_delta_base_cache(entry); } } @@ -2814,8 +2834,9 @@ static int sha1_loose_object_info(const unsigned char *sha1, * object even exists. */ if (!oi->typep && !oi->typename && !oi->sizep) { + const char *path; struct stat st; - if (stat_sha1_file(sha1, &st) < 0) + if (stat_sha1_file(sha1, &st, &path) < 0) return -1; if (oi->disk_sizep) *oi->disk_sizep = st.st_size; @@ -3011,6 +3032,8 @@ void *read_sha1_file_extended(const unsigned char *sha1, { void *data; const struct packed_git *p; + const char *path; + struct stat st; const unsigned char *repl = lookup_replace_object_extended(sha1, flag); errno = 0; @@ -3026,12 +3049,9 @@ void *read_sha1_file_extended(const unsigned char *sha1, die("replacement %s not found for %s", sha1_to_hex(repl), sha1_to_hex(sha1)); - if (has_loose_object(repl)) { - const char *path = sha1_file_name(sha1); - + if (!stat_sha1_file(repl, &st, &path)) die("loose object %s (stored in %s) is corrupt", sha1_to_hex(repl), path); - } if ((p = has_packed_and_bad(repl)) != NULL) die("packed object %s (stored in %s) is corrupt", @@ -3801,3 +3821,122 @@ int for_each_packed_object(each_packed_object_fn cb, void *data, unsigned flags) } return r ? r : pack_errors; } + +static int check_stream_sha1(git_zstream *stream, + const char *hdr, + unsigned long size, + const char *path, + const unsigned char *expected_sha1) +{ + git_SHA_CTX c; + unsigned char real_sha1[GIT_SHA1_RAWSZ]; + unsigned char buf[4096]; + unsigned long total_read; + int status = Z_OK; + + git_SHA1_Init(&c); + git_SHA1_Update(&c, hdr, stream->total_out); + + /* + * We already read some bytes into hdr, but the ones up to the NUL + * do not count against the object's content size. + */ + total_read = stream->total_out - strlen(hdr) - 1; + + /* + * This size comparison must be "<=" to read the final zlib packets; + * see the comment in unpack_sha1_rest for details. + */ + while (total_read <= size && + (status == Z_OK || status == Z_BUF_ERROR)) { + stream->next_out = buf; + stream->avail_out = sizeof(buf); + if (size - total_read < stream->avail_out) + stream->avail_out = size - total_read; + status = git_inflate(stream, Z_FINISH); + git_SHA1_Update(&c, buf, stream->next_out - buf); + total_read += stream->next_out - buf; + } + git_inflate_end(stream); + + if (status != Z_STREAM_END) { + error("corrupt loose object '%s'", sha1_to_hex(expected_sha1)); + return -1; + } + if (stream->avail_in) { + error("garbage at end of loose object '%s'", + sha1_to_hex(expected_sha1)); + return -1; + } + + git_SHA1_Final(real_sha1, &c); + if (hashcmp(expected_sha1, real_sha1)) { + error("sha1 mismatch for %s (expected %s)", path, + sha1_to_hex(expected_sha1)); + return -1; + } + + return 0; +} + +int read_loose_object(const char *path, + const unsigned char *expected_sha1, + enum object_type *type, + unsigned long *size, + void **contents) +{ + int ret = -1; + int fd = -1; + void *map = NULL; + unsigned long mapsize; + git_zstream stream; + char hdr[32]; + + *contents = NULL; + + map = map_sha1_file_1(path, NULL, &mapsize); + if (!map) { + error_errno("unable to mmap %s", path); + goto out; + } + + if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) { + error("unable to unpack header of %s", path); + goto out; + } + + *type = parse_sha1_header(hdr, size); + if (*type < 0) { + error("unable to parse header of %s", path); + git_inflate_end(&stream); + goto out; + } + + if (*type == OBJ_BLOB) { + if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0) + goto out; + } else { + *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1); + if (!*contents) { + error("unable to unpack contents of %s", path); + git_inflate_end(&stream); + goto out; + } + if (check_sha1_signature(expected_sha1, *contents, + *size, typename(*type))) { + error("sha1 mismatch for %s (expected %s)", path, + sha1_to_hex(expected_sha1)); + free(*contents); + goto out; + } + } + + ret = 0; /* everything checks out */ + +out: + if (map) + munmap(map, mapsize); + if (fd >= 0) + close(fd); + return ret; +} diff --git a/shortlog.h b/shortlog.h index 5a326c6860..5d64cfe929 100644 --- a/shortlog.h +++ b/shortlog.h @@ -13,6 +13,7 @@ struct shortlog { int in2; int user_format; int abbrev; + int committer; char *common_repo_prefix; int email; diff --git a/string-list.c b/string-list.c index 8c83cac189..45016ad86d 100644 --- a/string-list.c +++ b/string-list.c @@ -211,21 +211,18 @@ struct string_list_item *string_list_append(struct string_list *list, list->strdup_strings ? xstrdup(string) : (char *)string); } -/* Yuck */ -static compare_strings_fn compare_for_qsort; - -/* Only call this from inside string_list_sort! */ -static int cmp_items(const void *a, const void *b) +static int cmp_items(const void *a, const void *b, void *ctx) { + compare_strings_fn cmp = ctx; const struct string_list_item *one = a; const struct string_list_item *two = b; - return compare_for_qsort(one->string, two->string); + return cmp(one->string, two->string); } void string_list_sort(struct string_list *list) { - compare_for_qsort = list->cmp ? list->cmp : strcmp; - QSORT(list->items, list->nr, cmp_items); + QSORT_S(list->items, list->nr, cmp_items, + list->cmp ? list->cmp : strcmp); } struct string_list_item *unsorted_string_list_lookup(struct string_list *list, diff --git a/submodule-config.c b/submodule-config.c index 098085be69..93453909cf 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -251,6 +251,8 @@ static int parse_push_recurse(const char *opt, const char *arg, return RECURSE_SUBMODULES_ON_DEMAND; else if (!strcmp(arg, "check")) return RECURSE_SUBMODULES_CHECK; + else if (!strcmp(arg, "only")) + return RECURSE_SUBMODULES_ONLY; else if (die_on_error) die("bad %s argument: %s", opt, arg); else @@ -263,12 +265,12 @@ int parse_push_recurse_submodules_arg(const char *opt, const char *arg) return parse_push_recurse(opt, arg, 1); } -static void warn_multiple_config(const unsigned char *commit_sha1, +static void warn_multiple_config(const unsigned char *treeish_name, const char *name, const char *option) { const char *commit_string = "WORKTREE"; - if (commit_sha1) - commit_string = sha1_to_hex(commit_sha1); + if (treeish_name) + commit_string = sha1_to_hex(treeish_name); warning("%s:.gitmodules, multiple configurations found for " "'submodule.%s.%s'. Skipping second one!", commit_string, name, option); @@ -276,7 +278,7 @@ static void warn_multiple_config(const unsigned char *commit_sha1, struct parse_config_parameter { struct submodule_cache *cache; - const unsigned char *commit_sha1; + const unsigned char *treeish_name; const unsigned char *gitmodules_sha1; int overwrite; }; @@ -300,7 +302,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->path) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "path"); else { if (submodule->path) @@ -314,7 +316,7 @@ static int parse_config(const char *var, const char *value, void *data) int die_on_error = is_null_sha1(me->gitmodules_sha1); if (!me->overwrite && submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "fetchrecursesubmodules"); else submodule->fetch_recurse = parse_fetch_recurse( @@ -324,7 +326,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->ignore) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "ignore"); else if (strcmp(value, "untracked") && strcmp(value, "dirty") && @@ -340,7 +342,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!value) { ret = config_error_nonbool(var); } else if (!me->overwrite && submodule->url) { - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "url"); } else { free((void *) submodule->url); @@ -351,21 +353,21 @@ static int parse_config(const char *var, const char *value, void *data) ret = config_error_nonbool(var); else if (!me->overwrite && submodule->update_strategy.type != SM_UPDATE_UNSPECIFIED) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "update"); else if (parse_submodule_update_strategy(value, &submodule->update_strategy) < 0) die(_("invalid value for %s"), var); } else if (!strcmp(item.buf, "shallow")) { if (!me->overwrite && submodule->recommend_shallow != -1) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "shallow"); else submodule->recommend_shallow = git_config_bool(var, value); } else if (!strcmp(item.buf, "branch")) { if (!me->overwrite && submodule->branch) - warn_multiple_config(me->commit_sha1, submodule->name, + warn_multiple_config(me->treeish_name, submodule->name, "branch"); else { free((void *)submodule->branch); @@ -379,18 +381,18 @@ static int parse_config(const char *var, const char *value, void *data) return ret; } -static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, +int gitmodule_sha1_from_commit(const unsigned char *treeish_name, unsigned char *gitmodules_sha1, struct strbuf *rev) { int ret = 0; - if (is_null_sha1(commit_sha1)) { + if (is_null_sha1(treeish_name)) { hashclr(gitmodules_sha1); return 1; } - strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(commit_sha1)); + strbuf_addf(rev, "%s:.gitmodules", sha1_to_hex(treeish_name)); if (get_sha1(rev->buf, gitmodules_sha1) >= 0) ret = 1; @@ -402,7 +404,7 @@ static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, * revisions. */ static const struct submodule *config_from(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *key, + const unsigned char *treeish_name, const char *key, enum lookup_type lookup_type) { struct strbuf rev = STRBUF_INIT; @@ -418,7 +420,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, * return the first submodule. Can be used to check whether * there are any submodules parsed. */ - if (!commit_sha1 || !key) { + if (!treeish_name || !key) { struct hashmap_iter iter; struct submodule_entry *entry; @@ -428,7 +430,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, return entry->config; } - if (!gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) + if (!gitmodule_sha1_from_commit(treeish_name, sha1, &rev)) goto out; switch (lookup_type) { @@ -448,7 +450,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, /* fill the submodule config into the cache */ parameter.cache = cache; - parameter.commit_sha1 = commit_sha1; + parameter.treeish_name = treeish_name; parameter.gitmodules_sha1 = sha1; parameter.overwrite = 0; git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf, @@ -471,18 +473,6 @@ out: return submodule; } -static const struct submodule *config_from_path(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *path) -{ - return config_from(cache, commit_sha1, path, lookup_path); -} - -static const struct submodule *config_from_name(struct submodule_cache *cache, - const unsigned char *commit_sha1, const char *name) -{ - return config_from(cache, commit_sha1, name, lookup_name); -} - static void ensure_cache_init(void) { if (is_cache_init) @@ -496,7 +486,7 @@ int parse_submodule_config_option(const char *var, const char *value) { struct parse_config_parameter parameter; parameter.cache = &the_submodule_cache; - parameter.commit_sha1 = NULL; + parameter.treeish_name = NULL; parameter.gitmodules_sha1 = null_sha1; parameter.overwrite = 1; @@ -504,18 +494,18 @@ int parse_submodule_config_option(const char *var, const char *value) return parse_config(var, value, ¶meter); } -const struct submodule *submodule_from_name(const unsigned char *commit_sha1, +const struct submodule *submodule_from_name(const unsigned char *treeish_name, const char *name) { ensure_cache_init(); - return config_from_name(&the_submodule_cache, commit_sha1, name); + return config_from(&the_submodule_cache, treeish_name, name, lookup_name); } -const struct submodule *submodule_from_path(const unsigned char *commit_sha1, +const struct submodule *submodule_from_path(const unsigned char *treeish_name, const char *path) { ensure_cache_init(); - return config_from_path(&the_submodule_cache, commit_sha1, path); + return config_from(&the_submodule_cache, treeish_name, path, lookup_path); } void submodule_free(void) diff --git a/submodule-config.h b/submodule-config.h index d05c542d2c..70f19363fd 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -25,10 +25,13 @@ struct submodule { 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_sha1, +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_sha1, +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); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index ece17315d6..4c4f033e8a 100644 --- a/submodule.c +++ b/submodule.c @@ -14,6 +14,7 @@ #include "blob.h" #include "thread-utils.h" #include "quote.h" +#include "worktree.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static int parallel_jobs = 1; @@ -198,6 +199,56 @@ void gitmodules_config(void) } } +void gitmodules_config_sha1(const unsigned char *commit_sha1) +{ + struct strbuf rev = STRBUF_INIT; + unsigned char sha1[20]; + + if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) { + git_config_from_blob_sha1(submodule_config, rev.buf, + sha1, NULL); + } + strbuf_release(&rev); +} + +/* + * Determine if a submodule has been initialized at a given 'path' + */ +int is_submodule_initialized(const char *path) +{ + int ret = 0; + const struct submodule *module = NULL; + + module = submodule_from_path(null_sha1, path); + + if (module) { + char *key = xstrfmt("submodule.%s.url", module->name); + char *value = NULL; + + ret = !git_config_get_string(key, &value); + + free(value); + free(key); + } + + return ret; +} + +/* + * Determine if a submodule has been populated at a given 'path' + */ +int is_submodule_populated(const char *path) +{ + int ret = 0; + char *gitdir = xstrfmt("%s/.git", path); + + if (resolve_gitdir(gitdir)) + ret = 1; + + free(gitdir); + return ret; +} + int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst) { @@ -1092,45 +1143,64 @@ int submodule_uses_gitfile(const char *path) return 1; } -int ok_to_remove_submodule(const char *path) +/* + * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data + * when doing so. + * + * Return 1 if we'd lose data, return 0 if the removal is fine, + * and negative values for errors. + */ +int bad_to_remove_submodule(const char *path, unsigned flags) { ssize_t len; struct child_process cp = CHILD_PROCESS_INIT; - const char *argv[] = { - "status", - "--porcelain", - "-u", - "--ignore-submodules=none", - NULL, - }; struct strbuf buf = STRBUF_INIT; - int ok_to_remove = 1; + int ret = 0; if (!file_exists(path) || is_empty_dir(path)) - return 1; + return 0; if (!submodule_uses_gitfile(path)) - return 0; + return 1; + + argv_array_pushl(&cp.args, "status", "--porcelain", + "--ignore-submodules=none", NULL); + + if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED) + argv_array_push(&cp.args, "-uno"); + else + argv_array_push(&cp.args, "-uall"); + + if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)) + argv_array_push(&cp.args, "--ignored"); - cp.argv = argv; prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; cp.dir = path; - if (start_command(&cp)) - die("Could not run 'git status --porcelain -uall --ignore-submodules=none' in submodule %s", path); + if (start_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not start 'git status in submodule '%s'"), + path); + ret = -1; + goto out; + } len = strbuf_read(&buf, cp.out, 1024); if (len > 2) - ok_to_remove = 0; + ret = 1; close(cp.out); - if (finish_command(&cp)) - die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path); - + if (finish_command(&cp)) { + if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR) + die(_("could not run 'git status in submodule '%s'"), + path); + ret = -1; + } +out: strbuf_release(&buf); - return ok_to_remove; + return ret; } static int find_first_merges(struct object_array *result, const char *path, @@ -1296,30 +1366,6 @@ int merge_submodule(unsigned char result[20], const char *path, return 0; } -/* 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 rel_path = STRBUF_INIT; - const char *real_work_tree = xstrdup(real_path(work_tree)); - - /* Update gitfile */ - strbuf_addf(&file_name, "%s/.git", work_tree); - write_file(file_name.buf, "gitdir: %s", - relative_path(git_dir, real_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", - relative_path(real_work_tree, git_dir, - &rel_path)); - - strbuf_release(&file_name); - strbuf_release(&rel_path); - free((void *)real_work_tree); -} - int parallel_submodules(void) { return parallel_jobs; @@ -1333,5 +1379,108 @@ void prepare_submodule_repo_env(struct argv_array *out) if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) argv_array_push(out, *var); } - argv_array_push(out, "GIT_DIR=.git"); + 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. + */ +static void relocate_single_git_dir_into_superproject(const char *prefix, + const char *path) +{ + char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL; + const char *new_git_dir; + const struct submodule *sub; + + if (submodule_uses_worktrees(path)) + die(_("relocate_gitdir for submodule '%s' with " + "more than one worktree not supported"), path); + + old_git_dir = xstrfmt("%s/.git", path); + if (read_gitfile(old_git_dir)) + /* If it is an actual gitfile, it doesn't need migration. */ + return; + + real_old_git_dir = real_pathdup(old_git_dir); + + 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); + + if (!prefix) + prefix = get_super_prefix(); + + fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"), + prefix ? prefix : "", path, + real_old_git_dir, real_new_git_dir); + + relocate_gitdir(path, real_old_git_dir, real_new_git_dir); + + free(old_git_dir); + free(real_old_git_dir); + free(real_new_git_dir); +} + +/* + * Migrate the git directory of the submodule given by path from + * having its git directory within the working tree to the git dir nested + * in its superprojects git dir under modules/. + */ +void absorb_git_dir_into_superproject(const char *prefix, + const char *path, + unsigned flags) +{ + const char *sub_git_dir, *v; + char *real_sub_git_dir = NULL, *real_common_git_dir = NULL; + struct strbuf gitdir = STRBUF_INIT; + + strbuf_addf(&gitdir, "%s/.git", path); + sub_git_dir = resolve_gitdir(gitdir.buf); + + /* Not populated? */ + if (!sub_git_dir) + goto out; + + /* Is it already absorbed into the superprojects git dir? */ + real_sub_git_dir = real_pathdup(sub_git_dir); + real_common_git_dir = real_pathdup(get_git_common_dir()); + if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v)) + relocate_single_git_dir_into_superproject(prefix, path); + + if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) { + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; + + 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, path); + strbuf_addch(&sb, '/'); + + cp.dir = path; + cp.git_cmd = 1; + cp.no_stdin = 1; + argv_array_pushl(&cp.args, "--super-prefix", sb.buf, + "submodule--helper", + "absorb-git-dirs", NULL); + prepare_submodule_repo_env(&cp.env_array); + if (run_command(&cp)) + die(_("could not recurse into submodule '%s'"), path); + + strbuf_release(&sb); + } + +out: + strbuf_release(&gitdir); + free(real_sub_git_dir); + free(real_common_git_dir); } diff --git a/submodule.h b/submodule.h index 23d76682b1..05ab674f06 100644 --- a/submodule.h +++ b/submodule.h @@ -6,6 +6,7 @@ struct argv_array; struct sha1_array; enum { + RECURSE_SUBMODULES_ONLY = -5, RECURSE_SUBMODULES_CHECK = -4, RECURSE_SUBMODULES_ERROR = -3, RECURSE_SUBMODULES_NONE = -2, @@ -30,52 +31,66 @@ struct submodule_update_strategy { }; #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} -int is_staging_gitmodules_ok(void); -int update_path_in_gitmodules(const char *oldpath, const char *newpath); -int remove_path_from_gitmodules(const char *path); -void stage_updated_gitmodules(void); -void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, +extern int is_staging_gitmodules_ok(void); +extern int update_path_in_gitmodules(const char *oldpath, const char *newpath); +extern int remove_path_from_gitmodules(const char *path); +extern void stage_updated_gitmodules(void); +extern void set_diffopt_flags_from_submodule_config(struct diff_options *, const char *path); -int submodule_config(const char *var, const char *value, void *cb); -void gitmodules_config(void); -int parse_submodule_update_strategy(const char *value, +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); +extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); -const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); -void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); -void show_submodule_summary(FILE *f, const char *path, +extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); +extern void handle_ignore_submodules_arg(struct diff_options *, const char *); +extern void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset); -void show_submodule_inline_diff(FILE *f, const char *path, +extern void show_submodule_inline_diff(FILE *f, const char *path, const char *line_prefix, struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset, const struct diff_options *opt); -void set_config_fetch_recurse_submodules(int value); -void check_for_new_submodule_commits(unsigned char new_sha1[20]); -int fetch_populated_submodules(const struct argv_array *options, +extern void set_config_fetch_recurse_submodules(int value); +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, int quiet, int max_parallel_jobs); -unsigned is_submodule_modified(const char *path, int ignore_untracked); -int submodule_uses_gitfile(const char *path); -int ok_to_remove_submodule(const char *path); -int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], - const unsigned char a[20], const unsigned char b[20], int search); -int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, - struct string_list *needs_pushing); +extern unsigned is_submodule_modified(const char *path, int ignore_untracked); +extern int submodule_uses_gitfile(const char *path); + +#define SUBMODULE_REMOVAL_DIE_ON_ERROR (1<<0) +#define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1) +#define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2) +extern int bad_to_remove_submodule(const char *path, unsigned flags); +extern int merge_submodule(unsigned char result[20], const char *path, + const unsigned char base[20], + const unsigned char a[20], + const unsigned char b[20], int search); +extern int find_unpushed_submodules(struct sha1_array *commits, + const char *remotes_name, + struct string_list *needs_pushing); extern int push_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, int dry_run); -void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); -int parallel_submodules(void); +extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +extern int parallel_submodules(void); /* * Prepare the "env_array" parameter of a "struct child_process" for executing * a submodule by clearing any repo-specific envirionment variables, but * retaining any config in the environment. */ -void prepare_submodule_repo_env(struct argv_array *out); +extern void prepare_submodule_repo_env(struct argv_array *out); +#define ABSORB_GITDIR_RECURSE_SUBMODULES (1<<0) +extern void absorb_git_dir_into_superproject(const char *prefix, + const char *path, + unsigned flags); #endif diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 27fe0405b8..d2a63bea43 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -8,7 +8,7 @@ static struct lock_file index_lock; int cmd_main(int ac, const char **av) { setup_git_directory(); - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die("unable to read index file"); active_cache_tree = NULL; diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c index 4a68967bd1..c502fa16d3 100644 --- a/t/helper/test-string-list.c +++ b/t/helper/test-string-list.c @@ -97,6 +97,31 @@ int cmd_main(int argc, const char **argv) return 0; } + if (argc == 2 && !strcmp(argv[1], "sort")) { + struct string_list list = STRING_LIST_INIT_NODUP; + struct strbuf sb = STRBUF_INIT; + struct string_list_item *item; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create a string_list item + * for the empty string after the last separator. + */ + if (sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + string_list_split_in_place(&list, sb.buf, '\n', -1); + + string_list_sort(&list); + + for_each_string_list_item(item, &list) + puts(item->string); + + string_list_clear(&list, 0); + strbuf_release(&sb); + return 0; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh index b0917d93e6..02f49cb409 100644 --- a/t/lib-proto-disable.sh +++ b/t/lib-proto-disable.sh @@ -1,15 +1,12 @@ # Test routines for checking protocol disabling. -# test cloning a particular protocol -# $1 - description of the protocol -# $2 - machine-readable name of the protocol -# $3 - the URL to try cloning -test_proto () { +# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist +test_whitelist () { desc=$1 proto=$2 url=$3 - test_expect_success "clone $1 (enabled)" ' + test_expect_success "clone $desc (enabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=$proto && @@ -18,7 +15,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (enabled)" ' + test_expect_success "fetch $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -27,7 +24,7 @@ test_proto () { ) ' - test_expect_success "push $1 (enabled)" ' + test_expect_success "push $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -36,7 +33,7 @@ test_proto () { ) ' - test_expect_success "push $1 (disabled)" ' + test_expect_success "push $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -45,7 +42,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (disabled)" ' + test_expect_success "fetch $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -54,7 +51,7 @@ test_proto () { ) ' - test_expect_success "clone $1 (disabled)" ' + test_expect_success "clone $desc (disabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=none && @@ -62,6 +59,129 @@ test_proto () { test_must_fail git clone --bare "$url" tmp.git ) ' + + test_expect_success "clone $desc (env var has precedence)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git -c protocol.allow=always clone --bare "$url" tmp.git && + test_must_fail git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ) + ' +} + +test_config () { + desc=$1 + proto=$2 + url=$3 + + # Test clone/fetch/push with protocol.<type>.allow config + test_expect_success "clone $desc (enabled with config)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_must_fail git -c protocol.$proto.allow=never clone --bare "$url" tmp.git + ' + + # Test clone/fetch/push with protocol.user.allow and its env var + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user push origin HEAD:pushed + ) + ' + + test_expect_success "fetch $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user fetch + ) + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + ( + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ) + ' + + # Test clone/fetch/push with protocol.allow user defined default + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + git config --global protocol.allow always && + git clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + git config --global protocol.allow never && + test_must_fail git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_must_fail git -C tmp.git fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_must_fail git clone --bare "$url" tmp.git + ' +} + +# test cloning a particular protocol +# $1 - description of the protocol +# $2 - machine-readable name of the protocol +# $3 - the URL to try cloning +test_proto () { + test_whitelist "$@" + + test_config "$@" } # set up an ssh wrapper that will access $host/$repo in the diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 79cdd34a54..915eb4a7c6 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -69,10 +69,7 @@ create_lib_submodule_repo () { git checkout -b "replace_sub1_with_directory" "add_sub1" && git submodule update && - ( - cd sub1 && - git checkout modifications - ) && + git -C sub1 checkout modifications && git rm --cached sub1 && rm sub1/.git* && git config -f .gitmodules --remove-section "submodule.sub1" && diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh new file mode 100755 index 0000000000..7c9a35a646 --- /dev/null +++ b/t/perf/p0071-sort.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='Basic sort performance tests' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup' ' + git ls-files --stage "*.[ch]" "*.sh" | + cut -f2 -d" " | + git cat-file --batch >unsorted +' + +test_perf 'sort(1)' ' + sort <unsorted >expect +' + +test_perf 'string_list_sort()' ' + test-string-list sort <unsorted >actual +' + +test_expect_success 'string_list_sort() sorts like sort(1)' ' + test_cmp_bin expect actual +' + +test_done diff --git a/t/t0001-init.sh b/t/t0001-init.sh index b8fc588b19..e424de5363 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -258,6 +258,9 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar ( # Leading directories should honor umask while # the repository itself should follow "shared" + mkdir newdir && + # Remove a default ACL if possible. + (setfacl -k newdir 2>/dev/null || true) && umask 002 && git init --bare --shared=0660 newdir/a/b/c && test_path_is_dir newdir/a/b/c/refs && diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 2c10cb4693..2cbf7b9590 100755 --- a/t/t0202/test.pl +++ b/t/t0202/test.pl @@ -4,7 +4,7 @@ use lib (split(/:/, $ENV{GITPERLLIB})); use strict; use warnings; use POSIX qw(:locale_h); -use Test::More tests => 8; +use Test::More tests => 13; use Git::I18N; my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; @@ -31,6 +31,8 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export # more gettext wrapper functions. my %prototypes = (qw( __ $ + __n $$$ + N__ $ )); while (my ($sub, $proto) = each %prototypes) { is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); @@ -46,6 +48,16 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export my ($got, $expect) = (('TEST: A Perl test string') x 2); is(__($got), $expect, "Passing a string through __() in the C locale works"); + + my ($got_singular, $got_plural, $expect_singular, $expect_plural) = + (('TEST: 1 file', 'TEST: n files') x 2); + + is(__n($got_singular, $got_plural, 1), $expect_singular, + "Get singular string through __n() in C locale"); + is(__n($got_singular, $got_plural, 2), $expect_plural, + "Get plural string through __n() in C locale"); + + is(N__($got), $expect, "Passing a string through N__() in the C locale works"); } # Test a basic message on different locales diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 7e10bcfe39..30354fd26c 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -97,6 +97,9 @@ test_expect_success 'show-ref -d' ' git show-ref -d refs/tags/A refs/tags/C >actual && test_cmp expect actual && + git show-ref --verify -d refs/tags/A refs/tags/C >actual && + test_cmp expect actual && + echo $(git rev-parse refs/heads/master) refs/heads/master >expect && git show-ref -d master >actual && test_cmp expect actual && @@ -116,6 +119,12 @@ test_expect_success 'show-ref -d' ' test_cmp expect actual && test_must_fail git show-ref -d --verify heads/master >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d A C >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d tags/A tags/C >actual && test_cmp expect actual ' @@ -164,4 +173,37 @@ test_expect_success 'show-ref --heads, --tags, --head, pattern' ' test_cmp expect actual ' +test_expect_success 'show-ref --verify HEAD' ' + echo $(git rev-parse HEAD) HEAD >expect && + git show-ref --verify HEAD >actual && + test_cmp expect actual && + + >expect && + + git show-ref --verify -q HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'show-ref --verify with dangling ref' ' + sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" + } && + + remove_object() { + file=$(sha1_file "$*") && + test -e "$file" && + rm -f "$file" + } && + + test_when_finished "rm -rf dangling" && + ( + git init dangling && + cd dangling && + test_commit dangling && + sha=$(git rev-parse refs/tags/dangling) && + remove_object $sha && + test_must_fail git show-ref --verify refs/tags/dangling + ) +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index ee7d4736db..33a51c9a67 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -43,13 +43,13 @@ test_expect_success 'HEAD is part of refs, valid objects appear valid' ' test_expect_success 'setup: helpers for corruption tests' ' sha1_file() { - echo "$*" | sed "s#..#.git/objects/&/#" + remainder=${1#??} && + firsttwo=${1%$remainder} && + echo ".git/objects/$firsttwo/$remainder" } && remove_object() { - file=$(sha1_file "$*") && - test -e "$file" && - rm -f "$file" + rm "$(sha1_file "$1")" } ' @@ -189,14 +189,16 @@ test_expect_success 'commit with NUL in header' ' ' test_expect_success 'tree object with duplicate entries' ' - test_when_finished "remove_object \$T" && + test_when_finished "for i in \$T; do remove_object \$i; done" && T=$( GIT_INDEX_FILE=test-index && export GIT_INDEX_FILE && rm -f test-index && >x && git add x && + git rev-parse :x && T=$(git write-tree) && + echo $T && ( git cat-file tree $T && git cat-file tree $T @@ -521,9 +523,21 @@ test_expect_success 'fsck --connectivity-only' ' touch empty && git add empty && test_commit empty && + + # Drop the index now; we want to be sure that we + # recursively notice the broken objects + # because they are reachable from refs, not because + # they are in the index. + rm -f .git/index && + + # corrupt the blob, but in a way that we can still identify + # its type. That lets us see that --connectivity-only is + # not actually looking at the contents, but leaves it + # free to examine the type if it chooses. empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 && - rm -f $empty && - echo invalid >$empty && + blob=$(echo unrelated | git hash-object -w --stdin) && + mv -f $(sha1_file $blob) $empty && + test_must_fail git fsck --strict && git fsck --strict --connectivity-only && tree=$(git rev-parse HEAD:) && @@ -535,12 +549,18 @@ test_expect_success 'fsck --connectivity-only' ' ) ' -remove_loose_object () { - sha1="$(git rev-parse "$1")" && - remainder=${sha1#??} && - firsttwo=${sha1%$remainder} && - rm .git/objects/$firsttwo/$remainder -} +test_expect_success 'fsck --connectivity-only with explicit head' ' + rm -rf connectivity-only && + git init connectivity-only && + ( + cd connectivity-only && + test_commit foo && + rm -f .git/index && + tree=$(git rev-parse HEAD^{tree}) && + remove_object $(git rev-parse HEAD:foo.t) && + test_must_fail git fsck --connectivity-only $tree + ) +' test_expect_success 'fsck --name-objects' ' rm -rf name-objects && @@ -550,11 +570,123 @@ test_expect_success 'fsck --name-objects' ' test_commit julius caesar.t && test_commit augustus && test_commit caesar && - remove_loose_object $(git rev-parse julius:caesar.t) && + remove_object $(git rev-parse julius:caesar.t) && test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out ) ' +test_expect_success 'alternate objects are correctly blamed' ' + test_when_finished "rm -rf alt.git .git/objects/info/alternates" && + git init --bare alt.git && + echo "../../alt.git/objects" >.git/objects/info/alternates && + mkdir alt.git/objects/12 && + >alt.git/objects/12/34567890123456789012345678901234567890 && + test_must_fail git fsck >out 2>&1 && + grep alt.git out +' + +test_expect_success 'fsck errors in packed objects' ' + git cat-file commit HEAD >basis && + sed "s/</one/" basis >one && + sed "s/</foo/" basis >two && + one=$(git hash-object -t commit -w one) && + two=$(git hash-object -t commit -w two) && + pack=$( + { + echo $one && + echo $two + } | git pack-objects .git/objects/pack/pack + ) && + test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && + remove_object $one && + remove_object $two && + test_must_fail git fsck 2>out && + grep "error in commit $one.* - bad name" out && + grep "error in commit $two.* - bad name" out && + ! grep corrupt out +' + +test_expect_success 'fsck finds problems in duplicate loose objects' ' + rm -rf broken-duplicate && + git init broken-duplicate && + ( + cd broken-duplicate && + test_commit duplicate && + # no "-d" here, so we end up with duplicates + git repack && + # now corrupt the loose copy + file=$(sha1_file "$(git rev-parse HEAD)") && + rm "$file" && + echo broken >"$file" && + test_must_fail git fsck + ) +' + +test_expect_success 'fsck detects trailing loose garbage (commit)' ' + git cat-file commit HEAD >basis && + echo bump-commit-sha1 >>basis && + commit=$(git hash-object -w -t commit basis) && + file=$(sha1_file $commit) && + test_when_finished "remove_object $commit" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$commit" out +' + +test_expect_success 'fsck detects trailing loose garbage (blob)' ' + blob=$(echo trailing | git hash-object -w --stdin) && + file=$(sha1_file $blob) && + test_when_finished "remove_object $blob" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$blob" out +' + +# for each of type, we have one version which is referenced by another object +# (and so while unreachable, not dangling), and another variant which really is +# dangling. +test_expect_success 'fsck notices dangling objects' ' + git init dangling && + ( + cd dangling && + blob=$(echo not-dangling | git hash-object -w --stdin) && + dblob=$(echo dangling | git hash-object -w --stdin) && + tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) && + dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) && + commit=$(git commit-tree $tree) && + dcommit=$(git commit-tree -p $commit $tree) && + + cat >expect <<-EOF && + dangling blob $dblob + dangling commit $dcommit + dangling tree $dtree + EOF + + git fsck >actual && + # the output order is non-deterministic, as it comes from a hash + sort <actual >actual.sorted && + test_cmp expect actual.sorted + ) +' + +test_expect_success 'fsck $name notices bogus $name' ' + test_must_fail git fsck bogus && + test_must_fail git fsck $_z40 +' + +test_expect_success 'bogus head does not fallback to all heads' ' + # set up a case that will cause a reachability complaint + echo to-be-deleted >foo && + git add foo && + blob=$(git rev-parse :foo) && + test_when_finished "git rm --cached foo" && + remove_object $blob && + test_must_fail git fsck $_z40 >out 2>&1 && + ! grep $blob out +' + test_done diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index c6a3ccba1b..52283dfc8c 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -89,6 +89,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" <tmp >actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* @@ -196,4 +201,28 @@ test_expect_success 'local-branch symrefs shortened properly' ' test_cmp expect actual ' +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one && + git branch BRANCH-two && + git branch --list | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + BRANCH-two + branch-one + master + EOF + test_cmp expected actual && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c896a4c106..e2f18d11f6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -237,6 +237,22 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'retain authorship w/ conflicts' ' + git reset --hard twerp && + test_commit a conflict a conflict-a && + git reset --hard twerp && + GIT_AUTHOR_NAME=AttributeMe \ + test_commit b conflict b conflict-b && + set_fake_editor && + test_must_fail git rebase -i conflict-a && + echo resolved >conflict && + git add conflict && + git rebase --continue && + test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) && + git show >out && + grep AttributeMe out +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index a6a6c40a98..910f218284 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -99,4 +99,28 @@ testrebase() { testrebase "" .git/rebase-apply testrebase " --merge" .git/rebase-merge +test_expect_success 'rebase --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase master && + test_path_is_dir .git/rebase-apply && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-apply +' + +test_expect_success 'rebase --merge --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase --merge master && + test_path_is_dir .git/rebase-merge && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-merge +' + test_done diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 372307c21b..0acf4b1461 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -385,7 +385,7 @@ test_expect_success '--continue respects opts' ' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "cherry picked from" initial_msg && + ! grep "cherry picked from" initial_msg && grep "cherry picked from" unrelatedpick_msg && grep "cherry picked from" picked_msg && grep "cherry picked from" anotherpick_msg @@ -426,9 +426,9 @@ test_expect_failure '--signoff is automatically propagated to resolved conflict' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "Signed-off-by:" initial_msg && + ! grep "Signed-off-by:" initial_msg && grep "Signed-off-by:" unrelatedpick_msg && - test_must_fail grep "Signed-off-by:" picked_msg && + ! grep "Signed-off-by:" picked_msg && grep "Signed-off-by:" anotherpick_msg ' diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh index 9cce5ae881..bf0a5c9887 100755 --- a/t/t3511-cherry-pick-x.sh +++ b/t/t3511-cherry-pick-x.sh @@ -25,9 +25,8 @@ Signed-off-by: B.U. Thor <buthor@example.com>" mesg_broken_footer="$mesg_no_footer -The signed-off-by string should begin with the words Signed-off-by followed -by a colon and space, and then the signers name and email address. e.g. -Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +This is not recognized as a footer because Myfooter is not a recognized token. +Myfooter: A.U. Thor <author@example.com>" mesg_with_footer_sob="$mesg_with_footer Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" @@ -112,6 +111,17 @@ test_expect_success 'cherry-pick -s inserts blank line after non-conforming foot test_cmp expect actual ' +test_expect_success 'cherry-pick -s recognizes trailer config' ' + pristine_detach initial && + git -c "trailer.Myfooter.ifexists=add" cherry-pick -s mesg-broken-footer && + cat <<-EOF >expect && + $mesg_broken_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' ' pristine_detach initial && sha1=$(git rev-parse mesg-no-footer^0) && diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index bcbb680651..5aa6db584c 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -569,26 +569,22 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' ' test_cmp expect actual ' -test_expect_success 'rm of a populated submodule with a .git directory fails even when forced' ' +test_expect_success 'rm of a populated submodule with a .git directory migrates git dir' ' git checkout -f master && git reset --hard && git submodule update && (cd submod && rm .git && cp -R ../.git/modules/sub .git && - GIT_WORK_TREE=. git config --unset core.worktree + GIT_WORK_TREE=. git config --unset core.worktree && + rm -r ../.git/modules/sub ) && - test_must_fail git rm submod && - test -d submod && - test -d submod/.git && - git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/.git && + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/.git && git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - rm -rf submod + test -s actual && + test_i18ngrep Migrating output.err ' cat >expect.deepmodified <<EOF @@ -667,24 +663,19 @@ test_expect_success 'rm of a populated nested submodule with a nested .git direc git submodule update --recursive && (cd submod/subsubmod && rm .git && - cp -R ../../.git/modules/sub/modules/sub .git && + mv ../../.git/modules/sub/modules/sub .git && GIT_WORK_TREE=. git config --unset core.worktree ) && - test_must_fail git rm submod && - test -d submod && - test -d submod/subsubmod/.git && - git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/subsubmod/.git && + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/subsubmod/.git && git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - rm -rf submod + test -s actual && + test_i18ngrep Migrating output.err ' test_expect_success 'checking out a commit after submodule removal needs manual updates' ' - git commit -m "submodule removal" submod && + git commit -m "submodule removal" submod .gitmodules && git checkout HEAD^ && git submodule update && git checkout -q HEAD^ && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index ba4902df2b..482112ca33 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1294,8 +1294,7 @@ EOF 4:Subject: [PATCH] subject 8: 10:Signed-off-by: example happens to be wrapped here. -11: -12:Signed-off-by: C O Mitter <committer@example.com> +11:Signed-off-by: C O Mitter <committer@example.com> EOF test_cmp expected actual ' @@ -1368,7 +1367,7 @@ EOF test_cmp expected actual ' -test_expect_success 'signoff: detect garbage in non-conforming footer' ' +test_expect_success 'signoff: tolerate garbage in conforming footer' ' append_signoff <<\EOF >actual && subject @@ -1383,8 +1382,36 @@ EOF 8: 10: 13:Signed-off-by: C O Mitter <committer@example.com> -14: -15:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: respect trailer config' ' + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11: +12:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual && + + test_config trailer.Myfooter.ifexists add && + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11:Signed-off-by: C O Mitter <committer@example.com> EOF test_cmp expected actual ' diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh index e4e3e28fc7..bada0cbd32 100755 --- a/t/t4032-diff-inter-hunk-context.sh +++ b/t/t4032-diff-inter-hunk-context.sh @@ -16,11 +16,15 @@ f() { } t() { + use_config= + git config --unset diff.interHunkContext + case $# in 4) hunks=$4; cmd="diff -U$3";; 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";; + 6) hunks=$5; cmd="diff -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";; esac - label="$cmd, $1 common $2" + label="$use_config$cmd, $1 common $2" file=f$1 expected=expected.$file.$3.$hunks @@ -89,4 +93,25 @@ t 9 lines 3 2 t 9 lines 3 2 2 t 9 lines 3 3 1 +# use diff.interHunkContext? +t 1 line 0 0 2 config +t 1 line 0 1 1 config +t 1 line 0 2 1 config +t 9 lines 3 3 1 config +t 2 lines 0 0 2 config +t 2 lines 0 1 2 config +t 2 lines 0 2 1 config +t 3 lines 1 0 2 config +t 3 lines 1 1 1 config +t 3 lines 1 2 1 config +t 9 lines 3 2 2 config +t 9 lines 3 3 1 config + +test_expect_success 'diff.interHunkContext invalid' ' + git config diff.interHunkContext asdf && + test_must_fail git diff && + git config diff.interHunkContext -1 && + test_must_fail git diff +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index ae08b57712..9df054bf05 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -190,4 +190,23 @@ test_expect_success 'shortlog with --output=<file>' ' test_line_count = 3 shortlog ' +test_expect_success 'shortlog --committer (internal)' ' + git checkout --orphan side && + git commit --allow-empty -m one && + git commit --allow-empty -m two && + GIT_COMMITTER_NAME="Sin Nombre" git commit --allow-empty -m three && + + cat >expect <<-\EOF && + 2 C O Mitter + 1 Sin Nombre + EOF + git shortlog -nsc HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'shortlog --committer (external)' ' + git log --format=full | git shortlog -nsc >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index f5435fd250..21eb8c8587 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -535,4 +535,30 @@ test_expect_success 'clean log decoration' ' test_cmp expected actual1 ' +cat >trailers <<EOF +Signed-off-by: A U Thor <author@example.com> +Acked-by: A U Thor <author@example.com> +[ v2 updated patch description ] +Signed-off-by: A U Thor <author@example.com> +EOF + +test_expect_success 'pretty format %(trailers) shows trailers' ' + echo "Some contents" >trailerfile && + git add trailerfile && + git commit -F - <<-EOF && + trailers: this commit message has trailers + + This commit is a test commit with trailers at the end. We parse this + message and display the trailers using %bT + + $(cat trailers) + EOF + git log --no-walk --pretty="%(trailers)" >actual && + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual +' + test_done diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 9b19cff729..49d3621a92 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -152,7 +152,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' ' git --git-dir=dst/.git config --add \ receive.fsck.badDate warn && git push --porcelain dst bogus >act 2>&1 && - test_must_fail grep "missingEmail" act + ! grep "missingEmail" act ' test_expect_success \ diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 8198d8eb05..ba46e86de0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -764,6 +764,13 @@ test_expect_success 'rename a remote with name prefix of other remote' ' ) ' +test_expect_success 'rename succeeds with existing remote.<target>.prune' ' + git clone one four.four && + test_when_finished git config --global --unset remote.upstream.prune && + git config --global remote.upstream.prune true && + git -C four.four remote rename origin upstream +' + cat >remotes_origin <<EOF URL: $(pwd)/one Push: refs/heads/master:refs/heads/upstream diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh index bc44ac36d5..75c570adca 100755 --- a/t/t5509-fetch-push-namespaces.sh +++ b/t/t5509-fetch-push-namespaces.sh @@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git init original && ( diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 26b2cafc47..0fc5a7c596 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1004,7 +1004,7 @@ test_expect_success 'push --porcelain' ' test_expect_success 'push --porcelain bad url' ' mk_empty testrepo && test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master && - test_must_fail grep -q Done .git/bar + ! grep -q Done .git/bar ' test_expect_success 'push --porcelain rejected' ' diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1524ff5ba6..f55137f76f 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -454,4 +454,25 @@ test_expect_success 'push --dry-run does not recursively update submodules' ' test_cmp expected_submodule actual_submodule ' +test_expect_success 'push --dry-run does not recursively update submodules' ' + git -C work push --dry-run --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + +test_expect_success 'push only unpushed submodules recursively' ' + git -C work/gar/bage rev-parse master >expected_submodule && + git -C pub.git rev-parse master >expected_pub && + + git -C work push --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 264a1ab8b0..aeb3a63f7c 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -368,5 +368,15 @@ test_expect_success 'http-alternates cannot point at funny protocols' ' clone "$HTTPD_URL/dumb/evil.git" evil-file ' +test_expect_success 'http-alternates triggers not-from-user protocol check' ' + echo "$HTTPD_URL/dumb/victim.git/objects" \ + >"$evil/objects/info/http-alternates" && + test_config_global http.followRedirects true && + test_must_fail git -c protocol.http.allow=user \ + clone $HTTPD_URL/dumb/evil.git evil-user && + git -c protocol.http.allow=always \ + clone $HTTPD_URL/dumb/evil.git evil-user +' + stop_httpd test_done diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh new file mode 100755 index 0000000000..b195f71ea9 --- /dev/null +++ b/t/t5580-clone-push-unc.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='various UNC path tests (Windows-only)' +. ./test-lib.sh + +if ! test_have_prereq MINGW; then + skip_all='skipping UNC path tests, requires Windows' + test_done +fi + +UNCPATH="$(pwd)" +case "$UNCPATH" in +[A-Z]:*) + # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git + # (we use forward slashes here because MSYS2 and Git accept them, and + # they are easier on the eyes) + UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}" + test -d "$UNCPATH" || { + skip_all='could not access administrative share; skipping' + test_done + } + ;; +*) + skip_all='skipping UNC path tests, cannot determine current path as UNC' + test_done + ;; +esac + +test_expect_success setup ' + test_commit initial +' + +test_expect_success clone ' + git clone "file://$UNCPATH" clone +' + +test_expect_success push ' + ( + cd clone && + git checkout -b to-push && + test_commit to-push && + git push origin HEAD + ) && + rev="$(git -C clone rev-parse --verify refs/heads/to-push)" && + test "$rev" = "$(git rev-parse --verify refs/heads/to-push)" +' + +test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index a433394200..4241ea5b32 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -151,7 +151,7 @@ test_expect_success 'clone --mirror does not repeat tags' ' git clone --mirror src mirror2 && (cd mirror2 && git show-ref 2> clone.err > clone.out) && - test_must_fail grep Duplicate mirror2/clone.err && + ! grep Duplicate mirror2/clone.err && grep some-tag mirror2/clone.out ' diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh index b7a7f9d588..c6c2661878 100755 --- a/t/t5802-connect-helper.sh +++ b/t/t5802-connect-helper.sh @@ -4,6 +4,7 @@ test_description='ext::cmd remote "connect" helper' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git commit --allow-empty -m initial && test_tick && diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index 044cc152f8..d911afd24c 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -30,5 +30,12 @@ test_expect_success 'curl limits redirects' ' test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git" ' +test_expect_success 'http can be limited to from-user' ' + git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart/repo.git" plain.git && + test_must_fail git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart-redir-perm/repo.git" redir.git +' + stop_httpd test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 5e5370feb4..8c2c6eaef8 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -407,7 +407,7 @@ test_expect_success 'good merge base when good and bad are siblings' ' test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && git bisect good > my_bisect_log.txt && - test_must_fail grep "merge base must be tested" my_bisect_log.txt && + ! grep "merge base must be tested" my_bisect_log.txt && grep $HASH6 my_bisect_log.txt && git bisect reset ' diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh new file mode 100755 index 0000000000..fd401ca605 --- /dev/null +++ b/t/t6134-pathspec-in-submodule.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='test case exclude pathspec' + +. ./test-lib.sh + +test_expect_success 'setup a submodule' ' + test_create_repo pretzel && + : >pretzel/a && + git -C pretzel add a && + git -C pretzel commit -m "add a file" -- a && + git submodule add ./pretzel sub && + git commit -a -m "add submodule" && + git submodule deinit --all +' + +cat <<EOF >expect +fatal: Pathspec 'sub/a' is in submodule 'sub' +EOF + +test_expect_success 'error message for path inside submodule' ' + echo a >sub/a && + test_must_fail git add sub/a 2>actual && + test_cmp expect actual +' + +cat <<EOF >expect +fatal: Pathspec '.' is in submodule 'sub' +EOF + +test_expect_success 'error message for path inside submodule from within submodule' ' + test_must_fail git -C sub add . 2>actual && + test_cmp expect actual +' + +test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 039509a9cb..aea1dfc714 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -563,4 +563,29 @@ test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' test_cmp expect actual ' +cat >trailers <<EOF +Reviewed-by: A U Thor <author@example.com> +Signed-off-by: A U Thor <author@example.com> +EOF + +test_expect_success 'basic atom: head contents:trailers' ' + echo "Some contents" > two && + git add two && + git commit -F - <<-EOF && + trailers: this commit message has trailers + + Some message contents + + $(cat trailers) + EOF + git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual && + sanitize_pgp <actual >actual.clean && + # git for-each-ref ends with a blank line + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual.clean +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 8b0f71a2ac..b676b90c7d 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -27,6 +27,30 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' test $(git tag | wc -l) -eq 0 ' +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one && + git tag TAG-two && + git tag -l >actual && + cat >expected <<-\EOF && + TAG-two + initial + tag-one + EOF + test_cmp expected actual && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) +' + test_expect_success 'looking for a tag in an empty tree should fail' \ '! (tag_exists mytag)' @@ -81,6 +105,9 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag with --ignore-case' \ + 'test $(git tag -l --ignore-case MYTAG) = mytag' + test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ 'test $(git tag -l mytag) = mytag' @@ -122,11 +149,11 @@ test_expect_success '--force can create a tag with the name of one existing' ' tag_exists mytag' test_expect_success '--force is moot with a non-existing tag name' ' + test_when_finished git tag -d newtag forcetag && git tag newtag >expect && git tag --force forcetag >actual && test_cmp expect actual ' -git tag -d newtag forcetag # deleting tags: @@ -297,11 +324,9 @@ EOF ' test_expect_success 'listing tags in column with column.*' ' - git config column.tag row && - git config column.ui dense && + test_config column.tag row && + test_config column.ui dense && COLUMNS=40 git tag -l >actual && - git config --unset column.ui && - git config --unset column.tag && cat >expected <<\EOF && a1 aa1 cba t210 t211 v0.2.1 v1.0 v1.0.1 v1.1.3 @@ -314,9 +339,8 @@ test_expect_success 'listing tag with -n --column should fail' ' ' test_expect_success 'listing tags -n in column with column.ui ignored' ' - git config column.ui "row dense" && + test_config column.ui "row dense" && COLUMNS=40 git tag -l -n >actual && - git config --unset column.ui && cat >expected <<\EOF && a1 Foo aa1 Foo @@ -847,6 +871,22 @@ 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 + tagname : signed-tag + 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_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual && + test_cmp expect actual +' + # blank and empty messages for signed tags: get_tag_header empty-signed-tag $commit commit $time >expect @@ -1200,11 +1240,10 @@ test_expect_success GPG,RFC1991 \ ' # try to sign with bad user.signingkey -git config user.signingkey BobTheMouse test_expect_success GPG \ 'git tag -s fails if gpg is misconfigured (bad key)' \ - 'test_must_fail git tag -s -m tail tag-gpg-failure' -git config --unset user.signingkey + 'test_config user.signingkey BobTheMouse && + test_must_fail git tag -s -m tail tag-gpg-failure' # try to produce invalid signature test_expect_success GPG \ @@ -1484,7 +1523,7 @@ test_expect_success 'reverse lexical sort' ' ' test_expect_success 'configured lexical sort' ' - git config tag.sort "v:refname" && + test_config tag.sort "v:refname" && git tag -l "foo*" >actual && cat >expect <<-\EOF && foo1.3 @@ -1495,6 +1534,7 @@ test_expect_success 'configured lexical sort' ' ' test_expect_success 'option override configured sort' ' + test_config tag.sort "v:refname" && git tag -l --sort=-refname "foo*" >actual && cat >expect <<-\EOF && foo1.6 @@ -1509,13 +1549,12 @@ test_expect_success 'invalid sort parameter on command line' ' ' test_expect_success 'invalid sort parameter in configuratoin' ' - git config tag.sort "v:notvalid" && + test_config tag.sort "v:notvalid" && test_must_fail git tag -l "foo*" ' test_expect_success 'version sort with prerelease reordering' ' - git config --unset tag.sort && - git config versionsort.prereleaseSuffix -rc && + test_config versionsort.prereleaseSuffix -rc && git tag foo1.6-rc1 && git tag foo1.6-rc2 && git tag -l --sort=version:refname "foo*" >actual && @@ -1530,6 +1569,7 @@ test_expect_success 'version sort with prerelease reordering' ' ' test_expect_success 'reverse version sort with prerelease reordering' ' + test_config versionsort.prereleaseSuffix -rc && git tag -l --sort=-version:refname "foo*" >actual && cat >expect <<-\EOF && foo1.10 @@ -1541,6 +1581,103 @@ test_expect_success 'reverse version sort with prerelease reordering' ' test_cmp expect actual ' +test_expect_success 'version sort with prerelease reordering and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git tag foo1.7-before1 && + git tag foo1.7 && + git tag foo1.7-after1 && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git config --add versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7-after1 + foo1.7 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' ' + test_config versionsort.prereleaseSuffix -bar && + git config --add versionsort.prereleaseSuffix -foo-baz && + git config --add versionsort.prereleaseSuffix -foo-bar && + git tag foo1.8-foo-bar && + git tag foo1.8-foo-baz && + git tag foo1.8 && + git tag -l --sort=version:refname "foo1.8*" >actual && + cat >expect <<-\EOF && + foo1.8-foo-baz + foo1.8-foo-bar + foo1.8 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' ' + test_config versionsort.prereleaseSuffix -pre && + git config --add versionsort.prereleaseSuffix -prerelease && + git tag foo1.9-pre1 && + git tag foo1.9-pre2 && + git tag foo1.9-prerelease1 && + git tag -l --sort=version:refname "foo1.9*" >actual && + cat >expect <<-\EOF && + foo1.9-pre1 + foo1.9-pre2 + foo1.9-prerelease1 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with general suffix reordering' ' + test_config versionsort.suffix -alpha && + git config --add versionsort.suffix -beta && + git config --add versionsort.suffix "" && + git config --add versionsort.suffix -gamma && + git config --add versionsort.suffix -delta && + git tag foo1.10-alpha && + git tag foo1.10-beta && + git tag foo1.10-gamma && + git tag foo1.10-delta && + git tag foo1.10-unlisted-suffix && + git tag -l --sort=version:refname "foo1.10*" >actual && + cat >expect <<-\EOF && + foo1.10-alpha + foo1.10-beta + foo1.10 + foo1.10-unlisted-suffix + foo1.10-gamma + foo1.10-delta + EOF + test_cmp expect actual +' + +test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' ' + test_config versionsort.suffix -before && + test_config versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with very long prerelease suffix' ' + test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix && + git tag -l --sort=version:refname +' + run_with_limited_stack () { (ulimit -s 128 && "$@") } @@ -1569,13 +1706,11 @@ EOF" test_expect_success '--format should list tags as per format given' ' cat >expect <<-\EOF && - refname : refs/tags/foo1.10 - refname : refs/tags/foo1.3 - refname : refs/tags/foo1.6 - refname : refs/tags/foo1.6-rc1 - refname : refs/tags/foo1.6-rc2 + refname : refs/tags/v1.0 + refname : refs/tags/v1.0.1 + refname : refs/tags/v1.1.3 EOF - git tag -l --format="refname : %(refname)" "foo*" >actual && + git tag -l --format="refname : %(refname)" "v1*" >actual && test_cmp expect actual ' diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 07079a41c4..d62ccbb98e 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -125,4 +125,20 @@ test_expect_success GPG 'verify multiple tags' ' test_cmp expect.stderr actual.stderr ' +test_expect_success 'verifying tag with --format' ' + cat >expect <<-\EOF + tagname : fourth-signed + 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_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && + test_cmp expect actual-forged +' + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index b77cce8e40..c09ce0d4c1 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -152,6 +152,20 @@ test_expect_success 'submodule add to .gitignored path with --force' ' ) ' +test_expect_success 'submodule add to reconfigure existing submodule with --force' ' + ( + cd addtest-ignore && + git submodule add --force bogus-url submod && + git submodule add -b initial "$submodurl" submod-branch && + test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" && + test "bogus-url" = "$(git config submodule.submod.url)" && + # Restore the url + git submodule add --force "$submodurl" submod + test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" && + test "$submodurl" = "$(git config submodule.submod.url)" + ) +' + test_expect_success 'submodule add --branch' ' echo "refs/heads/initial" >expect-head && cat <<-\EOF >expect-heads && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 64f322c4cc..725bbed1f8 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -140,6 +140,23 @@ test_expect_success 'submodule update --init --recursive from subdirectory' ' test_i18ncmp expect2 actual2 ' +cat <<EOF >expect2 +Submodule 'foo/sub' ($pwd/withsubs/../rebasing) registered for path 'sub' +EOF + +test_expect_success 'submodule update --init from and of subdirectory' ' + git init withsubs && + (cd withsubs && + mkdir foo && + git submodule add "$(pwd)/../rebasing" foo/sub && + (cd foo && + git submodule deinit -f sub && + git submodule update --init sub 2>../../actual2 + ) + ) && + test_i18ncmp expect2 actual2 +' + apos="'"; test_expect_success 'submodule update does not fetch already present commits' ' (cd submodule && diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index 1c1e289ffd..e159fc5035 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -125,4 +125,70 @@ test_expect_success 'ignoring missing submodule alternates passes clone and subm ) ' +test_expect_success 'preparing second superproject with a nested submodule plus partial clone' ' + test_create_repo supersuper && + ( + cd supersuper && + echo "I am super super." >file && + git add file && + git commit -m B-super-super-initial + git submodule add "file://$base_dir/super" subwithsub && + git commit -m B-super-super-added && + git submodule update --init --recursive && + git repack -ad + ) && + git clone supersuper supersuper2 && + ( + cd supersuper2 && + git submodule update --init + ) +' + +# At this point there are three root-level positories: A, B, super and super2 + +test_expect_success 'nested submodule alternate in works and is actually used' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference supersuper supersuper supersuper-clone && + ( + cd supersuper-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # nested submodule also has alternate: + test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub + ) +' + +check_that_two_of_three_alternates_are_used() { + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # but nested submodule has no alternate: + test_must_fail test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub +} + + +test_expect_success 'missing nested submodule alternate fails clone and submodule update' ' + test_when_finished "rm -rf supersuper-clone" && + test_must_fail git clone --recursive --reference supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule fails + test_must_fail git submodule update --init --recursive + ) +' + +test_expect_success 'missing nested submodule alternate in --reference-if-able mode' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference-if-able supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule succeeds + git submodule update --init --recursive + ) +' + test_done diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index 47562ce465..eea36f1dbe 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -93,6 +93,20 @@ test_expect_success 'error message contains blob reference' ' ) ' +test_expect_success 'using different treeishs works' ' + ( + cd super && + git tag new_tag && + tree=$(git rev-parse HEAD^{tree}) && + commit=$(git rev-parse HEAD^{commit}) && + test-submodule-config $commit b >expect && + test-submodule-config $tree b >actual.1 && + test-submodule-config new_tag b >actual.2 && + test_cmp expect actual.1 && + test_cmp expect actual.2 + ) +' + cat >super/expect_url <<EOF Submodule url: 'git@somewhere.else.net:a.git' for path 'b' Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' @@ -120,12 +134,33 @@ test_expect_success 'reading of local configuration' ' "" submodule \ >actual && test_cmp expect_local_path actual && - git config submodule.a.url $old_a && - git config submodule.submodule.url $old_submodule && + git config submodule.a.url "$old_a" && + git config submodule.submodule.url "$old_submodule" && git config --unset submodule.a.path c ) ' +cat >super/expect_url <<EOF +Submodule url: '../submodule' for path 'b' +Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' +EOF + +test_expect_success 'reading of local configuration for uninitialized submodules' ' + ( + cd super && + git submodule deinit -f b && + old_submodule=$(git config submodule.submodule.url) && + git config submodule.submodule.url git@somewhere.else.net:submodule.git && + test-submodule-config --url \ + "" b \ + "" submodule \ + >actual && + test_cmp expect_url actual && + git config submodule.submodule.url "$old_submodule" && + git submodule init b + ) +' + cat >super/expect_fetchrecurse_die.err <<EOF fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla EOF diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh new file mode 100755 index 0000000000..1c47780e2b --- /dev/null +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +test_description='Test submodule absorbgitdirs + +This test verifies that `git submodue absorbgitdirs` moves a submodules git +directory into the superproject. +' + +. ./test-lib.sh + +test_expect_success 'setup a real submodule' ' + git init sub1 && + test_commit -C sub1 first && + git submodule add ./sub1 && + test_tick && + git commit -m superproject +' + +test_expect_success 'absorb the git dir' ' + >expect.1 && + >expect.2 && + >actual.1 && + >actual.2 && + git status >expect.1 && + git -C sub1 rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + git fsck && + test -f sub1/.git && + test -d .git/modules/sub1 && + git status >actual.1 && + git -C sub1 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'absorbing does not fail for deinitalized submodules' ' + test_when_finished "git submodule update --init" && + git submodule deinit --all && + git submodule absorbgitdirs && + test -d .git/modules/sub1 && + test -d sub1 && + ! test -e sub1/.git +' + +test_expect_success 'setup nested submodule' ' + git init sub1/nested && + test_commit -C sub1/nested first_nested && + git -C sub1 submodule add ./nested && + test_tick && + git -C sub1 commit -m "add nested" && + git add sub1 && + git commit -m "sub1 to include nested submodule" +' + +test_expect_success 'absorb the git dir in a nested submodule' ' + git status >expect.1 && + git -C sub1/nested rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + test -f sub1/nested/.git && + test -d .git/modules/sub1/modules/nested && + git status >actual.1 && + git -C sub1/nested rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a gitlink with missing .gitmodules entry' ' + git init sub2 && + test_commit -C sub2 first && + git add sub2 && + git commit -m superproject +' + +test_expect_success 'absorbing the git dir fails for incomplete submodules' ' + git status >expect.1 && + git -C sub2 rev-parse HEAD >expect.2 && + test_must_fail git submodule absorbgitdirs && + git -C sub2 fsck && + test -d sub2/.git && + git status >actual && + git -C sub2 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a submodule with multiple worktrees' ' + # first create another unembedded git dir in a new submodule + git init sub3 && + test_commit -C sub3 first && + git submodule add ./sub3 && + test_tick && + git commit -m "add another submodule" && + git -C sub3 worktree add ../sub3_second_work_tree +' + +test_expect_success 'absorbing fails for a submodule with multiple worktrees' ' + test_must_fail git submodule absorbgitdirs sub3 2>error && + test_i18ngrep "not supported" error +' + +test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 0d8d893090..0b6da7ae1f 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -469,6 +469,42 @@ $alt" && test_cmp expected actual ' +test_expect_success 'signoff respects trailer config' ' + + echo 5 >positive && + git add positive && + git commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual && + + echo 6 >positive && + git add positive && + git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 5c3db656df..458608cc1e 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -944,4 +944,23 @@ EOF test_i18ncmp expected actual ' +test_expect_success 'status: handle not-yet-started rebase -i gracefully' ' + ONTO=$(git rev-parse --short HEAD^) && + COMMIT=$(git rev-parse --short HEAD) && + EDITOR="git status --untracked-files=no >actual" git rebase -i HEAD^ && + cat >expected <<EOF && +On branch several_commits +No commands done. +Next command to do (1 remaining command): + pick $COMMIT four_commit + (use "git rebase --edit-todo" to view and edit) +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + test_i18ncmp expected actual +' + test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 85248a14b6..2ebda509ac 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -154,6 +154,10 @@ test_expect_success 'test option parsing' ' test_must_fail git merge -s foobar c1 && test_must_fail git merge -s=foobar c1 && test_must_fail git merge -m && + test_must_fail git merge --abort foobar && + test_must_fail git merge --abort --quiet && + test_must_fail git merge --continue foobar && + test_must_fail git merge --continue --quiet && test_must_fail git merge ' @@ -763,4 +767,11 @@ test_expect_success 'merge nothing into void' ' ) ' +test_expect_success 'merge can be completed with --continue' ' + git reset --hard c0 && + git merge --no-ff --no-commit c1 && + git merge --continue && + verify_parents $c0 $c1 +' + test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 63d36fb28e..381b7df452 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -55,6 +55,22 @@ test_expect_success 'setup' ' git rm file12 && git commit -m "branch1 changes" && + git checkout -b delete-base branch1 && + mkdir -p a/a && + (echo one; echo two; echo 3; echo 4) >a/a/file.txt && + git add a/a/file.txt && + git commit -m"base file" && + git checkout -b move-to-b delete-base && + mkdir -p b/b && + git mv a/a/file.txt b/b/file.txt && + (echo one; echo two; echo 4) >b/b/file.txt && + git commit -a -m"move to b" && + git checkout -b move-to-c delete-base && + mkdir -p c/c && + git mv a/a/file.txt c/c/file.txt && + (echo one; echo two; echo 3) >c/c/file.txt && + git commit -a -m"move to c" && + git checkout -b stash1 master && echo stash1 change file11 >file11 && git add file11 && @@ -86,6 +102,23 @@ test_expect_success 'setup' ' git rm file11 && git commit -m "master updates" && + git clean -fdx && + git checkout -b order-file-start master && + echo start >a && + echo start >b && + git add a b && + git commit -m start && + git checkout -b order-file-side1 order-file-start && + echo side1 >a && + echo side1 >b && + git add a b && + git commit -m side1 && + git checkout -b order-file-side2 order-file-start && + echo side2 >a && + echo side2 >b && + git add a b && + git commit -m side2 && + git config merge.tool mytool && git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && git config mergetool.mytool.trustExitCode true && @@ -94,7 +127,8 @@ test_expect_success 'setup' ' ' test_expect_success 'custom mergetool' ' - git checkout -b test1 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -112,8 +146,13 @@ test_expect_success 'custom mergetool' ' ' test_expect_success 'mergetool crlf' ' + test_when_finished "git reset --hard" && + # This test_config line must go after the above reset line so that + # core.autocrlf is unconfigured before reset runs. (The + # test_config command uses test_when_finished internally and + # test_when_finished is LIFO.) test_config core.autocrlf true && - git checkout -b test2 branch1 && + git checkout -b test$test_count branch1 && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && @@ -128,13 +167,12 @@ test_expect_success 'mergetool crlf' ' test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git commit -m "branch1 resolved with mergetool - autocrlf" && - test_config core.autocrlf false && - git reset --hard + git commit -m "branch1 resolved with mergetool - autocrlf" ' test_expect_success 'mergetool in subdir' ' - git checkout -b test3 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -145,8 +183,13 @@ test_expect_success 'mergetool in subdir' ' ' test_expect_success 'mergetool on file in parent dir' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + git submodule update -N && ( cd subdir && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && ( yes "" | git mergetool ../both >/dev/null 2>&1 ) && @@ -161,7 +204,8 @@ test_expect_success 'mergetool on file in parent dir' ' ' test_expect_success 'mergetool skips autoresolved' ' - git checkout -b test4 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -169,11 +213,12 @@ test_expect_success 'mergetool skips autoresolved' ' ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" ' -test_expect_success 'mergetool merges all from subdir' ' +test_expect_success 'mergetool merges all from subdir (rerere disabled)' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config rerere.enabled false && ( cd subdir && @@ -189,21 +234,41 @@ test_expect_success 'mergetool merges all from subdir' ' ) ' +test_expect_success 'mergetool merges all from subdir (rerere enabled)' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + test_config rerere.enabled true && + rm -rf .git/rr-cache && + ( + cd subdir && + test_must_fail git merge master && + ( yes "r" | git mergetool ../submod ) && + ( yes "d" "d" | git mergetool --no-prompt ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + ( cd .. && git submodule update -N ) && + test "$(cat ../submod/bar)" = "master submodule" && + git commit -m "branch2 resolved by mergetool from subdir" + ) +' + test_expect_success 'mergetool skips resolved paths when rerere is active' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && rm -rf .git/rr-cache && - git checkout -b test5 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && git submodule update -N && output="$(yes "n" | git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" ' test_expect_success 'conflicted stash sets up rerere' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && git checkout stash1 && echo "Conflicting stash content" >file11 && @@ -231,67 +296,57 @@ test_expect_success 'conflicted stash sets up rerere' ' ' test_expect_success 'mergetool takes partial path' ' - git reset --hard && + test_when_finished "git reset --hard" && test_config rerere.enabled false && - git checkout -b test12 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && ( yes "" | git mergetool subdir ) && - test "$(cat subdir/file3)" = "master new sub" && - git reset --hard + test "$(cat subdir/file3)" = "master new sub" ' test_expect_success 'mergetool delete/delete conflict' ' - git checkout -b delete-base branch1 && - mkdir -p a/a && - (echo one; echo two; echo 3; echo 4) >a/a/file.txt && - git add a/a/file.txt && - git commit -m"base file" && - git checkout -b move-to-b delete-base && - mkdir -p b/b && - git mv a/a/file.txt b/b/file.txt && - (echo one; echo two; echo 4) >b/b/file.txt && - git commit -a -m"move to b" && - git checkout -b move-to-c delete-base && - mkdir -p c/c && - git mv a/a/file.txt c/c/file.txt && - (echo one; echo two; echo 3) >c/c/file.txt && - git commit -a -m"move to c" && + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && ! test -f a/a/file.txt && - git reset --hard HEAD && + git reset --hard && test_must_fail git merge move-to-b && echo m | git mergetool a/a/file.txt && test -f b/b/file.txt && - git reset --hard HEAD && + git reset --hard && test_must_fail git merge move-to-b && ! echo a | git mergetool a/a/file.txt && - ! test -f a/a/file.txt && - git reset --hard HEAD + ! test -f a/a/file.txt ' test_expect_success 'mergetool produces no errors when keepBackup is used' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && : >expect && echo d | git mergetool a/a/file.txt 2>actual && test_cmp expect actual && - ! test -d a && - git reset --hard HEAD + ! test -d a ' test_expect_success 'mergetool honors tempfile config for deleted files' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries false && test_must_fail git merge move-to-b && echo d | git mergetool a/a/file.txt && - ! test -d a && - git reset --hard HEAD + ! test -d a ' test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' + test_when_finished "git reset --hard" && + test_when_finished "git clean -fdx" && + git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries true && test_must_fail git merge move-to-b && ! (echo a; echo n) | git mergetool a/a/file.txt && @@ -302,18 +357,17 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' file_REMOTE_.txt EOF ls -1 a/a | sed -e "s/[0-9]*//g" >actual && - test_cmp expect actual && - git clean -fdx && - git reset --hard HEAD + test_cmp expect actual ' test_expect_success 'deleted vs modified submodule' ' - git checkout -b test6 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && git commit -m "Submodule deleted from branch" && - git checkout -b test6.a test6 && + git checkout -b test$test_count.a test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -329,7 +383,7 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test6.b test6 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -343,9 +397,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod-movedaside submod && - git checkout -b test6.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -359,9 +413,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod.orig submod && - git checkout -b test6.d master && + git checkout -b test$test_count.d master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -372,19 +426,19 @@ test_expect_success 'deleted vs modified submodule' ' test "$(cat submod/bar)" = "master submodule" && output="$(git mergetool --no-prompt)" && test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - git reset --hard HEAD + git commit -m "Merge resolved by keeping module" ' test_expect_success 'file vs modified submodule' ' - git checkout -b test7 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && echo not a submodule >submod && git add submod && git commit -m "Submodule path becomes file" && - git checkout -b test7.a branch1 && + git checkout -b test$test_count.a branch1 && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -400,7 +454,7 @@ test_expect_success 'file vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test7.b test7 && + git checkout -b test$test_count.b test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -413,11 +467,11 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.c master && + git checkout -b test$test_count.c master && rmdir submod && mv submod-movedaside submod && test ! -e submod.orig && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -430,10 +484,10 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.d master && + git checkout -b test$test_count.d master && rmdir submod && mv submod.orig submod && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both>/dev/null 2>&1 ) && @@ -448,7 +502,8 @@ test_expect_success 'file vs modified submodule' ' ' test_expect_success 'submodule in subdirectory' ' - git checkout -b test10 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -460,56 +515,57 @@ test_expect_success 'submodule in subdirectory' ' git commit -m "add initial versions" ) ) && + test_when_finished "rm -rf subdir/subdir_module" && git submodule add git://example.com/subsubmodule subdir/subdir_module && git add subdir/subdir_module && git commit -m "add submodule in subdirectory" && - git checkout -b test10.a test10 && + git checkout -b test$test_count.a test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.a && - echo test10.a >file15 && + echo test$test_count.a >file15 && git add file15 && git commit -m "on branch 10.a" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.a" && + git commit -m "change submodule in subdirectory on test$test_count.a" && - git checkout -b test10.b test10 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.b && - echo test10.b >file15 && + echo test$test_count.b >file15 && git add file15 && git commit -m "on branch 10.b" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.b" && + git commit -m "change submodule in subdirectory on test$test_count.b" && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( cd subdir && ( yes "l" | git mergetool subdir_module ) ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git reset --hard && git submodule update -N && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( yes "r" | git mergetool subdir/subdir_module ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.a" && - git commit -m "branch1 resolved with mergetool" && - rm -rf subdir/subdir_module + test "$(cat subdir/subdir_module/file15)" = "test$test_count.a" && + git commit -m "branch1 resolved with mergetool" ' test_expect_success 'directory vs modified submodule' ' - git checkout -b test11 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && mv submod submod-movedaside && git rm --cached submod && mkdir submod && @@ -523,7 +579,7 @@ test_expect_success 'directory vs modified submodule' ' test "$(cat submod/file16)" = "not a submodule" && rm -rf submod.orig && - git reset --hard >/dev/null 2>&1 && + git reset --hard && test_must_fail git merge master && test -n "$(git ls-files -u)" && test ! -e submod.orig && @@ -535,58 +591,59 @@ test_expect_success 'directory vs modified submodule' ' ( cd submod && git clean -f && git reset --hard ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && + git reset --hard && + rm -rf submod-movedaside && - git checkout -b test11.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "l" | git mergetool submod ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && + git reset --hard && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && test ! -e submod.orig && ( yes "r" | git mergetool submod ) && test "$(cat submod/file16)" = "not a submodule" && - git reset --hard master >/dev/null 2>&1 && + git reset --hard master && ( cd submod && git clean -f && git reset --hard ) && git submodule update -N ' test_expect_success 'file with no base' ' - git checkout -b test13 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp both expected ' test_expect_success 'custom commands override built-ins' ' - git checkout -b test14 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && test_config mergetool.defaults.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool defaults -- both && echo master both added >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp both expected ' test_expect_success 'filenames seen by tools start with ./' ' - git checkout -b test15 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp false && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - grep ^\./both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + grep ^\./both_LOCAL_ actual >/dev/null ' test_lazy_prereq MKTEMP ' @@ -596,53 +653,48 @@ test_lazy_prereq MKTEMP ' ' test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' - git checkout -b test16 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - test_must_fail grep ^\./both_LOCAL_ actual >/dev/null && - grep /both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + ! grep ^\./both_LOCAL_ actual >/dev/null && + grep /both_LOCAL_ actual >/dev/null ' test_expect_success 'diff.orderFile configuration is honored' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && echo b >order-file && echo a >>order-file && - git checkout -b order-file-start master && - echo start >a && - echo start >b && - git add a b && - git commit -m start && - git checkout -b order-file-side1 order-file-start && - echo side1 >a && - echo side1 >b && - git add a b && - git commit -m side1 && - git checkout -b order-file-side2 order-file-start && - echo side2 >a && - echo side2 >b && - git add a b && - git commit -m side2 && test_must_fail git merge order-file-side1 && cat >expect <<-\EOF && Merging: b a EOF + + # make sure "order-file" that is ambiguous between + # rev and path is understood correctly. + git branch order-file HEAD && + git mergetool --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && - test_cmp expect actual && - git reset --hard >/dev/null + test_cmp expect actual ' test_expect_success 'mergetool -Oorder-file is honored' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count order-file-side2 && test_config diff.orderFile order-file && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && + echo b >order-file && + echo a >>order-file && test_must_fail git merge order-file-side1 && cat >expect <<-\EOF && Merging: @@ -652,7 +704,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' git mergetool -O/dev/null --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && test_cmp expect actual && - git reset --hard >/dev/null 2>&1 && + git reset --hard && git config --unset diff.orderFile && test_must_fail git merge order-file-side1 && @@ -663,8 +715,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' EOF git mergetool -Oorder-file --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && - test_cmp expect actual && - git reset --hard >/dev/null 2>&1 + test_cmp expect actual ' test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..aa0ef02597 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -24,7 +24,7 @@ prompt_given () } # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -36,7 +36,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -49,21 +49,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -76,40 +76,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -124,13 +124,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +test_expect_success 'difftool honors exit status if command not found' ' test_config difftool.nonexistent.cmd i-dont-exist && test_config difftool.trustExitCode false && test_must_fail git difftool -y -t nonexistent branch ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -141,7 +141,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -155,7 +155,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -163,7 +163,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -173,7 +173,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -191,7 +191,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -200,7 +200,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -210,7 +210,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -219,7 +219,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -229,7 +229,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -238,7 +238,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -248,7 +248,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -261,7 +261,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -275,49 +275,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && lines=$(grep file output | wc -l) && test "$lines" -eq 1 ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -325,7 +325,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -337,7 +337,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -346,7 +346,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -355,7 +355,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -363,12 +363,12 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && @@ -382,11 +382,11 @@ test_expect_success PERL 'setup change in subdirectory' ' ' run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -508,7 +508,7 @@ do done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -545,7 +545,7 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && @@ -558,7 +558,7 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && @@ -571,7 +571,7 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' @@ -585,7 +585,7 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' git init dirlinks && ( cd dirlinks && diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh new file mode 100755 index 0000000000..67247a01d6 --- /dev/null +++ b/t/t7814-grep-recurse-submodules.sh @@ -0,0 +1,241 @@ +#!/bin/sh + +test_description='Test grep recurse-submodules feature + +This test verifies the recurse-submodules feature correctly greps across +submodules. +' + +. ./test-lib.sh + +test_expect_success 'setup directory structure and submodule' ' + echo "foobar" >a && + mkdir b && + echo "bar" >b/b && + git add a b && + git commit -m "add a and b" && + git init submodule && + echo "foobar" >submodule/a && + git -C submodule add a && + git -C submodule commit -m "add a" && + git submodule add ./submodule && + git commit -m "added submodule" +' + +test_expect_success 'grep correctly finds patterns in a submodule' ' + cat >expect <<-\EOF && + a:foobar + b/b:bar + submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and basic pathspecs' ' + cat >expect <<-\EOF && + submodule/a:foobar + EOF + + git grep -e. --recurse-submodules -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep and nested submodules' ' + git init submodule/sub && + echo "foobar" >submodule/sub/a && + git -C submodule/sub add a && + git -C submodule/sub commit -m "add a" && + git -C submodule submodule add ./sub && + git -C submodule add sub && + git -C submodule commit -m "added sub" && + git add submodule && + git commit -m "updated submodule" && + + cat >expect <<-\EOF && + a:foobar + b/b:bar + submodule/a:foobar + submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + a:foobar + submodule/a:foobar + submodule/sub/a:foobar + EOF + + git grep -e "bar" --and -e "foo" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + b/b:bar + EOF + + git grep -e "bar" --and --not -e "foo" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'basic grep tree' ' + cat >expect <<-\EOF && + HEAD:a:foobar + HEAD:b/b:bar + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^' ' + cat >expect <<-\EOF && + HEAD^:a:foobar + HEAD^:b/b:bar + HEAD^:submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^^' ' + cat >expect <<-\EOF && + HEAD^^:a:foobar + HEAD^^:b/b:bar + EOF + + git grep -e "bar" --recurse-submodules HEAD^^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/sub/a:foobar + EOF + + git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual && + test_cmp expect actual +' + +test_expect_success !MINGW 'grep recurse submodule colon in name' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "foobar" >"parent/fi:le" && + git -C parent add "fi:le" && + git -C parent commit -m "add fi:le" && + + git init "su:b" && + test_when_finished "rm -rf su:b" && + echo "foobar" >"su:b/fi:le" && + git -C "su:b" add "fi:le" && + git -C "su:b" commit -m "add fi:le" && + + git -C parent submodule add "../su:b" "su:b" && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + fi:le:foobar + su:b/fi:le:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD:fi:le:foobar + HEAD:su:b/fi:le:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'grep history with moved submoules' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "foobar" >parent/file && + git -C parent add file && + git -C parent commit -m "add file" && + + git init sub && + test_when_finished "rm -rf sub" && + echo "foobar" >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + + git -C parent submodule add ../sub dir/sub && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + dir/sub/file:foobar + file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + git -C parent mv dir/sub sub-moved && + git -C parent commit -m "moved submodule" && + + cat >expect <<-\EOF && + file:foobar + sub-moved/file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD^:dir/sub/file:foobar + HEAD^:file:foobar + EOF + git -C parent grep -e "foobar" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + +test_incompatible_with_recurse_submodules () +{ + test_expect_success "--recurse-submodules and $1 are incompatible" " + test_must_fail git grep -e. --recurse-submodules $1 2>actual && + test_i18ngrep 'not supported with --recurse-submodules' actual + " +} + +test_incompatible_with_recurse_submodules --untracked +test_incompatible_with_recurse_submodules --no-index + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 3dc4a3454d..0f398dd160 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -50,7 +50,7 @@ test_no_confirm () { --smtp-server="$(pwd)/fake.sendmail" \ $@ \ $patches >stdout && - test_must_fail grep "Send this email" stdout && + ! grep "Send this email" stdout && >no_confirm_okay } diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index 69a675052e..044f65e916 100755 --- a/t/t9117-git-svn-init-clone.sh +++ b/t/t9117-git-svn-init-clone.sh @@ -55,7 +55,7 @@ test_expect_success 'clone to target directory with --stdlayout' ' test_expect_success 'init without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn init "$svnrepo"/project/trunk trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -63,7 +63,7 @@ test_expect_success 'init without -s/-T/-b/-t does not warn' ' test_expect_success 'clone without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn clone "$svnrepo"/project/trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -86,7 +86,7 @@ EOF test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn init -s "$svnrepo"/project project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -95,7 +95,7 @@ test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn clone -s "$svnrepo"/project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -104,7 +104,7 @@ test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn init -s "$svnrepo"/project project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning @@ -113,7 +113,7 @@ test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test_expect_success 'clone with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn clone -s "$svnrepo"/project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 4d935222e9..4849edc4ef 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -277,6 +277,26 @@ test_expect_success 'submit from detached head' ' ) ' +test_expect_success 'submit from worktree' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git worktree add ../worktree-test + ) && + ( + cd "$git/../worktree-test" && + test_commit "worktree-commit" && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file worktree-commit.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index 254d428b73..1ab76c4246 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -269,6 +269,38 @@ test_expect_success 'submit works with two branches' ' ) ' +test_expect_success 'use --git-dir option and GIT_DIR' ' + test_when_finished cleanup_git && + git p4 clone //depot --destination="$git" && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit first-change && + git p4 submit --git-dir "$git" + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file first-change.t && + echo "cli_file" >cli_file.t && + p4 add cli_file.t && + p4 submit -d "cli change" + ) && + (git --git-dir "$git" p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file.t && + ( + cd "$cli" && + echo "cli_file2" >cli_file2.t && + p4 add cli_file2.t && + p4 submit -d "cli change2" + ) && + (GIT_DIR="$git" git p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file2.t +' + + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 593152817d..e37239e657 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -413,6 +413,75 @@ test_expect_success 'submit --prepare-p4-only' ' ) ' +test_expect_success 'submit --shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit "shelveme1" && + git p4 submit --origin=HEAD^ && + + echo 654321 >shelveme2.t && + echo 123456 >>shelveme1.t && + git add shelveme* && + git commit -m"shelvetest" && + git p4 submit --shelve --origin=HEAD^ && + + test_path_is_file shelveme1.t && + test_path_is_file shelveme2.t + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 describe -S $change | grep shelveme2 && + p4 describe -S $change | grep 123456 && + test_path_is_file shelveme1.t && + test_path_is_missing shelveme2.t + ) +' + +# Update an existing shelved changelist + +test_expect_success 'submit --update-shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit "test-update-shelved-change" && + git p4 submit --origin=HEAD^ --shelve && + + shelf_cl=$(p4 -G changes -s shelved -m 1 |\ + marshal_dump change) && + test -n $shelf_cl && + echo "updating shelved change list $shelf_cl" && + + echo "updated-line" >>shelf.t && + echo added-file.t >added-file.t && + git add shelf.t added-file.t && + git rm -f test-update-shelved-change.t && + git commit --amend -C HEAD && + git show --stat HEAD && + git p4 submit -v --origin HEAD^ --update-shelve $shelf_cl && + echo "done git p4 submit" + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 unshelve -c $change -s $change && + grep -q updated-line shelf.t && + p4 describe -S $change | grep added-file.t && + test_path_is_missing test-update-shelved-change.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh index 0fe2312807..bda222aa02 100755 --- a/t/t9813-git-p4-preserve-users.sh +++ b/t/t9813-git-p4-preserve-users.sh @@ -118,21 +118,21 @@ test_expect_success 'not preserving user with mixed authorship' ' make_change_by_user usernamefile3 Derek derek@example.com && P4EDITOR=cat P4USER=alice P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && - git p4 commit |\ - grep "git author derek@example.com does not match" && + git p4 commit >actual && + grep "git author derek@example.com does not match" actual && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - grep "git author charlie@example.com does not match" && + git p4 commit >actual && + grep "git author charlie@example.com does not match" actual && make_change_by_user usernamefile3 alice alice@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && git config git-p4.skipUserNameCheck true && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && p4_check_commit_author usernamefile3 alice ) diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index c89992cf95..e7e0268e98 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -141,7 +141,7 @@ test_expect_success 'detect copies' ' git diff-tree -r -C HEAD && git p4 submit && p4 filelog //depot/file8 && - p4 filelog //depot/file8 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file8 | grep -q "branch from" && echo "file9" >>file2 && git commit -a -m "Differentiate file2" && @@ -154,7 +154,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies true && git p4 submit && p4 filelog //depot/file9 && - p4 filelog //depot/file9 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file9 | grep -q "branch from" && echo "file10" >>file2 && git commit -a -m "Differentiate file2" && @@ -202,7 +202,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies $(($level + 2)) && git p4 submit && p4 filelog //depot/file12 && - p4 filelog //depot/file12 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file12 | grep -q "branch from" && echo "file13" >>file2 && git commit -a -m "Differentiate file2" && diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index 734b8db4cb..ed80ca858c 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -83,9 +83,9 @@ test_expect_success 'Store files in LFS based on size (>24 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /file4.bin filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -111,7 +111,7 @@ test_expect_success 'Store files in LFS based on size (>25 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file4.bin filter=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -137,7 +137,7 @@ test_expect_success 'Store files in LFS based on extension (dat)' ' # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text + *.dat filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -165,8 +165,8 @@ test_expect_success 'Store files in LFS based on size (>25 bytes) and extension # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text - /file4.bin filter=lfs -text + *.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -201,8 +201,8 @@ test_expect_success 'Remove file from repo and store files in LFS based on size # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -239,8 +239,8 @@ test_expect_success 'Add .gitattributes and store files in LFS based on size (>2 # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -280,7 +280,7 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr # # Git LFS (see https://git-lfs.github.com/) # - /file6.bin filter=lfs -text + /file6.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index adab7f51f4..bd357704cc 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -157,16 +157,21 @@ debug () { GIT_TEST_GDB=1 "$@" } -# Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]" +# Call test_commit with the arguments +# [-C <directory>] <message> [<file> [<contents> [<tag>]]]" # # This will commit a file with the given contents and the given commit # message, and tag the resulting commit with the given tag name. # # <file>, <contents>, and <tag> all default to <message>. +# +# If the first argument is "-C", the second argument is used as a path for +# the git invocations. test_commit () { notick= && signoff= && + indir= && while test $# != 0 do case "$1" in @@ -176,21 +181,26 @@ test_commit () { --signoff) signoff="$1" ;; + -C) + indir="$2" + shift + ;; *) break ;; esac shift done && + indir=${indir:+"$indir"/} && file=${2:-"$1.t"} && - echo "${3-$1}" > "$file" && - git add "$file" && + echo "${3-$1}" > "$indir$file" && + git ${indir:+ -C "$indir"} add "$file" && if test -z "$notick" then test_tick fi && - git commit $signoff -m "$1" && - git tag "${4:-$1}" + git ${indir:+ -C "$indir"} commit $signoff -m "$1" && + git ${indir:+ -C "$indir"} tag "${4:-$1}" } # Call test_merge with the arguments "<message> <commit>", where <commit> diff --git a/t/test-lib.sh b/t/test-lib.sh index cde7fc7fcf..86d77c16dd 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -966,7 +966,8 @@ yes () { } # Fix some commands on Windows -case $(uname -s) in +uname_s=$(uname -s) +case $uname_s in *MINGW*) # Windows has its own (incompatible) sort and find sort () { @@ -1141,6 +1142,7 @@ test_lazy_prereq SANITY ' return $status ' +test FreeBSD != $uname_s || GIT_UNZIP=${GIT_UNZIP:-/usr/local/bin/unzip} GIT_UNZIP=${GIT_UNZIP:-unzip} test_lazy_prereq UNZIP ' "$GIT_UNZIP" -v @@ -3,6 +3,7 @@ #include "commit.h" #include "tree.h" #include "blob.h" +#include "gpg-interface.h" const char *tag_type = "tag"; @@ -24,7 +25,9 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) ret = check_signature(buf, payload_size, buf + payload_size, size - payload_size, &sigc); - print_signature_buffer(&sigc, flags); + + if (!(flags & GPG_VERIFY_OMIT_STATUS)) + print_signature_buffer(&sigc, flags); signature_check_clear(&sigc); return ret; @@ -46,6 +46,8 @@ static LIST_HEAD(conf_head); static char *separators = ":"; +static int configured; + #define TRAILER_ARG_STRING "$ARG" static const char *git_generated_prefixes[] = { @@ -102,12 +104,12 @@ static int same_trailer(struct trailer_item *a, struct arg_item *b) return same_token(a, b) && same_value(a, b); } -static inline int contains_only_spaces(const char *str) +static inline int is_blank_line(const char *str) { const char *s = str; - while (*s && isspace(*s)) + while (*s && *s != '\n' && isspace(*s)) s++; - return !*s; + return !*s || *s == '\n'; } static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b) @@ -546,6 +548,17 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) return 0; } +static void ensure_configured(void) +{ + if (configured) + return; + + /* Default config must be setup first */ + git_config(git_trailer_default_config, NULL); + git_config(git_trailer_config, NULL); + configured = 1; +} + static const char *token_from_item(struct arg_item *item, char *tok) { if (item->conf.key) @@ -563,15 +576,32 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le } /* - * Return the location of the first separator in line, or -1 if there is no - * separator. + * If the given line is of the form + * "<token><optional whitespace><separator>..." or "<separator>...", return the + * location of the separator. Otherwise, return -1. The optional whitespace + * is allowed there primarily to allow things like "Bug #43" where <token> is + * "Bug" and <separator> is "#". + * + * The separator-starts-line case (in which this function returns 0) is + * distinguished from the non-well-formed-line case (in which this function + * returns -1) because some callers of this function need such a distinction. */ static int find_separator(const char *line, const char *separators) { - int loc = strcspn(line, separators); - if (!line[loc]) - return -1; - return loc; + int whitespace_found = 0; + const char *c; + for (c = line; *c; c++) { + if (strchr(separators, *c)) + return c - line; + if (!whitespace_found && (isalnum(*c) || *c == '-')) + continue; + if (c != line && (*c == ' ' || *c == '\t')) { + whitespace_found = 1; + continue; + } + break; + } + return -1; } /* @@ -685,51 +715,71 @@ static void process_command_line_args(struct list_head *arg_head, free(cl_separators); } -static struct strbuf **read_input_file(const char *file) +static void read_input_file(struct strbuf *sb, const char *file) { - struct strbuf **lines; - struct strbuf sb = STRBUF_INIT; - if (file) { - if (strbuf_read_file(&sb, file, 0) < 0) + if (strbuf_read_file(sb, file, 0) < 0) die_errno(_("could not read input file '%s'"), file); } else { - if (strbuf_read(&sb, fileno(stdin), 0) < 0) + if (strbuf_read(sb, fileno(stdin), 0) < 0) die_errno(_("could not read from stdin")); } +} - lines = strbuf_split(&sb, '\n'); +static const char *next_line(const char *str) +{ + const char *nl = strchrnul(str, '\n'); + return nl + !!*nl; +} - strbuf_release(&sb); +/* + * Return the position of the start of the last line. If len is 0, return -1. + */ +static int last_line(const char *buf, size_t len) +{ + int i; + if (len == 0) + return -1; + if (len == 1) + return 0; + /* + * Skip the last character (in addition to the null terminator), + * because if the last character is a newline, it is considered as part + * of the last line anyway. + */ + i = len - 2; - return lines; + for (; i >= 0; i--) { + if (buf[i] == '\n') + return i + 1; + } + return 0; } /* - * Return the (0 based) index of the start of the patch or the line - * count if there is no patch in the message. + * Return the position of the start of the patch or the length of str if there + * is no patch in the message. */ -static int find_patch_start(struct strbuf **lines, int count) +static int find_patch_start(const char *str) { - int i; + const char *s; - /* Get the start of the patch part if any */ - for (i = 0; i < count; i++) { - if (starts_with(lines[i]->buf, "---")) - return i; + for (s = str; *s; s = next_line(s)) { + if (starts_with(s, "---")) + return s - str; } - return count; + return s - str; } /* - * Return the (0 based) index of the first trailer line or count if - * there are no trailers. Trailers are searched only in the lines from - * index (count - 1) down to index 0. + * Return the position of the first trailer line or len if there are no + * trailers. */ -static int find_trailer_start(struct strbuf **lines, int count) +static int find_trailer_start(const char *buf, size_t len) { - int start, end_of_title, only_spaces = 1; + const char *s; + int end_of_title, l, only_spaces = 1; int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; /* * Number of possible continuation lines encountered. This will be @@ -741,13 +791,13 @@ static int find_trailer_start(struct strbuf **lines, int count) int possible_continuation_lines = 0; /* The first paragraph is the title and cannot be trailers */ - for (start = 0; start < count; start++) { - if (lines[start]->buf[0] == comment_line_char) + for (s = buf; s < buf + len; s = next_line(s)) { + if (s[0] == comment_line_char) continue; - if (contains_only_spaces(lines[start]->buf)) + if (is_blank_line(s)) break; } - end_of_title = start; + end_of_title = s - buf; /* * Get the start of the trailers by looking starting from the end for a @@ -755,30 +805,33 @@ static int find_trailer_start(struct strbuf **lines, int count) * trailers, or (ii) contains at least one Git-generated trailer and * consists of at least 25% trailers. */ - for (start = count - 1; start >= end_of_title; start--) { + for (l = last_line(buf, len); + l >= end_of_title; + l = last_line(buf, l)) { + const char *bol = buf + l; const char **p; int separator_pos; - if (lines[start]->buf[0] == comment_line_char) { + if (bol[0] == comment_line_char) { non_trailer_lines += possible_continuation_lines; possible_continuation_lines = 0; continue; } - if (contains_only_spaces(lines[start]->buf)) { + if (is_blank_line(bol)) { if (only_spaces) continue; non_trailer_lines += possible_continuation_lines; if (recognized_prefix && trailer_lines * 3 >= non_trailer_lines) - return start + 1; - if (trailer_lines && !non_trailer_lines) - return start + 1; - return count; + return next_line(bol) - buf; + else if (trailer_lines && !non_trailer_lines) + return next_line(bol) - buf; + return len; } only_spaces = 0; for (p = git_generated_prefixes; *p; p++) { - if (starts_with(lines[start]->buf, *p)) { + if (starts_with(bol, *p)) { trailer_lines++; possible_continuation_lines = 0; recognized_prefix = 1; @@ -786,8 +839,8 @@ static int find_trailer_start(struct strbuf **lines, int count) } } - separator_pos = find_separator(lines[start]->buf, separators); - if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { + separator_pos = find_separator(bol, separators); + if (separator_pos >= 1 && !isspace(bol[0])) { struct list_head *pos; trailer_lines++; @@ -797,13 +850,13 @@ static int find_trailer_start(struct strbuf **lines, int count) list_for_each(pos, &conf_head) { struct arg_item *item; item = list_entry(pos, struct arg_item, list); - if (token_matches_item(lines[start]->buf, item, + if (token_matches_item(bol, item, separator_pos)) { recognized_prefix = 1; break; } } - } else if (isspace(lines[start]->buf[0])) + } else if (isspace(bol[0])) possible_continuation_lines++; else { non_trailer_lines++; @@ -814,97 +867,64 @@ continue_outer_loop: ; } - return count; -} - -/* Get the index of the end of the trailers */ -static int find_trailer_end(struct strbuf **lines, int patch_start) -{ - struct strbuf sb = STRBUF_INIT; - int i, ignore_bytes; - - for (i = 0; i < patch_start; i++) - strbuf_addbuf(&sb, lines[i]); - ignore_bytes = ignore_non_trailer(&sb); - strbuf_release(&sb); - for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--) - ignore_bytes -= lines[i]->len; - - return i + 1; + return len; } -static int has_blank_line_before(struct strbuf **lines, int start) +/* Return the position of the end of the trailers. */ +static int find_trailer_end(const char *buf, size_t len) { - for (;start >= 0; start--) { - if (lines[start]->buf[0] == comment_line_char) - continue; - return contains_only_spaces(lines[start]->buf); - } - return 0; + return len - ignore_non_trailer(buf, len); } -static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end) +static int ends_with_blank_line(const char *buf, size_t len) { - int i; - for (i = start; lines[i] && i < end; i++) - fprintf(outfile, "%s", lines[i]->buf); + int ll = last_line(buf, len); + if (ll < 0) + return 0; + return is_blank_line(buf + ll); } static int process_input_file(FILE *outfile, - struct strbuf **lines, + const char *str, struct list_head *head) { - int count = 0; - int patch_start, trailer_start, trailer_end, i; + struct trailer_info info; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; - struct trailer_item *last = NULL; - - /* Get the line count */ - while (lines[count]) - count++; + int i; - patch_start = find_patch_start(lines, count); - trailer_end = find_trailer_end(lines, patch_start); - trailer_start = find_trailer_start(lines, trailer_end); + trailer_info_get(&info, str); /* Print lines before the trailers as is */ - print_lines(outfile, lines, 0, trailer_start); + fwrite(str, 1, info.trailer_start - str, outfile); - if (!has_blank_line_before(lines, trailer_start - 1)) + if (!info.blank_line_before_trailer) fprintf(outfile, "\n"); - /* Parse trailer lines */ - for (i = trailer_start; i < trailer_end; i++) { + for (i = 0; i < info.trailer_nr; i++) { int separator_pos; - if (lines[i]->buf[0] == comment_line_char) + char *trailer = info.trailers[i]; + if (trailer[0] == comment_line_char) continue; - if (last && isspace(lines[i]->buf[0])) { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf); - strbuf_strip_suffix(&sb, "\n"); - free(last->value); - last->value = strbuf_detach(&sb, NULL); - continue; - } - separator_pos = find_separator(lines[i]->buf, separators); + separator_pos = find_separator(trailer, separators); if (separator_pos >= 1) { - parse_trailer(&tok, &val, NULL, lines[i]->buf, + parse_trailer(&tok, &val, NULL, trailer, separator_pos); - last = add_trailer_item(head, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); } else { - strbuf_addbuf(&val, lines[i]); + strbuf_addstr(&val, trailer); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, NULL, strbuf_detach(&val, NULL)); - last = NULL; } } - return trailer_end; + trailer_info_release(&info); + + return info.trailer_end - str; } static void free_all(struct list_head *head) @@ -951,21 +971,19 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str { LIST_HEAD(head); LIST_HEAD(arg_head); - struct strbuf **lines; + struct strbuf sb = STRBUF_INIT; int trailer_end; FILE *outfile = stdout; - /* Default config must be setup first */ - git_config(git_trailer_default_config, NULL); - git_config(git_trailer_config, NULL); + ensure_configured(); - lines = read_input_file(file); + read_input_file(&sb, file); if (in_place) outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, lines, &head); + trailer_end = process_input_file(outfile, sb.buf, &head); process_command_line_args(&arg_head, trailers); @@ -976,11 +994,62 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str free_all(&head); /* Print the lines after the trailers as is */ - print_lines(outfile, lines, trailer_end, INT_MAX); + fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); if (in_place) if (rename_tempfile(&trailers_tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); - strbuf_list_free(lines); + strbuf_release(&sb); +} + +void trailer_info_get(struct trailer_info *info, const char *str) +{ + int patch_start, trailer_end, trailer_start; + struct strbuf **trailer_lines, **ptr; + char **trailer_strings = NULL; + size_t nr = 0, alloc = 0; + char **last = NULL; + + ensure_configured(); + + patch_start = find_patch_start(str); + trailer_end = find_trailer_end(str, patch_start); + trailer_start = find_trailer_start(str, trailer_end); + + trailer_lines = strbuf_split_buf(str + trailer_start, + trailer_end - trailer_start, + '\n', + 0); + for (ptr = trailer_lines; *ptr; ptr++) { + if (last && isspace((*ptr)->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_attach(&sb, *last, strlen(*last), strlen(*last)); + strbuf_addbuf(&sb, *ptr); + *last = strbuf_detach(&sb, NULL); + continue; + } + ALLOC_GROW(trailer_strings, nr + 1, alloc); + trailer_strings[nr] = strbuf_detach(*ptr, NULL); + last = find_separator(trailer_strings[nr], separators) >= 1 + ? &trailer_strings[nr] + : NULL; + nr++; + } + strbuf_list_free(trailer_lines); + + info->blank_line_before_trailer = ends_with_blank_line(str, + trailer_start); + info->trailer_start = str + trailer_start; + info->trailer_end = str + trailer_end; + info->trailers = trailer_strings; + info->trailer_nr = nr; +} + +void trailer_info_release(struct trailer_info *info) +{ + int i; + for (i = 0; i < info->trailer_nr; i++) + free(info->trailers[i]); + free(info->trailers); } @@ -1,7 +1,32 @@ #ifndef TRAILER_H #define TRAILER_H +struct trailer_info { + /* + * True if there is a blank line before the location pointed to by + * trailer_start. + */ + int blank_line_before_trailer; + + /* + * Pointers to the start and end of the trailer block found. If there + * is no trailer block found, these 2 pointers point to the end of the + * input string. + */ + const char *trailer_start, *trailer_end; + + /* + * Array of trailers found. + */ + char **trailers; + size_t trailer_nr; +}; + void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers); +void trailer_info_get(struct trailer_info *info, const char *str); + +void trailer_info_release(struct trailer_info *info); + #endif /* TRAILER_H */ diff --git a/transport.c b/transport.c index 04e5d6623e..d72e089484 100644 --- a/transport.c +++ b/transport.c @@ -664,21 +664,89 @@ static const struct string_list *protocol_whitelist(void) return enabled ? &allowed : NULL; } -int is_transport_allowed(const char *type) +enum protocol_allow_config { + PROTOCOL_ALLOW_NEVER = 0, + PROTOCOL_ALLOW_USER_ONLY, + PROTOCOL_ALLOW_ALWAYS +}; + +static enum protocol_allow_config parse_protocol_config(const char *key, + const char *value) { - const struct string_list *allowed = protocol_whitelist(); - return !allowed || string_list_has_string(allowed, type); + if (!strcasecmp(value, "always")) + return PROTOCOL_ALLOW_ALWAYS; + else if (!strcasecmp(value, "never")) + return PROTOCOL_ALLOW_NEVER; + else if (!strcasecmp(value, "user")) + return PROTOCOL_ALLOW_USER_ONLY; + + die("unknown value for config '%s': %s", key, value); } -void transport_check_allowed(const char *type) +static enum protocol_allow_config get_protocol_config(const char *type) { - if (!is_transport_allowed(type)) - die("transport '%s' not allowed", type); + char *key = xstrfmt("protocol.%s.allow", type); + char *value; + + /* first check the per-protocol config */ + if (!git_config_get_string(key, &value)) { + enum protocol_allow_config ret = + parse_protocol_config(key, value); + free(key); + free(value); + return ret; + } + free(key); + + /* if defined, fallback to user-defined default for unknown protocols */ + if (!git_config_get_string("protocol.allow", &value)) { + enum protocol_allow_config ret = + parse_protocol_config("protocol.allow", value); + free(value); + return ret; + } + + /* fallback to built-in defaults */ + /* known safe */ + if (!strcmp(type, "http") || + !strcmp(type, "https") || + !strcmp(type, "git") || + !strcmp(type, "ssh") || + !strcmp(type, "file")) + return PROTOCOL_ALLOW_ALWAYS; + + /* known scary; err on the side of caution */ + if (!strcmp(type, "ext")) + return PROTOCOL_ALLOW_NEVER; + + /* unknown; by default let them be used only directly by the user */ + return PROTOCOL_ALLOW_USER_ONLY; } -int transport_restrict_protocols(void) +int is_transport_allowed(const char *type, int from_user) { - return !!protocol_whitelist(); + const struct string_list *whitelist = protocol_whitelist(); + if (whitelist) + return string_list_has_string(whitelist, type); + + switch (get_protocol_config(type)) { + case PROTOCOL_ALLOW_ALWAYS: + return 1; + case PROTOCOL_ALLOW_NEVER: + return 0; + case PROTOCOL_ALLOW_USER_ONLY: + if (from_user < 0) + from_user = git_env_bool("GIT_PROTOCOL_FROM_USER", 1); + return from_user; + } + + die("BUG: invalid protocol_allow_config type"); +} + +void transport_check_allowed(const char *type) +{ + if (!is_transport_allowed(type, -1)) + die("transport '%s' not allowed", type); } struct transport *transport_get(struct remote *remote, const char *url) @@ -947,7 +1015,9 @@ int transport_push(struct transport *transport, if (run_pre_push_hook(transport, remote_refs)) return -1; - if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) { + if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && + !is_bare_repository()) { struct ref *ref = remote_refs; struct sha1_array commits = SHA1_ARRAY_INIT; @@ -965,7 +1035,8 @@ int transport_push(struct transport *transport, } if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) || - ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && + ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_ONLY)) && !pretend)) && !is_bare_repository()) { struct ref *ref = remote_refs; struct string_list needs_pushing = STRING_LIST_INIT_DUP; @@ -984,7 +1055,10 @@ int transport_push(struct transport *transport, sha1_array_clear(&commits); } - push_ret = transport->push_refs(transport, remote_refs, flags); + if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY)) + push_ret = transport->push_refs(transport, remote_refs, flags); + else + push_ret = 0; err = push_had_errors(remote_refs); ret = push_ret | err; @@ -996,7 +1070,8 @@ int transport_push(struct transport *transport, if (flags & TRANSPORT_PUSH_SET_UPSTREAM) set_upstreams(transport, remote_refs, pretend); - if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { + if (!(flags & (TRANSPORT_PUSH_DRY_RUN | + TRANSPORT_RECURSE_SUBMODULES_ONLY))) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) transport_update_tracking_ref(transport->remote, ref, verbose); @@ -1146,7 +1221,7 @@ static int refs_from_alternate_cb(struct alternate_object_database *e, const struct ref *extra; struct alternate_refs_data *cb = data; - other = xstrdup(real_path(e->path)); + other = real_pathdup(e->path); len = strlen(other); while (other[len-1] == '/') diff --git a/transport.h b/transport.h index b8e4ee8099..e597b31b38 100644 --- a/transport.h +++ b/transport.h @@ -131,21 +131,22 @@ struct transport { enum transport_family family; }; -#define TRANSPORT_PUSH_ALL 1 -#define TRANSPORT_PUSH_FORCE 2 -#define TRANSPORT_PUSH_DRY_RUN 4 -#define TRANSPORT_PUSH_MIRROR 8 -#define TRANSPORT_PUSH_PORCELAIN 16 -#define TRANSPORT_PUSH_SET_UPSTREAM 32 -#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64 -#define TRANSPORT_PUSH_PRUNE 128 -#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256 -#define TRANSPORT_PUSH_NO_HOOK 512 -#define TRANSPORT_PUSH_FOLLOW_TAGS 1024 -#define TRANSPORT_PUSH_CERT_ALWAYS 2048 -#define TRANSPORT_PUSH_CERT_IF_ASKED 4096 -#define TRANSPORT_PUSH_ATOMIC 8192 -#define TRANSPORT_PUSH_OPTIONS 16384 +#define TRANSPORT_PUSH_ALL (1<<0) +#define TRANSPORT_PUSH_FORCE (1<<1) +#define TRANSPORT_PUSH_DRY_RUN (1<<2) +#define TRANSPORT_PUSH_MIRROR (1<<3) +#define TRANSPORT_PUSH_PORCELAIN (1<<4) +#define TRANSPORT_PUSH_SET_UPSTREAM (1<<5) +#define TRANSPORT_RECURSE_SUBMODULES_CHECK (1<<6) +#define TRANSPORT_PUSH_PRUNE (1<<7) +#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND (1<<8) +#define TRANSPORT_PUSH_NO_HOOK (1<<9) +#define TRANSPORT_PUSH_FOLLOW_TAGS (1<<10) +#define TRANSPORT_PUSH_CERT_ALWAYS (1<<11) +#define TRANSPORT_PUSH_CERT_IF_ASKED (1<<12) +#define TRANSPORT_PUSH_ATOMIC (1<<13) +#define TRANSPORT_PUSH_OPTIONS (1<<14) +#define TRANSPORT_RECURSE_SUBMODULES_ONLY (1<<15) extern int transport_summary_width(const struct ref *refs); @@ -153,10 +154,17 @@ extern int transport_summary_width(const struct ref *refs); struct transport *transport_get(struct remote *, const char *); /* - * Check whether a transport is allowed by the environment. Type should - * generally be the URL scheme, as described in Documentation/git.txt + * Check whether a transport is allowed by the environment. + * + * Type should generally be the URL scheme, as described in + * Documentation/git.txt + * + * from_user specifies if the transport was given by the user. If unknown pass + * a -1 to read from the environment to determine if the transport was given by + * the user. + * */ -int is_transport_allowed(const char *type); +int is_transport_allowed(const char *type, int from_user); /* * Check whether a transport is allowed by the environment, @@ -164,12 +172,6 @@ int is_transport_allowed(const char *type); */ void transport_check_allowed(const char *type); -/* - * Returns true if the user has attempted to turn on protocol - * restrictions at all. - */ -int transport_restrict_protocols(void); - /* Transport options which apply to git:// and scp-style URLs */ /* The program to use on the remote side to send a pack */ diff --git a/tree-walk.c b/tree-walk.c index 828f4356be..ff77605680 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -1004,6 +1004,19 @@ static enum interesting do_match(const struct name_entry *entry, */ if (ps->recursive && S_ISDIR(entry->mode)) return entry_interesting; + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match + baselen, + entry->path, + item->nowildcard_len - baselen)) + return entry_interesting; } continue; @@ -1040,6 +1053,21 @@ match_wildcards: strbuf_setlen(base, base_offset + baselen); return entry_interesting; } + + /* + * When matching against submodules with + * wildcard characters, ensure that the entry + * at least matches up to the first wild + * character. More accurate matching can then + * be performed in the submodule itself. + */ + if (ps->recursive && S_ISGITLINK(entry->mode) && + !ps_strncmp(item, match, base->buf + base_offset, + item->nowildcard_len)) { + strbuf_setlen(base, base_offset + baselen); + return entry_interesting; + } + strbuf_setlen(base, base_offset + baselen); /* diff --git a/unpack-trees.c b/unpack-trees.c index 7a6df99d10..129d49ff45 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -218,29 +218,42 @@ static void unlink_entry(const struct cache_entry *ce) schedule_dir_for_removal(ce->name, ce_namelen(ce)); } -static int check_updates(struct unpack_trees_options *o, - const struct checkout *state) +static struct progress *get_progress(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; + struct index_state *index = &o->result; + + if (!o->update || !o->verbose_update) + return NULL; + + for (; cnt < index->cache_nr; cnt++) { + const struct cache_entry *ce = index->cache[cnt]; + if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) + total++; + } + + return start_progress_delay(_("Checking out files"), + total, 50, 1); +} + +static int check_updates(struct unpack_trees_options *o) +{ + unsigned cnt = 0; + int errs = 0; struct progress *progress = NULL; struct index_state *index = &o->result; + struct checkout state = CHECKOUT_INIT; int i; - int errs = 0; - if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < index->cache_nr; cnt++) { - const struct cache_entry *ce = index->cache[cnt]; - if (ce->ce_flags & (CE_UPDATE | CE_WT_REMOVE)) - total++; - } + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + state.istate = index; - progress = start_progress_delay(_("Checking out files"), - total, 50, 1); - cnt = 0; - } + progress = get_progress(o); if (o->update) - git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result); + git_attr_set_direction(GIT_ATTR_CHECKOUT, index); for (i = 0; i < index->cache_nr; i++) { const struct cache_entry *ce = index->cache[i]; @@ -248,10 +261,9 @@ static int check_updates(struct unpack_trees_options *o, display_progress(progress, ++cnt); if (o->update && !o->dry_run) unlink_entry(ce); - continue; } } - remove_marked_cache_entries(&o->result); + remove_marked_cache_entries(index); remove_scheduled_dirs(); for (i = 0; i < index->cache_nr; i++) { @@ -264,7 +276,7 @@ static int check_updates(struct unpack_trees_options *o, display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; if (o->update && !o->dry_run) { - errs |= checkout_entry(ce, state, NULL); + errs |= checkout_entry(ce, &state, NULL); } } } @@ -1094,14 +1106,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options int i, ret; static struct cache_entry *dfc; struct exclude_list el; - struct checkout state = CHECKOUT_INIT; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); - state.force = 1; - state.quiet = 1; - state.refresh_cache = 1; - state.istate = &o->result; memset(&el, 0, sizeof(el)); if (!core_apply_sparse_checkout || !o->update) @@ -1238,7 +1245,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } o->src_index = NULL; - ret = check_updates(o, &state) ? (-2) : 0; + ret = check_updates(o) ? (-2) : 0; if (o->dst_index) { if (!ret) { if (!o->result.cache_tree) @@ -7,21 +7,19 @@ #include "cache.h" static FILE *error_handle; -static int tweaked_error_buffering; void vreportf(const char *prefix, const char *err, va_list params) { + char msg[4096]; FILE *fh = error_handle ? error_handle : stderr; + char *p; - fflush(fh); - if (!tweaked_error_buffering) { - setvbuf(fh, NULL, _IOLBF, 0); - tweaked_error_buffering = 1; + vsnprintf(msg, sizeof(msg), err, params); + for (p = msg; *p; p++) { + if (iscntrl(*p) && *p != '\t' && *p != '\n') + *p = '?'; } - - fputs(prefix, fh); - vfprintf(fh, err, params); - fputc('\n', fh); + fprintf(fh, "%s%s\n", prefix, msg); } static NORETURN void usage_builtin(const char *err, va_list params) @@ -93,7 +91,6 @@ void set_die_is_recursing_routine(int (*routine)(void)) void set_error_handle(FILE *fh) { error_handle = fh; - tweaked_error_buffering = 0; } void NORETURN usagef(const char *err, ...) diff --git a/versioncmp.c b/versioncmp.c index 80bfd109fa..9f81dc1062 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -24,42 +24,83 @@ static const struct string_list *prereleases; static int initialized; +struct suffix_match { + int conf_pos; + int start; + int len; +}; + +static void find_better_matching_suffix(const char *tagname, const char *suffix, + int suffix_len, int start, int conf_pos, + struct suffix_match *match) +{ + /* + * A better match either starts earlier or starts at the same offset + * but is longer. + */ + int end = match->len < suffix_len ? match->start : match->start-1; + int i; + for (i = start; i <= end; i++) + if (starts_with(tagname + i, suffix)) { + match->conf_pos = conf_pos; + match->start = i; + match->len = suffix_len; + break; + } +} + /* - * p1 and p2 point to the first different character in two strings. If - * either p1 or p2 starts with a prerelease suffix, it will be forced - * to be on top. - * - * If both p1 and p2 start with (different) suffix, the order is - * determined by config file. + * off is the offset of the first different character in the two strings + * s1 and s2. If either s1 or s2 contains a prerelease suffix containing + * that offset or a suffix ends right before that offset, then that + * string will be forced to be on top. * - * Note that we don't have to deal with the situation when both p1 and - * p2 start with the same suffix because the common part is already - * consumed by the caller. + * If both s1 and s2 contain a (different) suffix around that position, + * their order is determined by the order of those two suffixes in the + * configuration. + * If any of the strings contains more than one different suffixes around + * that position, then that string is sorted according to the contained + * suffix which starts at the earliest offset in that string. + * If more than one different contained suffixes start at that earliest + * offset, then that string is sorted according to the longest of those + * suffixes. * * Return non-zero if *diff contains the return value for versioncmp() */ -static int swap_prereleases(const void *p1_, - const void *p2_, +static int swap_prereleases(const char *s1, + const char *s2, + int off, int *diff) { - const char *p1 = p1_; - const char *p2 = p2_; - int i, i1 = -1, i2 = -1; + int i; + struct suffix_match match1 = { -1, off, -1 }; + struct suffix_match match2 = { -1, off, -1 }; for (i = 0; i < prereleases->nr; i++) { const char *suffix = prereleases->items[i].string; - if (i1 == -1 && starts_with(p1, suffix)) - i1 = i; - if (i2 == -1 && starts_with(p2, suffix)) - i2 = i; + int start, suffix_len = strlen(suffix); + if (suffix_len < off) + start = off - suffix_len; + else + start = 0; + find_better_matching_suffix(s1, suffix, suffix_len, start, + i, &match1); + find_better_matching_suffix(s2, suffix, suffix_len, start, + i, &match2); } - if (i1 == -1 && i2 == -1) + if (match1.conf_pos == -1 && match2.conf_pos == -1) return 0; - if (i1 >= 0 && i2 >= 0) - *diff = i1 - i2; - else if (i1 >= 0) + if (match1.conf_pos == match2.conf_pos) + /* Found the same suffix in both, e.g. "-rc" in "v1.0-rcX" + * and "v1.0-rcY": the caller should decide based on "X" + * and "Y". */ + return 0; + + if (match1.conf_pos >= 0 && match2.conf_pos >= 0) + *diff = match1.conf_pos - match2.conf_pos; + else if (match1.conf_pos >= 0) *diff = -1; - else /* if (i2 >= 0) */ + else /* if (match2.conf_pos >= 0) */ *diff = 1; return 1; } @@ -118,10 +159,18 @@ int versioncmp(const char *s1, const char *s2) } if (!initialized) { + const struct string_list *deprecated_prereleases; initialized = 1; - prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); + prereleases = git_config_get_value_multi("versionsort.suffix"); + deprecated_prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); + if (prereleases) { + if (deprecated_prereleases) + warning("ignoring versionsort.prereleasesuffix because versionsort.suffix is set"); + } else + prereleases = deprecated_prereleases; } - if (prereleases && swap_prereleases(p1 - 1, p2 - 1, &diff)) + if (prereleases && swap_prereleases(s1, s2, (const char *) p1 - s1 - 1, + &diff)) return diff; state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))]; diff --git a/worktree.c b/worktree.c index eb6121263b..d633761575 100644 --- a/worktree.c +++ b/worktree.c @@ -145,7 +145,7 @@ done: static void mark_current_worktree(struct worktree **worktrees) { - char *git_dir = xstrdup(absolute_path(get_git_dir())); + char *git_dir = absolute_pathdup(get_git_dir()); int i; for (i = 0; worktrees[i]; i++) { @@ -255,7 +255,7 @@ struct worktree *find_worktree(struct worktree **list, return wt; arg = prefix_filename(prefix, strlen(prefix), arg); - path = xstrdup(real_path(arg)); + path = real_pathdup(arg); for (; *list; list++) if (!fspathcmp(path, real_path((*list)->path))) break; @@ -380,3 +380,53 @@ const struct worktree *find_shared_symref(const char *symref, return existing; } + +int submodule_uses_worktrees(const char *path) +{ + char *submodule_gitdir; + struct strbuf sb = STRBUF_INIT; + DIR *dir; + struct dirent *d; + int ret = 0; + struct repository_format format; + + submodule_gitdir = git_pathdup_submodule(path, "%s", ""); + if (!submodule_gitdir) + return 0; + + /* The env would be set for the superproject. */ + get_common_dir_noenv(&sb, submodule_gitdir); + + /* + * The check below is only known to be good for repository format + * version 0 at the time of writing this code. + */ + strbuf_addstr(&sb, "/config"); + read_repository_format(&format, sb.buf); + if (format.version != 0) { + strbuf_release(&sb); + return 1; + } + + /* Replace config by worktrees. */ + strbuf_setlen(&sb, sb.len - strlen("config")); + strbuf_addstr(&sb, "worktrees"); + + /* See if there is any file inside the worktrees directory. */ + dir = opendir(sb.buf); + strbuf_release(&sb); + free(submodule_gitdir); + + if (!dir) + return 0; + + while ((d = readdir(dir)) != NULL) { + if (is_dot_or_dotdot(d->d_name)) + continue; + + ret = 1; + break; + } + closedir(dir); + return ret; +} diff --git a/worktree.h b/worktree.h index d59ce1fee8..6bfb985203 100644 --- a/worktree.h +++ b/worktree.h @@ -28,6 +28,11 @@ struct worktree { extern struct worktree **get_worktrees(unsigned flags); /* + * Returns 1 if linked worktrees exist, 0 otherwise. + */ +extern int submodule_uses_worktrees(const char *path); + +/* * Return git dir of the worktree. Note that the path may be relative. * If wt is NULL, git dir of current worktree is returned. */ diff --git a/wt-status.c b/wt-status.c index a2e9d332d8..4dff0b3e21 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1135,14 +1135,17 @@ static void abbrev_sha1_in_line(struct strbuf *line) strbuf_list_free(split); } -static void read_rebase_todolist(const char *fname, struct string_list *lines) +static int read_rebase_todolist(const char *fname, struct string_list *lines) { struct strbuf line = STRBUF_INIT; FILE *f = fopen(git_path("%s", fname), "r"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -1; die_errno("Could not open file %s for reading", git_path("%s", fname)); + } while (!strbuf_getline_lf(&line, f)) { if (line.len && line.buf[0] == comment_line_char) continue; @@ -1152,6 +1155,7 @@ static void read_rebase_todolist(const char *fname, struct string_list *lines) abbrev_sha1_in_line(&line); string_list_append(lines, line.buf); } + return 0; } static void show_rebase_information(struct wt_status *s, @@ -1166,8 +1170,10 @@ static void show_rebase_information(struct wt_status *s, struct string_list yet_to_do = STRING_LIST_INIT_DUP; read_rebase_todolist("rebase-merge/done", &have_done); - read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do); - + if (read_rebase_todolist("rebase-merge/git-rebase-todo", + &yet_to_do)) + status_printf_ln(s, color, + _("git-rebase-todo is missing.")); if (have_done.nr == 0) status_printf_ln(s, color, _("No commands done.")); else { @@ -2258,11 +2264,12 @@ int has_uncommitted_changes(int ignore_submodules) int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int err = 0; + int err = 0, fd; - hold_locked_index(lock_file, 0); + fd = hold_locked_index(lock_file, 0); refresh_cache(REFRESH_QUIET); - update_index_if_able(&the_index, lock_file); + if (0 <= fd) + update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); if (has_unstaged_changes(ignore_submodules)) { diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 8db16d4ae6..b090ad8eac 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -41,8 +41,7 @@ extern "C" { #define XDF_IGNORE_BLANK_LINES (1 << 7) -#define XDF_COMPACTION_HEURISTIC (1 << 8) -#define XDF_INDENT_HEURISTIC (1 << 9) +#define XDF_INDENT_HEURISTIC (1 << 8) #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 760fbb6db7..93a65680a1 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -400,11 +400,6 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int is_blank_line(xrecord_t *rec, long flags) -{ - return xdl_blankline(rec->ptr, rec->size, flags); -} - static int recs_match(xrecord_t *rec1, xrecord_t *rec2, long flags) { return (rec1->ha == rec2->ha && @@ -821,7 +816,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { struct xdlgroup g, go; long earliest_end, end_matching_other; long groupsize; - unsigned int blank_lines; group_init(xdf, &g); group_init(xdfo, &go); @@ -846,13 +840,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { */ end_matching_other = -1; - /* - * Boolean value that records whether there are any blank - * lines that could be made to be the last line of this - * group. - */ - blank_lines = 0; - /* Shift the group backward as much as possible: */ while (!group_slide_up(xdf, &g, flags)) if (group_previous(xdfo, &go)) @@ -869,11 +856,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { /* Now shift the group forward as far as possible: */ while (1) { - if (!blank_lines) - blank_lines = is_blank_line( - xdf->recs[g.end - 1], - flags); - if (group_slide_down(xdf, &g, flags)) break; if (group_next(xdfo, &go)) @@ -906,21 +888,6 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { if (group_previous(xdfo, &go)) xdl_bug("group sync broken sliding to match"); } - } else if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) { - /* - * Compaction heuristic: if it is possible to shift the - * group to make its bottom line a blank line, do so. - * - * As we already shifted the group forward as far as - * possible in the earlier loop, we only need to handle - * backward shifts, not forward ones. - */ - while (!is_blank_line(xdf->recs[g.end - 1], flags)) { - if (group_slide_up(xdf, &g, flags)) - xdl_bug("blank line disappeared"); - if (group_previous(xdfo, &go)) - xdl_bug("group sync broken sliding to blank line"); - } } else if (flags & XDF_INDENT_HEURISTIC) { /* * Indent heuristic: a group of pure add/delete lines diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 027192a1c7..04d7b32e4e 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -264,110 +264,6 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, return ha; } -#ifdef XDL_FAST_HASH - -#define REPEAT_BYTE(x) ((~0ul / 0xff) * (x)) - -#define ONEBYTES REPEAT_BYTE(0x01) -#define NEWLINEBYTES REPEAT_BYTE(0x0a) -#define HIGHBITS REPEAT_BYTE(0x80) - -/* Return the high bit set in the first byte that is a zero */ -static inline unsigned long has_zero(unsigned long a) -{ - return ((a - ONEBYTES) & ~a) & HIGHBITS; -} - -static inline long count_masked_bytes(unsigned long mask) -{ - if (sizeof(long) == 8) { - /* - * Jan Achrenius on G+: microoptimized version of - * the simpler "(mask & ONEBYTES) * ONEBYTES >> 56" - * that works for the bytemasks without having to - * mask them first. - */ - /* - * return mask * 0x0001020304050608 >> 56; - * - * Doing it like this avoids warnings on 32-bit machines. - */ - long a = (REPEAT_BYTE(0x01) / 0xff + 1); - return mask * a >> (sizeof(long) * 7); - } else { - /* Carl Chatfield / Jan Achrenius G+ version for 32-bit */ - /* (000000 0000ff 00ffff ffffff) -> ( 1 1 2 3 ) */ - long a = (0x0ff0001 + mask) >> 23; - /* Fix the 1 for 00 case */ - return a & mask; - } -} - -unsigned long xdl_hash_record(char const **data, char const *top, long flags) -{ - unsigned long hash = 5381; - unsigned long a = 0, mask = 0; - char const *ptr = *data; - char const *end = top - sizeof(unsigned long) + 1; - - if (flags & XDF_WHITESPACE_FLAGS) - return xdl_hash_record_with_whitespace(data, top, flags); - - ptr -= sizeof(unsigned long); - do { - hash += hash << 5; - hash ^= a; - ptr += sizeof(unsigned long); - if (ptr >= end) - break; - a = *(unsigned long *)ptr; - /* Do we have any '\n' bytes in this word? */ - mask = has_zero(a ^ NEWLINEBYTES); - } while (!mask); - - if (ptr >= end) { - /* - * There is only a partial word left at the end of the - * buffer. Because we may work with a memory mapping, - * we have to grab the rest byte by byte instead of - * blindly reading it. - * - * To avoid problems with masking in a signed value, - * we use an unsigned char here. - */ - const char *p; - for (p = top - 1; p >= ptr; p--) - a = (a << 8) + *((const unsigned char *)p); - mask = has_zero(a ^ NEWLINEBYTES); - if (!mask) - /* - * No '\n' found in the partial word. Make a - * mask that matches what we read. - */ - mask = 1UL << (8 * (top - ptr) + 7); - } - - /* The mask *below* the first high bit set */ - mask = (mask - 1) & ~mask; - mask >>= 7; - hash += hash << 5; - hash ^= a & mask; - - /* Advance past the last (possibly partial) word */ - ptr += count_masked_bytes(mask); - - if (ptr < top) { - assert(*ptr == '\n'); - ptr++; - } - - *data = ptr; - - return hash; -} - -#else /* XDL_FAST_HASH */ - unsigned long xdl_hash_record(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; @@ -384,8 +280,6 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { return ha; } -#endif /* XDL_FAST_HASH */ - unsigned int xdl_hashbits(unsigned int size) { unsigned int val = 1, bits = 0; |